Introduced type classes, added debugging info for constraint
This commit is contained in:
parent
9f21d0fd1d
commit
3bac625714
2
TODO.md
2
TODO.md
@ -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.
|
||||||
|
|||||||
@ -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)})'
|
||||||
|
|||||||
@ -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')
|
||||||
|
|
||||||
|
|||||||
@ -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 ^ === ')
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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_):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user