Compare commits

..

No commits in common. "bee0c845a834669f0f21ebc6b9df9c38bcc9cce0" and "11fde4cb9ee40131873636d9a2a29b4112990652" have entirely different histories.

20 changed files with 252 additions and 422 deletions

View File

@ -29,6 +29,7 @@
- Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded - Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded
- Try to implement the min and max functions using select - Try to implement the min and max functions using select
- Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed - Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed
- Move UnaryOp.operator into type class methods
- Move foldr into type class methods - Move foldr into type class methods
- PrimitiveType is just the type, nothing primitive about it (change the name). u32 are still basic types or something. - PrimitiveType is just the type, nothing primitive about it (change the name). u32 are still basic types or something.
- Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types. - Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.

View File

@ -51,7 +51,7 @@ _CRC32_Table: u32[256] = (
) )
def _crc32_f(crc: u32, byt: u8) -> u32: def _crc32_f(crc: u32, byt: u8) -> u32:
return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ extend(byt)] return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ u32(byt)]
@exported @exported
def crc32(data: bytes) -> u32: def crc32(data: bytes) -> u32:

View File

@ -75,6 +75,16 @@ def expression(inp: ourlang.Expression) -> str:
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
return str(inp.variable.name) return str(inp.variable.name)
if isinstance(inp, ourlang.UnaryOp):
if inp.operator == 'cast':
mtyp = type3(inp.type3)
if mtyp is None:
raise NotImplementedError(f'Casting to type {inp.type_var}')
return f'{mtyp}({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.name} {expression(inp.right)}' return f'{expression(inp.left)} {inp.operator.name} {expression(inp.right)}'

View File

@ -237,28 +237,6 @@ INSTANCES = {
prelude.Sized_.methods['len']: { prelude.Sized_.methods['len']: {
'a=bytes': stdlib_types.bytes_sized_len, 'a=bytes': stdlib_types.bytes_sized_len,
}, },
prelude.Extendable.methods['extend']: {
'a=u8,b=u32': stdlib_types.u8_u32_extend,
'a=u8,b=u64': stdlib_types.u8_u64_extend,
'a=u32,b=u64': stdlib_types.u32_u64_extend,
'a=i8,b=i32': stdlib_types.i8_i32_extend,
'a=i8,b=i64': stdlib_types.i8_i64_extend,
'a=i32,b=i64': stdlib_types.i32_i64_extend,
},
prelude.Extendable.methods['wrap']: {
'a=u8,b=u32': stdlib_types.u8_u32_wrap,
'a=u8,b=u64': stdlib_types.u8_u64_wrap,
'a=u32,b=u64': stdlib_types.u32_u64_wrap,
'a=i8,b=i32': stdlib_types.i8_i32_wrap,
'a=i8,b=i64': stdlib_types.i8_i64_wrap,
'a=i32,b=i64': stdlib_types.i32_i64_wrap,
},
prelude.Promotable.methods['promote']: {
'a=f32,b=f64': stdlib_types.f32_f64_promote,
},
prelude.Promotable.methods['demote']: {
'a=f32,b=f64': stdlib_types.f32_f64_demote,
},
} }
def phasm_compile(inp: ourlang.Module) -> wasm.Module: def phasm_compile(inp: ourlang.Module) -> wasm.Module:
@ -318,7 +296,7 @@ def type3(inp: type3placeholders.Type3OrPlaceholder) -> wasm.WasmType:
# And pointers are i32 # And pointers are i32
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if (prelude.InternalPassAsPointer, (inp, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in inp.classes:
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
raise NotImplementedError(type3, inp) raise NotImplementedError(type3, inp)
@ -366,7 +344,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
assert element.type3 == exp_type3 assert element.type3 == exp_type3
if (prelude.InternalPassAsPointer, (exp_type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in exp_type3.classes:
mtyp = 'i32' mtyp = 'i32'
else: else:
assert isinstance(exp_type3, type3types.Type3), NotImplementedError('Tuple of applied types / structs') assert isinstance(exp_type3, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
@ -435,7 +413,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if isinstance(inp.variable, ourlang.ModuleConstantDef): if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
if (prelude.InternalPassAsPointer, (inp.type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in inp.type3.classes:
assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, )) assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, ))
address = inp.variable.constant.data_block.address address = inp.variable.constant.data_block.address
@ -479,6 +457,18 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
raise NotImplementedError(inp.operator, instance_key) raise NotImplementedError(inp.operator, instance_key)
if isinstance(inp, ourlang.UnaryOp):
expression(wgn, inp.right)
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
if inp.operator == 'cast':
if inp.type3 == prelude.u32 and inp.right.type3 == prelude.u8:
# Nothing to do, you can use an u8 value as a u32 no problem
return
raise NotImplementedError(expression, inp.type3, inp.operator)
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
for arg in inp.arguments: for arg in inp.arguments:
expression(wgn, arg) expression(wgn, arg)
@ -497,7 +487,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
instance_key = ','.join( instance_key = ','.join(
f'{k.letter}={v.name}' f'{k.letter}={v.name}'
for k, v in sorted(type_var_map.items(), key=lambda x: x[0].letter) for k, v in type_var_map.items()
) )
instance = INSTANCES.get(inp.function, {}).get(instance_key, None) instance = INSTANCES.get(inp.function, {}).get(instance_key, None)
@ -570,7 +560,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
expression(wgn, inp.varref) expression(wgn, inp.varref)
if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in el_type.classes:
mtyp = 'i32' mtyp = 'i32'
else: else:
assert isinstance(el_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs') assert isinstance(el_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
@ -982,7 +972,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
# Store each member individually # Store each member individually
for memname, mtyp3 in st_args.items(): for memname, mtyp3 in st_args.items():
mtyp: Optional[str] mtyp: Optional[str]
if (prelude.InternalPassAsPointer, (mtyp3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in mtyp3.classes:
mtyp = 'i32' mtyp = 'i32'
else: else:
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name) mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)

View File

@ -127,6 +127,21 @@ class VariableReference(Expression):
super().__init__() super().__init__()
self.variable = variable self.variable = variable
class UnaryOp(Expression):
"""
A unary operator expression within a statement
"""
__slots__ = ('operator', 'right', )
operator: str
right: Expression
def __init__(self, operator: str, right: Expression) -> None:
super().__init__()
self.operator = operator
self.right = right
class BinaryOp(Expression): class BinaryOp(Expression):
""" """
A binary operator expression within a statement A binary operator expression within a statement

View File

@ -29,6 +29,7 @@ from .ourlang import (
StructDefinition, StructDefinition,
Subscript, Subscript,
TupleInstantiation, TupleInstantiation,
UnaryOp,
VariableReference, VariableReference,
) )
from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES
@ -246,7 +247,7 @@ class OurVisitor:
members[stmt.target.id] = self.visit_type(module, stmt.annotation) members[stmt.target.id] = self.visit_type(module, stmt.annotation)
return StructDefinition(prelude.struct(node.name, members), node.lineno) return StructDefinition(prelude.struct(node.name, members, set()), node.lineno)
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name): if not isinstance(node.target, ast.Name):
@ -388,6 +389,19 @@ class OurVisitor:
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
) )
if isinstance(node, ast.UnaryOp):
if isinstance(node.op, ast.UAdd):
operator = '+'
elif isinstance(node.op, ast.USub):
operator = '-'
else:
raise NotImplementedError(f'Operator {node.op}')
return UnaryOp(
operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.operand),
)
if isinstance(node, ast.Compare): if isinstance(node, ast.Compare):
if 1 < len(node.ops): if 1 < len(node.ops):
raise NotImplementedError('Multiple operators') raise NotImplementedError('Multiple operators')
@ -462,7 +476,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as expr in FunctionDef') raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[Fold, FunctionCall]: def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
if node.keywords: if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet? _raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -475,6 +489,16 @@ class OurVisitor:
if node.func.id in PRELUDE_METHODS: if node.func.id in PRELUDE_METHODS:
func = PRELUDE_METHODS[node.func.id] func = PRELUDE_METHODS[node.func.id]
elif node.func.id == 'u32':
if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
unary_op = UnaryOp(
'cast',
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
)
unary_op.type3 = prelude.u32
return unary_op
elif node.func.id == 'foldl': elif node.func.id == 'foldl':
if 3 != len(node.args): if 3 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given') _raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given')

View File

@ -3,7 +3,7 @@ The prelude are all the builtin types, type classes and methods
""" """
from ..type3.functions import TypeVariable from ..type3.functions import TypeVariable
from ..type3.typeclasses import Type3Class from ..type3.typeclasses import Type3Class, instance_type_class
from ..type3.types import ( from ..type3.types import (
Type3, Type3,
TypeConstructor_StaticArray, TypeConstructor_StaticArray,
@ -11,50 +11,40 @@ from ..type3.types import (
TypeConstructor_Tuple, TypeConstructor_Tuple,
) )
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3, ...]]] = set() none = Type3('none', [])
def instance_type_class(cls: Type3Class, *typ: Type3) -> None:
global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING
# TODO: Check for required existing instantiations
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), ))
none = Type3('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_ = Type3('bool') bool_ = Type3('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 Suffixes with an underscores, as it's a Python builtin
""" """
u8 = Type3('u8') u8 = Type3('u8', [])
""" """
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 = Type3('u32') u32 = Type3('u32', [])
""" """
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 = Type3('u64') u64 = Type3('u64', [])
""" """
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 = Type3('i8') i8 = Type3('i8', [])
""" """
The signed 8-bit integer type. The signed 8-bit integer type.
@ -62,7 +52,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 = Type3('i32') i32 = Type3('i32', [])
""" """
The unsigned 32-bit integer type. The unsigned 32-bit integer type.
@ -70,7 +60,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 = Type3('i64') i64 = Type3('i64', [])
""" """
The unsigned 64-bit integer type. The unsigned 64-bit integer type.
@ -78,25 +68,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 = Type3('f32') f32 = Type3('f32', [])
""" """
A 32-bits IEEE 754 float, of 32 bits width. A 32-bits IEEE 754 float, of 32 bits width.
""" """
f64 = Type3('f64') f64 = Type3('f64', [])
""" """
A 32-bits IEEE 754 float, of 64 bits width. A 32-bits IEEE 754 float, of 64 bits width.
""" """
bytes_ = Type3('bytes') bytes_ = Type3('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.
""" """
def sa_on_create(typ: Type3) -> None: static_array = TypeConstructor_StaticArray('static_array', [], [])
instance_type_class(InternalPassAsPointer, typ)
static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create)
""" """
A type constructor. A type constructor.
@ -106,10 +93,7 @@ It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated. of the same type repeated.
""" """
def tp_on_create(typ: Type3) -> None: tuple_ = TypeConstructor_Tuple('tuple', [], [])
instance_type_class(InternalPassAsPointer, typ)
tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create)
""" """
This is a fixed length piece of memory. This is a fixed length piece of memory.
@ -117,10 +101,7 @@ It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different. determined length, and each argument can be different.
""" """
def st_on_create(typ: Type3) -> None: struct = TypeConstructor_Struct('struct', [], [])
instance_type_class(InternalPassAsPointer, typ)
struct = TypeConstructor_Struct('struct', on_create=st_on_create)
""" """
This is like a tuple, but each argument is named, so that developers This is like a tuple, but each argument is named, so that developers
can get and set fields by name. can get and set fields by name.
@ -141,7 +122,6 @@ PRELUDE_TYPES: dict[str, Type3] = {
} }
a = TypeVariable('a') a = TypeVariable('a')
b = TypeVariable('b')
InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={}) InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={})
@ -150,9 +130,9 @@ Internal type class to keep track which types we pass arounds as a pointer.
""" """
instance_type_class(InternalPassAsPointer, bytes_) instance_type_class(InternalPassAsPointer, bytes_)
# instance_type_class(InternalPassAsPointer, static_array) instance_type_class(InternalPassAsPointer, static_array)
# instance_type_class(InternalPassAsPointer, tuple_) instance_type_class(InternalPassAsPointer, tuple_)
# instance_type_class(InternalPassAsPointer, struct) instance_type_class(InternalPassAsPointer, struct)
Eq = Type3Class('Eq', [a], methods={}, operators={ Eq = Type3Class('Eq', [a], methods={}, operators={
'==': [a, a, bool_], '==': [a, a, bool_],
@ -168,6 +148,7 @@ instance_type_class(Eq, i32)
instance_type_class(Eq, i64) instance_type_class(Eq, i64)
instance_type_class(Eq, f32) instance_type_class(Eq, f32)
instance_type_class(Eq, f64) instance_type_class(Eq, f64)
instance_type_class(Eq, static_array)
Ord = Type3Class('Ord', [a], methods={ Ord = Type3Class('Ord', [a], methods={
'min': [a, a, a], 'min': [a, a, a],
@ -267,25 +248,6 @@ Sized_ = Type3Class('Sized', [a], methods={
instance_type_class(Sized_, bytes_) instance_type_class(Sized_, bytes_)
Extendable = Type3Class('Extendable', [a, b], methods={
'extend': [a, b],
'wrap': [b, a],
}, operators={})
instance_type_class(Extendable, u8, u32)
instance_type_class(Extendable, u8, u64)
instance_type_class(Extendable, u32, u64)
instance_type_class(Extendable, i8, i32)
instance_type_class(Extendable, i8, i64)
instance_type_class(Extendable, i32, i64)
Promotable = Type3Class('Promotable', [a, b], methods={
'promote': [a, b],
'demote': [b, a],
}, operators={})
instance_type_class(Promotable, f32, f64)
PRELUDE_TYPE_CLASSES = { PRELUDE_TYPE_CLASSES = {
'Eq': Eq, 'Eq': Eq,
'Ord': Ord, 'Ord': Ord,
@ -295,8 +257,6 @@ PRELUDE_TYPE_CLASSES = {
'Integral': Integral, 'Integral': Integral,
'Fractional': Fractional, 'Fractional': Fractional,
'Floating': Floating, 'Floating': Floating,
'Extendable': Extendable,
'Promotable': Promotable,
} }
PRELUDE_OPERATORS = { PRELUDE_OPERATORS = {
@ -319,6 +279,4 @@ PRELUDE_METHODS = {
**IntNum.methods, **IntNum.methods,
**NatNum.methods, **NatNum.methods,
**Sized_.methods, **Sized_.methods,
**Extendable.methods,
**Promotable.methods,
} }

View File

@ -865,59 +865,3 @@ def f64_intnum_neg(g: Generator) -> None:
def bytes_sized_len(g: Generator) -> None: def bytes_sized_len(g: Generator) -> None:
# The length is stored in the first 4 bytes # The length is stored in the first 4 bytes
g.i32.load() g.i32.load()
## ###
## Extendable
def u8_u32_extend(g: Generator) -> None:
# No-op
# u8 is already stored as u32
pass
def u8_u64_extend(g: Generator) -> None:
g.i64.extend_i32_u()
def u32_u64_extend(g: Generator) -> None:
g.i64.extend_i32_u()
def i8_i32_extend(g: Generator) -> None:
# No-op
# i8 is already stored as i32
pass
def i8_i64_extend(g: Generator) -> None:
g.i64.extend_i32_s()
def i32_i64_extend(g: Generator) -> None:
g.i64.extend_i32_s()
def u8_u32_wrap(g: Generator) -> None:
g.i32.const(0xFF)
g.i32.and_()
def u8_u64_wrap(g: Generator) -> None:
g.i32.wrap_i64()
g.i32.const(0xFF)
g.i32.and_()
def u32_u64_wrap(g: Generator) -> None:
g.i32.wrap_i64()
def i8_i32_wrap(g: Generator) -> None:
g.i32.const(0xFF)
g.i32.and_()
def i8_i64_wrap(g: Generator) -> None:
g.i32.wrap_i64()
def i32_i64_wrap(g: Generator) -> None:
g.i32.wrap_i64()
## ###
## Promotable
def f32_f64_promote(g: Generator) -> None:
g.f64.promote_f32()
def f32_f64_demote(g: Generator) -> None:
g.f32.demote_f64()

View File

@ -45,13 +45,7 @@ class Context:
Context for constraints Context for constraints
""" """
__slots__ = ('type_class_instances_existing', ) __slots__ = ()
# Constraint_TypeClassInstanceExists
type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[types.Type3, ...]]]
def __init__(self) -> None:
self.type_class_instances_existing = set()
class ConstraintBase: class ConstraintBase:
""" """
@ -197,68 +191,97 @@ class TupleMatchConstraint(ConstraintBase):
raise NotImplementedError(exp_type) raise NotImplementedError(exp_type)
class CastableConstraint(ConstraintBase):
"""
A type can be cast to another type
"""
__slots__ = ('from_type3', 'to_type3', )
from_type3: placeholders.Type3OrPlaceholder
to_type3: placeholders.Type3OrPlaceholder
def __init__(self, from_type3: placeholders.Type3OrPlaceholder, to_type3: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
self.from_type3 = from_type3
self.to_type3 = to_type3
def check(self) -> CheckResult:
ftyp = self.from_type3
if isinstance(ftyp, placeholders.PlaceholderForType) and ftyp.resolve_as is not None:
ftyp = ftyp.resolve_as
ttyp = self.to_type3
if isinstance(ttyp, placeholders.PlaceholderForType) and ttyp.resolve_as is not None:
ttyp = ttyp.resolve_as
if isinstance(ftyp, placeholders.PlaceholderForType) or isinstance(ttyp, placeholders.PlaceholderForType):
return RequireTypeSubstitutes()
if ftyp is prelude.u8 and ttyp is prelude.u32:
return None
return Error(f'Cannot cast {ftyp.name} to {ttyp.name}')
def human_readable(self) -> HumanReadableRet:
return (
'{to_type3}({from_type3})',
{
'to_type3': self.to_type3,
'from_type3': self.from_type3,
},
)
def __repr__(self) -> str:
return f'CastableConstraint({repr(self.from_type3)}, {repr(self.to_type3)}, comment={repr(self.comment)})'
class MustImplementTypeClassConstraint(ConstraintBase): class MustImplementTypeClassConstraint(ConstraintBase):
""" """
A type must implement a given type class A type must implement a given type class
""" """
__slots__ = ('context', 'type_class3', 'types', ) __slots__ = ('type_class3', 'type3', )
context: Context
type_class3: Union[str, typeclasses.Type3Class] type_class3: Union[str, typeclasses.Type3Class]
types: list[placeholders.Type3OrPlaceholder] type3: placeholders.Type3OrPlaceholder
DATA = { DATA = {
'bytes': {'Foldable'}, 'bytes': {'Foldable'},
} }
def __init__(self, context: Context, type_class3: Union[str, typeclasses.Type3Class], types: list[placeholders.Type3OrPlaceholder], comment: Optional[str] = None) -> None: def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment) super().__init__(comment=comment)
self.context = context
self.type_class3 = type_class3 self.type_class3 = type_class3
self.types = types self.type3 = type3
def check(self) -> CheckResult: def check(self) -> CheckResult:
typ_list = [] typ = self.type3
for typ in self.types: if isinstance(typ, placeholders.PlaceholderForType) and typ.resolve_as is not None:
if isinstance(typ, placeholders.PlaceholderForType) and typ.resolve_as is not None: typ = typ.resolve_as
typ = typ.resolve_as
if isinstance(typ, placeholders.PlaceholderForType): if isinstance(typ, placeholders.PlaceholderForType):
return RequireTypeSubstitutes() return RequireTypeSubstitutes()
typ_list.append(typ)
assert len(typ_list) == len(self.types)
if isinstance(self.type_class3, typeclasses.Type3Class): if isinstance(self.type_class3, typeclasses.Type3Class):
key = (self.type_class3, tuple(typ_list), ) if self.type_class3 in typ.classes:
if key in self.context.type_class_instances_existing:
return None return None
else: else:
if self.type_class3 in self.__class__.DATA.get(typ_list[0].name, set()): if self.type_class3 in self.__class__.DATA.get(typ.name, set()):
return None return None
typ_cls_name = self.type_class3 if isinstance(self.type_class3, str) else self.type_class3.name return Error(f'{typ.name} does not implement the {self.type_class3} type class')
typ_name_list = ' '.join(x.name for x in typ_list)
return Error(f'Missing type class instantation: {typ_cls_name} {typ_name_list}')
def human_readable(self) -> HumanReadableRet: def human_readable(self) -> HumanReadableRet:
keys = {
f'type{idx}': typ
for idx, typ in enumerate(self.types)
}
return ( return (
'Exists instance {type_class3} ' + ' '.join(f'{{{x}}}' for x in keys), '{type3} derives {type_class3}',
{ {
'type_class3': str(self.type_class3), 'type_class3': str(self.type_class3),
**keys, 'type3': self.type3,
}, },
) )
def __repr__(self) -> str: def __repr__(self) -> str:
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.types)}, comment={repr(self.comment)})' return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)}, comment={repr(self.comment)})'
class LiteralFitsConstraint(ConstraintBase): class LiteralFitsConstraint(ConstraintBase):
""" """

View File

@ -12,6 +12,7 @@ from . import typeclasses as typeclasses
from . import types as type3types from . import types as type3types
from .constraints import ( from .constraints import (
CanBeSubscriptedConstraint, CanBeSubscriptedConstraint,
CastableConstraint,
ConstraintBase, ConstraintBase,
Context, Context,
LiteralFitsConstraint, LiteralFitsConstraint,
@ -24,7 +25,6 @@ ConstraintGenerator = Generator[ConstraintBase, None, None]
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]: def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
ctx = Context() ctx = Context()
ctx.type_class_instances_existing.update(prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING)
return [*module(ctx, inp)] return [*module(ctx, inp)]
@ -48,6 +48,14 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})') comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
return return
if isinstance(inp, ourlang.UnaryOp):
if 'cast' == inp.operator:
yield from expression(ctx, inp.right)
yield CastableConstraint(inp.right.type3, inp.type3)
return
raise NotImplementedError(expression, inp, inp.operator)
if isinstance(inp, ourlang.BinaryOp) or isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.BinaryOp) or isinstance(inp, ourlang.FunctionCall):
signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature
arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments
@ -63,16 +71,18 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
for call_arg in arguments: for call_arg in arguments:
yield from expression(ctx, call_arg) yield from expression(ctx, call_arg)
for constraint in signature.context.constraints: for type_var, constraint_list in signature.context.constraints.items():
if isinstance(constraint, functions.Constraint_TypeClassInstanceExists): assert type_var in type_var_map # When can this happen?
yield MustImplementTypeClassConstraint(
ctx,
constraint.type_class3,
[type_var_map[x] for x in constraint.types],
)
continue
raise NotImplementedError(constraint) for constraint in constraint_list:
if isinstance(constraint, functions.TypeVariableConstraint_TypeHasTypeClass):
yield MustImplementTypeClassConstraint(
constraint.type_class3,
type_var_map[type_var],
)
continue
raise NotImplementedError(constraint)
for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [inp], strict=True)): for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [inp], strict=True)):
if arg_no == len(arguments): if arg_no == len(arguments):
@ -128,7 +138,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
yield SameTypeConstraint(inp.func.posonlyargs[0].type3, inp.func.returns_type3, inp.base.type3, inp.type3, yield SameTypeConstraint(inp.func.posonlyargs[0].type3, inp.func.returns_type3, inp.base.type3, inp.type3,
comment='foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b') comment='foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b')
yield MustImplementTypeClassConstraint(ctx, 'Foldable', [inp.iter.type3]) yield MustImplementTypeClassConstraint('Foldable', inp.iter.type3)
return return

View File

@ -34,37 +34,26 @@ class TypeVariable:
def __repr__(self) -> str: def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})' return f'TypeVariable({repr(self.letter)})'
class ConstraintBase: class TypeVariableConstraintBase:
__slots__ = () __slots__ = ()
class Constraint_TypeClassInstanceExists(ConstraintBase): class TypeVariableConstraint_TypeHasTypeClass(TypeVariableConstraintBase):
__slots__ = ('type_class3', 'types', ) __slots__ = ('type_class3', )
type_class3: 'Type3Class' def __init__(self, type_class3: 'Type3Class') -> None:
types: list[TypeVariable]
def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None:
self.type_class3 = type_class3 self.type_class3 = type_class3
self.types = list(types)
# Sanity check. AFAIK, if you have a multi-parameter type class,
# you can only add a constraint by supplying types for all variables
assert len(self.type_class3.args) == len(self.types)
class TypeVariableContext: class TypeVariableContext:
__slots__ = ('variables', 'constraints', ) __slots__ = ('constraints', )
variables: set[TypeVariable] constraints: dict[TypeVariable, list[TypeVariableConstraintBase]]
constraints: list[ConstraintBase]
def __init__(self) -> None: def __init__(self) -> None:
self.variables = set() self.constraints = {}
self.constraints = []
def __copy__(self) -> 'TypeVariableContext': def __copy__(self) -> 'TypeVariableContext':
result = TypeVariableContext() result = TypeVariableContext()
result.variables.update(self.variables) result.constraints.update(self.constraints)
result.constraints.extend(self.constraints)
return result return result
class FunctionSignature: class FunctionSignature:

View File

@ -1,12 +1,12 @@
from typing import Dict, Iterable, List, Mapping, Optional, Union from typing import Any, Dict, Iterable, List, Mapping, Optional, Union
from .functions import ( from .functions import (
Constraint_TypeClassInstanceExists,
FunctionSignature, FunctionSignature,
TypeVariable, TypeVariable,
TypeVariableConstraint_TypeHasTypeClass,
TypeVariableContext, TypeVariableContext,
) )
from .types import Type3 from .types import Type3, TypeConstructor, TypeConstructor_Struct
class Type3ClassMethod: class Type3ClassMethod:
@ -43,7 +43,17 @@ class Type3Class:
self.args = list(args) self.args = list(args)
context = TypeVariableContext() context = TypeVariableContext()
context.constraints.append(Constraint_TypeClassInstanceExists(self, args)) for arg in args:
context.constraints[arg] = [
TypeVariableConstraint_TypeHasTypeClass(self)
]
# FIXME: Multi parameter class types
# To fix this, realise that an instantiation of a multi paramater type class
# means that the instantiation depends on the combination of type classes
# and so we can't store the type classes on the types anymore
# This also means constraints should store a tuple of types as its key
assert len(context.constraints) <= 1
self.methods = { self.methods = {
k: Type3ClassMethod(k, FunctionSignature(context, v)) k: Type3ClassMethod(k, FunctionSignature(context, v))
@ -57,3 +67,9 @@ class Type3Class:
def __repr__(self) -> str: def __repr__(self) -> str:
return self.name return self.name
def instance_type_class(cls: Type3Class, typ: Type3 | TypeConstructor[Any] | TypeConstructor_Struct) -> None:
if isinstance(typ, Type3):
typ.classes.add(cls)
else:
typ.type_classes.add(cls)

View File

@ -2,13 +2,18 @@
Contains the final types for use in Phasm, as well as construtors. Contains the final types for use in Phasm, as well as construtors.
""" """
from typing import ( from typing import (
TYPE_CHECKING,
Any, Any,
Callable,
Generic, Generic,
Iterable,
Set,
Tuple, Tuple,
TypeVar, TypeVar,
) )
if TYPE_CHECKING:
from .typeclasses import Type3Class
class KindArgument: class KindArgument:
pass pass
@ -20,18 +25,32 @@ class Type3(KindArgument):
(Having a separate name makes it easier to distinguish from (Having a separate name makes it easier to distinguish from
Python's Type) Python's Type)
""" """
__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: Set['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 = set(classes)
for cls in self.classes:
for inh_cls in cls.inherited_classes:
if inh_cls not in self.classes:
raise Exception(
f'No instance for ({inh_cls} {self.name})'
f'; required for ({cls} {self.name})'
)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Type3({repr(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
@ -99,16 +118,21 @@ class TypeConstructor(Generic[T]):
""" """
Base class for type construtors Base class for type construtors
""" """
__slots__ = ('name', 'on_create', '_cache', '_reverse_cache') __slots__ = ('name', 'classes', 'type_classes', '_cache', '_reverse_cache')
name: str name: str
""" """
The name of the type constructor The name of the type constructor
""" """
on_create: Callable[[Type3], None] classes: Set['Type3Class']
""" """
Who to let know if a type is created The type classes that this constructor implements
"""
type_classes: Set['Type3Class']
"""
The type classes that the constructed types implement
""" """
_cache: dict[T, Type3] _cache: dict[T, Type3]
@ -122,9 +146,10 @@ class TypeConstructor(Generic[T]):
Sometimes we need to know the key that created a type. Sometimes we need to know the key that created a type.
""" """
def __init__(self, name: str, on_create: Callable[[Type3], None]) -> None: def __init__(self, name: str, classes: Iterable['Type3Class'], type_classes: Iterable['Type3Class']) -> None:
self.name = name self.name = name
self.on_create = on_create self.classes = set(classes)
self.type_classes = set(type_classes)
self._cache = {} self._cache = {}
self._reverse_cache = {} self._reverse_cache = {}
@ -150,9 +175,8 @@ class TypeConstructor(Generic[T]):
""" """
result = self._cache.get(key, None) result = self._cache.get(key, None)
if result is None: if result is None:
self._cache[key] = result = Type3(self.make_name(key)) self._cache[key] = result = Type3(self.make_name(key), self.type_classes)
self._reverse_cache[result] = key self._reverse_cache[result] = key
self.on_create(result)
return result return result
@ -201,16 +225,21 @@ class TypeConstructor_Struct:
""" """
Base class for type construtors Base class for type construtors
""" """
__slots__ = ('name', 'on_create', '_cache', '_reverse_cache') __slots__ = ('name', 'classes', 'type_classes', '_cache', '_reverse_cache')
name: str name: str
""" """
The name of the type constructor The name of the type constructor
""" """
on_create: Callable[[Type3], None] classes: Set['Type3Class']
""" """
Who to let know if a type is created The type classes that this constructor implements
"""
type_classes: Set['Type3Class']
"""
The type classes that the constructed types implement
""" """
_cache: dict[str, Type3] _cache: dict[str, Type3]
@ -225,9 +254,10 @@ class TypeConstructor_Struct:
used for making the type used for making the type
""" """
def __init__(self, name: str, on_create: Callable[[Type3], None]) -> None: def __init__(self, name: str, classes: Iterable['Type3Class'], type_classes: Iterable['Type3Class']) -> None:
self.name = name self.name = name
self.on_create = on_create self.classes = set(classes)
self.type_classes = set(type_classes)
self._cache = {} self._cache = {}
self._reverse_cache = {} self._reverse_cache = {}
@ -240,9 +270,8 @@ class TypeConstructor_Struct:
""" """
return self._reverse_cache.get(typ) return self._reverse_cache.get(typ)
def __call__(self, name: str, args: dict[str, Type3]) -> Type3: def __call__(self, name: str, args: dict[str, Type3], classes: Set['Type3Class']) -> Type3:
result = Type3(name) result = Type3(name, classes | self.type_classes)
self._reverse_cache[result] = args self._reverse_cache[result] = args
self.on_create(result)
return result return result

View File

@ -74,9 +74,6 @@ class Generator_i32(Generator_i32i64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('i32', generator) super().__init__('i32', generator)
# 2.4.1. Numeric Instructions
self.wrap_i64 = functools.partial(self.generator.add_statement, 'i32.wrap_i64')
class Generator_i64(Generator_i32i64): class Generator_i64(Generator_i32i64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('i64', generator) super().__init__('i64', generator)
@ -135,16 +132,10 @@ class Generator_f32(Generator_f32f64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('f32', generator) super().__init__('f32', generator)
# 2.4.1 Numeric Instructions
self.demote_f64 = functools.partial(self.generator.add_statement, 'f32.demote_f64')
class Generator_f64(Generator_f32f64): class Generator_f64(Generator_f32f64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('f64', generator) super().__init__('f64', generator)
# 2.4.1 Numeric Instructions
self.promote_f32 = functools.partial(self.generator.add_statement, 'f64.promote_f32')
class Generator_Local: class Generator_Local:
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
self.generator = generator self.generator = generator

View File

@ -264,13 +264,6 @@ def _load_memory_stored_returned_value(
if ret_type3 in (prelude.i8, prelude.i32, prelude.i64): if ret_type3 in (prelude.i8, prelude.i32, prelude.i64):
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
if ret_type3 is prelude.i8:
# Values are actually i32
# Have to reinterpret to load proper value
data = struct.pack('<i', wasm_value)
wasm_value, = struct.unpack('<bxxx', data)
return wasm_value return wasm_value
if ret_type3 in (prelude.u8, prelude.u32, prelude.u64): if ret_type3 in (prelude.u8, prelude.u32, prelude.u64):
@ -361,7 +354,7 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
return _load_bytes_from_address(runner, typ, adr) return _load_bytes_from_address(runner, typ, adr)
if (prelude.InternalPassAsPointer, (typ, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in typ.classes:
# Note: For applied types, inp should contain a 4 byte pointer # Note: For applied types, 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]

View File

@ -49,7 +49,7 @@ def testEntry(a: bytes, b: bytes) -> u8:
def test_foldl_3(): def test_foldl_3():
code_py = """ code_py = """
def xor(l: u32, r: u8) -> u32: def xor(l: u32, r: u8) -> u32:
return l ^ extend(r) return l ^ u32(r)
@exported @exported
def testEntry(a: bytes) -> u32: def testEntry(a: bytes) -> u32:

View File

@ -29,7 +29,7 @@ def testEntry(x: Foo, y: Foo) -> Foo:
return x == y return x == y
""" """
with pytest.raises(Type3Exception, match='Missing type class instantation: Eq Foo'): with pytest.raises(Type3Exception, match='Foo does not implement the Eq type class'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test
@ -111,7 +111,7 @@ def testEntry(x: Foo, y: Foo) -> Foo:
return x != y return x != y
""" """
with pytest.raises(Type3Exception, match='Missing type class instantation: Eq Foo'): with pytest.raises(Type3Exception, match='Foo does not implement the Eq type class'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test

View File

@ -1,112 +0,0 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
EXTENTABLE = [
('u8', 'u32', ),
('u8', 'u64', ),
('u32', 'u64', ),
('i8', 'i32', ),
('i8', 'i64', ),
('i32', 'i64', ),
]
@pytest.mark.integration_test
def test_extend_not_implemented():
code_py = """
class Foo:
val: i32
class Baz:
val: i32
@exported
def testEntry(x: Foo) -> Baz:
return extend(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Extendable Foo Baz'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('ext_from,ext_to', EXTENTABLE)
def test_extend_ok(ext_from,ext_to):
code_py = f"""
CONSTANT: {ext_from} = 10
@exported
def testEntry() -> {ext_to}:
return extend(CONSTANT)
"""
result = Suite(code_py).run_code()
assert 10 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('ext_from,in_put,ext_to,exp_out', [
('u8', 241, 'u32', 241),
('u32', 4059165169, 'u64', 4059165169),
('u8', 241, 'u64', 241),
('i8', 113, 'i32', 113),
('i32', 1911681521, 'i64', 1911681521),
('i8', 113, 'i64', 113),
('i8', -15, 'i32', -15),
('i32', -15, 'i64', -15),
('i8', -15, 'i64', -15),
])
def test_extend_results(ext_from, ext_to, in_put, exp_out):
code_py = f"""
@exported
def testEntry(x: {ext_from}) -> {ext_to}:
return extend(x)
"""
result = Suite(code_py).run_code(in_put)
assert exp_out == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('ext_from,ext_to', EXTENTABLE)
def test_wrap_ok(ext_from,ext_to):
code_py = f"""
CONSTANT: {ext_to} = 10
@exported
def testEntry() -> {ext_from}:
return wrap(CONSTANT)
"""
result = Suite(code_py).run_code()
assert 10 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('ext_to,in_put,ext_from,exp_out', [
('u32', 0xF1F1F1F1, 'u8', 0xF1),
('u64', 0xF1F1F1F1F1F1F1F1, 'u32', 0xF1F1F1F1),
('u64', 0xF1F1F1F1F1F1F1F1, 'u8', 0xF1),
('i32', 0xF1F1F171, 'i8', 113),
('i32', 0xF1F1F1F1, 'i8', -15),
('i64', 0x71F1F1F171F1F1F1, 'i32', 1911681521),
('i64', 0x71F1F1F1F1F1F1F1, 'i32', -235802127),
('i64', 0xF1F1F1F1F1F1F171, 'i8', 113),
('i64', 0xF1F1F1F1F1F1F1F1, 'i8', -15),
])
def test_wrap_results(ext_from, ext_to, in_put, exp_out):
code_py = f"""
@exported
def testEntry(x: {ext_to}) -> {ext_from}:
return wrap(x)
"""
result = Suite(code_py).run_code(in_put)
assert exp_out == result.returned_value

View File

@ -27,7 +27,7 @@ def testEntry(x: Foo, y: Foo) -> Foo:
return x + y return x + y
""" """
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'): with pytest.raises(Type3Exception, match='Foo does not implement the NatNum type class'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test

View File

@ -1,51 +0,0 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
@pytest.mark.integration_test
def test_promote_not_implemented():
code_py = """
class Foo:
val: i32
class Baz:
val: i32
@exported
def testEntry(x: Foo) -> Baz:
return promote(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Promotable Foo Baz'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_promote_ok():
code_py = """
CONSTANT: f32 = 10.5
@exported
def testEntry() -> f64:
return promote(CONSTANT)
"""
result = Suite(code_py).run_code()
assert 10.5 == result.returned_value
@pytest.mark.integration_test
def test_demote_ok():
code_py = """
CONSTANT: f64 = 10.5
@exported
def testEntry() -> f32:
return demote(CONSTANT)
"""
result = Suite(code_py).run_code()
assert 10.5 == result.returned_value