Compare commits

...

55 Commits

Author SHA1 Message Date
Johan B.W. de Vries
14b8065974 IntType3 2023-01-07 16:20:33 +01:00
Johan B.W. de Vries
0fa076bb93 Re-implemented if statements 2023-01-06 15:59:22 +01:00
Johan B.W. de Vries
da6e306fad Tuple instantiation works again
Typing is getting ugly again
2023-01-01 16:04:53 +01:00
Johan B.W. de Vries
f281b78c78 Fixed a wrong test that the new typer caught
Also, some fixes of things that were broken on structs
due to the tuple changes
2023-01-01 15:02:45 +01:00
Johan B.W. de Vries
7f0acf00fe Started work on applied type constraints 2022-12-31 15:31:39 +01:00
Johan B.W. de Vries
e936d6e885 Started on tuples 2022-12-31 14:52:10 +01:00
Johan B.W. de Vries
ac8daf74f5 PP UP, Python3.10 2022-12-31 14:40:41 +01:00
Johan B.W. de Vries
8f8f79d799 Comment fixes 2022-12-26 13:35:26 +01:00
Johan B.W. de Vries
4876be77fd Cleanup type comments, old generator file 2022-12-26 13:20:15 +01:00
Johan B.W. de Vries
1afa3efc31 Reimplements casting and foldl
Probably not 100% type checked yet.

Also, fixed logical right shift; standard says the type
is different.
2022-12-26 13:11:36 +01:00
Johan B.W. de Vries
a164976f63 Restored importing of function 2022-12-25 14:30:21 +01:00
Johan B.W. de Vries
2ab574706f Updated main, buffer, added test 2022-12-24 20:00:24 +01:00
Johan B.W. de Vries
750f8806b6 Work on bytes 2022-12-24 19:47:13 +01:00
Johan B.W. de Vries
25b5d6fc06 More work on tuples 2022-12-24 19:40:07 +01:00
Johan B.W. de Vries
17f538d8cc Fixed a missing constraint 2022-12-24 19:25:30 +01:00
Johan B.W. de Vries
e456f55bb0 Type compare, code cleanup, extra test 2022-12-24 19:22:03 +01:00
Johan B.W. de Vries
6e0c554cf2 Starting to see how this could work with static arrays 2022-12-24 17:56:47 +01:00
Johan B.W. de Vries
08b1d78faf Edge case fixes 2022-12-24 17:43:05 +01:00
Johan B.W. de Vries
1ab6dfd333 Some fixes for static array 2022-12-24 17:37:53 +01:00
Johan B.W. de Vries
d4d5e9e482 Constraints can now explode into more constraints
What could possibly go wrong.

All struct tests seem to pass.
2022-12-24 15:07:21 +01:00
Johan B.W. de Vries
7ce3f0f11c Started on struct constants 2022-12-18 15:05:41 +01:00
Johan B.W. de Vries
d18f1c6956 Re-implemented allocation calculations 2022-12-18 14:31:17 +01:00
Johan B.W. de Vries
6d426753c8 Various small fixes
Re-implementing some things that were broken.

Also, if a typing error is found, and we detect an
infinite loop, we return the errors instead, as that's
probably what causing the loop anyhow.
2022-12-18 14:26:10 +01:00
Johan B.W. de Vries
05e7c356ea Fixes
- Moved the three ConstraintBase functions into one, as it was
  giving trouble calculating everything
- Gave each constraint the option to have a comment
- SameTypeConstraint now can have multiple arguments
- Redid the infinite loop detection
- Implemented some more basic operators
- Redid the division by zero for floats, it wasn't giving the
  right results.
- Also postponed the updating the AST type3 until afterwards
- Various linting fixes
2022-11-27 14:11:14 +01:00
Johan B.W. de Vries
a838035e1a Cleanup
- Removed unused _str types
- Tested with untyped function arguments, seems to work a bit
2022-11-27 12:33:17 +01:00
Johan B.W. de Vries
30a4cee5af Fixes
- Tuple wasn't an applied type yet
- phasm_type3 would re-order type IDs between prints
- AppliedType3 wouldn't store the args for iterators
-
2022-11-26 14:46:31 +01:00
Johan B.W. de Vries
3bac625714 Introduced type classes, added debugging info for constraint 2022-11-25 15:04:15 +01:00
Johan B.W. de Vries
9f21d0fd1d More work on type3 2022-11-24 15:43:23 +01:00
Johan B.W. de Vries
b5a28daebf Ripping out Type2 (type_var) system 2022-11-24 14:49:17 +01:00
Johan B.W. de Vries
8cc47ae63e Cleanup to module_constant_def, function calls 2022-11-17 19:30:37 +01:00
Johan B.W. de Vries
13f3f33740 PlaceHolder all the things 2022-11-17 15:23:56 +01:00
Johan B.W. de Vries
a0645d94dd More ideas for type3 2022-11-17 14:51:14 +01:00
Johan B.W. de Vries
79ff11f622 Started on attempt3 of the type system 2022-11-17 13:31:10 +01:00
Johan B.W. de Vries
55a45ff17c Trying out some things regarding StaticArray 2022-11-16 13:50:03 +01:00
Johan B.W. de Vries
42c9ff6ca7 idea: Actual type class 2022-10-23 13:52:48 +02:00
Johan B.W. de Vries
312f7949bd Add mathematic edge case tests 2022-09-22 11:58:18 +02:00
Johan B.W. de Vries
bce3ed7ba1 Implements division
Division by zero is a trap on most runtimes, following
the Let it Crash philosophy
2022-09-22 11:50:38 +02:00
Johan B.W. de Vries
977c449c3f Removed redundant file 2022-09-19 14:55:05 +02:00
Johan B.W. de Vries
2a6da91eb9 Simplified locations, adds typing tests 2022-09-19 14:53:22 +02:00
Johan B.W. de Vries
4d3c0c6c3c StaticArray with constant index works again
Also, fix issue with f64 being parsed as f32
2022-09-19 14:43:15 +02:00
Johan B.W. de Vries
5da45e78c2 More work on StaticArray
Also naming fix, buildin => builtin.

Removes the use of ConstantStaticArray, as this was context dependent
2022-09-19 13:50:20 +02:00
Johan B.W. de Vries
4f7608a601 Fix: ModuleConstantDef type annotation was ignored 2022-09-19 12:29:48 +02:00
Johan B.W. de Vries
0097ce782d First work on restoring StaticArray
Removed the separate ModuleConstantRef since you can tell by the variable
property of VariableReference. We'll also add local variables there later
on.
2022-09-19 12:15:03 +02:00
Johan B.W. de Vries
299551db1b All primitive tests work again 2022-09-19 11:49:10 +02:00
Johan B.W. de Vries
906b15c93c Large cleanup to the tests
They are now better organized and easier to extend, I hope.
2022-09-19 11:16:34 +02:00
Johan B.W. de Vries
07c0688d1b Ripping out old type system. Will have to reimplement
bytes, static array, tuple and struct.
2022-09-17 20:50:06 +02:00
Johan B.W. de Vries
564f00a419 Work on ripping out old type system 2022-09-17 20:13:16 +02:00
Johan B.W. de Vries
58f74d3e1d Restored function calling 2022-09-17 19:37:49 +02:00
Johan B.W. de Vries
4b46483895 Worked on floats 2022-09-17 19:21:56 +02:00
Johan B.W. de Vries
b2816164f9 Improved unification 2022-09-17 17:14:17 +02:00
Johan B.W. de Vries
6f3d9a5bcc First attempt at ripping out old system
This breaks test_addition[u32], which is a good thing to chase next.
2022-09-16 17:39:46 +02:00
Johan B.W. de Vries
2d0daf4b90 Fixes 2022-09-16 17:04:13 +02:00
Johan B.W. de Vries
7669f3cbca More framework stuff 2022-09-16 17:01:23 +02:00
Johan B.W. de Vries
48e16c38b9 FunctionParam is a class, more framework stuff 2022-09-16 16:43:40 +02:00
Johan B.W. de Vries
7acb2bd8e6 Framework sketch 2022-09-16 15:54:24 +02:00
43 changed files with 3254 additions and 2052 deletions

View File

@ -34,11 +34,14 @@ typecheck: venv/.done
venv/bin/mypy --strict phasm tests/integration/runners.py venv/bin/mypy --strict phasm tests/integration/runners.py
venv/.done: requirements.txt venv/.done: requirements.txt
python3.8 -m venv venv python3.10 -m venv venv
venv/bin/python3 -m pip install wheel pip --upgrade venv/bin/python3 -m pip install wheel pip --upgrade
venv/bin/python3 -m pip install -r $^ venv/bin/python3 -m pip install -r $^
touch $@ touch $@
clean-examples:
rm -f examples/*.wat examples/*.wasm examples/*.wat.html examples/*.py.html
.SECONDARY: # Keep intermediate files .SECONDARY: # Keep intermediate files
.PHONY: examples .PHONY: examples

View File

@ -12,7 +12,7 @@ This is a hobby project for now. Use at your own risk.
How to run How to run
---------- ----------
You should only need make and python3. Currently, we're working with python3.8, You should only need make and python3. Currently, we're working with python3.10,
since we're using the python ast parser, it might not work on other versions. since we're using the python ast parser, it might not work on other versions.
To run the examples: To run the examples:
@ -32,7 +32,7 @@ make lint typecheck
To compile a Phasm file: To compile a Phasm file:
```sh ```sh
python3.8 -m phasm source.py output.wat python3.10 -m phasm source.py output.wat
``` ```
Additional required tools Additional required tools

View File

@ -1,7 +1,13 @@
# TODO # TODO
- Implement a trace() builtin for debugging
- Implement a proper type matching / checking system - Implement a proper type matching / checking system
- Implement subscript as an operator
- Re-implement Subscript contraints - Doing the LiteralFitsConstraint with a tuple doesn't put the types on the tuple elements
- Implement structs again, with the `.foo` notation working
- Rename constant to literal
- Implement a trace() builtin for debugging
- Check if we can use DataView in the Javascript examples, e.g. with setUint32 - Check if we can use DataView in the Javascript examples, e.g. with setUint32
- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this. - Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this.
- Implement a FizzBuzz example - Implement a FizzBuzz example

View File

@ -3,5 +3,5 @@ def index(inp: bytes, idx: u32) -> u8:
return inp[idx] return inp[idx]
@exported @exported
def length(inp: bytes) -> i32: def length(inp: bytes) -> u32:
return len(inp) return len(inp)

View File

@ -5,6 +5,7 @@ Functions for using this module from CLI
import sys import sys
from .parser import phasm_parse from .parser import phasm_parse
from .type3.entry import phasm_type3
from .compiler import phasm_compile from .compiler import phasm_compile
def main(source: str, sink: str) -> int: def main(source: str, sink: str) -> int:
@ -16,6 +17,7 @@ def main(source: str, sink: str) -> int:
code_py = fil.read() code_py = fil.read()
our_module = phasm_parse(code_py) our_module = phasm_parse(code_py)
phasm_type3(our_module, verbose=False)
wasm_module = phasm_compile(our_module) wasm_module = phasm_compile(our_module)
code_wat = wasm_module.to_wat() code_wat = wasm_module.to_wat()

View File

@ -6,7 +6,8 @@ It's intented to be a "any color, as long as it's black" kind of renderer
from typing import Generator from typing import Generator
from . import ourlang from . import ourlang
from . import typing from .type3 import types as type3types
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
def phasm_render(inp: ourlang.Module) -> str: def phasm_render(inp: ourlang.Module) -> str:
""" """
@ -16,63 +17,39 @@ def phasm_render(inp: ourlang.Module) -> str:
Statements = Generator[str, None, None] Statements = Generator[str, None, None]
def type_(inp: typing.TypeBase) -> str: def type3(inp: Type3OrPlaceholder) -> str:
""" """
Render: Type (name) Render: type's name
""" """
if isinstance(inp, typing.TypeNone): assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR
if inp is type3types.none:
return 'None' return 'None'
if isinstance(inp, typing.TypeBool): if isinstance(inp, type3types.AppliedType3):
return 'bool' if inp.base == type3types.tuple:
return '(' + ', '.join(
type3(x)
for x in inp.args
if isinstance(x, Type3) # Skip ints, not allowed here anyhow
) + ', )'
if isinstance(inp, typing.TypeUInt8): if inp.base == type3types.static_array:
return 'u8' assert 2 == len(inp.args)
assert isinstance(inp.args[0], Type3), TYPE3_ASSERTION_ERROR
assert isinstance(inp.args[1], type3types.IntType3), TYPE3_ASSERTION_ERROR
if isinstance(inp, typing.TypeUInt32): return inp.args[0].name + '[' + inp.args[1].name + ']'
return 'u32'
if isinstance(inp, typing.TypeUInt64): return inp.name
return 'u64'
if isinstance(inp, typing.TypeInt32): def struct_definition(inp: ourlang.StructDefinition) -> str:
return 'i32'
if isinstance(inp, typing.TypeInt64):
return 'i64'
if isinstance(inp, typing.TypeFloat32):
return 'f32'
if isinstance(inp, typing.TypeFloat64):
return 'f64'
if isinstance(inp, typing.TypeBytes):
return 'bytes'
if isinstance(inp, typing.TypeTuple):
mems = ', '.join(
type_(x.type)
for x in inp.members
)
return f'({mems}, )'
if isinstance(inp, typing.TypeStaticArray):
return f'{type_(inp.member_type)}[{len(inp.members)}]'
if isinstance(inp, typing.TypeStruct):
return inp.name
raise NotImplementedError(type_, inp)
def struct_definition(inp: typing.TypeStruct) -> str:
""" """
Render: TypeStruct's definition Render: TypeStruct's definition
""" """
result = f'class {inp.name}:\n' result = f'class {inp.struct_type3.name}:\n'
for mem in inp.members: for mem, typ in inp.struct_type3.members.items():
result += f' {mem.name}: {type_(mem.type)}\n' result += f' {mem}: {type3(typ)}\n'
return result return result
@ -80,40 +57,44 @@ def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
""" """
Render: Module Constant's definition Render: Module Constant's definition
""" """
return f'{inp.name}: {type_(inp.type)} = {expression(inp.constant)}\n' return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n'
def expression(inp: ourlang.Expression) -> str: def expression(inp: ourlang.Expression) -> str:
""" """
Render: A Phasm expression Render: A Phasm expression
""" """
if isinstance(inp, ( if isinstance(inp, ourlang.ConstantPrimitive):
ourlang.ConstantUInt8, ourlang.ConstantUInt32, ourlang.ConstantUInt64, # Floats might not round trip if the original constant
ourlang.ConstantInt32, ourlang.ConstantInt64,
)):
return str(inp.value)
if isinstance(inp, (ourlang.ConstantFloat32, ourlang.ConstantFloat64, )):
# These might not round trip if the original constant
# could not fit in the given float type # could not fit in the given float type
return str(inp.value) return str(inp.value)
if isinstance(inp, (ourlang.ConstantTuple, ourlang.ConstantStaticArray, )): if isinstance(inp, ourlang.ConstantTuple):
return '(' + ', '.join( return '(' + ', '.join(
expression(x) expression(x)
for x in inp.value for x in inp.value
) + ', )' ) + ', )'
if isinstance(inp, ourlang.ConstantStruct):
return inp.struct_name + '(' + ', '.join(
expression(x)
for x in inp.value
) + ')'
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
return str(inp.name) return str(inp.variable.name)
if isinstance(inp, ourlang.UnaryOp): if isinstance(inp, ourlang.UnaryOp):
if ( if (
inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS
or inp.operator in ourlang.WEBASSEMBLY_BUILDIN_BYTES_OPS): or inp.operator in ourlang.WEBASSEMBLY_BUILTIN_BYTES_OPS):
return f'{inp.operator}({expression(inp.right)})' return f'{inp.operator}({expression(inp.right)})'
if inp.operator == 'cast': if inp.operator == 'cast':
return f'{type_(inp.type)}({expression(inp.right)})' mtyp = type3(inp.type3)
if mtyp is None:
raise NotImplementedError(f'Casting to type {inp.type_var}')
return f'{mtyp}({expression(inp.right)})'
return f'{inp.operator}{expression(inp.right)}' return f'{inp.operator}{expression(inp.right)}'
@ -127,32 +108,31 @@ def expression(inp: ourlang.Expression) -> str:
) )
if isinstance(inp.function, ourlang.StructConstructor): if isinstance(inp.function, ourlang.StructConstructor):
return f'{inp.function.struct.name}({args})' return f'{inp.function.struct_type3.name}({args})'
if isinstance(inp.function, ourlang.TupleConstructor):
return f'({args}, )'
return f'{inp.function.name}({args})' return f'{inp.function.name}({args})'
if isinstance(inp, ourlang.AccessBytesIndex): if isinstance(inp, ourlang.TupleInstantiation):
return f'{expression(inp.varref)}[{expression(inp.index)}]' args = ', '.join(
expression(arg)
for arg in inp.elements
)
return f'({args}, )'
if isinstance(inp, ourlang.Subscript):
varref = expression(inp.varref)
index = expression(inp.index)
return f'{varref}[{index}]'
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.AccessStructMember):
return f'{expression(inp.varref)}.{inp.member.name}' return f'{expression(inp.varref)}.{inp.member}'
if isinstance(inp, (ourlang.AccessTupleMember, ourlang.AccessStaticArrayMember, )):
if isinstance(inp.member, ourlang.Expression):
return f'{expression(inp.varref)}[{expression(inp.member)}]'
return f'{expression(inp.varref)}[{inp.member.idx}]'
if isinstance(inp, ourlang.Fold): if isinstance(inp, ourlang.Fold):
fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr' fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr'
return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})' return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})'
if isinstance(inp, ourlang.ModuleConstantReference):
return inp.definition.name
raise NotImplementedError(expression, inp) raise NotImplementedError(expression, inp)
def statement(inp: ourlang.Statement) -> Statements: def statement(inp: ourlang.Statement) -> Statements:
@ -193,11 +173,11 @@ def function(inp: ourlang.Function) -> str:
result += '@imported\n' result += '@imported\n'
args = ', '.join( args = ', '.join(
f'{x}: {type_(y)}' f'{p.name}: {type3(p.type3)}'
for x, y in inp.posonlyargs for p in inp.posonlyargs
) )
result += f'def {inp.name}({args}) -> {type_(inp.returns)}:\n' result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n'
if inp.imported: if inp.imported:
result += ' pass\n' result += ' pass\n'
@ -215,7 +195,7 @@ def module(inp: ourlang.Module) -> str:
""" """
result = '' result = ''
for struct in inp.structs.values(): for struct in inp.struct_definitions.values():
if result: if result:
result += '\n' result += '\n'
result += struct_definition(struct) result += struct_definition(struct)
@ -227,7 +207,7 @@ def module(inp: ourlang.Module) -> str:
for func in inp.functions.values(): for func in inp.functions.values():
if func.lineno < 0: if func.lineno < 0:
# Buildin (-2) or auto generated (-1) # Builtin (-2) or auto generated (-1)
continue continue
if result: if result:

View File

@ -1,11 +1,13 @@
""" """
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 . import codestyle from . import codestyle
from . import ourlang from . import ourlang
from . import typing from .type3 import types as type3types
from . import wasm from . import wasm
from .stdlib import alloc as stdlib_alloc from .stdlib import alloc as stdlib_alloc
@ -13,17 +15,14 @@ from .stdlib import types as stdlib_types
from .wasmgenerator import Generator as WasmGenerator from .wasmgenerator import Generator as WasmGenerator
LOAD_STORE_TYPE_MAP = { LOAD_STORE_TYPE_MAP = {
typing.TypeUInt8: 'i32', 'u8': 'i32', # Have to use an u32, since there is no native u8 type
typing.TypeUInt32: 'i32', 'i32': 'i32',
typing.TypeUInt64: 'i64', 'i64': 'i64',
typing.TypeInt32: 'i32', 'u32': 'i32',
typing.TypeInt64: 'i64', 'u64': 'i64',
typing.TypeFloat32: 'f32', 'f32': 'f32',
typing.TypeFloat64: 'f64', 'f64': 'f64',
} }
"""
When generating code, we sometimes need to load or store simple values
"""
def phasm_compile(inp: ourlang.Module) -> wasm.Module: def phasm_compile(inp: ourlang.Module) -> wasm.Module:
""" """
@ -32,42 +31,60 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module:
""" """
return module(inp) return module(inp)
def type_(inp: typing.TypeBase) -> wasm.WasmType: def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
""" """
Compile: type Compile: type
Types are used for example in WebAssembly function parameters
and return types.
""" """
if isinstance(inp, typing.TypeNone): assert isinstance(inp, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp == type3types.none:
return wasm.WasmTypeNone() return wasm.WasmTypeNone()
if isinstance(inp, typing.TypeUInt8): if inp == type3types.u8:
# WebAssembly has only support for 32 and 64 bits # WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte # So we need to store more memory per byte
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if isinstance(inp, typing.TypeUInt32): if inp == type3types.u32:
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if isinstance(inp, typing.TypeUInt64): if inp == type3types.u64:
return wasm.WasmTypeInt64() return wasm.WasmTypeInt64()
if isinstance(inp, typing.TypeInt32): if inp == type3types.i32:
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if isinstance(inp, typing.TypeInt64): if inp == type3types.i64:
return wasm.WasmTypeInt64() return wasm.WasmTypeInt64()
if isinstance(inp, typing.TypeFloat32): if inp == type3types.f32:
return wasm.WasmTypeFloat32() return wasm.WasmTypeFloat32()
if isinstance(inp, typing.TypeFloat64): if inp == type3types.f64:
return wasm.WasmTypeFloat64() return wasm.WasmTypeFloat64()
if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeStaticArray, typing.TypeBytes)): if inp == type3types.bytes:
# Structs and tuples are passed as pointer # bytes are passed as pointer
# And pointers are i32 # And pointers are i32
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
raise NotImplementedError(type_, inp) if isinstance(inp, type3types.StructType3):
# Structs are passed as pointer, which are i32
return wasm.WasmTypeInt32()
if isinstance(inp, type3types.AppliedType3):
if inp.base == type3types.static_array:
# Static Arrays are passed as pointer, which are i32
return wasm.WasmTypeInt32()
if inp.base == type3types.tuple:
# Tuples are passed as pointer, which are i32
return wasm.WasmTypeInt32()
raise NotImplementedError(type3, inp)
# Operators that work for i32, i64, f32, f64 # Operators that work for i32, i64, f32, f64
OPERATOR_MAP = { OPERATOR_MAP = {
@ -81,8 +98,6 @@ U8_OPERATOR_MAP = {
# Under the hood, this is an i32 # Under the hood, this is an i32
# Implementing Right Shift XOR, OR, AND is fine since the 3 remaining # Implementing Right Shift XOR, OR, AND is fine since the 3 remaining
# bytes stay zero after this operation # bytes stay zero after this operation
# Since it's unsigned an unsigned value, Logical or Arithmetic shift right
# are the same operation
'>>': 'shr_u', '>>': 'shr_u',
'^': 'xor', '^': 'xor',
'|': 'or', '|': 'or',
@ -99,6 +114,7 @@ U32_OPERATOR_MAP = {
'^': 'xor', '^': 'xor',
'|': 'or', '|': 'or',
'&': 'and', '&': 'and',
'/': 'div_u' # Division by zero is a trap and the program will panic
} }
U64_OPERATOR_MAP = { U64_OPERATOR_MAP = {
@ -111,6 +127,7 @@ U64_OPERATOR_MAP = {
'^': 'xor', '^': 'xor',
'|': 'or', '|': 'or',
'&': 'and', '&': 'and',
'/': 'div_u' # Division by zero is a trap and the program will panic
} }
I32_OPERATOR_MAP = { I32_OPERATOR_MAP = {
@ -118,6 +135,7 @@ I32_OPERATOR_MAP = {
'>': 'gt_s', '>': 'gt_s',
'<=': 'le_s', '<=': 'le_s',
'>=': 'ge_s', '>=': 'ge_s',
'/': 'div_s' # Division by zero is a trap and the program will panic
} }
I64_OPERATOR_MAP = { I64_OPERATOR_MAP = {
@ -125,115 +143,226 @@ I64_OPERATOR_MAP = {
'>': 'gt_s', '>': 'gt_s',
'<=': 'le_s', '<=': 'le_s',
'>=': 'ge_s', '>=': 'ge_s',
'/': 'div_s' # Division by zero is a trap and the program will panic
} }
F32_OPERATOR_MAP = {
'/': 'div' # Division by zero is a trap and the program will panic
}
F64_OPERATOR_MAP = {
'/': 'div' # Division by zero is a trap and the program will panic
}
def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> None:
"""
Compile: Instantiation (allocation) of a tuple
"""
assert isinstance(inp.type3, type3types.AppliedType3)
assert inp.type3.base is type3types.tuple
assert len(inp.elements) == len(inp.type3.args)
comment_elements = ''
for element in inp.elements:
assert isinstance(element.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
comment_elements += f'{element.type3.name}, '
tmp_var = wgn.temp_var_i32('tuple_adr')
wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})')
# Allocated the required amounts of bytes in memory
wgn.i32.const(_calculate_alloc_size(inp.type3))
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
# Store each element individually
offset = 0
for element, exp_type3 in zip(inp.elements, inp.type3.args):
if isinstance(exp_type3, type3types.PlaceholderForType):
assert exp_type3.resolve_as is not None
exp_type3 = exp_type3.resolve_as
assert element.type3 == exp_type3
assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name]
wgn.local.get(tmp_var)
expression(wgn, element)
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset))
offset += _calculate_alloc_size(exp_type3)
# Return the allocated address
wgn.local.get(tmp_var)
def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
""" """
Compile: Any expression Compile: Any expression
""" """
if isinstance(inp, ourlang.ConstantUInt8): if isinstance(inp, ourlang.ConstantPrimitive):
wgn.i32.const(inp.value) assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
return
if isinstance(inp, ourlang.ConstantUInt32): if inp.type3 == type3types.u8:
wgn.i32.const(inp.value) # No native u8 type - treat as i32, with caution
return assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
return
if isinstance(inp, ourlang.ConstantUInt64): if inp.type3 in (type3types.i32, type3types.u32, ):
wgn.i64.const(inp.value) assert isinstance(inp.value, int)
return wgn.i32.const(inp.value)
return
if isinstance(inp, ourlang.ConstantInt32): if inp.type3 in (type3types.i64, type3types.u64, ):
wgn.i32.const(inp.value) assert isinstance(inp.value, int)
return wgn.i64.const(inp.value)
return
if isinstance(inp, ourlang.ConstantInt64): if inp.type3 == type3types.f32:
wgn.i64.const(inp.value) assert isinstance(inp.value, float)
return wgn.f32.const(inp.value)
return
if isinstance(inp, ourlang.ConstantFloat32): if inp.type3 == type3types.f64:
wgn.f32.const(inp.value) assert isinstance(inp.value, float)
return wgn.f64.const(inp.value)
return
if isinstance(inp, ourlang.ConstantFloat64): raise NotImplementedError(f'Constants with type {inp.type3}')
wgn.f64.const(inp.value)
return
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
wgn.add_statement('local.get', '${}'.format(inp.name)) if isinstance(inp.variable, ourlang.FunctionParam):
return wgn.add_statement('local.get', '${}'.format(inp.variable.name))
return
if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if isinstance(inp.type3, type3types.StructType3):
assert inp.variable.data_block is not None, 'Structs must be memory stored'
assert inp.variable.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.variable.data_block.address)
return
if isinstance(inp.type3, type3types.AppliedType3):
if inp.type3.base == type3types.static_array:
assert inp.variable.data_block is not None, 'Static arrays must be memory stored'
assert inp.variable.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.variable.data_block.address)
return
if inp.type3.base == type3types.tuple:
assert inp.variable.data_block is not None, 'Tuples must be memory stored'
assert inp.variable.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.variable.data_block.address)
return
raise NotImplementedError(expression, inp.variable, inp.type3.base)
assert inp.variable.data_block is None, 'Primitives are not memory stored'
expression(wgn, inp.variable.constant)
return
raise NotImplementedError(expression, inp.variable)
if isinstance(inp, ourlang.BinaryOp): if isinstance(inp, ourlang.BinaryOp):
expression(wgn, inp.left) expression(wgn, inp.left)
expression(wgn, inp.right) expression(wgn, inp.right)
if isinstance(inp.type, typing.TypeUInt8): assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
# FIXME: Re-implement build-in operators
# Maybe operator_annotation is the way to go
# Maybe the older stuff below that is the way to go
operator_annotation = f'({inp.operator}) :: {inp.left.type3:s} -> {inp.right.type3:s} -> {inp.type3:s}'
if operator_annotation == '(>) :: i32 -> i32 -> bool':
wgn.add_statement('i32.gt_s')
return
if operator_annotation == '(<) :: u64 -> u64 -> bool':
wgn.add_statement('i64.lt_u')
return
if operator_annotation == '(==) :: u64 -> u64 -> bool':
wgn.add_statement('i64.eq')
return
if inp.type3 == type3types.u8:
if operator := U8_OPERATOR_MAP.get(inp.operator, None): if operator := U8_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if isinstance(inp.type, typing.TypeUInt32): if inp.type3 == type3types.u32:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if operator := U32_OPERATOR_MAP.get(inp.operator, None): if operator := U32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if isinstance(inp.type, typing.TypeUInt64): if inp.type3 == type3types.u64:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if operator := U64_OPERATOR_MAP.get(inp.operator, None): if operator := U64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if isinstance(inp.type, typing.TypeInt32): if inp.type3 == type3types.i32:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if operator := I32_OPERATOR_MAP.get(inp.operator, None): if operator := I32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if isinstance(inp.type, typing.TypeInt64): if inp.type3 == type3types.i64:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if operator := I64_OPERATOR_MAP.get(inp.operator, None): if operator := I64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if isinstance(inp.type, typing.TypeFloat32): if inp.type3 == type3types.f32:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}') wgn.add_statement(f'f32.{operator}')
return return
if isinstance(inp.type, typing.TypeFloat64): if operator := F32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}')
return
if inp.type3 == type3types.f64:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f64.{operator}') wgn.add_statement(f'f64.{operator}')
return return
if operator := F64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f64.{operator}')
return
raise NotImplementedError(expression, inp.type, inp.operator) raise NotImplementedError(expression, inp.operator, inp.left.type3, inp.right.type3, inp.type3)
if isinstance(inp, ourlang.UnaryOp): if isinstance(inp, ourlang.UnaryOp):
expression(wgn, inp.right) expression(wgn, inp.right)
if isinstance(inp.type, typing.TypeFloat32): assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
if inp.type3 == type3types.f32:
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
wgn.add_statement(f'f32.{inp.operator}') wgn.add_statement(f'f32.{inp.operator}')
return return
if isinstance(inp.type, typing.TypeFloat64): if inp.type3 == type3types.f64:
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS: if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
wgn.add_statement(f'f64.{inp.operator}') wgn.add_statement(f'f64.{inp.operator}')
return return
if isinstance(inp.type, typing.TypeInt32): if inp.type3 == type3types.u32:
if inp.operator == 'len': if inp.operator == 'len':
if isinstance(inp.right.type, typing.TypeBytes): if inp.right.type3 == type3types.bytes:
wgn.i32.load() wgn.i32.load()
return return
if inp.operator == 'cast': if inp.operator == 'cast':
if isinstance(inp.type, typing.TypeUInt32) and isinstance(inp.right.type, typing.TypeUInt8): if inp.type3 == type3types.u32 and inp.right.type3 == type3types.u8:
# Nothing to do, you can use an u8 value as a u32 no problem # Nothing to do, you can use an u8 value as a u32 no problem
return return
raise NotImplementedError(expression, inp.type, inp.operator) raise NotImplementedError(expression, inp.type3, inp.operator)
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
for arg in inp.arguments: for arg in inp.arguments:
@ -242,104 +371,103 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.add_statement('call', '${}'.format(inp.function.name)) wgn.add_statement('call', '${}'.format(inp.function.name))
return return
if isinstance(inp, ourlang.AccessBytesIndex): if isinstance(inp, ourlang.TupleInstantiation):
if not isinstance(inp.type, typing.TypeUInt8): tuple_instantiation(wgn, inp)
raise NotImplementedError(inp, inp.type)
expression(wgn, inp.varref)
expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__)
return return
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.Subscript):
mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__) assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.member)
expression(wgn, inp.varref) if inp.varref.type3 is type3types.bytes:
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
return
if isinstance(inp, ourlang.AccessTupleMember):
mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.member)
expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
return
if isinstance(inp, ourlang.AccessStaticArrayMember):
mtyp = LOAD_STORE_TYPE_MAP.get(inp.static_array.member_type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of static arrays
raise NotImplementedError(expression, inp, inp.member)
if isinstance(inp.member, typing.TypeStaticArrayMember):
expression(wgn, inp.varref) expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset)) expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__)
return return
if isinstance(inp.varref.type3, type3types.AppliedType3):
if inp.varref.type3.base == type3types.static_array:
assert 2 == len(inp.varref.type3.args)
el_type = inp.varref.type3.args[0]
assert isinstance(el_type, type3types.Type3)
el_len = inp.varref.type3.args[1]
assert isinstance(el_len, type3types.IntType3)
# OPTIMIZE: If index is a constant, we can use offset instead of multiply
# and we don't need to do the out of bounds check
expression(wgn, inp.varref)
tmp_var = wgn.temp_var_i32('index')
expression(wgn, inp.index)
wgn.local.tee(tmp_var)
# Out of bounds check based on el_len.value
wgn.i32.const(el_len.value)
wgn.i32.ge_u()
with wgn.if_():
wgn.unreachable(comment='Out of bounds')
wgn.local.get(tmp_var)
wgn.i32.const(_calculate_alloc_size(el_type))
wgn.i32.mul()
wgn.i32.add()
assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load')
return
if inp.varref.type3.base == type3types.tuple:
assert isinstance(inp.index, ourlang.ConstantPrimitive)
assert isinstance(inp.index.value, int)
offset = 0
for el_type in inp.varref.type3.args[0:inp.index.value]:
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
offset += _calculate_alloc_size(el_type)
# This doubles as the out of bounds check
el_type = inp.varref.type3.args[inp.index.value]
assert isinstance(el_type, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
expression(wgn, inp.varref)
assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
return
raise NotImplementedError(expression, inp, inp.varref.type3)
if isinstance(inp, ourlang.AccessStructMember):
assert isinstance(inp.struct_type3.members[inp.member], type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[inp.struct_type3.members[inp.member].name]
expression(wgn, inp.varref) expression(wgn, inp.varref)
expression(wgn, inp.member) wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset(
wgn.i32.const(inp.static_array.member_type.alloc_size()) inp.struct_type3, inp.member
wgn.i32.mul() )))
wgn.i32.add()
wgn.add_statement(f'{mtyp}.load')
return return
if isinstance(inp, ourlang.Fold): if isinstance(inp, ourlang.Fold):
expression_fold(wgn, inp) expression_fold(wgn, inp)
return return
if isinstance(inp, ourlang.ModuleConstantReference):
if isinstance(inp.type, typing.TypeTuple):
assert isinstance(inp.definition.constant, ourlang.ConstantTuple)
assert inp.definition.data_block is not None, 'Combined values are memory stored'
assert inp.definition.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.definition.data_block.address)
return
if isinstance(inp.type, typing.TypeStaticArray):
assert isinstance(inp.definition.constant, ourlang.ConstantStaticArray)
assert inp.definition.data_block is not None, 'Combined values are memory stored'
assert inp.definition.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.definition.data_block.address)
return
assert inp.definition.data_block is None, 'Primitives are not memory stored'
mtyp = LOAD_STORE_TYPE_MAP.get(inp.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.type)
expression(wgn, inp.definition.constant)
return
raise NotImplementedError(expression, inp) raise NotImplementedError(expression, inp)
def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None: def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
""" """
Compile: Fold expression Compile: Fold expression
""" """
mtyp = LOAD_STORE_TYPE_MAP.get(inp.base.type.__class__) assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.base)
if inp.iter.type.__class__.__name__ != 'TypeBytes': if inp.iter.type3 is not type3types.bytes:
raise NotImplementedError(expression, inp, inp.iter.type) raise NotImplementedError(expression_fold, inp, inp.iter.type3)
wgn.add_statement('nop', comment='acu :: u8') wgn.add_statement('nop', comment='acu :: u8')
acu_var = wgn.temp_var_u8(f'fold_{codestyle.type_(inp.type)}_acu') acu_var = wgn.temp_var_u8(f'fold_{codestyle.type3(inp.type3)}_acu')
wgn.add_statement('nop', comment='adr :: bytes*') wgn.add_statement('nop', comment='adr :: bytes*')
adr_var = wgn.temp_var_i32('fold_i32_adr') adr_var = wgn.temp_var_i32('fold_i32_adr')
wgn.add_statement('nop', comment='len :: i32') wgn.add_statement('nop', comment='len :: i32')
@ -359,7 +487,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
wgn.local.set(len_var) wgn.local.set(len_var)
wgn.add_statement('nop', comment='i = 0') wgn.add_statement('nop', comment='i = 0')
idx_var = wgn.temp_var_i32(f'fold_{codestyle.type_(inp.type)}_idx') idx_var = wgn.temp_var_i32(f'fold_{codestyle.type3(inp.type3)}_idx')
wgn.i32.const(0) wgn.i32.const(0)
wgn.local.set(idx_var) wgn.local.set(idx_var)
@ -450,7 +578,7 @@ def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
""" """
Compile: function argument Compile: function argument
""" """
return (inp[0], type_(inp[1]), ) return (inp.name, type3(inp.type3), )
def import_(inp: ourlang.Function) -> wasm.Import: def import_(inp: ourlang.Function) -> wasm.Import:
""" """
@ -466,7 +594,7 @@ def import_(inp: ourlang.Function) -> wasm.Import:
function_argument(x) function_argument(x)
for x in inp.posonlyargs for x in inp.posonlyargs
], ],
type_(inp.returns) type3(inp.returns_type3)
) )
def function(inp: ourlang.Function) -> wasm.Function: def function(inp: ourlang.Function) -> wasm.Function:
@ -477,9 +605,7 @@ def function(inp: ourlang.Function) -> wasm.Function:
wgn = WasmGenerator() wgn = WasmGenerator()
if isinstance(inp, ourlang.TupleConstructor): if isinstance(inp, ourlang.StructConstructor):
_generate_tuple_constructor(wgn, inp)
elif isinstance(inp, ourlang.StructConstructor):
_generate_struct_constructor(wgn, inp) _generate_struct_constructor(wgn, inp)
else: else:
for stat in inp.statements: for stat in inp.statements:
@ -496,7 +622,7 @@ def function(inp: ourlang.Function) -> wasm.Function:
(k, v.wasm_type(), ) (k, v.wasm_type(), )
for k, v in wgn.locals.items() for k, v in wgn.locals.items()
], ],
type_(inp.returns), type3(inp.returns_type3),
wgn.statements wgn.statements
) )
@ -555,38 +681,47 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
for block in inp.blocks: for block in inp.blocks:
block.address = unalloc_ptr + 4 # 4 bytes for allocator header block.address = unalloc_ptr + 4 # 4 bytes for allocator header
data_list = [] data_list: List[bytes] = []
for constant in block.data: for constant in block.data:
if isinstance(constant, ourlang.ConstantUInt8): assert isinstance(constant.type3, type3types.Type3), (id(constant), type3types.TYPE3_ASSERTION_ERROR)
if constant.type3 == type3types.u8:
assert isinstance(constant.value, int)
data_list.append(module_data_u8(constant.value)) data_list.append(module_data_u8(constant.value))
continue continue
if isinstance(constant, ourlang.ConstantUInt32): if constant.type3 == type3types.u32:
assert isinstance(constant.value, int)
data_list.append(module_data_u32(constant.value)) data_list.append(module_data_u32(constant.value))
continue continue
if isinstance(constant, ourlang.ConstantUInt64): if constant.type3 == type3types.u64:
assert isinstance(constant.value, int)
data_list.append(module_data_u64(constant.value)) data_list.append(module_data_u64(constant.value))
continue continue
if isinstance(constant, ourlang.ConstantInt32): if constant.type3 == type3types.i32:
assert isinstance(constant.value, int)
data_list.append(module_data_i32(constant.value)) data_list.append(module_data_i32(constant.value))
continue continue
if isinstance(constant, ourlang.ConstantInt64): if constant.type3 == type3types.i64:
assert isinstance(constant.value, int)
data_list.append(module_data_i64(constant.value)) data_list.append(module_data_i64(constant.value))
continue continue
if isinstance(constant, ourlang.ConstantFloat32): if constant.type3 == type3types.f32:
assert isinstance(constant.value, float)
data_list.append(module_data_f32(constant.value)) data_list.append(module_data_f32(constant.value))
continue continue
if isinstance(constant, ourlang.ConstantFloat64): if constant.type3 == type3types.f64:
assert isinstance(constant.value, float)
data_list.append(module_data_f64(constant.value)) data_list.append(module_data_f64(constant.value))
continue continue
raise NotImplementedError(constant) raise NotImplementedError(constant, constant.type3, constant.value)
block_data = b''.join(data_list) block_data = b''.join(data_list)
@ -636,48 +771,70 @@ def module(inp: ourlang.Module) -> wasm.Module:
return result return result
def _generate_tuple_constructor(wgn: WasmGenerator, inp: ourlang.TupleConstructor) -> None:
tmp_var = wgn.temp_var_i32('tuple_adr')
# Allocated the required amounts of bytes in memory
wgn.i32.const(inp.tuple.alloc_size())
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
# Store each member individually
for member in inp.tuple.members:
mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, member)
wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'$arg{member.idx}')
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
# Return the allocated address
wgn.local.get(tmp_var)
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
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(inp.struct.alloc_size()) 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 member in inp.struct.members: for memname, mtyp3 in inp.struct_type3.members.items():
mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__) 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 # In the future might extend this by having structs or tuples
# as members of struct or tuples # as members of struct or tuples
raise NotImplementedError(expression, inp, member) raise NotImplementedError(expression, inp, mtyp3)
wgn.local.get(tmp_var) wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'${member.name}') wgn.add_statement('local.get', f'${memname}')
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset)) wgn.add_statement(f'{mtyp}.store', 'offset=' + str(_calculate_member_offset(
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]) -> 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):
return sum(
_calculate_alloc_size(x)
for x in typ.members.values()
)
if isinstance(typ, type3types.AppliedType3):
if typ.base is type3types.tuple:
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)
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)
raise Exception(f'{member} not in {struct_type3}')

View File

@ -1,128 +1,51 @@
""" """
Contains the syntax tree for ourlang Contains the syntax tree for ourlang
""" """
from typing import Dict, List, Tuple, Optional, Union from typing import Dict, List, Optional, Union
import enum import enum
from typing_extensions import Final from typing_extensions import Final
WEBASSEMBLY_BUILDIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', ) WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
WEBASSEMBLY_BUILDIN_BYTES_OPS: Final = ('len', ) WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
from .typing import ( from .type3 import types as type3types
TypeBase, from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType, StructType3
TypeNone,
TypeBool,
TypeUInt8, TypeUInt32, TypeUInt64,
TypeInt32, TypeInt64,
TypeFloat32, TypeFloat64,
TypeBytes,
TypeTuple, TypeTupleMember,
TypeStaticArray, TypeStaticArrayMember,
TypeStruct, TypeStructMember,
)
class Expression: class Expression:
""" """
An expression within a statement An expression within a statement
""" """
__slots__ = ('type', ) __slots__ = ('type3', )
type: TypeBase type3: Type3OrPlaceholder
def __init__(self, type_: TypeBase) -> None: def __init__(self) -> None:
self.type = type_ self.type3 = PlaceholderForType([self])
class Constant(Expression): class Constant(Expression):
""" """
An constant value expression within a statement An constant value expression within a statement
# FIXME: Rename to literal
""" """
__slots__ = () __slots__ = ()
class ConstantUInt8(Constant): class ConstantPrimitive(Constant):
""" """
An UInt8 constant value expression within a statement An primitive constant value expression within a statement
""" """
__slots__ = ('value', ) __slots__ = ('value', )
value: int value: Union[int, float]
def __init__(self, type_: TypeUInt8, value: int) -> None: def __init__(self, value: Union[int, float]) -> None:
super().__init__(type_) super().__init__()
self.value = value self.value = value
class ConstantUInt32(Constant): def __repr__(self) -> str:
""" return f'ConstantPrimitive({repr(self.value)})'
An UInt32 constant value expression within a statement
"""
__slots__ = ('value', )
value: int
def __init__(self, type_: TypeUInt32, value: int) -> None:
super().__init__(type_)
self.value = value
class ConstantUInt64(Constant):
"""
An UInt64 constant value expression within a statement
"""
__slots__ = ('value', )
value: int
def __init__(self, type_: TypeUInt64, value: int) -> None:
super().__init__(type_)
self.value = value
class ConstantInt32(Constant):
"""
An Int32 constant value expression within a statement
"""
__slots__ = ('value', )
value: int
def __init__(self, type_: TypeInt32, value: int) -> None:
super().__init__(type_)
self.value = value
class ConstantInt64(Constant):
"""
An Int64 constant value expression within a statement
"""
__slots__ = ('value', )
value: int
def __init__(self, type_: TypeInt64, value: int) -> None:
super().__init__(type_)
self.value = value
class ConstantFloat32(Constant):
"""
An Float32 constant value expression within a statement
"""
__slots__ = ('value', )
value: float
def __init__(self, type_: TypeFloat32, value: float) -> None:
super().__init__(type_)
self.value = value
class ConstantFloat64(Constant):
"""
An Float64 constant value expression within a statement
"""
__slots__ = ('value', )
value: float
def __init__(self, type_: TypeFloat64, value: float) -> None:
super().__init__(type_)
self.value = value
class ConstantTuple(Constant): class ConstantTuple(Constant):
""" """
@ -130,35 +53,43 @@ class ConstantTuple(Constant):
""" """
__slots__ = ('value', ) __slots__ = ('value', )
value: List[Constant] value: List[ConstantPrimitive]
def __init__(self, type_: TypeTuple, value: List[Constant]) -> None: def __init__(self, value: List[ConstantPrimitive]) -> None: # FIXME: Tuple of tuples?
super().__init__(type_) super().__init__()
self.value = value self.value = value
class ConstantStaticArray(Constant): def __repr__(self) -> str:
""" return f'ConstantTuple({repr(self.value)})'
A StaticArray constant value expression within a statement
"""
__slots__ = ('value', )
value: List[Constant] class ConstantStruct(Constant):
"""
A Struct constant value expression within a statement
"""
__slots__ = ('struct_name', 'value', )
def __init__(self, type_: TypeStaticArray, value: List[Constant]) -> None: struct_name: str
super().__init__(type_) value: List[ConstantPrimitive]
def __init__(self, struct_name: str, value: List[ConstantPrimitive]) -> None: # FIXME: Struct of structs?
super().__init__()
self.struct_name = struct_name
self.value = value self.value = value
def __repr__(self) -> str:
return f'ConstantStruct({repr(self.struct_name)}, {repr(self.value)})'
class VariableReference(Expression): class VariableReference(Expression):
""" """
An variable reference expression within a statement An variable reference expression within a statement
""" """
__slots__ = ('name', ) __slots__ = ('variable', )
name: str variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local
def __init__(self, type_: TypeBase, name: str) -> None: def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam']) -> None:
super().__init__(type_) super().__init__()
self.name = name self.variable = variable
class UnaryOp(Expression): class UnaryOp(Expression):
""" """
@ -169,8 +100,8 @@ class UnaryOp(Expression):
operator: str operator: str
right: Expression right: Expression
def __init__(self, type_: TypeBase, operator: str, right: Expression) -> None: def __init__(self, operator: str, right: Expression) -> None:
super().__init__(type_) super().__init__()
self.operator = operator self.operator = operator
self.right = right self.right = right
@ -185,13 +116,16 @@ class BinaryOp(Expression):
left: Expression left: Expression
right: Expression right: Expression
def __init__(self, type_: TypeBase, operator: str, left: Expression, right: Expression) -> None: def __init__(self, operator: str, left: Expression, right: Expression) -> None:
super().__init__(type_) super().__init__()
self.operator = operator self.operator = operator
self.left = left self.left = left
self.right = right self.right = right
def __repr__(self) -> str:
return f'BinaryOp({repr(self.operator)}, {repr(self.left)}, {repr(self.right)})'
class FunctionCall(Expression): class FunctionCall(Expression):
""" """
A function call expression within a statement A function call expression within a statement
@ -202,22 +136,36 @@ class FunctionCall(Expression):
arguments: List[Expression] arguments: List[Expression]
def __init__(self, function: 'Function') -> None: def __init__(self, function: 'Function') -> None:
super().__init__(function.returns) super().__init__()
self.function = function self.function = function
self.arguments = [] self.arguments = []
class AccessBytesIndex(Expression): class TupleInstantiation(Expression):
""" """
Access a bytes index for reading Instantiation a tuple
"""
__slots__ = ('elements', )
elements: List[Expression]
def __init__(self, elements: List[Expression]) -> None:
super().__init__()
self.elements = elements
class Subscript(Expression):
"""
A subscript, for example to refer to a static array or tuple
by index
""" """
__slots__ = ('varref', 'index', ) __slots__ = ('varref', 'index', )
varref: VariableReference varref: VariableReference
index: Expression index: Expression
def __init__(self, type_: TypeBase, varref: VariableReference, index: Expression) -> None: def __init__(self, varref: VariableReference, index: Expression) -> None:
super().__init__(type_) super().__init__()
self.varref = varref self.varref = varref
self.index = index self.index = index
@ -226,47 +174,17 @@ class AccessStructMember(Expression):
""" """
Access a struct member for reading of writing Access a struct member for reading of writing
""" """
__slots__ = ('varref', 'member', ) __slots__ = ('varref', 'struct_type3', 'member', )
varref: VariableReference varref: VariableReference
member: TypeStructMember struct_type3: StructType3
member: str
def __init__(self, varref: VariableReference, member: TypeStructMember) -> None: def __init__(self, varref: VariableReference, struct_type3: StructType3, member: str) -> None:
super().__init__(member.type) super().__init__()
self.varref = varref self.varref = varref
self.member = member self.struct_type3 = struct_type3
class AccessTupleMember(Expression):
"""
Access a tuple member for reading of writing
"""
__slots__ = ('varref', 'member', )
varref: VariableReference
member: TypeTupleMember
def __init__(self, varref: VariableReference, member: TypeTupleMember, ) -> None:
super().__init__(member.type)
self.varref = varref
self.member = member
class AccessStaticArrayMember(Expression):
"""
Access a tuple member for reading of writing
"""
__slots__ = ('varref', 'static_array', 'member', )
varref: Union['ModuleConstantReference', VariableReference]
static_array: TypeStaticArray
member: Union[Expression, TypeStaticArrayMember]
def __init__(self, varref: Union['ModuleConstantReference', VariableReference], static_array: TypeStaticArray, member: Union[TypeStaticArrayMember, Expression], ) -> None:
super().__init__(static_array.member_type)
self.varref = varref
self.static_array = static_array
self.member = member self.member = member
class Fold(Expression): class Fold(Expression):
@ -287,31 +205,18 @@ class Fold(Expression):
def __init__( def __init__(
self, self,
type_: TypeBase,
dir_: Direction, dir_: Direction,
func: 'Function', func: 'Function',
base: Expression, base: Expression,
iter_: Expression, iter_: Expression,
) -> None: ) -> None:
super().__init__(type_) super().__init__()
self.dir = dir_ self.dir = dir_
self.func = func self.func = func
self.base = base self.base = base
self.iter = iter_ self.iter = iter_
class ModuleConstantReference(Expression):
"""
An reference to a module constant expression within a statement
"""
__slots__ = ('definition', )
definition: 'ModuleConstantDef'
def __init__(self, type_: TypeBase, definition: 'ModuleConstantDef') -> None:
super().__init__(type_)
self.definition = definition
class Statement: class Statement:
""" """
A statement within a function A statement within a function
@ -348,20 +253,31 @@ class StatementIf(Statement):
self.statements = [] self.statements = []
self.else_statements = [] self.else_statements = []
FunctionParam = Tuple[str, TypeBase] class FunctionParam:
"""
A parameter for a Function
"""
__slots__ = ('name', 'type3', )
name: str
type3: Type3OrPlaceholder
def __init__(self, name: str, type3: Optional[Type3]) -> None:
self.name = name
self.type3 = PlaceholderForType([self]) if type3 is None else type3
class Function: class Function:
""" """
A function processes input and produces output A function processes input and produces output
""" """
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns', 'posonlyargs', ) __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'posonlyargs', )
name: str name: str
lineno: int lineno: int
exported: bool exported: bool
imported: bool imported: bool
statements: List[Statement] statements: List[Statement]
returns: TypeBase returns_type3: Type3
posonlyargs: List[FunctionParam] posonlyargs: List[FunctionParam]
def __init__(self, name: str, lineno: int) -> None: def __init__(self, name: str, lineno: int) -> None:
@ -370,9 +286,22 @@ class Function:
self.exported = False self.exported = False
self.imported = False self.imported = False
self.statements = [] self.statements = []
self.returns = TypeNone() self.returns_type3 = type3types.none # FIXME: This could be a placeholder
self.posonlyargs = [] self.posonlyargs = []
class StructDefinition:
"""
The definition for a struct
"""
__slots__ = ('struct_type3', 'lineno', )
struct_type3: StructType3
lineno: int
def __init__(self, struct_type3: StructType3, lineno: int) -> None:
self.struct_type3 = struct_type3
self.lineno = lineno
class StructConstructor(Function): class StructConstructor(Function):
""" """
The constructor method for a struct The constructor method for a struct
@ -380,56 +309,36 @@ class StructConstructor(Function):
A function will generated to instantiate a struct. The arguments A function will generated to instantiate a struct. The arguments
will be the defaults will be the defaults
""" """
__slots__ = ('struct', ) __slots__ = ('struct_type3', )
struct: TypeStruct struct_type3: StructType3
def __init__(self, struct: TypeStruct) -> None: def __init__(self, struct_type3: StructType3) -> None:
super().__init__(f'@{struct.name}@__init___@', -1) super().__init__(f'@{struct_type3.name}@__init___@', -1)
self.returns = struct self.returns_type3 = struct_type3
for mem in struct.members: for mem, typ in struct_type3.members.items():
self.posonlyargs.append((mem.name, mem.type, )) self.posonlyargs.append(FunctionParam(mem, typ, ))
self.struct = struct self.struct_type3 = struct_type3
class TupleConstructor(Function):
"""
The constructor method for a tuple
"""
__slots__ = ('tuple', )
tuple: TypeTuple
def __init__(self, tuple_: TypeTuple) -> None:
name = tuple_.render_internal_name()
super().__init__(f'@{name}@__init___@', -1)
self.returns = tuple_
for mem in tuple_.members:
self.posonlyargs.append((f'arg{mem.idx}', mem.type, ))
self.tuple = tuple_
class ModuleConstantDef: class ModuleConstantDef:
""" """
A constant definition within a module A constant definition within a module
""" """
__slots__ = ('name', 'lineno', 'type', 'constant', 'data_block', ) __slots__ = ('name', 'lineno', 'type3', 'constant', 'data_block', )
name: str name: str
lineno: int lineno: int
type: TypeBase type3: Type3
constant: Constant constant: Constant
data_block: Optional['ModuleDataBlock'] data_block: Optional['ModuleDataBlock']
def __init__(self, name: str, lineno: int, type_: TypeBase, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None: def __init__(self, name: str, lineno: int, type3: Type3, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
self.name = name self.name = name
self.lineno = lineno self.lineno = lineno
self.type = type_ self.type3 = type3
self.constant = constant self.constant = constant
self.data_block = data_block self.data_block = data_block
@ -439,10 +348,10 @@ class ModuleDataBlock:
""" """
__slots__ = ('data', 'address', ) __slots__ = ('data', 'address', )
data: List[Constant] data: List[ConstantPrimitive]
address: Optional[int] address: Optional[int]
def __init__(self, data: List[Constant]) -> None: def __init__(self, data: List[ConstantPrimitive]) -> None:
self.data = data self.data = data
self.address = None self.address = None
@ -461,27 +370,15 @@ class Module:
""" """
A module is a file and consists of functions A module is a file and consists of functions
""" """
__slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',) __slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions',)
data: ModuleData data: ModuleData
types: Dict[str, TypeBase] struct_definitions: Dict[str, StructDefinition]
structs: Dict[str, TypeStruct]
constant_defs: Dict[str, ModuleConstantDef] constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function] functions: Dict[str, Function]
def __init__(self) -> None: def __init__(self) -> None:
self.types = {
'None': TypeNone(),
'u8': TypeUInt8(),
'u32': TypeUInt32(),
'u64': TypeUInt64(),
'i32': TypeInt32(),
'i64': TypeInt64(),
'f32': TypeFloat32(),
'f64': TypeFloat64(),
'bytes': TypeBytes(),
}
self.data = ModuleData() self.data = ModuleData()
self.structs = {} self.struct_definitions = {}
self.constant_defs = {} self.constant_defs = {}
self.functions = {} self.functions = {}

View File

@ -5,49 +5,30 @@ from typing import Any, Dict, NoReturn, Union
import ast import ast
from .typing import ( from .type3 import types as type3types
TypeBase,
TypeUInt8,
TypeUInt32,
TypeUInt64,
TypeInt32,
TypeInt64,
TypeFloat32,
TypeFloat64,
TypeBytes,
TypeStruct,
TypeStructMember,
TypeTuple,
TypeTupleMember,
TypeStaticArray,
TypeStaticArrayMember,
)
from . import codestyle
from .exceptions import StaticError from .exceptions import StaticError
from .ourlang import ( from .ourlang import (
WEBASSEMBLY_BUILDIN_FLOAT_OPS, WEBASSEMBLY_BUILTIN_FLOAT_OPS,
Module, ModuleDataBlock, Module, ModuleDataBlock,
Function, Function,
Expression, Expression,
AccessBytesIndex, AccessStructMember, AccessTupleMember, AccessStaticArrayMember,
BinaryOp, BinaryOp,
Constant, ConstantPrimitive, ConstantTuple, ConstantStruct,
ConstantFloat32, ConstantFloat64, ConstantInt32, ConstantInt64, TupleInstantiation,
ConstantUInt8, ConstantUInt32, ConstantUInt64,
ConstantTuple, ConstantStaticArray,
FunctionCall, FunctionCall, AccessStructMember, Subscript,
StructConstructor, TupleConstructor, StructDefinition, StructConstructor,
UnaryOp, VariableReference, UnaryOp, VariableReference,
Fold, ModuleConstantReference, Fold,
Statement, Statement,
StatementIf, StatementPass, StatementReturn, StatementIf, StatementPass, StatementReturn,
FunctionParam,
ModuleConstantDef, ModuleConstantDef,
) )
@ -60,7 +41,7 @@ def phasm_parse(source: str) -> Module:
our_visitor = OurVisitor() our_visitor = OurVisitor()
return our_visitor.visit_Module(res) return our_visitor.visit_Module(res)
OurLocals = Dict[str, TypeBase] OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict?
class OurVisitor: class OurVisitor:
""" """
@ -95,14 +76,14 @@ class OurVisitor:
module.constant_defs[res.name] = res module.constant_defs[res.name] = res
if isinstance(res, TypeStruct): if isinstance(res, StructDefinition):
if res.name in module.structs: if res.struct_type3.name in module.struct_definitions:
raise StaticError( raise StaticError(
f'{res.name} already defined on line {module.structs[res.name].lineno}' f'{res.struct_type3.name} already defined on line {module.struct_definitions[res.struct_type3.name].lineno}'
) )
module.structs[res.name] = res module.struct_definitions[res.struct_type3.name] = res
constructor = StructConstructor(res) constructor = StructConstructor(res.struct_type3)
module.functions[constructor.name] = constructor module.functions[constructor.name] = constructor
if isinstance(res, Function): if isinstance(res, Function):
@ -120,7 +101,7 @@ class OurVisitor:
return module return module
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, TypeStruct, ModuleConstantDef]: def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
if isinstance(node, ast.FunctionDef): if isinstance(node, ast.FunctionDef):
return self.pre_visit_Module_FunctionDef(module, node) return self.pre_visit_Module_FunctionDef(module, node)
@ -138,12 +119,9 @@ class OurVisitor:
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs') _not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
for arg in node.args.args: for arg in node.args.args:
if not arg.annotation: function.posonlyargs.append(FunctionParam(
_raise_static_error(node, 'Type is required')
function.posonlyargs.append((
arg.arg, arg.arg,
self.visit_type(module, arg.annotation), self.visit_type(module, arg.annotation) if arg.annotation else None,
)) ))
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg') _not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
@ -166,21 +144,23 @@ class OurVisitor:
else: else:
function.imported = True function.imported = True
if node.returns: if node.returns is not None: # Note: `-> None` would be a ast.Constant
function.returns = self.visit_type(module, node.returns) function.returns_type3 = self.visit_type(module, node.returns)
else:
# FIXME: Mostly works already, needs to fix Function.returns_type3 and have it updated
raise NotImplementedError('Function without an explicit return type')
_not_implemented(not node.type_comment, 'FunctionDef.type_comment') _not_implemented(not node.type_comment, 'FunctionDef.type_comment')
return function return function
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> TypeStruct: def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition:
struct = TypeStruct(node.name, node.lineno)
_not_implemented(not node.bases, 'ClassDef.bases') _not_implemented(not node.bases, 'ClassDef.bases')
_not_implemented(not node.keywords, 'ClassDef.keywords') _not_implemented(not node.keywords, 'ClassDef.keywords')
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list') _not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
offset = 0 members: Dict[str, type3types.Type3] = {}
for stmt in node.body: for stmt in node.body:
if not isinstance(stmt, ast.AnnAssign): if not isinstance(stmt, ast.AnnAssign):
@ -195,47 +175,36 @@ class OurVisitor:
if stmt.simple != 1: if stmt.simple != 1:
raise NotImplementedError('Class with non-simple arguments') raise NotImplementedError('Class with non-simple arguments')
member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset) if stmt.target.id in members:
_raise_static_error(stmt, 'Struct members must have unique names')
struct.members.append(member) members[stmt.target.id] = self.visit_type(module, stmt.annotation)
offset += member.type.alloc_size()
return struct return StructDefinition(type3types.StructType3(node.name, members), node.lineno)
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name): if not isinstance(node.target, ast.Name):
_raise_static_error(node, 'Must be name') _raise_static_error(node.target, 'Must be name')
if not isinstance(node.target.ctx, ast.Store): if not isinstance(node.target.ctx, ast.Store):
_raise_static_error(node, 'Must be load context') _raise_static_error(node.target, 'Must be store context')
exp_type = self.visit_type(module, node.annotation) if isinstance(node.value, ast.Constant):
type3 = self.visit_type(module, node.annotation)
if isinstance(exp_type, TypeInt32): return ModuleConstantDef(
if not isinstance(node.value, ast.Constant):
_raise_static_error(node, 'Must be constant')
constant = ModuleConstantDef(
node.target.id, node.target.id,
node.lineno, node.lineno,
exp_type, type3,
self.visit_Module_Constant(module, exp_type, node.value), self.visit_Module_Constant(module, node.value),
None, None,
) )
return constant
if isinstance(exp_type, TypeTuple):
if not isinstance(node.value, ast.Tuple):
_raise_static_error(node, 'Must be tuple')
if len(exp_type.members) != len(node.value.elts):
_raise_static_error(node, 'Invalid number of tuple values')
if isinstance(node.value, ast.Tuple):
tuple_data = [ tuple_data = [
self.visit_Module_Constant(module, mem.type, arg_node) self.visit_Module_Constant(module, arg_node)
for arg_node, mem in zip(node.value.elts, exp_type.members) for arg_node in node.value.elts
if isinstance(arg_node, ast.Constant) if isinstance(arg_node, ast.Constant)
] ]
if len(exp_type.members) != len(tuple_data): if len(node.value.elts) != len(tuple_data):
_raise_static_error(node, 'Tuple arguments must be constants') _raise_static_error(node, 'Tuple arguments must be constants')
# Allocate the data # Allocate the data
@ -246,36 +215,47 @@ class OurVisitor:
return ModuleConstantDef( return ModuleConstantDef(
node.target.id, node.target.id,
node.lineno, node.lineno,
exp_type, self.visit_type(module, node.annotation),
ConstantTuple(exp_type, tuple_data), ConstantTuple(tuple_data),
data_block, data_block,
) )
if isinstance(exp_type, TypeStaticArray): if isinstance(node.value, ast.Call):
if not isinstance(node.value, ast.Tuple): # Struct constant
_raise_static_error(node, 'Must be static array') # Stored in memory like a tuple, so much of the code is the same
if len(exp_type.members) != len(node.value.elts): if not isinstance(node.value.func, ast.Name):
_raise_static_error(node, 'Invalid number of static array values') _raise_static_error(node.value.func, 'Must be name')
if not isinstance(node.value.func.ctx, ast.Load):
_raise_static_error(node.value.func, 'Must be load context')
static_array_data = [ if not node.value.func.id in module.struct_definitions:
self.visit_Module_Constant(module, exp_type.member_type, arg_node) _raise_static_error(node.value.func, 'Undefined struct')
for arg_node in node.value.elts
if node.value.keywords:
_raise_static_error(node.value.func, 'Cannot use keywords')
if not isinstance(node.annotation, ast.Name):
_raise_static_error(node.annotation, 'Must be name')
struct_data = [
self.visit_Module_Constant(module, arg_node)
for arg_node in node.value.args
if isinstance(arg_node, ast.Constant) if isinstance(arg_node, ast.Constant)
] ]
if len(exp_type.members) != len(static_array_data): if len(node.value.args) != len(struct_data):
_raise_static_error(node, 'Static array arguments must be constants') _raise_static_error(node, 'Struct arguments must be constants')
# Allocate the data # Allocate the data
data_block = ModuleDataBlock(static_array_data) data_block = ModuleDataBlock(struct_data)
module.data.blocks.append(data_block) module.data.blocks.append(data_block)
# Then return the constant as a pointer # Then return the constant as a pointer
return ModuleConstantDef( return ModuleConstantDef(
node.target.id, node.target.id,
node.lineno, node.lineno,
exp_type, self.visit_type(module, node.annotation),
ConstantStaticArray(exp_type, static_array_data), ConstantStruct(node.value.func.id, struct_data),
data_block, data_block,
) )
@ -297,7 +277,10 @@ class OurVisitor:
def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None: def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None:
function = module.functions[node.name] function = module.functions[node.name]
our_locals = dict(function.posonlyargs) our_locals: OurLocals = {
x.name: x
for x in function.posonlyargs
}
for stmt in node.body: for stmt in node.body:
function.statements.append( function.statements.append(
@ -311,12 +294,12 @@ class OurVisitor:
_raise_static_error(node, 'Return must have an argument') _raise_static_error(node, 'Return must have an argument')
return StatementReturn( return StatementReturn(
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.value) self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value)
) )
if isinstance(node, ast.If): if isinstance(node, ast.If):
result = StatementIf( result = StatementIf(
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.test) self.visit_Module_FunctionDef_expr(module, function, our_locals, node.test)
) )
for stmt in node.body: for stmt in node.body:
@ -336,7 +319,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as stmt in FunctionDef') raise NotImplementedError(f'{node} as stmt in FunctionDef')
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.expr) -> Expression: def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp): if isinstance(node, ast.BinOp):
if isinstance(node.op, ast.Add): if isinstance(node.op, ast.Add):
operator = '+' operator = '+'
@ -344,6 +327,8 @@ class OurVisitor:
operator = '-' operator = '-'
elif isinstance(node.op, ast.Mult): elif isinstance(node.op, ast.Mult):
operator = '*' operator = '*'
elif isinstance(node.op, ast.Div):
operator = '/'
elif isinstance(node.op, ast.LShift): elif isinstance(node.op, ast.LShift):
operator = '<<' operator = '<<'
elif isinstance(node.op, ast.RShift): elif isinstance(node.op, ast.RShift):
@ -357,14 +342,10 @@ class OurVisitor:
else: else:
raise NotImplementedError(f'Operator {node.op}') raise NotImplementedError(f'Operator {node.op}')
# Assume the type doesn't change when descending into a binary operator
# e.g. you can do `"hello" * 3` with the code below (yet)
return BinaryOp( return BinaryOp(
exp_type,
operator, operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.right), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
) )
if isinstance(node, ast.UnaryOp): if isinstance(node, ast.UnaryOp):
@ -376,9 +357,8 @@ class OurVisitor:
raise NotImplementedError(f'Operator {node.op}') raise NotImplementedError(f'Operator {node.op}')
return UnaryOp( return UnaryOp(
exp_type,
operator, operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.operand), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.operand),
) )
if isinstance(node, ast.Compare): if isinstance(node, ast.Compare):
@ -394,32 +374,28 @@ class OurVisitor:
else: else:
raise NotImplementedError(f'Operator {node.ops}') raise NotImplementedError(f'Operator {node.ops}')
# Assume the type doesn't change when descending into a binary operator
# e.g. you can do `"hello" * 3` with the code below (yet)
return BinaryOp( return BinaryOp(
exp_type,
operator, operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.comparators[0]), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
) )
if isinstance(node, ast.Call): if isinstance(node, ast.Call):
return self.visit_Module_FunctionDef_Call(module, function, our_locals, exp_type, node) return self.visit_Module_FunctionDef_Call(module, function, our_locals, node)
if isinstance(node, ast.Constant): if isinstance(node, ast.Constant):
return self.visit_Module_Constant( return self.visit_Module_Constant(
module, exp_type, node, module, node,
) )
if isinstance(node, ast.Attribute): if isinstance(node, ast.Attribute):
return self.visit_Module_FunctionDef_Attribute( return self.visit_Module_FunctionDef_Attribute(
module, function, our_locals, exp_type, node, module, function, our_locals, node,
) )
if isinstance(node, ast.Subscript): if isinstance(node, ast.Subscript):
return self.visit_Module_FunctionDef_Subscript( return self.visit_Module_FunctionDef_Subscript(
module, function, our_locals, exp_type, node, module, function, our_locals, node,
) )
if isinstance(node, ast.Name): if isinstance(node, ast.Name):
@ -427,45 +403,30 @@ class OurVisitor:
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if node.id in our_locals: if node.id in our_locals:
act_type = our_locals[node.id] param = our_locals[node.id]
if exp_type != act_type: return VariableReference(param)
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.id} is actually {codestyle.type_(act_type)}')
return VariableReference(act_type, node.id)
if node.id in module.constant_defs: if node.id in module.constant_defs:
cdef = module.constant_defs[node.id] cdef = module.constant_defs[node.id]
if exp_type != cdef.type: return VariableReference(cdef)
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.id} is actually {codestyle.type_(cdef.type)}')
return ModuleConstantReference(exp_type, cdef)
_raise_static_error(node, f'Undefined variable {node.id}') _raise_static_error(node, f'Undefined variable {node.id}')
if isinstance(node, ast.Tuple): if isinstance(node, ast.Tuple):
if not isinstance(node.ctx, ast.Load): arguments = [
_raise_static_error(node, 'Must be load context') self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_node)
for arg_node in node.elts
if isinstance(arg_node, ast.Constant)
]
if isinstance(exp_type, TypeTuple): if len(arguments) != len(node.elts):
if len(exp_type.members) != len(node.elts): raise NotImplementedError('Non-constant tuple members')
_raise_static_error(node, f'Expression is expecting a tuple of size {len(exp_type.members)}, but {len(node.elts)} are given')
tuple_constructor = TupleConstructor(exp_type) return TupleInstantiation(arguments)
func = module.functions[tuple_constructor.name]
result = FunctionCall(func)
result.arguments = [
self.visit_Module_FunctionDef_expr(module, function, our_locals, mem.type, arg_node)
for arg_node, mem in zip(node.elts, exp_type.members)
]
return result
_raise_static_error(node, f'Expression is expecting a {codestyle.type_(exp_type)}, not a tuple')
raise NotImplementedError(f'{node} as expr in FunctionDef') raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]: def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
if node.keywords: if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet? _raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -474,59 +435,42 @@ class OurVisitor:
if not isinstance(node.func.ctx, ast.Load): if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if node.func.id in module.structs: if node.func.id in module.struct_definitions:
struct = module.structs[node.func.id] struct_definition = module.struct_definitions[node.func.id]
struct_constructor = StructConstructor(struct) struct_constructor = StructConstructor(struct_definition.struct_type3)
# FIXME: Defer struct de-allocation
func = module.functions[struct_constructor.name] func = module.functions[struct_constructor.name]
elif node.func.id in WEBASSEMBLY_BUILDIN_FLOAT_OPS: elif node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS:
if not isinstance(exp_type, (TypeFloat32, TypeFloat64, )):
_raise_static_error(node, f'Cannot make {node.func.id} result in {codestyle.type_(exp_type)}')
if 1 != len(node.args): if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given') _raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
return UnaryOp( return UnaryOp(
exp_type,
'sqrt', 'sqrt',
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.args[0]), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
) )
elif node.func.id == 'u32': elif node.func.id == 'u32':
if not isinstance(exp_type, TypeUInt32):
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
if 1 != len(node.args): if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given') _raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
# FIXME: This is a stub, proper casting is todo
return UnaryOp( return UnaryOp(
exp_type,
'cast', 'cast',
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['u8'], node.args[0]), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
) )
elif node.func.id == 'len': elif node.func.id == 'len':
if not isinstance(exp_type, TypeInt32):
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
if 1 != len(node.args): if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given') _raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
return UnaryOp( return UnaryOp(
exp_type,
'len', 'len',
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['bytes'], node.args[0]), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
) )
elif node.func.id == 'foldl': elif node.func.id == 'foldl':
# TODO: This should a much more generic function!
# For development purposes, we're assuming you're doing a foldl(Callable[[u8, u8], u8], u8, bytes)
# In the future, we should probably infer the type of the second argument,
# and use it as expected types for the other u8s and the Iterable[u8] (i.e. bytes)
if 3 != len(node.args): if 3 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given') _raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given')
# TODO: This is not generic # TODO: This is not generic, you cannot return a function
subnode = node.args[0] subnode = node.args[0]
if not isinstance(subnode, ast.Name): if not isinstance(subnode, ast.Name):
raise NotImplementedError(f'Calling methods that are not a name {subnode}') raise NotImplementedError(f'Calling methods that are not a name {subnode}')
@ -538,21 +482,11 @@ class OurVisitor:
if 2 != len(func.posonlyargs): if 2 != len(func.posonlyargs):
_raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given') _raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given')
if exp_type.__class__ != func.returns.__class__:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {func.name} actually returns {codestyle.type_(func.returns)}')
if func.returns.__class__ != func.posonlyargs[0][1].__class__:
_raise_static_error(node, f'Expected a foldable function, {func.name} returns a {codestyle.type_(func.returns)} but expects a {codestyle.type_(func.posonlyargs[0][1])}')
if module.types['u8'].__class__ != func.posonlyargs[1][1].__class__:
_raise_static_error(node, 'Only folding over bytes (u8) is supported at this time')
return Fold( return Fold(
exp_type,
Fold.Direction.LEFT, Fold.Direction.LEFT,
func, func,
self.visit_Module_FunctionDef_expr(module, function, our_locals, func.returns, node.args[1]), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[1]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['bytes'], node.args[2]), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[2]),
) )
else: else:
if node.func.id not in module.functions: if node.func.id not in module.functions:
@ -560,206 +494,76 @@ class OurVisitor:
func = module.functions[node.func.id] func = module.functions[node.func.id]
if func.returns != exp_type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {func.name} actually returns {codestyle.type_(func.returns)}')
if len(func.posonlyargs) != len(node.args): if len(func.posonlyargs) != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given') _raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given')
result = FunctionCall(func) result = FunctionCall(func)
result.arguments.extend( result.arguments.extend(
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_type, arg_expr) self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr)
for arg_expr, (_, arg_type) in zip(node.args, func.posonlyargs) for arg_expr, param in zip(node.args, func.posonlyargs)
) )
return result return result
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Attribute) -> Expression: def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
del module
del function
if not isinstance(node.value, ast.Name): if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name') _raise_static_error(node, 'Must reference a name')
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if not node.value.id in our_locals: varref = self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value)
_raise_static_error(node, f'Undefined variable {node.value.id}') if not isinstance(varref, VariableReference):
_raise_static_error(node.value, 'Must refer to variable')
node_typ = our_locals[node.value.id] if not isinstance(varref.variable.type3, type3types.StructType3):
if not isinstance(node_typ, TypeStruct): _raise_static_error(node.value, 'Must refer to struct')
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
member = node_typ.get_member(node.attr)
if member is None:
_raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
if exp_type != member.type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}.{member.name} is actually {codestyle.type_(member.type)}')
return AccessStructMember( return AccessStructMember(
VariableReference(node_typ, node.value.id), varref,
member, varref.variable.type3,
node.attr,
) )
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Subscript) -> Expression: def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
if not isinstance(node.value, ast.Name): if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name') _raise_static_error(node, 'Must reference a name')
if not isinstance(node.slice, ast.Index): 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.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')
varref: Union[ModuleConstantReference, VariableReference] varref: VariableReference
if node.value.id in our_locals: if node.value.id in our_locals:
node_typ = our_locals[node.value.id] param = our_locals[node.value.id]
varref = VariableReference(node_typ, node.value.id) varref = VariableReference(param)
elif node.value.id in module.constant_defs: elif node.value.id in module.constant_defs:
constant_def = module.constant_defs[node.value.id] constant_def = module.constant_defs[node.value.id]
node_typ = constant_def.type varref = VariableReference(constant_def)
varref = ModuleConstantReference(node_typ, constant_def)
else: else:
_raise_static_error(node, f'Undefined variable {node.value.id}') _raise_static_error(node, f'Undefined variable {node.value.id}')
slice_expr = self.visit_Module_FunctionDef_expr( slice_expr = self.visit_Module_FunctionDef_expr(
module, function, our_locals, module.types['u32'], node.slice.value, module, function, our_locals, node.slice,
) )
if isinstance(node_typ, TypeBytes): return Subscript(varref, slice_expr)
t_u8 = module.types['u8']
if exp_type != t_u8:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{codestyle.expression(slice_expr)}] is actually {codestyle.type_(t_u8)}')
if isinstance(varref, ModuleConstantReference): def visit_Module_Constant(self, module: Module, node: ast.Constant) -> ConstantPrimitive:
raise NotImplementedError(f'{node} from module constant')
return AccessBytesIndex(
t_u8,
varref,
slice_expr,
)
if isinstance(node_typ, TypeTuple):
if not isinstance(slice_expr, ConstantUInt32):
_raise_static_error(node, 'Must subscript using a constant index')
idx = slice_expr.value
if len(node_typ.members) <= idx:
_raise_static_error(node, f'Index {idx} out of bounds for tuple {node.value.id}')
tuple_member = node_typ.members[idx]
if exp_type != tuple_member.type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{idx}] is actually {codestyle.type_(tuple_member.type)}')
if isinstance(varref, ModuleConstantReference):
raise NotImplementedError(f'{node} from module constant')
return AccessTupleMember(
varref,
tuple_member,
)
if isinstance(node_typ, TypeStaticArray):
if exp_type != node_typ.member_type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{idx}] is actually {codestyle.type_(node_typ.member_type)}')
if not isinstance(slice_expr, ConstantInt32):
return AccessStaticArrayMember(
varref,
node_typ,
slice_expr,
)
idx = slice_expr.value
if len(node_typ.members) <= idx:
_raise_static_error(node, f'Index {idx} out of bounds for static array {node.value.id}')
static_array_member = node_typ.members[idx]
return AccessStaticArrayMember(
varref,
node_typ,
static_array_member,
)
_raise_static_error(node, f'Cannot take index of {node_typ} {node.value.id}')
def visit_Module_Constant(self, module: Module, exp_type: TypeBase, node: ast.Constant) -> Constant:
del module del module
_not_implemented(node.kind is None, 'Constant.kind') _not_implemented(node.kind is None, 'Constant.kind')
if isinstance(exp_type, TypeUInt8): if isinstance(node.value, (int, float, )):
if not isinstance(node.value, int): return ConstantPrimitive(node.value)
_raise_static_error(node, 'Expected integer value')
if node.value < 0 or node.value > 255: raise NotImplementedError(f'{node.value} as constant')
_raise_static_error(node, f'Integer value out of range; expected 0..255, actual {node.value}')
return ConstantUInt8(exp_type, node.value) def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3:
if isinstance(exp_type, TypeUInt32):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if node.value < 0 or node.value > 4294967295:
_raise_static_error(node, 'Integer value out of range')
return ConstantUInt32(exp_type, node.value)
if isinstance(exp_type, TypeUInt64):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if node.value < 0 or node.value > 18446744073709551615:
_raise_static_error(node, 'Integer value out of range')
return ConstantUInt64(exp_type, node.value)
if isinstance(exp_type, TypeInt32):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if node.value < -2147483648 or node.value > 2147483647:
_raise_static_error(node, 'Integer value out of range')
return ConstantInt32(exp_type, node.value)
if isinstance(exp_type, TypeInt64):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if node.value < -9223372036854775808 or node.value > 9223372036854775807:
_raise_static_error(node, 'Integer value out of range')
return ConstantInt64(exp_type, node.value)
if isinstance(exp_type, TypeFloat32):
if not isinstance(node.value, (float, int, )):
_raise_static_error(node, 'Expected float value')
# FIXME: Range check
return ConstantFloat32(exp_type, node.value)
if isinstance(exp_type, TypeFloat64):
if not isinstance(node.value, (float, int, )):
_raise_static_error(node, 'Expected float value')
# FIXME: Range check
return ConstantFloat64(exp_type, node.value)
raise NotImplementedError(f'{node} as const for type {exp_type}')
def visit_type(self, module: Module, node: ast.expr) -> TypeBase:
if isinstance(node, ast.Constant): if isinstance(node, ast.Constant):
if node.value is None: if node.value is None:
return module.types['None'] return type3types.none
_raise_static_error(node, f'Unrecognized type {node.value}') _raise_static_error(node, f'Unrecognized type {node.value}')
@ -767,70 +571,42 @@ 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.id in module.types: if node.id in type3types.LOOKUP_TABLE:
return module.types[node.id] return type3types.LOOKUP_TABLE[node.id]
if node.id in module.structs: if node.id in module.struct_definitions:
return module.structs[node.id] return module.struct_definitions[node.id].struct_type3
_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): if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must be name') _raise_static_error(node, 'Must be name')
if not isinstance(node.slice, ast.Index): 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.value, ast.Constant): if not isinstance(node.slice, ast.Constant):
_raise_static_error(node, 'Must subscript using a constant index') _raise_static_error(node, 'Must subscript using a constant index')
if not isinstance(node.slice.value.value, int): if not isinstance(node.slice.value, int):
_raise_static_error(node, 'Must subscript using a constant integer index') _raise_static_error(node, 'Must subscript using a constant integer index')
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 in module.types: if node.value.id not in type3types.LOOKUP_TABLE: # FIXME: Tuple of tuples?
member_type = module.types[node.value.id]
else:
_raise_static_error(node, f'Unrecognized type {node.value.id}') _raise_static_error(node, f'Unrecognized type {node.value.id}')
type_static_array = TypeStaticArray(member_type) return type3types.AppliedType3(
type3types.static_array,
offset = 0 [self.visit_type(module, node.value), type3types.IntType3(node.slice.value)],
)
for idx in range(node.slice.value.value):
static_array_member = TypeStaticArrayMember(idx, offset)
type_static_array.members.append(static_array_member)
offset += member_type.alloc_size()
key = f'{node.value.id}[{node.slice.value.value}]'
if key not in module.types:
module.types[key] = type_static_array
return module.types[key]
if isinstance(node, ast.Tuple): if isinstance(node, ast.Tuple):
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')
type_tuple = TypeTuple() return type3types.AppliedType3(
type3types.tuple,
offset = 0 (self.visit_type(module, elt) for elt in node.elts)
)
for idx, elt in enumerate(node.elts):
tuple_member = TypeTupleMember(idx, self.visit_type(module, elt), offset)
type_tuple.members.append(tuple_member)
offset += tuple_member.type.alloc_size()
key = type_tuple.render_internal_name()
if key not in module.types:
module.types[key] = type_tuple
constructor = TupleConstructor(type_tuple)
module.functions[constructor.name] = constructor
return module.types[key]
raise NotImplementedError(f'{node} as type') raise NotImplementedError(f'{node} as type')

View File

@ -26,7 +26,7 @@ def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
g.i32.const(0) g.i32.const(0)
g.return_() g.return_()
del alloc_size # TODO del alloc_size # TODO: Actual implement using a previously freed block
g.unreachable() g.unreachable()
return i32('return') # To satisfy mypy return i32('return') # To satisfy mypy

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

549
phasm/type3/constraints.py Normal file
View File

@ -0,0 +1,549 @@
"""
This module contains possible constraints generated based on the AST
These need to be resolved before the program can be compiled.
"""
from typing import Dict, Optional, List, Tuple, Union
from .. import ourlang
from . import types
class Error:
"""
An error returned by the check functions for a contraint
This means the programmer has to make some kind of chance to the
typing of their program before the compiler can do its thing.
"""
def __init__(self, msg: str) -> None:
self.msg = msg
def __repr__(self) -> str:
return f'Error({repr(self.msg)})'
class RequireTypeSubstitutes:
"""
Returned by the check function for a contraint if they do not have all
their types substituted yet.
Hopefully, another constraint will give the right information about the
typing of the program, so this constraint can be updated.
"""
SubstitutionMap = Dict[types.PlaceholderForType, types.Type3]
NewConstraintList = List['ConstraintBase']
CheckResult = Union[None, SubstitutionMap, Error, NewConstraintList, RequireTypeSubstitutes]
HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]]
class Context:
"""
Context for constraints
"""
__slots__ = ()
class ConstraintBase:
"""
Base class for constraints
"""
__slots__ = ('comment', )
comment: Optional[str]
"""
A comment to help the programmer with debugging the types in their program
"""
def __init__(self, comment: Optional[str] = None) -> None:
self.comment = comment
def check(self) -> CheckResult:
"""
Checks if the constraint hold
This function can return an error, if the constraint does not hold,
which indicates an error in the typing of the input program.
This function can return RequireTypeSubstitutes(), if we cannot deduce
all the types yet.
This function can return a SubstitutionMap, if during the evaluation
of the contraint we discovered new types. In this case, the constraint
is expected to hold.
This function can return None, if the constraint holds, but no new
information was deduced from evaluating this constraint.
"""
raise NotImplementedError(self.__class__, self.check)
def human_readable(self) -> HumanReadableRet:
"""
Returns a more human readable form of this constraint
"""
return repr(self), {}
class SameTypeConstraint(ConstraintBase):
"""
Verifies that a number of types all are the same type
"""
__slots__ = ('type_list', )
type_list: List[types.Type3OrPlaceholder]
def __init__(self, *type_list: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
assert len(type_list) > 1
self.type_list = [*type_list]
def check(self) -> CheckResult:
known_types: List[types.Type3] = []
placeholders = []
do_applied_placeholder_check: bool = False
for typ in self.type_list:
if isinstance(typ, types.IntType3):
known_types.append(typ)
continue
if isinstance(typ, (types.PrimitiveType3, types.StructType3, )):
known_types.append(typ)
continue
if isinstance(typ, types.AppliedType3):
known_types.append(typ)
do_applied_placeholder_check = True
continue
if isinstance(typ, types.PlaceholderForType):
if typ.resolve_as is not None:
known_types.append(typ.resolve_as)
else:
placeholders.append(typ)
continue
raise NotImplementedError(typ)
if not known_types:
return RequireTypeSubstitutes()
new_constraint_list: List[ConstraintBase] = []
first_type = known_types[0]
for typ in known_types[1:]:
if isinstance(first_type, types.AppliedType3) and isinstance(typ, types.AppliedType3):
if len(first_type.args) != len(typ.args):
return Error('Mismatch between applied types argument count')
if first_type.base != typ.base:
return Error('Mismatch between applied types base')
for first_type_arg, typ_arg in zip(first_type.args, typ.args):
new_constraint_list.append(SameTypeConstraint(
first_type_arg, typ_arg
))
continue
if typ != first_type:
return Error(f'{typ:s} must be {first_type:s} instead')
if new_constraint_list:
# If this happens, make CheckResult a class that can have both
assert not placeholders, 'Cannot (yet) return both new placeholders and new constraints'
return new_constraint_list
if not placeholders:
return None
for typ in placeholders:
typ.resolve_as = first_type
return {
typ: first_type
for typ in placeholders
}
def human_readable(self) -> HumanReadableRet:
return (
' == '.join('{t' + str(idx) + '}' for idx in range(len(self.type_list))),
{
't' + str(idx): typ
for idx, typ in enumerate(self.type_list)
},
)
def __repr__(self) -> str:
args = ', '.join(repr(x) for x in self.type_list)
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
class IntegerCompareConstraint(ConstraintBase):
"""
Verifies that the given IntType3 are in order (<=)
"""
__slots__ = ('int_type3_list', )
int_type3_list: List[types.IntType3]
def __init__(self, *int_type3: types.IntType3, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
assert len(int_type3) > 1
self.int_type3_list = [*int_type3]
def check(self) -> CheckResult:
val_list = [x.value for x in self.int_type3_list]
prev_val = val_list.pop(0)
for next_val in val_list:
if prev_val > next_val:
return Error(f'{prev_val} must be less or equal than {next_val}')
prev_val = next_val
return None
def human_readable(self) -> HumanReadableRet:
return (
' <= '.join('{t' + str(idx) + '}' for idx in range(len(self.int_type3_list))),
{
't' + str(idx): typ
for idx, typ in enumerate(self.int_type3_list)
},
)
def __repr__(self) -> str:
args = ', '.join(repr(x) for x in self.int_type3_list)
return f'IntegerCompareConstraint({args}, comment={repr(self.comment)})'
class CastableConstraint(ConstraintBase):
"""
A type can be cast to another type
"""
__slots__ = ('from_type3', 'to_type3', )
from_type3: types.Type3OrPlaceholder
to_type3: types.Type3OrPlaceholder
def __init__(self, from_type3: types.Type3OrPlaceholder, to_type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
self.from_type3 = from_type3
self.to_type3 = to_type3
def check(self) -> CheckResult:
ftyp = self.from_type3
if isinstance(ftyp, types.PlaceholderForType) and ftyp.resolve_as is not None:
ftyp = ftyp.resolve_as
ttyp = self.to_type3
if isinstance(ttyp, types.PlaceholderForType) and ttyp.resolve_as is not None:
ttyp = ttyp.resolve_as
if isinstance(ftyp, types.PlaceholderForType) or isinstance(ttyp, types.PlaceholderForType):
return RequireTypeSubstitutes()
if ftyp is types.u8 and ttyp is types.u32:
return None
return Error(f'Cannot cast {ftyp.name} to {ttyp.name}')
def human_readable(self) -> HumanReadableRet:
return (
'{to_type3}({from_type3})',
{
'to_type3': self.to_type3,
'from_type3': self.from_type3,
},
)
def __repr__(self) -> str:
return f'CastableConstraint({repr(self.from_type3)}, {repr(self.to_type3)}, comment={repr(self.comment)})'
class MustImplementTypeClassConstraint(ConstraintBase):
"""
A type must implement a given type class
"""
__slots__ = ('type_class3', 'type3', )
type_class3: str
type3: types.Type3OrPlaceholder
DATA = {
'u8': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
'u32': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
'u64': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
'i32': {'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
'i64': {'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
'bytes': {'Foldable', 'Sized'},
'f32': {'BasicMathOperation', 'FloatingPoint'},
'f64': {'BasicMathOperation', 'FloatingPoint'},
}
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
self.type_class3 = type_class3
self.type3 = type3
def check(self) -> CheckResult:
typ = self.type3
if isinstance(typ, types.PlaceholderForType) and typ.resolve_as is not None:
typ = typ.resolve_as
if isinstance(typ, types.PlaceholderForType):
return RequireTypeSubstitutes()
if self.type_class3 in self.__class__.DATA.get(typ.name, set()):
return None
return Error(f'{typ.name} does not implement the {self.type_class3} type class')
def human_readable(self) -> HumanReadableRet:
return (
'{type3} derives {type_class3}',
{
'type_class3': self.type_class3,
'type3': self.type3,
},
)
def __repr__(self) -> str:
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)}, comment={repr(self.comment)})'
class LiteralFitsConstraint(ConstraintBase):
"""
A literal value fits a given type
"""
__slots__ = ('type3', 'literal', )
type3: types.Type3OrPlaceholder
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple, ourlang.ConstantStruct]
def __init__(
self,
type3: types.Type3OrPlaceholder,
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple, ourlang.ConstantStruct],
comment: Optional[str] = None,
) -> None:
super().__init__(comment=comment)
self.type3 = type3
self.literal = literal
def check(self) -> CheckResult:
int_table: Dict[str, Tuple[int, bool]] = {
'u8': (1, False),
'u32': (4, False),
'u64': (8, False),
'i8': (1, True),
'i32': (4, True),
'i64': (8, True),
}
float_table: Dict[str, None] = {
'f32': None,
'f64': None,
}
if isinstance(self.type3, types.PlaceholderForType):
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
self.type3 = self.type3.resolve_as
if self.type3.name in int_table:
bts, sgn = int_table[self.type3.name]
if isinstance(self.literal.value, int):
try:
self.literal.value.to_bytes(bts, 'big', signed=sgn)
except OverflowError:
return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information
return None
return Error('Must be integer') # FIXME: Add line information
if self.type3.name in float_table:
_ = float_table[self.type3.name]
if isinstance(self.literal.value, float):
# FIXME: Bit check
return None
return Error('Must be real') # FIXME: Add line information
res: NewConstraintList
if isinstance(self.type3, types.AppliedType3):
if self.type3.base == types.tuple:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple')
if len(self.type3.args) != len(self.literal.value):
return Error('Tuple element count mismatch')
res = []
res.extend(
LiteralFitsConstraint(x, y)
for x, y in zip(self.type3.args, self.literal.value)
)
res.extend(
SameTypeConstraint(x, y.type3)
for x, y in zip(self.type3.args, self.literal.value)
)
return res
if self.type3.base == types.static_array:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple')
assert 2 == len(self.type3.args)
assert isinstance(self.type3.args[1], types.IntType3)
if self.type3.args[1].value != len(self.literal.value):
return Error('Member count mismatch')
res = []
res.extend(
LiteralFitsConstraint(self.type3.args[0], y)
for y in self.literal.value
)
res.extend(
SameTypeConstraint(self.type3.args[0], y.type3)
for y in self.literal.value
)
return res
if isinstance(self.type3, types.StructType3):
if not isinstance(self.literal, ourlang.ConstantStruct):
return Error('Must be struct')
if self.literal.struct_name != self.type3.name:
return Error('Struct mismatch')
if len(self.type3.members) != len(self.literal.value):
return Error('Struct element count mismatch')
res = []
res.extend(
LiteralFitsConstraint(x, y)
for x, y in zip(self.type3.members.values(), self.literal.value)
)
res.extend(
SameTypeConstraint(x_t, y.type3, comment=f'{self.literal.struct_name}.{x_n}')
for (x_n, x_t, ), y in zip(self.type3.members.items(), self.literal.value)
)
return res
raise NotImplementedError(self.type3, self.literal)
def human_readable(self) -> HumanReadableRet:
return (
'{literal} : {type3}',
{
'literal': self.literal,
'type3': self.type3,
},
)
def __repr__(self) -> str:
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)}, comment={repr(self.comment)})'
class CanBeSubscriptedConstraint(ConstraintBase):
"""
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
"""
__slots__ = ('ret_type3', 'type3', 'index', 'index_type3', )
ret_type3: types.Type3OrPlaceholder
type3: types.Type3OrPlaceholder
index: ourlang.Expression
index_type3: types.Type3OrPlaceholder
def __init__(self, ret_type3: types.Type3OrPlaceholder, type3: types.Type3OrPlaceholder, index: ourlang.Expression, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
self.ret_type3 = ret_type3
self.type3 = type3
self.index = index
self.index_type3 = index.type3
def check(self) -> CheckResult:
if isinstance(self.type3, types.PlaceholderForType):
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
self.type3 = self.type3.resolve_as
if isinstance(self.type3, types.AppliedType3):
if self.type3.base == types.static_array:
result: List[ConstraintBase] = [
SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(self.type3.args[0], self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
]
if isinstance(self.index, ourlang.ConstantPrimitive):
assert isinstance(self.index.value, int)
assert isinstance(self.type3.args[1], types.IntType3)
result.append(
IntegerCompareConstraint(
types.IntType3(0), types.IntType3(self.index.value), types.IntType3(self.type3.args[1].value - 1),
comment='Subscript static array must fit the size of the array'
)
)
return result
if self.type3.base == types.tuple:
if not isinstance(self.index, ourlang.ConstantPrimitive):
return Error('Must index with literal')
if not isinstance(self.index.value, int):
return Error('Must index with integer literal')
if self.index.value < 0 or len(self.type3.args) <= self.index.value:
return Error('Tuple index out of range')
return [
SameTypeConstraint(types.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'),
SameTypeConstraint(self.type3.args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'),
]
if self.type3 is types.bytes:
return [
SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'),
SameTypeConstraint(types.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
]
if self.type3.name in types.LOOKUP_TABLE:
return Error(f'{self.type3.name} cannot be subscripted')
raise NotImplementedError(self.type3)
def human_readable(self) -> HumanReadableRet:
return (
'{type3}[{index}]',
{
'type3': self.type3,
'index': self.index,
},
)
def __repr__(self) -> str:
return f'CanBeSubscriptedConstraint({repr(self.type3)}, {repr(self.index)}, comment={repr(self.comment)})'

View File

@ -0,0 +1,219 @@
"""
This module generates the typing constraints for Phasm.
The constraints solver can then try to resolve all constraints.
"""
from typing import Generator, List
from .. import ourlang
from .constraints import (
Context,
ConstraintBase,
CastableConstraint, CanBeSubscriptedConstraint,
LiteralFitsConstraint, MustImplementTypeClassConstraint, SameTypeConstraint,
)
from . import types as type3types
ConstraintGenerator = Generator[ConstraintBase, None, None]
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
ctx = Context()
return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantTuple, ourlang.ConstantStruct)):
yield LiteralFitsConstraint(inp.type3, inp)
return
raise NotImplementedError(constant, inp)
def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
if isinstance(inp, ourlang.Constant):
yield from constant(ctx, inp)
return
if isinstance(inp, ourlang.VariableReference):
yield SameTypeConstraint(inp.variable.type3, inp.type3,
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
return
if isinstance(inp, ourlang.UnaryOp):
if 'len' == inp.operator:
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('Sized', inp.right.type3)
yield SameTypeConstraint(type3types.u32, inp.type3, comment='len :: Sized a => a -> u32')
return
if 'sqrt' == inp.operator:
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('FloatingPoint', inp.right.type3)
yield SameTypeConstraint(inp.right.type3, inp.type3, comment='sqrt :: FloatingPoint a => a -> a')
return
if 'cast' == inp.operator:
yield from expression(ctx, inp.right)
yield CastableConstraint(inp.right.type3, inp.type3)
return
raise NotImplementedError(expression, inp, inp.operator)
if isinstance(inp, ourlang.BinaryOp):
if inp.operator in ('|', '&', '^', ):
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('BitWiseOperation', inp.left.type3)
yield SameTypeConstraint(inp.left.type3, inp.right.type3, inp.type3,
comment=f'({inp.operator}) :: a -> a -> a')
return
if inp.operator in ('>>', '<<', ):
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('BitWiseOperation', inp.left.type3)
yield SameTypeConstraint(inp.left.type3, inp.right.type3, inp.type3,
comment=f'({inp.operator}) :: a -> a -> a')
return
if inp.operator in ('+', '-', '*', '/', ):
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('BasicMathOperation', inp.left.type3)
yield SameTypeConstraint(inp.left.type3, inp.right.type3, inp.type3,
comment=f'({inp.operator}) :: a -> a -> a')
return
if inp.operator == '==':
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('EqualComparison', inp.left.type3)
yield SameTypeConstraint(inp.left.type3, inp.right.type3,
comment=f'({inp.operator}) :: a -> a -> bool')
yield SameTypeConstraint(inp.type3, type3types.bool_,
comment=f'({inp.operator}) :: a -> a -> bool')
return
if inp.operator in ('<', '>'):
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('StrictPartialOrder', inp.left.type3)
yield SameTypeConstraint(inp.left.type3, inp.right.type3,
comment=f'({inp.operator}) :: a -> a -> bool')
yield SameTypeConstraint(inp.type3, type3types.bool_,
comment=f'({inp.operator}) :: a -> a -> bool')
return
raise NotImplementedError(expression, inp)
if isinstance(inp, ourlang.FunctionCall):
yield SameTypeConstraint(inp.function.returns_type3, inp.type3,
comment=f'The type of a function call to {inp.function.name} is the same as the type that the function returns')
assert len(inp.arguments) == len(inp.function.posonlyargs) # FIXME: Make this a Constraint
for fun_arg, call_arg in zip(inp.function.posonlyargs, inp.arguments):
yield from expression(ctx, call_arg)
yield SameTypeConstraint(fun_arg.type3, call_arg.type3,
comment=f'The type of the value passed to argument {fun_arg.name} of function {inp.function.name} should match the type of that argument')
return
if isinstance(inp, ourlang.TupleInstantiation):
r_type = []
for arg in inp.elements:
yield from expression(ctx, arg)
r_type.append(arg.type3)
yield SameTypeConstraint(
inp.type3,
type3types.AppliedType3(type3types.tuple, r_type),
comment=f'The type of a tuple is a combination of its members'
)
return
if isinstance(inp, ourlang.Subscript):
yield from expression(ctx, inp.varref)
yield from expression(ctx, inp.index)
yield CanBeSubscriptedConstraint(inp.type3, inp.varref.type3, inp.index)
return
if isinstance(inp, ourlang.AccessStructMember):
yield from expression(ctx, inp.varref)
yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3,
comment=f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
return
if isinstance(inp, ourlang.Fold):
yield from expression(ctx, inp.base)
yield from expression(ctx, inp.iter)
yield SameTypeConstraint(inp.func.posonlyargs[0].type3, inp.func.returns_type3, inp.base.type3, inp.type3,
comment='foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b')
yield MustImplementTypeClassConstraint('Foldable', inp.iter.type3)
return
raise NotImplementedError(expression, inp)
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
yield from expression(ctx, inp.value)
yield SameTypeConstraint(fun.returns_type3, inp.value.type3,
comment=f'The type of the value returned from function {fun.name} should match its return type')
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
yield from expression(ctx, inp.test)
yield SameTypeConstraint(inp.test.type3, type3types.bool_,
comment=f'Must pass a boolean expression to if')
for stmt in inp.statements:
yield from statement(ctx, fun, stmt)
for stmt in inp.else_statements:
yield from statement(ctx, fun, stmt)
def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> ConstraintGenerator:
if isinstance(inp, ourlang.StatementReturn):
yield from statement_return(ctx, fun, inp)
return
if isinstance(inp, ourlang.StatementIf):
yield from statement_if(ctx, fun, inp)
return
raise NotImplementedError(statement, fun, inp)
def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
assert not inp.imported
if isinstance(inp, ourlang.StructConstructor):
return
for stmt in inp.statements:
yield from statement(ctx, inp, stmt)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
yield from constant(ctx, inp.constant)
yield SameTypeConstraint(inp.type3, inp.constant.type3,
comment=f'The type of the value for module constant definition {inp.name} should match the type of that constant')
def module(ctx: Context, inp: ourlang.Module) -> ConstraintGenerator:
for cdef in inp.constant_defs.values():
yield from module_constant_def(ctx, cdef)
for func in inp.functions.values():
if func.imported:
continue
yield from function(ctx, func)

154
phasm/type3/entry.py Normal file
View File

@ -0,0 +1,154 @@
"""
Entry point to the type3 system
"""
from typing import Any, Dict, List, Set
from .. import codestyle
from .. import ourlang
from .constraints import ConstraintBase, Error, RequireTypeSubstitutes, SameTypeConstraint, SubstitutionMap
from .constraintsgenerator import phasm_type3_generate_constraints
from .types import AppliedType3, IntType3, PlaceholderForType, PrimitiveType3, StructType3, Type3, Type3OrPlaceholder
MAX_RESTACK_COUNT = 100
class Type3Exception(BaseException):
"""
Thrown when the Type3 system detects constraints that do not hold
"""
def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
constraint_list = phasm_type3_generate_constraints(inp)
assert constraint_list
placeholder_substitutes: Dict[PlaceholderForType, Type3] = {}
placeholder_id_map: Dict[int, str] = {}
error_list: List[Error] = []
for _ in range(MAX_RESTACK_COUNT):
if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
old_constraint_ids = {id(x) for x in constraint_list}
old_placeholder_substitutes_len = len(placeholder_substitutes)
new_constraint_list = []
for constraint in constraint_list:
check_result = constraint.check()
if check_result is None:
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Constraint checks out')
continue
if isinstance(check_result, dict):
placeholder_substitutes.update(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Constraint checks out, and gave us new information')
continue
if isinstance(check_result, Error):
error_list.append(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Got an error')
continue
if isinstance(check_result, RequireTypeSubstitutes):
new_constraint_list.append(constraint)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Back on the todo list')
continue
if isinstance(check_result, list):
new_constraint_list.extend(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print(f'-> Resulted in {len(check_result)} new constraints')
continue
raise NotImplementedError(constraint, check_result)
if not new_constraint_list:
constraint_list = new_constraint_list
break
# Infinite loop detection
new_constraint_ids = {id(x) for x in new_constraint_list}
new_placeholder_substitutes_len = len(placeholder_substitutes)
if old_constraint_ids == new_constraint_ids and old_placeholder_substitutes_len == new_placeholder_substitutes_len:
if error_list:
raise Type3Exception(error_list)
raise Exception('Cannot type this program - not enough information')
constraint_list = new_constraint_list
if constraint_list:
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')
if error_list:
raise Type3Exception(error_list)
# FIXME: This doesn't work with e.g. `:: [a] -> a`, as the placeholder is inside a type
for plh, typ in placeholder_substitutes.items():
for expr in plh.update_on_substitution:
assert expr.type3 is plh
expr.type3 = typ
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:
txt, fmt = constraint.human_readable()
act_fmt: Dict[str, str] = {}
for fmt_key, fmt_val in fmt.items():
if isinstance(fmt_val, ourlang.Expression):
fmt_val = codestyle.expression(fmt_val)
if isinstance(fmt_val, Type3) or isinstance(fmt_val, PlaceholderForType):
fmt_val = get_printable_type_name(fmt_val, placeholder_id_map)
if not isinstance(fmt_val, str):
fmt_val = repr(fmt_val)
act_fmt[fmt_key] = fmt_val
if constraint.comment is not None:
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + constraint.comment)
else:
print('- ' + txt.format(**act_fmt))
def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[int, str]) -> str:
if isinstance(inp, (PrimitiveType3, StructType3, IntType3, )):
return inp.name
if isinstance(inp, PlaceholderForType):
placeholder_id = id(inp)
if placeholder_id not in placeholder_id_map:
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
return placeholder_id_map[placeholder_id]
if isinstance(inp, AppliedType3):
return (
get_printable_type_name(inp.base, placeholder_id_map)
+ ' ('
+ ') ('.join(get_printable_type_name(x, placeholder_id_map) for x in inp.args)
+ ')'
)
raise NotImplementedError(inp)
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
print('=== v type3 constraint_list v === ')
for psk, psv in placeholder_substitutes.items():
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, comment='Deduced type'))
for constraint in constraint_list:
print_constraint(placeholder_id_map, constraint)
print('=== ^ type3 constraint_list ^ === ')

322
phasm/type3/types.py Normal file
View File

@ -0,0 +1,322 @@
"""
Contains the final types for use in Phasm
These are actual, instantiated types; not the abstract types that the
constraint generator works with.
"""
from typing import Any, Dict, Iterable, List, Optional, Protocol, Union
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
class ExpressionProtocol(Protocol):
"""
A protocol for classes that should be updated on substitution
"""
type3: 'Type3OrPlaceholder'
"""
The type to update
"""
class Type3:
"""
Base class for the type3 types
"""
__slots__ = ('name', )
name: str
"""
The name of the string, as parsed and outputted by codestyle.
"""
def __init__(self, name: str) -> None:
self.name = name
def __repr__(self) -> str:
return f'Type3("{self.name}")'
def __str__(self) -> str:
return self.name
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}')
return str(self)
def __eq__(self, other: Any) -> bool:
if isinstance(other, PlaceholderForType):
return False
if not isinstance(other, Type3):
raise NotImplementedError
return self is other
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
raise NotImplementedError
def __bool__(self) -> bool:
raise NotImplementedError
class PrimitiveType3(Type3):
"""
Intermediate class to tell primitive types from others
"""
__slots__ = ()
class IntType3(Type3):
"""
Sometimes you can have an int as type, e.g. when using static arrays
"""
__slots__ = ('value', )
value: int
def __init__(self, value: int) -> None:
super().__init__(str(value))
assert 0 <= value
self.value = value
def __eq__(self, other: Any) -> bool:
if isinstance(other, IntType3):
return self.value == other.value
if isinstance(other, Type3):
return False
raise NotImplementedError
class PlaceholderForType:
"""
A placeholder type, for when we don't know the final type yet
"""
__slots__ = ('update_on_substitution', 'resolve_as', )
update_on_substitution: List[ExpressionProtocol]
resolve_as: Optional[Type3]
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
self.update_on_substitution = [*update_on_substitution]
self.resolve_as = None
def __repr__(self) -> str:
uos = ', '.join(repr(x) for x in self.update_on_substitution)
return f'PlaceholderForType({id(self)}, [{uos}])'
def __str__(self) -> str:
return f'PhFT_{id(self)}'
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError('unsupported format string passed to Type3.__format__')
return str(self)
def __eq__(self, other: Any) -> bool:
if isinstance(other, Type3):
return False
if not isinstance(other, PlaceholderForType):
raise NotImplementedError
return self is other
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
return 0 # Valid but performs badly
def __bool__(self) -> bool:
raise NotImplementedError
Type3OrPlaceholder = Union[Type3, PlaceholderForType]
class AppliedType3(Type3):
"""
A Type3 that has been applied to another type
"""
__slots__ = ('base', 'args', )
base: PrimitiveType3
"""
The base type
"""
args: List[Type3OrPlaceholder]
"""
The applied types (or placeholders there for)
"""
def __init__(self, base: PrimitiveType3, args: Iterable[Type3OrPlaceholder]) -> None:
args = [*args]
assert args, 'Must at least one argument'
super().__init__(
base.name
+ ' ('
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
+ ')'
)
self.base = base
self.args = args
@property
def has_placeholders(self) -> bool:
return any(
isinstance(x, PlaceholderForType)
for x in self.args
)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Type3):
raise NotImplementedError
if not isinstance(other, AppliedType3):
return False
return (
self.base == other.base
and len(self.args) == len(other.args)
and all(
s == x
for s, x in zip(self.args, other.args)
)
)
def __repr__(self) -> str:
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
class StructType3(Type3):
"""
A Type3 struct with named members
"""
__slots__ = ('name', 'members', )
name: str
"""
The structs fully qualified name
"""
members: Dict[str, Type3]
"""
The struct's field definitions
"""
def __init__(self, name: str, members: Dict[str, Type3]) -> None:
super().__init__(name)
self.name = name
self.members = dict(members)
def __repr__(self) -> str:
return f'StructType3(repr({self.name}), repr({self.members}))'
none = PrimitiveType3('none')
"""
The none type, for when functions simply don't return anything. e.g., IO().
"""
bool_ = PrimitiveType3('bool')
"""
The bool type, either True or False
"""
u8 = PrimitiveType3('u8')
"""
The unsigned 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8.
"""
u32 = PrimitiveType3('u32')
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32.
"""
u64 = PrimitiveType3('u64')
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64.
"""
i8 = PrimitiveType3('i8')
"""
The signed 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8, but
with the middel point being 0.
"""
i32 = PrimitiveType3('i32')
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32, but
with the middel point being 0.
"""
i64 = PrimitiveType3('i64')
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64, but
with the middel point being 0.
"""
f32 = PrimitiveType3('f32')
"""
A 32-bits IEEE 754 float, of 32 bits width.
"""
f64 = PrimitiveType3('f64')
"""
A 32-bits IEEE 754 float, of 64 bits width.
"""
bytes = PrimitiveType3('bytes')
"""
This is a runtime-determined length piece of memory that can be indexed at runtime.
"""
static_array = PrimitiveType3('static_array')
"""
This is a fixed length piece of memory that can be indexed at runtime.
It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated.
"""
tuple = PrimitiveType3('tuple') # pylint: disable=W0622
"""
This is a fixed length piece of memory.
It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
"""
LOOKUP_TABLE: Dict[str, Type3] = {
'none': none,
'bool': bool_,
'u8': u8,
'u32': u32,
'u64': u64,
'i8': i8,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes,
}

View File

@ -1,202 +0,0 @@
"""
The phasm type system
"""
from typing import Optional, List
class TypeBase:
"""
TypeBase base class
"""
__slots__ = ()
def alloc_size(self) -> int:
"""
When allocating this type in memory, how many bytes do we need to reserve?
"""
raise NotImplementedError(self, 'alloc_size')
class TypeNone(TypeBase):
"""
The None (or Void) type
"""
__slots__ = ()
class TypeBool(TypeBase):
"""
The boolean type
"""
__slots__ = ()
class TypeUInt8(TypeBase):
"""
The Integer type, unsigned and 8 bits wide
Note that under the hood we need to use i32 to represent
these values in expressions. So we need to add some operations
to make sure the math checks out.
So while this does save bytes in memory, it may not actually
speed up or improve your code.
"""
__slots__ = ()
def alloc_size(self) -> int:
return 4 # Int32 under the hood
class TypeUInt32(TypeBase):
"""
The Integer type, unsigned and 32 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 4
class TypeUInt64(TypeBase):
"""
The Integer type, unsigned and 64 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 8
class TypeInt32(TypeBase):
"""
The Integer type, signed and 32 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 4
class TypeInt64(TypeBase):
"""
The Integer type, signed and 64 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 8
class TypeFloat32(TypeBase):
"""
The Float type, 32 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 4
class TypeFloat64(TypeBase):
"""
The Float type, 64 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 8
class TypeBytes(TypeBase):
"""
The bytes type
"""
__slots__ = ()
class TypeTupleMember:
"""
Represents a tuple member
"""
def __init__(self, idx: int, type_: TypeBase, offset: int) -> None:
self.idx = idx
self.type = type_
self.offset = offset
class TypeTuple(TypeBase):
"""
The tuple type
"""
__slots__ = ('members', )
members: List[TypeTupleMember]
def __init__(self) -> None:
self.members = []
def render_internal_name(self) -> str:
"""
Generates an internal name for this tuple
"""
mems = '@'.join('?' for x in self.members) # FIXME: Should not be a questionmark
assert ' ' not in mems, 'Not implement yet: subtuples'
return f'tuple@{mems}'
def alloc_size(self) -> int:
return sum(
x.type.alloc_size()
for x in self.members
)
class TypeStaticArrayMember:
"""
Represents a static array member
"""
def __init__(self, idx: int, offset: int) -> None:
self.idx = idx
self.offset = offset
class TypeStaticArray(TypeBase):
"""
The static array type
"""
__slots__ = ('member_type', 'members', )
member_type: TypeBase
members: List[TypeStaticArrayMember]
def __init__(self, member_type: TypeBase) -> None:
self.member_type = member_type
self.members = []
def alloc_size(self) -> int:
return self.member_type.alloc_size() * len(self.members)
class TypeStructMember:
"""
Represents a struct member
"""
def __init__(self, name: str, type_: TypeBase, offset: int) -> None:
self.name = name
self.type = type_
self.offset = offset
class TypeStruct(TypeBase):
"""
A struct has named properties
"""
__slots__ = ('name', 'lineno', 'members', )
name: str
lineno: int
members: List[TypeStructMember]
def __init__(self, name: str, lineno: int) -> None:
self.name = name
self.lineno = lineno
self.members = []
def get_member(self, name: str) -> Optional[TypeStructMember]:
"""
Returns a member by name
"""
for mem in self.members:
if mem.name == name:
return mem
return None
def alloc_size(self) -> int:
return sum(
x.type.alloc_size()
for x in self.members
)

View File

@ -1,69 +0,0 @@
"""
Helper functions to quickly generate WASM code
"""
from typing import Any, Dict, List, Optional, Type
import functools
from . import wasm
#pylint: disable=C0103,C0115,C0116,R0201,R0902
class Prefix_inn_fnn:
def __init__(self, prefix: str) -> None:
self.prefix = prefix
# 6.5.5. Memory Instructions
self.load = functools.partial(wasm.Statement, f'{self.prefix}.load')
self.store = functools.partial(wasm.Statement, f'{self.prefix}.store')
# 6.5.6. Numeric Instructions
self.clz = functools.partial(wasm.Statement, f'{self.prefix}.clz')
self.ctz = functools.partial(wasm.Statement, f'{self.prefix}.ctz')
self.popcnt = functools.partial(wasm.Statement, f'{self.prefix}.popcnt')
self.add = functools.partial(wasm.Statement, f'{self.prefix}.add')
self.sub = functools.partial(wasm.Statement, f'{self.prefix}.sub')
self.mul = functools.partial(wasm.Statement, f'{self.prefix}.mul')
self.div_s = functools.partial(wasm.Statement, f'{self.prefix}.div_s')
self.div_u = functools.partial(wasm.Statement, f'{self.prefix}.div_u')
self.rem_s = functools.partial(wasm.Statement, f'{self.prefix}.rem_s')
self.rem_u = functools.partial(wasm.Statement, f'{self.prefix}.rem_u')
self.and_ = functools.partial(wasm.Statement, f'{self.prefix}.and')
self.or_ = functools.partial(wasm.Statement, f'{self.prefix}.or')
self.xor = functools.partial(wasm.Statement, f'{self.prefix}.xor')
self.shl = functools.partial(wasm.Statement, f'{self.prefix}.shl')
self.shr_s = functools.partial(wasm.Statement, f'{self.prefix}.shr_s')
self.shr_u = functools.partial(wasm.Statement, f'{self.prefix}.shr_u')
self.rotl = functools.partial(wasm.Statement, f'{self.prefix}.rotl')
self.rotr = functools.partial(wasm.Statement, f'{self.prefix}.rotr')
self.eqz = functools.partial(wasm.Statement, f'{self.prefix}.eqz')
self.eq = functools.partial(wasm.Statement, f'{self.prefix}.eq')
self.ne = functools.partial(wasm.Statement, f'{self.prefix}.ne')
self.lt_s = functools.partial(wasm.Statement, f'{self.prefix}.lt_s')
self.lt_u = functools.partial(wasm.Statement, f'{self.prefix}.lt_u')
self.gt_s = functools.partial(wasm.Statement, f'{self.prefix}.gt_s')
self.gt_u = functools.partial(wasm.Statement, f'{self.prefix}.gt_u')
self.le_s = functools.partial(wasm.Statement, f'{self.prefix}.le_s')
self.le_u = functools.partial(wasm.Statement, f'{self.prefix}.le_u')
self.ge_s = functools.partial(wasm.Statement, f'{self.prefix}.ge_s')
self.ge_u = functools.partial(wasm.Statement, f'{self.prefix}.ge_u')
def const(self, value: int, comment: Optional[str] = None) -> wasm.Statement:
return wasm.Statement(f'{self.prefix}.const', f'0x{value:08x}', comment=comment)
i32 = Prefix_inn_fnn('i32')
i64 = Prefix_inn_fnn('i64')
class Block:
def __init__(self, start: str) -> None:
self.start = start
def __call__(self, *statements: wasm.Statement) -> List[wasm.Statement]:
return [
wasm.Statement('if'),
*statements,
wasm.Statement('end'),
]
if_ = Block('if')

View File

@ -1,5 +1,5 @@
[MASTER] [MASTER]
disable=C0122,R0903,R0911,R0912,R0913,R0915,R1710,W0223 disable=C0103,C0122,R0902,R0903,R0911,R0912,R0913,R0915,R1710,W0223
max-line-length=180 max-line-length=180
@ -7,4 +7,4 @@ max-line-length=180
good-names=g good-names=g
[tests] [tests]
disable=C0116, disable=C0116,R0201

View File

@ -1,10 +1,10 @@
mypy==0.812 mypy==0.991
pygments==2.12.0 pygments==2.12.0
pylint==2.7.4 pylint==2.15.9
pytest==6.2.2 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
wasmer==1.1.0 wasmer==1.1.0
wasmer_compiler_cranelift==1.1.0 wasmer_compiler_cranelift==1.1.0
wasmtime==0.36.0 wasmtime==3.0.0

View File

@ -0,0 +1,16 @@
"""
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

View File

@ -24,7 +24,7 @@ class Suite:
def __init__(self, code_py): def __init__(self, code_py):
self.code_py = code_py self.code_py = code_py
def run_code(self, *args, runtime='pywasm3', imports=None): def run_code(self, *args, runtime='pywasm3', func_name='testEntry', imports=None):
""" """
Compiles the given python code into wasm and Compiles the given python code into wasm and
then runs it then runs it
@ -74,7 +74,7 @@ class Suite:
runner.interpreter_dump_memory(sys.stderr) runner.interpreter_dump_memory(sys.stderr)
result = SuiteResult() result = SuiteResult()
result.returned_value = runner.call('testEntry', *wasm_args) result.returned_value = runner.call(func_name, *wasm_args)
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)

View File

@ -13,6 +13,7 @@ import wasmtime
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 import ourlang from phasm import ourlang
from phasm import wasm from phasm import wasm
@ -40,6 +41,7 @@ class RunnerBase:
Parses the Phasm code into an AST Parses the Phasm code into an AST
""" """
self.phasm_ast = phasm_parse(self.phasm_code) self.phasm_ast = phasm_parse(self.phasm_code)
phasm_type3(self.phasm_ast, verbose=True)
def compile_ast(self) -> None: def compile_ast(self) -> None:
""" """
@ -160,7 +162,7 @@ class RunnerPywasm3(RunnerBase):
memory = self.rtime.get_memory(0) memory = self.rtime.get_memory(0)
for idx, byt in enumerate(data): for idx, byt in enumerate(data):
memory[offset + idx] = byt # type: ignore memory[offset + idx] = byt
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)

View File

@ -1,87 +0,0 @@
import pytest
from .helpers import Suite
@pytest.mark.integration_test
def test_i32():
code_py = """
CONSTANT: i32 = 13
@exported
def testEntry() -> i32:
return CONSTANT * 5
"""
result = Suite(code_py).run_code()
assert 65 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
def test_tuple_1(type_):
code_py = f"""
CONSTANT: ({type_}, ) = (65, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT)
def helper(vector: ({type_}, )) -> {type_}:
return vector[0]
"""
result = Suite(code_py).run_code()
assert 65 == result.returned_value
@pytest.mark.integration_test
def test_tuple_6():
code_py = """
CONSTANT: (u8, u8, u32, u32, u64, u64, ) = (11, 22, 3333, 4444, 555555, 666666, )
@exported
def testEntry() -> u32:
return helper(CONSTANT)
def helper(vector: (u8, u8, u32, u32, u64, u64, )) -> u32:
return vector[2]
"""
result = Suite(code_py).run_code()
assert 3333 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
def test_static_array_1(type_):
code_py = f"""
CONSTANT: {type_}[1] = (65, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT)
def helper(vector: {type_}[1]) -> {type_}:
return vector[0]
"""
result = Suite(code_py).run_code()
assert 65 == result.returned_value
@pytest.mark.integration_test
def test_static_array_6():
code_py = """
CONSTANT: u32[6] = (11, 22, 3333, 4444, 555555, 666666, )
@exported
def testEntry() -> u32:
return helper(CONSTANT)
def helper(vector: u32[6]) -> u32:
return vector[2]
"""
result = Suite(code_py).run_code()
assert 3333 == result.returned_value

View File

@ -0,0 +1,19 @@
import pytest
from ..helpers import Suite
@pytest.mark.slow_integration_test
def test_index():
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
code_py = "\n" + fil.read()
result = Suite(code_py).run_code(b'Hello, world!', 5, func_name='index', runtime='wasmtime')
assert 44 == result.returned_value
@pytest.mark.slow_integration_test
def test_length():
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
code_py = "\n" + fil.read()
result = Suite(code_py).run_code(b'Hello, world!', func_name='length')
assert 13 == result.returned_value

View File

@ -3,9 +3,9 @@ import struct
import pytest import pytest
from .helpers import Suite from ..helpers import Suite
@pytest.mark.integration_test @pytest.mark.slow_integration_test
def test_crc32(): def test_crc32():
# FIXME: Stub # FIXME: Stub
# crc = 0xFFFFFFFF # crc = 0xFFFFFFFF

View File

@ -0,0 +1,12 @@
import pytest
from ..helpers import Suite
@pytest.mark.slow_integration_test
def test_fib():
with open('./examples/fib.py', 'r', encoding='UTF-8') as fil:
code_py = "\n" + fil.read()
result = Suite(code_py).run_code()
assert 102334155 == result.returned_value

View File

@ -1,30 +0,0 @@
import pytest
from .helpers import Suite
@pytest.mark.slow_integration_test
def test_fib():
code_py = """
def helper(n: i32, a: i32, b: i32) -> i32:
if n < 1:
return a + b
return helper(n - 1, a + b, a)
def fib(n: i32) -> i32:
if n == 0:
return 0
if n == 1:
return 1
return helper(n - 1, 0, 1)
@exported
def testEntry() -> i32:
return fib(40)
"""
result = Suite(code_py).run_code()
assert 102334155 == result.returned_value

View File

@ -1,70 +0,0 @@
import io
import pytest
from pywasm import binary
from pywasm import Runtime
from wasmer import wat2wasm
def run(code_wat):
code_wasm = wat2wasm(code_wat)
module = binary.Module.from_reader(io.BytesIO(code_wasm))
runtime = Runtime(module, {}, {})
out_put = runtime.exec('testEntry', [])
return (runtime, out_put)
@pytest.mark.parametrize('size,offset,exp_out_put', [
('32', 0, 0x3020100),
('32', 1, 0x4030201),
('64', 0, 0x706050403020100),
('64', 2, 0x908070605040302),
])
def test_i32_64_load(size, offset, exp_out_put):
code_wat = f"""
(module
(memory 1)
(data (memory 0) (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\10")
(func (export "testEntry") (result i{size})
i32.const {offset}
i{size}.load
return ))
"""
(_, out_put) = run(code_wat)
assert exp_out_put == out_put
def test_load_then_store():
code_wat = """
(module
(memory 1)
(data (memory 0) (i32.const 0) "\\04\\00\\00\\00")
(func (export "testEntry") (result i32) (local $my_memory_value i32)
;; Load i32 from address 0
i32.const 0
i32.load
;; Add 8 to the loaded value
i32.const 8
i32.add
local.set $my_memory_value
;; Store back to the memory
i32.const 0
local.get $my_memory_value
i32.store
;; Return something
i32.const 9
return ))
"""
(runtime, out_put) = run(code_wat)
assert 9 == out_put
assert (b'\x0c'+ b'\00' * 23) == runtime.store.mems[0].data[:24]

View File

View File

@ -2,8 +2,8 @@ import sys
import pytest 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)

View File

@ -0,0 +1,84 @@
import pytest
from phasm.type3.entry import Type3Exception
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
def test_bytes_length():
code_py = """
@exported
def testEntry(f: bytes) -> u32:
return len(f)
"""
result = Suite(code_py).run_code(b'This yet is another test')
assert 24 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[8]
"""
result = Suite(code_py).run_code(b'This is another test')
assert 0x61 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index_out_of_bounds():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[50]
"""
result = Suite(code_py).run_code(b'Short', b'Long' * 100)
assert 0 == result.returned_value
@pytest.mark.integration_test
def test_function_call_element_ok():
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 = """
@exported
def testEntry(f: bytes) -> u64:
return helper(f[0])
def helper(x: u64) -> u64:
return x
"""
with pytest.raises(Type3Exception, match=r'u64 must be u8 instead'):
Suite(code_py).run_code()

View File

@ -0,0 +1,71 @@
import pytest
from ..helpers import Suite
@pytest.mark.integration_test
@pytest.mark.parametrize('inp', [9, 10, 11, 12])
def test_if_simple(inp):
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a > 10:
return 15
return 3
"""
exp_result = 15 if inp > 10 else 3
suite = Suite(code_py)
result = suite.run_code(inp)
assert exp_result == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Such a return is not how things should be')
def test_if_complex():
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a > 10:
return 10
elif a > 0:
return a
else:
return 0
return -1 # Required due to function type
"""
suite = Suite(code_py)
assert 10 == suite.run_code(20).returned_value
assert 10 == suite.run_code(10).returned_value
assert 8 == suite.run_code(8).returned_value
assert 0 == suite.run_code(0).returned_value
assert 0 == suite.run_code(-1).returned_value
@pytest.mark.integration_test
def test_if_nested():
code_py = """
@exported
def testEntry(a: i32, b: i32) -> i32:
if a > 11:
if b > 11:
return 3
return 2
if b > 11:
return 1
return 0
"""
suite = Suite(code_py)
assert 3 == suite.run_code(20, 20).returned_value
assert 2 == suite.run_code(20, 10).returned_value
assert 1 == suite.run_code(10, 20).returned_value
assert 0 == suite.run_code(10, 10).returned_value

View File

@ -0,0 +1,79 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
@pytest.mark.integration_test
def test_imported_ok():
code_py = """
@imported
def helper(mul: i32) -> i32:
pass
@exported
def testEntry() -> i32:
return helper(2)
"""
def helper(mul: int) -> int:
return 4238 * mul
result = Suite(code_py).run_code(
runtime='wasmer',
imports={
'helper': helper,
}
)
assert 8476 == result.returned_value
@pytest.mark.integration_test
def test_imported_side_effect_no_return():
code_py = """
@imported
def helper(mul: u8) -> None:
pass
@exported
def testEntry() -> None:
return helper(3)
"""
prop = None
def helper(mul: int) -> None:
nonlocal prop
prop = mul
result = Suite(code_py).run_code(
runtime='wasmer',
imports={
'helper': helper,
}
)
assert None is result.returned_value
assert 3 == prop
@pytest.mark.integration_test
def test_imported_type_mismatch():
code_py = """
@imported
def helper(mul: u8) -> u8:
pass
@exported
def testEntry(x: u32) -> u8:
return helper(x)
"""
def helper(mul: int) -> int:
return 4238 * mul
with pytest.raises(Type3Exception, match=r'u32 must be u8 instead'):
Suite(code_py).run_code(
runtime='wasmer',
imports={
'helper': helper,
}
)

View File

@ -0,0 +1,486 @@
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()

View File

@ -0,0 +1,195 @@
import pytest
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
@pytest.mark.integration_test
def test_module_constant_def():
code_py = """
CONSTANT: u8[3] = (24, 57, 80, )
@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_', ALL_INT_TYPES)
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 = """
CONSTANT: u64[3] = (250, 250000, 250000000, )
@exported
def testEntry() -> u64:
return helper(CONSTANT[0])
def helper(x: u64) -> u64:
return x
"""
result = Suite(code_py).run_code()
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
def test_module_constant_type_mismatch_not_subscriptable():
code_py = """
CONSTANT: u8 = 24
@exported
def testEntry() -> u8:
return CONSTANT[0]
"""
with pytest.raises(Type3Exception, match='u8 cannot be subscripted'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_module_constant_type_mismatch_index_out_of_range_constant():
code_py = """
CONSTANT: u8[3] = (24, 57, 80, )
@exported
def testEntry() -> u8:
return CONSTANT[3]
"""
with pytest.raises(Type3Exception, match='3 must be less or equal than 2'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_module_constant_type_mismatch_index_out_of_range_variable():
code_py = """
CONSTANT: u8[3] = (24, 57, 80, )
@exported
def testEntry(x: u32) -> u8:
return CONSTANT[x]
"""
with pytest.raises(RuntimeError):
Suite(code_py).run_code(3)
@pytest.mark.integration_test
def test_static_array_constant_too_few_values():
code_py = """
CONSTANT: u8[4] = (24, 57, )
@exported
def testEntry() -> i32:
return 0
"""
with pytest.raises(Type3Exception, match='Member count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_static_array_constant_too_many_values():
code_py = """
CONSTANT: u8[3] = (24, 57, 1, 1, )
@exported
def testEntry() -> i32:
return 0
"""
with pytest.raises(Type3Exception, match='Member count mismatch'):
Suite(code_py).run_code()

View File

@ -0,0 +1,154 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..constants import (
ALL_INT_TYPES, TYPE_MAP
)
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)
@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_', 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))
def helper(cv: CheckedValue) -> {type_}:
return cv.value
"""
result = Suite(code_py).run_code()
assert 23 == result.returned_value
@pytest.mark.integration_test
def test_struct_1():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2))
def helper(shape: Rectangle) -> i32:
return shape.height + shape.width + shape.border
"""
result = Suite(code_py).run_code()
assert 252 == result.returned_value
@pytest.mark.integration_test
def test_struct_2():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2), Rectangle(200, 90, 3))
def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
return shape1.height + shape1.width + shape1.border + shape2.height + shape2.width + shape2.border
"""
result = Suite(code_py).run_code()
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.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_struct_member(type_):
code_py = f"""
class Struct:
param: {type_}
def testEntry(arg: Struct) -> (i32, i32, ):
return arg.param
"""
with pytest.raises(Type3Exception, match=type_ + r' must be tuple \(i32\) \(i32\) instead'):
Suite(code_py).run_code()

View File

@ -0,0 +1,188 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..constants import ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, TYPE_MAP
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
def test_function_call_element_ok():
code_py = """
CONSTANT: (u8, u32, u64, ) = (250, 250000, 250000000, )
@exported
def testEntry() -> u64:
return helper(CONSTANT[2])
def helper(x: u64) -> u64:
return x
"""
result = Suite(code_py).run_code()
assert 250000000 == 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.skip('SIMD support is but a dream')
def test_tuple_i32x4():
code_py = """
@exported
def testEntry() -> i32x4:
return (51, 153, 204, 0, )
"""
result = Suite(code_py).run_code()
assert (1, 2, 3, 0) == result.returned_value
@pytest.mark.integration_test
def test_assign_to_tuple_with_tuple():
code_py = """
CONSTANT: (u32, ) = 0
"""
with pytest.raises(Type3Exception, match='Must be tuple'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_constant_too_few_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, )
"""
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_constant_too_many_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
"""
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_constant_type_mismatch():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_must_use_literal_for_indexing():
code_py = """
CONSTANT: u32 = 0
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[CONSTANT]
"""
with pytest.raises(Type3Exception, match='Must index with literal'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_must_use_integer_for_indexing():
code_py = """
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[0.0]
"""
with pytest.raises(Type3Exception, match='Must index with integer literal'):
Suite(code_py).run_code()

View File

@ -1,31 +0,0 @@
import pytest
from .helpers import Suite
@pytest.mark.integration_test
def test_bytes_index_out_of_bounds():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[50]
"""
result = Suite(code_py).run_code(b'Short', b'Long' * 100)
assert 0 == result.returned_value
@pytest.mark.integration_test
def test_static_array_index_out_of_bounds():
code_py = """
CONSTANT0: u32[3] = (24, 57, 80, )
CONSTANT1: u32[16] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, )
@exported
def testEntry() -> u32:
return CONSTANT0[16]
"""
result = Suite(code_py).run_code()
assert 0 == result.returned_value

View File

@ -1,571 +0,0 @@
import pytest
from .helpers import Suite
TYPE_MAP = {
'u8': int,
'u32': int,
'u64': int,
'i32': int,
'i64': int,
'f32': float,
'f64': float,
}
COMPLETE_SIMPLE_TYPES = [
'u32', 'u64',
'i32', 'i64',
'f32', 'f64',
]
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
def test_return(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_', COMPLETE_SIMPLE_TYPES)
def test_addition(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_', COMPLETE_SIMPLE_TYPES)
def test_subtraction(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_', ['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_', ['u8', 'u32', 'u64'])
def test_logical_right_shift(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 >> 3
"""
result = Suite(code_py).run_code()
assert 1 == 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_or(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
@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_', ['f32', 'f64'])
def test_buildins_sqrt(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return sqrt(25)
"""
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_arg(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('Do we want it to work like this?')
def test_i32_to_i64():
code_py = """
@exported
def testEntry(a: i32) -> i64:
return a
"""
result = Suite(code_py).run_code(125)
assert 125 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_i32_plus_i64():
code_py = """
@exported
def testEntry(a: i32, b: i64) -> i64:
return a + b
"""
result = Suite(code_py).run_code(125, 100)
assert 225 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_f32_to_f64():
code_py = """
@exported
def testEntry(a: f32) -> f64:
return a
"""
result = Suite(code_py).run_code(125.5)
assert 125.5 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_f32_plus_f64():
code_py = """
@exported
def testEntry(a: f32, b: f64) -> f64:
return a + b
"""
result = Suite(code_py).run_code(125.5, 100.25)
assert 225.75 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('TODO')
def test_uadd():
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_usub():
code_py = """
@exported
def testEntry() -> i32:
return -19
"""
result = Suite(code_py).run_code()
assert -19 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('inp', [9, 10, 11, 12])
def test_if_simple(inp):
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a > 10:
return 15
return 3
"""
exp_result = 15 if inp > 10 else 3
suite = Suite(code_py)
result = suite.run_code(inp)
assert exp_result == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Such a return is not how things should be')
def test_if_complex():
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a > 10:
return 10
elif a > 0:
return a
else:
return 0
return -1 # Required due to function type
"""
suite = Suite(code_py)
assert 10 == suite.run_code(20).returned_value
assert 10 == suite.run_code(10).returned_value
assert 8 == suite.run_code(8).returned_value
assert 0 == suite.run_code(0).returned_value
assert 0 == suite.run_code(-1).returned_value
@pytest.mark.integration_test
def test_if_nested():
code_py = """
@exported
def testEntry(a: i32, b: i32) -> i32:
if a > 11:
if b > 11:
return 3
return 2
if b > 11:
return 1
return 0
"""
suite = Suite(code_py)
assert 3 == suite.run_code(20, 20).returned_value
assert 2 == suite.run_code(20, 10).returned_value
assert 1 == suite.run_code(10, 20).returned_value
assert 0 == suite.run_code(10, 10).returned_value
@pytest.mark.integration_test
def test_call_pre_defined():
code_py = """
def helper(left: i32, right: i32) -> i32:
return left + right
@exported
def testEntry() -> i32:
return helper(10, 3)
"""
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_SIMPLE_TYPES)
def test_call_with_expression(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.skip('Not yet implemented')
def test_assign():
code_py = """
@exported
def testEntry() -> i32:
a: i32 = 8947
return a
"""
result = Suite(code_py).run_code()
assert 8947 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
def test_struct_0(type_):
code_py = f"""
class CheckedValue:
value: {type_}
@exported
def testEntry() -> {type_}:
return helper(CheckedValue(23))
def helper(cv: CheckedValue) -> {type_}:
return cv.value
"""
result = Suite(code_py).run_code()
assert 23 == result.returned_value
@pytest.mark.integration_test
def test_struct_1():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2))
def helper(shape: Rectangle) -> i32:
return shape.height + shape.width + shape.border
"""
result = Suite(code_py).run_code()
assert 252 == result.returned_value
@pytest.mark.integration_test
def test_struct_2():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2), Rectangle(200, 90, 3))
def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
return shape1.height + shape1.width + shape1.border + shape2.height + shape2.width + shape2.border
"""
result = Suite(code_py).run_code()
assert 545 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
def test_tuple_simple_constructor(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
def test_tuple_float():
code_py = """
@exported
def testEntry() -> f32:
return helper((1.0, 2.0, 3.0, ))
def helper(v: (f32, f32, f32, )) -> f32:
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.parametrize('type_', COMPLETE_SIMPLE_TYPES)
def test_static_array_module_constant(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_', COMPLETE_SIMPLE_TYPES)
def test_static_array_indexed(type_):
code_py = f"""
CONSTANT: {type_}[3] = (24, 57, 80, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT, 0, 1, 2)
def helper(array: {type_}[3], i0: u32, i1: u32, i2: u32) -> {type_}:
return array[i0] + array[i1] + array[i2]
"""
result = Suite(code_py).run_code()
assert 161 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@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
def test_bytes_length():
code_py = """
@exported
def testEntry(f: bytes) -> i32:
return len(f)
"""
result = Suite(code_py).run_code(b'This is another test')
assert 20 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[8]
"""
result = Suite(code_py).run_code(b'This is another test')
assert 0x61 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('SIMD support is but a dream')
def test_tuple_i32x4():
code_py = """
@exported
def testEntry() -> i32x4:
return (51, 153, 204, 0, )
"""
result = Suite(code_py).run_code()
assert (1, 2, 3, 0) == result.returned_value
@pytest.mark.integration_test
def test_imported():
code_py = """
@imported
def helper(mul: i32) -> i32:
pass
@exported
def testEntry() -> i32:
return helper(2)
"""
def helper(mul: int) -> int:
return 4238 * mul
result = Suite(code_py).run_code(
runtime='wasmer',
imports={
'helper': helper,
}
)
assert 8476 == result.returned_value

View File

@ -1,109 +0,0 @@
import pytest
from phasm.parser import phasm_parse
from phasm.exceptions import StaticError
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_function_argument(type_):
code_py = f"""
def helper(a: {type_}) -> (i32, i32, ):
return a
"""
with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), a is actually {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_struct_member(type_):
code_py = f"""
class Struct:
param: {type_}
def testEntry(arg: Struct) -> (i32, i32, ):
return arg.param
"""
with pytest.raises(StaticError, match=f'Static error on line 6: Expected \\(i32, i32, \\), arg.param is actually {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_tuple_member(type_):
code_py = f"""
def testEntry(arg: ({type_}, )) -> (i32, i32, ):
return arg[0]
"""
with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), arg\\[0\\] is actually {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_function_result(type_):
code_py = f"""
def helper() -> {type_}:
return 1
@exported
def testEntry() -> (i32, i32, ):
return helper()
"""
with pytest.raises(StaticError, match=f'Static error on line 7: Expected \\(i32, i32, \\), helper actually returns {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_too_few_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_too_many_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_type_mismatch():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_static_array_constant_too_few_values():
code_py = """
CONSTANT: u8[3] = (24, 57, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of static array values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_static_array_constant_too_many_values():
code_py = """
CONSTANT: u8[3] = (24, 57, 1, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of static array values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_static_array_constant_type_mismatch():
code_py = """
CONSTANT: u8[3] = (24, 4000, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
phasm_parse(code_py)

View File

@ -2,8 +2,8 @@ import sys
import pytest 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)