Moves the prelude to runtime

Previously, it was hardcoded at 'compile' time (in as much
Python has that). This would make it more difficult to add
stuff to it. Also, in a lot of places we made assumptions
about prelude instead of checking properly.
This commit is contained in:
Johan B.W. de Vries 2025-05-26 19:25:14 +02:00
parent d97be81828
commit 073cd31091
18 changed files with 850 additions and 1328 deletions

0
phasm/build/__init__.py Normal file
View File

304
phasm/build/base.py Normal file
View File

@ -0,0 +1,304 @@
"""
The base class for build environments.
Contains nothing but the explicit compiler builtins.
"""
from typing import Any, Callable, NamedTuple, Type
from warnings import warn
from ..type3.functions import (
TypeConstructorVariable,
TypeVariable,
)
from ..type3.routers import (
NoRouteForTypeException,
TypeApplicationRouter,
TypeClassArgsRouter,
TypeVariableLookup,
)
from ..type3.typeclasses import Type3Class, Type3ClassMethod
from ..type3.types import (
IntType3,
Type3,
TypeConstructor_Base,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Struct,
TypeConstructor_Tuple,
)
from ..wasm import WasmType, WasmTypeInt32
from . import builtins
TypeInfo = NamedTuple('TypeInfo', [
# Name of the type
('typ', str, ),
# What WebAssembly type to use when passing this value around
# For example in function arguments
('wasm_type', Type[WasmType]),
# What WebAssembly function to use when loading a value from memory
('wasm_load_func', str),
# What WebAssembly function to use when storing a value to memory
('wasm_store_func', str),
# When storing this value in memory, how many bytes do we use?
# Only valid for non-constructed types, see calculate_alloc_size
# Should match wasm_load_func / wasm_store_func
('alloc_size', int),
# When storing integers, the values can be stored as natural number
# (False) or as integer number (True). For other types, this is None.
('signed', bool | None),
])
class MissingImplementationWarning(Warning):
pass
class BuildBase[G]:
__slots__ = (
'dynamic_array',
'function',
'static_array',
'struct',
'tuple_',
'none_',
'type_info_map',
'type_info_constructed',
'types',
'type_class_instances',
'type_class_instance_methods',
'methods',
'operators',
'alloc_size_router',
)
dynamic_array: TypeConstructor_DynamicArray
"""
This is a dynamic length piece of memory.
It should be applied with two arguments. It has a runtime
determined length, and each argument is the same.
"""
function: TypeConstructor_Function
"""
This is a function.
It should be applied with one or more arguments. The last argument is the 'return' type.
"""
static_array: TypeConstructor_StaticArray
"""
This is a fixed length piece of memory.
It should be applied with two arguments. It has a compile time
determined length, and each argument is the same.
"""
struct: TypeConstructor_Struct
"""
This is like a tuple, but each argument is named, so that developers
can get and set fields by name.
"""
tuple_: TypeConstructor_Tuple
"""
This is a fixed length piece of memory.
It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
"""
none_: Type3
"""
The none type, for when functions simply don't return anything. e.g., IO().
"""
type_info_map: dict[str, TypeInfo]
"""
Map from type name to the info of that type
"""
type_info_constructed: TypeInfo
"""
By default, constructed types are passed as pointers
NOTE: ALLOC SIZE IN THIS STRUCT DOES NOT WORK FOR CONSTRUCTED TYPES
USE calculate_alloc_size FOR ACCURATE RESULTS
Functions count as constructed types - even though they are
not memory pointers but table addresses instead.
"""
types: dict[str, Type3]
"""
Types that are available without explicit import.
"""
type_class_instances: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]]
"""
Type class instances that are available without explicit import.
"""
type_class_instance_methods: dict[Type3ClassMethod, TypeClassArgsRouter[G, None]]
"""
Methods (and operators) for type class instances that are available without explicit import.
"""
methods: dict[str, Type3ClassMethod]
"""
Methods that are available without explicit import.
"""
operators: dict[str, Type3ClassMethod]
"""
Operators that are available without explicit import.
"""
alloc_size_router: TypeApplicationRouter['BuildBase[G]', int]
"""
Helper value for calculate_alloc_size.
"""
def __init__(self) -> None:
self.dynamic_array = builtins.dynamic_array
self.function = builtins.function
self.static_array = builtins.static_array
self.struct = builtins.struct
self.tuple_ = builtins.tuple_
self.none_ = builtins.none
self.type_info_map = {
'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
}
self.type_info_constructed = self.type_info_map['ptr']
self.types = {
'None': self.none_,
}
self.type_class_instances = set()
self.type_class_instance_methods = {}
self.methods = {}
self.operators = {}
self.alloc_size_router = TypeApplicationRouter['BuildBase[G]', int]()
self.alloc_size_router.add(self.static_array, self.__class__.calculate_alloc_size_static_array)
self.alloc_size_router.add(self.struct, self.__class__.calculate_alloc_size_struct)
self.alloc_size_router.add(self.tuple_, self.__class__.calculate_alloc_size_tuple)
def instance_type_class(
self,
cls: Type3Class,
*typ: Type3 | TypeConstructor_Base[Any],
methods: dict[str, Callable[[G, TypeVariableLookup], None]] = {},
operators: dict[str, Callable[[G, TypeVariableLookup], None]] = {},
) -> None:
"""
Registered the given type class and its implementation
"""
assert len(cls.args) == len(typ)
# TODO: Check for required existing instantiations
# First just register the type
self.type_class_instances.add((cls, tuple(typ), ))
# Then make the implementation findable
# We route based on the type class arguments.
tv_map: dict[TypeVariable, Type3] = {}
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {}
for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
if isinstance(arg_tv, TypeVariable):
assert isinstance(arg_tp, Type3)
tv_map[arg_tv] = arg_tp
elif isinstance(arg_tv, TypeConstructorVariable):
assert isinstance(arg_tp, TypeConstructor_Base)
tc_map[arg_tv] = arg_tp
else:
raise NotImplementedError(arg_tv, arg_tp)
for method_name, method in cls.methods.items():
router = self.type_class_instance_methods.get(method)
if router is None:
router = TypeClassArgsRouter[G, None](cls.args)
self.type_class_instance_methods[method] = router
try:
generator = methods[method_name]
except KeyError:
warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ)))
continue
router.add(tv_map, tc_map, generator)
for operator_name, operator in cls.operators.items():
router = self.type_class_instance_methods.get(operator)
if router is None:
router = TypeClassArgsRouter[G, None](cls.args)
self.type_class_instance_methods[operator] = router
try:
generator = operators[operator_name]
except KeyError:
warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ)))
continue
router.add(tv_map, tc_map, generator)
def calculate_alloc_size_static_array(self, args: tuple[Type3, IntType3]) -> int:
"""
Helper method for calculate_alloc_size - static_array
"""
sa_type, sa_len = args
return sa_len.value * self.calculate_alloc_size(sa_type, is_member=True)
def calculate_alloc_size_tuple(self, args: tuple[Type3, ...]) -> int:
"""
Helper method for calculate_alloc_size - tuple
"""
return sum(
self.calculate_alloc_size(x, is_member=True)
for x in args
)
def calculate_alloc_size_struct(self, args: tuple[tuple[str, Type3], ...]) -> int:
"""
Helper method for calculate_alloc_size - struct
"""
return sum(
self.calculate_alloc_size(x, is_member=True)
for _, x in args
)
def calculate_alloc_size(self, typ: Type3, is_member: bool = False) -> int:
"""
Calculates how much bytes you need to allocate when reserving memory for the given type.
"""
typ_info = self.type_info_map.get(typ.name)
if typ_info is not None:
return typ_info.alloc_size
if is_member:
return self.type_info_constructed.alloc_size
try:
return self.alloc_size_router(self, typ)
except NoRouteForTypeException:
raise NotImplementedError(typ)
def calculate_member_offset(self, st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int:
"""
Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name.
"""
result = 0
for memnam, memtyp in st_args:
if needle == memnam:
return result
result += self.calculate_alloc_size(memtyp, is_member=True)
raise Exception(f'{needle} not in {st_name}')

25
phasm/build/builtins.py Normal file
View File

@ -0,0 +1,25 @@
"""
Type (constructors) that are used integral to the compiler.
These cannot be changed as there is compiler logic depending on them.
For mode documentation, see base.py.
"""
from ..type3.types import (
Type3,
TypeApplication_Nullary,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Struct,
TypeConstructor_Tuple,
)
dynamic_array = TypeConstructor_DynamicArray('dynamic_array')
function = TypeConstructor_Function('function')
static_array = TypeConstructor_StaticArray('static_array')
struct = TypeConstructor_Struct('struct')
tuple_ = TypeConstructor_Tuple('tuple')
none = Type3('none', TypeApplication_Nullary(None, None))

111
phasm/build/default.py Normal file
View File

@ -0,0 +1,111 @@
"""
The default class for build environments.
Contains the compiler builtins as well as some sane defaults.
# Added types
f32: A 32-bits IEEE 754 float, of 32 bits width.
"""
from ..stdlib import types as stdlib_types
from ..type3.functions import (
TypeVariable,
TypeVariableApplication_Nullary,
)
from ..type3.typeclasses import Type3Class
from ..type3.types import (
Type3,
TypeApplication_Nullary,
)
from ..wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
)
from ..wasmgenerator import Generator
from .base import BuildBase, TypeInfo
class BuildDefault(BuildBase[Generator]):
def __init__(self) -> None:
super().__init__()
u8 = Type3('u8', TypeApplication_Nullary(None, None))
u16 = Type3('u16', TypeApplication_Nullary(None, None))
u32 = Type3('u32', TypeApplication_Nullary(None, None))
u64 = Type3('u64', TypeApplication_Nullary(None, None))
i8 = Type3('i8', TypeApplication_Nullary(None, None))
i16 = Type3('i16', TypeApplication_Nullary(None, None))
i32 = Type3('i32', TypeApplication_Nullary(None, None))
i64 = Type3('i64', TypeApplication_Nullary(None, None))
f32 = Type3('f32', TypeApplication_Nullary(None, None))
f64 = Type3('f64', TypeApplication_Nullary(None, None))
bytes_ = self.dynamic_array(u8)
self.type_info_map.update({
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8_u', 1, False),
'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16_u', 2, False),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False),
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8_s', 1, True),
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16_s', 2, True),
'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4, True),
'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8, True),
'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4, None),
'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8, None),
})
self.types.update({
'u8': u8,
'u16': u16,
'u32': u32,
'u64': u64,
'i8': i8,
'i16': i16,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes_,
})
a = TypeVariable('a', TypeVariableApplication_Nullary(None, None))
NatNum = Type3Class('NatNum', (a, ), methods={}, operators={
'+': [a, a, a],
'-': [a, a, a],
'*': [a, a, a],
'<<': [a, u32, a], # Arithmic shift left
'>>': [a, u32, a], # Arithmic shift right
})
self.instance_type_class(NatNum, i32, operators={
'+': stdlib_types.i32_natnum_add,
'-': stdlib_types.i32_natnum_sub,
'*': stdlib_types.i32_natnum_mul,
'<<': stdlib_types.i32_natnum_arithmic_shift_left,
'>>': stdlib_types.i32_natnum_arithmic_shift_right,
})
self.instance_type_class(NatNum, f32, operators={
'+': stdlib_types.f32_natnum_add,
'-': stdlib_types.f32_natnum_sub,
'*': stdlib_types.f32_natnum_mul,
'<<': stdlib_types.f32_natnum_arithmic_shift_left,
'>>': stdlib_types.f32_natnum_arithmic_shift_right,
})
self.instance_type_class(NatNum, f64, operators={
'+': stdlib_types.f64_natnum_add,
'-': stdlib_types.f64_natnum_sub,
'*': stdlib_types.f64_natnum_mul,
'<<': stdlib_types.f64_natnum_arithmic_shift_left,
'>>': stdlib_types.f64_natnum_arithmic_shift_right,
})
self.methods.update({
**NatNum.methods,
})
self.operators.update({
**NatNum.operators,
})

View File

@ -3,13 +3,13 @@ This module generates source code based on the parsed AST
It's intented to be a "any color, as long as it's black" kind of renderer
"""
from typing import Generator
from typing import Any, Generator
from . import ourlang, prelude
from . import ourlang
from .type3.types import Type3, TypeApplication_Struct
def phasm_render(inp: ourlang.Module) -> str:
def phasm_render(inp: ourlang.Module[Any]) -> str:
"""
Public method for rendering a Phasm module into Phasm code
"""
@ -21,9 +21,6 @@ def type3(inp: Type3) -> str:
"""
Render: type's name
"""
if inp is prelude.none:
return 'None'
return inp.name
def struct_definition(inp: ourlang.StructDefinition) -> str:
@ -161,7 +158,7 @@ def function(inp: ourlang.Function) -> str:
return result
def module(inp: ourlang.Module) -> str:
def module(inp: ourlang.Module[Any]) -> str:
"""
Render: Module
"""

View File

@ -4,8 +4,9 @@ This module contains the code to convert parsed Ourlang into WebAssembly code
import struct
from typing import List
from . import ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset
from . import ourlang, wasm
from .build import builtins
from .build.base import TypeInfo
from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types
from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
@ -24,11 +25,17 @@ from .type3.types import (
TypeConstructor_StaticArray,
TypeConstructor_Tuple,
)
from .wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
)
from .wasmgenerator import Generator as WasmGenerator
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled'
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
def phasm_compile(inp: ourlang.Module[WasmGenerator]) -> wasm.Module:
"""
Public method for compiling a parsed Phasm module into
a WebAssembly module
@ -45,7 +52,7 @@ def type3(inp: Type3) -> wasm.WasmType:
typ_info = TYPE_INFO_MAP.get(inp.name, TYPE_INFO_CONSTRUCTED)
return typ_info.wasm_type()
def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.TupleInstantiation) -> None:
def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.TupleInstantiation) -> None:
"""
Compile: Instantiation (allocation) of a tuple
"""
@ -65,7 +72,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
args = tuple(sa_type for _ in inp.elements)
# Can't use calculate_alloc_size directly since that doesn't
# know the dynamic array's length
alloc_size = 4 + calculate_alloc_size(sa_type, is_member=True) * len(inp.elements)
alloc_size = 4 + mod.build.calculate_alloc_size(sa_type, is_member=True) * len(inp.elements)
alloc_size_header = len(inp.elements)
elif isinstance(inp.type3.application, TypeApplication_TypeStar):
# Possibly paranoid assert. If we have a future variadic type,
@ -73,7 +80,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple)
args = inp.type3.application.arguments
alloc_size = calculate_alloc_size(inp.type3, is_member=False)
alloc_size = mod.build.calculate_alloc_size(inp.type3, is_member=False)
elif isinstance(inp.type3.application, TypeApplication_TypeInt):
# Possibly paranoid assert. If we have a future type of kind * -> Int -> *,
# does it also do this tuple instantation like this?
@ -82,7 +89,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
sa_type, sa_len = inp.type3.application.arguments
args = tuple(sa_type for _ in range(sa_len.value))
alloc_size = calculate_alloc_size(inp.type3, is_member=False)
alloc_size = mod.build.calculate_alloc_size(inp.type3, is_member=False)
else:
raise NotImplementedError('tuple_instantiation', inp.type3)
@ -117,13 +124,13 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
wgn.add_statement(exp_type_info.wasm_store_func, 'offset=' + str(offset))
wgn.add_statement('nop', comment='POST')
offset += calculate_alloc_size(exp_type3, is_member=True)
offset += mod.build.calculate_alloc_size(exp_type3, is_member=True)
# Return the allocated address
wgn.local.get(tmp_var)
def expression_subscript_bytes(
attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript],
attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript],
) -> None:
wgn, mod, inp = attrs
@ -132,7 +139,7 @@ def expression_subscript_bytes(
wgn.call(stdlib_types.__subscript_bytes__)
def expression_subscript_static_array(
attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript],
attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript],
args: tuple[Type3, IntType3],
) -> None:
wgn, mod, inp = attrs
@ -164,7 +171,7 @@ def expression_subscript_static_array(
wgn.add_statement(el_type_info.wasm_load_func)
def expression_subscript_tuple(
attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript],
attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript],
args: tuple[Type3, ...],
) -> None:
wgn, mod, inp = attrs
@ -186,12 +193,12 @@ def expression_subscript_tuple(
el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED)
wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}')
SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], None]()
SUBSCRIPT_ROUTER.add_n(prelude.bytes_, expression_subscript_bytes)
SUBSCRIPT_ROUTER.add(prelude.static_array, expression_subscript_static_array)
SUBSCRIPT_ROUTER.add(prelude.tuple_, expression_subscript_tuple)
SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], None]()
# SUBSCRIPT_ROUTER.add(builtins.dynamic_array, expression_subscript_dynamic_array)
SUBSCRIPT_ROUTER.add(builtins.static_array, expression_subscript_static_array)
SUBSCRIPT_ROUTER.add(builtins.tuple_, expression_subscript_tuple)
def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) -> None:
def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Expression) -> None:
"""
Compile: Any expression
"""
@ -202,34 +209,23 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
if isinstance(inp, ourlang.ConstantPrimitive):
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
if inp.type3 in (prelude.i8, prelude.u8, ):
# No native u8 type - treat as i32, with caution
type_info = mod.build.type_info_map[inp.type3.name]
if type_info.wasm_type is WasmTypeInt32:
assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
return
if inp.type3 in (prelude.i16, prelude.u16, ):
# No native u16 type - treat as i32, with caution
assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
return
if inp.type3 in (prelude.i32, prelude.u32, ):
assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
return
if inp.type3 in (prelude.i64, prelude.u64, ):
if type_info.wasm_type is WasmTypeInt64:
assert isinstance(inp.value, int)
wgn.i64.const(inp.value)
return
if inp.type3 == prelude.f32:
if type_info.wasm_type is WasmTypeFloat32:
assert isinstance(inp.value, float)
wgn.f32.const(inp.value)
return
if inp.type3 == prelude.f64:
if type_info.wasm_type is WasmTypeFloat64:
assert isinstance(inp.value, float)
wgn.f64.const(inp.value)
return
@ -285,7 +281,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
raise NotImplementedError(type_var, arg_expr.type3)
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator]
router = mod.build.type_class_instance_methods[inp.operator]
router(wgn, type_var_map)
return
@ -314,7 +310,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
raise NotImplementedError(type_var, arg_expr.type3)
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function]
router = mod.build.type_class_instance_methods[inp.function]
try:
router(wgn, type_var_map)
except NoRouteForTypeException:
@ -367,14 +363,14 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
member_type_info = TYPE_INFO_MAP.get(member_type.name, TYPE_INFO_CONSTRUCTED)
expression(wgn, mod, inp.varref)
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(calculate_member_offset(
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(mod.build.calculate_member_offset(
inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member
)))
return
raise NotImplementedError(expression, inp)
def statement_return(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.StatementReturn) -> None:
def statement_return(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.StatementReturn) -> None:
"""
Compile: Return statement
"""
@ -391,7 +387,7 @@ def statement_return(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Funct
expression(wgn, mod, inp.value)
wgn.return_()
def statement_if(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.StatementIf) -> None:
def statement_if(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.StatementIf) -> None:
"""
Compile: If statement
"""
@ -406,7 +402,7 @@ def statement_if(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function,
# for stat in inp.else_statements:
# statement(wgn, stat)
def statement(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.Statement) -> None:
def statement(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.Statement) -> None:
"""
Compile: any statement
"""
@ -446,7 +442,7 @@ def import_(inp: ourlang.Function) -> wasm.Import:
type3(inp.returns_type3)
)
def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function:
def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Function:
"""
Compile: function
"""
@ -455,7 +451,7 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function:
wgn = WasmGenerator()
if isinstance(inp, ourlang.StructConstructor):
_generate_struct_constructor(wgn, inp)
_generate_struct_constructor(wgn, mod, inp)
else:
for stat in inp.statements:
statement(wgn, mod, inp, stat)
@ -475,71 +471,30 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function:
wgn.statements
)
def module_data_u8(inp: int) -> bytes:
"""
Compile: module data, u8 value
"""
return struct.pack('<B', inp)
def module_data_primitive(type_info: TypeInfo, inp: int | float) -> bytes:
letter_map = {
(WasmTypeInt32, 1, False): 'B',
(WasmTypeInt32, 1, True): 'b',
(WasmTypeInt32, 2, False): 'H',
(WasmTypeInt32, 2, True): 'h',
(WasmTypeInt32, 4, False): 'I',
(WasmTypeInt32, 4, True): 'i',
(WasmTypeInt64, 8, False): 'Q',
(WasmTypeInt64, 8, True): 'q',
(WasmTypeFloat32, 4, None): 'f',
(WasmTypeFloat32, 8, None): 'd',
}
def module_data_u16(inp: int) -> bytes:
"""
Compile: module data, u16 value
"""
return struct.pack('<H', inp)
letter = letter_map[(type_info.wasm_type, type_info.alloc_size, type_info.signed, )]
return struct.pack(f'<{letter}', inp)
def module_data_u32(inp: int) -> bytes:
"""
Compile: module data, u32 value
"""
return struct.pack('<I', inp)
def module_data_u64(inp: int) -> bytes:
"""
Compile: module data, u64 value
"""
return struct.pack('<Q', inp)
def module_data_i8(inp: int) -> bytes:
"""
Compile: module data, i8 value
"""
return struct.pack('<b', inp)
def module_data_i16(inp: int) -> bytes:
"""
Compile: module data, i16 value
"""
return struct.pack('<h', inp)
def module_data_i32(inp: int) -> bytes:
"""
Compile: module data, i32 value
"""
return struct.pack('<i', inp)
def module_data_i64(inp: int) -> bytes:
"""
Compile: module data, i64 value
"""
return struct.pack('<q', inp)
def module_data_f32(inp: float) -> bytes:
"""
Compile: module data, f32 value
"""
return struct.pack('<f', inp)
def module_data_f64(inp: float) -> bytes:
"""
Compile: module data, f64 value
"""
return struct.pack('<d', inp)
def module_data(inp: ourlang.ModuleData) -> bytes:
def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) -> bytes:
"""
Compile: module data
"""
unalloc_ptr = stdlib_alloc.UNALLOC_PTR
u32_type_info = mod.build.type_info_map['u32']
ptr_type_info = mod.build.type_info_map['ptr']
allocated_data = b''
@ -551,112 +506,55 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
for constant in block.data:
assert constant.type3 is not None, TYPE3_ASSERTION_ERROR
if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block:
if isinstance(constant, ourlang.ConstantBytes):
data_list.append(module_data_primitive(u32_type_info, len(constant.value)))
data_list.append(constant.value)
continue
if isinstance(constant, ourlang.ConstantMemoryStored):
if block is constant.data_block:
raise NotImplementedError(block, constant)
# It's stored in a different block
# We only need to store its address
# This happens for example when a tuple refers
# to a bytes constant
assert constant.data_block.address is not None, 'Referred memory not yet stored'
data_list.append(module_data_u32(constant.data_block.address))
data_list.append(module_data_primitive(ptr_type_info, constant.data_block.address))
continue
if constant.type3 == prelude.u8:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int)
data_list.append(module_data_u8(constant.value))
continue
type_info = mod.build.type_info_map[constant.type3.name]
if constant.type3 == prelude.u16:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int)
data_list.append(module_data_u16(constant.value))
continue
if constant.type3 == prelude.u32:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int)
data_list.append(module_data_u32(constant.value))
continue
if constant.type3 == prelude.u64:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int)
data_list.append(module_data_u64(constant.value))
continue
if constant.type3 == prelude.i8:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int)
data_list.append(module_data_i8(constant.value))
continue
if constant.type3 == prelude.i16:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int)
data_list.append(module_data_i16(constant.value))
continue
if constant.type3 == prelude.i32:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int)
data_list.append(module_data_i32(constant.value))
continue
if constant.type3 == prelude.i64:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, int)
data_list.append(module_data_i64(constant.value))
continue
if constant.type3 == prelude.f32:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, float)
data_list.append(module_data_f32(constant.value))
continue
if constant.type3 == prelude.f64:
assert isinstance(constant, ourlang.ConstantPrimitive)
assert isinstance(constant.value, float)
data_list.append(module_data_f64(constant.value))
continue
if constant.type3 == prelude.bytes_:
assert isinstance(constant, ourlang.ConstantBytes)
assert isinstance(constant.value, bytes)
data_list.append(module_data_u32(len(constant.value)))
data_list.append(constant.value)
continue
raise NotImplementedError(constant, constant.type3)
return module_data_primitive(type_info, constant.value)
block_data = b''.join(data_list)
allocated_data += module_data_u32(len(block_data)) + block_data
allocated_data += module_data_primitive(u32_type_info, len(block_data)) + block_data
unalloc_ptr += 4 + len(block_data)
return (
# Store that we've initialized the memory
module_data_u32(stdlib_alloc.IDENTIFIER)
module_data_primitive(u32_type_info, stdlib_alloc.IDENTIFIER)
# Store the first reserved i32
+ module_data_u32(0)
+ module_data_primitive(u32_type_info, 0)
# Store the pointer towards the first free block
# In this case, 0 since we haven't freed any blocks yet
+ module_data_u32(0)
+ module_data_primitive(u32_type_info, 0)
# Store the pointer towards the first unallocated block
# In this case the end of the stdlib.alloc header at the start
+ module_data_u32(unalloc_ptr)
+ module_data_primitive(u32_type_info, unalloc_ptr)
# Store the actual data
+ allocated_data
)
def module(inp: ourlang.Module) -> wasm.Module:
def module(inp: ourlang.Module[WasmGenerator]) -> wasm.Module:
"""
Compile: module
"""
result = wasm.Module()
result.memory.data = module_data(inp.data)
result.memory.data = module_data(inp, inp.data)
result.imports = [
import_(x)
@ -698,7 +596,7 @@ def module(inp: ourlang.Module) -> wasm.Module:
return result
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.StructConstructor) -> None:
assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
st_args = inp.struct_type3.application.arguments
@ -706,7 +604,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
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.i32.const(mod.build.calculate_alloc_size(inp.struct_type3))
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
@ -716,7 +614,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'${memname}')
wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(calculate_member_offset(
wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(mod.build.calculate_member_offset(
inp.struct_type3.name, st_args, memname
)))

View File

@ -3,7 +3,7 @@ Contains the syntax tree for ourlang
"""
from typing import Dict, Iterable, List, Optional, Union
from . import prelude
from .build.base import BuildBase
from .type3.functions import FunctionSignature, TypeVariableContext
from .type3.typeclasses import Type3ClassMethod
from .type3.types import Type3, TypeApplication_Struct
@ -288,14 +288,14 @@ class Function:
returns_type3: Type3
posonlyargs: List[FunctionParam]
def __init__(self, name: str, lineno: int) -> None:
def __init__(self, name: str, lineno: int, returns_type3: Type3) -> None:
self.name = name
self.lineno = lineno
self.exported = False
self.imported = None
self.statements = []
self.signature = FunctionSignature(TypeVariableContext(), [])
self.returns_type3 = prelude.none # FIXME: This could be a placeholder
self.returns_type3 = returns_type3
self.posonlyargs = []
class StructDefinition:
@ -323,7 +323,7 @@ class StructConstructor(Function):
struct_type3: Type3
def __init__(self, struct_type3: Type3) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', -1)
super().__init__(f'@{struct_type3.name}@__init___@', -1, struct_type3)
assert isinstance(struct_type3.application, TypeApplication_Struct)
@ -331,7 +331,6 @@ class StructConstructor(Function):
self.posonlyargs.append(FunctionParam(mem, typ, ))
self.signature.args.append(typ)
self.returns_type3 = struct_type3
self.signature.args.append(struct_type3)
self.struct_type3 = struct_type3
@ -377,25 +376,30 @@ class ModuleData:
def __init__(self) -> None:
self.blocks = []
class Module:
class Module[G]:
"""
A module is a file and consists of functions
"""
__slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions', 'operators', 'functions_table', )
__slots__ = ('build', 'data', 'types', 'struct_definitions', 'constant_defs', 'functions', 'methods', 'operators', 'functions_table', )
build: BuildBase[G]
data: ModuleData
types: dict[str, Type3]
struct_definitions: Dict[str, StructDefinition]
constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function]
methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod]
functions_table: dict[Function, int]
def __init__(self) -> None:
def __init__(self, build: BuildBase[G]) -> None:
self.build = build
self.data = ModuleData()
self.types = {}
self.struct_definitions = {}
self.constant_defs = {}
self.functions = {}
self.methods = {}
self.operators = {}
self.functions_table = {}

View File

@ -4,7 +4,8 @@ Parses the source code from the plain text into a syntax tree
import ast
from typing import Any, Dict, NoReturn, Union
from . import prelude
from .build.base import BuildBase
from .build.default import BuildDefault
from .exceptions import StaticError
from .ourlang import (
AccessStructMember,
@ -31,12 +32,12 @@ from .ourlang import (
TupleInstantiation,
VariableReference,
)
from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES
from .type3.typeclasses import Type3ClassMethod
from .type3.types import IntType3, Type3
from .wasmgenerator import Generator
def phasm_parse(source: str) -> Module:
def phasm_parse(source: str) -> Module[Generator]:
"""
Public method for parsing Phasm code into a Phasm Module
"""
@ -44,7 +45,8 @@ def phasm_parse(source: str) -> Module:
res = OptimizerTransformer().visit(res)
our_visitor = OurVisitor()
build = BuildDefault()
our_visitor = OurVisitor(build)
return our_visitor.visit_Module(res)
OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict?
@ -75,7 +77,7 @@ class OptimizerTransformer(ast.NodeTransformer):
return node.operand
return node
class OurVisitor:
class OurVisitor[G]:
"""
Class to visit a Python syntax tree and create an ourlang syntax tree
@ -90,14 +92,14 @@ class OurVisitor:
# pylint: disable=C0103,C0116,C0301,R0201,R0912
def __init__(self) -> None:
pass
def __init__(self, build: BuildBase[G]) -> None:
self.build = build
def visit_Module(self, node: ast.Module) -> Module:
module = Module()
def visit_Module(self, node: ast.Module) -> Module[G]:
module = Module(self.build)
module.operators.update(PRELUDE_OPERATORS)
module.types.update(PRELUDE_TYPES)
module.operators.update(self.build.operators)
module.types.update(self.build.types)
_not_implemented(not node.type_ignores, 'Module.type_ignores')
@ -140,7 +142,7 @@ class OurVisitor:
return module
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
def pre_visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
if isinstance(node, ast.FunctionDef):
return self.pre_visit_Module_FunctionDef(module, node)
@ -152,8 +154,8 @@ class OurVisitor:
raise NotImplementedError(f'{node} on Module')
def pre_visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> Function:
function = Function(node.name, node.lineno)
def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function:
function = Function(node.name, node.lineno, self.build.none_)
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
@ -220,7 +222,7 @@ class OurVisitor:
return function
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition:
def pre_visit_Module_ClassDef(self, module: Module[G], node: ast.ClassDef) -> StructDefinition:
_not_implemented(not node.bases, 'ClassDef.bases')
_not_implemented(not node.keywords, 'ClassDef.keywords')
@ -246,9 +248,9 @@ class OurVisitor:
members[stmt.target.id] = self.visit_type(module, stmt.annotation)
return StructDefinition(prelude.struct(node.name, tuple(members.items())), node.lineno)
return StructDefinition(module.build.struct(node.name, tuple(members.items())), node.lineno)
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
def pre_visit_Module_AnnAssign(self, module: Module[G], node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name):
_raise_static_error(node.target, 'Must be name')
if not isinstance(node.target.ctx, ast.Store):
@ -292,7 +294,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} on Module AnnAssign')
def visit_Module_stmt(self, module: Module, node: ast.stmt) -> None:
def visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> None:
if isinstance(node, ast.FunctionDef):
self.visit_Module_FunctionDef(module, node)
return
@ -305,7 +307,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} on Module')
def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None:
def visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> None:
function = module.functions[node.name]
our_locals: OurLocals = {
@ -318,7 +320,7 @@ class OurVisitor:
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:
def visit_Module_FunctionDef_stmt(self, module: Module[G], 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
@ -350,7 +352,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as stmt in FunctionDef')
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
def visit_Module_FunctionDef_expr(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp):
operator: Union[str, Type3ClassMethod]
@ -466,7 +468,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]:
def visit_Module_FunctionDef_Call(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]:
if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -477,8 +479,8 @@ class OurVisitor:
func: Union[Function, FunctionParam, Type3ClassMethod]
if node.func.id in PRELUDE_METHODS:
func = PRELUDE_METHODS[node.func.id]
if node.func.id in module.methods:
func = module.methods[node.func.id]
elif node.func.id in our_locals:
func = our_locals[node.func.id]
else:
@ -494,7 +496,7 @@ class OurVisitor:
)
return result
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
def visit_Module_FunctionDef_Attribute(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name')
@ -511,7 +513,7 @@ class OurVisitor:
node.attr,
)
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
def visit_Module_FunctionDef_Subscript(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name')
@ -537,7 +539,7 @@ class OurVisitor:
return Subscript(varref, slice_expr)
def visit_Module_Constant(self, module: Module, node: Union[ast.Constant, ast.Tuple, ast.Call]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct]:
def visit_Module_Constant(self, module: Module[G], node: Union[ast.Constant, ast.Tuple, ast.Call]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct]:
if isinstance(node, ast.Tuple):
tuple_data = [
self.visit_Module_Constant(module, arg_node)
@ -597,10 +599,10 @@ class OurVisitor:
raise NotImplementedError(f'{node.value} as constant')
def visit_type(self, module: Module, node: ast.expr) -> Type3:
def visit_type(self, module: Module[G], node: ast.expr) -> Type3:
if isinstance(node, ast.Constant):
if node.value is None:
return prelude.none
return module.types['None']
_raise_static_error(node, f'Unrecognized type {node.value}')
@ -625,7 +627,7 @@ class OurVisitor:
_raise_static_error(node, 'Must subscript using a list of types')
# Function type
return prelude.function(*[
return module.build.function(*[
self.visit_type(module, e)
for e in func_arg_types
])
@ -637,7 +639,7 @@ class OurVisitor:
_raise_static_error(node, 'Must subscript using a constant index')
if node.slice.value is Ellipsis:
return prelude.dynamic_array(
return module.build.dynamic_array(
self.visit_type(module, node.value),
)
@ -646,7 +648,7 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
return prelude.static_array(
return module.build.static_array(
self.visit_type(module, node.value),
IntType3(node.slice.value),
)
@ -655,7 +657,7 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
return prelude.tuple_(
return module.build.tuple_(
*(self.visit_type(module, elt) for elt in node.elts)
)

View File

@ -1,772 +0,0 @@
"""
The prelude are all the builtin types, type classes and methods
"""
from typing import Any, Callable
from warnings import warn
from phasm.stdlib import types as stdtypes
from phasm.wasmgenerator import Generator
from ..type3.functions import (
Constraint_TypeClassInstanceExists,
TypeConstructorVariable,
TypeVariable,
TypeVariableApplication_Nullary,
)
from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup
from ..type3.typeclasses import Type3Class, Type3ClassMethod
from ..type3.types import (
Type3,
TypeApplication_Nullary,
TypeConstructor_Base,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Struct,
TypeConstructor_Tuple,
)
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] = set()
PRELUDE_TYPE_CLASS_INSTANCE_METHODS: dict[Type3ClassMethod, TypeClassArgsRouter[Generator, None]] = {}
class MissingImplementationException(Exception):
pass
class MissingImplementationWarning(Warning):
pass
def instance_type_class(
cls: Type3Class,
*typ: Type3 | TypeConstructor_Base[Any],
methods: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {},
operators: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {},
) -> None:
global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING
global PRELUDE_TYPE_CLASS_INSTANCE_METHODS
assert len(cls.args) == len(typ)
tv_map: dict[TypeVariable, Type3] = {}
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {}
for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
if isinstance(arg_tv, TypeVariable):
assert isinstance(arg_tp, Type3)
tv_map[arg_tv] = arg_tp
elif isinstance(arg_tv, TypeConstructorVariable):
assert isinstance(arg_tp, TypeConstructor_Base)
tc_map[arg_tv] = arg_tp
else:
raise NotImplementedError(arg_tv, arg_tp)
# TODO: Check for required existing instantiations
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), ))
for method_name, method in cls.methods.items():
router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(method)
if router is None:
router = TypeClassArgsRouter[Generator, None](cls.args)
PRELUDE_TYPE_CLASS_INSTANCE_METHODS[method] = router
try:
generator = methods[method_name]
except KeyError:
warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ)))
continue
router.add(tv_map, tc_map, generator)
for operator_name, operator in cls.operators.items():
router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(operator)
if router is None:
router = TypeClassArgsRouter[Generator, None](cls.args)
PRELUDE_TYPE_CLASS_INSTANCE_METHODS[operator] = router
try:
generator = operators[operator_name]
except KeyError:
warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ)))
continue
router.add(tv_map, tc_map, generator)
none = Type3('none', TypeApplication_Nullary(None, None))
"""
The none type, for when functions simply don't return anything. e.g., IO().
"""
bool_ = Type3('bool', TypeApplication_Nullary(None, None))
"""
The bool type, either True or False
Suffixes with an underscores, as it's a Python builtin
"""
u8 = Type3('u8', TypeApplication_Nullary(None, None))
"""
The unsigned 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8.
"""
u16 = Type3('u16', TypeApplication_Nullary(None, None))
"""
The unsigned 16-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^16.
"""
u32 = Type3('u32', TypeApplication_Nullary(None, None))
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32.
"""
u64 = Type3('u64', TypeApplication_Nullary(None, None))
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64.
"""
i8 = Type3('i8', TypeApplication_Nullary(None, None))
"""
The signed 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8, but
with the middel point being 0.
"""
i16 = Type3('i16', TypeApplication_Nullary(None, None))
"""
The signed 16-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^16, but
with the middel point being 0.
"""
i32 = Type3('i32', TypeApplication_Nullary(None, None))
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32, but
with the middel point being 0.
"""
i64 = Type3('i64', TypeApplication_Nullary(None, None))
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64, but
with the middel point being 0.
"""
f32 = Type3('f32', TypeApplication_Nullary(None, None))
"""
A 32-bits IEEE 754 float, of 32 bits width.
"""
f64 = Type3('f64', TypeApplication_Nullary(None, None))
"""
A 32-bits IEEE 754 float, of 64 bits width.
"""
dynamic_array = TypeConstructor_DynamicArray('dynamic_array')
"""
This is a dynamic length piece of memory.
It should be applied with two arguments. It has a runtime
determined length, and each argument is the same.
"""
static_array = TypeConstructor_StaticArray('static_array')
"""
This is a fixed length piece of memory.
It should be applied with two arguments. It has a compile time
determined length, and each argument is the same.
"""
tuple_ = TypeConstructor_Tuple('tuple')
"""
This is a fixed length piece of memory.
It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
"""
function = TypeConstructor_Function('function')
"""
This is a function.
It should be applied with one or more arguments. The last argument is the 'return' type.
"""
struct = TypeConstructor_Struct('struct')
"""
This is like a tuple, but each argument is named, so that developers
can get and set fields by name.
"""
a = TypeVariable('a', TypeVariableApplication_Nullary(None, None))
b = TypeVariable('b', TypeVariableApplication_Nullary(None, None))
t = TypeConstructorVariable('t')
Eq = Type3Class('Eq', (a, ), methods={}, operators={
'==': [a, a, bool_],
'!=': [a, a, bool_],
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
})
instance_type_class(Eq, u8, operators={
'==': stdtypes.u8_eq_equals,
'!=': stdtypes.u8_eq_not_equals,
})
instance_type_class(Eq, u16, operators={
'==': stdtypes.u16_eq_equals,
'!=': stdtypes.u16_eq_not_equals,
})
instance_type_class(Eq, u32, operators={
'==': stdtypes.u32_eq_equals,
'!=': stdtypes.u32_eq_not_equals,
})
instance_type_class(Eq, u64, operators={
'==': stdtypes.u64_eq_equals,
'!=': stdtypes.u64_eq_not_equals,
})
instance_type_class(Eq, i8, operators={
'==': stdtypes.i8_eq_equals,
'!=': stdtypes.i8_eq_not_equals,
})
instance_type_class(Eq, i16, operators={
'==': stdtypes.i16_eq_equals,
'!=': stdtypes.i16_eq_not_equals,
})
instance_type_class(Eq, i32, operators={
'==': stdtypes.i32_eq_equals,
'!=': stdtypes.i32_eq_not_equals,
})
instance_type_class(Eq, i64, operators={
'==': stdtypes.i64_eq_equals,
'!=': stdtypes.i64_eq_not_equals,
})
instance_type_class(Eq, f32, operators={
'==': stdtypes.f32_eq_equals,
'!=': stdtypes.f32_eq_not_equals,
})
instance_type_class(Eq, f64, operators={
'==': stdtypes.f64_eq_equals,
'!=': stdtypes.f64_eq_not_equals,
})
Ord = Type3Class('Ord', (a, ), methods={
'min': [a, a, a],
'max': [a, a, a],
}, operators={
'<': [a, a, bool_],
'<=': [a, a, bool_],
'>': [a, a, bool_],
'>=': [a, a, bool_],
}, inherited_classes=[Eq])
instance_type_class(Ord, u8, methods={
'min': stdtypes.u8_ord_min,
'max': stdtypes.u8_ord_max,
}, operators={
'<': stdtypes.u8_ord_less_than,
'<=': stdtypes.u8_ord_less_than_or_equal,
'>': stdtypes.u8_ord_greater_than,
'>=': stdtypes.u8_ord_greater_than_or_equal,
})
instance_type_class(Ord, u16, methods={
'min': stdtypes.u16_ord_min,
'max': stdtypes.u16_ord_max,
}, operators={
'<': stdtypes.u16_ord_less_than,
'<=': stdtypes.u16_ord_less_than_or_equal,
'>': stdtypes.u16_ord_greater_than,
'>=': stdtypes.u16_ord_greater_than_or_equal,
})
instance_type_class(Ord, u32, methods={
'min': stdtypes.u32_ord_min,
'max': stdtypes.u32_ord_max,
}, operators={
'<': stdtypes.u32_ord_less_than,
'<=': stdtypes.u32_ord_less_than_or_equal,
'>': stdtypes.u32_ord_greater_than,
'>=': stdtypes.u32_ord_greater_than_or_equal,
})
instance_type_class(Ord, u64, methods={
'min': stdtypes.u64_ord_min,
'max': stdtypes.u64_ord_max,
}, operators={
'<': stdtypes.u64_ord_less_than,
'<=': stdtypes.u64_ord_less_than_or_equal,
'>': stdtypes.u64_ord_greater_than,
'>=': stdtypes.u64_ord_greater_than_or_equal,
})
instance_type_class(Ord, i8, methods={
'min': stdtypes.i8_ord_min,
'max': stdtypes.i8_ord_max,
}, operators={
'<': stdtypes.i8_ord_less_than,
'<=': stdtypes.i8_ord_less_than_or_equal,
'>': stdtypes.i8_ord_greater_than,
'>=': stdtypes.i8_ord_greater_than_or_equal,
})
instance_type_class(Ord, i16, methods={
'min': stdtypes.i16_ord_min,
'max': stdtypes.i16_ord_max,
}, operators={
'<': stdtypes.i16_ord_less_than,
'<=': stdtypes.i16_ord_less_than_or_equal,
'>': stdtypes.i16_ord_greater_than,
'>=': stdtypes.i16_ord_greater_than_or_equal,
})
instance_type_class(Ord, i32, methods={
'min': stdtypes.i32_ord_min,
'max': stdtypes.i32_ord_max,
}, operators={
'<': stdtypes.i32_ord_less_than,
'<=': stdtypes.i32_ord_less_than_or_equal,
'>': stdtypes.i32_ord_greater_than,
'>=': stdtypes.i32_ord_greater_than_or_equal,
})
instance_type_class(Ord, i64, methods={
'min': stdtypes.i64_ord_min,
'max': stdtypes.i64_ord_max,
}, operators={
'<': stdtypes.i64_ord_less_than,
'<=': stdtypes.i64_ord_less_than_or_equal,
'>': stdtypes.i64_ord_greater_than,
'>=': stdtypes.i64_ord_greater_than_or_equal,
})
instance_type_class(Ord, f32, methods={
'min': stdtypes.f32_ord_min,
'max': stdtypes.f32_ord_max,
}, operators={
'<': stdtypes.f32_ord_less_than,
'<=': stdtypes.f32_ord_less_than_or_equal,
'>': stdtypes.f32_ord_greater_than,
'>=': stdtypes.f32_ord_greater_than_or_equal,
})
instance_type_class(Ord, f64, methods={
'min': stdtypes.f64_ord_min,
'max': stdtypes.f64_ord_max,
}, operators={
'<': stdtypes.f64_ord_less_than,
'<=': stdtypes.f64_ord_less_than_or_equal,
'>': stdtypes.f64_ord_greater_than,
'>=': stdtypes.f64_ord_greater_than_or_equal,
})
Bits = Type3Class('Bits', (a, ), methods={
'shl': [a, u32, a], # Logical shift left
'shr': [a, u32, a], # Logical shift right
'rotl': [a, u32, a], # Rotate bits left
'rotr': [a, u32, a], # Rotate bits right
# FIXME: Do we want to expose clz, ctz, popcnt?
}, operators={
'&': [a, a, a], # Bit-wise and
'|': [a, a, a], # Bit-wise or
'^': [a, a, a], # Bit-wise xor
})
instance_type_class(Bits, u8, methods={
'shl': stdtypes.u8_bits_logical_shift_left,
'shr': stdtypes.u8_bits_logical_shift_right,
'rotl': stdtypes.u8_bits_rotate_left,
'rotr': stdtypes.u8_bits_rotate_right,
}, operators={
'&': stdtypes.u8_bits_bitwise_and,
'|': stdtypes.u8_bits_bitwise_or,
'^': stdtypes.u8_bits_bitwise_xor,
})
instance_type_class(Bits, u16, methods={
'shl': stdtypes.u16_bits_logical_shift_left,
'shr': stdtypes.u16_bits_logical_shift_right,
'rotl': stdtypes.u16_bits_rotate_left,
'rotr': stdtypes.u16_bits_rotate_right,
}, operators={
'&': stdtypes.u16_bits_bitwise_and,
'|': stdtypes.u16_bits_bitwise_or,
'^': stdtypes.u16_bits_bitwise_xor,
})
instance_type_class(Bits, u32, methods={
'shl': stdtypes.u32_bits_logical_shift_left,
'shr': stdtypes.u32_bits_logical_shift_right,
'rotl': stdtypes.u32_bits_rotate_left,
'rotr': stdtypes.u32_bits_rotate_right,
}, operators={
'&': stdtypes.u32_bits_bitwise_and,
'|': stdtypes.u32_bits_bitwise_or,
'^': stdtypes.u32_bits_bitwise_xor,
})
instance_type_class(Bits, u64, methods={
'shl': stdtypes.u64_bits_logical_shift_left,
'shr': stdtypes.u64_bits_logical_shift_right,
'rotl': stdtypes.u64_bits_rotate_left,
'rotr': stdtypes.u64_bits_rotate_right,
}, operators={
'&': stdtypes.u64_bits_bitwise_and,
'|': stdtypes.u64_bits_bitwise_or,
'^': stdtypes.u64_bits_bitwise_xor,
})
NatNum = Type3Class('NatNum', (a, ), methods={}, operators={
'+': [a, a, a],
'-': [a, a, a],
'*': [a, a, a],
'<<': [a, u32, a], # Arithmic shift left
'>>': [a, u32, a], # Arithmic shift right
})
instance_type_class(NatNum, u32, operators={
'+': stdtypes.u32_natnum_add,
'-': stdtypes.u32_natnum_sub,
'*': stdtypes.u32_natnum_mul,
'<<': stdtypes.u32_natnum_arithmic_shift_left,
'>>': stdtypes.u32_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, u64, operators={
'+': stdtypes.u64_natnum_add,
'-': stdtypes.u64_natnum_sub,
'*': stdtypes.u64_natnum_mul,
'<<': stdtypes.u64_natnum_arithmic_shift_left,
'>>': stdtypes.u64_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, i32, operators={
'+': stdtypes.i32_natnum_add,
'-': stdtypes.i32_natnum_sub,
'*': stdtypes.i32_natnum_mul,
'<<': stdtypes.i32_natnum_arithmic_shift_left,
'>>': stdtypes.i32_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, i64, operators={
'+': stdtypes.i64_natnum_add,
'-': stdtypes.i64_natnum_sub,
'*': stdtypes.i64_natnum_mul,
'<<': stdtypes.i64_natnum_arithmic_shift_left,
'>>': stdtypes.i64_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, f32, operators={
'+': stdtypes.f32_natnum_add,
'-': stdtypes.f32_natnum_sub,
'*': stdtypes.f32_natnum_mul,
'<<': stdtypes.f32_natnum_arithmic_shift_left,
'>>': stdtypes.f32_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, f64, operators={
'+': stdtypes.f64_natnum_add,
'-': stdtypes.f64_natnum_sub,
'*': stdtypes.f64_natnum_mul,
'<<': stdtypes.f64_natnum_arithmic_shift_left,
'>>': stdtypes.f64_natnum_arithmic_shift_right,
})
IntNum = Type3Class('IntNum', (a, ), methods={
'abs': [a, a],
'neg': [a, a],
}, operators={}, inherited_classes=[NatNum])
instance_type_class(IntNum, i32, methods={
'abs': stdtypes.i32_intnum_abs,
'neg': stdtypes.i32_intnum_neg,
})
instance_type_class(IntNum, i64, methods={
'abs': stdtypes.i64_intnum_abs,
'neg': stdtypes.i64_intnum_neg,
})
instance_type_class(IntNum, f32, methods={
'abs': stdtypes.f32_intnum_abs,
'neg': stdtypes.f32_intnum_neg,
})
instance_type_class(IntNum, f64, methods={
'abs': stdtypes.f64_intnum_abs,
'neg': stdtypes.f64_intnum_neg,
})
Integral = Type3Class('Integral', (a, ), methods={
}, operators={
'//': [a, a, a],
'%': [a, a, a],
}, inherited_classes=[NatNum])
instance_type_class(Integral, u32, operators={
'//': stdtypes.u32_integral_div,
'%': stdtypes.u32_integral_rem,
})
instance_type_class(Integral, u64, operators={
'//': stdtypes.u64_integral_div,
'%': stdtypes.u64_integral_rem,
})
instance_type_class(Integral, i32, operators={
'//': stdtypes.i32_integral_div,
'%': stdtypes.i32_integral_rem,
})
instance_type_class(Integral, i64, operators={
'//': stdtypes.i64_integral_div,
'%': stdtypes.i64_integral_rem,
})
Fractional = Type3Class('Fractional', (a, ), methods={
'ceil': [a, a],
'floor': [a, a],
'trunc': [a, a],
'nearest': [a, a],
}, operators={
'/': [a, a, a],
}, inherited_classes=[NatNum])
instance_type_class(Fractional, f32, methods={
'ceil': stdtypes.f32_fractional_ceil,
'floor': stdtypes.f32_fractional_floor,
'trunc': stdtypes.f32_fractional_trunc,
'nearest': stdtypes.f32_fractional_nearest,
}, operators={
'/': stdtypes.f32_fractional_div,
})
instance_type_class(Fractional, f64, methods={
'ceil': stdtypes.f64_fractional_ceil,
'floor': stdtypes.f64_fractional_floor,
'trunc': stdtypes.f64_fractional_trunc,
'nearest': stdtypes.f64_fractional_nearest,
}, operators={
'/': stdtypes.f64_fractional_div,
})
Floating = Type3Class('Floating', (a, ), methods={
'sqrt': [a, a],
}, operators={}, inherited_classes=[Fractional])
# FIXME: Do we want to expose copysign?
instance_type_class(Floating, f32, methods={
'sqrt': stdtypes.f32_floating_sqrt,
})
instance_type_class(Floating, f64, methods={
'sqrt': stdtypes.f64_floating_sqrt,
})
Sized_ = Type3Class('Sized', (t, ), methods={
'len': [t(a), u32],
}, operators={}) # FIXME: Once we get type class families, add [] here
instance_type_class(Sized_, dynamic_array, methods={
'len': stdtypes.dynamic_array_sized_len,
})
instance_type_class(Sized_, static_array, methods={
'len': stdtypes.static_array_sized_len,
})
Extendable = Type3Class('Extendable', (a, b, ), methods={
'extend': [a, b],
'wrap': [b, a],
}, operators={})
instance_type_class(Extendable, u8, u16, methods={
'extend': stdtypes.u8_u16_extend,
'wrap': stdtypes.u8_u16_wrap,
})
instance_type_class(Extendable, u8, u32, methods={
'extend': stdtypes.u8_u32_extend,
'wrap': stdtypes.u8_u32_wrap,
})
instance_type_class(Extendable, u8, u64, methods={
'extend': stdtypes.u8_u64_extend,
'wrap': stdtypes.u8_u64_wrap,
})
instance_type_class(Extendable, u16, u32, methods={
'extend': stdtypes.u16_u32_extend,
'wrap': stdtypes.u16_u32_wrap,
})
instance_type_class(Extendable, u16, u64, methods={
'extend': stdtypes.u16_u64_extend,
'wrap': stdtypes.u16_u64_wrap,
})
instance_type_class(Extendable, u32, u64, methods={
'extend': stdtypes.u32_u64_extend,
'wrap': stdtypes.u32_u64_wrap,
})
instance_type_class(Extendable, i8, i16, methods={
'extend': stdtypes.i8_i16_extend,
'wrap': stdtypes.i8_i16_wrap,
})
instance_type_class(Extendable, i8, i32, methods={
'extend': stdtypes.i8_i32_extend,
'wrap': stdtypes.i8_i32_wrap,
})
instance_type_class(Extendable, i8, i64, methods={
'extend': stdtypes.i8_i64_extend,
'wrap': stdtypes.i8_i64_wrap,
})
instance_type_class(Extendable, i16, i32, methods={
'extend': stdtypes.i16_i32_extend,
'wrap': stdtypes.i16_i32_wrap,
})
instance_type_class(Extendable, i16, i64, methods={
'extend': stdtypes.i16_i64_extend,
'wrap': stdtypes.i16_i64_wrap,
})
instance_type_class(Extendable, i32, i64, methods={
'extend': stdtypes.i32_i64_extend,
'wrap': stdtypes.i32_i64_wrap,
})
Promotable = Type3Class('Promotable', (a, b, ), methods={
'promote': [a, b],
'demote': [b, a],
}, operators={})
instance_type_class(Promotable, f32, f64, methods={
'promote': stdtypes.f32_f64_promote,
'demote': stdtypes.f32_f64_demote,
})
Reinterpretable = Type3Class('Reinterpretable', (a, b, ), methods={
'reinterpret': [a, b]
}, operators={})
instance_type_class(Reinterpretable, u32, f32, methods={
'reinterpret': stdtypes.u32_f32_reinterpret,
})
instance_type_class(Reinterpretable, u64, f64, methods={
'reinterpret': stdtypes.u64_f64_reinterpret,
})
instance_type_class(Reinterpretable, i32, f32, methods={
'reinterpret': stdtypes.i32_f32_reinterpret,
})
instance_type_class(Reinterpretable, i64, f64, methods={
'reinterpret': stdtypes.i64_f64_reinterpret,
})
instance_type_class(Reinterpretable, f32, u32, methods={
'reinterpret': stdtypes.f32_u32_reinterpret,
})
instance_type_class(Reinterpretable, f64, u64, methods={
'reinterpret': stdtypes.f64_u64_reinterpret,
})
instance_type_class(Reinterpretable, f32, i32, methods={
'reinterpret': stdtypes.f32_i32_reinterpret,
})
instance_type_class(Reinterpretable, f64, i64, methods={
'reinterpret': stdtypes.f64_i64_reinterpret,
})
Convertable = Type3Class('Convertable', (a, b, ), methods={
'convert': [a, b],
'truncate': [b, a], # To prevent name clas with Fractional
}, operators={})
instance_type_class(Convertable, u32, f32, methods={
'convert': stdtypes.u32_f32_convert,
'truncate': stdtypes.u32_f32_truncate,
})
instance_type_class(Convertable, u32, f64, methods={
'convert': stdtypes.u32_f64_convert,
'truncate': stdtypes.u32_f64_truncate,
})
instance_type_class(Convertable, u64, f32, methods={
'convert': stdtypes.u64_f32_convert,
'truncate': stdtypes.u64_f32_truncate,
})
instance_type_class(Convertable, u64, f64, methods={
'convert': stdtypes.u64_f64_convert,
'truncate': stdtypes.u64_f64_truncate,
})
instance_type_class(Convertable, i32, f32, methods={
'convert': stdtypes.i32_f32_convert,
'truncate': stdtypes.i32_f32_truncate,
})
instance_type_class(Convertable, i32, f64, methods={
'convert': stdtypes.i32_f64_convert,
'truncate': stdtypes.i32_f64_truncate,
})
instance_type_class(Convertable, i64, f32, methods={
'convert': stdtypes.i64_f32_convert,
'truncate': stdtypes.i64_f32_truncate,
})
instance_type_class(Convertable, i64, f64, methods={
'convert': stdtypes.i64_f64_convert,
'truncate': stdtypes.i64_f64_truncate,
})
Foldable = Type3Class('Foldable', (t, ), methods={
'sum': [t(a), a],
'foldl': [[b, a, b], b, t(a), b],
'foldr': [[a, b, b], b, t(a), b],
}, operators={}, additional_context={
'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))],
})
instance_type_class(Foldable, dynamic_array, methods={
'sum': stdtypes.dynamic_array_sum,
'foldl': stdtypes.dynamic_array_foldl,
'foldr': stdtypes.dynamic_array_foldr,
})
instance_type_class(Foldable, static_array, methods={
'sum': stdtypes.static_array_sum,
'foldl': stdtypes.static_array_foldl,
'foldr': stdtypes.static_array_foldr,
})
bytes_ = dynamic_array(u8)
PRELUDE_TYPES: dict[str, Type3] = {
'none': none,
'bool': bool_,
'u8': u8,
'u16': u16,
'u32': u32,
'u64': u64,
'i8': i8,
'i16': i16,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes_,
}
PRELUDE_TYPE_CLASSES = {
'Eq': Eq,
'Ord': Ord,
'Bits': Bits,
'NatNum': NatNum,
'IntNum': IntNum,
'Integral': Integral,
'Fractional': Fractional,
'Floating': Floating,
'Extendable': Extendable,
'Promotable': Promotable,
}
PRELUDE_OPERATORS = {
**Bits.operators,
**Eq.operators,
**Ord.operators,
**Fractional.operators,
**Integral.operators,
**IntNum.operators,
**NatNum.operators,
}
PRELUDE_METHODS = {
**Bits.methods,
**Eq.methods,
**Ord.methods,
**Floating.methods,
**Fractional.methods,
**Integral.methods,
**IntNum.methods,
**NatNum.methods,
**Sized_.methods,
**Extendable.methods,
**Promotable.methods,
**Reinterpretable.methods,
**Convertable.methods,
**Foldable.methods,
}

View File

@ -1,62 +0,0 @@
from . import prelude
from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
from .type3.types import IntType3, Type3
def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int:
if is_member:
return TYPE_INFO_CONSTRUCTED.alloc_size
sa_type, sa_len = args
return sa_len.value * calculate_alloc_size(sa_type, is_member=True)
def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int:
if is_member:
return TYPE_INFO_CONSTRUCTED.alloc_size
return sum(
calculate_alloc_size(x, is_member=True)
for x in args
)
def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int:
if is_member:
return TYPE_INFO_CONSTRUCTED.alloc_size
return sum(
calculate_alloc_size(x, is_member=True)
for _, x in args
)
ALLOC_SIZE_ROUTER = TypeApplicationRouter[bool, int]()
ALLOC_SIZE_ROUTER.add(prelude.static_array, calculate_alloc_size_static_array)
ALLOC_SIZE_ROUTER.add(prelude.struct, calculate_alloc_size_struct)
ALLOC_SIZE_ROUTER.add(prelude.tuple_, calculate_alloc_size_tuple)
def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int:
typ_info = TYPE_INFO_MAP.get(typ.name)
if typ_info is not None:
return typ_info.alloc_size
try:
return ALLOC_SIZE_ROUTER(is_member, typ)
except NoRouteForTypeException:
if is_member:
# By default, 'boxed' or 'constructed' types are
# stored as pointers when a member of a struct or tuple
return TYPE_INFO_CONSTRUCTED.alloc_size
raise NotImplementedError(typ)
def calculate_member_offset(st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int:
result = 0
for memnam, memtyp in st_args:
if needle == memnam:
return result
result += calculate_alloc_size(memtyp, is_member=True)
raise Exception(f'{needle} not in {st_name}')

View File

@ -1,13 +1,13 @@
"""
stdlib: Standard types that are not wasm primitives
"""
from typing import Mapping, NamedTuple, Type
from typing import Mapping
from phasm.build.base import TypeInfo
from phasm.stdlib import alloc
from phasm.type3.routers import TypeVariableLookup
from phasm.type3.types import IntType3, Type3
from phasm.wasm import (
WasmType,
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
@ -18,38 +18,22 @@ from phasm.wasmgenerator import Generator, func_wrapper
from phasm.wasmgenerator import VarType_i32 as i32
from phasm.wasmgenerator import VarType_i64 as i64
TypeInfo = NamedTuple('TypeInfo', [
# Name of the type
('typ', str, ),
# What WebAssembly type to use when passing this value around
# For example in function arguments
('wasm_type', Type[WasmType]),
# What WebAssembly function to use when loading a value from memory
('wasm_load_func', str),
# What WebAssembly function to use when storing a value to memory
('wasm_store_func', str),
# When storing this value in memory, how many bytes do we use?
# Only valid for non-constructed types, see calculate_alloc_size
# Should match wasm_load_func / wasm_store_func
('alloc_size', int),
])
TYPE_INFO_MAP: Mapping[str, TypeInfo] = {
'none': TypeInfo('none', WasmTypeNone, 'nop', 'nop', 0),
'bool': TypeInfo('bool', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1),
'none': TypeInfo('none', WasmTypeNone, 'nop', 'nop', 0, None),
'bool': TypeInfo('bool', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, None),
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1),
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1),
'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2),
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2),
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False),
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True),
'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2, False),
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4),
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8),
'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4),
'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8),
'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4),
'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8),
'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False),
'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4, True),
'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8, True),
'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4, None),
'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8, None),
'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, None),
}
# By default, constructed types are passed as pointers
@ -57,7 +41,7 @@ TYPE_INFO_MAP: Mapping[str, TypeInfo] = {
# USE runtime.calculate_alloc_size FOR ACCURATE RESULTS
# Functions count as constructed types - even though they are
# not memory pointers but table addresses instead.
TYPE_INFO_CONSTRUCTED = TypeInfo('t a', WasmTypeInt32, 'i32.load', 'i32.store', 4)
TYPE_INFO_CONSTRUCTED = TYPE_INFO_MAP['ptr']
@func_wrapper(exported=True)
def __alloc_bytes__(g: Generator, length: i32) -> i32:

View File

@ -5,7 +5,14 @@ These need to be resolved before the program can be compiled.
"""
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from .. import ourlang, prelude
from .. import ourlang
from ..build.base import BuildBase
from ..wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
)
from .functions import FunctionArgument, TypeVariable
from .placeholders import PlaceholderForType, Type3OrPlaceholder
from .routers import NoRouteForTypeException, TypeApplicationRouter
@ -59,26 +66,31 @@ class Context:
Context for constraints
"""
__slots__ = ('type_class_instances_existing', )
__slots__ = ('build', )
# Constraint_TypeClassInstanceExists
type_class_instances_existing: set[tuple[Type3Class, tuple[Union[Type3, TypeConstructor_Base[Any], TypeConstructor_Struct], ...]]]
build: BuildBase[Any]
def __init__(self) -> None:
self.type_class_instances_existing = set()
def __init__(self, build: BuildBase[Any]) -> None:
self.build = build
class ConstraintBase:
"""
Base class for constraints
"""
__slots__ = ('comment', )
__slots__ = ('context', 'comment', )
context: Context
"""
Additional information regarding the type environment
"""
comment: Optional[str]
"""
A comment to help the programmer with debugging the types in their program
"""
def __init__(self, comment: Optional[str] = None) -> None:
def __init__(self, context: Context, comment: Optional[str] = None) -> None:
self.context = context
self.comment = comment
def check(self) -> CheckResult:
@ -114,8 +126,8 @@ class SameTypeConstraint(ConstraintBase):
type_list: List[Type3OrPlaceholder]
def __init__(self, *type_list: Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
def __init__(self, context: Context, *type_list: Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(context=context, comment=comment)
assert len(type_list) > 1
self.type_list = [*type_list]
@ -176,8 +188,8 @@ class SameTypeArgumentConstraint(ConstraintBase):
tc_var: PlaceholderForType
arg_var: PlaceholderForType
def __init__(self, tc_var: PlaceholderForType, arg_var: PlaceholderForType, *, comment: str) -> None:
super().__init__(comment=comment)
def __init__(self, context: Context, tc_var: PlaceholderForType, arg_var: PlaceholderForType, *, comment: str) -> None:
super().__init__(context=context, comment=comment)
self.tc_var = tc_var
self.arg_var = arg_var
@ -202,6 +214,7 @@ class SameTypeArgumentConstraint(ConstraintBase):
if isinstance(tc_typ.application, TypeApplication_Type):
return [SameTypeConstraint(
self.context,
tc_typ.application.arguments[0],
self.arg_var,
comment=self.comment,
@ -211,6 +224,7 @@ class SameTypeArgumentConstraint(ConstraintBase):
# have the exact same number as arguments?
if isinstance(tc_typ.application, TypeApplication_TypeInt):
return [SameTypeConstraint(
self.context,
tc_typ.application.arguments[0],
self.arg_var,
comment=self.comment,
@ -234,8 +248,8 @@ class SameFunctionArgumentConstraint(ConstraintBase):
func_arg: FunctionArgument
type_var_map: dict[TypeVariable, PlaceholderForType]
def __init__(self, type3: PlaceholderForType, func_arg: FunctionArgument, type_var_map: dict[TypeVariable, PlaceholderForType], *, comment: str) -> None:
super().__init__(comment=comment)
def __init__(self, context: Context, type3: PlaceholderForType, func_arg: FunctionArgument, type_var_map: dict[TypeVariable, PlaceholderForType], *, comment: str) -> None:
super().__init__(context=context, comment=comment)
self.type3 = type3
self.func_arg = func_arg
@ -270,8 +284,9 @@ class SameFunctionArgumentConstraint(ConstraintBase):
return [
SameTypeConstraint(
self.context,
typ,
prelude.function(*exp_type_arg_list),
self.context.build.function(*exp_type_arg_list),
comment=self.comment,
)
]
@ -286,22 +301,27 @@ class SameFunctionArgumentConstraint(ConstraintBase):
)
class TupleMatchConstraint(ConstraintBase):
__slots__ = ('exp_type', 'args', )
__slots__ = ('exp_type', 'args', 'generate_router', )
exp_type: Type3OrPlaceholder
args: list[Type3OrPlaceholder]
generate_router: TypeApplicationRouter['TupleMatchConstraint', CheckResult]
def __init__(self, exp_type: Type3OrPlaceholder, args: Iterable[Type3OrPlaceholder], comment: str):
super().__init__(comment=comment)
def __init__(self, context: Context, exp_type: Type3OrPlaceholder, args: Iterable[Type3OrPlaceholder], comment: str):
super().__init__(context=context, comment=comment)
self.exp_type = exp_type
self.args = list(args)
self.generate_router = TypeApplicationRouter()
self.generate_router.add(context.build.dynamic_array, self.__class__._generate_dynamic_array)
self.generate_router.add(context.build.static_array, self.__class__._generate_static_array)
self.generate_router.add(context.build.tuple_, self.__class__._generate_tuple)
def _generate_dynamic_array(self, sa_args: tuple[Type3]) -> CheckResult:
sa_type, = sa_args
return [
SameTypeConstraint(arg, sa_type)
SameTypeConstraint(self.context, arg, sa_type)
for arg in self.args
]
@ -312,7 +332,7 @@ class TupleMatchConstraint(ConstraintBase):
return Error('Mismatch between applied types argument count', comment=self.comment)
return [
SameTypeConstraint(arg, sa_type)
SameTypeConstraint(self.context, arg, sa_type)
for arg in self.args
]
@ -321,15 +341,10 @@ class TupleMatchConstraint(ConstraintBase):
return Error('Mismatch between applied types argument count', comment=self.comment)
return [
SameTypeConstraint(arg, oth_arg)
SameTypeConstraint(self.context, arg, oth_arg)
for arg, oth_arg in zip(self.args, tp_args, strict=True)
]
GENERATE_ROUTER = TypeApplicationRouter['TupleMatchConstraint', CheckResult]()
GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array)
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
def check(self) -> CheckResult:
exp_type = self.exp_type
if isinstance(exp_type, PlaceholderForType):
@ -339,7 +354,7 @@ class TupleMatchConstraint(ConstraintBase):
exp_type = exp_type.resolve_as
try:
return self.__class__.GENERATE_ROUTER(self, exp_type)
return self.generate_router(self, exp_type)
except NoRouteForTypeException:
raise NotImplementedError(exp_type)
@ -347,16 +362,14 @@ class MustImplementTypeClassConstraint(ConstraintBase):
"""
A type must implement a given type class
"""
__slots__ = ('context', 'type_class3', 'types', )
__slots__ = ('type_class3', 'types', )
context: Context
type_class3: Type3Class
types: list[Type3OrPlaceholder]
def __init__(self, context: Context, type_class3: Type3Class, typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
super().__init__(context=context, comment=comment)
self.context = context
self.type_class3 = type_class3
self.types = typ_list
@ -382,7 +395,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
assert len(typ_list) == len(self.types)
key = (self.type_class3, tuple(typ_list), )
if key in self.context.type_class_instances_existing:
if key in self.context.build.type_class_instances:
return None
typ_cls_name = self.type_class3 if isinstance(self.type_class3, str) else self.type_class3.name
@ -410,32 +423,46 @@ class LiteralFitsConstraint(ConstraintBase):
"""
A literal value fits a given type
"""
__slots__ = ('type3', 'literal', )
__slots__ = ('type3', 'literal', 'generate_router', )
type3: Type3OrPlaceholder
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct]
generate_router: TypeApplicationRouter['LiteralFitsConstraint', CheckResult]
def __init__(
self,
context: Context,
type3: Type3OrPlaceholder,
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct],
comment: Optional[str] = None,
) -> None:
super().__init__(comment=comment)
super().__init__(context=context, comment=comment)
self.type3 = type3
self.literal = literal
self.generate_router = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]()
self.generate_router.add(context.build.dynamic_array, self.__class__._generate_dynamic_array)
self.generate_router.add(context.build.static_array, self.__class__._generate_static_array)
self.generate_router.add(context.build.struct, self.__class__._generate_struct)
self.generate_router.add(context.build.tuple_, self.__class__._generate_tuple)
def _generate_dynamic_array(self, da_args: tuple[Type3]) -> CheckResult:
da_type, = da_args
if da_type.name == 'u8':
if not isinstance(self.literal, ourlang.ConstantBytes):
return Error('Must be bytes', comment=self.comment)
return None
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
da_type, = da_args
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(da_type, y)
LiteralFitsConstraint(self.context, da_type, y)
for y in self.literal.value
)
@ -443,7 +470,7 @@ class LiteralFitsConstraint(ConstraintBase):
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(da_type, PlaceholderForType([y]))
SameTypeConstraint(self.context, da_type, PlaceholderForType([y]))
for y in self.literal.value
)
@ -461,7 +488,7 @@ class LiteralFitsConstraint(ConstraintBase):
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(sa_type, y)
LiteralFitsConstraint(self.context, sa_type, y)
for y in self.literal.value
)
@ -469,7 +496,7 @@ class LiteralFitsConstraint(ConstraintBase):
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(sa_type, PlaceholderForType([y]))
SameTypeConstraint(self.context, sa_type, PlaceholderForType([y]))
for y in self.literal.value
)
@ -485,7 +512,7 @@ class LiteralFitsConstraint(ConstraintBase):
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(x, y)
LiteralFitsConstraint(self.context, x, y)
for (_, x), y in zip(st_args, self.literal.value, strict=True)
)
@ -493,11 +520,12 @@ class LiteralFitsConstraint(ConstraintBase):
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_type3.name}.{x_n}')
SameTypeConstraint(self.context, x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_type3.name}.{x_n}')
for (x_n, x_t, ), y in zip(st_args, self.literal.value, strict=True)
)
res.append(SameTypeConstraint(
self.context,
self.literal.struct_type3,
self.type3,
comment='Struct types must match',
@ -515,7 +543,7 @@ class LiteralFitsConstraint(ConstraintBase):
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(x, y)
LiteralFitsConstraint(self.context, x, y)
for x, y in zip(tp_args, self.literal.value, strict=True)
)
@ -523,57 +551,35 @@ class LiteralFitsConstraint(ConstraintBase):
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(x, PlaceholderForType([y]))
SameTypeConstraint(self.context, x, PlaceholderForType([y]))
for x, y in zip(tp_args, self.literal.value, strict=True)
)
return res
GENERATE_ROUTER = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]()
GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array)
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.struct, _generate_struct)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
def check(self) -> CheckResult:
int_table: Dict[str, Tuple[int, bool]] = {
'u8': (1, False),
'u16': (2, False),
'u32': (4, False),
'u64': (8, False),
'i8': (1, True),
'i16': (2, True),
'i32': (4, True),
'i64': (8, True),
}
float_table: Dict[str, None] = {
'f32': None,
'f64': None,
}
if isinstance(self.type3, PlaceholderForType):
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
self.type3 = self.type3.resolve_as
if self.type3.name in int_table:
bts, sgn = int_table[self.type3.name]
type_info = self.context.build.type_info_map.get(self.type3.name)
if type_info is not None and (type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64):
assert type_info.signed is not None
if isinstance(self.literal.value, int):
try:
self.literal.value.to_bytes(bts, 'big', signed=sgn)
self.literal.value.to_bytes(type_info.alloc_size, 'big', signed=type_info.signed)
except OverflowError:
return Error(f'Must fit in {bts} byte(s)', comment=self.comment) # FIXME: Add line information
return Error(f'Must fit in {type_info.alloc_size} byte(s)', comment=self.comment) # FIXME: Add line information
return None
return Error('Must be integer', comment=self.comment) # FIXME: Add line information
if self.type3.name in float_table:
_ = float_table[self.type3.name]
if type_info is not None and (type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64):
if isinstance(self.literal.value, float):
# FIXME: Bit check
@ -581,16 +587,10 @@ class LiteralFitsConstraint(ConstraintBase):
return Error('Must be real', comment=self.comment) # FIXME: Add line information
if self.type3 is prelude.bytes_:
if isinstance(self.literal.value, bytes):
return None
return Error('Must be bytes', comment=self.comment) # FIXME: Add line information
exp_type = self.type3
try:
return self.__class__.GENERATE_ROUTER(self, exp_type)
return self.generate_router(self, exp_type)
except NoRouteForTypeException:
raise NotImplementedError(exp_type)
@ -610,32 +610,40 @@ class CanBeSubscriptedConstraint(ConstraintBase):
"""
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
"""
__slots__ = ('ret_type3', 'type3', 'index_type3', 'index_const', )
__slots__ = ('ret_type3', 'type3', 'index_type3', 'index_const', 'generate_router', )
ret_type3: PlaceholderForType
type3: PlaceholderForType
index_type3: PlaceholderForType
index_const: int | None
generate_router: TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]
def __init__(
self,
context: Context,
ret_type3: PlaceholderForType,
type3: PlaceholderForType,
index_type3: PlaceholderForType,
index_const: int | None,
comment: Optional[str] = None,
) -> None:
super().__init__(comment=comment)
super().__init__(context=context, comment=comment)
self.ret_type3 = ret_type3
self.type3 = type3
self.index_type3 = index_type3
self.index_const = index_const
self.generate_router = TypeApplicationRouter()
self.generate_router.add_n(context.build.types['bytes'], self.__class__._generate_bytes)
self.generate_router.add(context.build.static_array, self.__class__._generate_static_array)
self.generate_router.add(context.build.tuple_, self.__class__._generate_tuple)
def _generate_bytes(self) -> CheckResult:
return [
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'),
SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
SameTypeConstraint(self.context, self.context.build.types['u32'], self.index_type3, comment='([]) :: bytes -> u32 -> u8'),
SameTypeConstraint(self.context, self.context.build.types['u8'], self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
]
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
@ -645,8 +653,8 @@ class CanBeSubscriptedConstraint(ConstraintBase):
return Error('Tuple index out of range')
return [
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(self.context, self.context.build.types['u32'], self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(self.context, sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
]
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
@ -661,15 +669,10 @@ class CanBeSubscriptedConstraint(ConstraintBase):
return Error('Tuple index out of range')
return [
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(tp_args[self.index_const], self.ret_type3, comment=f'Tuple subscript index {self.index_const}'),
SameTypeConstraint(self.context, self.context.build.types['u32'], self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(self.context, tp_args[self.index_const], self.ret_type3, comment=f'Tuple subscript index {self.index_const}'),
]
GENERATE_ROUTER = TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]()
GENERATE_ROUTER.add_n(prelude.bytes_, _generate_bytes)
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
def check(self) -> CheckResult:
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
@ -677,7 +680,7 @@ class CanBeSubscriptedConstraint(ConstraintBase):
exp_type = self.type3.resolve_as
try:
return self.__class__.GENERATE_ROUTER(self, exp_type)
return self.generate_router(self, exp_type)
except NoRouteForTypeException:
return Error(f'{exp_type.name} cannot be subscripted')

View File

@ -3,9 +3,9 @@ This module generates the typing constraints for Phasm.
The constraints solver can then try to resolve all constraints.
"""
from typing import Generator, List
from typing import Any, Generator, List
from .. import ourlang, prelude
from .. import ourlang
from .constraints import (
CanBeSubscriptedConstraint,
ConstraintBase,
@ -30,16 +30,15 @@ from .types import Type3, TypeApplication_Struct, TypeConstructor_Function
ConstraintGenerator = Generator[ConstraintBase, None, None]
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
ctx = Context()
ctx.type_class_instances_existing.update(prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING)
def phasm_type3_generate_constraints(inp: ourlang.Module[Any]) -> List[ConstraintBase]:
ctx = Context(inp.build)
return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant, phft: PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
yield LiteralFitsConstraint(
phft, inp,
ctx, phft, inp,
comment='The given literal must fit the expected type'
)
return
@ -77,7 +76,8 @@ def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: Plac
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: PlaceholderForType) -> ConstraintGenerator:
yield SameTypeConstraint(
prelude.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3),
ctx,
ctx.build.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3),
phft,
comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})',
)
@ -151,6 +151,7 @@ def _expression_function_call(
type_var_map.setdefault(func_arg, PlaceholderForType([]))
yield SameFunctionArgumentConstraint(
ctx,
func_var_map[sig_arg],
sig_arg,
type_var_map,
@ -182,6 +183,7 @@ def _expression_function_call(
continue
yield SameTypeArgumentConstraint(
ctx,
type_var_map[sig_arg],
type_var_map[sig_arg.application.arguments],
comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}',
@ -195,15 +197,15 @@ def _expression_function_call(
comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument'
if isinstance(sig_part, TypeVariable):
yield SameTypeConstraint(type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
yield SameTypeConstraint(ctx, type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
continue
if isinstance(sig_part, Type3):
yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment)
yield SameTypeConstraint(ctx, sig_part, arg_placeholders[arg_expr], comment=comment)
continue
if isinstance(sig_part, FunctionArgument):
yield SameTypeConstraint(func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
yield SameTypeConstraint(ctx, func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
continue
raise NotImplementedError(sig_part)
@ -215,7 +217,7 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType)
return
if isinstance(inp, ourlang.VariableReference):
yield SameTypeConstraint(inp.variable.type3, phft,
yield SameTypeConstraint(ctx, inp.variable.type3, phft,
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
return
@ -239,6 +241,7 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType)
r_type.append(arg_phft)
yield TupleMatchConstraint(
ctx,
phft,
r_type,
comment='The type of a tuple is a combination of its members'
@ -254,9 +257,9 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType)
yield from expression(ctx, inp.index, index_phft)
if isinstance(inp.index, ourlang.ConstantPrimitive) and isinstance(inp.index.value, int):
yield CanBeSubscriptedConstraint(phft, varref_phft, index_phft, inp.index.value)
yield CanBeSubscriptedConstraint(ctx, phft, varref_phft, index_phft, inp.index.value)
else:
yield CanBeSubscriptedConstraint(phft, varref_phft, index_phft, None)
yield CanBeSubscriptedConstraint(ctx, phft, varref_phft, index_phft, None)
return
if isinstance(inp, ourlang.AccessStructMember):
@ -265,7 +268,7 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType)
mem_typ = dict(inp.struct_type3.application.arguments)[inp.member]
yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO
yield SameTypeConstraint(mem_typ, phft,
yield SameTypeConstraint(ctx, mem_typ, phft,
comment=f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
return
@ -276,7 +279,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement
yield from expression(ctx, inp.value, phft)
yield SameTypeConstraint(fun.returns_type3, phft,
yield SameTypeConstraint(ctx, fun.returns_type3, phft,
comment=f'The type of the value returned from function {fun.name} should match its return type')
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
@ -284,7 +287,7 @@ def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf)
yield from expression(ctx, inp.test, test_phft)
yield SameTypeConstraint(test_phft, prelude.bool_,
yield SameTypeConstraint(ctx, test_phft, ctx.build.types['bool'],
comment='Must pass a boolean expression to if')
for stmt in inp.statements:
@ -317,10 +320,10 @@ def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Constra
phft = PlaceholderForType([inp.constant])
yield from constant(ctx, inp.constant, phft)
yield SameTypeConstraint(inp.type3, phft,
yield SameTypeConstraint(ctx, inp.type3, phft,
comment=f'The type of the value for module constant definition {inp.name} should match the type of that constant')
def module(ctx: Context, inp: ourlang.Module) -> ConstraintGenerator:
def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator:
for cdef in inp.constant_defs.values():
yield from module_constant_def(ctx, cdef)

View File

@ -1,14 +1,14 @@
"""
Entry point to the type3 system
"""
from typing import Dict, List
from typing import Any, Dict, List
from .. import codestyle, ourlang
from .constraints import (
ConstraintBase,
Error,
HumanReadableRet,
RequireTypeSubstitutes,
SameTypeConstraint,
SubstitutionMap,
)
from .constraintsgenerator import phasm_type3_generate_constraints
@ -25,7 +25,28 @@ class Type3Exception(BaseException):
Thrown when the Type3 system detects constraints that do not hold
"""
def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
class DeducedType:
__slots__ = ('phft', 'typ', 'comment')
phft: PlaceholderForType
typ: Type3
comment: str
def __init__(self, phft: PlaceholderForType, typ: Type3) -> None:
self.phft = phft
self.typ = typ
self.comment = 'Deduced type'
def human_readable(self) -> HumanReadableRet:
return (
'{phft} == {typ}',
{
'phft': self.phft,
'typ': self.typ,
}
)
def phasm_type3(inp: ourlang.Module[Any], verbose: bool = False) -> None:
constraint_list = phasm_type3_generate_constraints(inp)
assert constraint_list
@ -116,7 +137,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
for expr in plh.update_on_substitution:
expr.type3 = typ
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase | DeducedType) -> None:
txt, fmt = constraint.human_readable()
act_fmt: Dict[str, str] = {}
for fmt_key, fmt_val in fmt.items():
@ -151,7 +172,7 @@ def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[in
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
print('=== v type3 constraint_list v === ')
for psk, psv in placeholder_substitutes.items():
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, comment='Deduced type'))
print_constraint(placeholder_id_map, DeducedType(psk, psv))
for constraint in constraint_list:
print_constraint(placeholder_id_map, constraint)

View File

@ -79,7 +79,7 @@ class Type3(KindArgument):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Type3):
raise NotImplementedError
raise NotImplementedError(other)
return self is other

View File

@ -3,17 +3,17 @@ import struct
import sys
from typing import Any, Callable, Generator, Iterable, List, TextIO, Union
from phasm import compiler, prelude
from phasm import compiler
from phasm.build import builtins
from phasm.codestyle import phasm_render
from phasm.runtime import (
calculate_alloc_size,
calculate_alloc_size_static_array,
calculate_alloc_size_struct,
calculate_alloc_size_tuple,
)
from phasm.stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP
from phasm.type3 import types as type3types
from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter
from phasm.wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
)
from . import runners
@ -125,17 +125,14 @@ class Suite:
runner.interpreter_dump_memory(sys.stderr)
for arg, arg_typ in zip(args, func_args, strict=True):
if arg_typ in (prelude.u8, prelude.u16, prelude.u32, prelude.u64, ):
arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name)
if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeInt32 or arg_typ_info.wasm_type is WasmTypeInt64):
assert isinstance(arg, int)
wasm_args.append(arg)
continue
if arg_typ in (prelude.i8, prelude.i16, prelude.i32, prelude.i64, ):
assert isinstance(arg, int)
wasm_args.append(arg)
continue
if arg_typ in (prelude.f32, prelude.f64, ):
if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeFloat32 or arg_typ_info.wasm_type is WasmTypeFloat64):
assert isinstance(arg, float)
wasm_args.append(arg)
continue
@ -168,19 +165,6 @@ class Suite:
def write_header(textio: TextIO, msg: str) -> None:
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
WRITE_LOOKUP_MAP = {
'u8': compiler.module_data_u8,
'u16': compiler.module_data_u16,
'u32': compiler.module_data_u32,
'u64': compiler.module_data_u64,
'i8': compiler.module_data_i8,
'i16': compiler.module_data_i16,
'i32': compiler.module_data_i32,
'i64': compiler.module_data_i64,
'f32': compiler.module_data_f32,
'f64': compiler.module_data_f64,
}
def _write_memory_stored_value(
runner: runners.RunnerBase,
adr: int,
@ -189,10 +173,14 @@ def _write_memory_stored_value(
) -> int:
try:
adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return TYPE_INFO_CONSTRUCTED.alloc_size
ptr_type_info = runner.phasm_ast.build.type_info_map['ptr']
to_write = compiler.module_data_primitive(ptr_type_info, adr2)
runner.interpreter_write_memory(adr, to_write)
return runner.phasm_ast.build.type_info_constructed.alloc_size
except NoRouteForTypeException:
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
val_typ_info = runner.phasm_ast.build.type_info_map[val_typ.name]
to_write = compiler.module_data_primitive(val_typ_info, val)
runner.interpreter_write_memory(adr, to_write)
return len(to_write)
@ -213,16 +201,19 @@ def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any],
da_type, = da_args
if da_type.name == 'u8':
return _allocate_memory_stored_bytes(attrs)
if not isinstance(val, tuple):
raise InvalidArgumentException(f'Expected tuple; got {val!r} instead')
alloc_size = 4 + len(val) * calculate_alloc_size(da_type, True)
alloc_size = 4 + len(val) * runner.phasm_ast.build.calculate_alloc_size(da_type, True)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
offset = adr
offset += _write_memory_stored_value(runner, offset, prelude.u32, len(val))
offset += _write_memory_stored_value(runner, offset, runner.phasm_ast.build.types['u32'], len(val))
for val_el_val in val:
offset += _write_memory_stored_value(runner, offset, da_type, val_el_val)
return adr
@ -237,7 +228,7 @@ def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any],
if sa_len.value != len(val):
raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead')
alloc_size = calculate_alloc_size_static_array(False, sa_args)
alloc_size = runner.phasm_ast.build.calculate_alloc_size_static_array(sa_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
@ -252,7 +243,7 @@ def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_arg
assert isinstance(val, dict)
alloc_size = calculate_alloc_size_struct(False, st_args)
alloc_size = runner.phasm_ast.build.calculate_alloc_size_struct(st_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
@ -272,7 +263,7 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args
assert isinstance(val, tuple)
alloc_size = calculate_alloc_size_tuple(False, tp_args)
alloc_size = runner.phasm_ast.build.calculate_alloc_size_tuple(tp_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
@ -285,11 +276,11 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args
return adr
ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]()
ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.dynamic_array, _allocate_memory_stored_dynamic_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.static_array, _allocate_memory_stored_static_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple)
# ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.dynamic_array, _allocate_memory_stored_dynamic_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.static_array, _allocate_memory_stored_static_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.struct, _allocate_memory_stored_struct)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.tuple_, _allocate_memory_stored_tuple)
def _load_memory_stored_returned_value(
runner: runners.RunnerBase,
@ -298,96 +289,105 @@ def _load_memory_stored_returned_value(
) -> Any:
ret_type3 = runner.phasm_ast.functions[func_name].returns_type3
if ret_type3 is prelude.none:
return None
if ret_type3.name in runner.phasm_ast.build.type_info_map:
type_info = runner.phasm_ast.build.type_info_map[ret_type3.name]
if ret_type3 is prelude.bool_:
assert isinstance(wasm_value, int), wasm_value
return 0 != wasm_value
# if ret_type3 is prelude.none:
# return None
if ret_type3 in (prelude.i8, prelude.i16, prelude.i32, prelude.i64):
assert isinstance(wasm_value, int), wasm_value
# if ret_type3 is prelude.bool_:
# assert isinstance(wasm_value, int), wasm_value
# return 0 != wasm_value
if ret_type3 is prelude.i8:
# Values are actually i32
# Have to reinterpret to load proper value
data = struct.pack('<i', wasm_value)
wasm_value, = struct.unpack('<bxxx', data)
# if ret_type3 in (prelude.i8, prelude.i16, prelude.i32, prelude.i64):
# assert isinstance(wasm_value, int), wasm_value
if ret_type3 is prelude.i16:
# Values are actually i32
# Have to reinterpret to load proper value
data = struct.pack('<i', wasm_value)
wasm_value, = struct.unpack('<hxx', data)
# if ret_type3 is prelude.i8:
# # Values are actually i32
# # Have to reinterpret to load proper value
# data = struct.pack('<i', wasm_value)
# wasm_value, = struct.unpack('<bxxx', data)
return wasm_value
# if ret_type3 is prelude.i16:
# # Values are actually i32
# # Have to reinterpret to load proper value
# data = struct.pack('<i', wasm_value)
# wasm_value, = struct.unpack('<hxx', data)
if ret_type3 in (prelude.u8, prelude.u16, prelude.u32, prelude.u64):
assert isinstance(wasm_value, int), wasm_value
# return wasm_value
if wasm_value < 0:
# WASM does not support unsigned values through its interface
# Cast and then reinterpret
# if ret_type3 in (prelude.u8, prelude.u16, prelude.u32, prelude.u64):
# assert isinstance(wasm_value, int), wasm_value
letter = {
'u32': 'i',
'u64': 'q',
}[ret_type3.name]
# if wasm_value < 0:
# # WASM does not support unsigned values through its interface
# # Cast and then reinterpret
data = struct.pack(f'<{letter}', wasm_value)
wasm_value, = struct.unpack(f'<{letter.upper()}', data)
# letter = {
# 'u32': 'i',
# 'u64': 'q',
# }[ret_type3.name]
return wasm_value
# data = struct.pack(f'<{letter}', wasm_value)
# wasm_value, = struct.unpack(f'<{letter.upper()}', data)
if ret_type3 in (prelude.f32, prelude.f64, ):
assert isinstance(wasm_value, float), wasm_value
return wasm_value
# return wasm_value
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
assert isinstance(wasm_value, int), wasm_value
return wasm_value
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
assert isinstance(wasm_value, float), wasm_value
return wasm_value
assert isinstance(wasm_value, int), wasm_value
return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
typ_info = TYPE_INFO_MAP.get(typ.name, TYPE_INFO_CONSTRUCTED)
typ_info = runner.phasm_ast.build.type_info_map.get(typ.name)
assert len(inp) == typ_info.alloc_size
if typ_info is None:
assert len(inp) == 4
if typ is prelude.u8:
return struct.unpack('<B', inp)[0]
if typ is prelude.u16:
return struct.unpack('<H', inp)[0]
if typ is prelude.u32:
return struct.unpack('<I', inp)[0]
if typ is prelude.u64:
return struct.unpack('<Q', inp)[0]
if typ is prelude.i8:
return struct.unpack('<b', inp)[0]
if typ is prelude.i16:
return struct.unpack('<h', inp)[0]
if typ is prelude.i32:
return struct.unpack('<i', inp)[0]
if typ is prelude.i64:
return struct.unpack('<q', inp)[0]
if typ is prelude.f32:
return struct.unpack('<f', inp)[0]
if typ is prelude.f64:
return struct.unpack('<d', inp)[0]
if typ_info is TYPE_INFO_CONSTRUCTED:
# Note: For applied types, inp should contain a 4 byte pointer
adr = struct.unpack('<I', inp)[0]
return LOAD_FROM_ADDRESS_ROUTER((runner, adr), typ)
assert len(inp) == typ_info.alloc_size
if typ.name == 'u8':
return struct.unpack('<B', inp)[0]
if typ.name == 'u16':
return struct.unpack('<H', inp)[0]
if typ.name == 'u32':
return struct.unpack('<I', inp)[0]
if typ.name == 'u64':
return struct.unpack('<Q', inp)[0]
if typ.name == 'i8':
return struct.unpack('<b', inp)[0]
if typ.name == 'i16':
return struct.unpack('<h', inp)[0]
if typ.name == 'i32':
return struct.unpack('<i', inp)[0]
if typ.name == 'i64':
return struct.unpack('<q', inp)[0]
if typ.name == 'f32':
return struct.unpack('<f', inp)[0]
if typ.name == 'f64':
return struct.unpack('<d', inp)[0]
raise NotImplementedError(typ, inp)
def _load_bytes_from_address(attrs: tuple[runners.RunnerBase, int]) -> bytes:
@ -410,13 +410,16 @@ def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_a
runner, adr = attrs
da_type, = da_args
if da_type.name == 'u8':
return _load_bytes_from_address(attrs)
sys.stderr.write(f'Reading 0x{adr:08x} {da_type:s}[...]\n')
read_bytes = runner.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
adr += 4
arg_size_1 = calculate_alloc_size(da_type, is_member=True)
arg_size_1 = runner.phasm_ast.build.calculate_alloc_size(da_type, is_member=True)
arg_sizes = [arg_size_1 for _ in range(array_len)] # _split_read_bytes requires one arg per value
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
@ -434,7 +437,7 @@ def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_ar
sa_len = len_typ.value
arg_size_1 = calculate_alloc_size(sub_typ, is_member=True)
arg_size_1 = runner.phasm_ast.build.calculate_alloc_size(sub_typ, is_member=True)
arg_sizes = [arg_size_1 for _ in range(sa_len)] # _split_read_bytes requires one arg per value
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
@ -450,7 +453,7 @@ def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tu
sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
arg_sizes = [
calculate_alloc_size(x, is_member=True)
runner.phasm_ast.build.calculate_alloc_size(x, is_member=True)
for _, x in st_args
]
@ -467,7 +470,7 @@ def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tup
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n')
arg_sizes = [
calculate_alloc_size(x, is_member=True)
runner.phasm_ast.build.calculate_alloc_size(x, is_member=True)
for x in tp_args
]
@ -479,8 +482,7 @@ def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tup
)
LOAD_FROM_ADDRESS_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]()
LOAD_FROM_ADDRESS_ROUTER.add_n(prelude.bytes_, _load_bytes_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.dynamic_array, _load_dynamic_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.static_array, _load_static_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.struct, _load_struct_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.tuple_, _load_tuple_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.dynamic_array, _load_dynamic_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.static_array, _load_static_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.struct, _load_struct_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.tuple_, _load_tuple_from_address)

View File

@ -11,6 +11,7 @@ from phasm.compiler import phasm_compile
from phasm.optimise.removeunusedfuncs import removeunusedfuncs
from phasm.parser import phasm_parse
from phasm.type3.entry import phasm_type3
from phasm.wasmgenerator import Generator as WasmGenerator
Imports = Optional[Dict[str, Callable[[Any], Any]]]
@ -19,7 +20,7 @@ class RunnerBase:
Base class
"""
phasm_code: str
phasm_ast: ourlang.Module
phasm_ast: ourlang.Module[WasmGenerator]
wasm_ast: wasm.Module
wasm_asm: str
wasm_bin: bytes

View File

@ -15,7 +15,8 @@ def testEntry() -> bytes:
result = Suite(code_py).run_code()
assert b"Hello" == result.returned_value
assert b"Hello" == result.returned_value[0:5]
assert 5 == len(result.returned_value)
@pytest.mark.integration_test
def test_bytes_export_instantiation():