# fx-92 Scientifique Collège+ language interpreter: AST interpreter from ast import N, Node 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 = float(0) self.y = float(0) self.pen = False self.angle = float(0) self.sin = float(0) self.cos = float(1) # TODO: Also specify the style! self.w.clear(self.w.WHITE) self.w.update() 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) dx = self.cos * dist dy = self.sin * dist if self.pen: coord = self.x, self.y, self.x + dx, self.y + dy coord = [int(round(x)) for x in coord] x1, y1, x2, y2 = coord x1 += self.w.width // 2 x2 += self.w.width // 2 y1 = self.w.height // 2 - y1 y2 = self.w.height // 2 - y2 self.w.line(x1, y1, x2, y2, self.w.BLACK) self.w.update() self.x += dx self.y += dy elif n.type == N.ROTATE: angle, = self.runs(n) self.angle += angle self.sin = math.sin(math.pi * self.angle / 180) self.cos = math.cos(math.pi * self.angle / 180) elif n.type == N.ORIENT: angle, = self.runs(n) self.angle = angle self.sin = math.sin(math.pi * self.angle / 180) self.cos = math.cos(math.pi * self.angle / 180) elif n.type == N.GOTO: x, y = self.runs(n) if self.pen: coord = self.x, self.y, x, y coord = [int(round(x)) for x in coord] x1, y1, x2, y2 = coord x1 += self.w.width // 2 x2 += self.w.width // 2 y1 = self.w.height // 2 - y1 y2 = self.w.height // 2 - y2 self.w.line(x1, y1, x2, y2, 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] 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: val, = self.runs(n) print(val) elif n.type == N.STYLE: # TODO: Support style raise Exception("Style statement not supported yet x_x") elif n.type == N.WAIT: # TODO: Display "paused" and wait for a key press to continue" print("[interpreter] Delay ignored") # 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.IFELSE: 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 = 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 return self.vars[var] elif n.type == N.CONST: return n.value def runs(self, n): """Evaluate all the arguments of a node.""" return [self.run(e) for e in n.args]