Compare commits
9 Commits
c3124f4325
...
be28450658
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be28450658 | ||
|
|
4001b086db | ||
|
|
19a29b7327 | ||
|
|
ffd11c4f72 | ||
|
|
5d9ef0e276 | ||
|
|
521171540b | ||
|
|
3e916a242e | ||
|
|
96f52a274c | ||
|
|
5c537f712e |
15
Makefile
15
Makefile
@ -1,7 +1,4 @@
|
|||||||
WABT_DIR := /home/johan/sources/github.com/WebAssembly/wabt
|
WAT2WASM := venv/bin/python wat2wasm.py
|
||||||
|
|
||||||
WAT2WASM := $(WABT_DIR)/bin/wat2wasm
|
|
||||||
WASM2C := $(WABT_DIR)/bin/wasm2c
|
|
||||||
|
|
||||||
%.wat: %.py $(shell find phasm -name '*.py') venv/.done
|
%.wat: %.py $(shell find phasm -name '*.py') venv/.done
|
||||||
venv/bin/python -m phasm $< $@
|
venv/bin/python -m phasm $< $@
|
||||||
@ -15,12 +12,6 @@ WASM2C := $(WABT_DIR)/bin/wasm2c
|
|||||||
%.wasm: %.wat
|
%.wasm: %.wat
|
||||||
$(WAT2WASM) $^ -o $@
|
$(WAT2WASM) $^ -o $@
|
||||||
|
|
||||||
%.c: %.wasm
|
|
||||||
$(WASM2C) $^ -o $@
|
|
||||||
|
|
||||||
# %.exe: %.c
|
|
||||||
# cc $^ -o $@ -I $(WABT_DIR)/wasm2c
|
|
||||||
|
|
||||||
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
|
examples: venv/.done $(subst .py,.wasm,$(wildcard examples/*.py)) $(subst .py,.wat.html,$(wildcard examples/*.py)) $(subst .py,.py.html,$(wildcard examples/*.py))
|
||||||
venv/bin/python3 -m http.server --directory examples
|
venv/bin/python3 -m http.server --directory examples
|
||||||
|
|
||||||
@ -31,10 +22,10 @@ lint: venv/.done
|
|||||||
venv/bin/ruff check phasm tests
|
venv/bin/ruff check phasm tests
|
||||||
|
|
||||||
typecheck: venv/.done
|
typecheck: venv/.done
|
||||||
venv/bin/mypy --strict phasm tests/integration/helpers.py tests/integration/runners.py
|
venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/runners.py
|
||||||
|
|
||||||
venv/.done: requirements.txt
|
venv/.done: requirements.txt
|
||||||
python3.10 -m venv venv
|
python3.12 -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 $@
|
||||||
|
|||||||
@ -32,7 +32,7 @@ make lint typecheck
|
|||||||
|
|
||||||
To compile a Phasm file:
|
To compile a Phasm file:
|
||||||
```sh
|
```sh
|
||||||
python3.10 -m phasm source.py output.wat
|
python3.12 -m phasm source.py output.wat
|
||||||
```
|
```
|
||||||
|
|
||||||
Additional required tools
|
Additional required tools
|
||||||
|
|||||||
17
TODO.md
17
TODO.md
@ -10,3 +10,20 @@
|
|||||||
- 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
|
||||||
- Also, check the codes for FIXME and TODO
|
- Also, check the codes for FIXME and TODO
|
||||||
|
- Allocation is done using pointers for members, is this desired?
|
||||||
|
|
||||||
|
- Merge in type classes
|
||||||
|
|
||||||
|
- test_bitwise_or_inv_type
|
||||||
|
- test_bytes_index_out_of_bounds vs static trap(?)
|
||||||
|
- test_num.py is probably best as part of the generator?
|
||||||
|
- Find pytest.mark.skip
|
||||||
|
- There's a weird resolve_as reference in calculate_alloc_size
|
||||||
|
- Either there should be more of them or less
|
||||||
|
- At first glance, looks like failure in the typing system
|
||||||
|
- Related to the FIXME in phasm_type3?
|
||||||
|
- WEBASSEMBLY_BUILTIN_FLOAT_OPS and WEBASSEMBLY_BUILTIN_BYTES_OPS are special cased
|
||||||
|
- Should be part of a prelude
|
||||||
|
- Casting is not implemented except u32 which is special cased
|
||||||
|
- Parser is putting stuff in ModuleDataBlock
|
||||||
|
- Compiler should probably do that
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# CRC-32/ISO-HDLC
|
||||||
|
#
|
||||||
# #include <inttypes.h> // uint32_t, uint8_t
|
# #include <inttypes.h> // uint32_t, uint8_t
|
||||||
#
|
#
|
||||||
# uint32_t CRC32(const uint8_t data[], size_t data_length) {
|
# uint32_t CRC32(const uint8_t data[], size_t data_length) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ 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 .type3 import typeclasses as type3classes
|
||||||
from .type3 import types as type3types
|
from .type3 import types as type3types
|
||||||
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
|
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
|
||||||
|
|
||||||
@ -103,7 +104,12 @@ def expression(inp: ourlang.Expression) -> str:
|
|||||||
return f'{inp.operator}{expression(inp.right)}'
|
return f'{inp.operator}{expression(inp.right)}'
|
||||||
|
|
||||||
if isinstance(inp, ourlang.BinaryOp):
|
if isinstance(inp, ourlang.BinaryOp):
|
||||||
return f'{expression(inp.left)} {inp.operator} {expression(inp.right)}'
|
if isinstance(inp.operator, type3classes.Type3ClassMethod):
|
||||||
|
operator = inp.operator.name
|
||||||
|
else:
|
||||||
|
operator = inp.operator
|
||||||
|
|
||||||
|
return f'{expression(inp.left)} {operator} {expression(inp.right)}'
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionCall):
|
if isinstance(inp, ourlang.FunctionCall):
|
||||||
args = ', '.join(
|
args = ', '.join(
|
||||||
|
|||||||
@ -2,12 +2,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
|
||||||
"""
|
"""
|
||||||
import struct
|
import struct
|
||||||
from typing import List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from . import codestyle, ourlang, wasm
|
from . import codestyle, ourlang, wasm
|
||||||
from .runtime import calculate_alloc_size, calculate_member_offset
|
from .runtime import calculate_alloc_size, calculate_member_offset
|
||||||
from .stdlib import alloc as stdlib_alloc
|
from .stdlib import alloc as stdlib_alloc
|
||||||
from .stdlib import types as stdlib_types
|
from .stdlib import types as stdlib_types
|
||||||
|
from .type3 import typeclasses as type3classes
|
||||||
from .type3 import types as type3types
|
from .type3 import types as type3types
|
||||||
from .wasmgenerator import Generator as WasmGenerator
|
from .wasmgenerator import Generator as WasmGenerator
|
||||||
|
|
||||||
@ -25,6 +26,55 @@ LOAD_STORE_TYPE_MAP = {
|
|||||||
'bytes': 'i32', # Bytes are passed around as pointers
|
'bytes': 'i32', # Bytes are passed around as pointers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# For now this is nice & clean, but this will get messy quick
|
||||||
|
# Especially once we get functions with polymorphying applied types
|
||||||
|
INSTANCES = {
|
||||||
|
type3classes.Eq.operators['==']: {
|
||||||
|
'a=u8': stdlib_types.u8_eq_equals,
|
||||||
|
'a=u32': stdlib_types.u32_eq_equals,
|
||||||
|
'a=u64': stdlib_types.u64_eq_equals,
|
||||||
|
'a=i8': stdlib_types.i8_eq_equals,
|
||||||
|
'a=i32': stdlib_types.i32_eq_equals,
|
||||||
|
'a=i64': stdlib_types.i64_eq_equals,
|
||||||
|
'a=f32': stdlib_types.f32_eq_equals,
|
||||||
|
'a=f64': stdlib_types.f64_eq_equals,
|
||||||
|
},
|
||||||
|
type3classes.Fractional.operators['/']: {
|
||||||
|
'a=f32': stdlib_types.f32_fractional_div,
|
||||||
|
'a=f64': stdlib_types.f64_fractional_div,
|
||||||
|
},
|
||||||
|
type3classes.Integral.methods['div']: {
|
||||||
|
'a=u32': stdlib_types.u32_integral_div,
|
||||||
|
'a=u64': stdlib_types.u64_integral_div,
|
||||||
|
'a=i32': stdlib_types.i32_integral_div,
|
||||||
|
'a=i64': stdlib_types.i64_integral_div,
|
||||||
|
},
|
||||||
|
type3classes.Num.operators['+']: {
|
||||||
|
'a=u32': stdlib_types.u32_num_add,
|
||||||
|
'a=u64': stdlib_types.u64_num_add,
|
||||||
|
'a=i32': stdlib_types.i32_num_add,
|
||||||
|
'a=i64': stdlib_types.i64_num_add,
|
||||||
|
'a=f32': stdlib_types.f32_num_add,
|
||||||
|
'a=f64': stdlib_types.f64_num_add,
|
||||||
|
},
|
||||||
|
type3classes.Num.operators['-']: {
|
||||||
|
'a=u32': stdlib_types.u32_num_sub,
|
||||||
|
'a=u64': stdlib_types.u64_num_sub,
|
||||||
|
'a=i32': stdlib_types.i32_num_sub,
|
||||||
|
'a=i64': stdlib_types.i64_num_sub,
|
||||||
|
'a=f32': stdlib_types.f32_num_sub,
|
||||||
|
'a=f64': stdlib_types.f64_num_sub,
|
||||||
|
},
|
||||||
|
type3classes.Num.operators['*']: {
|
||||||
|
'a=u32': stdlib_types.u32_num_mul,
|
||||||
|
'a=u64': stdlib_types.u64_num_mul,
|
||||||
|
'a=i32': stdlib_types.i32_num_mul,
|
||||||
|
'a=i64': stdlib_types.i64_num_mul,
|
||||||
|
'a=f32': stdlib_types.f32_num_mul,
|
||||||
|
'a=f64': stdlib_types.f64_num_mul,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
|
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
|
||||||
"""
|
"""
|
||||||
Public method for compiling a parsed Phasm module into
|
Public method for compiling a parsed Phasm module into
|
||||||
@ -44,6 +94,11 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
|||||||
if inp == type3types.none:
|
if inp == type3types.none:
|
||||||
return wasm.WasmTypeNone()
|
return wasm.WasmTypeNone()
|
||||||
|
|
||||||
|
if inp == type3types.bool_:
|
||||||
|
# WebAssembly stores booleans as i32
|
||||||
|
# See e.g. f32.eq, which is [f32 f32] -> [i32]
|
||||||
|
return wasm.WasmTypeInt32()
|
||||||
|
|
||||||
if inp == type3types.u8:
|
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
|
||||||
@ -92,14 +147,6 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
|||||||
|
|
||||||
raise NotImplementedError(type3, inp)
|
raise NotImplementedError(type3, inp)
|
||||||
|
|
||||||
# Operators that work for i32, i64, f32, f64
|
|
||||||
OPERATOR_MAP = {
|
|
||||||
'+': 'add',
|
|
||||||
'-': 'sub',
|
|
||||||
'*': 'mul',
|
|
||||||
'==': 'eq',
|
|
||||||
}
|
|
||||||
|
|
||||||
U8_OPERATOR_MAP = {
|
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
|
||||||
@ -120,7 +167,6 @@ 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 = {
|
||||||
@ -133,7 +179,6 @@ 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 = {
|
||||||
@ -141,7 +186,6 @@ 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 = {
|
||||||
@ -149,15 +193,6 @@ 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:
|
def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> None:
|
||||||
@ -303,6 +338,30 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
expression(wgn, inp.right)
|
expression(wgn, inp.right)
|
||||||
|
|
||||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||||
|
|
||||||
|
if isinstance(inp.operator, type3classes.Type3ClassMethod):
|
||||||
|
type_var_map: Dict[type3classes.TypeVariable, type3types.Type3] = {}
|
||||||
|
|
||||||
|
for type_var, arg_expr in zip(inp.operator.signature, [inp.left, inp.right, inp]):
|
||||||
|
if not isinstance(type_var, type3classes.TypeVariable):
|
||||||
|
# Fixed type, not part of the lookup requirements
|
||||||
|
continue
|
||||||
|
|
||||||
|
assert isinstance(arg_expr.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||||
|
type_var_map[type_var] = arg_expr.type3
|
||||||
|
|
||||||
|
instance_key = ','.join(
|
||||||
|
f'{k.letter}={v.name}'
|
||||||
|
for k, v in type_var_map.items()
|
||||||
|
)
|
||||||
|
|
||||||
|
instance = INSTANCES.get(inp.operator, {}).get(instance_key, None)
|
||||||
|
if instance is not None:
|
||||||
|
instance(wgn)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise NotImplementedError(inp.operator, instance_key)
|
||||||
|
|
||||||
# FIXME: Re-implement build-in operators
|
# FIXME: Re-implement build-in operators
|
||||||
# Maybe operator_annotation is the way to go
|
# Maybe operator_annotation is the way to go
|
||||||
# Maybe the older stuff below that is the way to go
|
# Maybe the older stuff below that is the way to go
|
||||||
@ -315,56 +374,27 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
if operator_annotation == '(<) :: u64 -> u64 -> bool':
|
if operator_annotation == '(<) :: u64 -> u64 -> bool':
|
||||||
wgn.add_statement('i64.lt_u')
|
wgn.add_statement('i64.lt_u')
|
||||||
return
|
return
|
||||||
if operator_annotation == '(==) :: u64 -> u64 -> bool':
|
|
||||||
wgn.add_statement('i64.eq')
|
|
||||||
return
|
|
||||||
|
|
||||||
if inp.type3 == type3types.u8:
|
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 inp.type3 == type3types.u32:
|
if inp.type3 == type3types.u32:
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i32.{operator}')
|
|
||||||
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 inp.type3 == type3types.u64:
|
if inp.type3 == type3types.u64:
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i64.{operator}')
|
|
||||||
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 inp.type3 == type3types.i32:
|
if inp.type3 == type3types.i32:
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i32.{operator}')
|
|
||||||
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 inp.type3 == type3types.i64:
|
if inp.type3 == type3types.i64:
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'i64.{operator}')
|
|
||||||
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 inp.type3 == type3types.f32:
|
|
||||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'f32.{operator}')
|
|
||||||
return
|
|
||||||
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):
|
|
||||||
wgn.add_statement(f'f64.{operator}')
|
|
||||||
return
|
|
||||||
if operator := F64_OPERATOR_MAP.get(inp.operator, None):
|
|
||||||
wgn.add_statement(f'f64.{operator}')
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp.operator, inp.left.type3, inp.right.type3, inp.type3)
|
raise NotImplementedError(expression, inp.operator, inp.left.type3, inp.right.type3, inp.type3)
|
||||||
|
|
||||||
@ -399,6 +429,30 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
for arg in inp.arguments:
|
for arg in inp.arguments:
|
||||||
expression(wgn, arg)
|
expression(wgn, arg)
|
||||||
|
|
||||||
|
if isinstance(inp.function, type3classes.Type3ClassMethod):
|
||||||
|
# FIXME: Duplicate code with BinaryOp
|
||||||
|
type_var_map = {}
|
||||||
|
|
||||||
|
for type_var, arg_expr in zip(inp.function.signature, inp.arguments + [inp]):
|
||||||
|
if not isinstance(type_var, type3classes.TypeVariable):
|
||||||
|
# Fixed type, not part of the lookup requirements
|
||||||
|
continue
|
||||||
|
|
||||||
|
assert isinstance(arg_expr.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||||
|
type_var_map[type_var] = arg_expr.type3
|
||||||
|
|
||||||
|
instance_key = ','.join(
|
||||||
|
f'{k.letter}={v.name}'
|
||||||
|
for k, v in type_var_map.items()
|
||||||
|
)
|
||||||
|
|
||||||
|
instance = INSTANCES.get(inp.function, {}).get(instance_key, None)
|
||||||
|
if instance is not None:
|
||||||
|
instance(wgn)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise NotImplementedError(inp.function, instance_key)
|
||||||
|
|
||||||
wgn.add_statement('call', '${}'.format(inp.function.name))
|
wgn.add_statement('call', '${}'.format(inp.function.name))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from typing import Dict, Iterable, List, Optional, Union
|
|||||||
|
|
||||||
from typing_extensions import Final
|
from typing_extensions import Final
|
||||||
|
|
||||||
|
from .type3 import typeclasses as type3typeclasses
|
||||||
from .type3 import types as type3types
|
from .type3 import types as type3types
|
||||||
from .type3.types import PlaceholderForType, StructType3, Type3, Type3OrPlaceholder
|
from .type3.types import PlaceholderForType, StructType3, Type3, Type3OrPlaceholder
|
||||||
|
|
||||||
@ -149,11 +150,11 @@ class BinaryOp(Expression):
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('operator', 'left', 'right', )
|
__slots__ = ('operator', 'left', 'right', )
|
||||||
|
|
||||||
operator: str
|
operator: Union[str, type3typeclasses.Type3ClassMethod]
|
||||||
left: Expression
|
left: Expression
|
||||||
right: Expression
|
right: Expression
|
||||||
|
|
||||||
def __init__(self, operator: str, left: Expression, right: Expression) -> None:
|
def __init__(self, operator: Union[str, type3typeclasses.Type3ClassMethod], left: Expression, right: Expression) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.operator = operator
|
self.operator = operator
|
||||||
@ -169,10 +170,10 @@ class FunctionCall(Expression):
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('function', 'arguments', )
|
__slots__ = ('function', 'arguments', )
|
||||||
|
|
||||||
function: 'Function'
|
function: Union['Function', type3typeclasses.Type3ClassMethod]
|
||||||
arguments: List[Expression]
|
arguments: List[Expression]
|
||||||
|
|
||||||
def __init__(self, function: 'Function') -> None:
|
def __init__(self, function: Union['Function', type3typeclasses.Type3ClassMethod]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.function = function
|
self.function = function
|
||||||
|
|||||||
@ -32,8 +32,22 @@ from .ourlang import (
|
|||||||
UnaryOp,
|
UnaryOp,
|
||||||
VariableReference,
|
VariableReference,
|
||||||
)
|
)
|
||||||
|
from .type3 import typeclasses as type3typeclasses
|
||||||
from .type3 import types as type3types
|
from .type3 import types as type3types
|
||||||
|
|
||||||
|
PRELUDE_OPERATORS = {
|
||||||
|
**type3typeclasses.Eq.operators,
|
||||||
|
**type3typeclasses.Fractional.operators,
|
||||||
|
**type3typeclasses.Integral.operators,
|
||||||
|
**type3typeclasses.Num.operators,
|
||||||
|
}
|
||||||
|
|
||||||
|
PRELUDE_METHODS = {
|
||||||
|
**type3typeclasses.Eq.methods,
|
||||||
|
**type3typeclasses.Fractional.methods,
|
||||||
|
**type3typeclasses.Integral.methods,
|
||||||
|
**type3typeclasses.Num.methods,
|
||||||
|
}
|
||||||
|
|
||||||
def phasm_parse(source: str) -> Module:
|
def phasm_parse(source: str) -> Module:
|
||||||
"""
|
"""
|
||||||
@ -338,6 +352,8 @@ class OurVisitor:
|
|||||||
|
|
||||||
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
|
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
|
||||||
if isinstance(node, ast.BinOp):
|
if isinstance(node, ast.BinOp):
|
||||||
|
operator: Union[str, type3typeclasses.Type3ClassMethod]
|
||||||
|
|
||||||
if isinstance(node.op, ast.Add):
|
if isinstance(node.op, ast.Add):
|
||||||
operator = '+'
|
operator = '+'
|
||||||
elif isinstance(node.op, ast.Sub):
|
elif isinstance(node.op, ast.Sub):
|
||||||
@ -359,6 +375,9 @@ class OurVisitor:
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError(f'Operator {node.op}')
|
raise NotImplementedError(f'Operator {node.op}')
|
||||||
|
|
||||||
|
if operator in PRELUDE_OPERATORS:
|
||||||
|
operator = PRELUDE_OPERATORS[operator]
|
||||||
|
|
||||||
return BinaryOp(
|
return BinaryOp(
|
||||||
operator,
|
operator,
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
||||||
@ -391,6 +410,9 @@ class OurVisitor:
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError(f'Operator {node.ops}')
|
raise NotImplementedError(f'Operator {node.ops}')
|
||||||
|
|
||||||
|
if operator in PRELUDE_OPERATORS:
|
||||||
|
operator = PRELUDE_OPERATORS[operator]
|
||||||
|
|
||||||
return BinaryOp(
|
return BinaryOp(
|
||||||
operator,
|
operator,
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
|
||||||
@ -452,7 +474,11 @@ 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.struct_definitions:
|
func: Union[Function, type3typeclasses.Type3ClassMethod]
|
||||||
|
|
||||||
|
if node.func.id in PRELUDE_METHODS:
|
||||||
|
func = PRELUDE_METHODS[node.func.id]
|
||||||
|
elif node.func.id in module.struct_definitions:
|
||||||
struct_definition = module.struct_definitions[node.func.id]
|
struct_definition = module.struct_definitions[node.func.id]
|
||||||
struct_constructor = StructConstructor(struct_definition.struct_type3)
|
struct_constructor = StructConstructor(struct_definition.struct_type3)
|
||||||
|
|
||||||
@ -471,10 +497,12 @@ class OurVisitor:
|
|||||||
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(
|
unary_op = UnaryOp(
|
||||||
'cast',
|
'cast',
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
|
||||||
)
|
)
|
||||||
|
unary_op.type3 = type3types.u32
|
||||||
|
return unary_op
|
||||||
elif node.func.id == 'len':
|
elif node.func.id == 'len':
|
||||||
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')
|
||||||
@ -511,13 +539,15 @@ class OurVisitor:
|
|||||||
|
|
||||||
func = module.functions[node.func.id]
|
func = module.functions[node.func.id]
|
||||||
|
|
||||||
if len(func.posonlyargs) != len(node.args):
|
exp_arg_count = len(func.posonlyargs) if isinstance(func, Function) else len(func.signature) - 1
|
||||||
_raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given')
|
|
||||||
|
if exp_arg_count != len(node.args):
|
||||||
|
_raise_static_error(node, f'Function {node.func.id} requires {exp_arg_count} 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_expr)
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr)
|
||||||
for arg_expr, param in zip(node.args, func.posonlyargs)
|
for arg_expr in node.args
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -675,7 +705,7 @@ def _not_implemented(check: Any, msg: str) -> None:
|
|||||||
if not check:
|
if not check:
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
def _raise_static_error(node: Union[ast.mod, ast.stmt, ast.expr], msg: str) -> NoReturn:
|
def _raise_static_error(node: Union[ast.stmt, ast.expr], msg: str) -> NoReturn:
|
||||||
raise StaticError(
|
raise StaticError(
|
||||||
f'Static error on line {node.lineno}: {msg}'
|
f'Static error on line {node.lineno}: {msg}'
|
||||||
)
|
)
|
||||||
|
|||||||
@ -65,3 +65,99 @@ def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
|
|||||||
g.return_()
|
g.return_()
|
||||||
|
|
||||||
return i32('return') # To satisfy mypy
|
return i32('return') # To satisfy mypy
|
||||||
|
|
||||||
|
def u8_eq_equals(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.eq')
|
||||||
|
|
||||||
|
def u32_eq_equals(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.eq')
|
||||||
|
|
||||||
|
def u64_eq_equals(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.eq')
|
||||||
|
|
||||||
|
def i8_eq_equals(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.eq')
|
||||||
|
|
||||||
|
def i32_eq_equals(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.eq')
|
||||||
|
|
||||||
|
def i64_eq_equals(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.eq')
|
||||||
|
|
||||||
|
def f32_eq_equals(g: Generator) -> None:
|
||||||
|
g.add_statement('f32.eq')
|
||||||
|
|
||||||
|
def f64_eq_equals(g: Generator) -> None:
|
||||||
|
g.add_statement('f64.eq')
|
||||||
|
|
||||||
|
def f32_fractional_div(g: Generator) -> None:
|
||||||
|
g.add_statement('f32.div')
|
||||||
|
|
||||||
|
def f64_fractional_div(g: Generator) -> None:
|
||||||
|
g.add_statement('f64.div')
|
||||||
|
|
||||||
|
def u32_integral_div(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.div_u')
|
||||||
|
|
||||||
|
def u64_integral_div(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.div_u')
|
||||||
|
|
||||||
|
def i32_integral_div(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.div_s')
|
||||||
|
|
||||||
|
def i64_integral_div(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.div_s')
|
||||||
|
|
||||||
|
def u32_num_add(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.add')
|
||||||
|
|
||||||
|
def u64_num_add(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.add')
|
||||||
|
|
||||||
|
def i32_num_add(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.add')
|
||||||
|
|
||||||
|
def i64_num_add(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.add')
|
||||||
|
|
||||||
|
def f32_num_add(g: Generator) -> None:
|
||||||
|
g.add_statement('f32.add')
|
||||||
|
|
||||||
|
def f64_num_add(g: Generator) -> None:
|
||||||
|
g.add_statement('f64.add')
|
||||||
|
|
||||||
|
def u32_num_sub(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.sub')
|
||||||
|
|
||||||
|
def u64_num_sub(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.sub')
|
||||||
|
|
||||||
|
def i32_num_sub(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.sub')
|
||||||
|
|
||||||
|
def i64_num_sub(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.sub')
|
||||||
|
|
||||||
|
def f32_num_sub(g: Generator) -> None:
|
||||||
|
g.add_statement('f32.sub')
|
||||||
|
|
||||||
|
def f64_num_sub(g: Generator) -> None:
|
||||||
|
g.add_statement('f64.sub')
|
||||||
|
|
||||||
|
def u32_num_mul(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.mul')
|
||||||
|
|
||||||
|
def u64_num_mul(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.mul')
|
||||||
|
|
||||||
|
def i32_num_mul(g: Generator) -> None:
|
||||||
|
g.add_statement('i32.mul')
|
||||||
|
|
||||||
|
def i64_num_mul(g: Generator) -> None:
|
||||||
|
g.add_statement('i64.mul')
|
||||||
|
|
||||||
|
def f32_num_mul(g: Generator) -> None:
|
||||||
|
g.add_statement('f32.mul')
|
||||||
|
|
||||||
|
def f64_num_mul(g: Generator) -> None:
|
||||||
|
g.add_statement('f64.mul')
|
||||||
|
|||||||
@ -6,7 +6,7 @@ These need to be resolved before the program can be compiled.
|
|||||||
from typing import Dict, List, Optional, Tuple, Union
|
from typing import Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from .. import ourlang
|
from .. import ourlang
|
||||||
from . import types
|
from . import typeclasses, types
|
||||||
|
|
||||||
|
|
||||||
class Error:
|
class Error:
|
||||||
@ -288,21 +288,21 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
"""
|
"""
|
||||||
__slots__ = ('type_class3', 'type3', )
|
__slots__ = ('type_class3', 'type3', )
|
||||||
|
|
||||||
type_class3: str
|
type_class3: Union[str, typeclasses.Type3Class]
|
||||||
type3: types.Type3OrPlaceholder
|
type3: types.Type3OrPlaceholder
|
||||||
|
|
||||||
DATA = {
|
DATA = {
|
||||||
'u8': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
|
'u8': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
|
||||||
'u32': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
|
'u32': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
|
||||||
'u64': {'BitWiseOperation', 'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
|
'u64': {'BitWiseOperation', 'EqualComparison', 'StrictPartialOrder'},
|
||||||
'i32': {'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
|
'i32': {'EqualComparison', 'StrictPartialOrder'},
|
||||||
'i64': {'BasicMathOperation', 'EqualComparison', 'StrictPartialOrder'},
|
'i64': {'EqualComparison', 'StrictPartialOrder'},
|
||||||
'bytes': {'Foldable', 'Sized'},
|
'bytes': {'Foldable', 'Sized'},
|
||||||
'f32': {'BasicMathOperation', 'FloatingPoint'},
|
'f32': {'Fractional', 'FloatingPoint'},
|
||||||
'f64': {'BasicMathOperation', 'FloatingPoint'},
|
'f64': {'Fractional', 'FloatingPoint'},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
|
def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
|
||||||
super().__init__(comment=comment)
|
super().__init__(comment=comment)
|
||||||
|
|
||||||
self.type_class3 = type_class3
|
self.type_class3 = type_class3
|
||||||
@ -316,6 +316,10 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
if isinstance(typ, types.PlaceholderForType):
|
if isinstance(typ, types.PlaceholderForType):
|
||||||
return RequireTypeSubstitutes()
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
|
if isinstance(self.type_class3, typeclasses.Type3Class):
|
||||||
|
if self.type_class3 in typ.classes:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
if self.type_class3 in self.__class__.DATA.get(typ.name, set()):
|
if self.type_class3 in self.__class__.DATA.get(typ.name, set()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -325,7 +329,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
return (
|
return (
|
||||||
'{type3} derives {type_class3}',
|
'{type3} derives {type_class3}',
|
||||||
{
|
{
|
||||||
'type_class3': self.type_class3,
|
'type_class3': str(self.type_class3),
|
||||||
'type3': self.type3,
|
'type3': self.type3,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -6,6 +6,7 @@ The constraints solver can then try to resolve all constraints.
|
|||||||
from typing import Generator, List
|
from typing import Generator, List
|
||||||
|
|
||||||
from .. import ourlang
|
from .. import ourlang
|
||||||
|
from . import typeclasses as type3typeclasses
|
||||||
from . import types as type3types
|
from . import types as type3types
|
||||||
from .constraints import (
|
from .constraints import (
|
||||||
CanBeSubscriptedConstraint,
|
CanBeSubscriptedConstraint,
|
||||||
@ -65,6 +66,37 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
|
|||||||
raise NotImplementedError(expression, inp, inp.operator)
|
raise NotImplementedError(expression, inp, inp.operator)
|
||||||
|
|
||||||
if isinstance(inp, ourlang.BinaryOp):
|
if isinstance(inp, ourlang.BinaryOp):
|
||||||
|
if isinstance(inp.operator, type3typeclasses.Type3ClassMethod):
|
||||||
|
type_var_map = {
|
||||||
|
x: type3types.PlaceholderForType([])
|
||||||
|
for x in inp.operator.signature
|
||||||
|
if isinstance(x, type3typeclasses.TypeVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
yield from expression(ctx, inp.left)
|
||||||
|
yield from expression(ctx, inp.right)
|
||||||
|
|
||||||
|
for type_var in inp.operator.type3_class.args:
|
||||||
|
assert type_var in type_var_map # When can this happen?
|
||||||
|
|
||||||
|
yield MustImplementTypeClassConstraint(
|
||||||
|
inp.operator.type3_class,
|
||||||
|
type_var_map[type_var],
|
||||||
|
)
|
||||||
|
|
||||||
|
for sig_part, arg_expr in zip(inp.operator.signature, [inp.left, inp.right, inp]):
|
||||||
|
if isinstance(sig_part, type3typeclasses.TypeVariable):
|
||||||
|
yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(sig_part, type3typeclasses.TypeReference):
|
||||||
|
# On key error: We probably have to a lot of work to do refactoring
|
||||||
|
# the type lookups
|
||||||
|
exp_type = type3types.LOOKUP_TABLE[sig_part.name]
|
||||||
|
yield SameTypeConstraint(exp_type, arg_expr.type3)
|
||||||
|
continue
|
||||||
|
return
|
||||||
|
|
||||||
if inp.operator in ('|', '&', '^', ):
|
if inp.operator in ('|', '&', '^', ):
|
||||||
yield from expression(ctx, inp.left)
|
yield from expression(ctx, inp.left)
|
||||||
yield from expression(ctx, inp.right)
|
yield from expression(ctx, inp.right)
|
||||||
@ -83,15 +115,6 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
|
|||||||
comment=f'({inp.operator}) :: a -> a -> a')
|
comment=f'({inp.operator}) :: a -> a -> a')
|
||||||
return
|
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 == '==':
|
if inp.operator == '==':
|
||||||
yield from expression(ctx, inp.left)
|
yield from expression(ctx, inp.left)
|
||||||
yield from expression(ctx, inp.right)
|
yield from expression(ctx, inp.right)
|
||||||
@ -117,6 +140,39 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
|
|||||||
raise NotImplementedError(expression, inp)
|
raise NotImplementedError(expression, inp)
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionCall):
|
if isinstance(inp, ourlang.FunctionCall):
|
||||||
|
if isinstance(inp.function, type3typeclasses.Type3ClassMethod):
|
||||||
|
# FIXME: Duplicate code with BinaryOp
|
||||||
|
|
||||||
|
type_var_map = {
|
||||||
|
x: type3types.PlaceholderForType([])
|
||||||
|
for x in inp.function.signature
|
||||||
|
if isinstance(x, type3typeclasses.TypeVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
for call_arg in inp.arguments:
|
||||||
|
yield from expression(ctx, call_arg)
|
||||||
|
|
||||||
|
for type_var in inp.function.type3_class.args:
|
||||||
|
assert type_var in type_var_map # When can this happen?
|
||||||
|
|
||||||
|
yield MustImplementTypeClassConstraint(
|
||||||
|
inp.function.type3_class,
|
||||||
|
type_var_map[type_var],
|
||||||
|
)
|
||||||
|
|
||||||
|
for sig_part, arg_expr in zip(inp.function.signature, inp.arguments + [inp]):
|
||||||
|
if isinstance(sig_part, type3typeclasses.TypeVariable):
|
||||||
|
yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(sig_part, type3typeclasses.TypeReference):
|
||||||
|
# On key error: We probably have to a lot of work to do refactoring
|
||||||
|
# the type lookups
|
||||||
|
exp_type = type3types.LOOKUP_TABLE[sig_part.name]
|
||||||
|
yield SameTypeConstraint(exp_type, arg_expr.type3)
|
||||||
|
continue
|
||||||
|
return
|
||||||
|
|
||||||
yield SameTypeConstraint(inp.function.returns_type3, inp.type3,
|
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')
|
comment=f'The type of a function call to {inp.function.name} is the same as the type that the function returns')
|
||||||
|
|
||||||
|
|||||||
103
phasm/type3/typeclasses.py
Normal file
103
phasm/type3/typeclasses.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
from typing import Any, Dict, Iterable, List, Mapping, Union
|
||||||
|
|
||||||
|
|
||||||
|
class TypeVariable:
|
||||||
|
__slots__ = ('letter', )
|
||||||
|
|
||||||
|
letter: str
|
||||||
|
|
||||||
|
def __init__(self, letter: str) -> None:
|
||||||
|
assert len(letter) == 1, f'{letter} is not a valid type variable'
|
||||||
|
self.letter = letter
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.letter)
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
if not isinstance(other, TypeVariable):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
return self.letter == other.letter
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'TypeVariable({repr(self.letter)})'
|
||||||
|
|
||||||
|
class TypeReference:
|
||||||
|
__slots__ = ('name', )
|
||||||
|
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
assert len(name) > 1, f'{name} is not a valid type reference'
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.name)
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
if not isinstance(other, TypeReference):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
return self.name == other.name
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'TypeReference({repr(self.name)})'
|
||||||
|
|
||||||
|
class Type3ClassMethod:
|
||||||
|
__slots__ = ('type3_class', 'name', 'signature', )
|
||||||
|
|
||||||
|
type3_class: 'Type3Class'
|
||||||
|
name: str
|
||||||
|
signature: List[Union[TypeReference, TypeVariable]]
|
||||||
|
|
||||||
|
def __init__(self, type3_class: 'Type3Class', name: str, signature: str) -> None:
|
||||||
|
self.type3_class = type3_class
|
||||||
|
self.name = name
|
||||||
|
self.signature = [
|
||||||
|
TypeVariable(x) if len(x) == 1 else TypeReference(x)
|
||||||
|
for x in signature.split(' -> ')
|
||||||
|
]
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'Type3ClassMethod({repr(self.type3_class)}, {repr(self.name)}, {repr(self.signature)})'
|
||||||
|
|
||||||
|
class Type3Class:
|
||||||
|
__slots__ = ('name', 'args', 'methods', 'operators', )
|
||||||
|
|
||||||
|
name: str
|
||||||
|
args: List[TypeVariable]
|
||||||
|
methods: Dict[str, Type3ClassMethod]
|
||||||
|
operators: Dict[str, Type3ClassMethod]
|
||||||
|
|
||||||
|
def __init__(self, name: str, args: Iterable[str], methods: Mapping[str, str], operators: Mapping[str, str]) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.args = [TypeVariable(x) for x in args]
|
||||||
|
self.methods = {
|
||||||
|
k: Type3ClassMethod(self, k, v)
|
||||||
|
for k, v in methods.items()
|
||||||
|
}
|
||||||
|
self.operators = {
|
||||||
|
k: Type3ClassMethod(self, k, v)
|
||||||
|
for k, v in operators.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
Eq = Type3Class('Eq', ['a'], methods={}, operators={
|
||||||
|
'==': 'a -> a -> bool',
|
||||||
|
})
|
||||||
|
|
||||||
|
Fractional = Type3Class('Fractional', ['a'], methods={}, operators={
|
||||||
|
'/': 'a -> a -> a',
|
||||||
|
})
|
||||||
|
|
||||||
|
Integral = Type3Class('Eq', ['a'], methods={
|
||||||
|
'div': 'a -> a -> a',
|
||||||
|
}, operators={})
|
||||||
|
|
||||||
|
Num = Type3Class('Num', ['a'], methods={}, operators={
|
||||||
|
'+': 'a -> a -> a',
|
||||||
|
'-': 'a -> a -> a',
|
||||||
|
'*': 'a -> a -> a',
|
||||||
|
})
|
||||||
@ -6,6 +6,8 @@ constraint generator works with.
|
|||||||
"""
|
"""
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Protocol, Union
|
from typing import Any, Dict, Iterable, List, Optional, Protocol, Union
|
||||||
|
|
||||||
|
from .typeclasses import Eq, Fractional, Integral, Num, Type3Class
|
||||||
|
|
||||||
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
|
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
|
||||||
|
|
||||||
class ExpressionProtocol(Protocol):
|
class ExpressionProtocol(Protocol):
|
||||||
@ -22,18 +24,24 @@ class Type3:
|
|||||||
"""
|
"""
|
||||||
Base class for the type3 types
|
Base class for the type3 types
|
||||||
"""
|
"""
|
||||||
__slots__ = ('name', )
|
__slots__ = ('name', 'classes', )
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
"""
|
"""
|
||||||
The name of the string, as parsed and outputted by codestyle.
|
The name of the string, as parsed and outputted by codestyle.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str) -> None:
|
classes: List[Type3Class]
|
||||||
|
"""
|
||||||
|
The type classes that this type implements
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, classes: Iterable[Type3Class]) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.classes = [*classes]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'Type3("{self.name}")'
|
return f'Type3({repr(self.name)}, {repr(self.classes)})'
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
@ -79,7 +87,7 @@ class IntType3(Type3):
|
|||||||
value: int
|
value: int
|
||||||
|
|
||||||
def __init__(self, value: int) -> None:
|
def __init__(self, value: int) -> None:
|
||||||
super().__init__(str(value))
|
super().__init__(str(value), [])
|
||||||
|
|
||||||
assert 0 <= value
|
assert 0 <= value
|
||||||
self.value = value
|
self.value = value
|
||||||
@ -164,7 +172,8 @@ class AppliedType3(Type3):
|
|||||||
base.name
|
base.name
|
||||||
+ ' ('
|
+ ' ('
|
||||||
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
|
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
|
||||||
+ ')'
|
+ ')',
|
||||||
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.base = base
|
self.base = base
|
||||||
@ -213,7 +222,7 @@ class StructType3(Type3):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str, members: Dict[str, Type3]) -> None:
|
def __init__(self, name: str, members: Dict[str, Type3]) -> None:
|
||||||
super().__init__(name)
|
super().__init__(name, [])
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.members = dict(members)
|
self.members = dict(members)
|
||||||
@ -221,38 +230,40 @@ class StructType3(Type3):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'StructType3(repr({self.name}), repr({self.members}))'
|
return f'StructType3(repr({self.name}), repr({self.members}))'
|
||||||
|
|
||||||
none = PrimitiveType3('none')
|
none = PrimitiveType3('none', [])
|
||||||
"""
|
"""
|
||||||
The none type, for when functions simply don't return anything. e.g., IO().
|
The none type, for when functions simply don't return anything. e.g., IO().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bool_ = PrimitiveType3('bool')
|
bool_ = PrimitiveType3('bool', [])
|
||||||
"""
|
"""
|
||||||
The bool type, either True or False
|
The bool type, either True or False
|
||||||
|
|
||||||
|
Suffixes with an underscores, as it's a Python builtin
|
||||||
"""
|
"""
|
||||||
|
|
||||||
u8 = PrimitiveType3('u8')
|
u8 = PrimitiveType3('u8', [Eq, Integral])
|
||||||
"""
|
"""
|
||||||
The unsigned 8-bit integer type.
|
The unsigned 8-bit integer type.
|
||||||
|
|
||||||
Operations on variables employ modular arithmetic, with modulus 2^8.
|
Operations on variables employ modular arithmetic, with modulus 2^8.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
u32 = PrimitiveType3('u32')
|
u32 = PrimitiveType3('u32', [Eq, Integral, Num])
|
||||||
"""
|
"""
|
||||||
The unsigned 32-bit integer type.
|
The unsigned 32-bit integer type.
|
||||||
|
|
||||||
Operations on variables employ modular arithmetic, with modulus 2^32.
|
Operations on variables employ modular arithmetic, with modulus 2^32.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
u64 = PrimitiveType3('u64')
|
u64 = PrimitiveType3('u64', [Eq, Integral, Num])
|
||||||
"""
|
"""
|
||||||
The unsigned 64-bit integer type.
|
The unsigned 64-bit integer type.
|
||||||
|
|
||||||
Operations on variables employ modular arithmetic, with modulus 2^64.
|
Operations on variables employ modular arithmetic, with modulus 2^64.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
i8 = PrimitiveType3('i8')
|
i8 = PrimitiveType3('i8', [Eq, Integral])
|
||||||
"""
|
"""
|
||||||
The signed 8-bit integer type.
|
The signed 8-bit integer type.
|
||||||
|
|
||||||
@ -260,7 +271,7 @@ Operations on variables employ modular arithmetic, with modulus 2^8, but
|
|||||||
with the middel point being 0.
|
with the middel point being 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
i32 = PrimitiveType3('i32')
|
i32 = PrimitiveType3('i32', [Eq, Integral, Num])
|
||||||
"""
|
"""
|
||||||
The unsigned 32-bit integer type.
|
The unsigned 32-bit integer type.
|
||||||
|
|
||||||
@ -268,7 +279,7 @@ Operations on variables employ modular arithmetic, with modulus 2^32, but
|
|||||||
with the middel point being 0.
|
with the middel point being 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
i64 = PrimitiveType3('i64')
|
i64 = PrimitiveType3('i64', [Eq, Integral, Num])
|
||||||
"""
|
"""
|
||||||
The unsigned 64-bit integer type.
|
The unsigned 64-bit integer type.
|
||||||
|
|
||||||
@ -276,22 +287,22 @@ Operations on variables employ modular arithmetic, with modulus 2^64, but
|
|||||||
with the middel point being 0.
|
with the middel point being 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
f32 = PrimitiveType3('f32')
|
f32 = PrimitiveType3('f32', [Eq, Fractional, Num])
|
||||||
"""
|
"""
|
||||||
A 32-bits IEEE 754 float, of 32 bits width.
|
A 32-bits IEEE 754 float, of 32 bits width.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
f64 = PrimitiveType3('f64')
|
f64 = PrimitiveType3('f64', [Eq, Fractional, Num])
|
||||||
"""
|
"""
|
||||||
A 32-bits IEEE 754 float, of 64 bits width.
|
A 32-bits IEEE 754 float, of 64 bits width.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bytes = PrimitiveType3('bytes')
|
bytes = PrimitiveType3('bytes', [])
|
||||||
"""
|
"""
|
||||||
This is a runtime-determined length piece of memory that can be indexed at runtime.
|
This is a runtime-determined length piece of memory that can be indexed at runtime.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
static_array = PrimitiveType3('static_array')
|
static_array = PrimitiveType3('static_array', [])
|
||||||
"""
|
"""
|
||||||
This is a fixed length piece of memory that can be indexed at runtime.
|
This is a fixed length piece of memory that can be indexed at runtime.
|
||||||
|
|
||||||
@ -299,7 +310,7 @@ It should be applied with one argument. It has a runtime-dynamic length
|
|||||||
of the same type repeated.
|
of the same type repeated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tuple = PrimitiveType3('tuple') # pylint: disable=W0622
|
tuple = PrimitiveType3('tuple', []) # pylint: disable=W0622
|
||||||
"""
|
"""
|
||||||
This is a fixed length piece of memory.
|
This is a fixed length piece of memory.
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,19 @@
|
|||||||
mypy==0.991
|
marko==2.1.3
|
||||||
pygments==2.12.0
|
mypy==1.15.0
|
||||||
pytest==7.2.0
|
pygments==2.19.1
|
||||||
|
pytest==8.3.5
|
||||||
pytest-integration==0.2.2
|
pytest-integration==0.2.2
|
||||||
pywasm==1.0.7
|
ruff==0.11.4
|
||||||
pywasm3==0.5.0
|
|
||||||
ruff==0.1.5
|
|
||||||
wasmer==1.1.0
|
wasmtime==31.0.0
|
||||||
wasmer_compiler_cranelift==1.1.0
|
|
||||||
wasmtime==3.0.0
|
# TODO:
|
||||||
|
# extism?
|
||||||
|
# wasmedge
|
||||||
|
|
||||||
|
# Check 2025-04-05
|
||||||
|
# wasm3: minimal maintenance phase
|
||||||
|
# py-wasm: last updated 6 years ago
|
||||||
|
# wasmer-python: Not compatible with python3.12, last updated 2 years ago
|
||||||
|
# WAVM: Last updated 3 years ago
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
from typing import Any, Dict, List, Optional, Union
|
|
||||||
|
|
||||||
from . import binary
|
|
||||||
from . import option
|
|
||||||
from . import execution
|
|
||||||
|
|
||||||
class Runtime:
|
|
||||||
store: execution.Store
|
|
||||||
|
|
||||||
def __init__(self, module: binary.Module, imps: Optional[Dict[str, Any]] = None, opts: Optional[option.Option] = None):
|
|
||||||
...
|
|
||||||
|
|
||||||
def exec(self, name: str, args: List[Union[int, float]]) -> Any:
|
|
||||||
...
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
from typing import BinaryIO
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
@classmethod
|
|
||||||
def from_reader(cls, reader: BinaryIO) -> 'Module':
|
|
||||||
...
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
|
|
||||||
class Result:
|
|
||||||
...
|
|
||||||
|
|
||||||
class MemoryInstance:
|
|
||||||
data: bytearray
|
|
||||||
|
|
||||||
class Store:
|
|
||||||
memory_list: List[MemoryInstance]
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
class Option:
|
|
||||||
...
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
from typing import Any, Callable
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Runtime:
|
|
||||||
...
|
|
||||||
|
|
||||||
def load(self, wasm_bin: Module) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
def get_memory(self, memid: int) -> memoryview:
|
|
||||||
...
|
|
||||||
|
|
||||||
def find_function(self, name: str) -> Callable[[Any], Any]:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Environment:
|
|
||||||
def new_runtime(self, mem_size: int) -> Runtime:
|
|
||||||
...
|
|
||||||
|
|
||||||
def parse_module(self, wasm_bin: bytes) -> Module:
|
|
||||||
...
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
from typing import Any, Dict, Callable, Union
|
|
||||||
|
|
||||||
def wat2wasm(inp: str) -> bytes:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Store:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Function:
|
|
||||||
def __init__(self, store: Store, func: Callable[[Any], Any]) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
def __init__(self, store: Store, wasm: bytes) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Uint8Array:
|
|
||||||
def __getitem__(self, index: Union[int, slice]) -> int:
|
|
||||||
...
|
|
||||||
|
|
||||||
def __setitem__(self, idx: int, value: int) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Memory:
|
|
||||||
def uint8_view(self, offset: int = 0) -> Uint8Array:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Exports:
|
|
||||||
...
|
|
||||||
|
|
||||||
class ImportObject:
|
|
||||||
def register(self, region: str, values: Dict[str, Function]) -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
class Instance:
|
|
||||||
exports: Exports
|
|
||||||
|
|
||||||
def __init__(self, module: Module, imports: ImportObject) -> None:
|
|
||||||
...
|
|
||||||
@ -16,10 +16,7 @@ class SuiteResult:
|
|||||||
self.returned_value = None
|
self.returned_value = None
|
||||||
|
|
||||||
RUNNER_CLASS_MAP = {
|
RUNNER_CLASS_MAP = {
|
||||||
'pywasm': runners.RunnerPywasm,
|
|
||||||
'pywasm3': runners.RunnerPywasm3,
|
|
||||||
'wasmtime': runners.RunnerWasmtime,
|
'wasmtime': runners.RunnerWasmtime,
|
||||||
'wasmer': runners.RunnerWasmer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Suite:
|
class Suite:
|
||||||
@ -29,7 +26,7 @@ class Suite:
|
|||||||
def __init__(self, code_py: str) -> None:
|
def __init__(self, code_py: str) -> None:
|
||||||
self.code_py = code_py
|
self.code_py = code_py
|
||||||
|
|
||||||
def run_code(self, *args: Any, runtime: str = 'pywasm3', func_name: str = 'testEntry', imports: runners.Imports = None) -> Any:
|
def run_code(self, *args: Any, runtime: str = 'wasmtime', func_name: str = 'testEntry', imports: runners.Imports = None, do_format_check: bool = True) -> Any:
|
||||||
"""
|
"""
|
||||||
Compiles the given python code into wasm and
|
Compiles the given python code into wasm and
|
||||||
then runs it
|
then runs it
|
||||||
@ -50,11 +47,11 @@ class Suite:
|
|||||||
write_header(sys.stderr, 'Assembly')
|
write_header(sys.stderr, 'Assembly')
|
||||||
runner.dump_wasm_wat(sys.stderr)
|
runner.dump_wasm_wat(sys.stderr)
|
||||||
|
|
||||||
runner.compile_wasm()
|
|
||||||
runner.interpreter_setup()
|
runner.interpreter_setup()
|
||||||
runner.interpreter_load(imports)
|
runner.interpreter_load(imports)
|
||||||
|
|
||||||
# Check if code formatting works
|
# Check if code formatting works
|
||||||
|
if do_format_check:
|
||||||
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
||||||
|
|
||||||
func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs]
|
func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs]
|
||||||
@ -252,6 +249,10 @@ def _load_memory_stored_returned_value(
|
|||||||
if ret_type3 is type3types.none:
|
if ret_type3 is type3types.none:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if ret_type3 is type3types.bool_:
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
return 0 != wasm_value
|
||||||
|
|
||||||
if ret_type3 in (type3types.i32, type3types.i64):
|
if ret_type3 in (type3types.i32, type3types.i64):
|
||||||
assert isinstance(wasm_value, int), wasm_value
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
return wasm_value
|
return wasm_value
|
||||||
|
|||||||
@ -2,12 +2,8 @@
|
|||||||
Runners to help run WebAssembly code on various interpreters
|
Runners to help run WebAssembly code on various interpreters
|
||||||
"""
|
"""
|
||||||
import ctypes
|
import ctypes
|
||||||
import io
|
|
||||||
from typing import Any, Callable, Dict, Iterable, Optional, TextIO
|
from typing import Any, Callable, Dict, Iterable, Optional, TextIO
|
||||||
|
|
||||||
import pywasm.binary
|
|
||||||
import wasm3
|
|
||||||
import wasmer
|
|
||||||
import wasmtime
|
import wasmtime
|
||||||
|
|
||||||
from phasm import ourlang, wasm
|
from phasm import ourlang, wasm
|
||||||
@ -65,7 +61,7 @@ class RunnerBase:
|
|||||||
"""
|
"""
|
||||||
Compiles the WebAssembly AST into WebAssembly Binary
|
Compiles the WebAssembly AST into WebAssembly Binary
|
||||||
"""
|
"""
|
||||||
self.wasm_bin = wasmer.wat2wasm(self.wasm_asm)
|
raise NotImplementedError
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
def interpreter_setup(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -103,77 +99,6 @@ class RunnerBase:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
class RunnerPywasm(RunnerBase):
|
|
||||||
"""
|
|
||||||
Implements a runner for pywasm
|
|
||||||
|
|
||||||
See https://pypi.org/project/pywasm/
|
|
||||||
"""
|
|
||||||
module: pywasm.binary.Module
|
|
||||||
runtime: pywasm.Runtime
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
|
||||||
# Nothing to set up
|
|
||||||
pass
|
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
|
||||||
if imports is not None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
bytesio = io.BytesIO(self.wasm_bin)
|
|
||||||
self.module = pywasm.binary.Module.from_reader(bytesio)
|
|
||||||
self.runtime = pywasm.Runtime(self.module, {}, None)
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
|
||||||
for idx, byt in enumerate(data):
|
|
||||||
self.runtime.store.memory_list[0].data[offset + idx] = byt
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
return self.runtime.store.memory_list[0].data[offset:length]
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
|
||||||
_dump_memory(textio, self.runtime.store.memory_list[0].data)
|
|
||||||
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
return self.runtime.exec(function, [*args])
|
|
||||||
|
|
||||||
class RunnerPywasm3(RunnerBase):
|
|
||||||
"""
|
|
||||||
Implements a runner for pywasm3
|
|
||||||
|
|
||||||
See https://pypi.org/project/pywasm3/
|
|
||||||
"""
|
|
||||||
env: wasm3.Environment
|
|
||||||
rtime: wasm3.Runtime
|
|
||||||
mod: wasm3.Module
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
|
||||||
self.env = wasm3.Environment()
|
|
||||||
self.rtime = self.env.new_runtime(1024 * 1024)
|
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
|
||||||
if imports is not None:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
self.mod = self.env.parse_module(self.wasm_bin)
|
|
||||||
self.rtime.load(self.mod)
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
|
||||||
memory = self.rtime.get_memory(0)
|
|
||||||
|
|
||||||
for idx, byt in enumerate(data):
|
|
||||||
memory[offset + idx] = byt
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
memory = self.rtime.get_memory(0)
|
|
||||||
return memory[offset:offset + length].tobytes()
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
|
||||||
_dump_memory(textio, self.rtime.get_memory(0))
|
|
||||||
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
return self.rtime.find_function(function)(*args)
|
|
||||||
|
|
||||||
class RunnerWasmtime(RunnerBase):
|
class RunnerWasmtime(RunnerBase):
|
||||||
"""
|
"""
|
||||||
Implements a runner for wasmtime
|
Implements a runner for wasmtime
|
||||||
@ -184,15 +109,44 @@ class RunnerWasmtime(RunnerBase):
|
|||||||
module: wasmtime.Module
|
module: wasmtime.Module
|
||||||
instance: wasmtime.Instance
|
instance: wasmtime.Instance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def func2type(cls, func: Callable[[Any], Any]) -> wasmtime.FuncType:
|
||||||
|
params: list[wasmtime.ValType] = []
|
||||||
|
|
||||||
|
code = func.__code__
|
||||||
|
for idx in range(code.co_argcount):
|
||||||
|
varname = code.co_varnames[idx]
|
||||||
|
vartype = func.__annotations__[varname]
|
||||||
|
|
||||||
|
if vartype is int:
|
||||||
|
params.append(wasmtime.ValType.i32())
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
results: list[wasmtime.ValType] = []
|
||||||
|
if func.__annotations__['return'] is None:
|
||||||
|
pass # No return value
|
||||||
|
elif func.__annotations__['return'] is int:
|
||||||
|
results.append(wasmtime.ValType.i32())
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Return type', func.__annotations__['return'])
|
||||||
|
|
||||||
|
return wasmtime.FuncType(params, results)
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
def interpreter_setup(self) -> None:
|
||||||
self.store = wasmtime.Store()
|
self.store = wasmtime.Store()
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
||||||
if imports is not None:
|
functions: list[wasmtime.Func] = []
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
self.module = wasmtime.Module(self.store.engine, self.wasm_bin)
|
if imports is not None:
|
||||||
self.instance = wasmtime.Instance(self.store, self.module, [])
|
functions = [
|
||||||
|
wasmtime.Func(self.store, self.__class__.func2type(f), f)
|
||||||
|
for f in imports.values()
|
||||||
|
]
|
||||||
|
|
||||||
|
self.module = wasmtime.Module(self.store.engine, self.wasm_asm)
|
||||||
|
self.instance = wasmtime.Instance(self.store, self.module, functions)
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
||||||
exports = self.instance.exports(self.store)
|
exports = self.instance.exports(self.store)
|
||||||
@ -217,8 +171,7 @@ class RunnerWasmtime(RunnerBase):
|
|||||||
data_len = memory.data_len(self.store)
|
data_len = memory.data_len(self.store)
|
||||||
|
|
||||||
raw = ctypes.string_at(data_ptr, data_len)
|
raw = ctypes.string_at(data_ptr, data_len)
|
||||||
|
return raw[offset:offset + length]
|
||||||
return raw[offset:length]
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
||||||
exports = self.instance.exports(self.store)
|
exports = self.instance.exports(self.store)
|
||||||
@ -237,63 +190,6 @@ class RunnerWasmtime(RunnerBase):
|
|||||||
|
|
||||||
return func(self.store, *args)
|
return func(self.store, *args)
|
||||||
|
|
||||||
class RunnerWasmer(RunnerBase):
|
|
||||||
"""
|
|
||||||
Implements a runner for wasmer
|
|
||||||
|
|
||||||
See https://pypi.org/project/wasmer/
|
|
||||||
"""
|
|
||||||
|
|
||||||
# pylint: disable=E1101
|
|
||||||
|
|
||||||
store: wasmer.Store
|
|
||||||
module: wasmer.Module
|
|
||||||
instance: wasmer.Instance
|
|
||||||
|
|
||||||
def interpreter_setup(self) -> None:
|
|
||||||
self.store = wasmer.Store()
|
|
||||||
|
|
||||||
def interpreter_load(self, imports: Optional[Dict[str, Callable[[Any], Any]]] = None) -> None:
|
|
||||||
import_object = wasmer.ImportObject()
|
|
||||||
if imports:
|
|
||||||
import_object.register('imports', {
|
|
||||||
k: wasmer.Function(self.store, v)
|
|
||||||
for k, v in (imports or {}).items()
|
|
||||||
})
|
|
||||||
|
|
||||||
self.module = wasmer.Module(self.store, self.wasm_bin)
|
|
||||||
self.instance = wasmer.Instance(self.module, import_object)
|
|
||||||
|
|
||||||
def interpreter_write_memory(self, offset: int, data: Iterable[int]) -> None:
|
|
||||||
exports = self.instance.exports
|
|
||||||
memory = getattr(exports, 'memory')
|
|
||||||
assert isinstance(memory, wasmer.Memory)
|
|
||||||
view = memory.uint8_view(offset)
|
|
||||||
|
|
||||||
for idx, byt in enumerate(data):
|
|
||||||
view[idx] = byt
|
|
||||||
|
|
||||||
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
|
||||||
exports = self.instance.exports
|
|
||||||
memory = getattr(exports, 'memory')
|
|
||||||
assert isinstance(memory, wasmer.Memory)
|
|
||||||
view = memory.uint8_view(offset)
|
|
||||||
return bytes(view[offset:length])
|
|
||||||
|
|
||||||
def interpreter_dump_memory(self, textio: TextIO) -> None:
|
|
||||||
exports = self.instance.exports
|
|
||||||
memory = getattr(exports, 'memory')
|
|
||||||
assert isinstance(memory, wasmer.Memory)
|
|
||||||
view = memory.uint8_view()
|
|
||||||
|
|
||||||
_dump_memory(textio, view) # type: ignore
|
|
||||||
|
|
||||||
def call(self, function: str, *args: Any) -> Any:
|
|
||||||
exports = self.instance.exports
|
|
||||||
func = getattr(exports, function)
|
|
||||||
|
|
||||||
return func(*args)
|
|
||||||
|
|
||||||
def _dump_memory(textio: TextIO, mem: bytes) -> None:
|
def _dump_memory(textio: TextIO, mem: bytes) -> None:
|
||||||
line_width = 16
|
line_width = 16
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ def test_index():
|
|||||||
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
|
with open('examples/buffer.py', 'r', encoding='ASCII') as fil:
|
||||||
code_py = "\n" + fil.read()
|
code_py = "\n" + fil.read()
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'Hello, world!', 5, func_name='index', runtime='wasmtime')
|
result = Suite(code_py).run_code(b'Hello, world!', 5, func_name='index')
|
||||||
assert 44 == result.returned_value
|
assert 44 == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.slow_integration_test
|
@pytest.mark.slow_integration_test
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import binascii
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
@ -7,28 +5,15 @@ from ..helpers import Suite
|
|||||||
|
|
||||||
@pytest.mark.slow_integration_test
|
@pytest.mark.slow_integration_test
|
||||||
def test_crc32():
|
def test_crc32():
|
||||||
# FIXME: Stub
|
with open('examples/crc32.py', 'r', encoding='ASCII') as fil:
|
||||||
# crc = 0xFFFFFFFF
|
code_py = "\n" + fil.read()
|
||||||
# byt = 0x61
|
|
||||||
# => (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ byt]
|
|
||||||
# (crc >> 8) = 0x00FFFFFF
|
|
||||||
# => 0x00FFFFFF ^ _CRC32_Table[(crc & 0xFF) ^ byt]
|
|
||||||
# (crc & 0xFF) = 0xFF
|
|
||||||
# => 0x00FFFFFF ^ _CRC32_Table[0xFF ^ byt]
|
|
||||||
# 0xFF ^ 0x61 = 0x9E
|
|
||||||
# => 0x00FFFFFF ^ _CRC32_Table[0x9E]
|
|
||||||
# _CRC32_Table[0x9E] = 0x17b7be43
|
|
||||||
# => 0x00FFFFFF ^ 0x17b7be43
|
|
||||||
|
|
||||||
code_py = """
|
# https://reveng.sourceforge.io/crc-catalogue/legend.htm#crc.legend.params
|
||||||
def _crc32_f(crc: u32, byt: u8) -> u32:
|
in_put = b'123456789'
|
||||||
return 16777215 ^ 397917763
|
|
||||||
|
|
||||||
def testEntry(data: bytes) -> u32:
|
# https://reveng.sourceforge.io/crc-catalogue/17plus.htm#crc.cat.crc-32-iso-hdlc
|
||||||
return 4294967295 ^ _crc32_f(4294967295, data[0])
|
check = 0xcbf43926
|
||||||
"""
|
|
||||||
exp_result = binascii.crc32(b'a')
|
|
||||||
|
|
||||||
result = Suite(code_py).run_code(b'a')
|
result = Suite(code_py).run_code(in_put, func_name='crc32', do_format_check=False)
|
||||||
|
|
||||||
assert exp_result == result.returned_value
|
assert check == result.returned_value
|
||||||
|
|||||||
@ -102,6 +102,7 @@ def generate_code(markdown, template, settings):
|
|||||||
print()
|
print()
|
||||||
print('from ..helpers import Suite')
|
print('from ..helpers import Suite')
|
||||||
print()
|
print()
|
||||||
|
print()
|
||||||
|
|
||||||
for test in get_tests(template):
|
for test in get_tests(template):
|
||||||
assert len(test) == 4, test
|
assert len(test) == 4, test
|
||||||
@ -121,7 +122,7 @@ def generate_code(markdown, template, settings):
|
|||||||
print('@pytest.mark.integration_test')
|
print('@pytest.mark.integration_test')
|
||||||
print(f'def test_{type_name}_{test_id}():')
|
print(f'def test_{type_name}_{test_id}():')
|
||||||
print(' """')
|
print(' """')
|
||||||
print(' ' + user_story.replace('\n', '\n '))
|
print(' ' + user_story.strip().replace('\n', '\n '))
|
||||||
print(' """')
|
print(' """')
|
||||||
print(' code_py = """')
|
print(' code_py = """')
|
||||||
if 'CODE_HEADER' in settings:
|
if 'CODE_HEADER' in settings:
|
||||||
|
|||||||
@ -3,16 +3,15 @@ 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 RunnerWasmtime
|
||||||
|
|
||||||
|
|
||||||
def setup_interpreter(phash_code: str) -> RunnerPywasm:
|
def setup_interpreter(phash_code: str) -> RunnerWasmtime:
|
||||||
runner = RunnerPywasm(phash_code)
|
runner = RunnerWasmtime(phash_code)
|
||||||
|
|
||||||
runner.parse()
|
runner.parse()
|
||||||
runner.compile_ast()
|
runner.compile_ast()
|
||||||
runner.compile_wat()
|
runner.compile_wat()
|
||||||
runner.compile_wasm()
|
|
||||||
runner.interpreter_setup()
|
runner.interpreter_setup()
|
||||||
runner.interpreter_load()
|
runner.interpreter_load()
|
||||||
|
|
||||||
@ -38,16 +37,16 @@ def testEntry(b: bytes) -> u8:
|
|||||||
result = suite.run_code(b'')
|
result = suite.run_code(b'')
|
||||||
assert 128 == result.returned_value
|
assert 128 == result.returned_value
|
||||||
|
|
||||||
result = suite.run_code(b'\x80', runtime='pywasm')
|
result = suite.run_code(b'\x80')
|
||||||
assert 128 == result.returned_value
|
assert 128 == result.returned_value
|
||||||
|
|
||||||
result = suite.run_code(b'\x80\x40', runtime='pywasm')
|
result = suite.run_code(b'\x80\x40')
|
||||||
assert 192 == result.returned_value
|
assert 192 == result.returned_value
|
||||||
|
|
||||||
result = suite.run_code(b'\x80\x40\x20\x10', runtime='pywasm')
|
result = suite.run_code(b'\x80\x40\x20\x10')
|
||||||
assert 240 == result.returned_value
|
assert 240 == result.returned_value
|
||||||
|
|
||||||
result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01', runtime='pywasm')
|
result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01')
|
||||||
assert 255 == result.returned_value
|
assert 255 == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
|
|||||||
101
tests/integration/test_lang/test_eq.py
Normal file
101
tests/integration/test_lang/test_eq.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from phasm.type3.entry import Type3Exception
|
||||||
|
|
||||||
|
from ..helpers import Suite
|
||||||
|
|
||||||
|
INT_TYPES = ['u8', 'u32', 'u64', 'i8', 'i32', 'i64']
|
||||||
|
FLOAT_TYPES = ['f32', 'f64']
|
||||||
|
|
||||||
|
TYPE_MAP = {
|
||||||
|
'u8': int,
|
||||||
|
'u32': int,
|
||||||
|
'u64': int,
|
||||||
|
'i8': int,
|
||||||
|
'i32': int,
|
||||||
|
'i64': int,
|
||||||
|
'f32': float,
|
||||||
|
'f64': float,
|
||||||
|
}
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_equals_not_implemented():
|
||||||
|
code_py = """
|
||||||
|
class Foo:
|
||||||
|
val: i32
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry(x: Foo, y: Foo) -> Foo:
|
||||||
|
return x + y
|
||||||
|
"""
|
||||||
|
|
||||||
|
with pytest.raises(Type3Exception, match='Foo does not implement the Num type class'):
|
||||||
|
Suite(code_py).run_code()
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
|
def test_equals_int_same(type_):
|
||||||
|
code_py = f"""
|
||||||
|
CONSTANT0: {type_} = 10
|
||||||
|
|
||||||
|
CONSTANT1: {type_} = 10
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> bool:
|
||||||
|
return CONSTANT0 == CONSTANT1
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert True is result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
|
def test_equals_int_different(type_):
|
||||||
|
code_py = f"""
|
||||||
|
CONSTANT0: {type_} = 10
|
||||||
|
|
||||||
|
CONSTANT1: {type_} = 3
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> bool:
|
||||||
|
return CONSTANT0 == CONSTANT1
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert False is result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
|
def test_equals_float_same(type_):
|
||||||
|
code_py = f"""
|
||||||
|
CONSTANT0: {type_} = 10.125
|
||||||
|
|
||||||
|
CONSTANT1: {type_} = 10.125
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> bool:
|
||||||
|
return CONSTANT0 == CONSTANT1
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert True is result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
|
def test_equals_float_different(type_):
|
||||||
|
code_py = f"""
|
||||||
|
CONSTANT0: {type_} = 10.32
|
||||||
|
|
||||||
|
CONSTANT1: {type_} = 10.33
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry() -> bool:
|
||||||
|
return CONSTANT0 == CONSTANT1
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert False is result.returned_value
|
||||||
@ -20,7 +20,7 @@ def testEntry() -> {type_}:
|
|||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', TYPE_LIST)
|
@pytest.mark.parametrize('type_', TYPE_LIST)
|
||||||
def test_division_zero_let_it_crash_float(type_):
|
def test_division_float_follow_ieee_so_inf_pos(type_):
|
||||||
code_py = f"""
|
code_py = f"""
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> {type_}:
|
def testEntry() -> {type_}:
|
||||||
@ -31,3 +31,17 @@ def testEntry() -> {type_}:
|
|||||||
# https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2
|
# https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2
|
||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
assert float('+inf') == result.returned_value
|
assert float('+inf') == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', TYPE_LIST)
|
||||||
|
def test_division_float_follow_ieee_so_inf_neg(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
|
||||||
|
|||||||
@ -21,7 +21,6 @@ def testEntry() -> i32:
|
|||||||
return 4238 * mul
|
return 4238 * mul
|
||||||
|
|
||||||
result = Suite(code_py).run_code(
|
result = Suite(code_py).run_code(
|
||||||
runtime='wasmer',
|
|
||||||
imports={
|
imports={
|
||||||
'helper': helper,
|
'helper': helper,
|
||||||
}
|
}
|
||||||
@ -47,7 +46,6 @@ def testEntry() -> None:
|
|||||||
prop = mul
|
prop = mul
|
||||||
|
|
||||||
result = Suite(code_py).run_code(
|
result = Suite(code_py).run_code(
|
||||||
runtime='wasmer',
|
|
||||||
imports={
|
imports={
|
||||||
'helper': helper,
|
'helper': helper,
|
||||||
}
|
}
|
||||||
@ -73,7 +71,6 @@ def testEntry(x: u32) -> u8:
|
|||||||
|
|
||||||
with pytest.raises(Type3Exception, match=r'u32 must be u8 instead'):
|
with pytest.raises(Type3Exception, match=r'u32 must be u8 instead'):
|
||||||
Suite(code_py).run_code(
|
Suite(code_py).run_code(
|
||||||
runtime='wasmer',
|
|
||||||
imports={
|
imports={
|
||||||
'helper': helper,
|
'helper': helper,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ def test_division_int(type_):
|
|||||||
code_py = f"""
|
code_py = f"""
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> {type_}:
|
def testEntry() -> {type_}:
|
||||||
return 10 / 3
|
return div(10, 3)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
@ -24,7 +24,7 @@ def test_division_zero_let_it_crash_int(type_):
|
|||||||
code_py = f"""
|
code_py = f"""
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> {type_}:
|
def testEntry() -> {type_}:
|
||||||
return 10 / 0
|
return div(10, 0)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# WebAssembly dictates that integer division is a partial operator (e.g. unreachable for 0)
|
# WebAssembly dictates that integer division is a partial operator (e.g. unreachable for 0)
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from phasm.type3.entry import Type3Exception
|
||||||
|
|
||||||
from ..helpers import Suite
|
from ..helpers import Suite
|
||||||
|
|
||||||
INT_TYPES = ['u32', 'u64', 'i32', 'i64']
|
INT_TYPES = ['u32', 'u64', 'i32', 'i64']
|
||||||
@ -14,6 +16,20 @@ TYPE_MAP = {
|
|||||||
'f64': float,
|
'f64': float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
def test_addition_not_implemented():
|
||||||
|
code_py = """
|
||||||
|
class Foo:
|
||||||
|
val: i32
|
||||||
|
|
||||||
|
@exported
|
||||||
|
def testEntry(x: Foo, y: Foo) -> Foo:
|
||||||
|
return x + y
|
||||||
|
"""
|
||||||
|
|
||||||
|
with pytest.raises(Type3Exception, match='Foo does not implement the Num type class'):
|
||||||
|
Suite(code_py).run_code()
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', INT_TYPES)
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
def test_addition_int(type_):
|
def test_addition_int(type_):
|
||||||
@ -26,7 +42,7 @@ def testEntry() -> {type_}:
|
|||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
assert 13 == result.returned_value
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
assert TYPE_MAP[type_] is type(result.returned_value)
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
@ -40,7 +56,7 @@ def testEntry() -> {type_}:
|
|||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
assert 32.125 == result.returned_value
|
assert 32.125 == result.returned_value
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
assert TYPE_MAP[type_] is type(result.returned_value)
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', INT_TYPES)
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
@ -54,7 +70,7 @@ def testEntry() -> {type_}:
|
|||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
assert 7 == result.returned_value
|
assert 7 == result.returned_value
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
assert TYPE_MAP[type_] is type(result.returned_value)
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
@ -68,23 +84,59 @@ def testEntry() -> {type_}:
|
|||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
assert 32.125 == result.returned_value
|
assert 32.125 == result.returned_value
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
assert TYPE_MAP[type_] is type(result.returned_value)
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.skip('TODO: Runtimes return a signed value, which is difficult to test')
|
def test_subtraction_negative_result():
|
||||||
@pytest.mark.parametrize('type_', ('u32', 'u64')) # FIXME: u8
|
code_py = """
|
||||||
def test_subtraction_underflow(type_):
|
|
||||||
code_py = f"""
|
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> {type_}:
|
def testEntry() -> i32:
|
||||||
return 10 - 11
|
return 10 - 11
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
assert 0 < result.returned_value
|
assert -1 == result.returned_value
|
||||||
|
|
||||||
# TODO: Multiplication
|
@pytest.mark.integration_test
|
||||||
|
def test_subtraction_underflow():
|
||||||
|
code_py = """
|
||||||
|
@exported
|
||||||
|
def testEntry() -> u32:
|
||||||
|
return 10 - 11
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 4294967295 == result.returned_value
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
|
def test_multiplication_int(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10 * 3
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 30 == result.returned_value
|
||||||
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
|
def test_multiplication_float(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 32.0 * 0.125
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
|
assert 4.0 == result.returned_value
|
||||||
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', INT_TYPES)
|
@pytest.mark.parametrize('type_', INT_TYPES)
|
||||||
@ -101,7 +153,7 @@ def helper(left: {type_}, right: {type_}) -> {type_}:
|
|||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
assert 22 == result.returned_value
|
assert 22 == result.returned_value
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
assert TYPE_MAP[type_] is type(result.returned_value)
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
@pytest.mark.parametrize('type_', FLOAT_TYPES)
|
||||||
@ -118,4 +170,4 @@ def helper(left: {type_}, right: {type_}) -> {type_}:
|
|||||||
result = Suite(code_py).run_code()
|
result = Suite(code_py).run_code()
|
||||||
|
|
||||||
assert 32.125 == result.returned_value
|
assert 32.125 == result.returned_value
|
||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
assert TYPE_MAP[type_] is type(result.returned_value)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
import wasmtime
|
||||||
|
|
||||||
from phasm.type3.entry import Type3Exception
|
from phasm.type3.entry import Type3Exception
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ def testEntry(x: u32) -> u8:
|
|||||||
return CONSTANT[x]
|
return CONSTANT[x]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(wasmtime.Trap):
|
||||||
Suite(code_py).run_code(3)
|
Suite(code_py).run_code(3)
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
|
|||||||
@ -74,11 +74,9 @@ CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
|
|||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
def test_tuple_must_use_literal_for_indexing():
|
def test_tuple_must_use_literal_for_indexing():
|
||||||
code_py = """
|
code_py = """
|
||||||
CONSTANT: u32 = 0
|
|
||||||
|
|
||||||
@exported
|
@exported
|
||||||
def testEntry(x: (u8, u32, u64)) -> u64:
|
def testEntry(x: (u8, u32, u64), y: u8) -> u64:
|
||||||
return x[CONSTANT]
|
return x[y]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match='Must index with literal'):
|
with pytest.raises(Type3Exception, match='Must index with literal'):
|
||||||
|
|||||||
@ -3,7 +3,7 @@ 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 RunnerWasmtime as Runner
|
||||||
|
|
||||||
|
|
||||||
def setup_interpreter(phash_code: str) -> Runner:
|
def setup_interpreter(phash_code: str) -> Runner:
|
||||||
@ -12,7 +12,6 @@ def setup_interpreter(phash_code: str) -> Runner:
|
|||||||
runner.parse()
|
runner.parse()
|
||||||
runner.compile_ast()
|
runner.compile_ast()
|
||||||
runner.compile_wat()
|
runner.compile_wat()
|
||||||
runner.compile_wasm()
|
|
||||||
runner.interpreter_setup()
|
runner.interpreter_setup()
|
||||||
runner.interpreter_load()
|
runner.interpreter_load()
|
||||||
|
|
||||||
|
|||||||
11
wat2wasm.py
Normal file
11
wat2wasm.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
from wasmtime import wat2wasm
|
||||||
|
|
||||||
|
def main(_prog: str, inp: str, _dash_o: str, out: str) -> None:
|
||||||
|
with open(inp, 'rb') as inp_obj:
|
||||||
|
with open(out, 'wb') as out_obj:
|
||||||
|
out_obj.write(wat2wasm(inp_obj.read()))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(*sys.argv)
|
||||||
Loading…
x
Reference in New Issue
Block a user