"""
This module derives rules with actions from the Rules defined in
grammar.py.  An action here simply is a python expression with
yacc-like access to the values of the constituents on the right
side.
"""

import re
import grammar, symbol


class MultiStack(list):
	"""This is a simple data structure consisting of a number of
	stacks, each of which can push and pop things.  The stacks
	themselves have names (which, for rule execution, simply
	are Symbols).
	"""
	def __str__(self):
		return "[%s]"%(", ".join(map(lambda a: "%s/%s"%a, self)))

	def copy(self):
		return MultiStack(self[:])
	
	def push(self, cat, val):
		self.append((cat, val))
	
	def pop(self, cat):
		for pos in xrange(len(self)-1, -1, -1):
			if self[pos][0]==cat:
				return list.pop(self, pos)[1]


class RuleWithAction(grammar.Rule):
	"""These are constructed with a normal rule, three dashes, and 
	a python expression (the action).  Use $1...$n to access the
	values of the constituents on the right side of the rule within
	this action.
	"""
	def __str__(self):
		actionStr = " --- "+self.action
		return grammar.Rule.__str__(self)+actionStr

	def __hash__(self):
		return hash(str(self))
	
	def __cmp__(self, other):
		return cmp(str(self), str(other))

	def _parse(self, rawRule):
		self.action = "$1"
		ruleAction = rawRule.split("---")
		if len(ruleAction)==2:
			self.action = ruleAction[1].strip()
		else:
			if len(ruleAction)!=1:
				raise ValueError("Too many --- in '%s'"%rawRule)
		grammar.Rule._parse(self, ruleAction[0])

	def _getSymVals(self, currentNonterms):
		"""returns a list of the values of the constituents on the right
		side of the rule.  For Terminals, this is contents of the Terminal,
		for NonTerminals, the value is taken from the MultiStack 
		currentNonterms.  This is done from back to front, such that
		for a rule like S -> AbA the A that was pushed later is assigned
		to the second A.  Within the list, the i-th value corresponds to
		the i-th constituent.  We return representations of the values
		to facilitate easy inclusion into evals.
		"""
		nontermVals = []
		syms = self.getRight()[:]
		syms.reverse()
		for sym in syms:
			if isinstance(sym, symbol.Terminal):
				nontermVals.append(sym.getContent())
			else:
				nontermVals.append(currentNonterms.pop(sym))
		nontermVals.reverse()
		return map(repr, nontermVals)

	def runAction(self, currentNonterms):
		"""returns the value of the action assigned to the rule.  If no
		action was defined, raise an error if the right hand side contains
		more than one symbol, return the value of the single symbol on
		the right hand side otherwise.
		"""
		symVals = self._getSymVals(currentNonterms)
		val = eval(
			re.sub(r"\$(\d+)", 
				lambda m: symVals[int(m.group(1))-1],
			self.action)
		)
		currentNonterms.push(self.getLeft(), val)
		return val


def runRuleSeq(ruleSeq):
	"""exectues all actions attached to the rules in ruleSeq in reverse
	order (that's the way they need to be exectued:-) and returns
	the result of the last rule.

	Btw., one could argue that this ought to be a static method of 
	RuleWithAction, but the course doesn't teach static methods,
	so it isn't one.
	"""
	ruleSeq = ruleSeq[:]
	ruleSeq.reverse()
	currentNonterms = MultiStack()
	for rule in ruleSeq:
		val = rule.runAction(currentNonterms)
	return val

