More work on type3
This commit is contained in:
parent
b5a28daebf
commit
9f21d0fd1d
7
TODO.md
7
TODO.md
@ -1,7 +1,12 @@
|
||||
# TODO
|
||||
|
||||
- Implement a trace() builtin for debugging
|
||||
- Implement a proper type matching / checking system
|
||||
- Implement subscript as an operator
|
||||
- Implement another operator
|
||||
- Figure out how to do type classes
|
||||
- Implement structs again, with the `.foo` notation working
|
||||
|
||||
- Implement a trace() builtin for debugging
|
||||
- Check if we can use DataView in the Javascript examples, e.g. with setUint32
|
||||
- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this.
|
||||
- Implement a FizzBuzz example
|
||||
|
||||
@ -36,16 +36,15 @@ def type3(inp: Type3OrPlaceholder) -> str:
|
||||
|
||||
return inp.name
|
||||
|
||||
# def struct_definition(inp: typing.TypeStruct) -> str:
|
||||
# """
|
||||
# Render: TypeStruct's definition
|
||||
# """
|
||||
# result = f'class {inp.name}:\n'
|
||||
# for mem in inp.members: # TODO: Broken after new type system
|
||||
# raise NotImplementedError('Structs broken after new type system')
|
||||
# # result += f' {mem.name}: {type_(mem.type)}\n'
|
||||
#
|
||||
# return result
|
||||
def struct_definition(inp: ourlang.StructDefinition) -> str:
|
||||
"""
|
||||
Render: TypeStruct's definition
|
||||
"""
|
||||
result = f'class {inp.struct_type3.name}:\n'
|
||||
for mem, typ in inp.struct_type3.members.items():
|
||||
result += f' {mem}: {type3(typ)}\n'
|
||||
|
||||
return result
|
||||
|
||||
def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
|
||||
"""
|
||||
@ -95,10 +94,10 @@ def expression(inp: ourlang.Expression) -> str:
|
||||
for arg in inp.arguments
|
||||
)
|
||||
|
||||
if isinstance(inp.function, ourlang.StructConstructor):
|
||||
return f'{inp.function.struct_type3.name}({args})'
|
||||
|
||||
# TODO: Broken after new type system
|
||||
# if isinstance(inp.function, ourlang.StructConstructor):
|
||||
# return f'{inp.function.struct.name}({args})'
|
||||
#
|
||||
# if isinstance(inp.function, ourlang.TupleConstructor):
|
||||
# return f'({args}, )'
|
||||
|
||||
@ -110,9 +109,8 @@ def expression(inp: ourlang.Expression) -> str:
|
||||
|
||||
return f'{varref}[{index}]'
|
||||
|
||||
# TODO: Broken after new type system
|
||||
# if isinstance(inp, ourlang.AccessStructMember):
|
||||
# return f'{expression(inp.varref)}.{inp.member.name}'
|
||||
if isinstance(inp, ourlang.AccessStructMember):
|
||||
return f'{expression(inp.varref)}.{inp.member}'
|
||||
|
||||
if isinstance(inp, ourlang.Fold):
|
||||
fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr'
|
||||
@ -180,10 +178,10 @@ def module(inp: ourlang.Module) -> str:
|
||||
"""
|
||||
result = ''
|
||||
|
||||
# for struct in inp.structs.values():
|
||||
# if result:
|
||||
# result += '\n'
|
||||
# result += struct_definition(struct)
|
||||
for struct in inp.struct_definitions.values():
|
||||
if result:
|
||||
result += '\n'
|
||||
result += struct_definition(struct)
|
||||
|
||||
for cdef in inp.constant_defs.values():
|
||||
if result:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""
|
||||
This module contains the code to convert parsed Ourlang into WebAssembly code
|
||||
"""
|
||||
from typing import List, Optional
|
||||
from typing import List, Union
|
||||
|
||||
import struct
|
||||
|
||||
@ -14,6 +14,16 @@ from .stdlib import alloc as stdlib_alloc
|
||||
from .stdlib import types as stdlib_types
|
||||
from .wasmgenerator import Generator as WasmGenerator
|
||||
|
||||
LOAD_STORE_TYPE_MAP = {
|
||||
'u8': 'i32', # Have to use an u32, since there is no native u8 type
|
||||
'i32': 'i32',
|
||||
'i64': 'i64',
|
||||
'u32': 'i32',
|
||||
'u64': 'i64',
|
||||
'f32': 'f32',
|
||||
'f64': 'f64',
|
||||
}
|
||||
|
||||
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
|
||||
"""
|
||||
Public method for compiling a parsed Phasm module into
|
||||
@ -53,16 +63,10 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
||||
if inp is type3types.f64:
|
||||
return wasm.WasmTypeFloat64()
|
||||
|
||||
# if tc_prim.primitive is typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
|
||||
# # StaticArray, Tuples and Structs are passed as pointer
|
||||
# # And pointers are i32
|
||||
# return wasm.WasmTypeInt32()
|
||||
|
||||
# TODO: Broken after new type system
|
||||
# if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeStaticArray, typing.TypeBytes)):
|
||||
# # Structs and tuples are passed as pointer
|
||||
# # And pointers are i32
|
||||
# return wasm.WasmTypeInt32()
|
||||
if isinstance(inp, type3types.StructType3):
|
||||
# Structs and tuples are passed as pointer
|
||||
# And pointers are i32
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
raise NotImplementedError(type3, inp)
|
||||
|
||||
@ -349,17 +353,19 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
# wgn.call(stdlib_types.__subscript_bytes__)
|
||||
# return
|
||||
|
||||
# if isinstance(inp, ourlang.AccessStructMember):
|
||||
# mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
|
||||
# if mtyp is None:
|
||||
# # In the future might extend this by having structs or tuples
|
||||
# # as members of struct or tuples
|
||||
# raise NotImplementedError(expression, inp, inp.member)
|
||||
#
|
||||
# expression(wgn, inp.varref)
|
||||
# wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
|
||||
# return
|
||||
#
|
||||
if isinstance(inp, ourlang.AccessStructMember):
|
||||
mtyp = LOAD_STORE_TYPE_MAP.get(inp.struct_type3.members[inp.member].name)
|
||||
if mtyp is None:
|
||||
# In the future might extend this by having structs or tuples
|
||||
# as members of struct or tuples
|
||||
raise NotImplementedError(expression, inp, inp.struct_type3)
|
||||
|
||||
expression(wgn, inp.varref)
|
||||
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset(
|
||||
inp.struct_type3, inp.member
|
||||
)))
|
||||
return
|
||||
|
||||
# if isinstance(inp, ourlang.AccessTupleMember):
|
||||
# mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
|
||||
# if mtyp is None:
|
||||
@ -544,8 +550,8 @@ def function(inp: ourlang.Function) -> wasm.Function:
|
||||
|
||||
if False: # TODO: isinstance(inp, ourlang.TupleConstructor):
|
||||
pass # _generate_tuple_constructor(wgn, inp)
|
||||
elif False: # TODO: isinstance(inp, ourlang.StructConstructor):
|
||||
pass # _generate_struct_constructor(wgn, inp)
|
||||
elif isinstance(inp, ourlang.StructConstructor):
|
||||
_generate_struct_constructor(wgn, inp)
|
||||
else:
|
||||
for stat in inp.statements:
|
||||
statement(wgn, stat)
|
||||
@ -733,26 +739,34 @@ def module(inp: ourlang.Module) -> wasm.Module:
|
||||
#
|
||||
# # Return the allocated address
|
||||
# wgn.local.get(tmp_var)
|
||||
#
|
||||
# def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
|
||||
# tmp_var = wgn.temp_var_i32('struct_adr')
|
||||
#
|
||||
# # Allocated the required amounts of bytes in memory
|
||||
# wgn.i32.const(inp.struct.alloc_size())
|
||||
# wgn.call(stdlib_alloc.__alloc__)
|
||||
# wgn.local.set(tmp_var)
|
||||
#
|
||||
# # Store each member individually
|
||||
# for member in inp.struct.members:
|
||||
# mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
|
||||
# if mtyp is None:
|
||||
# # In the future might extend this by having structs or tuples
|
||||
# # as members of struct or tuples
|
||||
# raise NotImplementedError(expression, inp, member)
|
||||
#
|
||||
# wgn.local.get(tmp_var)
|
||||
# wgn.add_statement('local.get', f'${member.name}')
|
||||
# wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
|
||||
#
|
||||
# # Return the allocated address
|
||||
# wgn.local.get(tmp_var)
|
||||
|
||||
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
|
||||
tmp_var = wgn.temp_var_i32('struct_adr')
|
||||
|
||||
# Allocated the required amounts of bytes in memory
|
||||
wgn.i32.const(_calculate_alloc_size(inp.struct_type3))
|
||||
wgn.call(stdlib_alloc.__alloc__)
|
||||
wgn.local.set(tmp_var)
|
||||
|
||||
# Store each member individually
|
||||
for memname, mtyp3 in inp.struct_type3.members.items():
|
||||
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
||||
if mtyp is None:
|
||||
# In the future might extend this by having structs or tuples
|
||||
# as members of struct or tuples
|
||||
raise NotImplementedError(expression, inp, mtyp3)
|
||||
|
||||
wgn.local.get(tmp_var)
|
||||
wgn.add_statement('local.get', f'${memname}')
|
||||
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(_calculate_member_offset(
|
||||
inp.struct_type3, memname
|
||||
)))
|
||||
|
||||
# Return the allocated address
|
||||
wgn.local.get(tmp_var)
|
||||
|
||||
def _calculate_alloc_size(type3: Union[type3types.StructType3, type3types.Type3]) -> int:
|
||||
return 0 # FIXME: Stub
|
||||
|
||||
def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
|
||||
return 0 # FIXME: Stub
|
||||
|
||||
@ -11,7 +11,7 @@ WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc',
|
||||
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
|
||||
|
||||
from .type3 import types as type3types
|
||||
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType
|
||||
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType, StructType3
|
||||
|
||||
class Expression:
|
||||
"""
|
||||
@ -134,6 +134,23 @@ class Subscript(Expression):
|
||||
self.varref = varref
|
||||
self.index = index
|
||||
|
||||
class AccessStructMember(Expression):
|
||||
"""
|
||||
Access a struct member for reading of writing
|
||||
"""
|
||||
__slots__ = ('varref', 'struct_type3', 'member', )
|
||||
|
||||
varref: VariableReference
|
||||
struct_type3: StructType3
|
||||
member: str
|
||||
|
||||
def __init__(self, varref: VariableReference, struct_type3: StructType3, member: str) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.varref = varref
|
||||
self.struct_type3 = struct_type3
|
||||
self.member = member
|
||||
|
||||
class Fold(Expression):
|
||||
"""
|
||||
A (left or right) fold
|
||||
@ -240,28 +257,41 @@ class Function:
|
||||
self.returns_str = 'None'
|
||||
self.posonlyargs = []
|
||||
|
||||
class StructDefinition:
|
||||
"""
|
||||
The definition for a struct
|
||||
"""
|
||||
__slots__ = ('struct_type3', 'lineno', )
|
||||
|
||||
struct_type3: StructType3
|
||||
lineno: int
|
||||
|
||||
def __init__(self, struct_type3: StructType3, lineno: int) -> None:
|
||||
self.struct_type3 = struct_type3
|
||||
self.lineno = lineno
|
||||
|
||||
class StructConstructor(Function):
|
||||
"""
|
||||
The constructor method for a struct
|
||||
|
||||
A function will generated to instantiate a struct. The arguments
|
||||
will be the defaults
|
||||
"""
|
||||
__slots__ = ('struct_type3', )
|
||||
|
||||
struct_type3: StructType3
|
||||
|
||||
def __init__(self, struct_type3: StructType3) -> None:
|
||||
super().__init__(f'@{struct_type3.name}@__init___@', -1)
|
||||
|
||||
self.returns_type3 = struct_type3
|
||||
|
||||
for mem, typ in struct_type3.members.items():
|
||||
self.posonlyargs.append(FunctionParam(mem, typ, ))
|
||||
|
||||
self.struct_type3 = struct_type3
|
||||
|
||||
# TODO: Broken after new type system
|
||||
# class StructConstructor(Function):
|
||||
# """
|
||||
# The constructor method for a struct
|
||||
#
|
||||
# A function will generated to instantiate a struct. The arguments
|
||||
# will be the defaults
|
||||
# """
|
||||
# __slots__ = ('struct', )
|
||||
#
|
||||
# struct: TypeStruct
|
||||
#
|
||||
# def __init__(self, struct: TypeStruct) -> None:
|
||||
# super().__init__(f'@{struct.name}@__init___@', -1)
|
||||
#
|
||||
# self.returns = struct
|
||||
#
|
||||
# for mem in struct.members:
|
||||
# self.posonlyargs.append(FunctionParam(mem.name, mem.type, ))
|
||||
#
|
||||
# self.struct = struct
|
||||
#
|
||||
# class TupleConstructor(Function):
|
||||
# """
|
||||
# The constructor method for a tuple
|
||||
@ -331,15 +361,15 @@ class Module:
|
||||
"""
|
||||
A module is a file and consists of functions
|
||||
"""
|
||||
__slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',)
|
||||
__slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions',)
|
||||
|
||||
data: ModuleData
|
||||
# structs: Dict[str, TypeStruct]
|
||||
struct_definitions: Dict[str, StructDefinition]
|
||||
constant_defs: Dict[str, ModuleConstantDef]
|
||||
functions: Dict[str, Function]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.data = ModuleData()
|
||||
# self.structs = {}
|
||||
self.struct_definitions = {}
|
||||
self.constant_defs = {}
|
||||
self.functions = {}
|
||||
|
||||
166
phasm/parser.py
166
phasm/parser.py
@ -18,8 +18,9 @@ from .ourlang import (
|
||||
BinaryOp,
|
||||
ConstantPrimitive, ConstantTuple,
|
||||
|
||||
FunctionCall, Subscript,
|
||||
# StructConstructor, TupleConstructor,
|
||||
FunctionCall, AccessStructMember, Subscript,
|
||||
StructDefinition, StructConstructor,
|
||||
# TupleConstructor,
|
||||
UnaryOp, VariableReference,
|
||||
|
||||
Fold,
|
||||
@ -75,16 +76,15 @@ class OurVisitor:
|
||||
|
||||
module.constant_defs[res.name] = res
|
||||
|
||||
# TODO: Broken after type system
|
||||
# if isinstance(res, TypeStruct):
|
||||
# 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
|
||||
if isinstance(res, StructDefinition):
|
||||
if res.struct_type3.name in module.struct_definitions:
|
||||
raise StaticError(
|
||||
f'{res.struct_type3.name} already defined on line {module.struct_definitions[res.struct_type3.name].lineno}'
|
||||
)
|
||||
|
||||
module.struct_definitions[res.struct_type3.name] = res
|
||||
constructor = StructConstructor(res.struct_type3)
|
||||
module.functions[constructor.name] = constructor
|
||||
|
||||
if isinstance(res, Function):
|
||||
if res.name in module.functions:
|
||||
@ -101,12 +101,12 @@ class OurVisitor:
|
||||
|
||||
return module
|
||||
|
||||
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, ModuleConstantDef]: # TypeStruct
|
||||
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
|
||||
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)
|
||||
if isinstance(node, ast.ClassDef):
|
||||
return self.pre_visit_Module_ClassDef(module, node)
|
||||
|
||||
if isinstance(node, ast.AnnAssign):
|
||||
return self.pre_visit_Module_AnnAssign(module, node)
|
||||
@ -155,36 +155,33 @@ class OurVisitor:
|
||||
|
||||
return function
|
||||
|
||||
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> None: # TypeStruct:
|
||||
raise NotImplementedError
|
||||
# struct = TypeStruct(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')
|
||||
#
|
||||
# raise NotImplementedError('TODO: Broken after new type system')
|
||||
# member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
|
||||
#
|
||||
# struct.members.append(member)
|
||||
# offset += member.type.alloc_size()
|
||||
#
|
||||
# return struct
|
||||
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition:
|
||||
|
||||
_not_implemented(not node.bases, 'ClassDef.bases')
|
||||
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
||||
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
||||
|
||||
members: Dict[str, type3types.Type3] = {}
|
||||
|
||||
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')
|
||||
|
||||
if stmt.target.id in members:
|
||||
_raise_static_error(stmt, 'Struct members must have unique names')
|
||||
|
||||
members[stmt.target.id] = self.visit_type(module, stmt.annotation)
|
||||
|
||||
return StructDefinition(type3types.StructType3(node.name, members), node.lineno)
|
||||
|
||||
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
|
||||
if not isinstance(node.target, ast.Name):
|
||||
@ -474,13 +471,14 @@ class OurVisitor:
|
||||
if not isinstance(node.func.ctx, ast.Load):
|
||||
_raise_static_error(node, 'Must be load context')
|
||||
|
||||
# if node.func.id in module.structs:
|
||||
# raise NotImplementedError('TODO: Broken after new type system')
|
||||
# struct = module.structs[node.func.id]
|
||||
# struct_constructor = StructConstructor(struct)
|
||||
#
|
||||
# func = module.functions[struct_constructor.name]
|
||||
if node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS:
|
||||
if node.func.id in module.struct_definitions:
|
||||
struct_definition = module.struct_definitions[node.func.id]
|
||||
struct_constructor = StructConstructor(struct_definition.struct_type3)
|
||||
|
||||
# FIXME: Defer struct de-allocation
|
||||
|
||||
func = module.functions[struct_constructor.name]
|
||||
elif node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS:
|
||||
if 1 != len(node.args):
|
||||
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
|
||||
|
||||
@ -552,33 +550,33 @@ class OurVisitor:
|
||||
return result
|
||||
|
||||
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
|
||||
raise NotImplementedError('Broken after new type system')
|
||||
# del module
|
||||
# del function
|
||||
#
|
||||
# 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}')
|
||||
#
|
||||
# param = our_locals[node.value.id]
|
||||
#
|
||||
# node_typ = param.type
|
||||
# if not isinstance(node_typ, TypeStruct):
|
||||
# _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}')
|
||||
#
|
||||
# return AccessStructMember(
|
||||
# VariableReference(param),
|
||||
# member,
|
||||
# )
|
||||
del module
|
||||
del function
|
||||
|
||||
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}')
|
||||
|
||||
param = our_locals[node.value.id]
|
||||
|
||||
node_typ = param.type3
|
||||
if not isinstance(node_typ, type3types.StructType3):
|
||||
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
|
||||
|
||||
member = node_typ.members.get(node.attr)
|
||||
if member is None:
|
||||
_raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
|
||||
|
||||
return AccessStructMember(
|
||||
VariableReference(param),
|
||||
node_typ,
|
||||
node.attr,
|
||||
)
|
||||
|
||||
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
|
||||
if not isinstance(node.value, ast.Name):
|
||||
@ -687,12 +685,10 @@ class OurVisitor:
|
||||
if node.id in type3types.LOOKUP_TABLE:
|
||||
return type3types.LOOKUP_TABLE[node.id]
|
||||
|
||||
raise NotImplementedError('TODO: Broken after type system')
|
||||
#
|
||||
# if node.id in module.structs:
|
||||
# return module.structs[node.id]
|
||||
#
|
||||
# _raise_static_error(node, f'Unrecognized type {node.id}')
|
||||
if node.id in module.struct_definitions:
|
||||
return module.struct_definitions[node.id].struct_type3
|
||||
|
||||
_raise_static_error(node, f'Unrecognized type {node.id}')
|
||||
|
||||
if isinstance(node, ast.Subscript):
|
||||
if not isinstance(node.value, ast.Name):
|
||||
|
||||
@ -13,7 +13,6 @@ from .constraints import (
|
||||
ConstraintBase,
|
||||
LiteralFitsConstraint, SameTypeConstraint,
|
||||
)
|
||||
from . import types
|
||||
|
||||
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
|
||||
ctx = Context()
|
||||
@ -48,9 +47,17 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas
|
||||
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.AccessStructMember):
|
||||
yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3,
|
||||
f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
|
||||
return
|
||||
|
||||
raise NotImplementedError(expression, inp)
|
||||
|
||||
def function(ctx: Context, inp: ourlang.Function) -> Generator[ConstraintBase, None, None]:
|
||||
if isinstance(inp, ourlang.StructConstructor):
|
||||
return
|
||||
|
||||
if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn):
|
||||
raise NotImplementedError('Functions with not just a return statement')
|
||||
|
||||
|
||||
@ -139,6 +139,31 @@ class AppliedType3(Type3):
|
||||
def __repr__(self) -> str:
|
||||
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
|
||||
|
||||
class StructType3(Type3):
|
||||
"""
|
||||
A Type3 struct with named members
|
||||
"""
|
||||
__slots__ = ('name', 'members', )
|
||||
|
||||
name: str
|
||||
"""
|
||||
The structs fully qualified name
|
||||
"""
|
||||
|
||||
members: Dict[str, Type3]
|
||||
"""
|
||||
The struct's field definitions
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, members: Dict[str, Type3]) -> None:
|
||||
super().__init__(name)
|
||||
|
||||
self.name = name
|
||||
self.members = dict(members)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'StructType3(repr({self.name}), repr({self.members}))'
|
||||
|
||||
none = Type3('none')
|
||||
"""
|
||||
The none type, for when functions simply don't return anything. e.g., IO().
|
||||
|
||||
@ -1,12 +1,36 @@
|
||||
import pytest
|
||||
|
||||
from phasm.exceptions import StaticError
|
||||
from phasm.type3.entry import Type3Exception
|
||||
|
||||
from phasm.parser import phasm_parse
|
||||
|
||||
from ..constants import (
|
||||
ALL_INT_TYPES
|
||||
)
|
||||
from ..helpers import Suite
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', ('i32', 'f64', ))
|
||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
||||
def test_module_constant(type_):
|
||||
code_py = f"""
|
||||
class CheckedValue:
|
||||
value: {type_}
|
||||
|
||||
CONSTANT: CheckedValue = CheckedValue(24)
|
||||
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
return CONSTANT.value
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
assert 24 == result.returned_value
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
||||
def test_struct_0(type_):
|
||||
code_py = f"""
|
||||
class CheckedValue:
|
||||
@ -75,43 +99,5 @@ def testEntry(arg: Struct) -> (i32, i32, ):
|
||||
return arg.param
|
||||
"""
|
||||
|
||||
with pytest.raises(StaticError, match=f'Static error on line 6: Expected \\(i32, i32, \\), arg.param is actually {type_}'):
|
||||
phasm_parse(code_py)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
|
||||
def test_type_mismatch_tuple_member(type_):
|
||||
code_py = f"""
|
||||
def testEntry(arg: ({type_}, )) -> (i32, i32, ):
|
||||
return arg[0]
|
||||
"""
|
||||
|
||||
with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), arg\\[0\\] is actually {type_}'):
|
||||
phasm_parse(code_py)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_tuple_constant_too_few_values():
|
||||
code_py = """
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 57, )
|
||||
"""
|
||||
|
||||
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
|
||||
phasm_parse(code_py)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_tuple_constant_too_many_values():
|
||||
code_py = """
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
|
||||
"""
|
||||
|
||||
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
|
||||
phasm_parse(code_py)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_tuple_constant_type_mismatch():
|
||||
code_py = """
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
|
||||
"""
|
||||
|
||||
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
|
||||
phasm_parse(code_py)
|
||||
with pytest.raises(Type3Exception, match=r'\(i32, i32, \) must be ' + type_ + ' instead'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@ -82,3 +82,30 @@ def testEntry() -> i32x4:
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
assert (1, 2, 3, 0) == result.returned_value
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_tuple_constant_too_few_values():
|
||||
code_py = """
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 57, )
|
||||
"""
|
||||
|
||||
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_tuple_constant_too_many_values():
|
||||
code_py = """
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
|
||||
"""
|
||||
|
||||
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_tuple_constant_type_mismatch():
|
||||
code_py = """
|
||||
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
|
||||
"""
|
||||
|
||||
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user