# fx-92 Scientifique Collège+ language interpreter: AST interpreter from fx92.ast import N, Node from decimal import Decimal from fx92.util import decfun, degree2decimal, decimal2degree, decimal_repr import math class Context: """ An execution context, tied to a window, in which a script can be run. Provides both variables, state management, and interpretation. """ def __init__(self, window): """ Create a new execution context with a clean variable state and a window. """ self.vars = { name: 0 for name in "MABCDEF" } self.w = window self.x = Decimal(0) self.y = Decimal(0) self.theta = Decimal(0) self.pen = False # TODO: Also specify the style! self.w.clear(self.w.WHITE) self.w.update() def settheta(self, newtheta): self.theta = newtheta % 360 def run(self, n): """ Execute a full program tree. For expression nodes, returns a value; for statements returns None. Also draws as a side-effect. """ # Core nodes if n.type == N.PROGRAM: for stmt in n.args: self.run(stmt) # Basic statements elif n.type == N.FORWARD: dist, = self.runs(n) if self.pen: self.w.linef(self.x, self.y, self.theta, dist, self.w.BLACK) self.w.update() dx = dist * degree2decimal(math.cos)(self.theta) dy = dist * degree2decimal(math.sin)(self.theta) self.x = round(self.x + dx, 9) self.y = round(self.y + dy, 9) elif n.type == N.ROTATE: angle, = self.runs(n) self.settheta(self.theta + angle) elif n.type == N.ORIENT: angle, = self.runs(n) self.settheta(angle) elif n.type == N.GOTO: x, y = self.runs(n) if self.pen: self.w.line(self.x, self.y, x, y, self.w.BLACK) self.w.update() self.x = x self.y = y elif n.type == N.PENDOWN: self.pen = True elif n.type == N.PENUP: self.pen = False elif n.type == N.ASSIGN: val = self.run(n.args[0]) var = n.args[1] if var == "x": self.x = val elif var == "y": self.y = val elif var == "theta": self.theta = val else: self.vars[var] = val elif n.type == N.INPUT: # TODO: Input stmt should call into the lexer+parser (urg) raise Exception("Input statement not supported yet x_x") elif n.type == N.PRINT: x, = self.runs(n) s = decimal_repr(x) print(s) diff = abs(Decimal(s) - x) approx = Decimal(1e-9) * abs(x) assert diff <= approx, \ "Incorrect representation of {} as {}".format(x,s) elif n.type == N.STYLE: # TODO: Support style raise Exception("Style statement not supported yet x_x") elif n.type == N.WAIT: print("---- pause") print("x={} y={} theta={}".format(self.x, self.y, self.theta)) print("A={} B={}".format(self.vars['A'], self.vars['B'])) print("C={} D={}".format(self.vars['C'], self.vars['D'])) print("E={} F={}".format(self.vars['E'], self.vars['F'])) print("M={}".format(self.vars['M'])) self.w.pause() # Flow control elif n.type == N.REPEAT: count = self.run(n.args[0]) count = int(count) for i in range(count): self.run(n.args[1]) elif n.type == N.WHILE: while self.run(n.args[0]): self.run(n.args[1]) elif n.type == N.IF: if self.run(n.args[0]): self.run(n.args[1]) elif n.args[2] is not None: self.run(n.args[2]) # Expression elif n.type == N.ADD: return sum(self.runs(n)) elif n.type == N.SUB: x, y = self.runs(n) return x - y elif n.type == N.MUL: prod = Decimal(1) for x in self.runs(n): prod *= x return prod elif n.type == N.DIV: x, y = self.runs(n) return x / y elif n.type == N.MINUS: x, = self.runs(n) return -x elif n.type == N.EXP: x, y = self.runs(n) return x ** y elif n.type == N.VAR: var = n.args[0] if var == "x": return self.x if var == "y": return self.y if var == "theta": return self.theta return self.vars[var] elif n.type == N.CONST: return n.value elif n.type == N.FUN: args = [self.run(arg) for arg in n.args[1]] f = { "Abs": abs, "Rnd": None, "sinh": decfun(math.sinh), "cosh": decfun(math.cosh), "tanh": decfun(math.tanh), "asinh": decfun(math.asinh), "acosh": decfun(math.acosh), "atanh": decfun(math.atanh), "sqrt": decfun(math.sqrt), "log": decfun(math.log), "cbrt": lambda x: x ** (1/3), "sin": degree2decimal(math.sin), "cos": degree2decimal(math.cos), "tan": degree2decimal(math.tan), "asin": decimal2degree(math.asin), "acos": decimal2degree(math.acos), "atan": decimal2degree(math.atan), "log10": None, "Ent": lambda x: Decimal(int(x)), "EntEx": lambda x: Decimal(int(x)) - (x < 0), "RanInt": None, "GCD": lambda x, y: Decimal(math.gcd(int(x), int(y))), "LCM": lambda x, y: x*y/Decimal(math.gcd(int(x),int(y))), "Arond": None, }[n.args[0]] if f is None: raise Exception( "Function {} not there yet x_x".format(n.args[0])) return f(*args) elif n.type == N.REL: left = self.run(n.args[0]) right = self.run(n.args[2]) return { ">": left > right, "<": left < right, ">=": left >= right, "<=": left <= right, "=": left == right, "!=": left != right }[n.args[1]] else: t = N(n.type).name raise Exception("Unhandled node of type {}".format(t)) def runs(self, n): """Evaluate all the arguments of a node.""" return [self.run(e) for e in n.args]