restructure + syntactic sugar implementation
This commit is contained in:
@ -25,7 +25,7 @@ class ObjectSpace(object):
|
|||||||
import os
|
import os
|
||||||
builtins = os.path.join(
|
builtins = os.path.join(
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
'builtins.simple')
|
'../builtins.simple')
|
||||||
with open(builtins, 'r') as f:
|
with open(builtins, 'r') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
@ -6,26 +6,29 @@ primitive_number_of_arguments = []
|
|||||||
def primitive(name, unwrap_spec, wrap_spec):
|
def primitive(name, unwrap_spec, wrap_spec):
|
||||||
assert '$' + name not in registry, '${name} already defined'.format(name=name)
|
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
|
primitive_number_of_arguments.append(len(unwrap_spec) - 1) # first argument is the receiver
|
||||||
|
|
||||||
def expose(func):
|
def expose(func):
|
||||||
def unwrapper(w_receiver, args_w, space):
|
def unwrapper(w_receiver, args_w, space):
|
||||||
args = [w_receiver] + args_w
|
args = [w_receiver] + args_w
|
||||||
if len(args) != len(unwrap_spec):
|
if len(args) != len(unwrap_spec):
|
||||||
raise TypeError(
|
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 = ()
|
unwrapped_args = ()
|
||||||
for t, arg in zip(unwrap_spec, args):
|
for t, arg in zip(unwrap_spec, args):
|
||||||
if t is int:
|
if t is int:
|
||||||
unwrapped_args += (arg.value, )
|
unwrapped_args += (arg.value,)
|
||||||
else:
|
else:
|
||||||
unwrapped_args += (arg, )
|
unwrapped_args += (arg,)
|
||||||
result = func(*unwrapped_args)
|
result = func(*unwrapped_args)
|
||||||
if wrap_spec is int:
|
if wrap_spec is int:
|
||||||
return space.newint(result)
|
return space.newint(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
unwrapper.__qualname__ = name
|
unwrapper.__qualname__ = name
|
||||||
all_primitives.append(unwrapper)
|
all_primitives.append(unwrapper)
|
||||||
registry['$' + name] = len(all_primitives) - 1
|
registry['$' + name] = len(all_primitives) - 1
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return expose
|
return expose
|
||||||
|
|
||||||
|
|
||||||
@ -45,3 +48,29 @@ def simple_int_add(a, b):
|
|||||||
@primitive('int_eq', [int, int], int)
|
@primitive('int_eq', [int, int], int)
|
||||||
def simple_int_eq(a, b):
|
def simple_int_eq(a, b):
|
||||||
return 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
|
@ -158,6 +158,16 @@ Assign = r'\='
|
|||||||
OpenBracket = r'[\[\(\{]'
|
OpenBracket = r'[\[\(\{]'
|
||||||
CloseBracket = r'[\]\)\}]'
|
CloseBracket = r'[\]\)\}]'
|
||||||
|
|
||||||
|
# ____________________________________________________________
|
||||||
|
# Syntactic Sugar Operators
|
||||||
|
|
||||||
|
Plus = r'\+'
|
||||||
|
Minus = r'-'
|
||||||
|
Multiply = r'\*'
|
||||||
|
Divide = r'/'
|
||||||
|
Increment = r'\+\+'
|
||||||
|
Modulo = r'%'
|
||||||
|
|
||||||
# ____________________________________________________________
|
# ____________________________________________________________
|
||||||
# Keywords
|
# Keywords
|
||||||
|
|
||||||
@ -168,8 +178,9 @@ Def = r'def'
|
|||||||
Object = r'object'
|
Object = r'object'
|
||||||
|
|
||||||
tokens = ["If", "Else", "While", "Def", "Object", "Number", "String", "Ignore",
|
tokens = ["If", "Else", "While", "Def", "Object", "Number", "String", "Ignore",
|
||||||
"NewlineAndWhitespace", "OpenBracket", "CloseBracket", "Comma", "Assign",
|
"NewlineAndWhitespace", "OpenBracket", "CloseBracket", "Comma", "Assign", "Colon",
|
||||||
"Colon", "Name", "PrimitiveName"]
|
"Increment", "Plus", "Minus", "Multiply", "Divide", "Modulo", # Sugar
|
||||||
|
"Name", "PrimitiveName"]
|
||||||
|
|
||||||
|
|
||||||
def make_lexer():
|
def make_lexer():
|
@ -1,35 +1,40 @@
|
|||||||
"""
|
"""
|
||||||
A 'simple' parser. Don't look into this file :-)
|
A 'simple' parser. Don't look into this file :-)
|
||||||
"""
|
"""
|
||||||
import py
|
from rply import ParserGenerator, Token
|
||||||
import simpleast
|
|
||||||
from simplelexer import lex
|
from simplelexer import lex
|
||||||
from rply.token import Token
|
import simpleast
|
||||||
|
|
||||||
from rply import ParserGenerator
|
|
||||||
|
|
||||||
pg = ParserGenerator(["If", "Else", "While", "Def", "Object", "Number",
|
pg = ParserGenerator(["If", "Else", "While", "Def", "Object", "Number",
|
||||||
"String", "Name", "Indent", "Dedent", "Newline", "OpenBracket",
|
"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:
|
if len(call) == 1:
|
||||||
args = []
|
args = []
|
||||||
else:
|
else:
|
||||||
args = call[1]
|
args = call[1]
|
||||||
name = call[0]
|
name = call[0]
|
||||||
return cls(None, name, args)
|
|
||||||
|
return cls(receiver, name, args)
|
||||||
|
|
||||||
|
|
||||||
@pg.production("program : statements EOF")
|
@pg.production("program : statements EOF")
|
||||||
@pg.production("program : newlines statements EOF")
|
@pg.production("program : newlines statements EOF")
|
||||||
def program(prog):
|
def program(prog):
|
||||||
# import pdb; pdb.set_trace()
|
# import pdb; pdb.set_trace()
|
||||||
if prog[0] is None:
|
if prog[0] is None: # source starts with newlines
|
||||||
prog = prog[1]
|
prog = prog[1]
|
||||||
else:
|
else:
|
||||||
prog = prog[0]
|
prog = prog[0]
|
||||||
|
|
||||||
return prog
|
return prog
|
||||||
|
|
||||||
|
|
||||||
@ -37,16 +42,17 @@ def program(prog):
|
|||||||
@pg.production("statements : statement statements")
|
@pg.production("statements : statement statements")
|
||||||
@pg.production("statements : statement newlines statements")
|
@pg.production("statements : statement newlines statements")
|
||||||
def statements(stmts):
|
def statements(stmts):
|
||||||
if len(stmts) == 1:
|
if len(stmts) == 1: # single statement
|
||||||
stmt = stmts[0]
|
stmt = stmts[0]
|
||||||
return simpleast.Program([stmt])
|
return simpleast.Program([stmt])
|
||||||
elif stmts[0] is None:
|
elif stmts[0] is None: # ?
|
||||||
assert len(stmts) == 2
|
assert len(stmts) == 2
|
||||||
return stmts[1]
|
return stmts[1]
|
||||||
elif len(stmts) == 2:
|
elif len(stmts) == 2: # merge programs (simpleast.Program)
|
||||||
stmt = stmts[0]
|
stmt = stmts[0]
|
||||||
result = stmts[1]
|
result = stmts[1]
|
||||||
result.statements.insert(0, stmt)
|
result.statements.insert(0, stmt)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@ -71,6 +77,7 @@ def ifstatement(ifstmt):
|
|||||||
elseblock = None
|
elseblock = None
|
||||||
if len(ifstmt) > 3:
|
if len(ifstmt) > 3:
|
||||||
elseblock = ifstmt[-1]
|
elseblock = ifstmt[-1]
|
||||||
|
|
||||||
return simpleast.IfStatement(ifstmt[1], ifstmt[2], elseblock)
|
return simpleast.IfStatement(ifstmt[1], ifstmt[2], elseblock)
|
||||||
|
|
||||||
|
|
||||||
@ -88,10 +95,12 @@ def objectstatement(obj):
|
|||||||
if len(obj) == 3:
|
if len(obj) == 3:
|
||||||
blk = obj[2]
|
blk = obj[2]
|
||||||
else:
|
else:
|
||||||
parents = obj[2]
|
print(obj)
|
||||||
|
parents = obj[2] # list of assignments (simpleast.Assignment)
|
||||||
names = [p.attrname for p in parents]
|
names = [p.attrname for p in parents]
|
||||||
expressions = [p.expression for p in parents]
|
expressions = [p.expression for p in parents]
|
||||||
blk = obj[3]
|
blk = obj[3]
|
||||||
|
|
||||||
return simpleast.ObjectDefinition(name, blk, names, expressions)
|
return simpleast.ObjectDefinition(name, blk, names, expressions)
|
||||||
|
|
||||||
|
|
||||||
@ -105,6 +114,7 @@ def defstatement(defn):
|
|||||||
else:
|
else:
|
||||||
args = []
|
args = []
|
||||||
blk = defn[2]
|
blk = defn[2]
|
||||||
|
|
||||||
return simpleast.FunctionDefinition(name, args, blk)
|
return simpleast.FunctionDefinition(name, args, blk)
|
||||||
|
|
||||||
|
|
||||||
@ -118,37 +128,63 @@ def block(blk):
|
|||||||
def simplestatement(stmts):
|
def simplestatement(stmts):
|
||||||
if len(stmts) == 2:
|
if len(stmts) == 2:
|
||||||
return simpleast.ExprStatement(stmts[0])
|
return simpleast.ExprStatement(stmts[0])
|
||||||
# assignement
|
|
||||||
|
# assignment
|
||||||
result = stmts[0]
|
result = stmts[0]
|
||||||
assign = stmts[2]
|
assign = stmts[2]
|
||||||
if (isinstance(result, simpleast.MethodCall) and
|
if isinstance(result, simpleast.MethodCall) and result.arguments == []: # assign to attribute
|
||||||
result.arguments == []):
|
return simpleast.Assignment(result.receiver, result.methodname, assign)
|
||||||
return simpleast.Assignment(
|
|
||||||
result.receiver, result.methodname, assign)
|
source_pos = stmts[1].source_pos
|
||||||
else:
|
raise ParseError(source_pos,
|
||||||
source_pos = stmts[1].source_pos
|
ErrorInformation(source_pos.idx,
|
||||||
raise ParseError(source_pos,
|
customerror="can only assign to attribute")) # , self.source)
|
||||||
ErrorInformation(source_pos.idx,
|
|
||||||
customerror="can only assign to attribute")) # , self.source)
|
|
||||||
|
|
||||||
|
|
||||||
@pg.production("expression : basic_expression")
|
@pg.production("expression : basic_expression")
|
||||||
@pg.production("expression : basic_expression msg-chain")
|
@pg.production("expression : basic_expression msg-chain")
|
||||||
def expression(expr):
|
def expression(expr):
|
||||||
if len(expr) > 1:
|
if len(expr) > 1: # expression has messages
|
||||||
prev = expr[0]
|
prev = expr[0]
|
||||||
for i in expr[1]:
|
for i in expr[1]: # reverse message order
|
||||||
i.receiver = prev
|
i.receiver = prev
|
||||||
prev = i
|
prev = i
|
||||||
return expr[1][-1]
|
return expr[1][-1] # return last message
|
||||||
|
|
||||||
return expr[0]
|
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")
|
||||||
@pg.production("msg-chain : methodcall msg-chain")
|
@pg.production("msg-chain : methodcall msg-chain")
|
||||||
def msg_chain(cc):
|
def msg_chain(cc):
|
||||||
if len(cc) > 1:
|
if len(cc) > 1:
|
||||||
return [cc[0]] + cc[1]
|
return [cc[0]] + cc[1] # merge message lists for chain
|
||||||
|
|
||||||
return cc
|
return cc
|
||||||
|
|
||||||
|
|
||||||
@ -166,6 +202,7 @@ def string_expression(stmt):
|
|||||||
def implicitselfmethodcall(call):
|
def implicitselfmethodcall(call):
|
||||||
methodcall = call[0]
|
methodcall = call[0]
|
||||||
methodcall.receiver = simpleast.ImplicitSelf()
|
methodcall.receiver = simpleast.ImplicitSelf()
|
||||||
|
|
||||||
return methodcall
|
return methodcall
|
||||||
|
|
||||||
|
|
||||||
@ -210,7 +247,8 @@ def argumentslist(args):
|
|||||||
@pg.production("parentdefinitions : assignment Comma parentdefinitions")
|
@pg.production("parentdefinitions : assignment Comma parentdefinitions")
|
||||||
def arguments(args):
|
def arguments(args):
|
||||||
if len(args) == 3:
|
if len(args) == 3:
|
||||||
return [args[0]] + args[2]
|
return [args[0]] + args[2] # merge argument lists for chain
|
||||||
|
|
||||||
return [args[0]]
|
return [args[0]]
|
||||||
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
|||||||
import py
|
|
||||||
|
|
||||||
from simpleparser import parse
|
from simpleparser import parse
|
||||||
from objmodel import W_NormalObject, W_Integer
|
from objmodel import W_Integer
|
||||||
from interpreter import Interpreter
|
from interpreter import Interpreter
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import py
|
|
||||||
|
|
||||||
from simpleparser import parse
|
from simpleparser import parse
|
||||||
from objmodel import W_NormalObject, W_Integer
|
|
||||||
from interpreter import Interpreter
|
from interpreter import Interpreter
|
||||||
|
|
||||||
|
|
||||||
|
13
mytest/test_sugar_lex.py
Normal file
13
mytest/test_sugar_lex.py
Normal 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]
|
14
mytest/test_sugar_parse.py
Normal file
14
mytest/test_sugar_parse.py
Normal 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.
35
shell.nix
35
shell.nix
@ -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
|
|
||||||
];
|
|
||||||
}
|
|
@ -1,9 +1,7 @@
|
|||||||
import py
|
|
||||||
|
|
||||||
from simpleparser import parse
|
from simpleparser import parse
|
||||||
from objmodel import W_NormalObject
|
|
||||||
from interpreter import Interpreter
|
from interpreter import Interpreter
|
||||||
|
|
||||||
|
|
||||||
def test_builtin_simple():
|
def test_builtin_simple():
|
||||||
builtincode = """
|
builtincode = """
|
||||||
x = 1
|
x = 1
|
||||||
@ -30,6 +28,7 @@ ax = a x
|
|||||||
assert w_module.getvalue("ax").value == 1
|
assert w_module.getvalue("ax").value == 1
|
||||||
assert w_module.getvalue("tx").value == 1
|
assert w_module.getvalue("tx").value == 1
|
||||||
|
|
||||||
|
|
||||||
def test_inttrait():
|
def test_inttrait():
|
||||||
builtincode = """
|
builtincode = """
|
||||||
object inttrait:
|
object inttrait:
|
||||||
@ -66,7 +65,7 @@ tr = inttrait
|
|||||||
assert w_module.getvalue("m1").value == 42
|
assert w_module.getvalue("m1").value == 42
|
||||||
assert w_module.getvalue("m2").value == 2
|
assert w_module.getvalue("m2").value == 2
|
||||||
|
|
||||||
|
|
||||||
def test_builtin_default():
|
def test_builtin_default():
|
||||||
ast = parse("""
|
ast = parse("""
|
||||||
def sumupto(x):
|
def sumupto(x):
|
||||||
@ -80,7 +79,7 @@ x = sumupto(100)
|
|||||||
# the constructor is called without arguments, so the default builtins are
|
# the constructor is called without arguments, so the default builtins are
|
||||||
# used
|
# used
|
||||||
interpreter = Interpreter()
|
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()
|
w_module = interpreter.make_module()
|
||||||
interpreter.eval(ast, w_module)
|
interpreter.eval(ast, w_module)
|
||||||
assert w_module.getvalue("x").value == 5050
|
assert w_module.getvalue("x").value == 5050
|
@ -1,5 +1,3 @@
|
|||||||
import py
|
|
||||||
|
|
||||||
from simpleparser import parse
|
from simpleparser import parse
|
||||||
from objmodel import W_NormalObject
|
from objmodel import W_NormalObject
|
||||||
from interpreter import Interpreter
|
from interpreter import Interpreter
|
@ -1,9 +1,8 @@
|
|||||||
import py
|
|
||||||
|
|
||||||
from simpleparser import parse
|
from simpleparser import parse
|
||||||
from objmodel import W_NormalObject
|
from objmodel import W_NormalObject
|
||||||
from interpreter import Interpreter
|
from interpreter import Interpreter
|
||||||
|
|
||||||
|
|
||||||
def test_assign():
|
def test_assign():
|
||||||
ast = parse("""
|
ast = parse("""
|
||||||
object a:
|
object a:
|
@ -1,7 +1,6 @@
|
|||||||
import py
|
import py
|
||||||
|
|
||||||
from simpleparser import parse
|
from simpleparser import parse
|
||||||
from objmodel import W_NormalObject
|
|
||||||
from interpreter import Interpreter
|
from interpreter import Interpreter
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user