1

restructure + syntactic sugar implementation

This commit is contained in:
Christoph
2021-08-11 21:11:35 +02:00
parent 7365c41e11
commit 3a6814a850
27 changed files with 145 additions and 86 deletions

1
.envrc
View File

@ -1 +0,0 @@
eval "$(lorri direnv)"

View File

@ -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()

View File

@ -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

View File

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

View File

@ -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]]

View File

@ -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

View File

@ -1,7 +1,4 @@
import py
from simpleparser import parse
from objmodel import W_NormalObject, W_Integer
from interpreter import Interpreter

13
mytest/test_sugar_lex.py Normal file
View File

@ -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]

View File

@ -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

Binary file not shown.

View File

@ -1,35 +0,0 @@
{ pkgs ? import <nixpkgs> {} }:
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
];
}

View File

@ -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

View File

@ -1,5 +1,3 @@
import py
from simpleparser import parse
from objmodel import W_NormalObject
from interpreter import Interpreter

View File

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

View File

@ -1,7 +1,6 @@
import py
from simpleparser import parse
from objmodel import W_NormalObject
from interpreter import Interpreter