More work on type3

This commit is contained in:
Johan B.W. de Vries 2022-11-24 15:43:23 +01:00
parent b5a28daebf
commit 9f21d0fd1d
9 changed files with 308 additions and 220 deletions

View File

@ -1,7 +1,12 @@
# TODO
- Implement a trace() builtin for debugging
- Implement a proper type matching / checking system
- Implement subscript as an operator
- Implement another operator
- Figure out how to do type classes
- Implement structs again, with the `.foo` notation working
- Implement a trace() builtin for debugging
- Check if we can use DataView in the Javascript examples, e.g. with setUint32
- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this.
- Implement a FizzBuzz example

View File

@ -36,16 +36,15 @@ def type3(inp: Type3OrPlaceholder) -> str:
return inp.name
# def struct_definition(inp: typing.TypeStruct) -> str:
# """
# Render: TypeStruct's definition
# """
# result = f'class {inp.name}:\n'
# for mem in inp.members: # TODO: Broken after new type system
# raise NotImplementedError('Structs broken after new type system')
# # result += f' {mem.name}: {type_(mem.type)}\n'
#
# return result
def struct_definition(inp: ourlang.StructDefinition) -> str:
"""
Render: TypeStruct's definition
"""
result = f'class {inp.struct_type3.name}:\n'
for mem, typ in inp.struct_type3.members.items():
result += f' {mem}: {type3(typ)}\n'
return result
def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
"""
@ -95,10 +94,10 @@ def expression(inp: ourlang.Expression) -> str:
for arg in inp.arguments
)
if isinstance(inp.function, ourlang.StructConstructor):
return f'{inp.function.struct_type3.name}({args})'
# TODO: Broken after new type system
# if isinstance(inp.function, ourlang.StructConstructor):
# return f'{inp.function.struct.name}({args})'
#
# if isinstance(inp.function, ourlang.TupleConstructor):
# return f'({args}, )'
@ -110,9 +109,8 @@ def expression(inp: ourlang.Expression) -> str:
return f'{varref}[{index}]'
# TODO: Broken after new type system
# if isinstance(inp, ourlang.AccessStructMember):
# return f'{expression(inp.varref)}.{inp.member.name}'
if isinstance(inp, ourlang.AccessStructMember):
return f'{expression(inp.varref)}.{inp.member}'
if isinstance(inp, ourlang.Fold):
fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr'
@ -180,10 +178,10 @@ def module(inp: ourlang.Module) -> str:
"""
result = ''
# for struct in inp.structs.values():
# if result:
# result += '\n'
# result += struct_definition(struct)
for struct in inp.struct_definitions.values():
if result:
result += '\n'
result += struct_definition(struct)
for cdef in inp.constant_defs.values():
if result:

View File

@ -1,7 +1,7 @@
"""
This module contains the code to convert parsed Ourlang into WebAssembly code
"""
from typing import List, Optional
from typing import List, Union
import struct
@ -14,6 +14,16 @@ from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types
from .wasmgenerator import Generator as WasmGenerator
LOAD_STORE_TYPE_MAP = {
'u8': 'i32', # Have to use an u32, since there is no native u8 type
'i32': 'i32',
'i64': 'i64',
'u32': 'i32',
'u64': 'i64',
'f32': 'f32',
'f64': 'f64',
}
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
"""
Public method for compiling a parsed Phasm module into
@ -53,16 +63,10 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
if inp is type3types.f64:
return wasm.WasmTypeFloat64()
# if tc_prim.primitive is typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
# # StaticArray, Tuples and Structs are passed as pointer
# # And pointers are i32
# return wasm.WasmTypeInt32()
# TODO: Broken after new type system
# if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeStaticArray, typing.TypeBytes)):
# # Structs and tuples are passed as pointer
# # And pointers are i32
# return wasm.WasmTypeInt32()
if isinstance(inp, type3types.StructType3):
# Structs and tuples are passed as pointer
# And pointers are i32
return wasm.WasmTypeInt32()
raise NotImplementedError(type3, inp)
@ -349,17 +353,19 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
# wgn.call(stdlib_types.__subscript_bytes__)
# return
# if isinstance(inp, ourlang.AccessStructMember):
# mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
# if mtyp is None:
# # In the future might extend this by having structs or tuples
# # as members of struct or tuples
# raise NotImplementedError(expression, inp, inp.member)
#
# expression(wgn, inp.varref)
# wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
# return
#
if isinstance(inp, ourlang.AccessStructMember):
mtyp = LOAD_STORE_TYPE_MAP.get(inp.struct_type3.members[inp.member].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, inp.struct_type3)
expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset(
inp.struct_type3, inp.member
)))
return
# if isinstance(inp, ourlang.AccessTupleMember):
# mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
# if mtyp is None:
@ -544,8 +550,8 @@ def function(inp: ourlang.Function) -> wasm.Function:
if False: # TODO: isinstance(inp, ourlang.TupleConstructor):
pass # _generate_tuple_constructor(wgn, inp)
elif False: # TODO: isinstance(inp, ourlang.StructConstructor):
pass # _generate_struct_constructor(wgn, inp)
elif isinstance(inp, ourlang.StructConstructor):
_generate_struct_constructor(wgn, inp)
else:
for stat in inp.statements:
statement(wgn, stat)
@ -733,26 +739,34 @@ def module(inp: ourlang.Module) -> wasm.Module:
#
# # Return the allocated address
# wgn.local.get(tmp_var)
#
# def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
# tmp_var = wgn.temp_var_i32('struct_adr')
#
# # Allocated the required amounts of bytes in memory
# wgn.i32.const(inp.struct.alloc_size())
# wgn.call(stdlib_alloc.__alloc__)
# wgn.local.set(tmp_var)
#
# # Store each member individually
# for member in inp.struct.members:
# mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
# if mtyp is None:
# # In the future might extend this by having structs or tuples
# # as members of struct or tuples
# raise NotImplementedError(expression, inp, member)
#
# wgn.local.get(tmp_var)
# wgn.add_statement('local.get', f'${member.name}')
# wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
#
# # Return the allocated address
# wgn.local.get(tmp_var)
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
tmp_var = wgn.temp_var_i32('struct_adr')
# Allocated the required amounts of bytes in memory
wgn.i32.const(_calculate_alloc_size(inp.struct_type3))
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
# Store each member individually
for memname, mtyp3 in inp.struct_type3.members.items():
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)
wgn.add_statement('local.get', f'${memname}')
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(_calculate_member_offset(
inp.struct_type3, memname
)))
# Return the allocated address
wgn.local.get(tmp_var)
def _calculate_alloc_size(type3: Union[type3types.StructType3, type3types.Type3]) -> int:
return 0 # FIXME: Stub
def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
return 0 # FIXME: Stub

View File

@ -11,7 +11,7 @@ WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc',
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
from .type3 import types as type3types
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType, StructType3
class Expression:
"""
@ -134,6 +134,23 @@ class Subscript(Expression):
self.varref = varref
self.index = index
class AccessStructMember(Expression):
"""
Access a struct member for reading of writing
"""
__slots__ = ('varref', 'struct_type3', 'member', )
varref: VariableReference
struct_type3: StructType3
member: str
def __init__(self, varref: VariableReference, struct_type3: StructType3, member: str) -> None:
super().__init__()
self.varref = varref
self.struct_type3 = struct_type3
self.member = member
class Fold(Expression):
"""
A (left or right) fold
@ -240,28 +257,41 @@ class Function:
self.returns_str = 'None'
self.posonlyargs = []
class StructDefinition:
"""
The definition for a struct
"""
__slots__ = ('struct_type3', 'lineno', )
struct_type3: StructType3
lineno: int
def __init__(self, struct_type3: StructType3, lineno: int) -> None:
self.struct_type3 = struct_type3
self.lineno = lineno
class StructConstructor(Function):
"""
The constructor method for a struct
A function will generated to instantiate a struct. The arguments
will be the defaults
"""
__slots__ = ('struct_type3', )
struct_type3: StructType3
def __init__(self, struct_type3: StructType3) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', -1)
self.returns_type3 = struct_type3
for mem, typ in struct_type3.members.items():
self.posonlyargs.append(FunctionParam(mem, typ, ))
self.struct_type3 = struct_type3
# TODO: Broken after new type system
# class StructConstructor(Function):
# """
# The constructor method for a struct
#
# A function will generated to instantiate a struct. The arguments
# will be the defaults
# """
# __slots__ = ('struct', )
#
# struct: TypeStruct
#
# def __init__(self, struct: TypeStruct) -> None:
# super().__init__(f'@{struct.name}@__init___@', -1)
#
# self.returns = struct
#
# for mem in struct.members:
# self.posonlyargs.append(FunctionParam(mem.name, mem.type, ))
#
# self.struct = struct
#
# class TupleConstructor(Function):
# """
# The constructor method for a tuple
@ -331,15 +361,15 @@ class Module:
"""
A module is a file and consists of functions
"""
__slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',)
__slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions',)
data: ModuleData
# structs: Dict[str, TypeStruct]
struct_definitions: Dict[str, StructDefinition]
constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function]
def __init__(self) -> None:
self.data = ModuleData()
# self.structs = {}
self.struct_definitions = {}
self.constant_defs = {}
self.functions = {}

View File

@ -18,8 +18,9 @@ from .ourlang import (
BinaryOp,
ConstantPrimitive, ConstantTuple,
FunctionCall, Subscript,
# StructConstructor, TupleConstructor,
FunctionCall, AccessStructMember, Subscript,
StructDefinition, StructConstructor,
# TupleConstructor,
UnaryOp, VariableReference,
Fold,
@ -75,16 +76,15 @@ class OurVisitor:
module.constant_defs[res.name] = res
# TODO: Broken after type system
# if isinstance(res, TypeStruct):
# if res.name in module.structs:
# raise StaticError(
# f'{res.name} already defined on line {module.structs[res.name].lineno}'
# )
#
# module.structs[res.name] = res
# constructor = StructConstructor(res)
# module.functions[constructor.name] = constructor
if isinstance(res, StructDefinition):
if res.struct_type3.name in module.struct_definitions:
raise StaticError(
f'{res.struct_type3.name} already defined on line {module.struct_definitions[res.struct_type3.name].lineno}'
)
module.struct_definitions[res.struct_type3.name] = res
constructor = StructConstructor(res.struct_type3)
module.functions[constructor.name] = constructor
if isinstance(res, Function):
if res.name in module.functions:
@ -101,12 +101,12 @@ class OurVisitor:
return module
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, ModuleConstantDef]: # TypeStruct
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
if isinstance(node, ast.FunctionDef):
return self.pre_visit_Module_FunctionDef(module, node)
# if isinstance(node, ast.ClassDef):
# return self.pre_visit_Module_ClassDef(module, node)
if isinstance(node, ast.ClassDef):
return self.pre_visit_Module_ClassDef(module, node)
if isinstance(node, ast.AnnAssign):
return self.pre_visit_Module_AnnAssign(module, node)
@ -155,36 +155,33 @@ class OurVisitor:
return function
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> None: # TypeStruct:
raise NotImplementedError
# struct = TypeStruct(node.name, node.lineno)
#
# _not_implemented(not node.bases, 'ClassDef.bases')
# _not_implemented(not node.keywords, 'ClassDef.keywords')
# _not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
#
# offset = 0
#
# for stmt in node.body:
# if not isinstance(stmt, ast.AnnAssign):
# raise NotImplementedError(f'Class with {stmt} nodes')
#
# if not isinstance(stmt.target, ast.Name):
# raise NotImplementedError('Class with default values')
#
# if not stmt.value is None:
# raise NotImplementedError('Class with default values')
#
# if stmt.simple != 1:
# raise NotImplementedError('Class with non-simple arguments')
#
# raise NotImplementedError('TODO: Broken after new type system')
# member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
#
# struct.members.append(member)
# offset += member.type.alloc_size()
#
# return struct
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition:
_not_implemented(not node.bases, 'ClassDef.bases')
_not_implemented(not node.keywords, 'ClassDef.keywords')
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
members: Dict[str, type3types.Type3] = {}
for stmt in node.body:
if not isinstance(stmt, ast.AnnAssign):
raise NotImplementedError(f'Class with {stmt} nodes')
if not isinstance(stmt.target, ast.Name):
raise NotImplementedError('Class with default values')
if not stmt.value is None:
raise NotImplementedError('Class with default values')
if stmt.simple != 1:
raise NotImplementedError('Class with non-simple arguments')
if stmt.target.id in members:
_raise_static_error(stmt, 'Struct members must have unique names')
members[stmt.target.id] = self.visit_type(module, stmt.annotation)
return StructDefinition(type3types.StructType3(node.name, members), node.lineno)
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name):
@ -474,13 +471,14 @@ class OurVisitor:
if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
# if node.func.id in module.structs:
# raise NotImplementedError('TODO: Broken after new type system')
# struct = module.structs[node.func.id]
# struct_constructor = StructConstructor(struct)
#
# func = module.functions[struct_constructor.name]
if node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS:
if node.func.id in module.struct_definitions:
struct_definition = module.struct_definitions[node.func.id]
struct_constructor = StructConstructor(struct_definition.struct_type3)
# FIXME: Defer struct de-allocation
func = module.functions[struct_constructor.name]
elif node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS:
if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
@ -552,33 +550,33 @@ class OurVisitor:
return result
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
raise NotImplementedError('Broken after new type system')
# del module
# del function
#
# if not isinstance(node.value, ast.Name):
# _raise_static_error(node, 'Must reference a name')
#
# if not isinstance(node.ctx, ast.Load):
# _raise_static_error(node, 'Must be load context')
#
# if not node.value.id in our_locals:
# _raise_static_error(node, f'Undefined variable {node.value.id}')
#
# param = our_locals[node.value.id]
#
# node_typ = param.type
# if not isinstance(node_typ, TypeStruct):
# _raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
#
# member = node_typ.get_member(node.attr)
# if member is None:
# _raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
#
# return AccessStructMember(
# VariableReference(param),
# member,
# )
del module
del function
if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name')
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if not node.value.id in our_locals:
_raise_static_error(node, f'Undefined variable {node.value.id}')
param = our_locals[node.value.id]
node_typ = param.type3
if not isinstance(node_typ, type3types.StructType3):
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
member = node_typ.members.get(node.attr)
if member is None:
_raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
return AccessStructMember(
VariableReference(param),
node_typ,
node.attr,
)
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
if not isinstance(node.value, ast.Name):
@ -687,12 +685,10 @@ class OurVisitor:
if node.id in type3types.LOOKUP_TABLE:
return type3types.LOOKUP_TABLE[node.id]
raise NotImplementedError('TODO: Broken after type system')
#
# if node.id in module.structs:
# return module.structs[node.id]
#
# _raise_static_error(node, f'Unrecognized type {node.id}')
if node.id in module.struct_definitions:
return module.struct_definitions[node.id].struct_type3
_raise_static_error(node, f'Unrecognized type {node.id}')
if isinstance(node, ast.Subscript):
if not isinstance(node.value, ast.Name):

View File

@ -13,7 +13,6 @@ from .constraints import (
ConstraintBase,
LiteralFitsConstraint, SameTypeConstraint,
)
from . import types
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
ctx = Context()
@ -48,9 +47,17 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas
return
if isinstance(inp, ourlang.AccessStructMember):
yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3,
f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
return
raise NotImplementedError(expression, inp)
def function(ctx: Context, inp: ourlang.Function) -> Generator[ConstraintBase, None, None]:
if isinstance(inp, ourlang.StructConstructor):
return
if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn):
raise NotImplementedError('Functions with not just a return statement')

View File

@ -139,6 +139,31 @@ class AppliedType3(Type3):
def __repr__(self) -> str:
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
class StructType3(Type3):
"""
A Type3 struct with named members
"""
__slots__ = ('name', 'members', )
name: str
"""
The structs fully qualified name
"""
members: Dict[str, Type3]
"""
The struct's field definitions
"""
def __init__(self, name: str, members: Dict[str, Type3]) -> None:
super().__init__(name)
self.name = name
self.members = dict(members)
def __repr__(self) -> str:
return f'StructType3(repr({self.name}), repr({self.members}))'
none = Type3('none')
"""
The none type, for when functions simply don't return anything. e.g., IO().

View File

@ -1,12 +1,36 @@
import pytest
from phasm.exceptions import StaticError
from phasm.type3.entry import Type3Exception
from phasm.parser import phasm_parse
from ..constants import (
ALL_INT_TYPES
)
from ..helpers import Suite
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ('i32', 'f64', ))
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
def test_module_constant(type_):
code_py = f"""
class CheckedValue:
value: {type_}
CONSTANT: CheckedValue = CheckedValue(24)
@exported
def testEntry() -> {type_}:
return CONSTANT.value
"""
result = Suite(code_py).run_code()
assert 24 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
def test_struct_0(type_):
code_py = f"""
class CheckedValue:
@ -75,43 +99,5 @@ def testEntry(arg: Struct) -> (i32, i32, ):
return arg.param
"""
with pytest.raises(StaticError, match=f'Static error on line 6: Expected \\(i32, i32, \\), arg.param is actually {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_tuple_member(type_):
code_py = f"""
def testEntry(arg: ({type_}, )) -> (i32, i32, ):
return arg[0]
"""
with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), arg\\[0\\] is actually {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_too_few_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_too_many_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_type_mismatch():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
phasm_parse(code_py)
with pytest.raises(Type3Exception, match=r'\(i32, i32, \) must be ' + type_ + ' instead'):
Suite(code_py).run_code()

View File

@ -82,3 +82,30 @@ def testEntry() -> i32x4:
result = Suite(code_py).run_code()
assert (1, 2, 3, 0) == result.returned_value
@pytest.mark.integration_test
def test_tuple_constant_too_few_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_constant_too_many_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_constant_type_mismatch():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
Suite(code_py).run_code()