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):