MVP #1

Merged
jbwdevries merged 73 commits from idea_crc32 into master 2022-08-21 12:59:21 +00:00
4 changed files with 328 additions and 64 deletions
Showing only changes of commit 658e442df2 - Show all commits

View File

@ -1,7 +1,7 @@
"""
Contains the syntax tree for ourlang
"""
from typing import Any, Dict, List, NoReturn, Union, Tuple
from typing import Any, Dict, List, Optional, NoReturn, Union, Tuple
import ast
@ -73,6 +73,21 @@ class OurTypeFloat64(OurType):
def render(self) -> str:
return 'f64'
class OurTypeTuple(OurType):
"""
The tuple type, 64 bits wide
"""
__slots__ = ('members', )
members: List[OurType]
def __init__(self) -> None:
self.members = []
def render(self) -> str:
mems = ', '.join(x.render() for x in self.members)
return f'({mems}, )'
class Expression:
"""
An expression within a statement
@ -227,8 +242,62 @@ class FunctionCall(Expression):
for arg in self.arguments
)
if isinstance(self.function, StructConstructor):
return f'{self.function.struct.name}({args})'
return f'{self.function.name}({args})'
class AccessStructMember(Expression):
"""
Access a struct member for reading of writing
"""
__slots__ = ('varref', 'member', )
varref: VariableReference
member: 'StructMember'
def __init__(self, varref: VariableReference, member: 'StructMember') -> None:
self.varref = varref
self.member = member
def render(self) -> str:
return f'{self.varref.render()}.{self.member.name}'
class AccessTupleMember(Expression):
"""
Access a tuple member for reading of writing
"""
__slots__ = ('varref', 'member_idx', 'member_type', )
varref: VariableReference
member_idx: int
member_type: 'OurType'
def __init__(self, varref: VariableReference, member_idx: int, member_type: 'OurType') -> None:
self.varref = varref
self.member_idx = member_idx
self.member_type = member_type
def render(self) -> str:
return f'{self.varref.render()}[{self.member_idx}]'
class TupleCreation(Expression):
"""
Create a tuple instance
"""
__slots__ = ('type', 'members', )
type: OurTypeTuple
members: List[Expression]
def __init__(self, type_: OurTypeTuple) -> None:
self.type = type_
self.members = []
def render(self) -> str:
mems = ', '. join(x.render() for x in self.members)
return f'({mems}, )'
class Statement:
"""
A statement within a function
@ -306,11 +375,12 @@ class Function:
"""
A function processes input and produces output
"""
__slots__ = ('name', 'lineno', 'exported', 'statements', 'returns', 'posonlyargs', )
__slots__ = ('name', 'lineno', 'exported', 'buildin', 'statements', 'returns', 'posonlyargs', )
name: str
lineno: int
exported: bool
buildin: bool
statements: List[Statement]
returns: OurType
posonlyargs: List[Tuple[str, OurType]]
@ -319,6 +389,7 @@ class Function:
self.name = name
self.lineno = lineno
self.exported = False
self.buildin = False
self.statements = []
self.returns = OurTypeNone()
self.posonlyargs = []
@ -344,6 +415,24 @@ class Function:
result += f' {line}\n' if line else '\n'
return result
class StructConstructor(Function):
"""
The constructor method for a struct
"""
__slots__ = ('struct', )
struct: 'Struct'
def __init__(self, struct: 'Struct') -> None:
super().__init__(f'@{struct.name}@__init___@', -1)
self.returns = struct
for mem in struct.members:
self.posonlyargs.append((mem.name, mem.type, ))
self.struct = struct
class StructMember:
"""
Represents a struct member
@ -353,7 +442,7 @@ class StructMember:
self.type = type_
self.offset = offset
class Struct:
class Struct(OurType):
"""
A struct has named properties
"""
@ -368,13 +457,35 @@ class Struct:
self.lineno = lineno
self.members = []
def get_member(self, name: str) -> Optional[StructMember]:
"""
Returns a member by name
"""
for mem in self.members:
if mem.name == name:
return mem
return None
def render(self) -> str:
"""
Renders the function back to source code format
Renders the type back to source code format
This'll look like Python code.
"""
return '?'
return self.name
def render_definition(self) -> str:
"""
Renders the definition back to source code format
This'll look like Python code.
"""
result = f'class {self.name}:\n'
for mem in self.members:
result += f' {mem.name}: {mem.type.render()}\n'
return result
class Module:
"""
@ -396,6 +507,14 @@ class Module:
self.functions = {}
self.structs = {}
# sqrt is guaranteed by wasm, so we should provide it
# ATM it's a 32 bit variant, but that might change.
sqrt = Function('sqrt', -2)
sqrt.buildin = True
sqrt.returns = self.types['f32']
sqrt.posonlyargs = [('@', self.types['f32'], )]
self.functions[sqrt.name] = sqrt
def render(self) -> str:
"""
Renders the module back to source code format
@ -403,10 +522,21 @@ class Module:
This'll look like Python code.
"""
result = ''
for struct in self.structs.values():
if result:
result += '\n'
result += struct.render_definition()
for function in self.functions.values():
if function.lineno < 0:
# Buildin (-2) or auto generated (-1)
continue
if result:
result += '\n'
result += function.render()
return result
class StaticError(Exception):
@ -431,8 +561,10 @@ class OurVisitor:
_not_implemented(not node.type_ignores, 'Module.type_ignores')
# Second pass for the types
for stmt in node.body:
res = self.visit_Module_stmt(module, stmt)
res = self.pre_visit_Module_stmt(module, stmt)
if isinstance(res, Function):
if res.name in module.functions:
@ -449,19 +581,26 @@ class OurVisitor:
)
module.structs[res.name] = res
constructor = StructConstructor(res)
module.functions[constructor.name] = constructor
# Second pass for the function bodies
for stmt in node.body:
self.visit_Module_stmt(module, stmt)
return module
def visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, Struct]:
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, Struct]:
if isinstance(node, ast.FunctionDef):
return self.visit_Module_FunctionDef(module, node)
return self.pre_visit_Module_FunctionDef(module, node)
if isinstance(node, ast.ClassDef):
return self.visit_Module_ClassDef(module, node)
return self.pre_visit_Module_ClassDef(module, node)
raise NotImplementedError(f'{node} on Module')
def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> Function:
def pre_visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> Function:
function = Function(node.name, node.lineno)
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
@ -496,18 +635,9 @@ class OurVisitor:
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
# Deferred parsing
our_locals = dict(function.posonlyargs)
for stmt in node.body:
function.statements.append(
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
)
return function
def visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> Struct:
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> Struct:
struct = Struct(node.name, node.lineno)
_not_implemented(not node.bases, 'ClassDef.bases')
@ -536,6 +666,26 @@ class OurVisitor:
return struct
def visit_Module_stmt(self, module: Module, node: ast.stmt) -> None:
if isinstance(node, ast.FunctionDef):
self.visit_Module_FunctionDef(module, node)
return
if isinstance(node, ast.ClassDef):
return
raise NotImplementedError(f'{node} on Module')
def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None:
function = module.functions[node.name]
our_locals = dict(function.posonlyargs)
for stmt in node.body:
function.statements.append(
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
)
def visit_Module_FunctionDef_stmt(self, module: Module, function: Function, our_locals: OurLocals, node: ast.stmt) -> Statement:
if isinstance(node, ast.Return):
if node.value is None:
@ -571,6 +721,8 @@ class OurVisitor:
operator = '+'
elif isinstance(node.op, ast.Sub):
operator = '-'
elif isinstance(node.op, ast.Mult):
operator = '*'
else:
raise NotImplementedError(f'Operator {node.op}')
@ -602,6 +754,10 @@ class OurVisitor:
if isinstance(node.ops[0], ast.Gt):
operator = '>'
elif isinstance(node.ops[0], ast.Eq):
operator = '=='
elif isinstance(node.ops[0], ast.Lt):
operator = '<'
else:
raise NotImplementedError(f'Operator {node.ops}')
@ -615,33 +771,23 @@ class OurVisitor:
)
if isinstance(node, ast.Call):
if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet?
if not isinstance(node.func, ast.Name):
raise NotImplementedError(f'Calling methods that are not a name {node.func}')
if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if node.func.id not in module.functions:
_raise_static_error(node, 'Call to undefined function')
func = module.functions[node.func.id]
if func.returns != exp_type:
_raise_static_error(node, f'Function does not return {exp_type.render()}')
result = FunctionCall(func)
result.arguments.extend(
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, expr)
for expr in node.args
)
return result
return self.visit_Module_FunctionDef_Call(module, function, our_locals, exp_type, node)
if isinstance(node, ast.Constant):
return self.visit_Module_FunctionDef_Constant(
module, function, exp_type, node,
)
if isinstance(node, ast.Attribute):
return self.visit_Module_FunctionDef_Attribute(
module, function, our_locals, exp_type, node,
)
if isinstance(node, ast.Subscript):
return self.visit_Module_FunctionDef_Subscript(
module, function, our_locals, exp_type, node,
)
if isinstance(node, ast.Name):
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
@ -649,8 +795,120 @@ class OurVisitor:
if node.id in our_locals:
return VariableReference(our_locals[node.id], node.id)
if isinstance(node, ast.Tuple):
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if not isinstance(exp_type, OurTypeTuple):
_raise_static_error(node, f'Expression is expecting a {exp_type.render()}, not a tuple')
if len(exp_type.members) != len(node.elts):
_raise_static_error(node, f'Expression is expecting a tuple of size {len(exp_type.members)}, but {len(node.elts)} are given')
result = TupleCreation(exp_type)
result.members = [
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_type, arg_node)
for arg_node, arg_type in zip(node.elts, exp_type.members)
]
return result
raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, exp_type: OurType, node: ast.Call) -> FunctionCall:
if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet?
if not isinstance(node.func, ast.Name):
raise NotImplementedError(f'Calling methods that are not a name {node.func}')
if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if node.func.id in module.structs:
struct = module.structs[node.func.id]
struct_constructor = StructConstructor(struct)
func = module.functions[struct_constructor.name]
else:
if node.func.id not in module.functions:
_raise_static_error(node, 'Call to undefined function')
func = module.functions[node.func.id]
if func.returns != exp_type:
_raise_static_error(node, f'Function {node.func.id} does not return {exp_type.render()}')
if len(func.posonlyargs) != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given')
result = FunctionCall(func)
result.arguments.extend(
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_type, arg_expr)
for arg_expr, (_, arg_type) in zip(node.args, func.posonlyargs)
)
return result
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, exp_type: OurType, node: ast.Attribute) -> Expression:
if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name')
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if not node.value.id in our_locals:
_raise_static_error(node, f'Undefined variable {node.value.id}')
node_typ = our_locals[node.value.id]
if not isinstance(node_typ, Struct):
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
member = node_typ.get_member(node.attr)
if member is None:
_raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
if exp_type != member.type:
_raise_static_error(node, f'Expected {exp_type.render()}, got {member.type.render()} instead')
return AccessStructMember(
VariableReference(node_typ, node.value.id),
member,
)
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, exp_type: OurType, node: ast.Subscript) -> Expression:
if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name')
if not isinstance(node.slice, ast.Index):
_raise_static_error(node, 'Must subscript using an index')
if not isinstance(node.slice.value, ast.Constant):
_raise_static_error(node, 'Must subscript using a constant index') # FIXME: Implement variable indexes
if not isinstance(node.slice.value.value, int):
_raise_static_error(node, 'Must subscript using a constant integer index')
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if not node.value.id in our_locals:
_raise_static_error(node, f'Undefined variable {node.value.id}')
node_typ = our_locals[node.value.id]
if not isinstance(node_typ, OurTypeTuple):
_raise_static_error(node, f'Cannot take index of non-tuple {node.value.id}')
if len(node_typ.members) <= node.slice.value.value:
_raise_static_error(node, f'Index {node.slice.value.value} out of bounds for tuple {node.value.id}')
member = node_typ.members[node.slice.value.value]
if exp_type != member:
_raise_static_error(node, f'Expected {exp_type.render()}, got {member.render()} instead')
return AccessTupleMember(
VariableReference(node_typ, node.value.id),
node.slice.value.value,
member,
)
def visit_Module_FunctionDef_Constant(self, module: Module, function: Function, exp_type: OurType, node: ast.Constant) -> Expression:
del module
del function
@ -696,11 +954,25 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if node.id not in module.types:
_raise_static_error(node, 'Unrecognized type')
if node.id in module.types:
return module.types[node.id]
if node.id in module.structs:
return module.structs[node.id]
_raise_static_error(node, f'Unrecognized type {node.id}')
if isinstance(node, ast.Tuple):
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
result = OurTypeTuple()
result.members = [
self.visit_type(module, elt)
for elt in node.elts
]
return result
raise NotImplementedError(f'{node} as type')
def _not_implemented(check: Any, msg: str) -> None:

View File

@ -41,14 +41,12 @@ class Visitor:
if isinstance(name, ast.expr):
if isinstance(name, ast.Name):
name = name.id
elif isinstance(name, ast.Subscript) and isinstance(name.value, ast.Name) and name.value.id == 'Tuple':
elif isinstance(name, ast.Tuple):
assert isinstance(name.ctx, ast.Load)
assert isinstance(name.slice, ast.Index)
assert isinstance(name.slice.value, ast.Tuple)
args: List[wasm.TupleMember] = []
offset = 0
for name_arg in name.slice.value.elts:
for name_arg in name.elts:
arg = wasm.TupleMember(
self._get_type(name_arg),
offset

View File

@ -14,7 +14,7 @@ import wasmer_compiler_cranelift
import wasmtime
from py2wasm.utils import process
from py2wasm.utils import our_process, process
DASHES = '-' * 16
@ -69,6 +69,9 @@ class Suite:
Returned is an object with the results set
"""
our_module = our_process(self.code_py, self.test_name)
assert self.code_py == '\n' + our_module.render() # \n for formatting in tests
code_wat = process(self.code_py, self.test_name)
sys.stderr.write(f'{DASHES} Assembly {DASHES}\n')

View File

@ -179,6 +179,7 @@ def testEntry(a: i32) -> i32:
assert 1 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Such a return is not how things should be')
def test_if_complex():
code_py = """
@exported
@ -254,7 +255,6 @@ def testEntry() -> i32:
@pytest.mark.integration_test
def test_struct_0():
code_py = """
class CheckedValue:
value: i32
@ -274,11 +274,10 @@ def helper(cv: CheckedValue) -> i32:
@pytest.mark.integration_test
def test_struct_1():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32 # = 5
border: i32
@exported
def testEntry() -> i32:
@ -296,21 +295,17 @@ def helper(shape: Rectangle) -> i32:
@pytest.mark.integration_test
def test_struct_2():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32 # = 5
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2), Rectangle(200, 90, 3))
def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
return (
shape1.height + shape1.width + shape1.border
+ shape2.height + shape2.width + shape2.border
)
return shape1.height + shape1.width + shape1.border + shape2.height + shape2.width + shape2.border
"""
result = Suite(code_py, 'test_call').run_code()
@ -321,12 +316,11 @@ def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
@pytest.mark.integration_test
def test_tuple_int():
code_py = """
@exported
def testEntry() -> i32:
return helper((24, 57, 80, ))
def helper(vector: Tuple[i32, i32, i32]) -> i32:
def helper(vector: (i32, i32, i32, )) -> i32:
return vector[0] + vector[1] + vector[2]
"""
@ -338,14 +332,11 @@ def helper(vector: Tuple[i32, i32, i32]) -> i32:
@pytest.mark.integration_test
def test_tuple_float():
code_py = """
@exported
def testEntry() -> f32:
return helper((1.0, 2.0, 3.0, ))
def helper(v: Tuple[f32, f32, f32]) -> f32:
# sqrt is guaranteed by wasm, so we can use it
# ATM it's a 32 bit variant, but that might change.
def helper(v: (f32, f32, f32, )) -> f32:
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
"""