From a983faedeb702366b5c2a22316d6dc22f206596c Mon Sep 17 00:00:00 2001 From: Lephe Date: Fri, 4 Oct 2019 12:41:42 +0200 Subject: [PATCH] add relations, and most functions This change adds a [cond] grammar symbol corresponding to binary relations in IF, IFELSE and WHILE conditions. It also adds support for unary functions with parenthesis syntax. Other functions will need specific rules depending on their operator precedence level. Also adds the Window.save() function that implements the --save option to save the output of the program into a bitmap file. This will be used to perform automated unit tests. --- ast.py | 4 +++- drawing.py | 13 +++++++++++++ fx92.py | 2 ++ interpreter.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++- lexer.py | 43 ++++++++++++++++++++++++++++++++++++++-- parser.py | 50 +++++++++++++++++++++++++++++++++++------------ printer.py | 8 ++------ 7 files changed, 151 insertions(+), 22 deletions(-) diff --git a/ast.py b/ast.py index 94fbaaf..2edfb14 100644 --- a/ast.py +++ b/ast.py @@ -28,7 +28,7 @@ class N(enum.IntEnum): # Flow control REPEAT = enum.auto() WHILE = enum.auto() - IFELSE = enum.auto() + IF = enum.auto() # Expressions ADD = enum.auto() @@ -39,6 +39,8 @@ class N(enum.IntEnum): EXP = enum.auto() VAR = enum.auto() CONST = enum.auto() + FUN = enum.auto() + REL = enum.auto() #--- # AST nodes diff --git a/drawing.py b/drawing.py index 69dd34e..c0500b4 100644 --- a/drawing.py +++ b/drawing.py @@ -126,6 +126,19 @@ class Window: if event.type == SDL_QUIT: break + def save(self, out): + """Save the output into a BMP file.""" + SDL_SetRenderTarget(self.r, self.t) + + surface = SDL_CreateRGBSurface(0, self.width, self.height, 32,0,0,0,0) + fmt = surface.contents.format.contents.format + pixels = surface.contents.pixels + pitch = surface.contents.pitch + + SDL_RenderReadPixels(self.r, None, fmt, pixels, pitch); + SDL_SaveBMP(surface, out.encode()) + SDL_FreeSurface(surface) + def update(self): """Push window contents on-screen.""" diff --git a/fx92.py b/fx92.py index 90773eb..9e24a21 100755 --- a/fx92.py +++ b/fx92.py @@ -99,6 +99,8 @@ def main(): ctx = Context(w) ctx.run(ast) + if out is not None: + w.save(out) w.wait() return 0 diff --git a/interpreter.py b/interpreter.py index 39df374..560e862 100644 --- a/interpreter.py +++ b/interpreter.py @@ -137,7 +137,7 @@ class Context: while self.run(n.args[0]): self.run(n.args[1]) - elif n.type == N.IFELSE: + elif n.type == N.IF: if self.run(n.args[0]): self.run(n.args[1]) elif n.args[2] is not None: @@ -181,6 +181,57 @@ class Context: elif n.type == N.CONST: return n.value + elif n.type == N.FUN: + arg = self.run(n.args[1]) + + f = { + "Abs": abs, + "Rnd": None, + "sinh": math.sinh, + "cosh": math.cosh, + "tanh": math.tanh, + "asinh": math.asinh, + "acosh": math.acosh, + "atanh": math.atanh, + "sqrt": math.sqrt, + "log": math.log, + "cbrt": lambda x: x ** (1/3), + "sin": math.sin, + "cos": math.cos, + "tan": math.tan, + "asin": math.asin, + "acos": math.acos, + "atan": math.atan, + "log10": math.log10, + "Ent": lambda x: float(int(x)), + "EntEx": lambda x: float(int(x)) - (x < 0), + "RanInt": None, + "GCD": None, + "LCM": None, + "Arond": None, + }[n.args[0]] + + if f is None: + raise Exception(f"Function {n.args[0]} not there yet x_x") + return f(arg) + + 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(f"Unhandled node of type {t}") + def runs(self, n): """Evaluate all the arguments of a node.""" return [self.run(e) for e in n.args] diff --git a/lexer.py b/lexer.py index fce551a..27b6885 100644 --- a/lexer.py +++ b/lexer.py @@ -68,6 +68,7 @@ class T(enum.IntEnum): CONST = -1 VAR = -2 REL = -3 + FUN = -4 class Token: def __init__(self, type, *args): @@ -104,7 +105,7 @@ class LexerBase: while not self.at_end(): x = self.lex() - print(x) + print("{:5d}: {}".format(self.position, x)) #--- # Bitcode lexer @@ -123,11 +124,13 @@ class BitcodeLexer(LexerBase): def rewind(self): """Restart lexing the same input.""" self.pos = 0 + self.position = 0 self.errors = 0 def lex(self): """Return the next token in the stream.""" h, p = self.hex, self.pos + self.position += 1 if self.at_end(): return Token(T.END) @@ -205,10 +208,44 @@ class BitcodeLexer(LexerBase): if code == 0x49: return Token(T.VAR, "y") + # Functions + fun = { + 0x68: "Abs", + 0x69: "Rnd", + 0x6C: "sinh", + 0x6D: "cosh", + 0x6E: "tanh", + 0x6F: "asinh", + 0x70: "acosh", + 0x71: "atanh", +# 0x72: "exp", +# 0x73: "exp10", + 0x74: "sqrt", + 0x75: "log", + 0x76: "cbrt", + 0x77: "sin", + 0x78: "cos", + 0x79: "tan", + 0x7A: "asin", + 0x7B: "acos", + 0x7C: "atan", + 0x7D: "log10", + 0x83: "Ent", + 0x84: "EntEx", + 0x87: "RanInt", +# 0x88: "GCD", +# 0x89: "LCM", + 0x8A: "Arond", + } + + if code in fun: + return Token(T.FUN, fun[code]) + print(f"[lexer] Unknown opcode {hex(code)}") self.errors += 1 - # Try to read another token + # Try to read another token after skipping one byte + self.position -= 1 return self.lex() def at_end(self): @@ -279,11 +316,13 @@ class TextLexer(LexerBase): """Restart lexing the same input.""" self.code = self.base_code + self.position = 0 self.errors = 0 self.pending_param = False def lex(self): """Return the next token in the stream.""" + self.position += 1 c = self.code.lstrip(" \t") diff --git a/parser.py b/parser.py index cbe1308..35f4333 100644 --- a/parser.py +++ b/parser.py @@ -19,15 +19,17 @@ class Parser: PENDOWN | PENUP | SETVAR arg argvar | INPUT argvar | MESSAGE arg | PRINT arg | STYLE style | WAIT arg | REPEAT arg program REPEAT_END | - WHILE arg program WHILE_END | - IF arg program IF_END | - IFELSE arg program ELSE program IFELSE_END + WHILE cond PARAM program WHILE_END | + IF cond PARAM program IF_END | + IFELSE cond PARAM program ELSE program IFELSE_END arg -> expr PARAM argvar -> var PARAM + cond -> expr REL expr expr -> factor | factor + expr | factor - expr factor -> atom | atom * factor | atom / factor - atom -> const (var | "(" expr ")")* | (var | "(" expr ")")+ + atom -> const (var | "(" expr ")" | FUN expr ")")* | + (var | "(" expr ")" | FUN expr ")")+ const -> (+|-)? CONST var -> VAR @@ -71,7 +73,8 @@ class Parser: expected = [T(t).name for t in types] got = T(self.la.type).name - err = f"Expected one of {expected}, got {got}" + pos = self.lexer.position + err = f"Expected one of {expected}, got {got} (at token {pos})" print("[urlparser] " + err) raise Exception("Syntax error: " + err) @@ -145,27 +148,30 @@ class Parser: return Node(N.REPEAT, arg, prg) if op.type == T.WHILE: - arg = self.arg() + cond = self.cond() + self.expect(T.PARAM) self.expect(T.EOL, optional=True) prg = self.program() self.expect(T.WHILE_END) - return Node(N.WHILE, arg, prg) + return Node(N.WHILE, cond, prg) if op.type == T.IF: - arg = self.arg() + cond = self.cond() + self.expect(T.PARAM) self.expect(T.EOL, optional=True) prg = self.program() self.expect(T.IF_END) - return Node(N.IF, arg, prg, None) + return Node(N.IF, cond, prg, None) if op.type == T.IFELSE: - arg = self.arg() + cond = self.cond() + self.expect(T.PARAM) self.expect(T.EOL, optional=True) p1 = self.program() self.expect(T.ELSE) p2 = self.program() self.expect(T.IFELSE_END) - return Node(N.IF, arg, p1, p2) + return Node(N.IF, cond, p1, p2) # arg -> expr PARAM def arg(self): @@ -173,6 +179,13 @@ class Parser: self.expect(T.PARAM) return e + # cond -> expr REL expr + def cond(self): + e1 = self.expr() + rel = self.expect(T.REL).args[0] + e2 = self.expr() + return Node(N.REL, e1, rel, e2) + # expr -> factor | factor + expr | factor - expr def expr(self): f = [self.factor()] @@ -216,7 +229,20 @@ class Parser: elif lat == T.LPAR: self.expect(T.LPAR) factors.append(self.expr()) - self.expect(T.RPAR) + + # Allow a parenthesis to be removed at the end of a parameter + optional = (self.la.type == T.PARAM) + self.expect(T.RPAR, optional=optional) + + elif lat == T.FUN: + name = self.expect(T.FUN).args[0] + e = self.expr() + + # Allow a parenthesis to be removed at the end of a parameter + optional = (self.la.type == T.PARAM) + self.expect(T.RPAR, optional=optional) + + factors.append(Node(N.FUN, name, e)) else: break diff --git a/printer.py b/printer.py index e7c75bc..94e1d73 100644 --- a/printer.py +++ b/printer.py @@ -34,7 +34,7 @@ def print_ast(n, lang="en", indent=0): if lang == "en": lang = MessageEnglish if lang == "ast": lang = MessageAST - if n.type == N.PROGRAM: + if isinstance(n, Node) and n.type == N.PROGRAM: for arg in n.args: print_ast(arg, lang=lang, indent=indent) return @@ -45,14 +45,10 @@ def print_ast(n, lang="en", indent=0): print(f"{type(n)}({n})") return - if n.type == N.CONST: + if n.type in [N.CONST, N.VAR, N.REL]: print(n.args[0]) return - if n.type == N.VAR: - print(f"{n.args[0]}") - return - id = n.type.name.lower() if hasattr(lang, id):