MVP #1

Merged
jbwdevries merged 73 commits from idea_crc32 into master 2022-08-21 12:59:21 +00:00
3 changed files with 302 additions and 276 deletions
Showing only changes of commit cc762cfa44 - Show all commits

View File

@ -4,32 +4,33 @@ This module contains the code to convert parsed Ourlang into WebAssembly code
from typing import Generator, Tuple from typing import Generator, Tuple
from . import ourlang from . import ourlang
from . import typing
from . import wasm from . import wasm
Statements = Generator[wasm.Statement, None, None] Statements = Generator[wasm.Statement, None, None]
def type_(inp: ourlang.OurType) -> wasm.WasmType: def type_(inp: typing.TypeBase) -> wasm.WasmType:
if isinstance(inp, ourlang.OurTypeNone): if isinstance(inp, typing.TypeNone):
return wasm.WasmTypeNone() return wasm.WasmTypeNone()
if isinstance(inp, ourlang.OurTypeUInt8): if isinstance(inp, typing.TypeUInt8):
# WebAssembly has only support for 32 and 64 bits # WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte # So we need to store more memory per byte
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if isinstance(inp, ourlang.OurTypeInt32): if isinstance(inp, typing.TypeInt32):
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if isinstance(inp, ourlang.OurTypeInt64): if isinstance(inp, typing.TypeInt64):
return wasm.WasmTypeInt64() return wasm.WasmTypeInt64()
if isinstance(inp, ourlang.OurTypeFloat32): if isinstance(inp, typing.TypeFloat32):
return wasm.WasmTypeFloat32() return wasm.WasmTypeFloat32()
if isinstance(inp, ourlang.OurTypeFloat64): if isinstance(inp, typing.TypeFloat64):
return wasm.WasmTypeFloat64() return wasm.WasmTypeFloat64()
if isinstance(inp, (ourlang.Struct, ourlang.OurTypeTuple, ourlang.OurTypeBytes)): if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeBytes)):
# Structs and tuples are passed as pointer # Structs and tuples are passed as pointer
# And pointers are i32 # And pointers are i32
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
@ -87,25 +88,25 @@ def expression(inp: ourlang.Expression) -> Statements:
yield from expression(inp.left) yield from expression(inp.left)
yield from expression(inp.right) yield from expression(inp.right)
if isinstance(inp.type, ourlang.OurTypeInt32): if isinstance(inp.type, typing.TypeInt32):
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i32.{operator}') yield wasm.Statement(f'i32.{operator}')
return return
if operator := I32_OPERATOR_MAP.get(inp.operator, None): if operator := I32_OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i32.{operator}') yield wasm.Statement(f'i32.{operator}')
return return
if isinstance(inp.type, ourlang.OurTypeInt64): if isinstance(inp.type, typing.TypeInt64):
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i64.{operator}') yield wasm.Statement(f'i64.{operator}')
return return
if operator := I64_OPERATOR_MAP.get(inp.operator, None): if operator := I64_OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i64.{operator}') yield wasm.Statement(f'i64.{operator}')
return return
if isinstance(inp.type, ourlang.OurTypeFloat32): if isinstance(inp.type, typing.TypeFloat32):
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'f32.{operator}') yield wasm.Statement(f'f32.{operator}')
return return
if isinstance(inp.type, ourlang.OurTypeFloat64): if isinstance(inp.type, typing.TypeFloat64):
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'f64.{operator}') yield wasm.Statement(f'f64.{operator}')
return return
@ -115,18 +116,18 @@ def expression(inp: ourlang.Expression) -> Statements:
if isinstance(inp, ourlang.UnaryOp): if isinstance(inp, ourlang.UnaryOp):
yield from expression(inp.right) yield from expression(inp.right)
if isinstance(inp.type, ourlang.OurTypeFloat32): if isinstance(inp.type, typing.TypeFloat32):
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS: if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
yield wasm.Statement(f'f32.{inp.operator}') yield wasm.Statement(f'f32.{inp.operator}')
return return
if isinstance(inp.type, ourlang.OurTypeFloat64): if isinstance(inp.type, typing.TypeFloat64):
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS: if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
yield wasm.Statement(f'f64.{inp.operator}') yield wasm.Statement(f'f64.{inp.operator}')
return return
if isinstance(inp.type, ourlang.OurTypeInt32): if isinstance(inp.type, typing.TypeInt32):
if inp.operator == 'len': if inp.operator == 'len':
if isinstance(inp.right.type, ourlang.OurTypeBytes): if isinstance(inp.right.type, typing.TypeBytes):
yield wasm.Statement('i32.load') yield wasm.Statement('i32.load')
return return
@ -140,7 +141,7 @@ def expression(inp: ourlang.Expression) -> Statements:
return return
if isinstance(inp, ourlang.AccessBytesIndex): if isinstance(inp, ourlang.AccessBytesIndex):
if not isinstance(inp.type, ourlang.OurTypeUInt8): if not isinstance(inp.type, typing.TypeUInt8):
raise NotImplementedError(inp, inp.type) raise NotImplementedError(inp, inp.type)
yield from expression(inp.varref) yield from expression(inp.varref)
@ -149,14 +150,14 @@ def expression(inp: ourlang.Expression) -> Statements:
return return
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.AccessStructMember):
if isinstance(inp.member.type, ourlang.OurTypeUInt8): if isinstance(inp.member.type, typing.TypeUInt8):
mtyp = 'i32' mtyp = 'i32'
else: else:
# FIXME: Properly implement this # FIXME: Properly implement this
# inp.type.render() is also a hack that doesn't really work consistently # inp.type.render() is also a hack that doesn't really work consistently
if not isinstance(inp.member.type, ( if not isinstance(inp.member.type, (
ourlang.OurTypeInt32, ourlang.OurTypeFloat32, typing.TypeInt32, typing.TypeFloat32,
ourlang.OurTypeInt64, ourlang.OurTypeFloat64, typing.TypeInt64, typing.TypeFloat64,
)): )):
raise NotImplementedError raise NotImplementedError
mtyp = inp.member.type.render() mtyp = inp.member.type.render()
@ -169,8 +170,8 @@ def expression(inp: ourlang.Expression) -> Statements:
# FIXME: Properly implement this # FIXME: Properly implement this
# inp.type.render() is also a hack that doesn't really work consistently # inp.type.render() is also a hack that doesn't really work consistently
if not isinstance(inp.type, ( if not isinstance(inp.type, (
ourlang.OurTypeInt32, ourlang.OurTypeFloat32, typing.TypeInt32, typing.TypeFloat32,
ourlang.OurTypeInt64, ourlang.OurTypeFloat64, typing.TypeInt64, typing.TypeFloat64,
)): )):
raise NotImplementedError(inp, inp.type) raise NotImplementedError(inp, inp.type)
@ -213,7 +214,7 @@ def statement(inp: ourlang.Statement) -> Statements:
raise NotImplementedError(statement, inp) raise NotImplementedError(statement, inp)
def function_argument(inp: Tuple[str, ourlang.OurType]) -> wasm.Param: def function_argument(inp: Tuple[str, typing.TypeBase]) -> wasm.Param:
return (inp[0], type_(inp[1]), ) return (inp[0], type_(inp[1]), )
def import_(inp: ourlang.Function) -> wasm.Import: def import_(inp: ourlang.Function) -> wasm.Import:
@ -352,8 +353,8 @@ def _generate_tuple_constructor(inp: ourlang.TupleConstructor) -> Statements:
# FIXME: Properly implement this # FIXME: Properly implement this
# inp.type.render() is also a hack that doesn't really work consistently # inp.type.render() is also a hack that doesn't really work consistently
if not isinstance(member.type, ( if not isinstance(member.type, (
ourlang.OurTypeInt32, ourlang.OurTypeFloat32, typing.TypeInt32, typing.TypeFloat32,
ourlang.OurTypeInt64, ourlang.OurTypeFloat64, typing.TypeInt64, typing.TypeFloat64,
)): )):
raise NotImplementedError raise NotImplementedError
@ -370,14 +371,14 @@ def _generate_struct_constructor(inp: ourlang.StructConstructor) -> Statements:
yield wasm.Statement('local.set', '$___new_reference___addr') yield wasm.Statement('local.set', '$___new_reference___addr')
for member in inp.struct.members: for member in inp.struct.members:
if isinstance(member.type, ourlang.OurTypeUInt8): if isinstance(member.type, typing.TypeUInt8):
mtyp = 'i32' mtyp = 'i32'
else: else:
# FIXME: Properly implement this # FIXME: Properly implement this
# inp.type.render() is also a hack that doesn't really work consistently # inp.type.render() is also a hack that doesn't really work consistently
if not isinstance(member.type, ( if not isinstance(member.type, (
ourlang.OurTypeInt32, ourlang.OurTypeFloat32, typing.TypeInt32, typing.TypeFloat32,
ourlang.OurTypeInt64, ourlang.OurTypeFloat64, typing.TypeInt64, typing.TypeFloat64,
)): )):
raise NotImplementedError raise NotImplementedError
mtyp = member.type.render() mtyp = member.type.render()

View File

@ -9,137 +9,16 @@ from typing_extensions import Final
WEBASSEMBLY_BUILDIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', ) WEBASSEMBLY_BUILDIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
class OurType: from .typing import (
""" TypeBase,
Type base class TypeNone,
""" TypeBool,
__slots__ = () TypeUInt8,
TypeInt32, TypeInt64,
def render(self) -> str: TypeFloat32, TypeFloat64,
""" TypeBytes,
Renders the type back to source code format TypeTuple, TypeTupleMember,
TypeStruct, TypeStructMember,
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 OurTypeUInt8(OurType):
"""
The Integer type, unsigned and 8 bits wide
"""
__slots__ = ()
def render(self) -> str:
return 'u8'
def alloc_size(self) -> int:
return 4 # Int32 under the hood
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 OurTypeBytes(OurType):
"""
The bytes type
"""
__slots__ = ()
def render(self) -> str:
return 'bytes'
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: class Expression:
@ -148,9 +27,9 @@ class Expression:
""" """
__slots__ = ('type', ) __slots__ = ('type', )
type: OurType type: TypeBase
def __init__(self, type_: OurType) -> None: def __init__(self, type_: TypeBase) -> None:
self.type = type_ self.type = type_
def render(self) -> str: def render(self) -> str:
@ -175,7 +54,7 @@ class ConstantUInt8(Constant):
value: int value: int
def __init__(self, type_: OurTypeUInt8, value: int) -> None: def __init__(self, type_: TypeUInt8, value: int) -> None:
super().__init__(type_) super().__init__(type_)
self.value = value self.value = value
@ -190,7 +69,7 @@ class ConstantInt32(Constant):
value: int value: int
def __init__(self, type_: OurTypeInt32, value: int) -> None: def __init__(self, type_: TypeInt32, value: int) -> None:
super().__init__(type_) super().__init__(type_)
self.value = value self.value = value
@ -205,7 +84,7 @@ class ConstantInt64(Constant):
value: int value: int
def __init__(self, type_: OurTypeInt64, value: int) -> None: def __init__(self, type_: TypeInt64, value: int) -> None:
super().__init__(type_) super().__init__(type_)
self.value = value self.value = value
@ -220,7 +99,7 @@ class ConstantFloat32(Constant):
value: float value: float
def __init__(self, type_: OurTypeFloat32, value: float) -> None: def __init__(self, type_: TypeFloat32, value: float) -> None:
super().__init__(type_) super().__init__(type_)
self.value = value self.value = value
@ -235,7 +114,7 @@ class ConstantFloat64(Constant):
value: float value: float
def __init__(self, type_: OurTypeFloat64, value: float) -> None: def __init__(self, type_: TypeFloat64, value: float) -> None:
super().__init__(type_) super().__init__(type_)
self.value = value self.value = value
@ -250,7 +129,7 @@ class VariableReference(Expression):
name: str name: str
def __init__(self, type_: OurType, name: str) -> None: def __init__(self, type_: TypeBase, name: str) -> None:
super().__init__(type_) super().__init__(type_)
self.name = name self.name = name
@ -267,7 +146,7 @@ class BinaryOp(Expression):
left: Expression left: Expression
right: Expression right: Expression
def __init__(self, type_: OurType, operator: str, left: Expression, right: Expression) -> None: def __init__(self, type_: TypeBase, operator: str, left: Expression, right: Expression) -> None:
super().__init__(type_) super().__init__(type_)
self.operator = operator self.operator = operator
@ -286,7 +165,7 @@ class UnaryOp(Expression):
operator: str operator: str
right: Expression right: Expression
def __init__(self, type_: OurType, operator: str, right: Expression) -> None: def __init__(self, type_: TypeBase, operator: str, right: Expression) -> None:
super().__init__(type_) super().__init__(type_)
self.operator = operator self.operator = operator
@ -336,7 +215,7 @@ class AccessBytesIndex(Expression):
varref: VariableReference varref: VariableReference
index: Expression index: Expression
def __init__(self, type_: OurType, varref: VariableReference, index: Expression) -> None: def __init__(self, type_: TypeBase, varref: VariableReference, index: Expression) -> None:
super().__init__(type_) super().__init__(type_)
self.varref = varref self.varref = varref
@ -352,9 +231,9 @@ class AccessStructMember(Expression):
__slots__ = ('varref', 'member', ) __slots__ = ('varref', 'member', )
varref: VariableReference varref: VariableReference
member: 'StructMember' member: TypeStructMember
def __init__(self, varref: VariableReference, member: 'StructMember') -> None: def __init__(self, varref: VariableReference, member: TypeStructMember) -> None:
super().__init__(member.type) super().__init__(member.type)
self.varref = varref self.varref = varref
@ -370,9 +249,9 @@ class AccessTupleMember(Expression):
__slots__ = ('varref', 'member', ) __slots__ = ('varref', 'member', )
varref: VariableReference varref: VariableReference
member: TupleMember member: TypeTupleMember
def __init__(self, varref: VariableReference, member: TupleMember, ) -> None: def __init__(self, varref: VariableReference, member: TypeTupleMember, ) -> None:
super().__init__(member.type) super().__init__(member.type)
self.varref = varref self.varref = varref
@ -465,8 +344,8 @@ class Function:
exported: bool exported: bool
imported: bool imported: bool
statements: List[Statement] statements: List[Statement]
returns: OurType returns: TypeBase
posonlyargs: List[Tuple[str, OurType]] posonlyargs: List[Tuple[str, TypeBase]]
def __init__(self, name: str, lineno: int) -> None: def __init__(self, name: str, lineno: int) -> None:
self.name = name self.name = name
@ -474,7 +353,7 @@ class Function:
self.exported = False self.exported = False
self.imported = False self.imported = False
self.statements = [] self.statements = []
self.returns = OurTypeNone() self.returns = TypeNone()
self.posonlyargs = [] self.posonlyargs = []
def render(self) -> str: def render(self) -> str:
@ -509,9 +388,9 @@ class StructConstructor(Function):
""" """
__slots__ = ('struct', ) __slots__ = ('struct', )
struct: 'Struct' struct: TypeStruct
def __init__(self, struct: 'Struct') -> None: def __init__(self, struct: TypeStruct) -> None:
super().__init__(f'@{struct.name}@__init___@', -1) super().__init__(f'@{struct.name}@__init___@', -1)
self.returns = struct self.returns = struct
@ -527,9 +406,9 @@ class TupleConstructor(Function):
""" """
__slots__ = ('tuple', ) __slots__ = ('tuple', )
tuple: OurTypeTuple tuple: TypeTuple
def __init__(self, tuple_: OurTypeTuple) -> None: def __init__(self, tuple_: TypeTuple) -> None:
name = tuple_.render_internal_name() name = tuple_.render_internal_name()
super().__init__(f'@{name}@__init___@', -1) super().__init__(f'@{name}@__init___@', -1)
@ -541,85 +420,25 @@ class TupleConstructor(Function):
self.tuple = tuple_ 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: class Module:
""" """
A module is a file and consists of functions A module is a file and consists of functions
""" """
__slots__ = ('types', 'functions', 'structs', ) __slots__ = ('types', 'functions', 'structs', )
types: Dict[str, OurType] types: Dict[str, TypeBase]
functions: Dict[str, Function] functions: Dict[str, Function]
structs: Dict[str, Struct] structs: Dict[str, TypeStruct]
def __init__(self) -> None: def __init__(self) -> None:
self.types = { self.types = {
'None': OurTypeNone(), 'None': TypeNone(),
'u8': OurTypeUInt8(), 'u8': TypeUInt8(),
'i32': OurTypeInt32(), 'i32': TypeInt32(),
'i64': OurTypeInt64(), 'i64': TypeInt64(),
'f32': OurTypeFloat32(), 'f32': TypeFloat32(),
'f64': OurTypeFloat64(), 'f64': TypeFloat64(),
'bytes': OurTypeBytes(), 'bytes': TypeBytes(),
} }
self.functions = {} self.functions = {}
self.structs = {} self.structs = {}
@ -653,7 +472,7 @@ class StaticError(Exception):
An error found during static analysis An error found during static analysis
""" """
OurLocals = Dict[str, OurType] OurLocals = Dict[str, TypeBase]
class OurVisitor: class OurVisitor:
""" """
@ -683,7 +502,7 @@ class OurVisitor:
module.functions[res.name] = res module.functions[res.name] = res
if isinstance(res, Struct): if isinstance(res, TypeStruct):
if res.name in module.structs: if res.name in module.structs:
raise StaticError( raise StaticError(
f'{res.name} already defined on line {module.structs[res.name].lineno}' f'{res.name} already defined on line {module.structs[res.name].lineno}'
@ -700,7 +519,7 @@ class OurVisitor:
return module return module
def pre_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, TypeStruct]:
if isinstance(node, ast.FunctionDef): if isinstance(node, ast.FunctionDef):
return self.pre_visit_Module_FunctionDef(module, node) return self.pre_visit_Module_FunctionDef(module, node)
@ -750,8 +569,8 @@ class OurVisitor:
return function return function
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> Struct: def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> TypeStruct:
struct = Struct(node.name, node.lineno) struct = TypeStruct(node.name, node.lineno)
_not_implemented(not node.bases, 'ClassDef.bases') _not_implemented(not node.bases, 'ClassDef.bases')
_not_implemented(not node.keywords, 'ClassDef.keywords') _not_implemented(not node.keywords, 'ClassDef.keywords')
@ -772,7 +591,7 @@ class OurVisitor:
if stmt.simple != 1: if stmt.simple != 1:
raise NotImplementedError('Class with non-simple arguments') raise NotImplementedError('Class with non-simple arguments')
member = StructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset) member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
struct.members.append(member) struct.members.append(member)
offset += member.type.alloc_size() offset += member.type.alloc_size()
@ -831,7 +650,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as stmt in FunctionDef') 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: def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp): if isinstance(node, ast.BinOp):
if isinstance(node.op, ast.Add): if isinstance(node.op, ast.Add):
operator = '+' operator = '+'
@ -924,7 +743,7 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if not isinstance(exp_type, OurTypeTuple): if not isinstance(exp_type, TypeTuple):
_raise_static_error(node, f'Expression is expecting a {exp_type.render()}, not a tuple') _raise_static_error(node, f'Expression is expecting a {exp_type.render()}, not a tuple')
if len(exp_type.members) != len(node.elts): if len(exp_type.members) != len(node.elts):
@ -943,7 +762,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as expr in FunctionDef') 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]: def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Call) -> Union[FunctionCall, UnaryOp]:
if node.keywords: if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet? _raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -958,7 +777,7 @@ class OurVisitor:
func = module.functions[struct_constructor.name] func = module.functions[struct_constructor.name]
elif node.func.id in WEBASSEMBLY_BUILDIN_FLOAT_OPS: elif node.func.id in WEBASSEMBLY_BUILDIN_FLOAT_OPS:
if not isinstance(exp_type, (OurTypeFloat32, OurTypeFloat64, )): if not isinstance(exp_type, (TypeFloat32, TypeFloat64, )):
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}') _raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
if 1 != len(node.args): if 1 != len(node.args):
@ -970,7 +789,7 @@ class OurVisitor:
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.args[0]), self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.args[0]),
) )
elif node.func.id == 'len': elif node.func.id == 'len':
if not isinstance(exp_type, OurTypeInt32): if not isinstance(exp_type, TypeInt32):
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}') _raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
if 1 != len(node.args): if 1 != len(node.args):
@ -1000,7 +819,7 @@ class OurVisitor:
) )
return result return result
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, exp_type: OurType, node: ast.Attribute) -> Expression: def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Attribute) -> Expression:
if not isinstance(node.value, ast.Name): if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name') _raise_static_error(node, 'Must reference a name')
@ -1011,7 +830,7 @@ class OurVisitor:
_raise_static_error(node, f'Undefined variable {node.value.id}') _raise_static_error(node, f'Undefined variable {node.value.id}')
node_typ = our_locals[node.value.id] node_typ = our_locals[node.value.id]
if not isinstance(node_typ, Struct): if not isinstance(node_typ, TypeStruct):
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}') _raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
member = node_typ.get_member(node.attr) member = node_typ.get_member(node.attr)
@ -1026,7 +845,7 @@ class OurVisitor:
member, member,
) )
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, exp_type: OurType, node: ast.Subscript) -> Expression: def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Subscript) -> Expression:
if not isinstance(node.value, ast.Name): if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name') _raise_static_error(node, 'Must reference a name')
@ -1045,14 +864,14 @@ class OurVisitor:
module, function, our_locals, module.types['i32'], node.slice.value, module, function, our_locals, module.types['i32'], node.slice.value,
) )
if isinstance(node_typ, OurTypeBytes): if isinstance(node_typ, TypeBytes):
return AccessBytesIndex( return AccessBytesIndex(
module.types['u8'], module.types['u8'],
VariableReference(node_typ, node.value.id), VariableReference(node_typ, node.value.id),
slice_expr, slice_expr,
) )
if isinstance(node_typ, OurTypeTuple): if isinstance(node_typ, TypeTuple):
if not isinstance(slice_expr, ConstantInt32): if not isinstance(slice_expr, ConstantInt32):
_raise_static_error(node, 'Must subscript using a constant index') _raise_static_error(node, 'Must subscript using a constant index')
@ -1072,13 +891,13 @@ class OurVisitor:
_raise_static_error(node, f'Cannot take index of {node_typ.render()} {node.value.id}') _raise_static_error(node, f'Cannot take index of {node_typ.render()} {node.value.id}')
def visit_Module_FunctionDef_Constant(self, module: Module, function: Function, exp_type: OurType, node: ast.Constant) -> Expression: def visit_Module_FunctionDef_Constant(self, module: Module, function: Function, exp_type: TypeBase, node: ast.Constant) -> Expression:
del module del module
del function del function
_not_implemented(node.kind is None, 'Constant.kind') _not_implemented(node.kind is None, 'Constant.kind')
if isinstance(exp_type, OurTypeUInt8): if isinstance(exp_type, TypeUInt8):
if not isinstance(node.value, int): if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value') _raise_static_error(node, 'Expected integer value')
@ -1086,7 +905,7 @@ class OurVisitor:
return ConstantUInt8(exp_type, node.value) return ConstantUInt8(exp_type, node.value)
if isinstance(exp_type, OurTypeInt32): if isinstance(exp_type, TypeInt32):
if not isinstance(node.value, int): if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value') _raise_static_error(node, 'Expected integer value')
@ -1094,7 +913,7 @@ class OurVisitor:
return ConstantInt32(exp_type, node.value) return ConstantInt32(exp_type, node.value)
if isinstance(exp_type, OurTypeInt64): if isinstance(exp_type, TypeInt64):
if not isinstance(node.value, int): if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value') _raise_static_error(node, 'Expected integer value')
@ -1102,7 +921,7 @@ class OurVisitor:
return ConstantInt64(exp_type, node.value) return ConstantInt64(exp_type, node.value)
if isinstance(exp_type, OurTypeFloat32): if isinstance(exp_type, TypeFloat32):
if not isinstance(node.value, (float, int, )): if not isinstance(node.value, (float, int, )):
_raise_static_error(node, 'Expected float value') _raise_static_error(node, 'Expected float value')
@ -1110,7 +929,7 @@ class OurVisitor:
return ConstantFloat32(exp_type, node.value) return ConstantFloat32(exp_type, node.value)
if isinstance(exp_type, OurTypeFloat64): if isinstance(exp_type, TypeFloat64):
if not isinstance(node.value, (float, int, )): if not isinstance(node.value, (float, int, )):
_raise_static_error(node, 'Expected float value') _raise_static_error(node, 'Expected float value')
@ -1120,7 +939,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as const for type {exp_type.render()}') raise NotImplementedError(f'{node} as const for type {exp_type.render()}')
def visit_type(self, module: Module, node: ast.expr) -> OurType: def visit_type(self, module: Module, node: ast.expr) -> TypeBase:
if isinstance(node, ast.Constant): if isinstance(node, ast.Constant):
if node.value is None: if node.value is None:
return module.types['None'] return module.types['None']
@ -1143,12 +962,12 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
result = OurTypeTuple() result = TypeTuple()
offset = 0 offset = 0
for idx, elt in enumerate(node.elts): for idx, elt in enumerate(node.elts):
member = TupleMember(idx, self.visit_type(module, elt), offset) member = TypeTupleMember(idx, self.visit_type(module, elt), offset)
result.members.append(member) result.members.append(member)
offset += member.type.alloc_size() offset += member.type.alloc_size()

206
phasm/typing.py Normal file
View File

@ -0,0 +1,206 @@
"""
The phasm type system
"""
from typing import Optional, List
class TypeBase:
"""
TypeBase 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 TypeNone(TypeBase):
"""
The None (or Void) type
"""
__slots__ = ()
def render(self) -> str:
return 'None'
class TypeBool(TypeBase):
"""
The boolean type
"""
__slots__ = ()
def render(self) -> str:
return 'bool'
class TypeUInt8(TypeBase):
"""
The Integer type, unsigned and 8 bits wide
"""
__slots__ = ()
def render(self) -> str:
return 'u8'
def alloc_size(self) -> int:
return 4 # Int32 under the hood
class TypeInt32(TypeBase):
"""
The Integer type, signed and 32 bits wide
"""
__slots__ = ()
def render(self) -> str:
return 'i32'
def alloc_size(self) -> int:
return 4
class TypeInt64(TypeBase):
"""
The Integer type, signed and 64 bits wide
"""
__slots__ = ()
def render(self) -> str:
return 'i64'
def alloc_size(self) -> int:
return 8
class TypeFloat32(TypeBase):
"""
The Float type, 32 bits wide
"""
__slots__ = ()
def render(self) -> str:
return 'f32'
def alloc_size(self) -> int:
return 4
class TypeFloat64(TypeBase):
"""
The Float type, 64 bits wide
"""
__slots__ = ()
def render(self) -> str:
return 'f64'
def alloc_size(self) -> int:
return 8
class TypeBytes(TypeBase):
"""
The bytes type
"""
__slots__ = ()
def render(self) -> str:
return 'bytes'
class TypeTupleMember:
"""
Represents a tuple member
"""
def __init__(self, idx: int, type_: TypeBase, offset: int) -> None:
self.idx = idx
self.type = type_
self.offset = offset
class TypeTuple(TypeBase):
"""
The tuple type
"""
__slots__ = ('members', )
members: List[TypeTupleMember]
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 TypeStructMember:
"""
Represents a struct member
"""
def __init__(self, name: str, type_: TypeBase, offset: int) -> None:
self.name = name
self.type = type_
self.offset = offset
class TypeStruct(TypeBase):
"""
A struct has named properties
"""
__slots__ = ('name', 'lineno', 'members', )
name: str
lineno: int
members: List[TypeStructMember]
def __init__(self, name: str, lineno: int) -> None:
self.name = name
self.lineno = lineno
self.members = []
def get_member(self, name: str) -> Optional[TypeStructMember]:
"""
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
)