Support structs in extracting and inputting values
This commit is contained in:
parent
a73a3b2bb4
commit
bd80210ba3
5
Makefile
5
Makefile
@ -51,3 +51,8 @@ clean-generated-tests:
|
||||
.SECONDARY: # Keep intermediate files
|
||||
|
||||
.PHONY: examples
|
||||
|
||||
# So generally the right thing to do is to delete the target file if the recipe fails after beginning to change the file.
|
||||
# make will do this if .DELETE_ON_ERROR appears as a target.
|
||||
# This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""
|
||||
This module contains the code to convert parsed Ourlang into WebAssembly code
|
||||
"""
|
||||
from typing import List, Union
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import struct
|
||||
|
||||
@ -59,6 +59,11 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
||||
if inp == type3types.u64:
|
||||
return wasm.WasmTypeInt64()
|
||||
|
||||
if inp == type3types.i8:
|
||||
# WebAssembly has only support for 32 and 64 bits
|
||||
# So we need to store more memory per byte
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
if inp == type3types.i32:
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
@ -853,10 +858,13 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
|
||||
|
||||
# Store each member individually
|
||||
for memname, mtyp3 in inp.struct_type3.members.items():
|
||||
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
||||
mtyp: Optional[str]
|
||||
if isinstance(mtyp3, type3types.StructType3):
|
||||
mtyp = 'i32'
|
||||
else:
|
||||
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
||||
|
||||
if mtyp is None:
|
||||
# In the future might extend this by having structs or tuples
|
||||
# as members of struct or tuples
|
||||
raise NotImplementedError(expression, inp, mtyp3)
|
||||
|
||||
wgn.local.get(tmp_var)
|
||||
|
||||
@ -24,7 +24,7 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
|
||||
return 4
|
||||
|
||||
return sum(
|
||||
calculate_alloc_size(x)
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
for x in typ.members.values()
|
||||
)
|
||||
|
||||
|
||||
@ -102,6 +102,11 @@ class Suite:
|
||||
wasm_args.append(adr)
|
||||
continue
|
||||
|
||||
if isinstance(arg_typ, type3types.StructType3):
|
||||
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||
wasm_args.append(adr)
|
||||
continue
|
||||
|
||||
raise NotImplementedError(arg)
|
||||
|
||||
write_header(sys.stderr, 'Memory (pre run)')
|
||||
@ -157,6 +162,11 @@ def _write_memory_stored_value(
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return 4
|
||||
|
||||
if isinstance(val_typ, type3types.StructType3):
|
||||
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
|
||||
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||
return 4
|
||||
|
||||
raise NotImplementedError(val_typ, val)
|
||||
|
||||
def _allocate_memory_stored_value(
|
||||
@ -214,6 +224,24 @@ def _allocate_memory_stored_value(
|
||||
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||
return adr
|
||||
|
||||
if isinstance(val_typ, type3types.StructType3):
|
||||
assert isinstance(val, dict)
|
||||
|
||||
alloc_size = calculate_alloc_size(val_typ)
|
||||
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
|
||||
assert isinstance(adr, int)
|
||||
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
|
||||
|
||||
assert list(val.keys()) == list(val_typ.members.keys())
|
||||
|
||||
offset = adr
|
||||
for val_el_name, val_el_typ in val_typ.members.items():
|
||||
assert not isinstance(val_el_typ, type3types.PlaceholderForType)
|
||||
|
||||
val_el_val = val[val_el_name]
|
||||
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||
return adr
|
||||
|
||||
raise NotImplementedError(val_typ, val)
|
||||
|
||||
def _load_memory_stored_returned_value(
|
||||
@ -267,6 +295,9 @@ def _load_memory_stored_returned_value(
|
||||
|
||||
return _load_tuple_from_address(runner, ret_type3, wasm_value)
|
||||
|
||||
if isinstance(ret_type3, type3types.StructType3):
|
||||
return _load_struct_from_address(runner, ret_type3, wasm_value)
|
||||
|
||||
raise NotImplementedError(ret_type3, wasm_value)
|
||||
|
||||
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
|
||||
@ -305,7 +336,7 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
|
||||
return struct.unpack('<d', inp)[0]
|
||||
|
||||
if typ is type3types.bytes:
|
||||
# Note: For applied types, inp should contain a 4 byte pointer
|
||||
# Note: For bytes, inp should contain a 4 byte pointer
|
||||
assert len(inp) == 4
|
||||
adr = struct.unpack('<I', inp)[0]
|
||||
|
||||
@ -322,6 +353,13 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
|
||||
if typ.base is type3types.tuple:
|
||||
return _load_tuple_from_address(runner, typ, adr)
|
||||
|
||||
if isinstance(typ, type3types.StructType3):
|
||||
# Note: For structs, inp should contain a 4 byte pointer
|
||||
assert len(inp) == 4
|
||||
adr = struct.unpack('<I', inp)[0]
|
||||
|
||||
return _load_struct_from_address(runner, typ, adr)
|
||||
|
||||
raise NotImplementedError(typ, inp)
|
||||
|
||||
def _load_bytes_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> bytes:
|
||||
@ -383,3 +421,29 @@ def _load_tuple_from_address(runner: runners.RunnerBase, typ: type3types.Type3,
|
||||
_unpack(runner, arg_typ, arg_bytes)
|
||||
for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes))
|
||||
)
|
||||
|
||||
def _load_struct_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any:
|
||||
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
|
||||
|
||||
assert isinstance(typ, type3types.StructType3)
|
||||
|
||||
name_list = list(typ.members)
|
||||
|
||||
typ_list = [
|
||||
x
|
||||
for x in typ.members.values()
|
||||
if not isinstance(x, type3types.PlaceholderForType)
|
||||
]
|
||||
assert len(typ_list) == len(typ.members)
|
||||
|
||||
arg_sizes = [
|
||||
calculate_alloc_size(x, is_member=True)
|
||||
for x in typ_list
|
||||
]
|
||||
|
||||
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
|
||||
|
||||
return {
|
||||
arg_name: _unpack(runner, arg_typ, arg_bytes)
|
||||
for arg_name, arg_typ, arg_bytes in zip(name_list, typ_list, _split_read_bytes(read_bytes, arg_sizes))
|
||||
}
|
||||
|
||||
@ -118,6 +118,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
'Must be tuple',
|
||||
@ -175,6 +180,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
'The type of the value returned from function constant should match its return type',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be tuple (u32) instead',
|
||||
@ -226,6 +236,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of the value returned from function select should match its return type',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
'The type of the value returned from function select should match its return type',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be tuple (u32) instead',
|
||||
@ -274,6 +289,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||
# FIXME: Shouldn't this be the same as for the else statement?
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
'Must be tuple',
|
||||
@ -325,6 +345,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
||||
'Mismatch between applied types argument count',
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
)
|
||||
elif TYPE_NAME.startswith('struct_'):
|
||||
expect_type_error(
|
||||
TYPE + ' must be tuple (u32) instead',
|
||||
'The type of the value passed to argument x of function helper should match the type of that argument',
|
||||
)
|
||||
else:
|
||||
expect_type_error(
|
||||
TYPE_NAME + ' must be tuple (u32) instead',
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
import functools
|
||||
import json
|
||||
import sys
|
||||
@ -27,6 +29,9 @@ def get_tests(template):
|
||||
|
||||
def apply_settings(settings, txt):
|
||||
for k, v in settings.items():
|
||||
if k in ('CODE_HEADER', 'PYTHON'):
|
||||
continue
|
||||
|
||||
txt = txt.replace(f'${k}', v)
|
||||
return txt
|
||||
|
||||
@ -42,16 +47,41 @@ def generate_assertion_expect_type_error(result, error_msg, error_comment = None
|
||||
result.append(f'assert {repr(error_msg)} == exc_info.value.args[0][0].msg')
|
||||
result.append(f'assert {repr(error_comment)} == exc_info.value.args[0][0].comment')
|
||||
|
||||
def json_does_not_support_byte_values_fix(inp: Dict[Any, Any]):
|
||||
key_names = list(inp)
|
||||
for key in key_names:
|
||||
value = inp[key]
|
||||
|
||||
if isinstance(value, str):
|
||||
if value.startswith('bytes:'):
|
||||
inp[key] = value[6:].encode()
|
||||
continue
|
||||
|
||||
if isinstance(value, dict):
|
||||
json_does_not_support_byte_values_fix(value)
|
||||
continue
|
||||
|
||||
if isinstance(value, list):
|
||||
raise NotImplementedError
|
||||
|
||||
def generate_assertions(settings, result_code):
|
||||
result = []
|
||||
|
||||
locals_ = {
|
||||
'VAL0': eval(settings['VAL0']),
|
||||
'TYPE': settings['TYPE'],
|
||||
'TYPE_NAME': settings['TYPE_NAME'],
|
||||
'expect': functools.partial(generate_assertion_expect, result),
|
||||
'expect_type_error': functools.partial(generate_assertion_expect_type_error, result),
|
||||
}
|
||||
|
||||
if 'PYTHON' in settings:
|
||||
locals_.update(settings['PYTHON'])
|
||||
|
||||
if 'VAL0' not in locals_:
|
||||
locals_['VAL0'] = eval(settings['VAL0'])
|
||||
|
||||
json_does_not_support_byte_values_fix(locals_)
|
||||
|
||||
exec(result_code, {}, locals_)
|
||||
|
||||
return ' ' + '\n '.join(result) + '\n'
|
||||
@ -93,9 +123,14 @@ def generate_code(markdown, template, settings):
|
||||
print(' ' + user_story.replace('\n', '\n '))
|
||||
print(' """')
|
||||
print(' code_py = """')
|
||||
if 'CODE_HEADER' in settings:
|
||||
for lin in settings['CODE_HEADER']:
|
||||
print(lin)
|
||||
print()
|
||||
print(inp_code.rstrip('\n'))
|
||||
print('"""')
|
||||
print()
|
||||
|
||||
print(generate_assertions(settings, result_code))
|
||||
print()
|
||||
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
{
|
||||
"TYPE_NAME": "struct_all_primitives",
|
||||
"TYPE": "StructallPrimitives",
|
||||
"VAL0": "StructallPrimitives(1, 4, 8, 1, -1, 4, -4, 8, -8, 125.125, -125.125, 5000.5, -5000.5, b'Hello, world!')",
|
||||
"CODE_HEADER": [
|
||||
"class StructallPrimitives:",
|
||||
" val00: u8",
|
||||
" val01: u32",
|
||||
" val02: u64",
|
||||
" val10: i8",
|
||||
" val11: i8",
|
||||
" val12: i32",
|
||||
" val13: i32",
|
||||
" val14: i64",
|
||||
" val15: i64",
|
||||
" val20: f32",
|
||||
" val21: f32",
|
||||
" val22: f64",
|
||||
" val23: f64",
|
||||
" val30: bytes"
|
||||
],
|
||||
"PYTHON": {
|
||||
"VAL0": {
|
||||
"val00": 1,
|
||||
"val01": 4,
|
||||
"val02": 8,
|
||||
"val10": 1,
|
||||
"val11": -1,
|
||||
"val12": 4,
|
||||
"val13": -4,
|
||||
"val14": 8,
|
||||
"val15": -8,
|
||||
"val20": 125.125,
|
||||
"val21": -125.125,
|
||||
"val22": 5000.5,
|
||||
"val23": -5000.5,
|
||||
"val30": "bytes:Hello, world!"
|
||||
}
|
||||
}
|
||||
}
|
||||
25
tests/integration/test_lang/generator_struct_nested.json
Normal file
25
tests/integration/test_lang/generator_struct_nested.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"TYPE_NAME": "struct_nested",
|
||||
"TYPE": "StructNested",
|
||||
"VAL0": "StructNested(4, SubStruct(8, 16), 20)",
|
||||
"CODE_HEADER": [
|
||||
"class SubStruct:",
|
||||
" val00: u8",
|
||||
" val01: u8",
|
||||
"",
|
||||
"class StructNested:",
|
||||
" val00: u64",
|
||||
" val01: SubStruct",
|
||||
" val02: u64"
|
||||
],
|
||||
"PYTHON": {
|
||||
"VAL0": {
|
||||
"val00": 4,
|
||||
"val01": {
|
||||
"val00": 8,
|
||||
"val01": 16
|
||||
},
|
||||
"val02": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
12
tests/integration/test_lang/generator_struct_one_field.json
Normal file
12
tests/integration/test_lang/generator_struct_one_field.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"TYPE_NAME": "struct_one_field",
|
||||
"TYPE": "StructOneField",
|
||||
"VAL0": "StructOneField(4)",
|
||||
"CODE_HEADER": [
|
||||
"class StructOneField:",
|
||||
" value: u32"
|
||||
],
|
||||
"PYTHON": {
|
||||
"VAL0": {"value": 4}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user