Ripping out Type2 (type_var) system
This commit is contained in:
parent
8cc47ae63e
commit
b5a28daebf
@ -6,7 +6,7 @@ It's intented to be a "any color, as long as it's black" kind of renderer
|
||||
from typing import Generator, Optional
|
||||
|
||||
from . import ourlang
|
||||
from . import typing
|
||||
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
|
||||
|
||||
def phasm_render(inp: ourlang.Module) -> str:
|
||||
"""
|
||||
@ -15,35 +15,43 @@ def phasm_render(inp: ourlang.Module) -> str:
|
||||
return module(inp)
|
||||
|
||||
Statements = Generator[str, None, None]
|
||||
#
|
||||
# def type_var(inp: Optional[typing.TypeVar]) -> str:
|
||||
# """
|
||||
# Render: type's name
|
||||
# """
|
||||
# assert inp is not None, typing.ASSERTION_ERROR
|
||||
#
|
||||
# mtyp = typing.simplify(inp)
|
||||
# if mtyp is None:
|
||||
# raise NotImplementedError(f'Rendering type {inp}')
|
||||
#
|
||||
# return mtyp
|
||||
|
||||
def type_var(inp: Optional[typing.TypeVar]) -> str:
|
||||
def type3(inp: Type3OrPlaceholder) -> str:
|
||||
"""
|
||||
Render: type's name
|
||||
"""
|
||||
assert inp is not None, typing.ASSERTION_ERROR
|
||||
assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR
|
||||
|
||||
mtyp = typing.simplify(inp)
|
||||
if mtyp is None:
|
||||
raise NotImplementedError(f'Rendering type {inp}')
|
||||
return inp.name
|
||||
|
||||
return mtyp
|
||||
|
||||
def struct_definition(inp: typing.TypeStruct) -> str:
|
||||
"""
|
||||
Render: TypeStruct's definition
|
||||
"""
|
||||
result = f'class {inp.name}:\n'
|
||||
for mem in inp.members: # TODO: Broken after new type system
|
||||
raise NotImplementedError('Structs broken after new type system')
|
||||
# result += f' {mem.name}: {type_(mem.type)}\n'
|
||||
|
||||
return result
|
||||
# def struct_definition(inp: typing.TypeStruct) -> str:
|
||||
# """
|
||||
# Render: TypeStruct's definition
|
||||
# """
|
||||
# result = f'class {inp.name}:\n'
|
||||
# for mem in inp.members: # TODO: Broken after new type system
|
||||
# raise NotImplementedError('Structs broken after new type system')
|
||||
# # result += f' {mem.name}: {type_(mem.type)}\n'
|
||||
#
|
||||
# return result
|
||||
|
||||
def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
|
||||
"""
|
||||
Render: Module Constant's definition
|
||||
"""
|
||||
return f'{inp.name}: {type_var(inp.type_var)} = {expression(inp.constant)}\n'
|
||||
return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n'
|
||||
|
||||
def expression(inp: ourlang.Expression) -> str:
|
||||
"""
|
||||
@ -70,7 +78,7 @@ def expression(inp: ourlang.Expression) -> str:
|
||||
return f'{inp.operator}({expression(inp.right)})'
|
||||
|
||||
if inp.operator == 'cast':
|
||||
mtyp = type_var(inp.type_var)
|
||||
mtyp = type3(inp.type3)
|
||||
if mtyp is None:
|
||||
raise NotImplementedError(f'Casting to type {inp.type_var}')
|
||||
|
||||
@ -150,11 +158,11 @@ def function(inp: ourlang.Function) -> str:
|
||||
result += '@imported\n'
|
||||
|
||||
args = ', '.join(
|
||||
f'{p.name}: {type_var(p.type_var)}'
|
||||
f'{p.name}: {type3(p.type3)}'
|
||||
for p in inp.posonlyargs
|
||||
)
|
||||
|
||||
result += f'def {inp.name}({args}) -> {type_var(inp.returns_type_var)}:\n'
|
||||
result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n'
|
||||
|
||||
if inp.imported:
|
||||
result += ' pass\n'
|
||||
@ -172,10 +180,10 @@ def module(inp: ourlang.Module) -> str:
|
||||
"""
|
||||
result = ''
|
||||
|
||||
for struct in inp.structs.values():
|
||||
if result:
|
||||
result += '\n'
|
||||
result += struct_definition(struct)
|
||||
# for struct in inp.structs.values():
|
||||
# if result:
|
||||
# result += '\n'
|
||||
# result += struct_definition(struct)
|
||||
|
||||
for cdef in inp.constant_defs.values():
|
||||
if result:
|
||||
|
||||
@ -7,7 +7,6 @@ import struct
|
||||
|
||||
from . import codestyle
|
||||
from . import ourlang
|
||||
from . import typing
|
||||
from .type3 import types as type3types
|
||||
from . import wasm
|
||||
|
||||
@ -22,45 +21,38 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module:
|
||||
"""
|
||||
return module(inp)
|
||||
|
||||
def type_var(inp: Optional[typing.TypeVar]) -> wasm.WasmType:
|
||||
def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
||||
"""
|
||||
Compile: type
|
||||
|
||||
Types are used for example in WebAssembly function parameters
|
||||
and return types.
|
||||
"""
|
||||
assert inp is not None, typing.ASSERTION_ERROR
|
||||
assert isinstance(inp, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
mtyp = typing.simplify(inp)
|
||||
|
||||
if mtyp == 'u8':
|
||||
if inp is type3types.u8:
|
||||
# WebAssembly has only support for 32 and 64 bits
|
||||
# So we need to store more memory per byte
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
if mtyp == 'u32':
|
||||
if inp is type3types.u32:
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
if mtyp == 'u64':
|
||||
if inp is type3types.u64:
|
||||
return wasm.WasmTypeInt64()
|
||||
|
||||
if mtyp == 'i32':
|
||||
if inp is type3types.i32:
|
||||
return wasm.WasmTypeInt32()
|
||||
|
||||
if mtyp == 'i64':
|
||||
if inp is type3types.i64:
|
||||
return wasm.WasmTypeInt64()
|
||||
|
||||
if mtyp == 'f32':
|
||||
if inp is type3types.f32:
|
||||
return wasm.WasmTypeFloat32()
|
||||
|
||||
if mtyp == 'f64':
|
||||
if inp is type3types.f64:
|
||||
return wasm.WasmTypeFloat64()
|
||||
|
||||
assert inp is not None, typing.ASSERTION_ERROR
|
||||
tc_type = inp.get_type()
|
||||
if tc_type is None:
|
||||
raise NotImplementedError(type_var, inp)
|
||||
|
||||
# if tc_prim.primitive is typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
|
||||
# # StaticArray, Tuples and Structs are passed as pointer
|
||||
# # And pointers are i32
|
||||
@ -72,7 +64,7 @@ def type_var(inp: Optional[typing.TypeVar]) -> wasm.WasmType:
|
||||
# # And pointers are i32
|
||||
# return wasm.WasmTypeInt32()
|
||||
|
||||
raise NotImplementedError(inp, mtyp)
|
||||
raise NotImplementedError(type3, inp)
|
||||
|
||||
# Operators that work for i32, i64, f32, f64
|
||||
OPERATOR_MAP = {
|
||||
@ -147,42 +139,35 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
Compile: Any expression
|
||||
"""
|
||||
if isinstance(inp, ourlang.ConstantPrimitive):
|
||||
assert inp.type3 is not None, typing.ASSERTION_ERROR
|
||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
|
||||
assert inp.type_var is not None, typing.ASSERTION_ERROR
|
||||
|
||||
stp = typing.simplify(inp.type_var)
|
||||
if stp is None:
|
||||
raise NotImplementedError(f'Constants with type {inp.type_var}')
|
||||
|
||||
if stp == 'u8':
|
||||
if inp.type3 is type3types.u8:
|
||||
# No native u8 type - treat as i32, with caution
|
||||
assert isinstance(inp.value, int)
|
||||
wgn.i32.const(inp.value)
|
||||
return
|
||||
|
||||
if stp in ('i32', 'u32'):
|
||||
if inp.type3 is type3types.i32 or inp.type3 is type3types.u32:
|
||||
assert isinstance(inp.value, int)
|
||||
wgn.i32.const(inp.value)
|
||||
return
|
||||
|
||||
if stp in ('i64', 'u64'):
|
||||
if inp.type3 is type3types.i64 or inp.type3 is type3types.u64:
|
||||
assert isinstance(inp.value, int)
|
||||
wgn.i64.const(inp.value)
|
||||
return
|
||||
|
||||
if stp == 'f32':
|
||||
if inp.type3 is type3types.f32:
|
||||
assert isinstance(inp.value, float)
|
||||
wgn.f32.const(inp.value)
|
||||
return
|
||||
|
||||
if stp == 'f64':
|
||||
if inp.type3 is type3types.f64:
|
||||
assert isinstance(inp.value, float)
|
||||
wgn.f64.const(inp.value)
|
||||
return
|
||||
|
||||
raise NotImplementedError(f'Constants with type {stp}')
|
||||
raise NotImplementedError(f'Constants with type {inp.type3}')
|
||||
|
||||
if isinstance(inp, ourlang.VariableReference):
|
||||
if isinstance(inp.variable, ourlang.FunctionParam):
|
||||
@ -190,10 +175,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
return
|
||||
|
||||
if isinstance(inp.variable, ourlang.ModuleConstantDef):
|
||||
assert inp.variable.type_var is not None, typing.ASSERTION_ERROR
|
||||
tc_type = inp.variable.type_var.get_type()
|
||||
if tc_type is None:
|
||||
raise NotImplementedError(expression, inp, inp.variable.type_var)
|
||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
# TODO: Broken after new type system
|
||||
# if isinstance(inp.type, typing.TypeTuple):
|
||||
@ -212,13 +194,6 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
|
||||
assert inp.variable.data_block is None, 'Primitives are not memory stored'
|
||||
|
||||
assert inp.variable.type_var is not None, typing.ASSERTION_ERROR
|
||||
mtyp = typing.simplify(inp.variable.type_var)
|
||||
if mtyp is None:
|
||||
# In the future might extend this by having structs or tuples
|
||||
# as members of struct or tuples
|
||||
raise NotImplementedError(expression, inp, inp.type_var)
|
||||
|
||||
expression(wgn, inp.variable.constant)
|
||||
return
|
||||
|
||||
@ -228,49 +203,49 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
expression(wgn, inp.left)
|
||||
expression(wgn, inp.right)
|
||||
|
||||
assert inp.type_var is not None, typing.ASSERTION_ERROR
|
||||
mtyp = typing.simplify(inp.type_var)
|
||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
# FIXME: Re-implement build-in operators
|
||||
|
||||
if mtyp == 'u8':
|
||||
if inp.type3 is type3types.u8:
|
||||
if operator := U8_OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i32.{operator}')
|
||||
return
|
||||
if mtyp == 'u32':
|
||||
if inp.type3 is type3types.u32:
|
||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i32.{operator}')
|
||||
return
|
||||
if operator := U32_OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i32.{operator}')
|
||||
return
|
||||
if mtyp == 'u64':
|
||||
if inp.type3 is type3types.u64:
|
||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i64.{operator}')
|
||||
return
|
||||
if operator := U64_OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i64.{operator}')
|
||||
return
|
||||
if mtyp == 'i32':
|
||||
if inp.type3 is type3types.i32:
|
||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i32.{operator}')
|
||||
return
|
||||
if operator := I32_OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i32.{operator}')
|
||||
return
|
||||
if mtyp == 'i64':
|
||||
if inp.type3 is type3types.i64:
|
||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i64.{operator}')
|
||||
return
|
||||
if operator := I64_OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'i64.{operator}')
|
||||
return
|
||||
if mtyp == 'f32':
|
||||
if inp.type3 is type3types.f32:
|
||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'f32.{operator}')
|
||||
return
|
||||
if operator := F32_OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'f32.{operator}')
|
||||
return
|
||||
if mtyp == 'f64':
|
||||
if inp.type3 is type3types.f64:
|
||||
if operator := OPERATOR_MAP.get(inp.operator, None):
|
||||
wgn.add_statement(f'f64.{operator}')
|
||||
return
|
||||
@ -278,19 +253,18 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
wgn.add_statement(f'f64.{operator}')
|
||||
return
|
||||
|
||||
raise NotImplementedError(expression, inp.type_var, inp.operator)
|
||||
raise NotImplementedError(expression, inp.type3, inp.operator)
|
||||
|
||||
if isinstance(inp, ourlang.UnaryOp):
|
||||
expression(wgn, inp.right)
|
||||
|
||||
assert inp.type_var is not None, typing.ASSERTION_ERROR
|
||||
mtyp = typing.simplify(inp.type_var)
|
||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
if mtyp == 'f32':
|
||||
if inp.type3 is type3types.f32:
|
||||
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
|
||||
wgn.add_statement(f'f32.{inp.operator}')
|
||||
return
|
||||
if mtyp == 'f64':
|
||||
if inp.type3 is type3types.f64:
|
||||
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
|
||||
wgn.add_statement(f'f64.{inp.operator}')
|
||||
return
|
||||
@ -307,7 +281,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
# # Nothing to do, you can use an u8 value as a u32 no problem
|
||||
# return
|
||||
|
||||
raise NotImplementedError(expression, inp.type_var, inp.operator)
|
||||
raise NotImplementedError(expression, inp.type3, inp.operator)
|
||||
|
||||
if isinstance(inp, ourlang.FunctionCall):
|
||||
for arg in inp.arguments:
|
||||
@ -317,12 +291,12 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
return
|
||||
|
||||
if isinstance(inp, ourlang.Subscript):
|
||||
assert inp.varref.type3 is not None, typing.ASSERTION_ERROR
|
||||
|
||||
assert inp.varref.type_var is not None, typing.ASSERTION_ERROR
|
||||
tc_type = inp.varref.type_var.get_type()
|
||||
if tc_type is None:
|
||||
raise NotImplementedError(expression, inp, inp.varref.type_var)
|
||||
# assert inp.varref.type3 is not None, typing.ASSERTION_ERROR
|
||||
#
|
||||
# assert inp.varref.type_var is not None, typing.ASSERTION_ERROR
|
||||
# tc_type = inp.varref.type_var.get_type()
|
||||
# if tc_type is None:
|
||||
# raise NotImplementedError(expression, inp, inp.varref.type_var)
|
||||
|
||||
# if tc_prim.primitive == typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
|
||||
# if not isinstance(inp.index, ourlang.ConstantPrimitive):
|
||||
@ -362,7 +336,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
# wgn.add_statement(f'{mtyp}.load', 'offset=' + str(bitwidth // 8 * inp.index.value))
|
||||
# return
|
||||
|
||||
raise NotImplementedError(expression, inp, inp.varref.type_var)
|
||||
raise NotImplementedError(expression, inp, inp.varref.type3)
|
||||
|
||||
|
||||
# TODO: Broken after new type system
|
||||
@ -422,12 +396,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
|
||||
"""
|
||||
Compile: Fold expression
|
||||
"""
|
||||
assert inp.base.type_var is not None, typing.ASSERTION_ERROR
|
||||
mtyp = typing.simplify(inp.base.type_var)
|
||||
if mtyp is None:
|
||||
# In the future might extend this by having structs or tuples
|
||||
# as members of struct or tuples
|
||||
raise NotImplementedError(expression, inp, inp.base)
|
||||
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||
|
||||
raise NotImplementedError('TODO: Broken after new type system')
|
||||
|
||||
@ -546,7 +515,7 @@ def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
|
||||
"""
|
||||
Compile: function argument
|
||||
"""
|
||||
return (inp.name, type_var(inp.type_var), )
|
||||
return (inp.name, type3(inp.type3), )
|
||||
|
||||
def import_(inp: ourlang.Function) -> wasm.Import:
|
||||
"""
|
||||
@ -562,7 +531,7 @@ def import_(inp: ourlang.Function) -> wasm.Import:
|
||||
function_argument(x)
|
||||
for x in inp.posonlyargs
|
||||
],
|
||||
type_var(inp.returns_type_var)
|
||||
type3(inp.returns_type3)
|
||||
)
|
||||
|
||||
def function(inp: ourlang.Function) -> wasm.Function:
|
||||
@ -592,7 +561,7 @@ def function(inp: ourlang.Function) -> wasm.Function:
|
||||
(k, v.wasm_type(), )
|
||||
for k, v in wgn.locals.items()
|
||||
],
|
||||
type_var(inp.returns_type_var),
|
||||
type3(inp.returns_type3),
|
||||
wgn.statements
|
||||
)
|
||||
|
||||
|
||||
@ -10,12 +10,6 @@ from typing_extensions import Final
|
||||
WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
|
||||
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
|
||||
|
||||
from .typing import (
|
||||
TypeStruct,
|
||||
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
from .type3 import types as type3types
|
||||
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType
|
||||
|
||||
@ -23,15 +17,12 @@ class Expression:
|
||||
"""
|
||||
An expression within a statement
|
||||
"""
|
||||
__slots__ = ('type3', 'type_var', )
|
||||
__slots__ = ('type3', )
|
||||
|
||||
type3: Type3OrPlaceholder
|
||||
|
||||
type_var: Optional[TypeVar]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.type3 = PlaceholderForType([self])
|
||||
self.type_var = None
|
||||
|
||||
class Constant(Expression):
|
||||
"""
|
||||
@ -213,24 +204,22 @@ class FunctionParam:
|
||||
"""
|
||||
A parameter for a Function
|
||||
"""
|
||||
__slots__ = ('name', 'type3', 'type_str', 'type_var', )
|
||||
__slots__ = ('name', 'type3', 'type_str', )
|
||||
|
||||
name: str
|
||||
type3: Type3
|
||||
type_str: str
|
||||
type_var: Optional[TypeVar]
|
||||
|
||||
def __init__(self, name: str, type3: Type3) -> None:
|
||||
self.name = name
|
||||
self.type3 = type3
|
||||
self.type_str = type3.name
|
||||
self.type_var = None
|
||||
|
||||
class Function:
|
||||
"""
|
||||
A function processes input and produces output
|
||||
"""
|
||||
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'returns_str', 'returns_type_var', 'posonlyargs', )
|
||||
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'returns_str', 'posonlyargs', )
|
||||
|
||||
name: str
|
||||
lineno: int
|
||||
@ -239,7 +228,6 @@ class Function:
|
||||
statements: List[Statement]
|
||||
returns_type3: Type3
|
||||
returns_str: str
|
||||
returns_type_var: Optional[TypeVar]
|
||||
posonlyargs: List[FunctionParam]
|
||||
|
||||
def __init__(self, name: str, lineno: int) -> None:
|
||||
@ -250,7 +238,6 @@ class Function:
|
||||
self.statements = []
|
||||
self.returns_type3 = type3types.none
|
||||
self.returns_str = 'None'
|
||||
self.returns_type_var = None
|
||||
self.posonlyargs = []
|
||||
|
||||
# TODO: Broken after new type system
|
||||
@ -299,13 +286,12 @@ class ModuleConstantDef:
|
||||
"""
|
||||
A constant definition within a module
|
||||
"""
|
||||
__slots__ = ('name', 'lineno', 'type3', 'type_str', 'type_var', 'constant', 'data_block', )
|
||||
__slots__ = ('name', 'lineno', 'type3', 'type_str', 'constant', 'data_block', )
|
||||
|
||||
name: str
|
||||
lineno: int
|
||||
type3: Type3
|
||||
type_str: str
|
||||
type_var: Optional[TypeVar]
|
||||
constant: Constant
|
||||
data_block: Optional['ModuleDataBlock']
|
||||
|
||||
@ -314,7 +300,6 @@ class ModuleConstantDef:
|
||||
self.lineno = lineno
|
||||
self.type3 = type3
|
||||
self.type_str = type3.name
|
||||
self.type_var = None
|
||||
self.constant = constant
|
||||
self.data_block = data_block
|
||||
|
||||
@ -349,12 +334,12 @@ class Module:
|
||||
__slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',)
|
||||
|
||||
data: ModuleData
|
||||
structs: Dict[str, TypeStruct]
|
||||
# structs: Dict[str, TypeStruct]
|
||||
constant_defs: Dict[str, ModuleConstantDef]
|
||||
functions: Dict[str, Function]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.data = ModuleData()
|
||||
self.structs = {}
|
||||
# self.structs = {}
|
||||
self.constant_defs = {}
|
||||
self.functions = {}
|
||||
|
||||
@ -5,13 +5,6 @@ from typing import Any, Dict, NoReturn, Union
|
||||
|
||||
import ast
|
||||
|
||||
from .typing import (
|
||||
BUILTIN_TYPES,
|
||||
|
||||
TypeStruct,
|
||||
TypeStructMember,
|
||||
)
|
||||
|
||||
from .type3 import types as type3types
|
||||
|
||||
from .exceptions import StaticError
|
||||
@ -108,12 +101,12 @@ class OurVisitor:
|
||||
|
||||
return module
|
||||
|
||||
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, TypeStruct, ModuleConstantDef]:
|
||||
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, ModuleConstantDef]: # TypeStruct
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
return self.pre_visit_Module_FunctionDef(module, node)
|
||||
|
||||
if isinstance(node, ast.ClassDef):
|
||||
return self.pre_visit_Module_ClassDef(module, node)
|
||||
# if isinstance(node, ast.ClassDef):
|
||||
# return self.pre_visit_Module_ClassDef(module, node)
|
||||
|
||||
if isinstance(node, ast.AnnAssign):
|
||||
return self.pre_visit_Module_AnnAssign(module, node)
|
||||
@ -162,35 +155,36 @@ class OurVisitor:
|
||||
|
||||
return function
|
||||
|
||||
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> TypeStruct:
|
||||
struct = TypeStruct(node.name, node.lineno)
|
||||
|
||||
_not_implemented(not node.bases, 'ClassDef.bases')
|
||||
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
||||
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
||||
|
||||
offset = 0
|
||||
|
||||
for stmt in node.body:
|
||||
if not isinstance(stmt, ast.AnnAssign):
|
||||
raise NotImplementedError(f'Class with {stmt} nodes')
|
||||
|
||||
if not isinstance(stmt.target, ast.Name):
|
||||
raise NotImplementedError('Class with default values')
|
||||
|
||||
if not stmt.value is None:
|
||||
raise NotImplementedError('Class with default values')
|
||||
|
||||
if stmt.simple != 1:
|
||||
raise NotImplementedError('Class with non-simple arguments')
|
||||
|
||||
raise NotImplementedError('TODO: Broken after new type system')
|
||||
member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
|
||||
|
||||
struct.members.append(member)
|
||||
offset += member.type.alloc_size()
|
||||
|
||||
return struct
|
||||
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> None: # TypeStruct:
|
||||
raise NotImplementedError
|
||||
# struct = TypeStruct(node.name, node.lineno)
|
||||
#
|
||||
# _not_implemented(not node.bases, 'ClassDef.bases')
|
||||
# _not_implemented(not node.keywords, 'ClassDef.keywords')
|
||||
# _not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
||||
#
|
||||
# offset = 0
|
||||
#
|
||||
# for stmt in node.body:
|
||||
# if not isinstance(stmt, ast.AnnAssign):
|
||||
# raise NotImplementedError(f'Class with {stmt} nodes')
|
||||
#
|
||||
# if not isinstance(stmt.target, ast.Name):
|
||||
# raise NotImplementedError('Class with default values')
|
||||
#
|
||||
# if not stmt.value is None:
|
||||
# raise NotImplementedError('Class with default values')
|
||||
#
|
||||
# if stmt.simple != 1:
|
||||
# raise NotImplementedError('Class with non-simple arguments')
|
||||
#
|
||||
# raise NotImplementedError('TODO: Broken after new type system')
|
||||
# member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
|
||||
#
|
||||
# struct.members.append(member)
|
||||
# offset += member.type.alloc_size()
|
||||
#
|
||||
# return struct
|
||||
|
||||
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
|
||||
if not isinstance(node.target, ast.Name):
|
||||
|
||||
@ -6,6 +6,8 @@ constraint generator works with.
|
||||
"""
|
||||
from typing import Any, Dict, Iterable, List, Protocol, Union
|
||||
|
||||
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
|
||||
|
||||
class ExpressionProtocol(Protocol):
|
||||
"""
|
||||
A protocol for classes that should be updated on substitution
|
||||
|
||||
193
phasm/typer.py
193
phasm/typer.py
@ -1,193 +0,0 @@
|
||||
"""
|
||||
Type checks and enriches the given ast
|
||||
"""
|
||||
from . import ourlang
|
||||
|
||||
from .exceptions import TypingError
|
||||
from .typing import (
|
||||
Context,
|
||||
PhasmTypeInteger, PhasmTypeReal,
|
||||
TypeConstraintBitWidth, TypeConstraintSigned, TypeConstraintSubscript,
|
||||
TypeVar,
|
||||
from_str,
|
||||
)
|
||||
|
||||
def phasm_type(inp: ourlang.Module) -> None:
|
||||
module(inp)
|
||||
|
||||
def constant(ctx: Context, inp: ourlang.Constant) -> TypeVar:
|
||||
if isinstance(inp, ourlang.ConstantPrimitive):
|
||||
result = ctx.new_var()
|
||||
|
||||
if isinstance(inp.value, int):
|
||||
result.set_type(PhasmTypeInteger)
|
||||
|
||||
# Need at least this many bits to store this constant value
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=len(bin(inp.value)) - 2))
|
||||
# Don't dictate anything about signedness - you can use a signed
|
||||
# constant in an unsigned variable if the bits fit
|
||||
result.add_constraint(TypeConstraintSigned(None))
|
||||
|
||||
result.add_location(str(inp.value))
|
||||
|
||||
inp.type_var = result
|
||||
|
||||
return result
|
||||
|
||||
if isinstance(inp.value, float):
|
||||
result.set_type(PhasmTypeReal)
|
||||
|
||||
# We don't have fancy logic here to detect if the float constant
|
||||
# fits in the given type. There a number of edge cases to consider,
|
||||
# before implementing this.
|
||||
|
||||
# 1) It may fit anyhow
|
||||
# e.g., if the user has 3.14 as a float constant, neither a
|
||||
# f32 nor a f64 can really fit this value. But does that mean
|
||||
# we should throw an error?
|
||||
|
||||
# If we'd implement it, we'd want to convert it to hex using
|
||||
# inp.value.hex(), which would give us the mantissa and exponent.
|
||||
# We can use those to determine what bit size the value should be in.
|
||||
|
||||
# If that doesn't work out, we'd need another way to calculate the
|
||||
# difference between what was written and what actually gets stored
|
||||
# in memory, and warn if the difference is beyond a treshold.
|
||||
|
||||
result.add_location(str(inp.value))
|
||||
|
||||
inp.type_var = result
|
||||
|
||||
return result
|
||||
|
||||
raise NotImplementedError(constant, inp, inp.value)
|
||||
|
||||
if isinstance(inp, ourlang.ConstantTuple):
|
||||
result = ctx.new_var()
|
||||
|
||||
result.add_constraint(TypeConstraintSubscript(members=(
|
||||
constant(ctx, x)
|
||||
for x in inp.value
|
||||
)))
|
||||
result.add_location(str(inp.value))
|
||||
|
||||
inp.type_var = result
|
||||
|
||||
return result
|
||||
|
||||
raise NotImplementedError(constant, inp)
|
||||
|
||||
def expression(ctx: Context, inp: ourlang.Expression) -> 'TypeVar':
|
||||
if isinstance(inp, ourlang.Constant):
|
||||
return constant(ctx, inp)
|
||||
|
||||
if isinstance(inp, ourlang.VariableReference):
|
||||
assert inp.variable.type_var is not None
|
||||
|
||||
inp.type_var = inp.variable.type_var
|
||||
return inp.variable.type_var
|
||||
|
||||
if isinstance(inp, ourlang.UnaryOp):
|
||||
# TODO: Simplified version
|
||||
if inp.operator not in ('sqrt', ):
|
||||
raise NotImplementedError(expression, inp, inp.operator)
|
||||
|
||||
right = expression(ctx, inp.right)
|
||||
|
||||
inp.type_var = right
|
||||
|
||||
return right
|
||||
|
||||
if isinstance(inp, ourlang.BinaryOp):
|
||||
if inp.operator in ('+', '-', '*', '/', '|', '&', '^'):
|
||||
left = expression(ctx, inp.left)
|
||||
right = expression(ctx, inp.right)
|
||||
ctx.unify(left, right)
|
||||
|
||||
inp.type_var = left
|
||||
return left
|
||||
|
||||
if inp.operator in ('<<', '>>', ):
|
||||
inp.type_var = ctx.new_var(PhasmTypeInteger)
|
||||
inp.type_var.add_constraint(TypeConstraintBitWidth(oneof=(32, 64, )))
|
||||
inp.type_var.add_constraint(TypeConstraintSigned(False))
|
||||
|
||||
left = expression(ctx, inp.left)
|
||||
right = expression(ctx, inp.right)
|
||||
ctx.unify(left, right)
|
||||
|
||||
ctx.unify(inp.type_var, left)
|
||||
|
||||
return left
|
||||
|
||||
raise NotImplementedError(expression, inp, inp.operator)
|
||||
|
||||
if isinstance(inp, ourlang.FunctionCall):
|
||||
assert inp.function.returns_type_var is not None
|
||||
|
||||
for param, expr in zip(inp.function.posonlyargs, inp.arguments):
|
||||
assert param.type_var is not None
|
||||
|
||||
arg = expression(ctx, expr)
|
||||
ctx.unify(param.type_var, arg)
|
||||
|
||||
return inp.function.returns_type_var
|
||||
|
||||
if isinstance(inp, ourlang.Subscript):
|
||||
if not isinstance(inp.index, ourlang.ConstantPrimitive):
|
||||
raise NotImplementedError(expression, inp, inp.index)
|
||||
if not isinstance(inp.index.value, int):
|
||||
raise NotImplementedError(expression, inp, inp.index.value)
|
||||
|
||||
expression(ctx, inp.varref)
|
||||
assert inp.varref.type_var is not None
|
||||
|
||||
# TODO: I'd much rather resolve this using the narrow functions
|
||||
tc_subs = inp.varref.type_var.get_constraint(TypeConstraintSubscript)
|
||||
if tc_subs is None:
|
||||
raise TypingError(f'Type cannot be subscripted: {inp.varref.type_var}') from None
|
||||
|
||||
try:
|
||||
# TODO: I'd much rather resolve this using the narrow functions
|
||||
member = tc_subs.members[inp.index.value]
|
||||
except IndexError:
|
||||
raise TypingError(f'Type cannot be subscripted with index {inp.index.value}: {inp.varref.type_var}') from None
|
||||
|
||||
inp.type_var = member
|
||||
return member
|
||||
|
||||
raise NotImplementedError(expression, inp)
|
||||
|
||||
def function(ctx: Context, inp: ourlang.Function) -> None:
|
||||
if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn):
|
||||
raise NotImplementedError('Functions with not just a return statement')
|
||||
typ = expression(ctx, inp.statements[0].value)
|
||||
|
||||
assert inp.returns_type_var is not None
|
||||
ctx.unify(inp.returns_type_var, typ)
|
||||
|
||||
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> None:
|
||||
constant(ctx, inp.constant)
|
||||
|
||||
if inp.type_str is None:
|
||||
inp.type_var = ctx.new_var()
|
||||
else:
|
||||
inp.type_var = from_str(ctx, inp.type_str)
|
||||
|
||||
assert inp.constant.type_var is not None
|
||||
# This doesn't work sufficiently with StaticArray
|
||||
ctx.unify(inp.type_var, inp.constant.type_var)
|
||||
|
||||
def module(inp: ourlang.Module) -> None:
|
||||
ctx = Context()
|
||||
|
||||
for func in inp.functions.values():
|
||||
func.returns_type_var = from_str(ctx, func.returns_str, f'{func.name}.(returns)')
|
||||
for param in func.posonlyargs:
|
||||
param.type_var = from_str(ctx, param.type_str, f'{func.name}.{param.name}')
|
||||
|
||||
for cdef in inp.constant_defs.values():
|
||||
module_constant_def(ctx, cdef)
|
||||
|
||||
for func in inp.functions.values():
|
||||
function(ctx, func)
|
||||
653
phasm/typing.py
653
phasm/typing.py
@ -1,653 +0,0 @@
|
||||
"""
|
||||
The phasm type system
|
||||
"""
|
||||
from typing import Any, Callable, Dict, Iterable, Optional, List, Set, Type, Union
|
||||
from typing import TypeVar as MyPyTypeVar
|
||||
|
||||
import enum
|
||||
import re
|
||||
|
||||
from .exceptions import TypingError
|
||||
|
||||
class TypeBase:
|
||||
"""
|
||||
TypeBase base class
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def alloc_size(self) -> int:
|
||||
"""
|
||||
When allocating this type in memory, how many bytes do we need to reserve?
|
||||
"""
|
||||
raise NotImplementedError(self, 'alloc_size')
|
||||
|
||||
class TypeBytes(TypeBase):
|
||||
"""
|
||||
The bytes type
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
class TypeTupleMember:
|
||||
"""
|
||||
Represents a tuple member
|
||||
"""
|
||||
def __init__(self, idx: int, type_: TypeBase, offset: int) -> None:
|
||||
self.idx = idx
|
||||
self.type = type_
|
||||
self.offset = offset
|
||||
|
||||
class TypeTuple(TypeBase):
|
||||
"""
|
||||
The tuple type
|
||||
"""
|
||||
__slots__ = ('members', )
|
||||
|
||||
members: List[TypeTupleMember]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.members = []
|
||||
|
||||
def render_internal_name(self) -> str:
|
||||
"""
|
||||
Generates an internal name for this tuple
|
||||
"""
|
||||
mems = '@'.join('?' for x in self.members) # FIXME: Should not be a questionmark
|
||||
assert ' ' not in mems, 'Not implement yet: subtuples'
|
||||
return f'tuple@{mems}'
|
||||
|
||||
def alloc_size(self) -> int:
|
||||
return sum(
|
||||
x.type.alloc_size()
|
||||
for x in self.members
|
||||
)
|
||||
|
||||
class TypeStaticArrayMember:
|
||||
"""
|
||||
Represents a static array member
|
||||
"""
|
||||
def __init__(self, idx: int, offset: int) -> None:
|
||||
self.idx = idx
|
||||
self.offset = offset
|
||||
|
||||
class TypeStaticArray(TypeBase):
|
||||
"""
|
||||
The static array type
|
||||
"""
|
||||
__slots__ = ('member_type', 'members', )
|
||||
|
||||
member_type: TypeBase
|
||||
members: List[TypeStaticArrayMember]
|
||||
|
||||
def __init__(self, member_type: TypeBase) -> None:
|
||||
self.member_type = member_type
|
||||
self.members = []
|
||||
|
||||
def alloc_size(self) -> int:
|
||||
return self.member_type.alloc_size() * len(self.members)
|
||||
|
||||
class TypeStructMember:
|
||||
"""
|
||||
Represents a struct member
|
||||
"""
|
||||
def __init__(self, name: str, type_: TypeBase, offset: int) -> None:
|
||||
self.name = name
|
||||
self.type = type_
|
||||
self.offset = offset
|
||||
|
||||
class TypeStruct(TypeBase):
|
||||
"""
|
||||
A struct has named properties
|
||||
"""
|
||||
__slots__ = ('name', 'lineno', 'members', )
|
||||
|
||||
name: str
|
||||
lineno: int
|
||||
members: List[TypeStructMember]
|
||||
|
||||
def __init__(self, name: str, lineno: int) -> None:
|
||||
self.name = name
|
||||
self.lineno = lineno
|
||||
self.members = []
|
||||
|
||||
def get_member(self, name: str) -> Optional[TypeStructMember]:
|
||||
"""
|
||||
Returns a member by name
|
||||
"""
|
||||
for mem in self.members:
|
||||
if mem.name == name:
|
||||
return mem
|
||||
|
||||
return None
|
||||
|
||||
def alloc_size(self) -> int:
|
||||
return sum(
|
||||
x.type.alloc_size()
|
||||
for x in self.members
|
||||
)
|
||||
|
||||
## NEW STUFF BELOW
|
||||
|
||||
# This error can also mean that the typer somewhere forgot to write a type
|
||||
# back to the AST. If so, we need to fix the typer.
|
||||
ASSERTION_ERROR = 'You must call phasm_type after calling phasm_parse before you can call any other method'
|
||||
|
||||
class PhasmType:
|
||||
__slots__ = ('name', 'args', 'arg_count', )
|
||||
|
||||
name: str
|
||||
args: List[Union['PhasmType', 'TypeVar']]
|
||||
arg_count: int
|
||||
|
||||
def __init__(self, name: str, arg_count: int = 0) -> None:
|
||||
self.name = name
|
||||
self.args = []
|
||||
self.arg_count = arg_count
|
||||
|
||||
def __call__(self, type_arg: Union['PhasmType', 'TypeVar']) -> 'PhasmType':
|
||||
assert 0 < self.arg_count
|
||||
|
||||
result = PhasmType(self.name, self.arg_count - 1)
|
||||
result.args = self.args + [type_arg]
|
||||
|
||||
return result
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, PhasmType):
|
||||
raise NotImplementedError
|
||||
|
||||
return (
|
||||
self.name == other.name
|
||||
and self.args == other.args
|
||||
and self.arg_count == other.arg_count
|
||||
)
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
if not isinstance(other, PhasmType):
|
||||
raise NotImplementedError
|
||||
|
||||
return (
|
||||
self.name != other.name
|
||||
or self.args != other.args
|
||||
or self.arg_count != other.arg_count
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
'PhasmType' + self.name + ' '
|
||||
+ ' '.join(map(repr, self.args)) + ' '
|
||||
+ ' '.join(['?'] * self.arg_count)
|
||||
).strip()
|
||||
|
||||
PhasmTypeInteger = PhasmType('Integer')
|
||||
PhasmTypeReal = PhasmType('Real')
|
||||
PhasmTypeStaticArray = PhasmType('StaticArray', 1)
|
||||
|
||||
class TypingNarrowProtoError(TypingError):
|
||||
"""
|
||||
A proto error when trying to narrow two types
|
||||
|
||||
This gets turned into a TypingNarrowError by the unify method
|
||||
"""
|
||||
# FIXME: Use consistent naming for unify / narrow / entangle
|
||||
|
||||
class TypingNarrowError(TypingError):
|
||||
"""
|
||||
An error when trying to unify two Type Variables
|
||||
"""
|
||||
def __init__(self, l: 'TypeVar', r: 'TypeVar', msg: str) -> None:
|
||||
super().__init__(
|
||||
f'Cannot narrow types {l} and {r}: {msg}'
|
||||
)
|
||||
|
||||
class TypeConstraintBase:
|
||||
"""
|
||||
Base class for classes implementing a constraint on a type
|
||||
"""
|
||||
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintBase':
|
||||
raise NotImplementedError('narrow', self, other)
|
||||
|
||||
class TypeConstraintSigned(TypeConstraintBase):
|
||||
"""
|
||||
Constraint on whether a signed value can be used or not, or whether
|
||||
a value can be used in a signed expression
|
||||
"""
|
||||
__slots__ = ('signed', )
|
||||
|
||||
signed: Optional[bool]
|
||||
|
||||
def __init__(self, signed: Optional[bool]) -> None:
|
||||
self.signed = signed
|
||||
|
||||
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintSigned':
|
||||
if not isinstance(other, TypeConstraintSigned):
|
||||
raise Exception('Invalid comparison')
|
||||
|
||||
if other.signed is None:
|
||||
return TypeConstraintSigned(self.signed)
|
||||
if self.signed is None:
|
||||
return TypeConstraintSigned(other.signed)
|
||||
|
||||
if self.signed is not other.signed:
|
||||
raise TypingNarrowProtoError('Signed does not match')
|
||||
|
||||
return TypeConstraintSigned(self.signed)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Signed={self.signed}'
|
||||
|
||||
class TypeConstraintBitWidth(TypeConstraintBase):
|
||||
"""
|
||||
Constraint on how many bits an expression has or can possibly have
|
||||
"""
|
||||
__slots__ = ('oneof', )
|
||||
|
||||
oneof: Set[int]
|
||||
|
||||
def __init__(self, *, oneof: Optional[Iterable[int]] = None, minb: Optional[int] = None, maxb: Optional[int] = None) -> None:
|
||||
# For now, support up to 64 bits values
|
||||
self.oneof = set(oneof) if oneof is not None else set(range(1, 65))
|
||||
|
||||
if minb is not None:
|
||||
self.oneof = {
|
||||
x
|
||||
for x in self.oneof
|
||||
if minb <= x
|
||||
}
|
||||
|
||||
if maxb is not None:
|
||||
self.oneof = {
|
||||
x
|
||||
for x in self.oneof
|
||||
if x <= maxb
|
||||
}
|
||||
|
||||
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintBitWidth':
|
||||
if not isinstance(other, TypeConstraintBitWidth):
|
||||
raise Exception('Invalid comparison')
|
||||
|
||||
new_oneof = self.oneof & other.oneof
|
||||
|
||||
if not new_oneof:
|
||||
raise TypingNarrowProtoError('Memory width cannot be resolved')
|
||||
|
||||
return TypeConstraintBitWidth(oneof=new_oneof)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
result = 'BitWidth='
|
||||
|
||||
items = list(sorted(self.oneof))
|
||||
if not items:
|
||||
return result
|
||||
|
||||
while items:
|
||||
itm = items.pop(0)
|
||||
result += str(itm)
|
||||
|
||||
cnt = 0
|
||||
while cnt < len(items) and items[cnt] == itm + cnt + 1:
|
||||
cnt += 1
|
||||
|
||||
if cnt == 1:
|
||||
result += ',' + str(items[0])
|
||||
elif cnt > 1:
|
||||
result += '..' + str(items[cnt - 1])
|
||||
|
||||
items = items[cnt:]
|
||||
if items:
|
||||
result += ','
|
||||
|
||||
return result
|
||||
|
||||
class TypeConstraintSubscript(TypeConstraintBase):
|
||||
"""
|
||||
Constraint on allowing a type to be subscripted
|
||||
"""
|
||||
__slots__ = ('members', )
|
||||
|
||||
members: List['TypeVar']
|
||||
|
||||
def __init__(self, *, members: Iterable['TypeVar']) -> None:
|
||||
self.members = list(members)
|
||||
|
||||
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintSubscript':
|
||||
if not isinstance(other, TypeConstraintSubscript):
|
||||
raise Exception('Invalid comparison')
|
||||
|
||||
if len(self.members) != len(other.members):
|
||||
raise TypingNarrowProtoError('Member count does not match')
|
||||
|
||||
newmembers = []
|
||||
for smb, omb in zip(self.members, other.members):
|
||||
nmb = ctx.new_var()
|
||||
ctx.unify(nmb, smb)
|
||||
ctx.unify(nmb, omb)
|
||||
newmembers.append(nmb)
|
||||
|
||||
return TypeConstraintSubscript(members=newmembers)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return 'Subscript=(' + ','.join(map(repr, self.members)) + ')'
|
||||
|
||||
TTypeConstraintClass = MyPyTypeVar('TTypeConstraintClass', bound=TypeConstraintBase)
|
||||
|
||||
class TypeVar:
|
||||
"""
|
||||
A type variable
|
||||
"""
|
||||
# FIXME: Explain the type system
|
||||
__slots__ = ('ctx', 'ctx_id', )
|
||||
|
||||
ctx: 'Context'
|
||||
ctx_id: int
|
||||
|
||||
def __init__(self, ctx: 'Context', ctx_id: int) -> None:
|
||||
self.ctx = ctx
|
||||
self.ctx_id = ctx_id
|
||||
|
||||
def get_type(self) -> Optional[PhasmType]:
|
||||
return self.ctx.var_types[self.ctx_id]
|
||||
|
||||
def set_type(self, type_: PhasmType) -> None:
|
||||
assert self.ctx.var_types[self.ctx_id] is None, 'Type already set'
|
||||
self.ctx.var_types[self.ctx_id] = type_
|
||||
|
||||
def add_constraint(self, newconst: TypeConstraintBase) -> None:
|
||||
csts = self.ctx.var_constraints[self.ctx_id]
|
||||
|
||||
if newconst.__class__ in csts:
|
||||
csts[newconst.__class__] = csts[newconst.__class__].narrow(self.ctx, newconst)
|
||||
else:
|
||||
csts[newconst.__class__] = newconst
|
||||
|
||||
def get_constraint(self, const_type: Type[TTypeConstraintClass]) -> Optional[TTypeConstraintClass]:
|
||||
csts = self.ctx.var_constraints[self.ctx_id]
|
||||
|
||||
res = csts.get(const_type, None)
|
||||
assert res is None or isinstance(res, const_type) # type hint
|
||||
return res
|
||||
|
||||
def add_location(self, ref: str) -> None:
|
||||
self.ctx.var_locations[self.ctx_id].add(ref)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self) -> str:
|
||||
typ = self.ctx.var_types[self.ctx_id]
|
||||
|
||||
return (
|
||||
'TypeVar<'
|
||||
+ ('?' if typ is None else repr(typ))
|
||||
+ '; '
|
||||
+ '; '.join(map(repr, self.ctx.var_constraints[self.ctx_id].values()))
|
||||
+ '; locations: '
|
||||
+ ', '.join(sorted(self.ctx.var_locations[self.ctx_id]))
|
||||
+ '>'
|
||||
)
|
||||
|
||||
class Context:
|
||||
"""
|
||||
The context for a collection of type variables
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
# Variables are unified (or entangled, if you will)
|
||||
# that means that each TypeVar within a context has an ID,
|
||||
# and all TypeVars with the same ID are the same TypeVar,
|
||||
# even if they are a different instance
|
||||
self.next_ctx_id = 1
|
||||
self.vars_by_id: Dict[int, List[TypeVar]] = {}
|
||||
|
||||
# Store the TypeVar properties as a lookup
|
||||
# so we can update these when unifying
|
||||
self.var_types: Dict[int, Optional[PhasmType]] = {}
|
||||
self.var_constraints: Dict[int, Dict[Type[TypeConstraintBase], TypeConstraintBase]] = {}
|
||||
self.var_locations: Dict[int, Set[str]] = {}
|
||||
|
||||
def new_var(self, type_: Optional[PhasmType] = None) -> TypeVar:
|
||||
ctx_id = self.next_ctx_id
|
||||
self.next_ctx_id += 1
|
||||
|
||||
result = TypeVar(self, ctx_id)
|
||||
|
||||
self.vars_by_id[ctx_id] = [result]
|
||||
self.var_types[ctx_id] = type_
|
||||
self.var_constraints[ctx_id] = {}
|
||||
self.var_locations[ctx_id] = set()
|
||||
|
||||
return result
|
||||
|
||||
def unify(self, l: Optional[TypeVar], r: Optional[TypeVar]) -> None:
|
||||
# FIXME: Write method doc, find out why pylint doesn't error
|
||||
|
||||
assert l is not None, ASSERTION_ERROR
|
||||
assert r is not None, ASSERTION_ERROR
|
||||
|
||||
assert l.ctx_id != r.ctx_id # Dunno if this'll happen, if so, just return
|
||||
|
||||
# Backup some values that we'll overwrite
|
||||
l_ctx_id = l.ctx_id
|
||||
r_ctx_id = r.ctx_id
|
||||
l_type = self.var_types[l_ctx_id]
|
||||
r_type = self.var_types[r_ctx_id]
|
||||
l_r_var_list = self.vars_by_id[l_ctx_id] + self.vars_by_id[r_ctx_id]
|
||||
|
||||
# Create a new TypeVar, with the combined constraints
|
||||
# and locations of the old ones
|
||||
n = self.new_var()
|
||||
|
||||
if l_type is not None and r_type is not None and l_type != r_type:
|
||||
raise TypingNarrowError(l, r, 'Type does not match')
|
||||
self.var_types[n.ctx_id] = l_type
|
||||
|
||||
try:
|
||||
for const in self.var_constraints[l_ctx_id].values():
|
||||
n.add_constraint(const)
|
||||
for const in self.var_constraints[r_ctx_id].values():
|
||||
n.add_constraint(const)
|
||||
except TypingNarrowProtoError as exc:
|
||||
raise TypingNarrowError(l, r, str(exc)) from None
|
||||
|
||||
self.var_locations[n.ctx_id] = self.var_locations[l_ctx_id] | self.var_locations[r_ctx_id]
|
||||
|
||||
# ##
|
||||
# And unify (or entangle) the old ones
|
||||
|
||||
# First update the IDs, so they all point to the new list
|
||||
for type_var in l_r_var_list:
|
||||
type_var.ctx_id = n.ctx_id
|
||||
|
||||
# Update our registry of TypeVars by ID, so we can find them
|
||||
# on the next unify
|
||||
self.vars_by_id[n.ctx_id].extend(l_r_var_list)
|
||||
|
||||
# Then delete the old values for the now gone variables
|
||||
# Do this last, so exceptions thrown in the code above
|
||||
# still have a valid context
|
||||
del self.var_constraints[l_ctx_id]
|
||||
del self.var_constraints[r_ctx_id]
|
||||
del self.var_locations[l_ctx_id]
|
||||
del self.var_locations[r_ctx_id]
|
||||
|
||||
def simplify(inp: TypeVar) -> Optional[str]:
|
||||
"""
|
||||
Simplifies a TypeVar into a string that wasm can work with
|
||||
and users can recognize
|
||||
|
||||
Should round trip with from_str
|
||||
"""
|
||||
tc_bits = inp.get_constraint(TypeConstraintBitWidth)
|
||||
tc_sign = inp.get_constraint(TypeConstraintSigned)
|
||||
|
||||
if inp.get_type() is None:
|
||||
return None
|
||||
|
||||
if inp.get_type() is PhasmTypeInteger:
|
||||
if tc_bits is None or tc_sign is None:
|
||||
return None
|
||||
|
||||
if tc_sign.signed is None or len(tc_bits.oneof) != 1:
|
||||
return None
|
||||
|
||||
bitwidth = next(iter(tc_bits.oneof))
|
||||
if bitwidth not in (8, 32, 64):
|
||||
return None
|
||||
|
||||
base = 'i' if tc_sign.signed else 'u'
|
||||
return f'{base}{bitwidth}'
|
||||
|
||||
if inp.get_type() is PhasmTypeReal:
|
||||
if tc_bits is None or tc_sign is not None: # Floats should not hava sign constraint
|
||||
return None
|
||||
|
||||
if len(tc_bits.oneof) != 1:
|
||||
return None
|
||||
|
||||
bitwidth = next(iter(tc_bits.oneof))
|
||||
if bitwidth not in (32, 64):
|
||||
return None
|
||||
|
||||
return f'f{bitwidth}'
|
||||
|
||||
# if primitive is TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
|
||||
# tc_subs = inp.get_constraint(TypeConstraintSubscript)
|
||||
# assert tc_subs is not None
|
||||
# assert tc_subs.members
|
||||
#
|
||||
# sab = simplify(tc_subs.members[0])
|
||||
# if sab is None:
|
||||
# return None
|
||||
#
|
||||
# return f'{sab}[{len(tc_subs.members)}]'
|
||||
|
||||
return None
|
||||
|
||||
def make_u8(ctx: Context) -> TypeVar:
|
||||
"""
|
||||
Makes a u8 TypeVar
|
||||
"""
|
||||
result = ctx.new_var(PhasmTypeInteger)
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=8, maxb=8))
|
||||
result.add_constraint(TypeConstraintSigned(False))
|
||||
result.add_location('u8')
|
||||
return result
|
||||
|
||||
def make_u32(ctx: Context) -> TypeVar:
|
||||
"""
|
||||
Makes a u32 TypeVar
|
||||
"""
|
||||
result = ctx.new_var(PhasmTypeInteger)
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
|
||||
result.add_constraint(TypeConstraintSigned(False))
|
||||
result.add_location('u32')
|
||||
return result
|
||||
|
||||
def make_u64(ctx: Context) -> TypeVar:
|
||||
"""
|
||||
Makes a u64 TypeVar
|
||||
"""
|
||||
result = ctx.new_var(PhasmTypeInteger)
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
|
||||
result.add_constraint(TypeConstraintSigned(False))
|
||||
result.add_location('u64')
|
||||
return result
|
||||
|
||||
def make_i32(ctx: Context) -> TypeVar:
|
||||
"""
|
||||
Makes a i32 TypeVar
|
||||
"""
|
||||
result = ctx.new_var(PhasmTypeInteger)
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
|
||||
result.add_constraint(TypeConstraintSigned(True))
|
||||
result.add_location('i32')
|
||||
return result
|
||||
|
||||
def make_i64(ctx: Context) -> TypeVar:
|
||||
"""
|
||||
Makes a i64 TypeVar
|
||||
"""
|
||||
result = ctx.new_var(PhasmTypeInteger)
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
|
||||
result.add_constraint(TypeConstraintSigned(True))
|
||||
result.add_location('i64')
|
||||
return result
|
||||
|
||||
def make_f32(ctx: Context) -> TypeVar:
|
||||
"""
|
||||
Makes a f32 TypeVar
|
||||
"""
|
||||
result = ctx.new_var(PhasmTypeReal)
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
|
||||
result.add_location('f32')
|
||||
return result
|
||||
|
||||
def make_f64(ctx: Context) -> TypeVar:
|
||||
"""
|
||||
Makes a f64 TypeVar
|
||||
"""
|
||||
result = ctx.new_var(PhasmTypeReal)
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
|
||||
result.add_location('f64')
|
||||
return result
|
||||
|
||||
BUILTIN_TYPES: Dict[str, Callable[[Context], TypeVar]] = {
|
||||
'u8': make_u8,
|
||||
'u32': make_u32,
|
||||
'u64': make_u64,
|
||||
'i32': make_i32,
|
||||
'i64': make_i64,
|
||||
'f32': make_f32,
|
||||
'f64': make_f64,
|
||||
}
|
||||
|
||||
TYPE_MATCH_STATIC_ARRAY = re.compile(r'^([uif][0-9]+)\[([0-9]+)\]')
|
||||
|
||||
def from_str(ctx: Context, inp: str, location: Optional[str] = None) -> TypeVar:
|
||||
"""
|
||||
Creates a new TypeVar from the string
|
||||
|
||||
Should round trip with simplify
|
||||
|
||||
The location is a reference to where you found the string
|
||||
in the source code.
|
||||
|
||||
This could be conidered part of parsing. Though that would give trouble
|
||||
with the context creation.
|
||||
"""
|
||||
if inp in BUILTIN_TYPES:
|
||||
result = BUILTIN_TYPES[inp](ctx)
|
||||
if location is not None:
|
||||
result.add_location(location)
|
||||
return result
|
||||
|
||||
match = TYPE_MATCH_STATIC_ARRAY.fullmatch(inp)
|
||||
if match:
|
||||
result = ctx.new_var(PhasmTypeStaticArray)
|
||||
|
||||
result.add_constraint(TypeConstraintSubscript(members=(
|
||||
# Make copies so they don't get entangled
|
||||
# with each other.
|
||||
from_str(ctx, match[1], match[1])
|
||||
for _ in range(int(match[2]))
|
||||
)))
|
||||
|
||||
result.add_location(inp)
|
||||
|
||||
if location is not None:
|
||||
result.add_location(location)
|
||||
|
||||
return result
|
||||
|
||||
if inp.startswith('static_array ('):
|
||||
# Temp workaround while both type2 and type3 are running simultaneous
|
||||
result = ctx.new_var(PhasmTypeStaticArray)
|
||||
result.add_location(inp)
|
||||
|
||||
if location is not None:
|
||||
result.add_location(location)
|
||||
|
||||
return result
|
||||
|
||||
raise NotImplementedError(from_str, inp)
|
||||
@ -13,7 +13,6 @@ import wasmtime
|
||||
|
||||
from phasm.compiler import phasm_compile
|
||||
from phasm.parser import phasm_parse
|
||||
from phasm.typer import phasm_type
|
||||
from phasm.type3.entry import phasm_type3
|
||||
from phasm import ourlang
|
||||
from phasm import wasm
|
||||
@ -43,7 +42,6 @@ class RunnerBase:
|
||||
"""
|
||||
self.phasm_ast = phasm_parse(self.phasm_code)
|
||||
phasm_type3(self.phasm_ast)
|
||||
phasm_type(self.phasm_ast)
|
||||
|
||||
def compile_ast(self) -> None:
|
||||
"""
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from phasm import typing as sut
|
||||
|
||||
class TestTypeConstraintBitWidth:
|
||||
@pytest.mark.parametrize('oneof,exp', [
|
||||
(set(), '', ),
|
||||
({1}, '1', ),
|
||||
({1,2}, '1,2', ),
|
||||
({1,2,3}, '1..3', ),
|
||||
({1,2,3,4}, '1..4', ),
|
||||
|
||||
({1,3}, '1,3', ),
|
||||
({1,4}, '1,4', ),
|
||||
|
||||
({1,2,3,4,6,7,8,9}, '1..4,6..9', ),
|
||||
])
|
||||
def test_repr(self, oneof, exp):
|
||||
mut_self = sut.TypeConstraintBitWidth(oneof=oneof)
|
||||
assert ('BitWidth=' + exp) == repr(mut_self)
|
||||
@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from phasm.exceptions import TypingError
|
||||
from phasm.type3.entry import Type3Exception
|
||||
|
||||
from ..helpers import Suite
|
||||
from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, COMPLETE_NUMERIC_TYPES, TYPE_MAP
|
||||
@ -34,14 +35,14 @@ def testEntry() -> {type_}:
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_expr_constant_entanglement():
|
||||
def test_expr_constant_literal_does_not_fit():
|
||||
code_py = """
|
||||
@exported
|
||||
def testEntry() -> u8:
|
||||
return 1000
|
||||
"""
|
||||
|
||||
with pytest.raises(TypingError, match='u8.*1000'):
|
||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@ -354,12 +355,12 @@ def testEntry() -> i32:
|
||||
@pytest.mark.integration_test
|
||||
def test_call_pre_defined():
|
||||
code_py = """
|
||||
def helper(left: i32, right: i32) -> i32:
|
||||
return left + right
|
||||
def helper(left: i32) -> i32:
|
||||
return left
|
||||
|
||||
@exported
|
||||
def testEntry() -> i32:
|
||||
return helper(10, 3)
|
||||
return helper(13)
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
@ -416,7 +417,7 @@ def helper(left: {type_}, right: {type_}) -> {type_}:
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_call_invalid_type():
|
||||
def test_call_invalid_return_type():
|
||||
code_py = """
|
||||
def helper() -> i64:
|
||||
return 19
|
||||
@ -426,5 +427,19 @@ def testEntry() -> i32:
|
||||
return helper()
|
||||
"""
|
||||
|
||||
with pytest.raises(TypingError, match=r'i32.*i64'):
|
||||
with pytest.raises(Type3Exception, match=r'i32.*i64'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@pytest.mark.integration_test
|
||||
def test_call_invalid_arg_type():
|
||||
code_py = """
|
||||
def helper(left: u8) -> u8:
|
||||
return left
|
||||
|
||||
@exported
|
||||
def testEntry() -> u8:
|
||||
return helper(500)
|
||||
"""
|
||||
|
||||
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
|
||||
Suite(code_py).run_code()
|
||||
|
||||
@ -15,7 +15,7 @@ CONSTANT: {type_}[3] = (24, 57, 80, )
|
||||
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
return CONSTANT[0]
|
||||
return CONSTANT[1]
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user