From 3a6814a850b44a4444523293016671113326ced0 Mon Sep 17 00:00:00 2001 From: Christoph Date: Wed, 11 Aug 2021 21:11:35 +0200 Subject: [PATCH] restructure + syntactic sugar implementation --- .envrc | 1 - .../bytecodeinterpreter.py | 0 .../c3computation.py | 0 compile.py => de.churl.simple/compile.py | 0 disass.py => de.churl.simple/disass.py | 0 .../interpreter.py | 0 objmodel.py => de.churl.simple/objmodel.py | 0 objspace.py => de.churl.simple/objspace.py | 2 +- .../primitives.py | 35 ++++++- simpleast.py => de.churl.simple/simpleast.py | 0 .../simplelexer.py | 15 ++- .../simpleparser.py | 94 ++++++++++++------ mytest/test_double.py | 4 +- mytest/test_gc.py | 3 - mytest/test_sugar_lex.py | 13 +++ mytest/test_sugar_parse.py | 14 +++ mytest/testcases.zip | Bin 2502 -> 0 bytes shell.nix | 35 ------- {test => tests}/test_builtin.py | 9 +- {test => tests}/test_bytecode.py | 0 {test => tests}/test_interpreter.py | 2 - {test => tests}/test_method.py | 3 +- {test => tests}/test_objmodel.py | 0 {test => tests}/test_parent.py | 1 - {test => tests}/test_primitive.py | 0 {test => tests}/test_simplelexer.py | 0 {test => tests}/test_simpleparser.py | 0 27 files changed, 145 insertions(+), 86 deletions(-) delete mode 100644 .envrc rename bytecodeinterpreter.py => de.churl.simple/bytecodeinterpreter.py (100%) rename c3computation.py => de.churl.simple/c3computation.py (100%) rename compile.py => de.churl.simple/compile.py (100%) rename disass.py => de.churl.simple/disass.py (100%) rename interpreter.py => de.churl.simple/interpreter.py (100%) rename objmodel.py => de.churl.simple/objmodel.py (100%) rename objspace.py => de.churl.simple/objspace.py (98%) rename primitives.py => de.churl.simple/primitives.py (65%) rename simpleast.py => de.churl.simple/simpleast.py (100%) rename simplelexer.py => de.churl.simple/simplelexer.py (94%) rename simpleparser.py => de.churl.simple/simpleparser.py (78%) create mode 100644 mytest/test_sugar_lex.py create mode 100644 mytest/test_sugar_parse.py delete mode 100644 mytest/testcases.zip delete mode 100644 shell.nix rename {test => tests}/test_builtin.py (94%) rename {test => tests}/test_bytecode.py (100%) rename {test => tests}/test_interpreter.py (99%) rename {test => tests}/test_method.py (99%) rename {test => tests}/test_objmodel.py (100%) rename {test => tests}/test_parent.py (99%) rename {test => tests}/test_primitive.py (100%) rename {test => tests}/test_simplelexer.py (100%) rename {test => tests}/test_simpleparser.py (100%) diff --git a/.envrc b/.envrc deleted file mode 100644 index 051d09d..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -eval "$(lorri direnv)" diff --git a/bytecodeinterpreter.py b/de.churl.simple/bytecodeinterpreter.py similarity index 100% rename from bytecodeinterpreter.py rename to de.churl.simple/bytecodeinterpreter.py diff --git a/c3computation.py b/de.churl.simple/c3computation.py similarity index 100% rename from c3computation.py rename to de.churl.simple/c3computation.py diff --git a/compile.py b/de.churl.simple/compile.py similarity index 100% rename from compile.py rename to de.churl.simple/compile.py diff --git a/disass.py b/de.churl.simple/disass.py similarity index 100% rename from disass.py rename to de.churl.simple/disass.py diff --git a/interpreter.py b/de.churl.simple/interpreter.py similarity index 100% rename from interpreter.py rename to de.churl.simple/interpreter.py diff --git a/objmodel.py b/de.churl.simple/objmodel.py similarity index 100% rename from objmodel.py rename to de.churl.simple/objmodel.py diff --git a/objspace.py b/de.churl.simple/objspace.py similarity index 98% rename from objspace.py rename to de.churl.simple/objspace.py index c98c87d..6ec005f 100644 --- a/objspace.py +++ b/de.churl.simple/objspace.py @@ -25,7 +25,7 @@ class ObjectSpace(object): import os builtins = os.path.join( os.path.dirname(__file__), - 'builtins.simple') + '../builtins.simple') with open(builtins, 'r') as f: return f.read() diff --git a/primitives.py b/de.churl.simple/primitives.py similarity index 65% rename from primitives.py rename to de.churl.simple/primitives.py index 60bbeb5..c862419 100644 --- a/primitives.py +++ b/de.churl.simple/primitives.py @@ -6,26 +6,29 @@ primitive_number_of_arguments = [] def primitive(name, unwrap_spec, wrap_spec): assert '$' + name not in registry, '${name} already defined'.format(name=name) primitive_number_of_arguments.append(len(unwrap_spec) - 1) # first argument is the receiver + def expose(func): def unwrapper(w_receiver, args_w, space): args = [w_receiver] + args_w if len(args) != len(unwrap_spec): raise TypeError( - "Expected {ex} arguments, received {re}.".format(ex=len(unwrap_spec), re=len(args))) + "Expected {ex} arguments, received {re}.".format(ex=len(unwrap_spec), re=len(args))) unwrapped_args = () for t, arg in zip(unwrap_spec, args): if t is int: - unwrapped_args += (arg.value, ) + unwrapped_args += (arg.value,) else: - unwrapped_args += (arg, ) + unwrapped_args += (arg,) result = func(*unwrapped_args) if wrap_spec is int: return space.newint(result) return result + unwrapper.__qualname__ = name all_primitives.append(unwrapper) registry['$' + name] = len(all_primitives) - 1 return None + return expose @@ -45,3 +48,29 @@ def simple_int_add(a, b): @primitive('int_eq', [int, int], int) def simple_int_eq(a, b): return a == b + + +# Syntactic Sugar Primitives +@primitive("int_sub", [int, int], int) +def simple_int_subtract(a, b): + return a - b + + +@primitive("int_mul", [int, int], int) +def simple_int_multiply(a, b): + return a * b + + +@primitive("int_div", [int, int], int) +def simple_int_divide(a, b): + return a // b + + +@primitive("int_mod", [int, int], int) +def simple_int_modulo(a, b): + return a % b + + +@primitive("int_inc", [int], int) +def simple_int_increment(a): + return a + 1 diff --git a/simpleast.py b/de.churl.simple/simpleast.py similarity index 100% rename from simpleast.py rename to de.churl.simple/simpleast.py diff --git a/simplelexer.py b/de.churl.simple/simplelexer.py similarity index 94% rename from simplelexer.py rename to de.churl.simple/simplelexer.py index 5a80052..9305bdc 100644 --- a/simplelexer.py +++ b/de.churl.simple/simplelexer.py @@ -158,6 +158,16 @@ Assign = r'\=' OpenBracket = r'[\[\(\{]' CloseBracket = r'[\]\)\}]' +# ____________________________________________________________ +# Syntactic Sugar Operators + +Plus = r'\+' +Minus = r'-' +Multiply = r'\*' +Divide = r'/' +Increment = r'\+\+' +Modulo = r'%' + # ____________________________________________________________ # Keywords @@ -168,8 +178,9 @@ Def = r'def' Object = r'object' tokens = ["If", "Else", "While", "Def", "Object", "Number", "String", "Ignore", - "NewlineAndWhitespace", "OpenBracket", "CloseBracket", "Comma", "Assign", - "Colon", "Name", "PrimitiveName"] + "NewlineAndWhitespace", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon", + "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Sugar + "Name", "PrimitiveName"] def make_lexer(): diff --git a/simpleparser.py b/de.churl.simple/simpleparser.py similarity index 78% rename from simpleparser.py rename to de.churl.simple/simpleparser.py index 02ad09e..0a4de7f 100644 --- a/simpleparser.py +++ b/de.churl.simple/simpleparser.py @@ -1,35 +1,40 @@ """ A 'simple' parser. Don't look into this file :-) """ -import py -import simpleast +from rply import ParserGenerator, Token from simplelexer import lex -from rply.token import Token - -from rply import ParserGenerator +import simpleast pg = ParserGenerator(["If", "Else", "While", "Def", "Object", "Number", "String", "Name", "Indent", "Dedent", "Newline", "OpenBracket", - "CloseBracket", "Comma", "Assign", "Colon", "PrimitiveName", "EOF"]) + "CloseBracket", "Comma", "Assign", "Colon", + "Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Sugar + "PrimitiveName", "EOF"], + # Operator precedence for ambiguous rules, ascending + precedence=[("left", ["Plus", "Minus"]), + ("left", ["Multiply", "Divide", "Modulo"]), + ("left", ["Increment"])]) -def build_methodcall(call, cls): +def build_methodcall(call, cls, receiver=None): if len(call) == 1: args = [] else: args = call[1] name = call[0] - return cls(None, name, args) + + return cls(receiver, name, args) @pg.production("program : statements EOF") @pg.production("program : newlines statements EOF") def program(prog): # import pdb; pdb.set_trace() - if prog[0] is None: + if prog[0] is None: # source starts with newlines prog = prog[1] else: prog = prog[0] + return prog @@ -37,16 +42,17 @@ def program(prog): @pg.production("statements : statement statements") @pg.production("statements : statement newlines statements") def statements(stmts): - if len(stmts) == 1: + if len(stmts) == 1: # single statement stmt = stmts[0] return simpleast.Program([stmt]) - elif stmts[0] is None: + elif stmts[0] is None: # ? assert len(stmts) == 2 return stmts[1] - elif len(stmts) == 2: + elif len(stmts) == 2: # merge programs (simpleast.Program) stmt = stmts[0] result = stmts[1] result.statements.insert(0, stmt) + return result @@ -71,6 +77,7 @@ def ifstatement(ifstmt): elseblock = None if len(ifstmt) > 3: elseblock = ifstmt[-1] + return simpleast.IfStatement(ifstmt[1], ifstmt[2], elseblock) @@ -88,10 +95,12 @@ def objectstatement(obj): if len(obj) == 3: blk = obj[2] else: - parents = obj[2] + print(obj) + parents = obj[2] # list of assignments (simpleast.Assignment) names = [p.attrname for p in parents] expressions = [p.expression for p in parents] blk = obj[3] + return simpleast.ObjectDefinition(name, blk, names, expressions) @@ -105,6 +114,7 @@ def defstatement(defn): else: args = [] blk = defn[2] + return simpleast.FunctionDefinition(name, args, blk) @@ -118,37 +128,63 @@ def block(blk): def simplestatement(stmts): if len(stmts) == 2: return simpleast.ExprStatement(stmts[0]) - # assignement + + # assignment result = stmts[0] assign = stmts[2] - if (isinstance(result, simpleast.MethodCall) and - result.arguments == []): - return simpleast.Assignment( - result.receiver, result.methodname, assign) - else: - source_pos = stmts[1].source_pos - raise ParseError(source_pos, - ErrorInformation(source_pos.idx, - customerror="can only assign to attribute")) # , self.source) + if isinstance(result, simpleast.MethodCall) and result.arguments == []: # assign to attribute + return simpleast.Assignment(result.receiver, result.methodname, assign) + + source_pos = stmts[1].source_pos + raise ParseError(source_pos, + ErrorInformation(source_pos.idx, + customerror="can only assign to attribute")) # , self.source) @pg.production("expression : basic_expression") @pg.production("expression : basic_expression msg-chain") def expression(expr): - if len(expr) > 1: + if len(expr) > 1: # expression has messages prev = expr[0] - for i in expr[1]: + for i in expr[1]: # reverse message order i.receiver = prev prev = i - return expr[1][-1] + return expr[1][-1] # return last message + return expr[0] +# TODO: Parenthesis +# Syntactic Sugar: Plus, Minus, Multiply, Divide, Modulo, Increment +@pg.production("expression : expression Plus expression") +@pg.production("expression : expression Minus expression") +@pg.production("expression : expression Multiply expression") +@pg.production("expression : expression Divide expression") +@pg.production("expression : expression Modulo expression") +@pg.production("expression : expression Increment expression") +@pg.production("expression : expression Increment") +def sugar(expr): + op = { + "Plus": "$int_add", + "Minus": "$int_sub", + "Multiply": "$int_mul", + "Divide": "$int_div", + "Modulo": "$int_mod", + "Increment": "$int_inc" + }[expr[1].name] + + if len(expr) == 2: + return build_methodcall([op, []], simpleast.PrimitiveMethodCall, expr[0]) # ([name, arg], class, receiver) + + return build_methodcall([op, [expr[2]]], simpleast.PrimitiveMethodCall, expr[0]) # ([name, arg], class, receiver) + + @pg.production("msg-chain : methodcall") @pg.production("msg-chain : methodcall msg-chain") def msg_chain(cc): if len(cc) > 1: - return [cc[0]] + cc[1] + return [cc[0]] + cc[1] # merge message lists for chain + return cc @@ -166,6 +202,7 @@ def string_expression(stmt): def implicitselfmethodcall(call): methodcall = call[0] methodcall.receiver = simpleast.ImplicitSelf() + return methodcall @@ -210,7 +247,8 @@ def argumentslist(args): @pg.production("parentdefinitions : assignment Comma parentdefinitions") def arguments(args): if len(args) == 3: - return [args[0]] + args[2] + return [args[0]] + args[2] # merge argument lists for chain + return [args[0]] diff --git a/mytest/test_double.py b/mytest/test_double.py index 9883744..6ce3f32 100644 --- a/mytest/test_double.py +++ b/mytest/test_double.py @@ -1,7 +1,5 @@ -import py - from simpleparser import parse -from objmodel import W_NormalObject, W_Integer +from objmodel import W_Integer from interpreter import Interpreter diff --git a/mytest/test_gc.py b/mytest/test_gc.py index 153654a..bf97dfb 100644 --- a/mytest/test_gc.py +++ b/mytest/test_gc.py @@ -1,7 +1,4 @@ -import py - from simpleparser import parse -from objmodel import W_NormalObject, W_Integer from interpreter import Interpreter diff --git a/mytest/test_sugar_lex.py b/mytest/test_sugar_lex.py new file mode 100644 index 0000000..0ffc301 --- /dev/null +++ b/mytest/test_sugar_lex.py @@ -0,0 +1,13 @@ +from rply import Token +from simplelexer import lex + + +def test_lexing(): + token = lex("+ - * / ++ %") + + assert Token("Plus", "+") == token[0] + assert Token("Minus", "-") == token[1] + assert Token("Multiply", "*") == token[2] + assert Token("Divide", "/") == token[3] + assert Token("Increment", "++") == token[4] + assert Token("Modulo", "%") == token[5] diff --git a/mytest/test_sugar_parse.py b/mytest/test_sugar_parse.py new file mode 100644 index 0000000..43a7749 --- /dev/null +++ b/mytest/test_sugar_parse.py @@ -0,0 +1,14 @@ +from simpleparser import parse + + +def test_parsing(): + assert parse("1 + 2") == parse("1 $int_add(2)") + assert parse("1 + 2 + 3") == parse("1 $int_add(2) $int_add(3)") + assert parse("1 + 2 * 3") == parse("1 $int_add(2 $int_mul(3))") + assert parse("1 - 2 - 3") == parse("1 $int_sub(2) $int_sub(3)") + assert parse("1 + 2 % 3 / 4 - 5 * 6 + 7++") \ + == parse("1 $int_add(2 $int_mod(3) $int_div(4)) $int_sub(5 $int_mul(6)) $int_add(7 $int_inc)") + + +def test_parsing_parenthesis(): + pass diff --git a/mytest/testcases.zip b/mytest/testcases.zip deleted file mode 100644 index a9d3c09ba73a52c300b54c21c5abc4d577efebbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2502 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 nR`e6z<|^OF3xlr9{Hx|c {} }: - -with pkgs; - -let myPython = python39.buildEnv.override { - extraLibs = with python39Packages; [ - # Common Libs - rich - # numpy - # matplotlib - # scipy - # pytorch - # notbook - - # Doom Emacs Libs - black - pyflakes - isort - nose - pytest - - # DynLang - rply - ]; - }; -in - -mkShell { - buildInputs = [ - myPython - nodePackages.pyright # LSP - pipenv # Doom - jetbrains.pycharm-professional - ]; -} diff --git a/test/test_builtin.py b/tests/test_builtin.py similarity index 94% rename from test/test_builtin.py rename to tests/test_builtin.py index 305725a..111cece 100644 --- a/test/test_builtin.py +++ b/tests/test_builtin.py @@ -1,9 +1,7 @@ -import py - from simpleparser import parse -from objmodel import W_NormalObject from interpreter import Interpreter + def test_builtin_simple(): builtincode = """ x = 1 @@ -30,6 +28,7 @@ ax = a x assert w_module.getvalue("ax").value == 1 assert w_module.getvalue("tx").value == 1 + def test_inttrait(): builtincode = """ object inttrait: @@ -66,7 +65,7 @@ tr = inttrait assert w_module.getvalue("m1").value == 42 assert w_module.getvalue("m2").value == 2 - + def test_builtin_default(): ast = parse(""" def sumupto(x): @@ -80,7 +79,7 @@ x = sumupto(100) # the constructor is called without arguments, so the default builtins are # used interpreter = Interpreter() - # test that the default inttrait defines a method ``add`` + # tests that the default inttrait defines a method ``add`` w_module = interpreter.make_module() interpreter.eval(ast, w_module) assert w_module.getvalue("x").value == 5050 diff --git a/test/test_bytecode.py b/tests/test_bytecode.py similarity index 100% rename from test/test_bytecode.py rename to tests/test_bytecode.py diff --git a/test/test_interpreter.py b/tests/test_interpreter.py similarity index 99% rename from test/test_interpreter.py rename to tests/test_interpreter.py index 962fb84..0623632 100644 --- a/test/test_interpreter.py +++ b/tests/test_interpreter.py @@ -1,5 +1,3 @@ -import py - from simpleparser import parse from objmodel import W_NormalObject from interpreter import Interpreter diff --git a/test/test_method.py b/tests/test_method.py similarity index 99% rename from test/test_method.py rename to tests/test_method.py index 843fbf5..eed26aa 100644 --- a/test/test_method.py +++ b/tests/test_method.py @@ -1,9 +1,8 @@ -import py - from simpleparser import parse from objmodel import W_NormalObject from interpreter import Interpreter + def test_assign(): ast = parse(""" object a: diff --git a/test/test_objmodel.py b/tests/test_objmodel.py similarity index 100% rename from test/test_objmodel.py rename to tests/test_objmodel.py diff --git a/test/test_parent.py b/tests/test_parent.py similarity index 99% rename from test/test_parent.py rename to tests/test_parent.py index 30c1240..2e2eefb 100644 --- a/test/test_parent.py +++ b/tests/test_parent.py @@ -1,7 +1,6 @@ import py from simpleparser import parse -from objmodel import W_NormalObject from interpreter import Interpreter diff --git a/test/test_primitive.py b/tests/test_primitive.py similarity index 100% rename from test/test_primitive.py rename to tests/test_primitive.py diff --git a/test/test_simplelexer.py b/tests/test_simplelexer.py similarity index 100% rename from test/test_simplelexer.py rename to tests/test_simplelexer.py diff --git a/test/test_simpleparser.py b/tests/test_simpleparser.py similarity index 100% rename from test/test_simpleparser.py rename to tests/test_simpleparser.py