1070 lines
32 KiB
Python
1070 lines
32 KiB
Python
"""
|
|
Contains the syntax tree for ourlang
|
|
"""
|
|
from typing import Any, Dict, List, Optional, NoReturn, Union, Tuple
|
|
|
|
import ast
|
|
|
|
from typing_extensions import Final
|
|
|
|
WEBASSEMBLY_BUILDIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
|
|
|
|
class OurType:
|
|
"""
|
|
Type base class
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def render(self) -> str:
|
|
"""
|
|
Renders the type back to source code format
|
|
|
|
This'll look like Python code.
|
|
"""
|
|
raise NotImplementedError(self, 'render')
|
|
|
|
def alloc_size(self) -> int:
|
|
"""
|
|
When allocating this type in memory, how many bytes do we need to reserve?
|
|
"""
|
|
raise NotImplementedError(self, 'alloc_size')
|
|
|
|
class OurTypeNone(OurType):
|
|
"""
|
|
The None (or Void) type
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def render(self) -> str:
|
|
return 'None'
|
|
|
|
class OurTypeInt32(OurType):
|
|
"""
|
|
The Integer type, signed and 32 bits wide
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def render(self) -> str:
|
|
return 'i32'
|
|
|
|
def alloc_size(self) -> int:
|
|
return 4
|
|
|
|
class OurTypeInt64(OurType):
|
|
"""
|
|
The Integer type, signed and 64 bits wide
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def render(self) -> str:
|
|
return 'i64'
|
|
|
|
def alloc_size(self) -> int:
|
|
return 8
|
|
|
|
class OurTypeFloat32(OurType):
|
|
"""
|
|
The Float type, 32 bits wide
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def render(self) -> str:
|
|
return 'f32'
|
|
|
|
def alloc_size(self) -> int:
|
|
return 4
|
|
|
|
class OurTypeFloat64(OurType):
|
|
"""
|
|
The Float type, 64 bits wide
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def render(self) -> str:
|
|
return 'f64'
|
|
|
|
def alloc_size(self) -> int:
|
|
return 8
|
|
|
|
class TupleMember:
|
|
"""
|
|
Represents a tuple member
|
|
"""
|
|
def __init__(self, idx: int, type_: OurType, offset: int) -> None:
|
|
self.idx = idx
|
|
self.type = type_
|
|
self.offset = offset
|
|
|
|
class OurTypeTuple(OurType):
|
|
"""
|
|
The tuple type
|
|
"""
|
|
__slots__ = ('members', )
|
|
|
|
members: List[TupleMember]
|
|
|
|
def __init__(self) -> None:
|
|
self.members = []
|
|
|
|
def render(self) -> str:
|
|
mems = ', '.join(x.type.render() for x in self.members)
|
|
return f'({mems}, )'
|
|
|
|
def render_internal_name(self) -> str:
|
|
mems = '@'.join(x.type.render() for x in self.members)
|
|
assert ' ' not in mems, 'Not implement yet: subtuples'
|
|
return f'tuple@{mems}'
|
|
|
|
def alloc_size(self) -> int:
|
|
return sum(
|
|
x.type.alloc_size()
|
|
for x in self.members
|
|
)
|
|
|
|
class Expression:
|
|
"""
|
|
An expression within a statement
|
|
"""
|
|
__slots__ = ('type', )
|
|
|
|
type: OurType
|
|
|
|
def __init__(self, type_: OurType) -> None:
|
|
self.type = type_
|
|
|
|
def render(self) -> str:
|
|
"""
|
|
Renders the expression back to source code format
|
|
|
|
This'll look like Python code.
|
|
"""
|
|
raise NotImplementedError(self, 'render')
|
|
|
|
class Constant(Expression):
|
|
"""
|
|
An constant value expression within a statement
|
|
"""
|
|
__slots__ = ()
|
|
|
|
class ConstantInt32(Constant):
|
|
"""
|
|
An Int32 constant value expression within a statement
|
|
"""
|
|
__slots__ = ('value', )
|
|
|
|
value: int
|
|
|
|
def __init__(self, type_: OurTypeInt32, value: int) -> None:
|
|
super().__init__(type_)
|
|
self.value = value
|
|
|
|
def render(self) -> str:
|
|
return str(self.value)
|
|
|
|
class ConstantInt64(Constant):
|
|
"""
|
|
An Int64 constant value expression within a statement
|
|
"""
|
|
__slots__ = ('value', )
|
|
|
|
value: int
|
|
|
|
def __init__(self, type_: OurTypeInt64, value: int) -> None:
|
|
super().__init__(type_)
|
|
self.value = value
|
|
|
|
def render(self) -> str:
|
|
return str(self.value)
|
|
|
|
class ConstantFloat32(Constant):
|
|
"""
|
|
An Float32 constant value expression within a statement
|
|
"""
|
|
__slots__ = ('value', )
|
|
|
|
value: float
|
|
|
|
def __init__(self, type_: OurTypeFloat32, value: float) -> None:
|
|
super().__init__(type_)
|
|
self.value = value
|
|
|
|
def render(self) -> str:
|
|
return str(self.value)
|
|
|
|
class ConstantFloat64(Constant):
|
|
"""
|
|
An Float64 constant value expression within a statement
|
|
"""
|
|
__slots__ = ('value', )
|
|
|
|
value: float
|
|
|
|
def __init__(self, type_: OurTypeFloat64, value: float) -> None:
|
|
super().__init__(type_)
|
|
self.value = value
|
|
|
|
def render(self) -> str:
|
|
return str(self.value)
|
|
|
|
class VariableReference(Expression):
|
|
"""
|
|
An variable reference expression within a statement
|
|
"""
|
|
__slots__ = ('name', )
|
|
|
|
name: str
|
|
|
|
def __init__(self, type_: OurType, name: str) -> None:
|
|
super().__init__(type_)
|
|
self.name = name
|
|
|
|
def render(self) -> str:
|
|
return str(self.name)
|
|
|
|
class BinaryOp(Expression):
|
|
"""
|
|
A binary operator expression within a statement
|
|
"""
|
|
__slots__ = ('operator', 'left', 'right', )
|
|
|
|
operator: str
|
|
left: Expression
|
|
right: Expression
|
|
|
|
def __init__(self, type_: OurType, operator: str, left: Expression, right: Expression) -> None:
|
|
super().__init__(type_)
|
|
|
|
self.operator = operator
|
|
self.left = left
|
|
self.right = right
|
|
|
|
def render(self) -> str:
|
|
return f'{self.left.render()} {self.operator} {self.right.render()}'
|
|
|
|
class UnaryOp(Expression):
|
|
"""
|
|
A unary operator expression within a statement
|
|
"""
|
|
__slots__ = ('operator', 'right', )
|
|
|
|
operator: str
|
|
right: Expression
|
|
|
|
def __init__(self, type_: OurType, operator: str, right: Expression) -> None:
|
|
super().__init__(type_)
|
|
|
|
self.operator = operator
|
|
self.right = right
|
|
|
|
def render(self) -> str:
|
|
if self.operator in WEBASSEMBLY_BUILDIN_FLOAT_OPS:
|
|
return f'{self.operator}({self.right.render()})'
|
|
|
|
return f'{self.operator}{self.right.render()}'
|
|
|
|
class FunctionCall(Expression):
|
|
"""
|
|
A function call expression within a statement
|
|
"""
|
|
__slots__ = ('function', 'arguments', )
|
|
|
|
function: 'Function'
|
|
arguments: List[Expression]
|
|
|
|
def __init__(self, function: 'Function') -> None:
|
|
super().__init__(function.returns)
|
|
|
|
self.function = function
|
|
self.arguments = []
|
|
|
|
def render(self) -> str:
|
|
args = ', '.join(
|
|
arg.render()
|
|
for arg in self.arguments
|
|
)
|
|
|
|
if isinstance(self.function, StructConstructor):
|
|
return f'{self.function.struct.name}({args})'
|
|
|
|
if isinstance(self.function, TupleConstructor):
|
|
return f'({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:
|
|
super().__init__(member.type)
|
|
|
|
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', )
|
|
|
|
varref: VariableReference
|
|
member: TupleMember
|
|
|
|
def __init__(self, varref: VariableReference, member: TupleMember, ) -> None:
|
|
super().__init__(member.type)
|
|
|
|
self.varref = varref
|
|
self.member = member
|
|
|
|
def render(self) -> str:
|
|
return f'{self.varref.render()}[{self.member.idx}]'
|
|
|
|
class Statement:
|
|
"""
|
|
A statement within a function
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def render(self) -> List[str]:
|
|
"""
|
|
Renders the type back to source code format
|
|
|
|
This'll look like Python code.
|
|
"""
|
|
raise NotImplementedError(self, 'render')
|
|
|
|
class StatementReturn(Statement):
|
|
"""
|
|
A return statement within a function
|
|
"""
|
|
__slots__ = ('value', )
|
|
|
|
def __init__(self, value: Expression) -> None:
|
|
self.value = value
|
|
|
|
def render(self) -> List[str]:
|
|
"""
|
|
Renders the type back to source code format
|
|
|
|
This'll look like Python code.
|
|
"""
|
|
return [f'return {self.value.render()}']
|
|
|
|
class StatementIf(Statement):
|
|
"""
|
|
An if statement within a function
|
|
"""
|
|
__slots__ = ('test', 'statements', 'else_statements', )
|
|
|
|
test: Expression
|
|
statements: List[Statement]
|
|
else_statements: List[Statement]
|
|
|
|
def __init__(self, test: Expression) -> None:
|
|
self.test = test
|
|
self.statements = []
|
|
self.else_statements = []
|
|
|
|
def render(self) -> List[str]:
|
|
"""
|
|
Renders the type back to source code format
|
|
|
|
This'll look like Python code.
|
|
"""
|
|
result = [f'if {self.test.render()}:']
|
|
|
|
for stmt in self.statements:
|
|
result.extend(
|
|
f' {line}' if line else ''
|
|
for line in stmt.render()
|
|
)
|
|
|
|
result.append('')
|
|
|
|
return result
|
|
|
|
class StatementPass(Statement):
|
|
"""
|
|
A pass statement
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def render(self) -> List[str]:
|
|
return ['pass']
|
|
|
|
class Function:
|
|
"""
|
|
A function processes input and produces output
|
|
"""
|
|
__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]]
|
|
|
|
def __init__(self, name: str, lineno: int) -> None:
|
|
self.name = name
|
|
self.lineno = lineno
|
|
self.exported = False
|
|
self.buildin = False
|
|
self.statements = []
|
|
self.returns = OurTypeNone()
|
|
self.posonlyargs = []
|
|
|
|
def render(self) -> str:
|
|
"""
|
|
Renders the function back to source code format
|
|
|
|
This'll look like Python code.
|
|
"""
|
|
result = ''
|
|
if self.exported:
|
|
result += '@exported\n'
|
|
|
|
args = ', '.join(
|
|
f'{x}: {y.render()}'
|
|
for x, y in self.posonlyargs
|
|
)
|
|
|
|
result += f'def {self.name}({args}) -> {self.returns.render()}:\n'
|
|
for stmt in self.statements:
|
|
for line in stmt.render():
|
|
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 TupleConstructor(Function):
|
|
"""
|
|
The constructor method for a tuple
|
|
"""
|
|
__slots__ = ('tuple', )
|
|
|
|
tuple: OurTypeTuple
|
|
|
|
def __init__(self, tuple_: OurTypeTuple) -> None:
|
|
name = tuple_.render_internal_name()
|
|
|
|
super().__init__(f'@{name}@__init___@', -1)
|
|
|
|
self.returns = tuple_
|
|
|
|
for mem in tuple_.members:
|
|
self.posonlyargs.append((f'arg{mem.idx}', mem.type, ))
|
|
|
|
self.tuple = tuple_
|
|
|
|
class StructMember:
|
|
"""
|
|
Represents a struct member
|
|
"""
|
|
def __init__(self, name: str, type_: OurType, offset: int) -> None:
|
|
self.name = name
|
|
self.type = type_
|
|
self.offset = offset
|
|
|
|
class Struct(OurType):
|
|
"""
|
|
A struct has named properties
|
|
"""
|
|
__slots__ = ('name', 'lineno', 'members', )
|
|
|
|
name: str
|
|
lineno: int
|
|
members: List[StructMember]
|
|
|
|
def __init__(self, name: str, lineno: int) -> None:
|
|
self.name = name
|
|
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 type back to source code format
|
|
|
|
This'll look like Python code.
|
|
"""
|
|
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
|
|
|
|
def alloc_size(self) -> int:
|
|
return sum(
|
|
x.type.alloc_size()
|
|
for x in self.members
|
|
)
|
|
|
|
class Module:
|
|
"""
|
|
A module is a file and consists of functions
|
|
"""
|
|
__slots__ = ('types', 'functions', 'structs', )
|
|
|
|
types: Dict[str, OurType]
|
|
functions: Dict[str, Function]
|
|
structs: Dict[str, Struct]
|
|
|
|
def __init__(self) -> None:
|
|
self.types = {
|
|
'i32': OurTypeInt32(),
|
|
'i64': OurTypeInt64(),
|
|
'f32': OurTypeFloat32(),
|
|
'f64': OurTypeFloat64(),
|
|
}
|
|
self.functions = {}
|
|
self.structs = {}
|
|
|
|
def render(self) -> str:
|
|
"""
|
|
Renders the module back to source code format
|
|
|
|
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):
|
|
"""
|
|
An error found during static analysis
|
|
"""
|
|
|
|
OurLocals = Dict[str, OurType]
|
|
|
|
class OurVisitor:
|
|
"""
|
|
Class to visit a Python syntax tree and create an ourlang syntax tree
|
|
"""
|
|
|
|
# pylint: disable=C0103,C0116,C0301,R0201,R0912
|
|
|
|
def __init__(self) -> None:
|
|
pass
|
|
|
|
def visit_Module(self, node: ast.Module) -> Module:
|
|
module = Module()
|
|
|
|
_not_implemented(not node.type_ignores, 'Module.type_ignores')
|
|
|
|
# Second pass for the types
|
|
|
|
for stmt in node.body:
|
|
res = self.pre_visit_Module_stmt(module, stmt)
|
|
|
|
if isinstance(res, Function):
|
|
if res.name in module.functions:
|
|
raise StaticError(
|
|
f'{res.name} already defined on line {module.functions[res.name].lineno}'
|
|
)
|
|
|
|
module.functions[res.name] = res
|
|
|
|
if isinstance(res, Struct):
|
|
if res.name in module.structs:
|
|
raise StaticError(
|
|
f'{res.name} already defined on line {module.structs[res.name].lineno}'
|
|
)
|
|
|
|
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 pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, Struct]:
|
|
if isinstance(node, ast.FunctionDef):
|
|
return self.pre_visit_Module_FunctionDef(module, node)
|
|
|
|
if isinstance(node, ast.ClassDef):
|
|
return self.pre_visit_Module_ClassDef(module, node)
|
|
|
|
raise NotImplementedError(f'{node} on Module')
|
|
|
|
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')
|
|
|
|
for arg in node.args.args:
|
|
if not arg.annotation:
|
|
_raise_static_error(node, 'Type is required')
|
|
|
|
function.posonlyargs.append((
|
|
arg.arg,
|
|
self.visit_type(module, arg.annotation),
|
|
))
|
|
|
|
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
|
|
_not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs')
|
|
_not_implemented(not node.args.kw_defaults, 'FunctionDef.args.kw_defaults')
|
|
_not_implemented(not node.args.kwarg, 'FunctionDef.args.kwarg')
|
|
_not_implemented(not node.args.defaults, 'FunctionDef.args.defaults')
|
|
|
|
# Do stmts at the end so we have the return value
|
|
|
|
for decorator in node.decorator_list:
|
|
if not isinstance(decorator, ast.Name):
|
|
_raise_static_error(decorator, 'Function decorators must be string')
|
|
if not isinstance(decorator.ctx, ast.Load):
|
|
_raise_static_error(decorator, 'Must be load context')
|
|
_not_implemented(decorator.id != 'exports', 'Custom decorators')
|
|
function.exported = True
|
|
|
|
if node.returns:
|
|
function.returns = self.visit_type(module, node.returns)
|
|
|
|
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
|
|
|
|
return function
|
|
|
|
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')
|
|
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
|
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
|
|
|
offset = 0
|
|
|
|
for stmt in node.body:
|
|
if not isinstance(stmt, ast.AnnAssign):
|
|
raise NotImplementedError(f'Class with {stmt} nodes')
|
|
|
|
if not isinstance(stmt.target, ast.Name):
|
|
raise NotImplementedError('Class with default values')
|
|
|
|
if not stmt.value is None:
|
|
raise NotImplementedError('Class with default values')
|
|
|
|
if stmt.simple != 1:
|
|
raise NotImplementedError('Class with non-simple arguments')
|
|
|
|
member = StructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
|
|
|
|
struct.members.append(member)
|
|
offset += member.type.alloc_size()
|
|
|
|
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:
|
|
# TODO: Implement methods without return values
|
|
_raise_static_error(node, 'Return must have an argument')
|
|
|
|
return StatementReturn(
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.value)
|
|
)
|
|
|
|
if isinstance(node, ast.If):
|
|
result = StatementIf(
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.test)
|
|
)
|
|
|
|
for stmt in node.body:
|
|
result.statements.append(
|
|
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
|
|
)
|
|
|
|
for stmt in node.orelse:
|
|
result.else_statements.append(
|
|
self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt)
|
|
)
|
|
|
|
return result
|
|
|
|
raise NotImplementedError(f'{node} as stmt in FunctionDef')
|
|
|
|
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, exp_type: OurType, node: ast.expr) -> Expression:
|
|
if isinstance(node, ast.BinOp):
|
|
if isinstance(node.op, ast.Add):
|
|
operator = '+'
|
|
elif isinstance(node.op, ast.Sub):
|
|
operator = '-'
|
|
elif isinstance(node.op, ast.Mult):
|
|
operator = '*'
|
|
else:
|
|
raise NotImplementedError(f'Operator {node.op}')
|
|
|
|
# Assume the type doesn't change when descending into a binary operator
|
|
# e.g. you can do `"hello" * 3` with the code below (yet)
|
|
|
|
return BinaryOp(
|
|
exp_type,
|
|
operator,
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left),
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.right),
|
|
)
|
|
|
|
if isinstance(node, ast.UnaryOp):
|
|
if isinstance(node.op, ast.UAdd):
|
|
operator = '+'
|
|
elif isinstance(node.op, ast.USub):
|
|
operator = '-'
|
|
else:
|
|
raise NotImplementedError(f'Operator {node.op}')
|
|
|
|
return UnaryOp(
|
|
exp_type,
|
|
operator,
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.operand),
|
|
)
|
|
|
|
if isinstance(node, ast.Compare):
|
|
if 1 < len(node.ops):
|
|
raise NotImplementedError('Multiple operators')
|
|
|
|
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}')
|
|
|
|
# Assume the type doesn't change when descending into a binary operator
|
|
# e.g. you can do `"hello" * 3` with the code below (yet)
|
|
|
|
return BinaryOp(
|
|
exp_type,
|
|
operator,
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left),
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.comparators[0]),
|
|
)
|
|
|
|
if isinstance(node, ast.Call):
|
|
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')
|
|
|
|
if node.id not in our_locals:
|
|
_raise_static_error(node, 'Undefined variable')
|
|
|
|
act_type = our_locals[node.id]
|
|
if exp_type != act_type:
|
|
_raise_static_error(node, f'Expected {exp_type.render()}, {node.id} is actually {act_type.render()}')
|
|
|
|
return VariableReference(act_type, 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')
|
|
|
|
tuple_constructor = TupleConstructor(exp_type)
|
|
|
|
func = module.functions[tuple_constructor.name]
|
|
|
|
result = FunctionCall(func)
|
|
result.arguments = [
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, mem.type, arg_node)
|
|
for arg_node, mem 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) -> Union[FunctionCall, UnaryOp]:
|
|
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]
|
|
elif node.func.id in WEBASSEMBLY_BUILDIN_FLOAT_OPS:
|
|
if not isinstance(exp_type, (OurTypeFloat32, OurTypeFloat64, )):
|
|
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
|
|
|
|
if 1 != len(node.args):
|
|
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
|
|
|
|
return UnaryOp(
|
|
exp_type,
|
|
'sqrt',
|
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.args[0]),
|
|
)
|
|
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'Expected {exp_type.render()}, {func.name} actually returns {func.returns.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()}, {node.value.id}.{member.name} is actually {member.type.render()}')
|
|
|
|
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
|
|
|
|
idx = node.slice.value.value
|
|
if not isinstance(idx, 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) <= idx:
|
|
_raise_static_error(node, f'Index {idx} out of bounds for tuple {node.value.id}')
|
|
|
|
member = node_typ.members[idx]
|
|
if exp_type != member.type:
|
|
_raise_static_error(node, f'Expected {exp_type.render()}, {node.value.id}[{idx}] is actually {member.type.render()}')
|
|
|
|
return AccessTupleMember(
|
|
VariableReference(node_typ, node.value.id),
|
|
member,
|
|
)
|
|
|
|
def visit_Module_FunctionDef_Constant(self, module: Module, function: Function, exp_type: OurType, node: ast.Constant) -> Expression:
|
|
del module
|
|
del function
|
|
|
|
_not_implemented(node.kind is None, 'Constant.kind')
|
|
|
|
if isinstance(exp_type, OurTypeInt32):
|
|
if not isinstance(node.value, int):
|
|
_raise_static_error(node, 'Expected integer value')
|
|
|
|
# FIXME: Range check
|
|
|
|
return ConstantInt32(exp_type, node.value)
|
|
|
|
if isinstance(exp_type, OurTypeInt64):
|
|
if not isinstance(node.value, int):
|
|
_raise_static_error(node, 'Expected integer value')
|
|
|
|
# FIXME: Range check
|
|
|
|
return ConstantInt64(exp_type, node.value)
|
|
|
|
if isinstance(exp_type, OurTypeFloat32):
|
|
if not isinstance(node.value, (float, int, )):
|
|
_raise_static_error(node, 'Expected float value')
|
|
|
|
# FIXME: Range check
|
|
|
|
return ConstantFloat32(exp_type, node.value)
|
|
|
|
if isinstance(exp_type, OurTypeFloat64):
|
|
if not isinstance(node.value, (float, int, )):
|
|
_raise_static_error(node, 'Expected float value')
|
|
|
|
# FIXME: Range check
|
|
|
|
return ConstantFloat64(exp_type, node.value)
|
|
|
|
raise NotImplementedError(f'{node} as const for type {exp_type.render()}')
|
|
|
|
def visit_type(self, module: Module, node: ast.expr) -> OurType:
|
|
if isinstance(node, ast.Name):
|
|
if not isinstance(node.ctx, ast.Load):
|
|
_raise_static_error(node, 'Must be load context')
|
|
|
|
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()
|
|
|
|
offset = 0
|
|
|
|
for idx, elt in enumerate(node.elts):
|
|
member = TupleMember(idx, self.visit_type(module, elt), offset)
|
|
|
|
result.members.append(member)
|
|
offset += member.type.alloc_size()
|
|
|
|
key = result.render_internal_name()
|
|
|
|
if key not in module.types:
|
|
module.types[key] = result
|
|
constructor = TupleConstructor(result)
|
|
module.functions[constructor.name] = constructor
|
|
|
|
return module.types[key]
|
|
|
|
raise NotImplementedError(f'{node} as type')
|
|
|
|
def _not_implemented(check: Any, msg: str) -> None:
|
|
if not check:
|
|
raise NotImplementedError(msg)
|
|
|
|
def _raise_static_error(node: Union[ast.mod, ast.stmt, ast.expr], msg: str) -> NoReturn:
|
|
raise StaticError(
|
|
f'Static error on line {node.lineno}: {msg}'
|
|
)
|