diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index bcc3775..dd73775 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -3,12 +3,23 @@ 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 typing import Dict, Tuple, Union from .. import ourlang from . import types +class Error: + def __init__(self, msg: str) -> None: + self.msg = msg + +class RequireTypeSubstitutes: + pass + +CheckResult = Union[None, Error, RequireTypeSubstitutes] + +SubstitutionMap = Dict[types.PlaceholderForType, types.Type3] + class Context: """ Context for constraints @@ -22,26 +33,78 @@ class ConstraintBase: """ __slots__ = () - def check(self) -> Optional[str]: + def check(self) -> CheckResult: """ - Checks if the contraint hold, returning an error if it doesn't + Checks if the constraint hold, returning an error if it doesn't """ raise NotImplementedError + def get_new_placeholder_substitutes(self) -> SubstitutionMap: + """ + Returns any new placeholders that can be substituted for actual types + """ + return {} + + def substitute_placeholders(self, smap: SubstitutionMap) -> None: + """ + Called with type substitutes, so you can update any placeholders + you may have with the known types. Note that this does not guarantee + that all types are known, you may still have some placeholders left. + """ + raise NotImplementedError(self, self.substitute_placeholders) + +class SameTypeConstraint(ConstraintBase): + """ + Verifies that an expression has an expected type + """ + __slots__ = ('expected', 'actual', ) + + expected: types.Type3 + actual: types.Type3OrPlaceholder + + def __init__(self, expected: types.Type3, actual: types.Type3OrPlaceholder) -> None: + self.expected = expected + self.actual = actual + + def check(self) -> CheckResult: + if isinstance(self.actual, types.PlaceholderForType): + return RequireTypeSubstitutes() + + if self.expected is self.actual: + return None + + raise NotImplementedError + + def get_new_placeholder_substitutes(self) -> SubstitutionMap: + if isinstance(self.actual, types.PlaceholderForType): + return { + self.actual: self.expected + } + + return {} + + def substitute_placeholders(self, smap: SubstitutionMap) -> None: + if isinstance(self.actual, types.PlaceholderForType) and self.actual in smap: + self.actual.get_substituted(smap[self.actual]) + self.actual = smap[self.actual] + class LiteralFitsConstraint(ConstraintBase): """ A literal value fits a given type """ __slots__ = ('type3', 'literal', ) - type3: types.Type3 + type3: types.Type3OrPlaceholder literal: ourlang.ConstantPrimitive - def __init__(self, type3: types.Type3, literal: ourlang.ConstantPrimitive) -> None: + def __init__(self, type3: types.Type3OrPlaceholder, literal: ourlang.ConstantPrimitive) -> None: self.type3 = type3 self.literal = literal - def check(self) -> Optional[str]: + def check(self) -> CheckResult: + if isinstance(self.type3, types.PlaceholderForType): + return RequireTypeSubstitutes() + val = self.literal.value int_table: Dict[str, Tuple[int, bool]] = { @@ -60,11 +123,11 @@ class LiteralFitsConstraint(ConstraintBase): try: val.to_bytes(bts, 'big', signed=sgn) except OverflowError: - return f'Must fit in {bts} byte(s)' # FIXME: Add line information + return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information return None - return 'Must be integer' # FIXME: Add line information + return Error('Must be integer') # FIXME: Add line information float_table: Dict[str, None] = { 'f32': None, @@ -79,6 +142,6 @@ class LiteralFitsConstraint(ConstraintBase): return None - return 'Must be real' # FIXME: Add line information + return Error('Must be real') # FIXME: Add line information raise NotImplementedError diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index ed1e6e0..3f61cd7 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -11,7 +11,7 @@ from .constraints import ( Context, ConstraintBase, - LiteralFitsConstraint, + LiteralFitsConstraint, SameTypeConstraint, ) from . import types @@ -34,16 +34,30 @@ def constant(ctx: Context, inp: ourlang.Constant) -> Generator[ConstraintBase, N raise NotImplementedError(constant, inp) def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBase, None, None]: - if isinstance(inp, ourlang.ConstantPrimitive): - print('hi') + if isinstance(inp, ourlang.Constant): + yield from constant(ctx, inp) + return 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) + if inp.returns_type3 is None: + raise NotImplementedError + + actual: types.Type3OrPlaceholder + if inp.statements[0].value.type3 is None: + print('inp.statements[0].value', inp.statements[0].value) + actual = types.PlaceholderForType([inp.statements[0].value]) + else: + actual = inp.statements[0].value.type3 + + yield SameTypeConstraint(inp.returns_type3, actual) + def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Generator[ConstraintBase, None, None]: yield from constant(ctx, inp.constant) @@ -65,7 +79,8 @@ def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Generat 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] + # lit.type3 = inp.type3.args[0] + raise NotImplementedError return raise NotImplementedError(constant, inp, inp.type3) diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index 7df41c7..82a9a4d 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -1,9 +1,13 @@ """ Entry point to the type3 system """ +from typing import Dict, List + from .. import ourlang +from .constraints import Error, RequireTypeSubstitutes from .constraintsgenerator import phasm_type3_generate_constraints +from .types import PlaceholderForType, Type3 class Type3Exception(BaseException): """ @@ -11,16 +15,33 @@ class Type3Exception(BaseException): """ def phasm_type3(inp: ourlang.Module) -> None: - constraints = phasm_type3_generate_constraints(inp) - assert constraints + constraint_list = phasm_type3_generate_constraints(inp) + assert constraint_list - error_list = [] - for constraint in constraints: - error = constraint.check() - if error is not None: - error_list.append(error) + placeholder_substitutes: Dict[PlaceholderForType, Type3] = {} + + error_list: List[Error] = [] + while constraint_list: # FIXME: How to detect infinite loop? Is that necessary? + constraint = constraint_list.pop(0) + + constraint.substitute_placeholders(placeholder_substitutes) + placeholder_substitutes.update(constraint.get_new_placeholder_substitutes()) + + check_result = constraint.check() + if check_result is None: + continue + + if isinstance(check_result, Error): + error_list.append(check_result) + + if isinstance(check_result, RequireTypeSubstitutes): + constraint_list.append(constraint) + continue + + raise NotImplementedError(constraint, check_result) if error_list: raise Type3Exception(error_list) - # TODO: Implement type substitution + # TODO: Implement type substitution on the AST + print('placeholder_substitutes', placeholder_substitutes) diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 1d7fc6a..21b522a 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -4,7 +4,17 @@ 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 +from typing import Any, ClassVar, Dict, Iterable, Optional, List, Protocol, Union + +class ExpressionProtocol(Protocol): + """ + A protocol for classes that should be updated on substitution + """ + + type3: Optional['Type3'] + """ + The type to update + """ class Type3: """ @@ -30,7 +40,7 @@ class Type3: if format_spec != 's': raise TypeError('unsupported format string passed to Type3.__format__') - return self.name + return str(self) def __eq__(self, other: Any) -> bool: raise NotImplementedError @@ -44,6 +54,62 @@ class Type3: def __bool__(self) -> bool: raise NotImplementedError +class PlaceholderForType: + """ + A placeholder type, for when we don't know the final type yet + """ + __slots__ = ('number', 'update_on_substitution', ) + + number: int + update_on_substitution: List[ExpressionProtocol] + + NEXT_NUMBER: ClassVar[int] = 1 + + def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None: + self.number = PlaceholderForType.NEXT_NUMBER + PlaceholderForType.NEXT_NUMBER += 1 + + self.update_on_substitution = [*update_on_substitution] + + def get_substituted(self, result_type: Type3) -> None: + """ + Informs this Placeholder that it's getting substituted + + This will also clear the update_on_substitution list + """ + for uos in self.update_on_substitution: + uos.type3 = result_type + + self.update_on_substitution = [] + + def __repr__(self) -> str: + uos = ', '.join(repr(x) for x in self.update_on_substitution) + + return f'PlaceholderForType({self.number} [{uos}])' + + def __str__(self) -> str: + return f'T{self.number}' + + def __format__(self, format_spec: str) -> str: + if format_spec != 's': + raise TypeError('unsupported format string passed to Type3.__format__') + + return str(self) + + def __eq__(self, other: Any) -> bool: + raise NotImplementedError + + def __ne__(self, other: Any) -> bool: + raise NotImplementedError + + def __hash__(self) -> int: + return 0 # Valid but performs badly + + def __bool__(self) -> bool: + raise NotImplementedError + +Type3OrPlaceholder = Union[Type3, PlaceholderForType] + class AppliedType3(Type3): """ A Type3 that has been applied to another type @@ -55,16 +121,16 @@ class AppliedType3(Type3): The base type """ - args: List[Type3] + args: List[Type3OrPlaceholder] """ - The applied types + The applied types (or placeholders there for) """ - def __init__(self, base: Type3, args: Iterable[Type3]) -> None: + def __init__(self, base: Type3, args: Iterable[Type3OrPlaceholder]) -> None: super().__init__( base.name + ' (' - + ') ('.join(x.name for x in args) + + ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution? + ')' ) @@ -141,11 +207,6 @@ 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,