Compare commits
1 Commits
167560d7dd
...
97b61e3ee1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97b61e3ee1 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,4 +3,6 @@
|
|||||||
/.coverage
|
/.coverage
|
||||||
/venv
|
/venv
|
||||||
|
|
||||||
|
/tests/integration/test_lang/test_generated_*.py
|
||||||
|
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|||||||
17
Makefile
17
Makefile
@ -24,14 +24,14 @@ WASM2C := $(WABT_DIR)/bin/wasm2c
|
|||||||
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
|
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
|
||||||
venv/bin/python3 -m http.server --directory examples
|
venv/bin/python3 -m http.server --directory examples
|
||||||
|
|
||||||
test: venv/.done
|
test: venv/.done $(subst .json,.py,$(subst /generator_,/test_generated_,$(wildcard tests/integration/test_lang/generator_*.json)))
|
||||||
venv/bin/pytest tests $(TEST_FLAGS)
|
venv/bin/pytest tests $(TEST_FLAGS)
|
||||||
|
|
||||||
lint: venv/.done
|
lint: venv/.done
|
||||||
venv/bin/pylint phasm
|
venv/bin/ruff check phasm tests
|
||||||
|
|
||||||
typecheck: venv/.done
|
typecheck: venv/.done
|
||||||
venv/bin/mypy --strict phasm tests/integration/runners.py
|
venv/bin/mypy --strict phasm tests/integration/helpers.py tests/integration/runners.py
|
||||||
|
|
||||||
venv/.done: requirements.txt
|
venv/.done: requirements.txt
|
||||||
python3.10 -m venv venv
|
python3.10 -m venv venv
|
||||||
@ -39,9 +39,20 @@ venv/.done: requirements.txt
|
|||||||
venv/bin/python3 -m pip install -r $^
|
venv/bin/python3 -m pip install -r $^
|
||||||
touch $@
|
touch $@
|
||||||
|
|
||||||
|
tests/integration/test_lang/test_generated_%.py: venv/.done tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_%.json
|
||||||
|
venv/bin/python3 tests/integration/test_lang/generator.py tests/integration/test_lang/generator.md tests/integration/test_lang/generator_$*.json > $@
|
||||||
|
|
||||||
clean-examples:
|
clean-examples:
|
||||||
rm -f examples/*.wat examples/*.wasm examples/*.wat.html examples/*.py.html
|
rm -f examples/*.wat examples/*.wasm examples/*.wat.html examples/*.py.html
|
||||||
|
|
||||||
|
clean-generated-tests:
|
||||||
|
rm -f tests/integration/test_lang/test_generated_*.py
|
||||||
|
|
||||||
.SECONDARY: # Keep intermediate files
|
.SECONDARY: # Keep intermediate files
|
||||||
|
|
||||||
.PHONY: examples
|
.PHONY: examples
|
||||||
|
|
||||||
|
# So generally the right thing to do is to delete the target file if the recipe fails after beginning to change the file.
|
||||||
|
# make will do this if .DELETE_ON_ERROR appears as a target.
|
||||||
|
# This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.
|
||||||
|
.DELETE_ON_ERROR:
|
||||||
|
|||||||
@ -4,9 +4,10 @@ Functions for using this module from CLI
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from .compiler import phasm_compile
|
||||||
from .parser import phasm_parse
|
from .parser import phasm_parse
|
||||||
from .type3.entry import phasm_type3
|
from .type3.entry import phasm_type3
|
||||||
from .compiler import phasm_compile
|
|
||||||
|
|
||||||
def main(source: str, sink: str) -> int:
|
def main(source: str, sink: str) -> int:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -9,6 +9,7 @@ from . import ourlang
|
|||||||
from .type3 import types as type3types
|
from .type3 import types as type3types
|
||||||
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
|
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
|
||||||
|
|
||||||
|
|
||||||
def phasm_render(inp: ourlang.Module) -> str:
|
def phasm_render(inp: ourlang.Module) -> str:
|
||||||
"""
|
"""
|
||||||
Public method for rendering a Phasm module into Phasm code
|
Public method for rendering a Phasm module into Phasm code
|
||||||
@ -39,7 +40,7 @@ def type3(inp: Type3OrPlaceholder) -> str:
|
|||||||
assert isinstance(inp.args[0], Type3), TYPE3_ASSERTION_ERROR
|
assert isinstance(inp.args[0], Type3), TYPE3_ASSERTION_ERROR
|
||||||
assert isinstance(inp.args[1], type3types.IntType3), TYPE3_ASSERTION_ERROR
|
assert isinstance(inp.args[1], type3types.IntType3), TYPE3_ASSERTION_ERROR
|
||||||
|
|
||||||
return inp.args[0].name + '[' + inp.args[1].name + ']'
|
return type3(inp.args[0]) + '[' + inp.args[1].name + ']'
|
||||||
|
|
||||||
return inp.name
|
return inp.name
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,20 @@
|
|||||||
"""
|
"""
|
||||||
This module contains the code to convert parsed Ourlang into WebAssembly code
|
This module contains the code to convert parsed Ourlang into WebAssembly code
|
||||||
"""
|
"""
|
||||||
from typing import List, Union
|
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
from . import codestyle
|
from . import codestyle, ourlang, wasm
|
||||||
from . import ourlang
|
from .runtime import calculate_alloc_size, calculate_member_offset
|
||||||
from .type3 import types as type3types
|
|
||||||
from . import wasm
|
|
||||||
|
|
||||||
from .stdlib import alloc as stdlib_alloc
|
from .stdlib import alloc as stdlib_alloc
|
||||||
from .stdlib import types as stdlib_types
|
from .stdlib import types as stdlib_types
|
||||||
|
from .type3 import types as type3types
|
||||||
from .wasmgenerator import Generator as WasmGenerator
|
from .wasmgenerator import Generator as WasmGenerator
|
||||||
|
|
||||||
LOAD_STORE_TYPE_MAP = {
|
LOAD_STORE_TYPE_MAP = {
|
||||||
|
'i8': 'i32', # Have to use an u32, since there is no native i8 type
|
||||||
'u8': 'i32', # Have to use an u32, since there is no native u8 type
|
'u8': 'i32', # Have to use an u32, since there is no native u8 type
|
||||||
|
|
||||||
'i32': 'i32',
|
'i32': 'i32',
|
||||||
'i64': 'i64',
|
'i64': 'i64',
|
||||||
'u32': 'i32',
|
'u32': 'i32',
|
||||||
@ -56,6 +55,11 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
|||||||
if inp == type3types.u64:
|
if inp == type3types.u64:
|
||||||
return wasm.WasmTypeInt64()
|
return wasm.WasmTypeInt64()
|
||||||
|
|
||||||
|
if inp == type3types.i8:
|
||||||
|
# WebAssembly has only support for 32 and 64 bits
|
||||||
|
# So we need to store more memory per byte
|
||||||
|
return wasm.WasmTypeInt32()
|
||||||
|
|
||||||
if inp == type3types.i32:
|
if inp == type3types.i32:
|
||||||
return wasm.WasmTypeInt32()
|
return wasm.WasmTypeInt32()
|
||||||
|
|
||||||
@ -173,7 +177,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
|
|||||||
wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})')
|
wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})')
|
||||||
|
|
||||||
# Allocated the required amounts of bytes in memory
|
# Allocated the required amounts of bytes in memory
|
||||||
wgn.i32.const(_calculate_alloc_size(inp.type3, is_member=False))
|
wgn.i32.const(calculate_alloc_size(inp.type3, is_member=False))
|
||||||
wgn.call(stdlib_alloc.__alloc__)
|
wgn.call(stdlib_alloc.__alloc__)
|
||||||
wgn.local.set(tmp_var)
|
wgn.local.set(tmp_var)
|
||||||
|
|
||||||
@ -186,7 +190,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
|
|||||||
|
|
||||||
assert element.type3 == exp_type3
|
assert element.type3 == exp_type3
|
||||||
|
|
||||||
if isinstance(exp_type3, type3types.AppliedType3) and exp_type3.base is type3types.tuple:
|
if isinstance(exp_type3, type3types.StructType3) or isinstance(exp_type3, type3types.AppliedType3):
|
||||||
mtyp = 'i32'
|
mtyp = 'i32'
|
||||||
else:
|
else:
|
||||||
assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
|
assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
|
||||||
@ -198,7 +202,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
|
|||||||
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset))
|
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset))
|
||||||
wgn.add_statement('nop', comment='POST')
|
wgn.add_statement('nop', comment='POST')
|
||||||
|
|
||||||
offset += _calculate_alloc_size(exp_type3, is_member=True)
|
offset += calculate_alloc_size(exp_type3, is_member=True)
|
||||||
|
|
||||||
# Return the allocated address
|
# Return the allocated address
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
@ -210,7 +214,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
if isinstance(inp, ourlang.ConstantPrimitive):
|
if isinstance(inp, ourlang.ConstantPrimitive):
|
||||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||||
|
|
||||||
if inp.type3 == type3types.u8:
|
if inp.type3 in (type3types.i8, type3types.u8, ):
|
||||||
# No native u8 type - treat as i32, with caution
|
# No native u8 type - treat as i32, with caution
|
||||||
assert isinstance(inp.value, int)
|
assert isinstance(inp.value, int)
|
||||||
wgn.i32.const(inp.value)
|
wgn.i32.const(inp.value)
|
||||||
@ -435,7 +439,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
wgn.unreachable(comment='Out of bounds')
|
wgn.unreachable(comment='Out of bounds')
|
||||||
|
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
wgn.i32.const(_calculate_alloc_size(el_type))
|
wgn.i32.const(calculate_alloc_size(el_type))
|
||||||
wgn.i32.mul()
|
wgn.i32.mul()
|
||||||
wgn.i32.add()
|
wgn.i32.add()
|
||||||
|
|
||||||
@ -452,7 +456,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
offset = 0
|
offset = 0
|
||||||
for el_type in inp.varref.type3.args[0:inp.index.value]:
|
for el_type in inp.varref.type3.args[0:inp.index.value]:
|
||||||
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||||
offset += _calculate_alloc_size(el_type)
|
offset += calculate_alloc_size(el_type)
|
||||||
|
|
||||||
# This doubles as the out of bounds check
|
# This doubles as the out of bounds check
|
||||||
el_type = inp.varref.type3.args[inp.index.value]
|
el_type = inp.varref.type3.args[inp.index.value]
|
||||||
@ -478,7 +482,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
mtyp = LOAD_STORE_TYPE_MAP[inp.struct_type3.members[inp.member].name]
|
mtyp = LOAD_STORE_TYPE_MAP[inp.struct_type3.members[inp.member].name]
|
||||||
|
|
||||||
expression(wgn, inp.varref)
|
expression(wgn, inp.varref)
|
||||||
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset(
|
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset(
|
||||||
inp.struct_type3, inp.member
|
inp.struct_type3, inp.member
|
||||||
)))
|
)))
|
||||||
return
|
return
|
||||||
@ -664,7 +668,7 @@ def module_data_u8(inp: int) -> bytes:
|
|||||||
|
|
||||||
# FIXME: All u8 values are stored as u32
|
# FIXME: All u8 values are stored as u32
|
||||||
"""
|
"""
|
||||||
return struct.pack('<i', inp) # Should be B
|
return struct.pack('<I', inp) # Should be 'B'
|
||||||
|
|
||||||
def module_data_u32(inp: int) -> bytes:
|
def module_data_u32(inp: int) -> bytes:
|
||||||
"""
|
"""
|
||||||
@ -678,6 +682,14 @@ def module_data_u64(inp: int) -> bytes:
|
|||||||
"""
|
"""
|
||||||
return struct.pack('<Q', inp)
|
return struct.pack('<Q', inp)
|
||||||
|
|
||||||
|
def module_data_i8(inp: int) -> bytes:
|
||||||
|
"""
|
||||||
|
Compile: module data, i8 value
|
||||||
|
|
||||||
|
# FIXME: All i8 values are stored as i32
|
||||||
|
"""
|
||||||
|
return struct.pack('<i', inp) # Should be a 'b'
|
||||||
|
|
||||||
def module_data_i32(inp: int) -> bytes:
|
def module_data_i32(inp: int) -> bytes:
|
||||||
"""
|
"""
|
||||||
Compile: module data, i32 value
|
Compile: module data, i32 value
|
||||||
@ -745,6 +757,12 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
|
|||||||
data_list.append(module_data_u64(constant.value))
|
data_list.append(module_data_u64(constant.value))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if constant.type3 == type3types.i8:
|
||||||
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||||
|
assert isinstance(constant.value, int)
|
||||||
|
data_list.append(module_data_i8(constant.value))
|
||||||
|
continue
|
||||||
|
|
||||||
if constant.type3 == type3types.i32:
|
if constant.type3 == type3types.i32:
|
||||||
assert isinstance(constant, ourlang.ConstantPrimitive)
|
assert isinstance(constant, ourlang.ConstantPrimitive)
|
||||||
assert isinstance(constant.value, int)
|
assert isinstance(constant.value, int)
|
||||||
@ -830,74 +848,26 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
|
|||||||
tmp_var = wgn.temp_var_i32('struct_adr')
|
tmp_var = wgn.temp_var_i32('struct_adr')
|
||||||
|
|
||||||
# Allocated the required amounts of bytes in memory
|
# Allocated the required amounts of bytes in memory
|
||||||
wgn.i32.const(_calculate_alloc_size(inp.struct_type3))
|
wgn.i32.const(calculate_alloc_size(inp.struct_type3))
|
||||||
wgn.call(stdlib_alloc.__alloc__)
|
wgn.call(stdlib_alloc.__alloc__)
|
||||||
wgn.local.set(tmp_var)
|
wgn.local.set(tmp_var)
|
||||||
|
|
||||||
# Store each member individually
|
# Store each member individually
|
||||||
for memname, mtyp3 in inp.struct_type3.members.items():
|
for memname, mtyp3 in inp.struct_type3.members.items():
|
||||||
|
mtyp: Optional[str]
|
||||||
|
if isinstance(mtyp3, type3types.StructType3) or isinstance(mtyp3, type3types.AppliedType3):
|
||||||
|
mtyp = 'i32'
|
||||||
|
else:
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
||||||
|
|
||||||
if mtyp is None:
|
if mtyp is None:
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, mtyp3)
|
raise NotImplementedError(expression, inp, mtyp3)
|
||||||
|
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
wgn.add_statement('local.get', f'${memname}')
|
wgn.add_statement('local.get', f'${memname}')
|
||||||
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(_calculate_member_offset(
|
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(calculate_member_offset(
|
||||||
inp.struct_type3, memname
|
inp.struct_type3, memname
|
||||||
)))
|
)))
|
||||||
|
|
||||||
# Return the allocated address
|
# Return the allocated address
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
|
|
||||||
def _calculate_alloc_size(typ: Union[type3types.StructType3, type3types.Type3], is_member: bool = False) -> int:
|
|
||||||
if typ == type3types.u8:
|
|
||||||
return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32
|
|
||||||
|
|
||||||
if typ in (type3types.u32, type3types.i32, type3types.f32, ):
|
|
||||||
return 4
|
|
||||||
|
|
||||||
if typ in (type3types.u64, type3types.i64, type3types.f64, ):
|
|
||||||
return 8
|
|
||||||
|
|
||||||
if isinstance(typ, type3types.StructType3):
|
|
||||||
if is_member:
|
|
||||||
# Structs referred to by other structs or tuples are pointers
|
|
||||||
return 4
|
|
||||||
|
|
||||||
return sum(
|
|
||||||
_calculate_alloc_size(x)
|
|
||||||
for x in typ.members.values()
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(typ, type3types.AppliedType3):
|
|
||||||
if typ.base is type3types.tuple:
|
|
||||||
if is_member:
|
|
||||||
# tuples referred to by other structs or tuples are pointers
|
|
||||||
return 4
|
|
||||||
|
|
||||||
size = 0
|
|
||||||
for arg in typ.args:
|
|
||||||
assert not isinstance(arg, type3types.IntType3)
|
|
||||||
|
|
||||||
if isinstance(arg, type3types.PlaceholderForType):
|
|
||||||
assert not arg.resolve_as is None
|
|
||||||
arg = arg.resolve_as
|
|
||||||
|
|
||||||
size += _calculate_alloc_size(arg, is_member=True)
|
|
||||||
|
|
||||||
return size
|
|
||||||
|
|
||||||
raise NotImplementedError(_calculate_alloc_size, typ)
|
|
||||||
|
|
||||||
def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
|
|
||||||
result = 0
|
|
||||||
|
|
||||||
for mem, memtyp in struct_type3.members.items():
|
|
||||||
if member == mem:
|
|
||||||
return result
|
|
||||||
|
|
||||||
result += _calculate_alloc_size(memtyp, is_member=True)
|
|
||||||
|
|
||||||
raise Exception(f'{member} not in {struct_type3}')
|
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
"""
|
"""
|
||||||
Contains the syntax tree for ourlang
|
Contains the syntax tree for ourlang
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Iterable, List, Optional, Union
|
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
|
from typing import Dict, Iterable, List, Optional, Union
|
||||||
|
|
||||||
from typing_extensions import Final
|
from typing_extensions import Final
|
||||||
|
|
||||||
|
from .type3 import types as type3types
|
||||||
|
from .type3.types import PlaceholderForType, StructType3, Type3, Type3OrPlaceholder
|
||||||
|
|
||||||
WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
|
WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
|
||||||
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
|
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
|
||||||
|
|
||||||
from .type3 import types as type3types
|
|
||||||
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType, StructType3
|
|
||||||
|
|
||||||
class Expression:
|
class Expression:
|
||||||
"""
|
"""
|
||||||
An expression within a statement
|
An expression within a statement
|
||||||
|
|||||||
@ -1,37 +1,39 @@
|
|||||||
"""
|
"""
|
||||||
Parses the source code from the plain text into a syntax tree
|
Parses the source code from the plain text into a syntax tree
|
||||||
"""
|
"""
|
||||||
from typing import Any, Dict, List, NoReturn, Union
|
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
from typing import Any, Dict, NoReturn, Union
|
||||||
from .type3 import types as type3types
|
|
||||||
|
|
||||||
from .exceptions import StaticError
|
from .exceptions import StaticError
|
||||||
from .ourlang import (
|
from .ourlang import (
|
||||||
WEBASSEMBLY_BUILTIN_FLOAT_OPS,
|
WEBASSEMBLY_BUILTIN_FLOAT_OPS,
|
||||||
|
AccessStructMember,
|
||||||
Module, ModuleDataBlock,
|
|
||||||
Function,
|
|
||||||
|
|
||||||
Expression,
|
|
||||||
BinaryOp,
|
BinaryOp,
|
||||||
ConstantPrimitive, ConstantMemoryStored,
|
ConstantBytes,
|
||||||
ConstantBytes, ConstantTuple, ConstantStruct,
|
ConstantPrimitive,
|
||||||
TupleInstantiation,
|
ConstantStruct,
|
||||||
|
ConstantTuple,
|
||||||
FunctionCall, AccessStructMember, Subscript,
|
Expression,
|
||||||
StructDefinition, StructConstructor,
|
|
||||||
UnaryOp, VariableReference,
|
|
||||||
|
|
||||||
Fold,
|
Fold,
|
||||||
|
Function,
|
||||||
Statement,
|
FunctionCall,
|
||||||
StatementIf, StatementPass, StatementReturn,
|
|
||||||
|
|
||||||
FunctionParam,
|
FunctionParam,
|
||||||
|
Module,
|
||||||
ModuleConstantDef,
|
ModuleConstantDef,
|
||||||
|
ModuleDataBlock,
|
||||||
|
Statement,
|
||||||
|
StatementIf,
|
||||||
|
StatementPass,
|
||||||
|
StatementReturn,
|
||||||
|
StructConstructor,
|
||||||
|
StructDefinition,
|
||||||
|
Subscript,
|
||||||
|
TupleInstantiation,
|
||||||
|
UnaryOp,
|
||||||
|
VariableReference,
|
||||||
)
|
)
|
||||||
|
from .type3 import types as type3types
|
||||||
|
|
||||||
|
|
||||||
def phasm_parse(source: str) -> Module:
|
def phasm_parse(source: str) -> Module:
|
||||||
"""
|
"""
|
||||||
@ -39,11 +41,39 @@ def phasm_parse(source: str) -> Module:
|
|||||||
"""
|
"""
|
||||||
res = ast.parse(source, '')
|
res = ast.parse(source, '')
|
||||||
|
|
||||||
|
res = OptimizerTransformer().visit(res)
|
||||||
|
|
||||||
our_visitor = OurVisitor()
|
our_visitor = OurVisitor()
|
||||||
return our_visitor.visit_Module(res)
|
return our_visitor.visit_Module(res)
|
||||||
|
|
||||||
OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict?
|
OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict?
|
||||||
|
|
||||||
|
class OptimizerTransformer(ast.NodeTransformer):
|
||||||
|
"""
|
||||||
|
This class optimizes the Python AST, to prepare it for parsing
|
||||||
|
by the OurVisitor class below.
|
||||||
|
"""
|
||||||
|
def visit_UnaryOp(self, node: ast.UnaryOp) -> Union[ast.UnaryOp, ast.Constant]:
|
||||||
|
"""
|
||||||
|
UnaryOp optimizations
|
||||||
|
|
||||||
|
In the given example:
|
||||||
|
```py
|
||||||
|
x = -4
|
||||||
|
```
|
||||||
|
Python will parse it as a unary minus operation on the constant four.
|
||||||
|
For Phasm purposes, this counts as a literal -4.
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
isinstance(node.op, (ast.UAdd, ast.USub, ))
|
||||||
|
and isinstance(node.operand, ast.Constant)
|
||||||
|
and isinstance(node.operand.value, (int, float, ))
|
||||||
|
):
|
||||||
|
if isinstance(node.op, ast.USub):
|
||||||
|
node.operand.value = -node.operand.value
|
||||||
|
return node.operand
|
||||||
|
return node
|
||||||
|
|
||||||
class OurVisitor:
|
class OurVisitor:
|
||||||
"""
|
"""
|
||||||
Class to visit a Python syntax tree and create an ourlang syntax tree
|
Class to visit a Python syntax tree and create an ourlang syntax tree
|
||||||
@ -52,6 +82,9 @@ class OurVisitor:
|
|||||||
|
|
||||||
At some point, we may deviate from Python syntax. If nothing else,
|
At some point, we may deviate from Python syntax. If nothing else,
|
||||||
we probably won't keep up with the Python syntax changes.
|
we probably won't keep up with the Python syntax changes.
|
||||||
|
|
||||||
|
See OptimizerTransformer for the changes we make after the Python
|
||||||
|
parsing is done but before the phasm parsing is done.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=C0103,C0116,C0301,R0201,R0912
|
# pylint: disable=C0103,C0116,C0301,R0201,R0912
|
||||||
@ -188,7 +221,7 @@ class OurVisitor:
|
|||||||
if not isinstance(stmt.target, ast.Name):
|
if not isinstance(stmt.target, ast.Name):
|
||||||
raise NotImplementedError('Class with default values')
|
raise NotImplementedError('Class with default values')
|
||||||
|
|
||||||
if not stmt.value is None:
|
if stmt.value is not None:
|
||||||
raise NotImplementedError('Class with default values')
|
raise NotImplementedError('Class with default values')
|
||||||
|
|
||||||
if stmt.simple != 1:
|
if stmt.simple != 1:
|
||||||
@ -400,7 +433,7 @@ class OurVisitor:
|
|||||||
arguments = [
|
arguments = [
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_node)
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_node)
|
||||||
for arg_node in node.elts
|
for arg_node in node.elts
|
||||||
if isinstance(arg_node, (ast.Constant, ast.Tuple, ))
|
if isinstance(arg_node, (ast.Constant, ast.Tuple, ast.Call, ))
|
||||||
]
|
]
|
||||||
|
|
||||||
if len(arguments) != len(node.elts):
|
if len(arguments) != len(node.elts):
|
||||||
@ -560,7 +593,7 @@ class OurVisitor:
|
|||||||
if not isinstance(node.func.ctx, ast.Load):
|
if not isinstance(node.func.ctx, ast.Load):
|
||||||
_raise_static_error(node.func, 'Must be load context')
|
_raise_static_error(node.func, 'Must be load context')
|
||||||
|
|
||||||
if not node.func.id in module.struct_definitions:
|
if node.func.id not in module.struct_definitions:
|
||||||
_raise_static_error(node.func, 'Undefined struct')
|
_raise_static_error(node.func, 'Undefined struct')
|
||||||
|
|
||||||
if node.keywords:
|
if node.keywords:
|
||||||
@ -613,8 +646,6 @@ class OurVisitor:
|
|||||||
_raise_static_error(node, f'Unrecognized type {node.id}')
|
_raise_static_error(node, f'Unrecognized type {node.id}')
|
||||||
|
|
||||||
if isinstance(node, ast.Subscript):
|
if isinstance(node, ast.Subscript):
|
||||||
if not isinstance(node.value, ast.Name):
|
|
||||||
_raise_static_error(node, 'Must be name')
|
|
||||||
if isinstance(node.slice, ast.Slice):
|
if isinstance(node.slice, ast.Slice):
|
||||||
_raise_static_error(node, 'Must subscript using an index')
|
_raise_static_error(node, 'Must subscript using an index')
|
||||||
if not isinstance(node.slice, ast.Constant):
|
if not isinstance(node.slice, ast.Constant):
|
||||||
@ -624,9 +655,6 @@ class OurVisitor:
|
|||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
_raise_static_error(node, 'Must be load context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
if node.value.id not in type3types.LOOKUP_TABLE: # FIXME: Tuple of tuples?
|
|
||||||
_raise_static_error(node, f'Unrecognized type {node.value.id}')
|
|
||||||
|
|
||||||
return type3types.AppliedType3(
|
return type3types.AppliedType3(
|
||||||
type3types.static_array,
|
type3types.static_array,
|
||||||
[self.visit_type(module, node.value), type3types.IntType3(node.slice.value)],
|
[self.visit_type(module, node.value), type3types.IntType3(node.slice.value)],
|
||||||
|
|||||||
69
phasm/runtime.py
Normal file
69
phasm/runtime.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from .type3 import types as type3types
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
|
||||||
|
if typ in (type3types.u8, type3types.i8, ):
|
||||||
|
return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32
|
||||||
|
|
||||||
|
if typ in (type3types.u32, type3types.i32, type3types.f32, ):
|
||||||
|
return 4
|
||||||
|
|
||||||
|
if typ in (type3types.u64, type3types.i64, type3types.f64, ):
|
||||||
|
return 8
|
||||||
|
|
||||||
|
if typ == type3types.bytes:
|
||||||
|
if is_member:
|
||||||
|
return 4
|
||||||
|
|
||||||
|
raise NotImplementedError # When does this happen?
|
||||||
|
|
||||||
|
if isinstance(typ, type3types.StructType3):
|
||||||
|
if is_member:
|
||||||
|
# Structs referred to by other structs or tuples are pointers
|
||||||
|
return 4
|
||||||
|
|
||||||
|
return sum(
|
||||||
|
calculate_alloc_size(x, is_member=True)
|
||||||
|
for x in typ.members.values()
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(typ, type3types.AppliedType3):
|
||||||
|
if typ.base is type3types.static_array:
|
||||||
|
if is_member:
|
||||||
|
# tuples referred to by other structs or tuples are pointers
|
||||||
|
return 4
|
||||||
|
|
||||||
|
assert isinstance(typ.args[0], type3types.Type3)
|
||||||
|
assert isinstance(typ.args[1], type3types.IntType3)
|
||||||
|
|
||||||
|
return typ.args[1].value * calculate_alloc_size(typ.args[0], is_member=True)
|
||||||
|
|
||||||
|
if typ.base is type3types.tuple:
|
||||||
|
if is_member:
|
||||||
|
# tuples referred to by other structs or tuples are pointers
|
||||||
|
return 4
|
||||||
|
|
||||||
|
size = 0
|
||||||
|
for arg in typ.args:
|
||||||
|
assert not isinstance(arg, type3types.IntType3)
|
||||||
|
|
||||||
|
if isinstance(arg, type3types.PlaceholderForType):
|
||||||
|
assert arg.resolve_as is not None
|
||||||
|
arg = arg.resolve_as
|
||||||
|
|
||||||
|
size += calculate_alloc_size(arg, is_member=True)
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
|
raise NotImplementedError(calculate_alloc_size, typ)
|
||||||
|
|
||||||
|
def calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
|
||||||
|
result = 0
|
||||||
|
|
||||||
|
for mem, memtyp in struct_type3.members.items():
|
||||||
|
if member == mem:
|
||||||
|
return result
|
||||||
|
|
||||||
|
result += calculate_alloc_size(memtyp, is_member=True)
|
||||||
|
|
||||||
|
raise Exception(f'{member} not in {struct_type3}')
|
||||||
@ -1,7 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
stdlib: Memory allocation
|
stdlib: Memory allocation
|
||||||
"""
|
"""
|
||||||
from phasm.wasmgenerator import Generator, VarType_i32 as i32, func_wrapper
|
from phasm.wasmgenerator import Generator, func_wrapper
|
||||||
|
from phasm.wasmgenerator import VarType_i32 as i32
|
||||||
|
|
||||||
IDENTIFIER = 0xA1C0
|
IDENTIFIER = 0xA1C0
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
stdlib: Standard types that are not wasm primitives
|
stdlib: Standard types that are not wasm primitives
|
||||||
"""
|
"""
|
||||||
from phasm.wasmgenerator import Generator, VarType_i32 as i32, func_wrapper
|
|
||||||
|
|
||||||
from phasm.stdlib import alloc
|
from phasm.stdlib import alloc
|
||||||
|
from phasm.wasmgenerator import Generator, func_wrapper
|
||||||
|
from phasm.wasmgenerator import VarType_i32 as i32
|
||||||
|
|
||||||
|
|
||||||
@func_wrapper()
|
@func_wrapper()
|
||||||
def __alloc_bytes__(g: Generator, length: i32) -> i32:
|
def __alloc_bytes__(g: Generator, length: i32) -> i32:
|
||||||
|
|||||||
@ -3,12 +3,12 @@ This module contains possible constraints generated based on the AST
|
|||||||
|
|
||||||
These need to be resolved before the program can be compiled.
|
These need to be resolved before the program can be compiled.
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Optional, List, Tuple, Union
|
from typing import Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from .. import ourlang
|
from .. import ourlang
|
||||||
|
|
||||||
from . import types
|
from . import types
|
||||||
|
|
||||||
|
|
||||||
class Error:
|
class Error:
|
||||||
"""
|
"""
|
||||||
An error returned by the check functions for a contraint
|
An error returned by the check functions for a contraint
|
||||||
@ -103,7 +103,6 @@ class SameTypeConstraint(ConstraintBase):
|
|||||||
def check(self) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
known_types: List[types.Type3] = []
|
known_types: List[types.Type3] = []
|
||||||
placeholders = []
|
placeholders = []
|
||||||
do_applied_placeholder_check: bool = False
|
|
||||||
for typ in self.type_list:
|
for typ in self.type_list:
|
||||||
if isinstance(typ, types.IntType3):
|
if isinstance(typ, types.IntType3):
|
||||||
known_types.append(typ)
|
known_types.append(typ)
|
||||||
@ -115,7 +114,6 @@ class SameTypeConstraint(ConstraintBase):
|
|||||||
|
|
||||||
if isinstance(typ, types.AppliedType3):
|
if isinstance(typ, types.AppliedType3):
|
||||||
known_types.append(typ)
|
known_types.append(typ)
|
||||||
do_applied_placeholder_check = True
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(typ, types.PlaceholderForType):
|
if isinstance(typ, types.PlaceholderForType):
|
||||||
@ -135,12 +133,30 @@ class SameTypeConstraint(ConstraintBase):
|
|||||||
first_type = known_types[0]
|
first_type = known_types[0]
|
||||||
for typ in known_types[1:]:
|
for typ in known_types[1:]:
|
||||||
if isinstance(first_type, types.AppliedType3) and isinstance(typ, types.AppliedType3):
|
if isinstance(first_type, types.AppliedType3) and isinstance(typ, types.AppliedType3):
|
||||||
if len(first_type.args) != len(typ.args):
|
if first_type.base is types.tuple and typ.base is types.static_array:
|
||||||
|
# Swap so we can reuse the code below
|
||||||
|
# Hope that it still gives proper type errors
|
||||||
|
first_type, typ = typ, first_type
|
||||||
|
|
||||||
|
if first_type.base is types.static_array and typ.base is types.tuple:
|
||||||
|
assert isinstance(first_type.args[1], types.IntType3)
|
||||||
|
length = first_type.args[1].value
|
||||||
|
|
||||||
|
if len(typ.args) != length:
|
||||||
return Error('Mismatch between applied types argument count', comment=self.comment)
|
return Error('Mismatch between applied types argument count', comment=self.comment)
|
||||||
|
|
||||||
|
for typ_arg in typ.args:
|
||||||
|
new_constraint_list.append(SameTypeConstraint(
|
||||||
|
first_type.args[0], typ_arg
|
||||||
|
))
|
||||||
|
continue
|
||||||
|
|
||||||
if first_type.base != typ.base:
|
if first_type.base != typ.base:
|
||||||
return Error('Mismatch between applied types base', comment=self.comment)
|
return Error('Mismatch between applied types base', comment=self.comment)
|
||||||
|
|
||||||
|
if len(first_type.args) != len(typ.args):
|
||||||
|
return Error('Mismatch between applied types argument count', comment=self.comment)
|
||||||
|
|
||||||
for first_type_arg, typ_arg in zip(first_type.args, typ.args):
|
for first_type_arg, typ_arg in zip(first_type.args, typ.args):
|
||||||
new_constraint_list.append(SameTypeConstraint(
|
new_constraint_list.append(SameTypeConstraint(
|
||||||
first_type_arg, typ_arg
|
first_type_arg, typ_arg
|
||||||
@ -365,11 +381,11 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
try:
|
try:
|
||||||
self.literal.value.to_bytes(bts, 'big', signed=sgn)
|
self.literal.value.to_bytes(bts, 'big', signed=sgn)
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information
|
return Error(f'Must fit in {bts} byte(s)', comment=self.comment) # FIXME: Add line information
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return Error('Must be integer') # FIXME: Add line information
|
return Error('Must be integer', comment=self.comment) # FIXME: Add line information
|
||||||
|
|
||||||
if self.type3.name in float_table:
|
if self.type3.name in float_table:
|
||||||
_ = float_table[self.type3.name]
|
_ = float_table[self.type3.name]
|
||||||
@ -379,23 +395,23 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return Error('Must be real') # FIXME: Add line information
|
return Error('Must be real', comment=self.comment) # FIXME: Add line information
|
||||||
|
|
||||||
if self.type3 is types.bytes:
|
if self.type3 is types.bytes:
|
||||||
if isinstance(self.literal.value, bytes):
|
if isinstance(self.literal.value, bytes):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return Error('Must be bytes') # FIXME: Add line information
|
return Error('Must be bytes', comment=self.comment) # FIXME: Add line information
|
||||||
|
|
||||||
res: NewConstraintList
|
res: NewConstraintList
|
||||||
|
|
||||||
if isinstance(self.type3, types.AppliedType3):
|
if isinstance(self.type3, types.AppliedType3):
|
||||||
if self.type3.base == types.tuple:
|
if self.type3.base == types.tuple:
|
||||||
if not isinstance(self.literal, ourlang.ConstantTuple):
|
if not isinstance(self.literal, ourlang.ConstantTuple):
|
||||||
return Error('Must be tuple')
|
return Error('Must be tuple', comment=self.comment)
|
||||||
|
|
||||||
if len(self.type3.args) != len(self.literal.value):
|
if len(self.type3.args) != len(self.literal.value):
|
||||||
return Error('Tuple element count mismatch')
|
return Error('Tuple element count mismatch', comment=self.comment)
|
||||||
|
|
||||||
res = []
|
res = []
|
||||||
|
|
||||||
@ -412,13 +428,13 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
|
|
||||||
if self.type3.base == types.static_array:
|
if self.type3.base == types.static_array:
|
||||||
if not isinstance(self.literal, ourlang.ConstantTuple):
|
if not isinstance(self.literal, ourlang.ConstantTuple):
|
||||||
return Error('Must be tuple')
|
return Error('Must be tuple', comment=self.comment)
|
||||||
|
|
||||||
assert 2 == len(self.type3.args)
|
assert 2 == len(self.type3.args)
|
||||||
assert isinstance(self.type3.args[1], types.IntType3)
|
assert isinstance(self.type3.args[1], types.IntType3)
|
||||||
|
|
||||||
if self.type3.args[1].value != len(self.literal.value):
|
if self.type3.args[1].value != len(self.literal.value):
|
||||||
return Error('Member count mismatch')
|
return Error('Member count mismatch', comment=self.comment)
|
||||||
|
|
||||||
res = []
|
res = []
|
||||||
|
|
||||||
|
|||||||
@ -6,16 +6,16 @@ The constraints solver can then try to resolve all constraints.
|
|||||||
from typing import Generator, List
|
from typing import Generator, List
|
||||||
|
|
||||||
from .. import ourlang
|
from .. import ourlang
|
||||||
|
|
||||||
from .constraints import (
|
|
||||||
Context,
|
|
||||||
|
|
||||||
ConstraintBase,
|
|
||||||
CastableConstraint, CanBeSubscriptedConstraint,
|
|
||||||
LiteralFitsConstraint, MustImplementTypeClassConstraint, SameTypeConstraint,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import types as type3types
|
from . import types as type3types
|
||||||
|
from .constraints import (
|
||||||
|
CanBeSubscriptedConstraint,
|
||||||
|
CastableConstraint,
|
||||||
|
ConstraintBase,
|
||||||
|
Context,
|
||||||
|
LiteralFitsConstraint,
|
||||||
|
MustImplementTypeClassConstraint,
|
||||||
|
SameTypeConstraint,
|
||||||
|
)
|
||||||
|
|
||||||
ConstraintGenerator = Generator[ConstraintBase, None, None]
|
ConstraintGenerator = Generator[ConstraintBase, None, None]
|
||||||
|
|
||||||
@ -26,7 +26,10 @@ def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase
|
|||||||
|
|
||||||
def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator:
|
def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator:
|
||||||
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
|
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
|
||||||
yield LiteralFitsConstraint(inp.type3, inp)
|
yield LiteralFitsConstraint(
|
||||||
|
inp.type3, inp,
|
||||||
|
comment='The given literal must fit the expected type'
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
raise NotImplementedError(constant, inp)
|
raise NotImplementedError(constant, inp)
|
||||||
@ -135,7 +138,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
|
|||||||
yield SameTypeConstraint(
|
yield SameTypeConstraint(
|
||||||
inp.type3,
|
inp.type3,
|
||||||
type3types.AppliedType3(type3types.tuple, r_type),
|
type3types.AppliedType3(type3types.tuple, r_type),
|
||||||
comment=f'The type of a tuple is a combination of its members'
|
comment='The type of a tuple is a combination of its members'
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -175,7 +178,7 @@ def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf)
|
|||||||
yield from expression(ctx, inp.test)
|
yield from expression(ctx, inp.test)
|
||||||
|
|
||||||
yield SameTypeConstraint(inp.test.type3, type3types.bool_,
|
yield SameTypeConstraint(inp.test.type3, type3types.bool_,
|
||||||
comment=f'Must pass a boolean expression to if')
|
comment='Must pass a boolean expression to if')
|
||||||
|
|
||||||
for stmt in inp.statements:
|
for stmt in inp.statements:
|
||||||
yield from statement(ctx, fun, stmt)
|
yield from statement(ctx, fun, stmt)
|
||||||
|
|||||||
@ -1,14 +1,26 @@
|
|||||||
"""
|
"""
|
||||||
Entry point to the type3 system
|
Entry point to the type3 system
|
||||||
"""
|
"""
|
||||||
from typing import Any, Dict, List, Set
|
from typing import Dict, List
|
||||||
|
|
||||||
from .. import codestyle
|
from .. import codestyle, ourlang
|
||||||
from .. import ourlang
|
from .constraints import (
|
||||||
|
ConstraintBase,
|
||||||
from .constraints import ConstraintBase, Error, RequireTypeSubstitutes, SameTypeConstraint, SubstitutionMap
|
Error,
|
||||||
|
RequireTypeSubstitutes,
|
||||||
|
SameTypeConstraint,
|
||||||
|
SubstitutionMap,
|
||||||
|
)
|
||||||
from .constraintsgenerator import phasm_type3_generate_constraints
|
from .constraintsgenerator import phasm_type3_generate_constraints
|
||||||
from .types import AppliedType3, IntType3, PlaceholderForType, PrimitiveType3, StructType3, Type3, Type3OrPlaceholder
|
from .types import (
|
||||||
|
AppliedType3,
|
||||||
|
IntType3,
|
||||||
|
PlaceholderForType,
|
||||||
|
PrimitiveType3,
|
||||||
|
StructType3,
|
||||||
|
Type3,
|
||||||
|
Type3OrPlaceholder,
|
||||||
|
)
|
||||||
|
|
||||||
MAX_RESTACK_COUNT = 100
|
MAX_RESTACK_COUNT = 100
|
||||||
|
|
||||||
@ -33,6 +45,8 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
|
|||||||
old_constraint_ids = {id(x) for x in constraint_list}
|
old_constraint_ids = {id(x) for x in constraint_list}
|
||||||
old_placeholder_substitutes_len = len(placeholder_substitutes)
|
old_placeholder_substitutes_len = len(placeholder_substitutes)
|
||||||
|
|
||||||
|
back_on_todo_list_count = 0
|
||||||
|
|
||||||
new_constraint_list = []
|
new_constraint_list = []
|
||||||
for constraint in constraint_list:
|
for constraint in constraint_list:
|
||||||
check_result = constraint.check()
|
check_result = constraint.check()
|
||||||
@ -60,9 +74,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
|
|||||||
if isinstance(check_result, RequireTypeSubstitutes):
|
if isinstance(check_result, RequireTypeSubstitutes):
|
||||||
new_constraint_list.append(constraint)
|
new_constraint_list.append(constraint)
|
||||||
|
|
||||||
if verbose:
|
back_on_todo_list_count += 1
|
||||||
print_constraint(placeholder_id_map, constraint)
|
|
||||||
print('-> Back on the todo list')
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(check_result, list):
|
if isinstance(check_result, list):
|
||||||
@ -75,6 +87,9 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
|
|||||||
|
|
||||||
raise NotImplementedError(constraint, check_result)
|
raise NotImplementedError(constraint, check_result)
|
||||||
|
|
||||||
|
if verbose and 0 < back_on_todo_list_count:
|
||||||
|
print(f'{back_on_todo_list_count} constraints skipped for now')
|
||||||
|
|
||||||
if not new_constraint_list:
|
if not new_constraint_list:
|
||||||
constraint_list = new_constraint_list
|
constraint_list = new_constraint_list
|
||||||
break
|
break
|
||||||
@ -91,6 +106,10 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
|
|||||||
|
|
||||||
constraint_list = new_constraint_list
|
constraint_list = new_constraint_list
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print()
|
||||||
|
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
|
||||||
|
|
||||||
if constraint_list:
|
if constraint_list:
|
||||||
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')
|
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ and being able to conver it to Web Assembly Text Format
|
|||||||
|
|
||||||
from typing import Iterable, List, Optional, Tuple
|
from typing import Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
class WatSerializable:
|
class WatSerializable:
|
||||||
"""
|
"""
|
||||||
Mixin for clases that can be serialized as WebAssembly Text
|
Mixin for clases that can be serialized as WebAssembly Text
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Helper functions to generate WASM code by writing Python functions
|
Helper functions to generate WASM code by writing Python functions
|
||||||
"""
|
"""
|
||||||
from typing import Any, Callable, Dict, List, Optional, Type
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Type
|
||||||
|
|
||||||
from . import wasm
|
from . import wasm
|
||||||
|
|
||||||
@ -45,7 +44,7 @@ class Generator_i32i64:
|
|||||||
self.store = functools.partial(self.generator.add_statement, f'{prefix}.store')
|
self.store = functools.partial(self.generator.add_statement, f'{prefix}.store')
|
||||||
|
|
||||||
def const(self, value: int, comment: Optional[str] = None) -> None:
|
def const(self, value: int, comment: Optional[str] = None) -> None:
|
||||||
self.generator.add_statement(f'{self.prefix}.const', f'0x{value:08x}', comment=comment)
|
self.generator.add_statement(f'{self.prefix}.const', f'{value}', comment=comment)
|
||||||
|
|
||||||
class Generator_i32(Generator_i32i64):
|
class Generator_i32(Generator_i32i64):
|
||||||
def __init__(self, generator: 'Generator') -> None:
|
def __init__(self, generator: 'Generator') -> None:
|
||||||
|
|||||||
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["F", "E", "W", "I"]
|
||||||
|
ignore = ["E501"]
|
||||||
@ -1,10 +1,10 @@
|
|||||||
mypy==0.991
|
mypy==0.991
|
||||||
pygments==2.12.0
|
pygments==2.12.0
|
||||||
pylint==2.15.9
|
|
||||||
pytest==7.2.0
|
pytest==7.2.0
|
||||||
pytest-integration==0.2.2
|
pytest-integration==0.2.2
|
||||||
pywasm==1.0.7
|
pywasm==1.0.7
|
||||||
pywasm3==0.5.0
|
pywasm3==0.5.0
|
||||||
|
ruff==0.1.5
|
||||||
wasmer==1.1.0
|
wasmer==1.1.0
|
||||||
wasmer_compiler_cranelift==1.1.0
|
wasmer_compiler_cranelift==1.1.0
|
||||||
wasmtime==3.0.0
|
wasmtime==3.0.0
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
Constants for use in the tests
|
|
||||||
"""
|
|
||||||
|
|
||||||
ALL_INT_TYPES = ['u8', 'u32', 'u64', 'i32', 'i64']
|
|
||||||
COMPLETE_INT_TYPES = ['u32', 'u64', 'i32', 'i64']
|
|
||||||
|
|
||||||
ALL_FLOAT_TYPES = ['f32', 'f64']
|
|
||||||
COMPLETE_FLOAT_TYPES = ALL_FLOAT_TYPES
|
|
||||||
|
|
||||||
TYPE_MAP = {
|
|
||||||
**{x: int for x in ALL_INT_TYPES},
|
|
||||||
**{x: float for x in ALL_FLOAT_TYPES},
|
|
||||||
}
|
|
||||||
|
|
||||||
COMPLETE_NUMERIC_TYPES = COMPLETE_INT_TYPES + COMPLETE_FLOAT_TYPES
|
|
||||||
@ -1,13 +1,18 @@
|
|||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Any, Generator, Iterable, List, TextIO, Union
|
||||||
|
|
||||||
|
from phasm import compiler
|
||||||
from phasm.codestyle import phasm_render
|
from phasm.codestyle import phasm_render
|
||||||
|
from phasm.runtime import calculate_alloc_size
|
||||||
|
from phasm.type3 import types as type3types
|
||||||
|
|
||||||
from . import runners
|
from . import runners
|
||||||
|
|
||||||
DASHES = '-' * 16
|
DASHES = '-' * 16
|
||||||
|
|
||||||
class SuiteResult:
|
class SuiteResult:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.returned_value = None
|
self.returned_value = None
|
||||||
|
|
||||||
RUNNER_CLASS_MAP = {
|
RUNNER_CLASS_MAP = {
|
||||||
@ -21,10 +26,10 @@ class Suite:
|
|||||||
"""
|
"""
|
||||||
WebAssembly test suite
|
WebAssembly test suite
|
||||||
"""
|
"""
|
||||||
def __init__(self, code_py):
|
def __init__(self, code_py: str) -> None:
|
||||||
self.code_py = code_py
|
self.code_py = code_py
|
||||||
|
|
||||||
def run_code(self, *args, runtime='pywasm3', func_name='testEntry', imports=None):
|
def run_code(self, *args: Any, runtime: str = 'pywasm3', func_name: str = 'testEntry', imports: runners.Imports = None) -> Any:
|
||||||
"""
|
"""
|
||||||
Compiles the given python code into wasm and
|
Compiles the given python code into wasm and
|
||||||
then runs it
|
then runs it
|
||||||
@ -35,36 +40,69 @@ class Suite:
|
|||||||
|
|
||||||
runner = class_(self.code_py)
|
runner = class_(self.code_py)
|
||||||
|
|
||||||
|
write_header(sys.stderr, 'Phasm')
|
||||||
|
runner.dump_phasm_code(sys.stderr)
|
||||||
|
|
||||||
runner.parse()
|
runner.parse()
|
||||||
runner.compile_ast()
|
runner.compile_ast()
|
||||||
runner.compile_wat()
|
runner.compile_wat()
|
||||||
|
|
||||||
|
write_header(sys.stderr, 'Assembly')
|
||||||
|
runner.dump_wasm_wat(sys.stderr)
|
||||||
|
|
||||||
runner.compile_wasm()
|
runner.compile_wasm()
|
||||||
runner.interpreter_setup()
|
runner.interpreter_setup()
|
||||||
runner.interpreter_load(imports)
|
runner.interpreter_load(imports)
|
||||||
|
|
||||||
write_header(sys.stderr, 'Phasm')
|
|
||||||
runner.dump_phasm_code(sys.stderr)
|
|
||||||
write_header(sys.stderr, 'Assembly')
|
|
||||||
runner.dump_wasm_wat(sys.stderr)
|
|
||||||
|
|
||||||
# Check if code formatting works
|
# Check if code formatting works
|
||||||
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
||||||
|
|
||||||
wasm_args = []
|
func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs]
|
||||||
|
if len(func_args) != len(args):
|
||||||
|
raise RuntimeError(f'Invalid number of args for {func_name}')
|
||||||
|
|
||||||
|
wasm_args: List[Union[float, int]] = []
|
||||||
if args:
|
if args:
|
||||||
write_header(sys.stderr, 'Memory (pre alloc)')
|
write_header(sys.stderr, 'Memory (pre alloc)')
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
runner.interpreter_dump_memory(sys.stderr)
|
||||||
|
|
||||||
for arg in args:
|
for arg, arg_typ in zip(args, func_args):
|
||||||
if isinstance(arg, (int, float, )):
|
assert not isinstance(arg_typ, type3types.PlaceholderForType), \
|
||||||
|
'Cannot call polymorphic function from outside'
|
||||||
|
|
||||||
|
if arg_typ in (type3types.u8, type3types.u32, type3types.u64, ):
|
||||||
|
assert isinstance(arg, int)
|
||||||
wasm_args.append(arg)
|
wasm_args.append(arg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(arg, bytes):
|
if arg_typ in (type3types.i8, type3types.i32, type3types.i64, ):
|
||||||
adr = runner.call('stdlib.types.__alloc_bytes__', len(arg))
|
assert isinstance(arg, int)
|
||||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(arg)}\n')
|
wasm_args.append(arg)
|
||||||
|
continue
|
||||||
|
|
||||||
runner.interpreter_write_memory(adr + 4, arg)
|
if arg_typ in (type3types.f32, type3types.f64, ):
|
||||||
|
assert isinstance(arg, float)
|
||||||
|
wasm_args.append(arg)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if arg_typ is type3types.bytes:
|
||||||
|
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||||
|
wasm_args.append(adr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(arg_typ, type3types.AppliedType3):
|
||||||
|
if arg_typ.base is type3types.static_array:
|
||||||
|
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||||
|
wasm_args.append(adr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if arg_typ.base is type3types.tuple:
|
||||||
|
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||||
|
wasm_args.append(adr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(arg_typ, type3types.StructType3):
|
||||||
|
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||||
wasm_args.append(adr)
|
wasm_args.append(adr)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -76,10 +114,334 @@ class Suite:
|
|||||||
result = SuiteResult()
|
result = SuiteResult()
|
||||||
result.returned_value = runner.call(func_name, *wasm_args)
|
result.returned_value = runner.call(func_name, *wasm_args)
|
||||||
|
|
||||||
|
result.returned_value = _load_memory_stored_returned_value(
|
||||||
|
runner,
|
||||||
|
func_name,
|
||||||
|
result.returned_value,
|
||||||
|
)
|
||||||
|
|
||||||
write_header(sys.stderr, 'Memory (post run)')
|
write_header(sys.stderr, 'Memory (post run)')
|
||||||
runner.interpreter_dump_memory(sys.stderr)
|
runner.interpreter_dump_memory(sys.stderr)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def write_header(textio, msg: str) -> None:
|
def write_header(textio: TextIO, msg: str) -> None:
|
||||||
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
|
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
|
||||||
|
|
||||||
|
WRITE_LOOKUP_MAP = {
|
||||||
|
'u8': compiler.module_data_u8,
|
||||||
|
'u32': compiler.module_data_u32,
|
||||||
|
'u64': compiler.module_data_u64,
|
||||||
|
'i8': compiler.module_data_i8,
|
||||||
|
'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,
|
||||||
|
val_typ: type3types.Type3,
|
||||||
|
val: Any,
|
||||||
|
) -> int:
|
||||||
|
if val_typ is type3types.bytes:
|
||||||
|
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
|
||||||
|
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||||
|
return 4
|
||||||
|
|
||||||
|
if isinstance(val_typ, type3types.PrimitiveType3):
|
||||||
|
to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
|
||||||
|
runner.interpreter_write_memory(adr, to_write)
|
||||||
|
return len(to_write)
|
||||||
|
|
||||||
|
if isinstance(val_typ, type3types.AppliedType3):
|
||||||
|
if val_typ.base in (type3types.static_array, type3types.tuple, ):
|
||||||
|
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
|
||||||
|
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||||
|
return 4
|
||||||
|
|
||||||
|
if isinstance(val_typ, type3types.StructType3):
|
||||||
|
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
|
||||||
|
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||||
|
return 4
|
||||||
|
|
||||||
|
raise NotImplementedError(val_typ, val)
|
||||||
|
|
||||||
|
def _allocate_memory_stored_value(
|
||||||
|
runner: runners.RunnerBase,
|
||||||
|
val_typ: type3types.Type3,
|
||||||
|
val: Any
|
||||||
|
) -> int:
|
||||||
|
if val_typ is type3types.bytes:
|
||||||
|
assert isinstance(val, bytes)
|
||||||
|
|
||||||
|
adr = runner.call('stdlib.types.__alloc_bytes__', len(val))
|
||||||
|
assert isinstance(adr, int)
|
||||||
|
|
||||||
|
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||||
|
runner.interpreter_write_memory(adr + 4, val)
|
||||||
|
return adr
|
||||||
|
|
||||||
|
if isinstance(val_typ, type3types.AppliedType3):
|
||||||
|
if val_typ.base is type3types.static_array:
|
||||||
|
assert isinstance(val, tuple)
|
||||||
|
|
||||||
|
alloc_size = calculate_alloc_size(val_typ)
|
||||||
|
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||||
|
assert isinstance(adr, int)
|
||||||
|
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||||
|
|
||||||
|
val_el_typ = val_typ.args[0]
|
||||||
|
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
|
||||||
|
|
||||||
|
tuple_len_obj = val_typ.args[1]
|
||||||
|
assert isinstance(tuple_len_obj, type3types.IntType3)
|
||||||
|
tuple_len = tuple_len_obj.value
|
||||||
|
assert tuple_len == len(val)
|
||||||
|
|
||||||
|
offset = adr
|
||||||
|
for val_el_val in val:
|
||||||
|
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||||
|
return adr
|
||||||
|
|
||||||
|
if val_typ.base is type3types.tuple:
|
||||||
|
assert isinstance(val, tuple)
|
||||||
|
|
||||||
|
alloc_size = calculate_alloc_size(val_typ)
|
||||||
|
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||||
|
assert isinstance(adr, int)
|
||||||
|
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||||
|
|
||||||
|
assert len(val) == len(val_typ.args)
|
||||||
|
|
||||||
|
offset = adr
|
||||||
|
for val_el_val, val_el_typ in zip(val, val_typ.args):
|
||||||
|
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
|
||||||
|
|
||||||
|
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||||
|
return adr
|
||||||
|
|
||||||
|
if isinstance(val_typ, type3types.StructType3):
|
||||||
|
assert isinstance(val, dict)
|
||||||
|
|
||||||
|
alloc_size = calculate_alloc_size(val_typ)
|
||||||
|
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||||
|
assert isinstance(adr, int)
|
||||||
|
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||||
|
|
||||||
|
assert list(val.keys()) == list(val_typ.members.keys())
|
||||||
|
|
||||||
|
offset = adr
|
||||||
|
for val_el_name, val_el_typ in val_typ.members.items():
|
||||||
|
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
|
||||||
|
|
||||||
|
val_el_val = val[val_el_name]
|
||||||
|
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||||
|
return adr
|
||||||
|
|
||||||
|
raise NotImplementedError(val_typ, val)
|
||||||
|
|
||||||
|
def _load_memory_stored_returned_value(
|
||||||
|
runner: runners.RunnerBase,
|
||||||
|
func_name: str,
|
||||||
|
wasm_value: Any,
|
||||||
|
) -> Any:
|
||||||
|
ret_type3 = runner.phasm_ast.functions[func_name].returns_type3
|
||||||
|
|
||||||
|
if ret_type3 is type3types.none:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if ret_type3 in (type3types.i32, type3types.i64):
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
return wasm_value
|
||||||
|
|
||||||
|
if ret_type3 in (type3types.u8, type3types.u32, type3types.u64):
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
|
||||||
|
if wasm_value < 0:
|
||||||
|
# WASM does not support unsigned values through its interface
|
||||||
|
# Cast and then reinterpret
|
||||||
|
|
||||||
|
letter = {
|
||||||
|
'u32': 'i',
|
||||||
|
'u64': 'q',
|
||||||
|
}[ret_type3.name]
|
||||||
|
|
||||||
|
data = struct.pack(f'<{letter}', wasm_value)
|
||||||
|
wasm_value, = struct.unpack(f'<{letter.upper()}', data)
|
||||||
|
|
||||||
|
return wasm_value
|
||||||
|
|
||||||
|
if ret_type3 in (type3types.f32, type3types.f64, ):
|
||||||
|
assert isinstance(wasm_value, float), wasm_value
|
||||||
|
return wasm_value
|
||||||
|
|
||||||
|
if ret_type3 is type3types.bytes:
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
|
||||||
|
return _load_bytes_from_address(runner, ret_type3, wasm_value)
|
||||||
|
|
||||||
|
if isinstance(ret_type3, type3types.AppliedType3):
|
||||||
|
if ret_type3.base is type3types.static_array:
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
|
||||||
|
return _load_static_array_from_address(runner, ret_type3, wasm_value)
|
||||||
|
|
||||||
|
if ret_type3.base is type3types.tuple:
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
|
||||||
|
return _load_tuple_from_address(runner, ret_type3, wasm_value)
|
||||||
|
|
||||||
|
if isinstance(ret_type3, type3types.StructType3):
|
||||||
|
return _load_struct_from_address(runner, ret_type3, wasm_value)
|
||||||
|
|
||||||
|
raise NotImplementedError(ret_type3, wasm_value)
|
||||||
|
|
||||||
|
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
|
||||||
|
if typ is type3types.u8:
|
||||||
|
# See compiler.py, LOAD_STORE_TYPE_MAP and module_data_u8
|
||||||
|
assert len(inp) == 4
|
||||||
|
return struct.unpack('<I', inp)[0]
|
||||||
|
|
||||||
|
if typ is type3types.u32:
|
||||||
|
assert len(inp) == 4
|
||||||
|
return struct.unpack('<I', inp)[0]
|
||||||
|
|
||||||
|
if typ is type3types.u64:
|
||||||
|
assert len(inp) == 8
|
||||||
|
return struct.unpack('<Q', inp)[0]
|
||||||
|
|
||||||
|
if typ is type3types.i8:
|
||||||
|
# See compiler.py, LOAD_STORE_TYPE_MAP and module_data_i8
|
||||||
|
assert len(inp) == 4
|
||||||
|
return struct.unpack('<i', inp)[0]
|
||||||
|
|
||||||
|
if typ is type3types.i32:
|
||||||
|
assert len(inp) == 4
|
||||||
|
return struct.unpack('<i', inp)[0]
|
||||||
|
|
||||||
|
if typ is type3types.i64:
|
||||||
|
assert len(inp) == 8
|
||||||
|
return struct.unpack('<q', inp)[0]
|
||||||
|
|
||||||
|
if typ is type3types.f32:
|
||||||
|
assert len(inp) == 4
|
||||||
|
return struct.unpack('<f', inp)[0]
|
||||||
|
|
||||||
|
if typ is type3types.f64:
|
||||||
|
assert len(inp) == 8
|
||||||
|
return struct.unpack('<d', inp)[0]
|
||||||
|
|
||||||
|
if typ is type3types.bytes:
|
||||||
|
# Note: For bytes, inp should contain a 4 byte pointer
|
||||||
|
assert len(inp) == 4
|
||||||
|
adr = struct.unpack('<I', inp)[0]
|
||||||
|
|
||||||
|
return _load_bytes_from_address(runner, typ, adr)
|
||||||
|
|
||||||
|
if isinstance(typ, type3types.AppliedType3):
|
||||||
|
# Note: For applied types, inp should contain a 4 byte pointer
|
||||||
|
assert len(inp) == 4
|
||||||
|
adr = struct.unpack('<I', inp)[0]
|
||||||
|
|
||||||
|
if typ.base is type3types.static_array:
|
||||||
|
return _load_static_array_from_address(runner, typ, adr)
|
||||||
|
|
||||||
|
if typ.base is type3types.tuple:
|
||||||
|
return _load_tuple_from_address(runner, typ, adr)
|
||||||
|
|
||||||
|
if isinstance(typ, type3types.StructType3):
|
||||||
|
# Note: For structs, inp should contain a 4 byte pointer
|
||||||
|
assert len(inp) == 4
|
||||||
|
adr = struct.unpack('<I', inp)[0]
|
||||||
|
|
||||||
|
return _load_struct_from_address(runner, typ, adr)
|
||||||
|
|
||||||
|
raise NotImplementedError(typ, inp)
|
||||||
|
|
||||||
|
def _load_bytes_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> bytes:
|
||||||
|
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
|
||||||
|
read_bytes = runner.interpreter_read_memory(adr, 4)
|
||||||
|
bytes_len, = struct.unpack('<I', read_bytes)
|
||||||
|
|
||||||
|
adr += 4
|
||||||
|
return runner.interpreter_read_memory(adr, bytes_len)
|
||||||
|
|
||||||
|
def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator[bytes, None, None]:
|
||||||
|
offset = 0
|
||||||
|
for size in split_sizes:
|
||||||
|
yield all_bytes[offset:offset + size]
|
||||||
|
offset += size
|
||||||
|
|
||||||
|
def _load_static_array_from_address(runner: runners.RunnerBase, typ: type3types.AppliedType3, adr: int) -> Any:
|
||||||
|
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
|
||||||
|
|
||||||
|
assert 2 == len(typ.args)
|
||||||
|
sub_typ, len_typ = typ.args
|
||||||
|
|
||||||
|
assert not isinstance(sub_typ, type3types.PlaceholderForType)
|
||||||
|
assert isinstance(len_typ, type3types.IntType3)
|
||||||
|
|
||||||
|
sa_len = len_typ.value
|
||||||
|
|
||||||
|
arg_size_1 = 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))
|
||||||
|
|
||||||
|
return tuple(
|
||||||
|
_unpack(runner, sub_typ, arg_bytes)
|
||||||
|
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _load_tuple_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:
|
||||||
|
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
|
||||||
|
|
||||||
|
assert isinstance(typ, type3types.AppliedType3)
|
||||||
|
assert typ.base is type3types.tuple
|
||||||
|
|
||||||
|
typ_list = [
|
||||||
|
x
|
||||||
|
for x in typ.args
|
||||||
|
if not isinstance(x, type3types.PlaceholderForType)
|
||||||
|
]
|
||||||
|
assert len(typ_list) == len(typ.args)
|
||||||
|
|
||||||
|
arg_sizes = [
|
||||||
|
calculate_alloc_size(x, is_member=True)
|
||||||
|
for x in typ_list
|
||||||
|
]
|
||||||
|
|
||||||
|
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
|
||||||
|
|
||||||
|
return tuple(
|
||||||
|
_unpack(runner, arg_typ, arg_bytes)
|
||||||
|
for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes))
|
||||||
|
)
|
||||||
|
|
||||||
|
def _load_struct_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:
|
||||||
|
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
|
||||||
|
|
||||||
|
assert isinstance(typ, type3types.StructType3)
|
||||||
|
|
||||||
|
name_list = list(typ.members)
|
||||||
|
|
||||||
|
typ_list = [
|
||||||
|
x
|
||||||
|
for x in typ.members.values()
|
||||||
|
if not isinstance(x, type3types.PlaceholderForType)
|
||||||
|
]
|
||||||
|
assert len(typ_list) == len(typ.members)
|
||||||
|
|
||||||
|
arg_sizes = [
|
||||||
|
calculate_alloc_size(x, is_member=True)
|
||||||
|
for x in typ_list
|
||||||
|
]
|
||||||
|
|
||||||
|
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
|
||||||
|
|
||||||
|
return {
|
||||||
|
arg_name: _unpack(runner, arg_typ, arg_bytes)
|
||||||
|
for arg_name, arg_typ, arg_bytes in zip(name_list, typ_list, _split_read_bytes(read_bytes, arg_sizes))
|
||||||
|
}
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
Runners to help run WebAssembly code on various interpreters
|
Runners to help run WebAssembly code on various interpreters
|
||||||
"""
|
"""
|
||||||
from typing import Any, Callable, Dict, Iterable, Optional, TextIO
|
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import io
|
import io
|
||||||
|
from typing import Any, Callable, Dict, Iterable, Optional, TextIO
|
||||||
|
|
||||||
import pywasm.binary
|
import pywasm.binary
|
||||||
import wasm3
|
import wasm3
|
||||||
import wasmer
|
import wasmer
|
||||||
import wasmtime
|
import wasmtime
|
||||||
|
|
||||||
|
from phasm import ourlang, wasm
|
||||||
from phasm.compiler import phasm_compile
|
from phasm.compiler import phasm_compile
|
||||||
from phasm.parser import phasm_parse
|
from phasm.parser import phasm_parse
|
||||||
from phasm.type3.entry import phasm_type3
|
from phasm.type3.entry import phasm_type3
|
||||||
from phasm import ourlang
|
|
||||||
from phasm import wasm
|
Imports = Optional[Dict[str, Callable[[Any], Any]]]
|
||||||
|
|
||||||
class RunnerBase:
|
class RunnerBase:
|
||||||
"""
|
"""
|
||||||
@ -73,7 +73,7 @@ class RunnerBase:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
def interpreter_load(self, imports: Imports = None) -> None:
|
||||||
"""
|
"""
|
||||||
Loads the code into the interpreter
|
Loads the code into the interpreter
|
||||||
"""
|
"""
|
||||||
@ -166,7 +166,7 @@ class RunnerPywasm3(RunnerBase):
|
|||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
||||||
memory = self.rtime.get_memory(0)
|
memory = self.rtime.get_memory(0)
|
||||||
return memory[offset:length].tobytes()
|
return memory[offset:offset + length].tobytes()
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
||||||
_dump_memory(textio, self.rtime.get_memory(0))
|
_dump_memory(textio, self.rtime.get_memory(0))
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import pytest
|
|||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow_integration_test
|
@pytest.mark.slow_integration_test
|
||||||
def test_index():
|
def test_index():
|
||||||
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
|
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import binascii
|
import binascii
|
||||||
import struct
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow_integration_test
|
@pytest.mark.slow_integration_test
|
||||||
def test_crc32():
|
def test_crc32():
|
||||||
# FIXME: Stub
|
# FIXME: Stub
|
||||||
@ -31,9 +31,4 @@ def testEntry(data: bytes) -> u32:
|
|||||||
|
|
||||||
result = Suite(code_py).run_code(b'a')
|
result = Suite(code_py).run_code(b'a')
|
||||||
|
|
||||||
# exp_result returns a unsigned integer, as is proper
|
assert exp_result == result.returned_value
|
||||||
exp_data = struct.pack('I', exp_result)
|
|
||||||
# ints extracted from WebAssembly are always signed
|
|
||||||
data = struct.pack('i', result.returned_value)
|
|
||||||
|
|
||||||
assert exp_data == data
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import pytest
|
|||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow_integration_test
|
@pytest.mark.slow_integration_test
|
||||||
def test_fib():
|
def test_fib():
|
||||||
with open('./examples/fib.py', 'r', encoding='UTF-8') as fil:
|
with open('./examples/fib.py', 'r', encoding='UTF-8') as fil:
|
||||||
|
|||||||
358
tests/integration/test_lang/generator.md
Normal file
358
tests/integration/test_lang/generator.md
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
# runtime_extract_value_literal
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to extract a $TYPE value
|
||||||
|
In order get the result I calculated with my phasm code
|
||||||
|
|
||||||
|
```py
|
||||||
|
@exported
|
||||||
|
def testEntry() -> $TYPE:
|
||||||
|
return $VAL0
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
expect(VAL0)
|
||||||
|
```
|
||||||
|
|
||||||
|
# runtime_extract_value_round_trip
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to extract a $TYPE value that I've input
|
||||||
|
In order let the code select a value that I've predefined
|
||||||
|
|
||||||
|
```py
|
||||||
|
@exported
|
||||||
|
def testEntry(x: $TYPE) -> $TYPE:
|
||||||
|
return x
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
expect(VAL0, given=[VAL0])
|
||||||
|
```
|
||||||
|
|
||||||
|
# module_constant_def_ok
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to define $TYPE module constants
|
||||||
|
In order to make hardcoded values more visible
|
||||||
|
and to make it easier to change hardcoded values
|
||||||
|
|
||||||
|
```py
|
||||||
|
CONSTANT: $TYPE = $VAL0
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return 9
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
expect(9)
|
||||||
|
```
|
||||||
|
|
||||||
|
# module_constant_def_bad
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to receive a type error on an invalid assignment on a $TYPE module constant
|
||||||
|
In order to make debugging easier
|
||||||
|
|
||||||
|
```py
|
||||||
|
CONSTANT: (u32, ) = $VAL0
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||||
|
expect_type_error(
|
||||||
|
'Tuple element count mismatch',
|
||||||
|
'The given literal must fit the expected type',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expect_type_error(
|
||||||
|
'Must be tuple',
|
||||||
|
'The given literal must fit the expected type',
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_result_is_literal_ok
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to use return a literal from a function
|
||||||
|
In order to define constants in a more dynamic way
|
||||||
|
|
||||||
|
```py
|
||||||
|
def drop_arg_return_9(x: $TYPE) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
def constant() -> $TYPE:
|
||||||
|
return $VAL0
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return drop_arg_return_9(constant())
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
expect(9)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_result_is_literal_bad
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to receive a type error when returning a $TYPE literal for a function that doesn't return that type
|
||||||
|
In order to make debugging easier
|
||||||
|
|
||||||
|
```py
|
||||||
|
def drop_arg_return_9(x: (u32, )) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
def constant() -> (u32, ):
|
||||||
|
return $VAL0
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return drop_arg_return_9(constant())
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||||
|
expect_type_error(
|
||||||
|
'Mismatch between applied types argument count',
|
||||||
|
'The type of the value returned from function constant should match its return type',
|
||||||
|
)
|
||||||
|
elif TYPE_NAME.startswith('struct_'):
|
||||||
|
expect_type_error(
|
||||||
|
TYPE + ' must be tuple (u32) instead',
|
||||||
|
'The type of the value returned from function constant should match its return type',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expect_type_error(
|
||||||
|
'Must be tuple',
|
||||||
|
'The given literal must fit the expected type',
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_result_is_module_constant_ok
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to use return a $TYPE module constant from a function
|
||||||
|
In order to use my module constants in return statements
|
||||||
|
|
||||||
|
```py
|
||||||
|
CONSTANT: $TYPE = $VAL0
|
||||||
|
|
||||||
|
def helper(x: $TYPE) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
def constant() -> $TYPE:
|
||||||
|
return CONSTANT
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return helper(constant())
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
expect(9)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_result_is_module_constant_bad
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to receive a type error when returning a $TYPE module constant for a function that doesn't return that type
|
||||||
|
In order to make debugging easier
|
||||||
|
|
||||||
|
```py
|
||||||
|
CONSTANT: $TYPE = $VAL0
|
||||||
|
|
||||||
|
def drop_arg_return_9(x: (u32, )) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
def constant() -> (u32, ):
|
||||||
|
return CONSTANT
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return drop_arg_return_9(constant())
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||||
|
expect_type_error(
|
||||||
|
'Mismatch between applied types argument count',
|
||||||
|
'The type of the value returned from function constant should match its return type',
|
||||||
|
)
|
||||||
|
elif TYPE_NAME.startswith('struct_'):
|
||||||
|
expect_type_error(
|
||||||
|
TYPE + ' must be tuple (u32) instead',
|
||||||
|
'The type of the value returned from function constant should match its return type',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expect_type_error(
|
||||||
|
TYPE_NAME + ' must be tuple (u32) instead',
|
||||||
|
'The type of the value returned from function constant should match its return type',
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_result_is_arg_ok
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to use return a $TYPE function argument
|
||||||
|
In order to make it possible to select a value using a function
|
||||||
|
|
||||||
|
```py
|
||||||
|
CONSTANT: $TYPE = $VAL0
|
||||||
|
|
||||||
|
def drop_arg_return_9(x: $TYPE) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
def select(x: $TYPE) -> $TYPE:
|
||||||
|
return x
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return drop_arg_return_9(select(CONSTANT))
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
expect(9)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_result_is_arg_bad
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to receive a type error when returning a $TYPE argument for a function that doesn't return that type
|
||||||
|
In order to make debugging easier
|
||||||
|
|
||||||
|
```py
|
||||||
|
def drop_arg_return_9(x: (u32, )) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
def select(x: $TYPE) -> (u32, ):
|
||||||
|
return x
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||||
|
expect_type_error(
|
||||||
|
'Mismatch between applied types argument count',
|
||||||
|
'The type of the value returned from function select should match its return type',
|
||||||
|
)
|
||||||
|
elif TYPE_NAME.startswith('struct_'):
|
||||||
|
expect_type_error(
|
||||||
|
TYPE + ' must be tuple (u32) instead',
|
||||||
|
'The type of the value returned from function select should match its return type',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expect_type_error(
|
||||||
|
TYPE_NAME + ' must be tuple (u32) instead',
|
||||||
|
'The type of the value returned from function select should match its return type',
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_arg_literal_ok
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to use a $TYPE literal by passing it to a function
|
||||||
|
In order to use a pre-existing function with the values I specify
|
||||||
|
|
||||||
|
```py
|
||||||
|
def helper(x: $TYPE) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return helper($VAL0)
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
expect(9)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_arg_literal_bad
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to receive a type error when passing a $TYPE literal to a function that does not accept it
|
||||||
|
In order to make debugging easier
|
||||||
|
|
||||||
|
```py
|
||||||
|
def helper(x: (u32, )) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return helper($VAL0)
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||||
|
expect_type_error(
|
||||||
|
'Mismatch between applied types argument count',
|
||||||
|
# FIXME: Shouldn't this be the same as for the else statement?
|
||||||
|
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||||
|
)
|
||||||
|
elif TYPE_NAME.startswith('struct_'):
|
||||||
|
expect_type_error(
|
||||||
|
TYPE + ' must be tuple (u32) instead',
|
||||||
|
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expect_type_error(
|
||||||
|
'Must be tuple',
|
||||||
|
'The given literal must fit the expected type',
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_arg_module_constant_def_ok
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to use a $TYPE module constant by passing it to a function
|
||||||
|
In order to use my defined value with a pre-existing function
|
||||||
|
|
||||||
|
```py
|
||||||
|
CONSTANT: $TYPE = $VAL0
|
||||||
|
|
||||||
|
def helper(x: $TYPE) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return helper(CONSTANT)
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
expect(9)
|
||||||
|
```
|
||||||
|
|
||||||
|
# function_arg_module_constant_def_bad
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to receive a type error when passing a $TYPE module constant to a function that does not accept it
|
||||||
|
In order to make debugging easier
|
||||||
|
|
||||||
|
```py
|
||||||
|
CONSTANT: $TYPE = $VAL0
|
||||||
|
|
||||||
|
def helper(x: (u32, )) -> i32:
|
||||||
|
return 9
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return helper(CONSTANT)
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||||
|
expect_type_error(
|
||||||
|
'Mismatch between applied types argument count',
|
||||||
|
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||||
|
)
|
||||||
|
elif TYPE_NAME.startswith('struct_'):
|
||||||
|
expect_type_error(
|
||||||
|
TYPE + ' must be tuple (u32) instead',
|
||||||
|
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expect_type_error(
|
||||||
|
TYPE_NAME + ' must be tuple (u32) instead',
|
||||||
|
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||||
|
)
|
||||||
|
```
|
||||||
154
tests/integration/test_lang/generator.py
Normal file
154
tests/integration/test_lang/generator.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import functools
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import marko
|
||||||
|
import marko.md_renderer
|
||||||
|
|
||||||
|
|
||||||
|
def get_tests(template):
|
||||||
|
test_data = None
|
||||||
|
for el in template.children:
|
||||||
|
if isinstance(el, marko.block.BlankLine):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(el, marko.block.Heading):
|
||||||
|
if test_data is not None:
|
||||||
|
yield test_data
|
||||||
|
|
||||||
|
test_data = []
|
||||||
|
test_data.append(el)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if test_data is not None:
|
||||||
|
test_data.append(el)
|
||||||
|
|
||||||
|
if test_data is not None:
|
||||||
|
yield test_data
|
||||||
|
|
||||||
|
def apply_settings(settings, txt):
|
||||||
|
for k, v in settings.items():
|
||||||
|
if k in ('CODE_HEADER', 'PYTHON'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
txt = txt.replace(f'${k}', v)
|
||||||
|
return txt
|
||||||
|
|
||||||
|
def generate_assertion_expect(result, arg, given=None):
|
||||||
|
given = given or []
|
||||||
|
|
||||||
|
result.append('result = Suite(code_py).run_code(' + ', '.join(repr(x) for x in given) + ')')
|
||||||
|
result.append(f'assert {repr(arg)} == result.returned_value')
|
||||||
|
|
||||||
|
def generate_assertion_expect_type_error(result, error_msg, error_comment = None):
|
||||||
|
result.append('with pytest.raises(Type3Exception) as exc_info:')
|
||||||
|
result.append(' Suite(code_py).run_code()')
|
||||||
|
result.append(f'assert {repr(error_msg)} == exc_info.value.args[0][0].msg')
|
||||||
|
result.append(f'assert {repr(error_comment)} == exc_info.value.args[0][0].comment')
|
||||||
|
|
||||||
|
def json_does_not_support_byte_or_tuple_values_fix(inp: Any):
|
||||||
|
if isinstance(inp, (int, float, )):
|
||||||
|
return inp
|
||||||
|
|
||||||
|
if isinstance(inp, str):
|
||||||
|
if inp.startswith('bytes:'):
|
||||||
|
return inp[6:].encode()
|
||||||
|
return inp
|
||||||
|
|
||||||
|
if isinstance(inp, list):
|
||||||
|
return tuple(map(json_does_not_support_byte_or_tuple_values_fix, inp))
|
||||||
|
|
||||||
|
if isinstance(inp, dict):
|
||||||
|
return {
|
||||||
|
key: json_does_not_support_byte_or_tuple_values_fix(val)
|
||||||
|
for key, val in inp.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
raise NotImplementedError(inp)
|
||||||
|
|
||||||
|
def generate_assertions(settings, result_code):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
locals_ = {
|
||||||
|
'TYPE': settings['TYPE'],
|
||||||
|
'TYPE_NAME': settings['TYPE_NAME'],
|
||||||
|
'expect': functools.partial(generate_assertion_expect, result),
|
||||||
|
'expect_type_error': functools.partial(generate_assertion_expect_type_error, result),
|
||||||
|
}
|
||||||
|
|
||||||
|
if 'PYTHON' in settings:
|
||||||
|
locals_.update(json_does_not_support_byte_or_tuple_values_fix(settings['PYTHON']))
|
||||||
|
|
||||||
|
if 'VAL0' not in locals_:
|
||||||
|
locals_['VAL0'] = eval(settings['VAL0'])
|
||||||
|
|
||||||
|
exec(result_code, {}, locals_)
|
||||||
|
|
||||||
|
return ' ' + '\n '.join(result) + '\n'
|
||||||
|
|
||||||
|
def generate_code(markdown, template, settings):
|
||||||
|
type_name = settings['TYPE_NAME']
|
||||||
|
|
||||||
|
print('"""')
|
||||||
|
print('AUTO GENERATED')
|
||||||
|
print()
|
||||||
|
print('TEMPLATE:', sys.argv[1])
|
||||||
|
print('SETTINGS:', sys.argv[2])
|
||||||
|
print('"""')
|
||||||
|
print('import pytest')
|
||||||
|
print()
|
||||||
|
print('from phasm.type3.entry import Type3Exception')
|
||||||
|
print()
|
||||||
|
print('from ..helpers import Suite')
|
||||||
|
print()
|
||||||
|
|
||||||
|
for test in get_tests(template):
|
||||||
|
assert len(test) == 4, test
|
||||||
|
heading, paragraph, code_block1, code_block2 = test
|
||||||
|
|
||||||
|
assert isinstance(heading, marko.block.Heading)
|
||||||
|
assert isinstance(paragraph, marko.block.Paragraph)
|
||||||
|
assert isinstance(code_block1, marko.block.FencedCode)
|
||||||
|
assert isinstance(code_block2, marko.block.FencedCode)
|
||||||
|
|
||||||
|
test_id = apply_settings(settings, heading.children[0].children)
|
||||||
|
user_story = apply_settings(settings, markdown.renderer.render(paragraph))
|
||||||
|
inp_code = apply_settings(settings, code_block1.children[0].children)
|
||||||
|
|
||||||
|
result_code = markdown.renderer.render_children(code_block2)
|
||||||
|
|
||||||
|
print('@pytest.mark.integration_test')
|
||||||
|
print(f'def test_{type_name}_{test_id}():')
|
||||||
|
print(' """')
|
||||||
|
print(' ' + user_story.replace('\n', '\n '))
|
||||||
|
print(' """')
|
||||||
|
print(' code_py = """')
|
||||||
|
if 'CODE_HEADER' in settings:
|
||||||
|
for lin in settings['CODE_HEADER']:
|
||||||
|
print(lin)
|
||||||
|
print()
|
||||||
|
print(inp_code.rstrip('\n'))
|
||||||
|
print('"""')
|
||||||
|
print()
|
||||||
|
|
||||||
|
print(generate_assertions(settings, result_code))
|
||||||
|
print()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
markdown = marko.Markdown(
|
||||||
|
renderer=marko.md_renderer.MarkdownRenderer,
|
||||||
|
)
|
||||||
|
with open(sys.argv[1], 'r', encoding='utf-8') as fil:
|
||||||
|
template = markdown.parse(fil.read())
|
||||||
|
|
||||||
|
with open(sys.argv[2], 'r', encoding='utf-8') as fil:
|
||||||
|
settings = json.load(fil)
|
||||||
|
|
||||||
|
if 'TYPE_NAME' not in settings:
|
||||||
|
settings['TYPE_NAME'] = settings['TYPE']
|
||||||
|
|
||||||
|
generate_code(markdown, template, settings)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
4
tests/integration/test_lang/generator_bytes.json
Normal file
4
tests/integration/test_lang/generator_bytes.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"TYPE": "bytes",
|
||||||
|
"VAL0": "b'ABCDEFG'"
|
||||||
|
}
|
||||||
4
tests/integration/test_lang/generator_f32.json
Normal file
4
tests/integration/test_lang/generator_f32.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"TYPE": "f32",
|
||||||
|
"VAL0": "1000000.125"
|
||||||
|
}
|
||||||
4
tests/integration/test_lang/generator_f64.json
Normal file
4
tests/integration/test_lang/generator_f64.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"TYPE": "f64",
|
||||||
|
"VAL0": "1000000.125"
|
||||||
|
}
|
||||||
4
tests/integration/test_lang/generator_i32.json
Normal file
4
tests/integration/test_lang/generator_i32.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"TYPE": "i32",
|
||||||
|
"VAL0": "1000000"
|
||||||
|
}
|
||||||
4
tests/integration/test_lang/generator_i64.json
Normal file
4
tests/integration/test_lang/generator_i64.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"TYPE": "i64",
|
||||||
|
"VAL0": "1000000"
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "static_array_tuple_u32_u32_3",
|
||||||
|
"TYPE": "(u32, u32, )[3]",
|
||||||
|
"VAL0": "((1, 100, ), (2, 200, ), (3, 300, ), )"
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "static_array_u64_32",
|
||||||
|
"TYPE": "u64[32]",
|
||||||
|
"VAL0": "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, )"
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "static_array_with_structs",
|
||||||
|
"TYPE": "StructMain[3]",
|
||||||
|
"VAL0": "(StructMain(1, (StructCode(-4), 4, 4.0, ), (StructCode(-1), StructCode(-2), )), StructMain(2, (StructCode(-16), 16, 16.0, ), (StructCode(3), StructCode(14), )), StructMain(3, (StructCode(-256), 256, 256.0, ), (StructCode(-9), StructCode(-98), )), )",
|
||||||
|
"CODE_HEADER": [
|
||||||
|
"class StructCode:",
|
||||||
|
" code: i32",
|
||||||
|
"",
|
||||||
|
"class StructMain:",
|
||||||
|
" val00: u8",
|
||||||
|
" val01: (StructCode, u64, f32, )",
|
||||||
|
" val02: StructCode[2]"
|
||||||
|
],
|
||||||
|
"PYTHON": {
|
||||||
|
"VAL0": [
|
||||||
|
{
|
||||||
|
"val00": 1,
|
||||||
|
"val01": [{"code": -4}, 4, 4.0],
|
||||||
|
"val02": [{"code": -1}, {"code": -2}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val00": 2,
|
||||||
|
"val01": [{"code": -16}, 16, 16.0],
|
||||||
|
"val02": [{"code": 3}, {"code": 14}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"val00": 3,
|
||||||
|
"val01": [{"code": -256}, 256, 256.0],
|
||||||
|
"val02": [{"code": -9}, {"code": -98}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "struct_all_primitives",
|
||||||
|
"TYPE": "StructallPrimitives",
|
||||||
|
"VAL0": "StructallPrimitives(1, 4, 8, 1, -1, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!')",
|
||||||
|
"CODE_HEADER": [
|
||||||
|
"class StructallPrimitives:",
|
||||||
|
" val00: u8",
|
||||||
|
" val01: u32",
|
||||||
|
" val02: u64",
|
||||||
|
" val10: i8",
|
||||||
|
" val11: i8",
|
||||||
|
" val12: i32",
|
||||||
|
" val13: i32",
|
||||||
|
" val14: i64",
|
||||||
|
" val15: i64",
|
||||||
|
" val20: f32",
|
||||||
|
" val21: f32",
|
||||||
|
" val22: f64",
|
||||||
|
" val23: f64",
|
||||||
|
" val30: bytes"
|
||||||
|
],
|
||||||
|
"PYTHON": {
|
||||||
|
"VAL0": {
|
||||||
|
"val00": 1,
|
||||||
|
"val01": 4,
|
||||||
|
"val02": 8,
|
||||||
|
"val10": 1,
|
||||||
|
"val11": -1,
|
||||||
|
"val12": 4,
|
||||||
|
"val13": -4,
|
||||||
|
"val14": 8,
|
||||||
|
"val15": -8,
|
||||||
|
"val20": 125.125,
|
||||||
|
"val21": -125.125,
|
||||||
|
"val22": 5000.5,
|
||||||
|
"val23": -5000.5,
|
||||||
|
"val30": "bytes:Hello, world!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
tests/integration/test_lang/generator_struct_nested.json
Normal file
25
tests/integration/test_lang/generator_struct_nested.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "struct_nested",
|
||||||
|
"TYPE": "StructNested",
|
||||||
|
"VAL0": "StructNested(4, SubStruct(8, 16), 20)",
|
||||||
|
"CODE_HEADER": [
|
||||||
|
"class SubStruct:",
|
||||||
|
" val00: u8",
|
||||||
|
" val01: u8",
|
||||||
|
"",
|
||||||
|
"class StructNested:",
|
||||||
|
" val00: u64",
|
||||||
|
" val01: SubStruct",
|
||||||
|
" val02: u64"
|
||||||
|
],
|
||||||
|
"PYTHON": {
|
||||||
|
"VAL0": {
|
||||||
|
"val00": 4,
|
||||||
|
"val01": {
|
||||||
|
"val00": 8,
|
||||||
|
"val01": 16
|
||||||
|
},
|
||||||
|
"val02": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
tests/integration/test_lang/generator_struct_one_field.json
Normal file
12
tests/integration/test_lang/generator_struct_one_field.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "struct_one_field",
|
||||||
|
"TYPE": "StructOneField",
|
||||||
|
"VAL0": "StructOneField(4)",
|
||||||
|
"CODE_HEADER": [
|
||||||
|
"class StructOneField:",
|
||||||
|
" value: u32"
|
||||||
|
],
|
||||||
|
"PYTHON": {
|
||||||
|
"VAL0": {"value": 4}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "tuple_all_primitives",
|
||||||
|
"TYPE": "(u8, u32, u64, i8, i8, i32, i32, i64, i64, f32, f32, f64, f64, bytes, )",
|
||||||
|
"VAL0": "(1, 4, 8, 1, -1, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!', )"
|
||||||
|
}
|
||||||
5
tests/integration/test_lang/generator_tuple_nested.json
Normal file
5
tests/integration/test_lang/generator_tuple_nested.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "tuple_nested",
|
||||||
|
"TYPE": "(u64, (u32, bytes, u32, ), (u8, u32[3], u8, ), )",
|
||||||
|
"VAL0": "(1000000, (1, b'test', 2, ), (1, (4, 4, 4, ), 1, ), )"
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"TYPE_NAME": "tuple_u64_u32_u8",
|
||||||
|
"TYPE": "(u64, u32, u8, )",
|
||||||
|
"VAL0": "(1000000, 1000, 1, )"
|
||||||
|
}
|
||||||
4
tests/integration/test_lang/generator_u32.json
Normal file
4
tests/integration/test_lang/generator_u32.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"TYPE": "u32",
|
||||||
|
"VAL0": "1000000"
|
||||||
|
}
|
||||||
4
tests/integration/test_lang/generator_u64.json
Normal file
4
tests/integration/test_lang/generator_u64.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"TYPE": "u64",
|
||||||
|
"VAL0": "1000000"
|
||||||
|
}
|
||||||
115
tests/integration/test_lang/test_bits.py
Normal file
115
tests/integration/test_lang/test_bits.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from phasm.type3.entry import Type3Exception
|
||||||
|
|
||||||
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation
|
||||||
|
def test_logical_left_shift(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 << 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 80 == result.returned_value
|
||||||
|
assert isinstance(result.returned_value, int)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', ['u32', 'u64'])
|
||||||
|
def test_logical_right_shift_left_bit_zero(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 >> 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check with wasmtime, as other engines don't mind if the type
|
||||||
|
# doesn't match. They'll complain when: (>>) : u32 -> u64 -> u32
|
||||||
|
result = Suite(code_py).run_code(runtime='wasmtime')
|
||||||
|
|
||||||
|
assert 1 == result.returned_value
|
||||||
|
assert isinstance(result.returned_value, int)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_logical_right_shift_left_bit_one():
|
||||||
|
code_py = """
|
||||||
|
@exported
|
||||||
|
def testEntry() -> u32:
|
||||||
|
return 4294967295 >> 16
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 0xFFFF == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
||||||
|
def test_bitwise_or_uint(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 | 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 11 == result.returned_value
|
||||||
|
assert isinstance(result.returned_value, int)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_bitwise_or_inv_type():
|
||||||
|
code_py = """
|
||||||
|
@exported
|
||||||
|
def testEntry() -> f64:
|
||||||
|
return 10.0 | 3.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
with pytest.raises(Type3Exception, match='f64 does not implement the BitWiseOperation type class'):
|
||||||
|
Suite(code_py).run_code()
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_bitwise_or_type_mismatch():
|
||||||
|
code_py = """
|
||||||
|
CONSTANT1: u32 = 3
|
||||||
|
CONSTANT2: u64 = 3
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> u64:
|
||||||
|
return CONSTANT1 | CONSTANT2
|
||||||
|
"""
|
||||||
|
|
||||||
|
with pytest.raises(Type3Exception, match='u64 must be u32 instead'):
|
||||||
|
Suite(code_py).run_code()
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
||||||
|
def test_bitwise_xor(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 ^ 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 9 == result.returned_value
|
||||||
|
assert isinstance(result.returned_value, int)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
||||||
|
def test_bitwise_and(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 & 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 2 == result.returned_value
|
||||||
|
assert isinstance(result.returned_value, int)
|
||||||
@ -5,6 +5,7 @@ import pytest
|
|||||||
from ..helpers import Suite, write_header
|
from ..helpers import Suite, write_header
|
||||||
from ..runners import RunnerPywasm
|
from ..runners import RunnerPywasm
|
||||||
|
|
||||||
|
|
||||||
def setup_interpreter(phash_code: str) -> RunnerPywasm:
|
def setup_interpreter(phash_code: str) -> RunnerPywasm:
|
||||||
runner = RunnerPywasm(phash_code)
|
runner = RunnerPywasm(phash_code)
|
||||||
|
|
||||||
|
|||||||
@ -4,19 +4,6 @@ from phasm.type3.entry import Type3Exception
|
|||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bytes_address():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(f: bytes) -> bytes:
|
|
||||||
return f
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'This is a test')
|
|
||||||
|
|
||||||
# THIS DEPENDS ON THE ALLOCATOR
|
|
||||||
# A different allocator will return a different value
|
|
||||||
assert 20 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_bytes_length():
|
def test_bytes_length():
|
||||||
@ -31,7 +18,7 @@ def testEntry(f: bytes) -> u32:
|
|||||||
assert 24 == result.returned_value
|
assert 24 == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_bytes_index():
|
def test_bytes_index_ok():
|
||||||
code_py = """
|
code_py = """
|
||||||
@exported
|
@exported
|
||||||
def testEntry(f: bytes) -> u8:
|
def testEntry(f: bytes) -> u8:
|
||||||
@ -42,25 +29,11 @@ def testEntry(f: bytes) -> u8:
|
|||||||
|
|
||||||
assert 0x61 == result.returned_value
|
assert 0x61 == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_constant():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: bytes = b'ABCDEF'
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u8:
|
|
||||||
return CONSTANT[0]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 0x41 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_bytes_index_out_of_bounds():
|
def test_bytes_index_out_of_bounds():
|
||||||
code_py = """
|
code_py = """
|
||||||
@exported
|
@exported
|
||||||
def testEntry(f: bytes) -> u8:
|
def testEntry(f: bytes, g: bytes) -> u8:
|
||||||
return f[50]
|
return f[50]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -69,30 +42,12 @@ def testEntry(f: bytes) -> u8:
|
|||||||
assert 0 == result.returned_value
|
assert 0 == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_function_call_element_ok():
|
def test_bytes_index_invalid_type():
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry(f: bytes) -> u8:
|
|
||||||
return helper(f[0])
|
|
||||||
|
|
||||||
def helper(x: u8) -> u8:
|
|
||||||
return x
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'Short')
|
|
||||||
|
|
||||||
assert 83 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_function_call_element_type_mismatch():
|
|
||||||
code_py = """
|
code_py = """
|
||||||
@exported
|
@exported
|
||||||
def testEntry(f: bytes) -> u64:
|
def testEntry(f: bytes) -> u64:
|
||||||
return helper(f[0])
|
return f[50]
|
||||||
|
|
||||||
def helper(x: u64) -> u64:
|
|
||||||
return x
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'u64 must be u8 instead'):
|
with pytest.raises(Type3Exception, match=r'u64 must be u8 instead'):
|
||||||
Suite(code_py).run_code()
|
Suite(code_py).run_code(b'Short')
|
||||||
|
|||||||
18
tests/integration/test_lang/test_floating.py
Normal file
18
tests/integration/test_lang/test_floating.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', ['f32', 'f64'])
|
||||||
|
def test_builtins_sqrt(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return sqrt(25.0)
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 5 == result.returned_value
|
||||||
|
assert isinstance(result.returned_value, float)
|
||||||
33
tests/integration/test_lang/test_fractional.py
Normal file
33
tests/integration/test_lang/test_fractional.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
TYPE_LIST = ['f32', 'f64']
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', TYPE_LIST)
|
||||||
|
def test_division_float(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10.0 / 8.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 1.25 == result.returned_value
|
||||||
|
assert isinstance(result.returned_value, float)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', TYPE_LIST)
|
||||||
|
def test_division_zero_let_it_crash_float(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10.0 / 0.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
# WebAssembly dictates that float division follows the IEEE rules
|
||||||
|
# https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
assert float('+inf') == result.returned_value
|
||||||
34
tests/integration/test_lang/test_function_calls.py
Normal file
34
tests/integration/test_lang/test_function_calls.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_call_pre_defined():
|
||||||
|
code_py = """
|
||||||
|
def helper(left: i32) -> i32:
|
||||||
|
return left
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return helper(13)
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 13 == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_call_post_defined():
|
||||||
|
code_py = """
|
||||||
|
@exported
|
||||||
|
def testEntry() -> i32:
|
||||||
|
return helper(10, 3)
|
||||||
|
|
||||||
|
def helper(left: i32, right: i32) -> i32:
|
||||||
|
return left - right
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 7 == result.returned_value
|
||||||
@ -2,6 +2,7 @@ import pytest
|
|||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('inp', [9, 10, 11, 12])
|
@pytest.mark.parametrize('inp', [9, 10, 11, 12])
|
||||||
def test_if_simple(inp):
|
def test_if_simple(inp):
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from phasm.type3.entry import Type3Exception
|
|||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_imported_ok():
|
def test_imported_ok():
|
||||||
code_py = """
|
code_py = """
|
||||||
33
tests/integration/test_lang/test_integral.py
Normal file
33
tests/integration/test_lang/test_integral.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
TYPE_LIST = ['u32', 'u64', 'i32', 'i64']
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', TYPE_LIST)
|
||||||
|
def test_division_int(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 / 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 3 == result.returned_value
|
||||||
|
assert isinstance(result.returned_value, int)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', TYPE_LIST)
|
||||||
|
def test_division_zero_let_it_crash_int(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 / 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
# WebAssembly dictates that integer division is a partial operator (e.g. unreachable for 0)
|
||||||
|
# https://www.w3.org/TR/wasm-core-1/#-hrefop-idiv-umathrmidiv_u_n-i_1-i_2
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
Suite(code_py).run_code()
|
||||||
17
tests/integration/test_lang/test_literals.py
Normal file
17
tests/integration/test_lang/test_literals.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from phasm.type3.entry import Type3Exception
|
||||||
|
|
||||||
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_expr_constant_literal_does_not_fit():
|
||||||
|
code_py = """
|
||||||
|
@exported
|
||||||
|
def testEntry() -> u8:
|
||||||
|
return 1000
|
||||||
|
"""
|
||||||
|
|
||||||
|
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
||||||
|
Suite(code_py).run_code()
|
||||||
121
tests/integration/test_lang/test_num.py
Normal file
121
tests/integration/test_lang/test_num.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
INT_TYPES = ['u32', 'u64', 'i32', 'i64']
|
||||||
|
FLOAT_TYPES = ['f32', 'f64']
|
||||||
|
|
||||||
|
TYPE_MAP = {
|
||||||
|
'u32': int,
|
||||||
|
'u64': int,
|
||||||
|
'i32': int,
|
||||||
|
'i64': int,
|
||||||
|
'f32': float,
|
||||||
|
'f64': float,
|
||||||
|
}
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
|
def test_addition_int(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 + 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 13 == result.returned_value
|
||||||
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
|
def test_addition_float(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 32.0 + 0.125
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 32.125 == result.returned_value
|
||||||
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
|
def test_subtraction_int(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 - 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 7 == result.returned_value
|
||||||
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
|
def test_subtraction_float(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 100.0 - 67.875
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 32.125 == result.returned_value
|
||||||
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.skip('TODO: Runtimes return a signed value, which is difficult to test')
|
||||||
|
@pytest.mark.parametrize('type_', ('u32', 'u64')) # FIXME: u8
|
||||||
|
def test_subtraction_underflow(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 - 11
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 0 < result.returned_value
|
||||||
|
|
||||||
|
# TODO: Multiplication
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
|
def test_call_with_expression_int(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return helper(10 + 20, 3 + 5)
|
||||||
|
|
||||||
|
def helper(left: {type_}, right: {type_}) -> {type_}:
|
||||||
|
return left - right
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 22 == result.returned_value
|
||||||
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
|
def test_call_with_expression_float(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return helper(10.078125 + 90.046875, 63.0 + 5.0)
|
||||||
|
|
||||||
|
def helper(left: {type_}, right: {type_}) -> {type_}:
|
||||||
|
return left - right
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 32.125 == result.returned_value
|
||||||
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
@ -1,486 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from phasm.type3.entry import Type3Exception
|
|
||||||
|
|
||||||
from ..helpers import Suite
|
|
||||||
from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, TYPE_MAP
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
|
||||||
def test_expr_constant_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 13
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_expr_constant_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 32.125
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 32.125 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_expr_constant_literal_does_not_fit():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u8:
|
|
||||||
return 1000
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
|
||||||
def test_module_constant_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: {type_} = 13
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return CONSTANT
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_module_constant_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: {type_} = 32.125
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return CONSTANT
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 32.125 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_module_constant_type_failure():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: u8 = 1000
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u32:
|
|
||||||
return 14
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation
|
|
||||||
def test_logical_left_shift(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 << 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 80 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u32', 'u64'])
|
|
||||||
def test_logical_right_shift_left_bit_zero(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 >> 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Check with wasmtime, as other engines don't mind if the type
|
|
||||||
# doesn't match. They'll complain when: (>>) : u32 -> u64 -> u32
|
|
||||||
result = Suite(code_py).run_code(runtime='wasmtime')
|
|
||||||
|
|
||||||
assert 1 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_logical_right_shift_left_bit_one():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u32:
|
|
||||||
return 4294967295 >> 16
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 0xFFFF == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
||||||
def test_bitwise_or_uint(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 | 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 11 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bitwise_or_inv_type():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> f64:
|
|
||||||
return 10.0 | 3.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match='f64 does not implement the BitWiseOperation type class'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bitwise_or_type_mismatch():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT1: u32 = 3
|
|
||||||
CONSTANT2: u64 = 3
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u64:
|
|
||||||
return CONSTANT1 | CONSTANT2
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match='u64 must be u32 instead'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
||||||
def test_bitwise_xor(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 ^ 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 9 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
|
|
||||||
def test_bitwise_and(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 & 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 2 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
||||||
def test_addition_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 + 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_addition_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 32.0 + 0.125
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 32.125 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
||||||
def test_subtraction_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 - 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 7 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_subtraction_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 100.0 - 67.875
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 32.125 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('TODO: Runtimes return a signed value, which is difficult to test')
|
|
||||||
@pytest.mark.parametrize('type_', ('u32', 'u64')) # FIXME: u8
|
|
||||||
def test_subtraction_underflow(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 - 11
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 0 < result.returned_value
|
|
||||||
|
|
||||||
# TODO: Multiplication
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
||||||
def test_division_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 / 3
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 3 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_division_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10.0 / 8.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 1.25 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
||||||
def test_division_zero_let_it_crash_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10 / 0
|
|
||||||
"""
|
|
||||||
|
|
||||||
# WebAssembly dictates that integer division is a partial operator (e.g. unreachable for 0)
|
|
||||||
# https://www.w3.org/TR/wasm-core-1/#-hrefop-idiv-umathrmidiv_u_n-i_1-i_2
|
|
||||||
with pytest.raises(Exception):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_division_zero_let_it_crash_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return 10.0 / 0.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
# WebAssembly dictates that float division follows the IEEE rules
|
|
||||||
# https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
assert float('+inf') == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['f32', 'f64'])
|
|
||||||
def test_builtins_sqrt(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return sqrt(25.0)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 5 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
|
|
||||||
def test_function_argument(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry(a: {type_}) -> {type_}:
|
|
||||||
return a
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(125)
|
|
||||||
|
|
||||||
assert 125 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('TODO')
|
|
||||||
def test_explicit_positive_number():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return +523
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 523 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.skip('TODO')
|
|
||||||
def test_explicit_negative_number():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return -19
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert -19 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_call_no_args():
|
|
||||||
code_py = """
|
|
||||||
def helper() -> i32:
|
|
||||||
return 19
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper()
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 19 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_call_pre_defined():
|
|
||||||
code_py = """
|
|
||||||
def helper(left: i32) -> i32:
|
|
||||||
return left
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(13)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_call_post_defined():
|
|
||||||
code_py = """
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper(10, 3)
|
|
||||||
|
|
||||||
def helper(left: i32, right: i32) -> i32:
|
|
||||||
return left - right
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 7 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
||||||
def test_call_with_expression_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(10 + 20, 3 + 5)
|
|
||||||
|
|
||||||
def helper(left: {type_}, right: {type_}) -> {type_}:
|
|
||||||
return left - right
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 22 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_call_with_expression_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(10.078125 + 90.046875, 63.0 + 5.0)
|
|
||||||
|
|
||||||
def helper(left: {type_}, right: {type_}) -> {type_}:
|
|
||||||
return left - right
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 32.125 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_call_invalid_return_type():
|
|
||||||
code_py = """
|
|
||||||
def helper() -> i64:
|
|
||||||
return 19
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return helper()
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'i64 must be i32 instead'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_call_invalid_arg_type():
|
|
||||||
code_py = """
|
|
||||||
def helper(left: u8) -> u8:
|
|
||||||
return left
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u8:
|
|
||||||
return helper(500)
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
@ -2,132 +2,31 @@ import pytest
|
|||||||
|
|
||||||
from phasm.type3.entry import Type3Exception
|
from phasm.type3.entry import Type3Exception
|
||||||
|
|
||||||
from ..constants import (
|
|
||||||
ALL_FLOAT_TYPES, ALL_INT_TYPES, COMPLETE_INT_TYPES, COMPLETE_NUMERIC_TYPES, TYPE_MAP
|
|
||||||
)
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_module_constant_def():
|
def test_static_array_index_ok():
|
||||||
code_py = """
|
code_py = """
|
||||||
CONSTANT: u8[3] = (24, 57, 80, )
|
|
||||||
|
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> i32:
|
def testEntry(f: u64[3]) -> u64:
|
||||||
return 0
|
return f[2]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code((1, 2, 3, ))
|
||||||
|
|
||||||
assert 0 == result.returned_value
|
assert 3 == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
def test_static_array_index_invalid_type():
|
||||||
def test_module_constant_3(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: {type_}[3] = (24, 57, 80, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return CONSTANT[1]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 57 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
||||||
def test_function_call_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: {type_}[3] = (24, 57, 80, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
|
|
||||||
def helper(array: {type_}[3]) -> {type_}:
|
|
||||||
return array[0] + array[1] + array[2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 161 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_function_call_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: {type_}[3] = (24.0, 57.5, 80.75, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(CONSTANT)
|
|
||||||
|
|
||||||
def helper(array: {type_}[3]) -> {type_}:
|
|
||||||
return array[0] + array[1] + array[2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 162.25 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_function_call_element_ok():
|
|
||||||
code_py = """
|
code_py = """
|
||||||
CONSTANT: u64[3] = (250, 250000, 250000000, )
|
|
||||||
|
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> u64:
|
def testEntry(f: f32[3]) -> u64:
|
||||||
return helper(CONSTANT[0])
|
return f[0]
|
||||||
|
|
||||||
def helper(x: u64) -> u64:
|
|
||||||
return x
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
with pytest.raises(Type3Exception, match=r'u64 must be f32 instead'):
|
||||||
|
Suite(code_py).run_code((0.0, 1.5, 2.25, ))
|
||||||
assert 250 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_function_call_element_type_mismatch():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: u64[3] = (250, 250000, 250000000, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u8:
|
|
||||||
return helper(CONSTANT[0])
|
|
||||||
|
|
||||||
def helper(x: u8) -> u8:
|
|
||||||
return x
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'u8 must be u64 instead'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_module_constant_type_mismatch_bitwidth():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: u8[3] = (24, 57, 280, )
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_return_as_int():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: u8[3] = (24, 57, 80, )
|
|
||||||
|
|
||||||
def testEntry() -> u32:
|
|
||||||
return CONSTANT
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'static_array \(u8\) \(3\) must be u32 instead'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_module_constant_type_mismatch_not_subscriptable():
|
def test_module_constant_type_mismatch_not_subscriptable():
|
||||||
|
|||||||
@ -2,61 +2,20 @@ import pytest
|
|||||||
|
|
||||||
from phasm.type3.entry import Type3Exception
|
from phasm.type3.entry import Type3Exception
|
||||||
|
|
||||||
from ..constants import (
|
|
||||||
ALL_INT_TYPES, TYPE_MAP
|
|
||||||
)
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_module_constant_def():
|
|
||||||
code_py = """
|
|
||||||
class SomeStruct:
|
|
||||||
value0: u8
|
|
||||||
value1: u32
|
|
||||||
value2: u64
|
|
||||||
|
|
||||||
CONSTANT: SomeStruct = SomeStruct(250, 250000, 250000000)
|
@pytest.mark.integration_test
|
||||||
|
def test_struct_0():
|
||||||
|
code_py = """
|
||||||
|
class CheckedValue:
|
||||||
|
value: i32
|
||||||
|
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> i32:
|
def testEntry() -> i32:
|
||||||
return 0
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 0 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
|
||||||
def test_module_constant(type_):
|
|
||||||
code_py = f"""
|
|
||||||
class CheckedValue:
|
|
||||||
value: {type_}
|
|
||||||
|
|
||||||
CONSTANT: CheckedValue = CheckedValue(24)
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return CONSTANT.value
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 24 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
|
||||||
def test_struct_0(type_):
|
|
||||||
code_py = f"""
|
|
||||||
class CheckedValue:
|
|
||||||
value: {type_}
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper(CheckedValue(23))
|
return helper(CheckedValue(23))
|
||||||
|
|
||||||
def helper(cv: CheckedValue) -> {type_}:
|
def helper(cv: CheckedValue) -> i32:
|
||||||
return cv.value
|
return cv.value
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -104,41 +63,6 @@ def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
|
|||||||
|
|
||||||
assert 545 == result.returned_value
|
assert 545 == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_returned_struct():
|
|
||||||
code_py = """
|
|
||||||
class CheckedValue:
|
|
||||||
value: u8
|
|
||||||
|
|
||||||
CONSTANT: CheckedValue = CheckedValue(199)
|
|
||||||
|
|
||||||
def helper() -> CheckedValue:
|
|
||||||
return CONSTANT
|
|
||||||
|
|
||||||
def helper2(x: CheckedValue) -> u8:
|
|
||||||
return x.value
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u8:
|
|
||||||
return helper2(helper())
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 199 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_type_mismatch_arg_module_constant():
|
|
||||||
code_py = """
|
|
||||||
class Struct:
|
|
||||||
param: f32
|
|
||||||
|
|
||||||
STRUCT: Struct = Struct(1)
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match='Must be real'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
|
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
|
||||||
def test_type_mismatch_struct_member(type_):
|
def test_type_mismatch_struct_member(type_):
|
||||||
|
|||||||
@ -2,51 +2,8 @@ import pytest
|
|||||||
|
|
||||||
from phasm.type3.entry import Type3Exception
|
from phasm.type3.entry import Type3Exception
|
||||||
|
|
||||||
from ..constants import ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, TYPE_MAP
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_module_constant_def():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (u8, u32, u64, ) = (250, 250000, 250000000, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return 0
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 0 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
|
|
||||||
def test_module_constant_1(type_):
|
|
||||||
code_py = f"""
|
|
||||||
CONSTANT: ({type_}, ) = (65, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return CONSTANT[0]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 65 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_module_constant_6():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (u8, u8, u32, u32, u64, u64, ) = (11, 22, 3333, 4444, 555555, 666666, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u32:
|
|
||||||
return CONSTANT[2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 3333 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_function_call_element_ok():
|
def test_function_call_element_ok():
|
||||||
@ -65,175 +22,6 @@ def helper(x: u64) -> u64:
|
|||||||
|
|
||||||
assert 250000000 == result.returned_value
|
assert 250000000 == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_tuple():
|
|
||||||
code_py = """
|
|
||||||
def l1(c: (u64, )) -> u64:
|
|
||||||
return c[0]
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u64:
|
|
||||||
return l1((32, ))
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 32 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_tuple_of_tuple():
|
|
||||||
code_py = """
|
|
||||||
def l1(c: (u64, )) -> u64:
|
|
||||||
return c[0]
|
|
||||||
|
|
||||||
def l2(c: ((u64, ), u64, )) -> u64:
|
|
||||||
return l1(c[0])
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u64:
|
|
||||||
return l2(((64, ), 32, ))
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 64 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_tuple_of_tuple_of_tuple():
|
|
||||||
code_py = """
|
|
||||||
def l1(c: (u64, )) -> u64:
|
|
||||||
return c[0]
|
|
||||||
|
|
||||||
def l2(c: ((u64, ), u64, )) -> u64:
|
|
||||||
return l1(c[0])
|
|
||||||
|
|
||||||
def l3(c: (((u64, ), u64, ), u64, )) -> u64:
|
|
||||||
return l2(c[0])
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u64:
|
|
||||||
return l3((((128, ), 64, ), 32, ))
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 128 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_constant_tuple_of_tuple_of_tuple():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (((u64, ), u64, ), u64, ) = (((128, ), 64, ), 32, )
|
|
||||||
|
|
||||||
def l1(c: (u64, )) -> u64:
|
|
||||||
return c[0]
|
|
||||||
|
|
||||||
def l2(c: ((u64, ), u64, )) -> u64:
|
|
||||||
return l1(c[0])
|
|
||||||
|
|
||||||
def l3(c: (((u64, ), u64, ), u64, )) -> u64:
|
|
||||||
return l2(c[0])
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u64:
|
|
||||||
return l3(CONSTANT)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 128 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_bytes_as_part_of_tuple():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (bytes, u64, ) = (b'ABCDEF', 19, )
|
|
||||||
|
|
||||||
def l0(c: bytes) -> u8:
|
|
||||||
return c[0]
|
|
||||||
|
|
||||||
def l1(c: (bytes, u64, )) -> u8:
|
|
||||||
return l0(c[0])
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u8:
|
|
||||||
return l1(CONSTANT)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 0x41 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_struct_as_part_of_tuple():
|
|
||||||
code_py = """
|
|
||||||
class ValueStruct:
|
|
||||||
value: i32
|
|
||||||
|
|
||||||
CONSTANT: (ValueStruct, u64, ) = (ValueStruct(234), 19, )
|
|
||||||
|
|
||||||
def l0(c: ValueStruct) -> i32:
|
|
||||||
return c.value
|
|
||||||
|
|
||||||
def l1(c: (ValueStruct, u64, )) -> i32:
|
|
||||||
return l0(c[0])
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> i32:
|
|
||||||
return l1(CONSTANT)
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 234 == result.returned_value
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
def test_function_call_element_type_mismatch():
|
|
||||||
code_py = """
|
|
||||||
CONSTANT: (u8, u32, u64, ) = (250, 250000, 250000000, )
|
|
||||||
|
|
||||||
@exported
|
|
||||||
def testEntry() -> u8:
|
|
||||||
return helper(CONSTANT[2])
|
|
||||||
|
|
||||||
def helper(x: u8) -> u8:
|
|
||||||
return x
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'u8 must be u64 instead'):
|
|
||||||
Suite(code_py).run_code()
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
|
||||||
def test_tuple_simple_constructor_int(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper((24, 57, 80, ))
|
|
||||||
|
|
||||||
def helper(vector: ({type_}, {type_}, {type_}, )) -> {type_}:
|
|
||||||
return vector[0] + vector[1] + vector[2]
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 161 == result.returned_value
|
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
|
||||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
|
||||||
def test_tuple_simple_constructor_float(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
|
||||||
def testEntry() -> {type_}:
|
|
||||||
return helper((1.0, 2.0, 3.0, ))
|
|
||||||
|
|
||||||
def helper(v: ({type_}, {type_}, {type_}, )) -> {type_}:
|
|
||||||
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
|
|
||||||
"""
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
|
||||||
|
|
||||||
assert 3.74 < result.returned_value < 3.75
|
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.skip('SIMD support is but a dream')
|
@pytest.mark.skip('SIMD support is but a dream')
|
||||||
def test_tuple_i32x4():
|
def test_tuple_i32x4():
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import pytest
|
|||||||
from ..helpers import write_header
|
from ..helpers import write_header
|
||||||
from ..runners import RunnerPywasm3 as Runner
|
from ..runners import RunnerPywasm3 as Runner
|
||||||
|
|
||||||
|
|
||||||
def setup_interpreter(phash_code: str) -> Runner:
|
def setup_interpreter(phash_code: str) -> Runner:
|
||||||
runner = Runner(phash_code)
|
runner = Runner(phash_code)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user