From cd2936c58240e4b1feff3af39af939b8f842a36a Mon Sep 17 00:00:00 2001 From: Lephe Date: Tue, 1 Oct 2019 18:05:22 +0200 Subject: [PATCH] add a first interpreter Note that there are still bugs and the output is really not what it should be. Will fix that with unit tests ASAP. --- ast.py | 2 +- fx92.py | 13 ++-- interpreter.py | 182 +++++++++++++++++++++++++++++++++++++++++++++++++ parser.py | 2 +- printer.py | 6 ++ 5 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 interpreter.py diff --git a/ast.py b/ast.py index 86e3982..7c013a9 100644 --- a/ast.py +++ b/ast.py @@ -62,7 +62,7 @@ class Node: @property def value(self): - """Retrive the value of a CONST node.""" + """Retrieve the value of a CONST node.""" if self.type != N.CONST: raise Exception("Taking value of non-const node") return self.args[0] diff --git a/fx92.py b/fx92.py index 3e739b4..c482e72 100755 --- a/fx92.py +++ b/fx92.py @@ -5,12 +5,7 @@ import sys from parser import UrlParser from printer import print_ast from drawing import Window - -#--- -# fx-92 SC+ interpreter -#--- - -pass +from interpreter import Context #--- # Main program @@ -41,9 +36,9 @@ def main(argv): print_ast(ast, lang="ast") with Window(width=96, height=32, scale=8) as w: - w.clear(w.WHITE) - w.line(1, 1, 20, 20, w.BLACK) - w.update() + ctx = Context(w) + ctx.run(ast) + w.wait() if __name__ == "__main__": diff --git a/interpreter.py b/interpreter.py new file mode 100644 index 0000000..e0eb328 --- /dev/null +++ b/interpreter.py @@ -0,0 +1,182 @@ +# 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 "MABCDEFxy" } + 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] + 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] diff --git a/parser.py b/parser.py index c0a9c18..a4ed165 100644 --- a/parser.py +++ b/parser.py @@ -266,7 +266,7 @@ class UrlParser: def argvar(self): n = self.var() self.expect(T.PARAM) - return n + return n.args[0] # var -> VAR def var(self): diff --git a/printer.py b/printer.py index 04b8e38..0e01c42 100644 --- a/printer.py +++ b/printer.py @@ -65,5 +65,11 @@ def print_ast(n, lang="en", indent=0): n.constchildren(): return + if n.type == N.ASSIGN: + print_ast(n.args[0], lang=lang, indent=indent+2) + print(" " * (indent+2), end="") + print(f"->{n.args[1]}") + return + for arg in n.args: print_ast(arg, lang=lang, indent=indent+2)