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
|
.SECONDARY: # Keep intermediate files
|
||||||
|
|
||||||
.PHONY: examples
|
.PHONY: examples
|
||||||
|
|
||||||
|
# So generally the right thing to do is to delete the target file if the recipe fails after beginning to change the file.
|
||||||
|
# make will do this if .DELETE_ON_ERROR appears as a target.
|
||||||
|
# This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.
|
||||||
|
.DELETE_ON_ERROR:
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
This module contains the code to convert parsed Ourlang into WebAssembly code
|
This module contains the code to convert parsed Ourlang into WebAssembly code
|
||||||
"""
|
"""
|
||||||
from typing import List, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
@ -59,6 +59,11 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
|||||||
if inp == type3types.u64:
|
if inp == type3types.u64:
|
||||||
return wasm.WasmTypeInt64()
|
return wasm.WasmTypeInt64()
|
||||||
|
|
||||||
|
if inp == type3types.i8:
|
||||||
|
# WebAssembly has only support for 32 and 64 bits
|
||||||
|
# So we need to store more memory per byte
|
||||||
|
return wasm.WasmTypeInt32()
|
||||||
|
|
||||||
if inp == type3types.i32:
|
if inp == type3types.i32:
|
||||||
return wasm.WasmTypeInt32()
|
return wasm.WasmTypeInt32()
|
||||||
|
|
||||||
@ -853,10 +858,13 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
|
|||||||
|
|
||||||
# Store each member individually
|
# Store each member individually
|
||||||
for memname, mtyp3 in inp.struct_type3.members.items():
|
for memname, mtyp3 in inp.struct_type3.members.items():
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
mtyp: Optional[str]
|
||||||
|
if isinstance(mtyp3, type3types.StructType3):
|
||||||
|
mtyp = 'i32'
|
||||||
|
else:
|
||||||
|
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
|
||||||
|
|
||||||
if mtyp is None:
|
if mtyp is None:
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, mtyp3)
|
raise NotImplementedError(expression, inp, mtyp3)
|
||||||
|
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
|
|||||||
@ -24,7 +24,7 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
|
|||||||
return 4
|
return 4
|
||||||
|
|
||||||
return sum(
|
return sum(
|
||||||
calculate_alloc_size(x)
|
calculate_alloc_size(x, is_member=True)
|
||||||
for x in typ.members.values()
|
for x in typ.members.values()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -102,6 +102,11 @@ class Suite:
|
|||||||
wasm_args.append(adr)
|
wasm_args.append(adr)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if isinstance(arg_typ, type3types.StructType3):
|
||||||
|
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
|
||||||
|
wasm_args.append(adr)
|
||||||
|
continue
|
||||||
|
|
||||||
raise NotImplementedError(arg)
|
raise NotImplementedError(arg)
|
||||||
|
|
||||||
write_header(sys.stderr, 'Memory (pre run)')
|
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))
|
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
|
||||||
return 4
|
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)
|
raise NotImplementedError(val_typ, val)
|
||||||
|
|
||||||
def _allocate_memory_stored_value(
|
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)
|
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||||
return adr
|
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)
|
raise NotImplementedError(val_typ, val)
|
||||||
|
|
||||||
def _load_memory_stored_returned_value(
|
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)
|
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)
|
raise NotImplementedError(ret_type3, wasm_value)
|
||||||
|
|
||||||
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
|
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]
|
return struct.unpack('<d', inp)[0]
|
||||||
|
|
||||||
if typ is type3types.bytes:
|
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
|
assert len(inp) == 4
|
||||||
adr = struct.unpack('<I', inp)[0]
|
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:
|
if typ.base is type3types.tuple:
|
||||||
return _load_tuple_from_address(runner, typ, adr)
|
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)
|
raise NotImplementedError(typ, inp)
|
||||||
|
|
||||||
def _load_bytes_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> bytes:
|
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)
|
_unpack(runner, arg_typ, arg_bytes)
|
||||||
for arg_typ, arg_bytes in zip(typ_list, _split_read_bytes(read_bytes, arg_sizes))
|
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',
|
'Mismatch between applied types argument count',
|
||||||
'The type of the value returned from function constant should match its return type',
|
'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:
|
else:
|
||||||
expect_type_error(
|
expect_type_error(
|
||||||
'Must be tuple',
|
'Must be tuple',
|
||||||
@ -175,6 +180,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
|||||||
'Mismatch between applied types argument count',
|
'Mismatch between applied types argument count',
|
||||||
'The type of the value returned from function constant should match its return type',
|
'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:
|
else:
|
||||||
expect_type_error(
|
expect_type_error(
|
||||||
TYPE_NAME + ' must be tuple (u32) instead',
|
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',
|
'Mismatch between applied types argument count',
|
||||||
'The type of the value returned from function select should match its return type',
|
'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:
|
else:
|
||||||
expect_type_error(
|
expect_type_error(
|
||||||
TYPE_NAME + ' must be tuple (u32) instead',
|
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?
|
# 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',
|
'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:
|
else:
|
||||||
expect_type_error(
|
expect_type_error(
|
||||||
'Must be tuple',
|
'Must be tuple',
|
||||||
@ -325,6 +345,11 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
|
|||||||
'Mismatch between applied types argument count',
|
'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',
|
'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:
|
else:
|
||||||
expect_type_error(
|
expect_type_error(
|
||||||
TYPE_NAME + ' must be tuple (u32) instead',
|
TYPE_NAME + ' must be tuple (u32) instead',
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
@ -27,6 +29,9 @@ def get_tests(template):
|
|||||||
|
|
||||||
def apply_settings(settings, txt):
|
def apply_settings(settings, txt):
|
||||||
for k, v in settings.items():
|
for k, v in settings.items():
|
||||||
|
if k in ('CODE_HEADER', 'PYTHON'):
|
||||||
|
continue
|
||||||
|
|
||||||
txt = txt.replace(f'${k}', v)
|
txt = txt.replace(f'${k}', v)
|
||||||
return txt
|
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_msg)} == exc_info.value.args[0][0].msg')
|
||||||
result.append(f'assert {repr(error_comment)} == exc_info.value.args[0][0].comment')
|
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):
|
def generate_assertions(settings, result_code):
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
locals_ = {
|
locals_ = {
|
||||||
'VAL0': eval(settings['VAL0']),
|
'TYPE': settings['TYPE'],
|
||||||
'TYPE_NAME': settings['TYPE_NAME'],
|
'TYPE_NAME': settings['TYPE_NAME'],
|
||||||
'expect': functools.partial(generate_assertion_expect, result),
|
'expect': functools.partial(generate_assertion_expect, result),
|
||||||
'expect_type_error': functools.partial(generate_assertion_expect_type_error, 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_)
|
exec(result_code, {}, locals_)
|
||||||
|
|
||||||
return ' ' + '\n '.join(result) + '\n'
|
return ' ' + '\n '.join(result) + '\n'
|
||||||
@ -93,9 +123,14 @@ def generate_code(markdown, template, settings):
|
|||||||
print(' ' + user_story.replace('\n', '\n '))
|
print(' ' + user_story.replace('\n', '\n '))
|
||||||
print(' """')
|
print(' """')
|
||||||
print(' code_py = """')
|
print(' code_py = """')
|
||||||
|
if 'CODE_HEADER' in settings:
|
||||||
|
for lin in settings['CODE_HEADER']:
|
||||||
|
print(lin)
|
||||||
|
print()
|
||||||
print(inp_code.rstrip('\n'))
|
print(inp_code.rstrip('\n'))
|
||||||
print('"""')
|
print('"""')
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print(generate_assertions(settings, result_code))
|
print(generate_assertions(settings, result_code))
|
||||||
print()
|
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