Started on attempt3 of the type system

This commit is contained in:
Johan B.W. de Vries 2022-11-17 13:31:10 +01:00
parent 55a45ff17c
commit 79ff11f622
12 changed files with 432 additions and 59 deletions

View File

@ -8,6 +8,7 @@ import struct
from . import codestyle from . import codestyle
from . import ourlang from . import ourlang
from . import typing from . import typing
from .type3 import types as type3types
from . import wasm from . import wasm
from .stdlib import alloc as stdlib_alloc from .stdlib import alloc as stdlib_alloc
@ -146,6 +147,9 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
Compile: Any expression Compile: Any expression
""" """
if isinstance(inp, ourlang.ConstantPrimitive): if isinstance(inp, ourlang.ConstantPrimitive):
assert inp.type3 is not None, typing.ASSERTION_ERROR
assert inp.type_var is not None, typing.ASSERTION_ERROR assert inp.type_var is not None, typing.ASSERTION_ERROR
stp = typing.simplify(inp.type_var) stp = typing.simplify(inp.type_var)
@ -313,6 +317,8 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
return return
if isinstance(inp, ourlang.Subscript): if isinstance(inp, ourlang.Subscript):
assert inp.varref.type3 is not None, typing.ASSERTION_ERROR
assert inp.varref.type_var is not None, typing.ASSERTION_ERROR assert inp.varref.type_var is not None, typing.ASSERTION_ERROR
tc_type = inp.varref.type_var.get_type() tc_type = inp.varref.type_var.get_type()
if tc_type is None: if tc_type is None:
@ -648,45 +654,44 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
data_list: List[bytes] = [] data_list: List[bytes] = []
for constant in block.data: for constant in block.data:
assert constant.type_var is not None assert constant.type3 is not None
mtyp = typing.simplify(constant.type_var)
if mtyp == 'u8': if constant.type3 is type3types.u8:
assert isinstance(constant.value, int) assert isinstance(constant.value, int)
data_list.append(module_data_u8(constant.value)) data_list.append(module_data_u8(constant.value))
continue continue
if mtyp == 'u32': if constant.type3 is type3types.u32:
assert isinstance(constant.value, int) assert isinstance(constant.value, int)
data_list.append(module_data_u32(constant.value)) data_list.append(module_data_u32(constant.value))
continue continue
if mtyp == 'u64': if constant.type3 is type3types.u64:
assert isinstance(constant.value, int) assert isinstance(constant.value, int)
data_list.append(module_data_u64(constant.value)) data_list.append(module_data_u64(constant.value))
continue continue
if mtyp == 'i32': if constant.type3 is type3types.i32:
assert isinstance(constant.value, int) assert isinstance(constant.value, int)
data_list.append(module_data_i32(constant.value)) data_list.append(module_data_i32(constant.value))
continue continue
if mtyp == 'i64': if constant.type3 is type3types.i64:
assert isinstance(constant.value, int) assert isinstance(constant.value, int)
data_list.append(module_data_i64(constant.value)) data_list.append(module_data_i64(constant.value))
continue continue
if mtyp == 'f32': if constant.type3 is type3types.f32:
assert isinstance(constant.value, float) assert isinstance(constant.value, float)
data_list.append(module_data_f32(constant.value)) data_list.append(module_data_f32(constant.value))
continue continue
if mtyp == 'f64': if constant.type3 is type3types.f64:
assert isinstance(constant.value, float) assert isinstance(constant.value, float)
data_list.append(module_data_f64(constant.value)) data_list.append(module_data_f64(constant.value))
continue continue
raise NotImplementedError(constant, constant.type_var, mtyp) raise NotImplementedError(constant, constant.type3, constant.value)
block_data = b''.join(data_list) block_data = b''.join(data_list)

View File

@ -16,20 +16,27 @@ from .typing import (
TypeVar, TypeVar,
) )
from .type3.types import Type3
class Expression: class Expression:
""" """
An expression within a statement An expression within a statement
""" """
__slots__ = ('type_var', ) __slots__ = ('type3', 'type_var', )
type3: Optional[Type3]
type_var: Optional[TypeVar] type_var: Optional[TypeVar]
def __init__(self) -> None: def __init__(self) -> None:
self.type3 = None
self.type_var = None self.type_var = None
class Constant(Expression): class Constant(Expression):
""" """
An constant value expression within a statement An constant value expression within a statement
# FIXME: Rename to literal
""" """
__slots__ = () __slots__ = ()
@ -217,13 +224,14 @@ class Function:
""" """
A function processes input and produces output A function processes input and produces output
""" """
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_str', 'returns_type_var', 'posonlyargs', ) __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'returns_str', 'returns_type_var', 'posonlyargs', )
name: str name: str
lineno: int lineno: int
exported: bool exported: bool
imported: bool imported: bool
statements: List[Statement] statements: List[Statement]
returns_type3: Optional[Type3]
returns_str: str returns_str: str
returns_type_var: Optional[TypeVar] returns_type_var: Optional[TypeVar]
posonlyargs: List[FunctionParam] posonlyargs: List[FunctionParam]
@ -284,19 +292,21 @@ class ModuleConstantDef:
""" """
A constant definition within a module A constant definition within a module
""" """
__slots__ = ('name', 'lineno', 'type_str', 'type_var', 'constant', 'data_block', ) __slots__ = ('name', 'lineno', 'type3', 'type_str', 'type_var', 'constant', 'data_block', )
name: str name: str
lineno: int lineno: int
type3: Type3
type_str: str type_str: str
type_var: Optional[TypeVar] type_var: Optional[TypeVar]
constant: Constant constant: Constant
data_block: Optional['ModuleDataBlock'] data_block: Optional['ModuleDataBlock']
def __init__(self, name: str, lineno: int, type_str: str, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None: def __init__(self, name: str, lineno: int, type3: Type3, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
self.name = name self.name = name
self.lineno = lineno self.lineno = lineno
self.type_str = type_str self.type3 = type3
self.type_str = type3.name
self.type_var = None self.type_var = None
self.constant = constant self.constant = constant
self.data_block = data_block self.data_block = data_block

View File

@ -12,6 +12,8 @@ from .typing import (
TypeStructMember, TypeStructMember,
) )
from .type3 import types as type3types
from .exceptions import StaticError from .exceptions import StaticError
from .ourlang import ( from .ourlang import (
WEBASSEMBLY_BUILTIN_FLOAT_OPS, WEBASSEMBLY_BUILTIN_FLOAT_OPS,
@ -129,7 +131,7 @@ class OurVisitor:
function.posonlyargs.append(FunctionParam( function.posonlyargs.append(FunctionParam(
arg.arg, arg.arg,
self.visit_type(module, arg.annotation), self.visit_type(module, arg.annotation).name,
)) ))
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg') _not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
@ -153,7 +155,8 @@ class OurVisitor:
function.imported = True function.imported = True
if node.returns: if node.returns:
function.returns_str = self.visit_type(module, node.returns) function.returns_type3 = self.visit_type(module, node.returns)
function.returns_str = function.returns_type3.name
_not_implemented(not node.type_comment, 'FunctionDef.type_comment') _not_implemented(not node.type_comment, 'FunctionDef.type_comment')
@ -196,10 +199,11 @@ class OurVisitor:
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if isinstance(node.value, ast.Constant): if isinstance(node.value, ast.Constant):
type3 = self.visit_type(module, node.annotation)
return ModuleConstantDef( return ModuleConstantDef(
node.target.id, node.target.id,
node.lineno, node.lineno,
self.visit_type(module, node.annotation), type3,
self.visit_Module_Constant(module, node.value), self.visit_Module_Constant(module, node.value),
None, None,
) )
@ -675,10 +679,10 @@ class OurVisitor:
raise NotImplementedError(f'{node.value} as constant') raise NotImplementedError(f'{node.value} as constant')
def visit_type(self, module: Module, node: ast.expr) -> str: def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3:
if isinstance(node, ast.Constant): if isinstance(node, ast.Constant):
if node.value is None: if node.value is None:
return 'None' return type3types.none
_raise_static_error(node, f'Unrecognized type {node.value}') _raise_static_error(node, f'Unrecognized type {node.value}')
@ -686,15 +690,15 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if node.id in BUILTIN_TYPES: if node.id in type3types.LOOKUP_TABLE:
return node.id return type3types.LOOKUP_TABLE[node.id]
raise NotImplementedError('TODO: Broken after type system') raise NotImplementedError('TODO: Broken after type system')
#
if node.id in module.structs: # if node.id in module.structs:
return module.structs[node.id] # return module.structs[node.id]
#
_raise_static_error(node, f'Unrecognized type {node.id}') # _raise_static_error(node, f'Unrecognized type {node.id}')
if isinstance(node, ast.Subscript): if isinstance(node, ast.Subscript):
if not isinstance(node.value, ast.Name): if not isinstance(node.value, ast.Name):
@ -708,35 +712,24 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if node.value.id not in BUILTIN_TYPES: # FIXME: Tuple of tuples? if node.value.id not in type3types.LOOKUP_TABLE: # FIXME: Tuple of tuples?
_raise_static_error(node, f'Unrecognized type {node.value.id}') _raise_static_error(node, f'Unrecognized type {node.value.id}')
return f'{node.value.id}[{node.slice.value.value}]' return type3types.AppliedType3(
type3types.static_array,
[self.visit_type(module, node.value)],
)
if isinstance(node, ast.Tuple): if isinstance(node, ast.Tuple):
raise NotImplementedError('TODO: Broken after new type system') if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
# if not isinstance(node.ctx, ast.Load): elements = ', '.join(
# _raise_static_error(node, 'Must be load context') self.visit_type(module, elt).name
# for elt in node.elts
# type_tuple = TypeTuple() )
#
# offset = 0 return type3types.Type3(f'({elements}, )')
#
# for idx, elt in enumerate(node.elts):
# tuple_member = TypeTupleMember(idx, self.visit_type(module, elt), offset)
#
# type_tuple.members.append(tuple_member)
# offset += tuple_member.type.alloc_size()
#
# key = type_tuple.render_internal_name()
#
# if key not in module.types:
# module.types[key] = type_tuple
# constructor = TupleConstructor(type_tuple)
# module.functions[constructor.name] = constructor
#
# return module.types[key]
raise NotImplementedError(f'{node} as type') raise NotImplementedError(f'{node} as type')

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

View File

@ -0,0 +1,84 @@
"""
This module contains possible constraints generated based on the AST
These need to be resolved before the program can be compiled.
"""
from typing import Dict, Optional, Tuple
from .. import ourlang
from . import types
class Context:
"""
Context for constraints
"""
__slots__ = ()
class ConstraintBase:
"""
Base class for constraints
"""
__slots__ = ()
def check(self) -> Optional[str]:
"""
Checks if the contraint hold, returning an error if it doesn't
"""
raise NotImplementedError
class LiteralFitsConstraint(ConstraintBase):
"""
A literal value fits a given type
"""
__slots__ = ('type3', 'literal', )
type3: types.Type3
literal: ourlang.ConstantPrimitive
def __init__(self, type3: types.Type3, literal: ourlang.ConstantPrimitive) -> None:
self.type3 = type3
self.literal = literal
def check(self) -> Optional[str]:
val = self.literal.value
int_table: Dict[str, Tuple[int, bool]] = {
'u8': (1, False),
'u32': (4, False),
'u64': (8, False),
'i8': (1, True),
'i32': (4, True),
'i64': (8, True),
}
if self.type3.name in int_table:
bts, sgn = int_table[self.type3.name]
if isinstance(val, int):
try:
val.to_bytes(bts, 'big', signed=sgn)
except OverflowError:
return f'Must fit in {bts} byte(s)' # FIXME: Add line information
return None
return 'Must be integer' # FIXME: Add line information
float_table: Dict[str, None] = {
'f32': None,
'f64': None,
}
if self.type3.name in float_table:
_ = float_table[self.type3.name]
if isinstance(val, float):
# FIXME: Bit check
return None
return 'Must be real' # FIXME: Add line information
raise NotImplementedError

View File

@ -0,0 +1,83 @@
"""
This module generates the typing constraints for Phasm.
The constraints solver can then try to resolve all constraints.
"""
from typing import Generator, List
from .. import ourlang
from .constraints import (
Context,
ConstraintBase,
LiteralFitsConstraint,
)
from . import types
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
ctx = Context()
return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant) -> Generator[ConstraintBase, None, None]:
if isinstance(inp, ourlang.ConstantPrimitive):
# A constant by itself doesn't have any constraints
yield from ()
return
if isinstance(inp, ourlang.ConstantTuple):
# A constant by itself doesn't have any constraints
yield from ()
return
raise NotImplementedError(constant, inp)
def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBase, None, None]:
if isinstance(inp, ourlang.ConstantPrimitive):
print('hi')
raise NotImplementedError(expression, inp)
def function(ctx: Context, inp: ourlang.Function) -> Generator[ConstraintBase, None, None]:
if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn):
raise NotImplementedError('Functions with not just a return statement')
yield from expression(ctx, inp.statements[0].value)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Generator[ConstraintBase, None, None]:
yield from constant(ctx, inp.constant)
if (
inp.type3 is types.u8
or inp.type3 is types.u32
or inp.type3 is types.u64
or inp.type3 is types.i32
or inp.type3 is types.i64
or inp.type3 is types.f32
or inp.type3 is types.f64
) and isinstance(inp.constant, ourlang.ConstantPrimitive):
yield LiteralFitsConstraint(inp.type3, inp.constant)
inp.constant.type3 = inp.type3
return
if isinstance(inp.type3, types.AppliedType3):
if inp.type3.base is types.static_array and isinstance(inp.constant, ourlang.ConstantTuple):
for lit in inp.constant.value:
yield LiteralFitsConstraint(inp.type3.args[0], lit)
lit.type3 = inp.type3.args[0]
return
raise NotImplementedError(constant, inp, inp.type3)
def module(ctx: Context, inp: ourlang.Module) -> Generator[ConstraintBase, None, None]:
for func in inp.functions.values():
assert func.returns_type3 is not None, 'TODO'
for param in func.posonlyargs:
assert False, 'TODO'
for cdef in inp.constant_defs.values():
yield from module_constant_def(ctx, cdef)
for func in inp.functions.values():
yield from function(ctx, func)

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

@ -0,0 +1,26 @@
"""
Entry point to the type3 system
"""
from .. import ourlang
from .constraintsgenerator import phasm_type3_generate_constraints
class Type3Exception(BaseException):
"""
Thrown when the Type3 system detects constraints that do not hold
"""
def phasm_type3(inp: ourlang.Module) -> None:
constraints = phasm_type3_generate_constraints(inp)
assert constraints
error_list = []
for constraint in constraints:
error = constraint.check()
if error is not None:
error_list.append(error)
if error_list:
raise Type3Exception(error_list)
# TODO: Implement type substitution

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

@ -0,0 +1,159 @@
"""
Contains the final types for use in Phasm
These are actual, instantiated types; not the abstract types that the
constraint generator works with.
"""
from typing import Any, Dict, Iterable, List
class Type3:
"""
Base class for the type3 types
"""
__slots__ = ('name', )
name: str
"""
The name of the string, as parsed and outputted by codestyle.
"""
def __init__(self, name: str) -> None:
self.name = name
def __repr__(self) -> str:
return f'Type3("{self.name}")'
def __str__(self) -> str:
return self.name
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError('unsupported format string passed to Type3.__format__')
return self.name
def __eq__(self, other: Any) -> bool:
raise NotImplementedError
def __ne__(self, other: Any) -> bool:
raise NotImplementedError
def __hash__(self) -> int:
raise NotImplementedError
def __bool__(self) -> bool:
raise NotImplementedError
class AppliedType3(Type3):
"""
A Type3 that has been applied to another type
"""
__slots__ = ('base', 'args', )
base: Type3
"""
The base type
"""
args: List[Type3]
"""
The applied types
"""
def __init__(self, base: Type3, args: Iterable[Type3]) -> None:
super().__init__(
base.name
+ ' ('
+ ') ('.join(x.name for x in args)
+ ')'
)
self.base = base
self.args = [*args]
def __repr__(self) -> str:
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
none = Type3('none')
"""
The none type, for when functions simply don't return anything. e.g., IO().
"""
u8 = Type3('u8')
"""
The unsigned 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8.
"""
u32 = Type3('u32')
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32.
"""
u64 = Type3('u64')
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64.
"""
i8 = Type3('i8')
"""
The signed 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8, but
with the middel point being 0.
"""
i32 = Type3('i32')
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32, but
with the middel point being 0.
"""
i64 = Type3('i64')
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64, but
with the middel point being 0.
"""
f32 = Type3('f32')
"""
A 32-bits IEEE 754 float, of 32 bits width.
"""
f64 = Type3('f64')
"""
A 32-bits IEEE 754 float, of 64 bits width.
"""
static_array = Type3('static_array')
"""
This is a fixed length piece of memory that can be indexed at runtime.
It should be applied with one argument.
"""
tbd = Type3('tbd')
"""
This type is yet to be determined
"""
LOOKUP_TABLE: Dict[str, Type3] = {
'none': none,
'u8': u8,
'u32': u32,
'u64': u64,
'i8': i8,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
}

View File

@ -201,14 +201,14 @@ class TypingNarrowError(TypingError):
class TypeConstraintBase: class TypeConstraintBase:
""" """
Base class for classes implementing a contraint on a type Base class for classes implementing a constraint on a type
""" """
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintBase': def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintBase':
raise NotImplementedError('narrow', self, other) raise NotImplementedError('narrow', self, other)
class TypeConstraintSigned(TypeConstraintBase): class TypeConstraintSigned(TypeConstraintBase):
""" """
Contraint on whether a signed value can be used or not, or whether Constraint on whether a signed value can be used or not, or whether
a value can be used in a signed expression a value can be used in a signed expression
""" """
__slots__ = ('signed', ) __slots__ = ('signed', )
@ -237,7 +237,7 @@ class TypeConstraintSigned(TypeConstraintBase):
class TypeConstraintBitWidth(TypeConstraintBase): class TypeConstraintBitWidth(TypeConstraintBase):
""" """
Contraint on how many bits an expression has or can possibly have Constraint on how many bits an expression has or can possibly have
""" """
__slots__ = ('oneof', ) __slots__ = ('oneof', )
@ -300,7 +300,7 @@ class TypeConstraintBitWidth(TypeConstraintBase):
class TypeConstraintSubscript(TypeConstraintBase): class TypeConstraintSubscript(TypeConstraintBase):
""" """
Contraint on allowing a type to be subscripted Constraint on allowing a type to be subscripted
""" """
__slots__ = ('members', ) __slots__ = ('members', )
@ -434,7 +434,7 @@ class Context:
r_type = self.var_types[r_ctx_id] r_type = self.var_types[r_ctx_id]
l_r_var_list = self.vars_by_id[l_ctx_id] + self.vars_by_id[r_ctx_id] l_r_var_list = self.vars_by_id[l_ctx_id] + self.vars_by_id[r_ctx_id]
# Create a new TypeVar, with the combined contraints # Create a new TypeVar, with the combined constraints
# and locations of the old ones # and locations of the old ones
n = self.new_var() n = self.new_var()
@ -499,7 +499,7 @@ def simplify(inp: TypeVar) -> Optional[str]:
return f'{base}{bitwidth}' return f'{base}{bitwidth}'
if inp.get_type() is PhasmTypeReal: if inp.get_type() is PhasmTypeReal:
if tc_bits is None or tc_sign is not None: # Floats should not hava sign contraint if tc_bits is None or tc_sign is not None: # Floats should not hava sign constraint
return None return None
if len(tc_bits.oneof) != 1: if len(tc_bits.oneof) != 1:
@ -640,4 +640,14 @@ def from_str(ctx: Context, inp: str, location: Optional[str] = None) -> TypeVar:
return result return result
if inp.startswith('static_array ('):
# Temp workaround while both type2 and type3 are running simultaneous
result = ctx.new_var(PhasmTypeStaticArray)
result.add_location(inp)
if location is not None:
result.add_location(location)
return result
raise NotImplementedError(from_str, inp) raise NotImplementedError(from_str, inp)

View File

@ -14,6 +14,7 @@ import wasmtime
from phasm.compiler import phasm_compile from phasm.compiler import phasm_compile
from phasm.parser import phasm_parse from phasm.parser import phasm_parse
from phasm.typer import phasm_type from phasm.typer import phasm_type
from phasm.type3.entry import phasm_type3
from phasm import ourlang from phasm import ourlang
from phasm import wasm from phasm import wasm
@ -41,6 +42,7 @@ class RunnerBase:
Parses the Phasm code into an AST Parses the Phasm code into an AST
""" """
self.phasm_ast = phasm_parse(self.phasm_code) self.phasm_ast = phasm_parse(self.phasm_code)
phasm_type3(self.phasm_ast)
phasm_type(self.phasm_ast) phasm_type(self.phasm_ast)
def compile_ast(self) -> None: def compile_ast(self) -> None:

View File

@ -75,6 +75,7 @@ def testEntry() -> {type_}:
assert 32.125 == result.returned_value assert 32.125 == result.returned_value
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.skip('Awaiting result of Type3 experiment')
def test_module_constant_entanglement(): def test_module_constant_entanglement():
code_py = """ code_py = """
CONSTANT: u8 = 1000 CONSTANT: u8 = 1000

View File

@ -5,7 +5,7 @@ from ..helpers import Suite
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ]) @pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
def test_tuple_1(type_): def test_module_constant_1(type_):
code_py = f""" code_py = f"""
CONSTANT: ({type_}, ) = (65, ) CONSTANT: ({type_}, ) = (65, )
@ -22,7 +22,7 @@ def helper(vector: ({type_}, )) -> {type_}:
assert 65 == result.returned_value assert 65 == result.returned_value
@pytest.mark.integration_test @pytest.mark.integration_test
def test_tuple_6(): def test_module_constant_6():
code_py = """ code_py = """
CONSTANT: (u8, u8, u32, u32, u64, u64, ) = (11, 22, 3333, 4444, 555555, 666666, ) CONSTANT: (u8, u8, u32, u32, u64, u64, ) = (11, 22, 3333, 4444, 555555, 666666, )