Introduced type classes, added debugging info for constraint

This commit is contained in:
Johan B.W. de Vries 2022-11-25 15:04:15 +01:00
parent 9f21d0fd1d
commit 3bac625714
7 changed files with 188 additions and 12 deletions

View File

@ -6,6 +6,8 @@
- Figure out how to do type classes - Figure out how to do type classes
- Implement structs again, with the `.foo` notation working - Implement structs again, with the `.foo` notation working
- Rename constant to literal
- Implement a trace() builtin for debugging - Implement a trace() builtin for debugging
- Check if we can use DataView in the Javascript examples, e.g. with setUint32 - 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. - 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.

View File

@ -23,6 +23,8 @@ CheckResult = Union[None, Error, RequireTypeSubstitutes]
SubstitutionMap = Dict[types.PlaceholderForType, types.Type3] SubstitutionMap = Dict[types.PlaceholderForType, types.Type3]
HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]]
class Context: class Context:
""" """
Context for constraints Context for constraints
@ -56,23 +58,29 @@ class ConstraintBase:
""" """
raise NotImplementedError(self, self.substitute_placeholders) raise NotImplementedError(self, self.substitute_placeholders)
def human_readable(self) -> HumanReadableRet:
"""
Returns a more human readable form of this constraint
"""
return repr(self), {}
class SameTypeConstraint(ConstraintBase): class SameTypeConstraint(ConstraintBase):
""" """
Verifies that an expression has an expected type Verifies that an expression has an expected type
""" """
__slots__ = ('expected', 'actual', 'message', ) __slots__ = ('expected', 'actual', 'message', )
expected: types.Type3 expected: types.Type3OrPlaceholder
actual: types.Type3OrPlaceholder actual: types.Type3OrPlaceholder
message: str message: str
def __init__(self, expected: types.Type3, actual: types.Type3OrPlaceholder, message: str) -> None: def __init__(self, expected: types.Type3OrPlaceholder, actual: types.Type3OrPlaceholder, message: str) -> None:
self.expected = expected self.expected = expected
self.actual = actual self.actual = actual
self.message = message self.message = message
def check(self) -> CheckResult: def check(self) -> CheckResult:
if isinstance(self.actual, types.PlaceholderForType): if isinstance(self.expected, types.PlaceholderForType) or isinstance(self.actual, types.PlaceholderForType):
return RequireTypeSubstitutes() return RequireTypeSubstitutes()
if self.expected is self.actual: if self.expected is self.actual:
@ -81,21 +89,88 @@ class SameTypeConstraint(ConstraintBase):
return Error(f'{self.expected:s} must be {self.actual:s} instead') return Error(f'{self.expected:s} must be {self.actual:s} instead')
def get_new_placeholder_substitutes(self) -> SubstitutionMap: def get_new_placeholder_substitutes(self) -> SubstitutionMap:
if isinstance(self.actual, types.PlaceholderForType): result: SubstitutionMap = {}
return {
if isinstance(self.expected, types.Type3) and isinstance(self.actual, types.PlaceholderForType):
result = {
self.actual: self.expected self.actual: self.expected
} }
return {} self.actual.get_substituted(self.expected)
self.actual = self.expected
if isinstance(self.actual, types.Type3) and isinstance(self.expected, types.PlaceholderForType):
result = {
self.expected: self.actual
}
self.expected.get_substituted(self.actual)
self.expected = self.actual
return result
def substitute_placeholders(self, smap: SubstitutionMap) -> None: def substitute_placeholders(self, smap: SubstitutionMap) -> None:
if isinstance(self.expected, types.PlaceholderForType) and self.expected in smap: # FIXME: Check recursive?
self.expected.get_substituted(smap[self.expected])
self.expected = smap[self.expected]
if isinstance(self.actual, types.PlaceholderForType) and self.actual in smap: # FIXME: Check recursive? if isinstance(self.actual, types.PlaceholderForType) and self.actual in smap: # FIXME: Check recursive?
self.actual.get_substituted(smap[self.actual]) self.actual.get_substituted(smap[self.actual])
self.actual = smap[self.actual] self.actual = smap[self.actual]
def human_readable(self) -> HumanReadableRet:
return (
'{expected} == {actual}',
{
'expected': self.expected,
'actual': self.actual,
'comment': self.message,
},
)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'SameTypeConstraint({repr(self.expected)}, {repr(self.actual)}, {repr(self.message)})' return f'SameTypeConstraint({repr(self.expected)}, {repr(self.actual)}, {repr(self.message)})'
class MustImplementTypeClassConstraint(ConstraintBase):
"""
A type must implement a given type class
"""
__slots__ = ('type_class3', 'type3', )
type_class3: str
type3: types.Type3OrPlaceholder
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder) -> None:
self.type_class3 = type_class3
self.type3 = type3
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
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]
def check(self) -> CheckResult:
if isinstance(self.type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
if 'BitWiseOr' == self.type_class3 and (self.type3 is types.u8 or self.type3 is types.u32 or self.type3 is types.u64):
return None
return Error(f'{self.type3.name} does not implement the {self.type_class3} type class')
def human_readable(self) -> HumanReadableRet:
return (
'{type3} derives {type_class3}',
{
'type_class3': self.type_class3,
'type3': self.type3,
},
)
def __repr__(self) -> str:
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)})'
class LiteralFitsConstraint(ConstraintBase): class LiteralFitsConstraint(ConstraintBase):
""" """
A literal value fits a given type A literal value fits a given type
@ -159,5 +234,14 @@ class LiteralFitsConstraint(ConstraintBase):
self.type3.get_substituted(smap[self.type3]) self.type3.get_substituted(smap[self.type3])
self.type3 = smap[self.type3] self.type3 = smap[self.type3]
def human_readable(self) -> HumanReadableRet:
return (
'{literal} : {type3}',
{
'literal': self.literal,
'type3': self.type3,
},
)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)})' return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)})'

View File

@ -11,7 +11,7 @@ from .constraints import (
Context, Context,
ConstraintBase, ConstraintBase,
LiteralFitsConstraint, SameTypeConstraint, LiteralFitsConstraint, MustImplementTypeClassConstraint, SameTypeConstraint,
) )
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]: def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
@ -35,6 +35,18 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas
yield SameTypeConstraint(inp.variable.type3, inp.type3, f'The type of a variable reference is the same as the type of variable {inp.variable.name}') yield SameTypeConstraint(inp.variable.type3, inp.type3, f'The type of a variable reference is the same as the type of variable {inp.variable.name}')
return return
if isinstance(inp, ourlang.BinaryOp):
if '|' == inp.operator:
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('BitWiseOr', inp.left.type3)
yield SameTypeConstraint(inp.right.type3, inp.left.type3, '(|) :: a -> a -> a')
yield SameTypeConstraint(inp.type3, inp.right.type3, '(|) :: a -> a -> a')
return
raise NotImplementedError(expression, inp)
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
yield SameTypeConstraint(inp.function.returns_type3, inp.type3, f'The type of a function call to {inp.function.name} is the same as the type that the function returns') yield SameTypeConstraint(inp.function.returns_type3, inp.type3, f'The type of a function call to {inp.function.name} is the same as the type that the function returns')

View File

@ -3,20 +3,21 @@ Entry point to the type3 system
""" """
from typing import Dict, List from typing import Dict, List
from .. import codestyle
from .. import ourlang from .. import ourlang
from .constraints import Error, RequireTypeSubstitutes from .constraints import ConstraintBase, Error, RequireTypeSubstitutes, SameTypeConstraint, SubstitutionMap
from .constraintsgenerator import phasm_type3_generate_constraints from .constraintsgenerator import phasm_type3_generate_constraints
from .types import PlaceholderForType, Type3 from .types import PlaceholderForType, Type3
MAX_RESTACK_COUNT = 10 MAX_RESTACK_COUNT = 100
class Type3Exception(BaseException): class Type3Exception(BaseException):
""" """
Thrown when the Type3 system detects constraints that do not hold Thrown when the Type3 system detects constraints that do not hold
""" """
def phasm_type3(inp: ourlang.Module) -> None: def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
constraint_list = phasm_type3_generate_constraints(inp) constraint_list = phasm_type3_generate_constraints(inp)
assert constraint_list assert constraint_list
@ -26,6 +27,10 @@ def phasm_type3(inp: ourlang.Module) -> None:
error_list: List[Error] = [] error_list: List[Error] = []
while constraint_list: while constraint_list:
if verbose:
print()
print_constraint_list(constraint_list, placeholder_substitutes)
constraint = constraint_list.pop(0) constraint = constraint_list.pop(0)
constraint.substitute_placeholders(placeholder_substitutes) constraint.substitute_placeholders(placeholder_substitutes)
@ -33,10 +38,14 @@ def phasm_type3(inp: ourlang.Module) -> None:
check_result = constraint.check() check_result = constraint.check()
if check_result is None: if check_result is None:
if verbose:
print('Constraint checks out')
continue continue
if isinstance(check_result, Error): if isinstance(check_result, Error):
error_list.append(check_result) error_list.append(check_result)
if verbose:
print('Got an error')
continue continue
if isinstance(check_result, RequireTypeSubstitutes): if isinstance(check_result, RequireTypeSubstitutes):
@ -46,6 +55,8 @@ def phasm_type3(inp: ourlang.Module) -> None:
raise Exception('This looks like an infinite loop', constraint_list) raise Exception('This looks like an infinite loop', constraint_list)
constraint_list.append(constraint) constraint_list.append(constraint)
if verbose:
print('Back on the todo list')
continue continue
raise NotImplementedError(constraint, check_result) raise NotImplementedError(constraint, check_result)
@ -54,3 +65,40 @@ def phasm_type3(inp: ourlang.Module) -> None:
raise Type3Exception(error_list) raise Type3Exception(error_list)
# TODO: Implement type substitution on the AST # TODO: Implement type substitution on the AST
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:
txt, fmt = constraint.human_readable()
act_fmt: Dict[str, str] = {}
for fmt_key, fmt_val in fmt.items():
if isinstance(fmt_val, ourlang.Expression):
fmt_val = codestyle.expression(fmt_val)
if isinstance(fmt_val, Type3):
fmt_val = fmt_val.name
if isinstance(fmt_val, PlaceholderForType):
placeholder_id = id(fmt_val)
if placeholder_id not in placeholder_id_map:
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
fmt_val = placeholder_id_map[placeholder_id]
if not isinstance(fmt_val, str):
fmt_val = repr(fmt_val)
act_fmt[fmt_key] = fmt_val
if 'comment' in act_fmt:
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + act_fmt['comment'])
else:
print('- ' + txt.format(**act_fmt))
def print_constraint_list(constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
placeholder_id_map: Dict[int, str] = {}
print('=== v type3 constraint_list v === ')
for psk, psv in placeholder_substitutes.items():
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, 'Deduced type'))
for constraint in constraint_list:
print_constraint(placeholder_id_map, constraint)
print('=== ^ type3 constraint_list ^ === ')

View File

@ -78,6 +78,10 @@ class PlaceholderForType:
self.update_on_substitution = [] self.update_on_substitution = []
@property
def name(self) -> str:
return f'T{id(self)}'
def __repr__(self) -> str: def __repr__(self) -> str:
uos = ', '.join(repr(x) for x in self.update_on_substitution) uos = ', '.join(repr(x) for x in self.update_on_substitution)

View File

@ -41,7 +41,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_type3(self.phasm_ast, verbose=True)
def compile_ast(self) -> None: def compile_ast(self) -> None:
""" """

View File

@ -131,7 +131,7 @@ def testEntry() -> u32:
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64']) @pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_bitwise_or(type_): def test_bitwise_or_uint(type_):
code_py = f""" code_py = f"""
@exported @exported
def testEntry() -> {type_}: def testEntry() -> {type_}:
@ -143,6 +143,32 @@ def testEntry() -> {type_}:
assert 11 == result.returned_value assert 11 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value) assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_bitwise_or_inv_type():
code_py = """
@exported
def testEntry() -> f64:
return 10.0 | 3.0
"""
with pytest.raises(Type3Exception, match='f64 does not implement the BitWiseOr type class'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_bitwise_or_type_mismatch():
code_py = """
CONSTANT1: u32 = 3
CONSTANT2: u64 = 3
@exported
def testEntry() -> u64:
return CONSTANT1 | CONSTANT2
"""
with pytest.raises(Type3Exception, match='u64 must be u32 instead'):
Suite(code_py).run_code()
assert False
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64']) @pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_bitwise_xor(type_): def test_bitwise_xor(type_):