Ripping out Type2 (type_var) system

This commit is contained in:
Johan B.W. de Vries 2022-11-24 14:49:17 +01:00
parent 8cc47ae63e
commit b5a28daebf
12 changed files with 141 additions and 1036 deletions

View File

@ -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 typing import Generator, Optional
from . import ourlang from . import ourlang
from . import typing from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
def phasm_render(inp: ourlang.Module) -> str: def phasm_render(inp: ourlang.Module) -> str:
""" """
@ -15,35 +15,43 @@ def phasm_render(inp: ourlang.Module) -> str:
return module(inp) return module(inp)
Statements = Generator[str, None, None] 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 Render: type's name
""" """
assert inp is not None, typing.ASSERTION_ERROR assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR
mtyp = typing.simplify(inp) return inp.name
if mtyp is None:
raise NotImplementedError(f'Rendering type {inp}')
return mtyp # def struct_definition(inp: typing.TypeStruct) -> str:
# """
def struct_definition(inp: typing.TypeStruct) -> str: # Render: TypeStruct's definition
""" # """
Render: TypeStruct's definition # result = f'class {inp.name}:\n'
""" # for mem in inp.members: # TODO: Broken after new type system
result = f'class {inp.name}:\n' # raise NotImplementedError('Structs broken after new type system')
for mem in inp.members: # TODO: Broken after new type system # # result += f' {mem.name}: {type_(mem.type)}\n'
raise NotImplementedError('Structs broken after new type system') #
# result += f' {mem.name}: {type_(mem.type)}\n' # return result
return result
def constant_definition(inp: ourlang.ModuleConstantDef) -> str: def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
""" """
Render: Module Constant's definition 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: def expression(inp: ourlang.Expression) -> str:
""" """
@ -70,7 +78,7 @@ def expression(inp: ourlang.Expression) -> str:
return f'{inp.operator}({expression(inp.right)})' return f'{inp.operator}({expression(inp.right)})'
if inp.operator == 'cast': if inp.operator == 'cast':
mtyp = type_var(inp.type_var) mtyp = type3(inp.type3)
if mtyp is None: if mtyp is None:
raise NotImplementedError(f'Casting to type {inp.type_var}') raise NotImplementedError(f'Casting to type {inp.type_var}')
@ -150,11 +158,11 @@ def function(inp: ourlang.Function) -> str:
result += '@imported\n' result += '@imported\n'
args = ', '.join( args = ', '.join(
f'{p.name}: {type_var(p.type_var)}' f'{p.name}: {type3(p.type3)}'
for p in inp.posonlyargs 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: if inp.imported:
result += ' pass\n' result += ' pass\n'
@ -172,10 +180,10 @@ def module(inp: ourlang.Module) -> str:
""" """
result = '' result = ''
for struct in inp.structs.values(): # for struct in inp.structs.values():
if result: # if result:
result += '\n' # result += '\n'
result += struct_definition(struct) # result += struct_definition(struct)
for cdef in inp.constant_defs.values(): for cdef in inp.constant_defs.values():
if result: if result:

View File

@ -7,7 +7,6 @@ import struct
from . import codestyle from . import codestyle
from . import ourlang from . import ourlang
from . import typing
from .type3 import types as type3types from .type3 import types as type3types
from . import wasm from . import wasm
@ -22,45 +21,38 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module:
""" """
return module(inp) return module(inp)
def type_var(inp: Optional[typing.TypeVar]) -> wasm.WasmType: def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
""" """
Compile: type Compile: type
Types are used for example in WebAssembly function parameters Types are used for example in WebAssembly function parameters
and return types. 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 inp is type3types.u8:
if mtyp == 'u8':
# WebAssembly has only support for 32 and 64 bits # WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte # So we need to store more memory per byte
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if mtyp == 'u32': if inp is type3types.u32:
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if mtyp == 'u64': if inp is type3types.u64:
return wasm.WasmTypeInt64() return wasm.WasmTypeInt64()
if mtyp == 'i32': if inp is type3types.i32:
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if mtyp == 'i64': if inp is type3types.i64:
return wasm.WasmTypeInt64() return wasm.WasmTypeInt64()
if mtyp == 'f32': if inp is type3types.f32:
return wasm.WasmTypeFloat32() return wasm.WasmTypeFloat32()
if mtyp == 'f64': if inp is type3types.f64:
return wasm.WasmTypeFloat64() 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: # if tc_prim.primitive is typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
# # StaticArray, Tuples and Structs are passed as pointer # # StaticArray, Tuples and Structs are passed as pointer
# # And pointers are i32 # # And pointers are i32
@ -72,7 +64,7 @@ def type_var(inp: Optional[typing.TypeVar]) -> wasm.WasmType:
# # And pointers are i32 # # And pointers are i32
# return wasm.WasmTypeInt32() # return wasm.WasmTypeInt32()
raise NotImplementedError(inp, mtyp) raise NotImplementedError(type3, inp)
# Operators that work for i32, i64, f32, f64 # Operators that work for i32, i64, f32, f64
OPERATOR_MAP = { OPERATOR_MAP = {
@ -147,42 +139,35 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
Compile: Any expression Compile: Any expression
""" """
if isinstance(inp, ourlang.ConstantPrimitive): if isinstance(inp, ourlang.ConstantPrimitive):
assert inp.type3 is not None, typing.ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp.type3 is type3types.u8:
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':
# No native u8 type - treat as i32, with caution # No native u8 type - treat as i32, with caution
assert isinstance(inp.value, int) assert isinstance(inp.value, int)
wgn.i32.const(inp.value) wgn.i32.const(inp.value)
return return
if stp in ('i32', 'u32'): if inp.type3 is type3types.i32 or inp.type3 is type3types.u32:
assert isinstance(inp.value, int) assert isinstance(inp.value, int)
wgn.i32.const(inp.value) wgn.i32.const(inp.value)
return return
if stp in ('i64', 'u64'): if inp.type3 is type3types.i64 or inp.type3 is type3types.u64:
assert isinstance(inp.value, int) assert isinstance(inp.value, int)
wgn.i64.const(inp.value) wgn.i64.const(inp.value)
return return
if stp == 'f32': if inp.type3 is type3types.f32:
assert isinstance(inp.value, float) assert isinstance(inp.value, float)
wgn.f32.const(inp.value) wgn.f32.const(inp.value)
return return
if stp == 'f64': if inp.type3 is type3types.f64:
assert isinstance(inp.value, float) assert isinstance(inp.value, float)
wgn.f64.const(inp.value) wgn.f64.const(inp.value)
return return
raise NotImplementedError(f'Constants with type {stp}') raise NotImplementedError(f'Constants with type {inp.type3}')
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
if isinstance(inp.variable, ourlang.FunctionParam): if isinstance(inp.variable, ourlang.FunctionParam):
@ -190,10 +175,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
return return
if isinstance(inp.variable, ourlang.ModuleConstantDef): if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert inp.variable.type_var is not None, typing.ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
tc_type = inp.variable.type_var.get_type()
if tc_type is None:
raise NotImplementedError(expression, inp, inp.variable.type_var)
# TODO: Broken after new type system # TODO: Broken after new type system
# if isinstance(inp.type, typing.TypeTuple): # 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.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) expression(wgn, inp.variable.constant)
return return
@ -228,49 +203,49 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
expression(wgn, inp.left) expression(wgn, inp.left)
expression(wgn, inp.right) expression(wgn, inp.right)
assert inp.type_var is not None, typing.ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
mtyp = typing.simplify(inp.type_var) # FIXME: Re-implement build-in operators
if mtyp == 'u8': if inp.type3 is type3types.u8:
if operator := U8_OPERATOR_MAP.get(inp.operator, None): if operator := U8_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if mtyp == 'u32': if inp.type3 is type3types.u32:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if operator := U32_OPERATOR_MAP.get(inp.operator, None): if operator := U32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if mtyp == 'u64': if inp.type3 is type3types.u64:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if operator := U64_OPERATOR_MAP.get(inp.operator, None): if operator := U64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if mtyp == 'i32': if inp.type3 is type3types.i32:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if operator := I32_OPERATOR_MAP.get(inp.operator, None): if operator := I32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}') wgn.add_statement(f'i32.{operator}')
return return
if mtyp == 'i64': if inp.type3 is type3types.i64:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if operator := I64_OPERATOR_MAP.get(inp.operator, None): if operator := I64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}') wgn.add_statement(f'i64.{operator}')
return return
if mtyp == 'f32': if inp.type3 is type3types.f32:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}') wgn.add_statement(f'f32.{operator}')
return return
if operator := F32_OPERATOR_MAP.get(inp.operator, None): if operator := F32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}') wgn.add_statement(f'f32.{operator}')
return return
if mtyp == 'f64': if inp.type3 is type3types.f64:
if operator := OPERATOR_MAP.get(inp.operator, None): if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f64.{operator}') wgn.add_statement(f'f64.{operator}')
return return
@ -278,19 +253,18 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.add_statement(f'f64.{operator}') wgn.add_statement(f'f64.{operator}')
return return
raise NotImplementedError(expression, inp.type_var, inp.operator) raise NotImplementedError(expression, inp.type3, inp.operator)
if isinstance(inp, ourlang.UnaryOp): if isinstance(inp, ourlang.UnaryOp):
expression(wgn, inp.right) expression(wgn, inp.right)
assert inp.type_var is not None, typing.ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
mtyp = typing.simplify(inp.type_var)
if mtyp == 'f32': if inp.type3 is type3types.f32:
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS: if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
wgn.add_statement(f'f32.{inp.operator}') wgn.add_statement(f'f32.{inp.operator}')
return return
if mtyp == 'f64': if inp.type3 is type3types.f64:
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS: if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
wgn.add_statement(f'f64.{inp.operator}') wgn.add_statement(f'f64.{inp.operator}')
return 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 # # Nothing to do, you can use an u8 value as a u32 no problem
# return # return
raise NotImplementedError(expression, inp.type_var, inp.operator) raise NotImplementedError(expression, inp.type3, inp.operator)
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
for arg in inp.arguments: for arg in inp.arguments:
@ -317,12 +291,12 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
return return
if isinstance(inp, ourlang.Subscript): if isinstance(inp, ourlang.Subscript):
assert inp.varref.type3 is not None, typing.ASSERTION_ERROR # assert inp.varref.type3 is not None, typing.ASSERTION_ERROR
#
assert inp.varref.type_var 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() # tc_type = inp.varref.type_var.get_type()
if tc_type is None: # if tc_type is None:
raise NotImplementedError(expression, inp, inp.varref.type_var) # raise NotImplementedError(expression, inp, inp.varref.type_var)
# if tc_prim.primitive == typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY: # if tc_prim.primitive == typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
# if not isinstance(inp.index, ourlang.ConstantPrimitive): # 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)) # wgn.add_statement(f'{mtyp}.load', 'offset=' + str(bitwidth // 8 * inp.index.value))
# return # return
raise NotImplementedError(expression, inp, inp.varref.type_var) raise NotImplementedError(expression, inp, inp.varref.type3)
# TODO: Broken after new type system # TODO: Broken after new type system
@ -422,12 +396,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
""" """
Compile: Fold expression Compile: Fold expression
""" """
assert inp.base.type_var is not None, typing.ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_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)
raise NotImplementedError('TODO: Broken after new type system') raise NotImplementedError('TODO: Broken after new type system')
@ -546,7 +515,7 @@ def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
""" """
Compile: function argument Compile: function argument
""" """
return (inp.name, type_var(inp.type_var), ) return (inp.name, type3(inp.type3), )
def import_(inp: ourlang.Function) -> wasm.Import: def import_(inp: ourlang.Function) -> wasm.Import:
""" """
@ -562,7 +531,7 @@ def import_(inp: ourlang.Function) -> wasm.Import:
function_argument(x) function_argument(x)
for x in inp.posonlyargs for x in inp.posonlyargs
], ],
type_var(inp.returns_type_var) type3(inp.returns_type3)
) )
def function(inp: ourlang.Function) -> wasm.Function: def function(inp: ourlang.Function) -> wasm.Function:
@ -592,7 +561,7 @@ def function(inp: ourlang.Function) -> wasm.Function:
(k, v.wasm_type(), ) (k, v.wasm_type(), )
for k, v in wgn.locals.items() for k, v in wgn.locals.items()
], ],
type_var(inp.returns_type_var), type3(inp.returns_type3),
wgn.statements wgn.statements
) )

View File

@ -10,12 +10,6 @@ from typing_extensions import Final
WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', ) WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', ) WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
from .typing import (
TypeStruct,
TypeVar,
)
from .type3 import types as type3types from .type3 import types as type3types
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType
@ -23,15 +17,12 @@ class Expression:
""" """
An expression within a statement An expression within a statement
""" """
__slots__ = ('type3', 'type_var', ) __slots__ = ('type3', )
type3: Type3OrPlaceholder type3: Type3OrPlaceholder
type_var: Optional[TypeVar]
def __init__(self) -> None: def __init__(self) -> None:
self.type3 = PlaceholderForType([self]) self.type3 = PlaceholderForType([self])
self.type_var = None
class Constant(Expression): class Constant(Expression):
""" """
@ -213,24 +204,22 @@ class FunctionParam:
""" """
A parameter for a Function A parameter for a Function
""" """
__slots__ = ('name', 'type3', 'type_str', 'type_var', ) __slots__ = ('name', 'type3', 'type_str', )
name: str name: str
type3: Type3 type3: Type3
type_str: str type_str: str
type_var: Optional[TypeVar]
def __init__(self, name: str, type3: Type3) -> None: def __init__(self, name: str, type3: Type3) -> None:
self.name = name self.name = name
self.type3 = type3 self.type3 = type3
self.type_str = type3.name self.type_str = type3.name
self.type_var = None
class Function: class Function:
""" """
A function processes input and produces output 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 name: str
lineno: int lineno: int
@ -239,7 +228,6 @@ class Function:
statements: List[Statement] statements: List[Statement]
returns_type3: Type3 returns_type3: Type3
returns_str: str returns_str: str
returns_type_var: Optional[TypeVar]
posonlyargs: List[FunctionParam] posonlyargs: List[FunctionParam]
def __init__(self, name: str, lineno: int) -> None: def __init__(self, name: str, lineno: int) -> None:
@ -250,7 +238,6 @@ class Function:
self.statements = [] self.statements = []
self.returns_type3 = type3types.none self.returns_type3 = type3types.none
self.returns_str = 'None' self.returns_str = 'None'
self.returns_type_var = None
self.posonlyargs = [] self.posonlyargs = []
# TODO: Broken after new type system # TODO: Broken after new type system
@ -299,13 +286,12 @@ class ModuleConstantDef:
""" """
A constant definition within a module 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 name: str
lineno: int lineno: int
type3: Type3 type3: Type3
type_str: str type_str: str
type_var: Optional[TypeVar]
constant: Constant constant: Constant
data_block: Optional['ModuleDataBlock'] data_block: Optional['ModuleDataBlock']
@ -314,7 +300,6 @@ class ModuleConstantDef:
self.lineno = lineno self.lineno = lineno
self.type3 = type3 self.type3 = type3
self.type_str = type3.name self.type_str = type3.name
self.type_var = None
self.constant = constant self.constant = constant
self.data_block = data_block self.data_block = data_block
@ -349,12 +334,12 @@ class Module:
__slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',) __slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',)
data: ModuleData data: ModuleData
structs: Dict[str, TypeStruct] # structs: Dict[str, TypeStruct]
constant_defs: Dict[str, ModuleConstantDef] constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function] functions: Dict[str, Function]
def __init__(self) -> None: def __init__(self) -> None:
self.data = ModuleData() self.data = ModuleData()
self.structs = {} # self.structs = {}
self.constant_defs = {} self.constant_defs = {}
self.functions = {} self.functions = {}

View File

@ -5,13 +5,6 @@ from typing import Any, Dict, NoReturn, Union
import ast import ast
from .typing import (
BUILTIN_TYPES,
TypeStruct,
TypeStructMember,
)
from .type3 import types as type3types from .type3 import types as type3types
from .exceptions import StaticError from .exceptions import StaticError
@ -108,12 +101,12 @@ class OurVisitor:
return module 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): if isinstance(node, ast.FunctionDef):
return self.pre_visit_Module_FunctionDef(module, node) return self.pre_visit_Module_FunctionDef(module, node)
if isinstance(node, ast.ClassDef): # if isinstance(node, ast.ClassDef):
return self.pre_visit_Module_ClassDef(module, node) # return self.pre_visit_Module_ClassDef(module, node)
if isinstance(node, ast.AnnAssign): if isinstance(node, ast.AnnAssign):
return self.pre_visit_Module_AnnAssign(module, node) return self.pre_visit_Module_AnnAssign(module, node)
@ -162,35 +155,36 @@ class OurVisitor:
return function return function
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> TypeStruct: def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> None: # TypeStruct:
struct = TypeStruct(node.name, node.lineno) 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.bases, 'ClassDef.bases')
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list') # _not_implemented(not node.keywords, 'ClassDef.keywords')
# _not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
offset = 0 #
# offset = 0
for stmt in node.body: #
if not isinstance(stmt, ast.AnnAssign): # for stmt in node.body:
raise NotImplementedError(f'Class with {stmt} nodes') # 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 isinstance(stmt.target, ast.Name):
# raise NotImplementedError('Class with default values')
if not stmt.value is None: #
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') # 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) # 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() # struct.members.append(member)
# offset += member.type.alloc_size()
return struct #
# return struct
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name): if not isinstance(node.target, ast.Name):

View File

@ -6,6 +6,8 @@ constraint generator works with.
""" """
from typing import Any, Dict, Iterable, List, Protocol, Union 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): class ExpressionProtocol(Protocol):
""" """
A protocol for classes that should be updated on substitution A protocol for classes that should be updated on substitution

View File

@ -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)

View File

@ -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)

View File

@ -13,7 +13,6 @@ import wasmtime
from phasm.compiler import phasm_compile from phasm.compiler import phasm_compile
from phasm.parser import phasm_parse from phasm.parser import phasm_parse
from phasm.typer import phasm_type
from phasm.type3.entry import phasm_type3 from phasm.type3.entry import phasm_type3
from phasm import ourlang from phasm import ourlang
from phasm import wasm from phasm import wasm
@ -43,7 +42,6 @@ class RunnerBase:
""" """
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)
phasm_type(self.phasm_ast)
def compile_ast(self) -> None: def compile_ast(self) -> None:
""" """

View File

@ -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)

View File

@ -1,6 +1,7 @@
import pytest import pytest
from phasm.exceptions import TypingError from phasm.exceptions import TypingError
from phasm.type3.entry import Type3Exception
from ..helpers import Suite from ..helpers import Suite
from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, COMPLETE_NUMERIC_TYPES, TYPE_MAP 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) assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
def test_expr_constant_entanglement(): def test_expr_constant_literal_does_not_fit():
code_py = """ code_py = """
@exported @exported
def testEntry() -> u8: def testEntry() -> u8:
return 1000 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() Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test
@ -354,12 +355,12 @@ def testEntry() -> i32:
@pytest.mark.integration_test @pytest.mark.integration_test
def test_call_pre_defined(): def test_call_pre_defined():
code_py = """ code_py = """
def helper(left: i32, right: i32) -> i32: def helper(left: i32) -> i32:
return left + right return left
@exported @exported
def testEntry() -> i32: def testEntry() -> i32:
return helper(10, 3) return helper(13)
""" """
result = Suite(code_py).run_code() 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) assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test @pytest.mark.integration_test
def test_call_invalid_type(): def test_call_invalid_return_type():
code_py = """ code_py = """
def helper() -> i64: def helper() -> i64:
return 19 return 19
@ -426,5 +427,19 @@ def testEntry() -> i32:
return helper() 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() Suite(code_py).run_code()

View File

@ -15,7 +15,7 @@ CONSTANT: {type_}[3] = (24, 57, 80, )
@exported @exported
def testEntry() -> {type_}: def testEntry() -> {type_}:
return CONSTANT[0] return CONSTANT[1]
""" """
result = Suite(code_py).run_code() result = Suite(code_py).run_code()