Test generation framework with typing improvements
Prior to this PR, each type would have its own handwritten test suite. The end result was that not all types were tested for all situations. This PR adds a framework based on a Markdown file, which generates the basic tests for the types defined in json files. These are auto generated and updated by the Makefile before the test suite is run. Also, a number of unsupported type combinations are now supported. Also, we now support negative literals. Also, allocation calculation fixes for nested types. Also, the test helpers can now properly import and export typed variables such as bytes, static arrays and tuples. This may come in handy when it comes to phasm platform wanting to route data. Also, adds better support for i8 type. Also, started on a runtime.py, since there's quite some code now that deals with compile time handling of WebAssembly stuff. Also, minor improvement to the type constrains, namely we better match 'tuple' literals with static array types. Also, reduced spam when printing the type analysis results; constraints that go back on the backlog are now no longer printed one by one. It now also prints the end results of the typing analysis. Also, reorganized the big test_primitives test into type classes. Also, replaced pylint with ruff.
This commit is contained in:
parent
fd3939a680
commit
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 = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
mtyp: Optional[str]
|
||||||
|
if isinstance(mtyp3, type3types.StructType3) or isinstance(mtyp3, type3types.AppliedType3):
|
||||||
|
mtyp = 'i32'
|
||||||
|
else:
|
||||||
|
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:
|
||||||
return Error('Mismatch between applied types argument count', comment=self.comment)
|
# 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)
|
||||||
|
|
||||||
|
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