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 a9d3c09..0000000 Binary files a/mytest/testcases.zip and /dev/null differ diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 089045c..0000000 --- a/shell.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ pkgs ? import {} }: - -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