- Tuple wasn't an applied type yet
- phasm_type3 would re-order type IDs between prints
- AppliedType3 wouldn't store the args for iterators
-
This commit is contained in:
Johan B.W. de Vries 2022-11-26 14:46:31 +01:00
parent 3bac625714
commit 30a4cee5af
9 changed files with 177 additions and 52 deletions

View File

@ -2,8 +2,7 @@
- Implement a proper type matching / checking system
- Implement subscript as an operator
- Implement another operator
- Figure out how to do type classes
- Re-implement Subscript contraints - Doing the LiteralFitsConstraint with a tuple doesn't put the types on the tuple elements
- Implement structs again, with the `.foo` notation working
- Rename constant to literal

View File

@ -59,6 +59,9 @@ class ConstantTuple(Constant):
super().__init__()
self.value = value
def __repr__(self) -> str:
return f'ConstantTuple({repr(self.value)})'
class VariableReference(Expression):
"""
An variable reference expression within a statement

View File

@ -714,13 +714,11 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
elements = ', '.join(
self.visit_type(module, elt).name
for elt in node.elts
return type3types.AppliedType3(
type3types.tuple,
(self.visit_type(module, elt) for elt in node.elts)
)
return type3types.Type3(f'({elements}, )')
raise NotImplementedError(f'{node} as type')
def _not_implemented(check: Any, msg: str) -> None:

View File

@ -178,18 +178,13 @@ class LiteralFitsConstraint(ConstraintBase):
__slots__ = ('type3', 'literal', )
type3: types.Type3OrPlaceholder
literal: ourlang.ConstantPrimitive
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]
def __init__(self, type3: types.Type3OrPlaceholder, literal: ourlang.ConstantPrimitive) -> None:
def __init__(self, type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> None:
self.type3 = type3
self.literal = literal
def check(self) -> CheckResult:
if isinstance(self.type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
val = self.literal.value
int_table: Dict[str, Tuple[int, bool]] = {
'u8': (1, False),
'u32': (4, False),
@ -199,35 +194,60 @@ class LiteralFitsConstraint(ConstraintBase):
'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 Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information
return None
return Error('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]
def _check(type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> CheckResult:
if isinstance(type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
if isinstance(val, float):
# FIXME: Bit check
val = literal.value
if type3.name in int_table:
bts, sgn = int_table[type3.name]
if isinstance(val, int):
try:
val.to_bytes(bts, 'big', signed=sgn)
except OverflowError:
return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information
return None
return Error('Must be integer') # FIXME: Add line information
if type3.name in float_table:
_ = float_table[type3.name]
if isinstance(val, float):
# FIXME: Bit check
return None
return Error('Must be real') # FIXME: Add line information
if isinstance(type3, types.AppliedType3) and type3.base is types.tuple:
if not isinstance(literal, ourlang.ConstantTuple):
return Error('Must be tuple')
assert isinstance(val, list) # type hint
if len(type3.args) != len(val):
return Error('Tuple element count mismatch')
for elt_typ, elt_lit in zip(type3.args, val):
res = _check(elt_typ, elt_lit)
if res is not None:
return res
return None
return Error('Must be real') # FIXME: Add line information
raise NotImplementedError
raise NotImplementedError
return _check(self.type3, self.literal)
def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
@ -245,3 +265,62 @@ class LiteralFitsConstraint(ConstraintBase):
def __repr__(self) -> str:
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)})'
class CanBeSubscriptedConstraint(ConstraintBase):
"""
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
"""
__slots__ = ('type3', 'index', 'index_type3', )
type3: types.Type3OrPlaceholder
index: ourlang.Expression
index_type3: types.Type3OrPlaceholder
def __init__(self, type3: types.Type3OrPlaceholder, index: ourlang.Expression) -> None:
self.type3 = type3
self.index = index
self.index_type3 = index.type3
def check(self) -> CheckResult:
if isinstance(self.type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
if isinstance(self.index_type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
if not isinstance(self.type3, types.AppliedType3):
return Error(f'Cannot subscript {self.type3:s}')
if self.type3.base is types.tuple:
return None
raise NotImplementedError(self.type3)
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
if isinstance(self.type3, types.AppliedType3) and self.type3.base is types.tuple and isinstance(self.index_type3, types.PlaceholderForType):
return {
self.index_type3: types.u32,
}
return {}
def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
self.type3.get_substituted(smap[self.type3])
self.type3 = smap[self.type3]
if isinstance(self.index_type3, types.PlaceholderForType) and self.index_type3 in smap: # FIXME: Check recursive?
self.index_type3.get_substituted(smap[self.index_type3])
self.index_type3 = smap[self.index_type3]
def human_readable(self) -> HumanReadableRet:
return (
'{type3}[{index}]',
{
'type3': self.type3,
'index': self.index,
},
)
def __repr__(self) -> str:
return f'CanBeSubscriptedConstraint({repr(self.type3)}, {repr(self.index)})'

View File

@ -11,6 +11,7 @@ from .constraints import (
Context,
ConstraintBase,
CanBeSubscriptedConstraint,
LiteralFitsConstraint, MustImplementTypeClassConstraint, SameTypeConstraint,
)
@ -20,7 +21,7 @@ def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase
return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant) -> Generator[ConstraintBase, None, None]:
if isinstance(inp, ourlang.ConstantPrimitive):
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantTuple, )):
yield LiteralFitsConstraint(inp.type3, inp)
return
@ -59,6 +60,13 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas
return
if isinstance(inp, ourlang.Subscript):
yield from expression(ctx, inp.varref)
yield from expression(ctx, inp.index)
yield CanBeSubscriptedConstraint(inp.varref.type3, inp.index)
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}')

View File

@ -22,6 +22,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
assert constraint_list
placeholder_substitutes: Dict[PlaceholderForType, Type3] = {}
placeholder_id_map: Dict[int, str] = {}
restack_counter = 0
@ -29,7 +30,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
while constraint_list:
if verbose:
print()
print_constraint_list(constraint_list, placeholder_substitutes)
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
constraint = constraint_list.pop(0)
@ -92,9 +93,7 @@ def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintB
else:
print('- ' + txt.format(**act_fmt))
def print_constraint_list(constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
placeholder_id_map: Dict[int, str] = {}
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
print('=== v type3 constraint_list v === ')
for psk, psv in placeholder_substitutes.items():
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, 'Deduced type'))

View File

@ -130,6 +130,8 @@ class AppliedType3(Type3):
"""
def __init__(self, base: Type3, args: Iterable[Type3OrPlaceholder]) -> None:
args = [*args]
super().__init__(
base.name
+ ' ('
@ -138,7 +140,7 @@ class AppliedType3(Type3):
)
self.base = base
self.args = [*args]
self.args = args
def __repr__(self) -> str:
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
@ -232,7 +234,16 @@ 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.
It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated.
"""
tuple = Type3('tuple') # pylint: disable=W0622
"""
This is a fixed length piece of memory.
It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
"""
LOOKUP_TABLE: Dict[str, Type3] = {

View File

@ -167,7 +167,6 @@ def testEntry() -> u64:
with pytest.raises(Type3Exception, match='u64 must be u32 instead'):
Suite(code_py).run_code()
assert False
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])

View File

@ -1,5 +1,7 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..constants import COMPLETE_NUMERIC_TYPES, TYPE_MAP
from ..helpers import Suite
@ -11,10 +13,7 @@ CONSTANT: ({type_}, ) = (65, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT)
def helper(vector: ({type_}, )) -> {type_}:
return vector[0]
return CONSTANT[0]
"""
result = Suite(code_py).run_code()
@ -28,10 +27,7 @@ CONSTANT: (u8, u8, u32, u32, u64, u64, ) = (11, 22, 3333, 4444, 555555, 666666,
@exported
def testEntry() -> u32:
return helper(CONSTANT)
def helper(vector: (u8, u8, u32, u32, u64, u64, )) -> u32:
return vector[2]
return CONSTANT[2]
"""
result = Suite(code_py).run_code()
@ -83,13 +79,22 @@ def testEntry() -> i32x4:
assert (1, 2, 3, 0) == result.returned_value
@pytest.mark.integration_test
def test_assign_to_tuple_with_tuple():
code_py = """
CONSTANT: (u32, ) = 0
"""
with pytest.raises(Type3Exception, match='Must be tuple'):
Suite(code_py).run_code()
@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'):
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -98,7 +103,7 @@ def test_tuple_constant_too_many_values():
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -107,5 +112,29 @@ def test_tuple_constant_type_mismatch():
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'):
with pytest.raises(Type3Exception, match='Must fit in 1 byte(s)'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_must_use_literal_for_indexing():
code_py = """
CONSTANT: u32 = 0
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[CONSTANT]
"""
with pytest.raises(Type3Exception, match='Tuples must be indexed with literals'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_must_use_integer_for_indexing():
code_py = """
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[0.0]
"""
with pytest.raises(Type3Exception, match='Must be integer'):
Suite(code_py).run_code()