Compare commits

...

4 Commits

Author SHA1 Message Date
Johan B.W. de Vries
47b7746787 Type fix 2023-11-07 11:07:33 +01:00
Johan B.W. de Vries
1fb52d1479 Ideas for the IO Monad 2023-11-07 11:07:33 +01:00
Johan B.W. de Vries
0aa8207987 Import service names 2023-11-07 10:51:55 +01:00
Johan B.W. de Vries
cea236494f Bytes constants 2023-11-07 10:51:43 +01:00
6 changed files with 171 additions and 29 deletions

View File

@ -84,6 +84,10 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
# Tuples are passed as pointer, which are i32 # Tuples are passed as pointer, which are i32
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if inp.base == type3types.IO:
assert 1 == len(inp.args)
return type3(inp.args[0])
raise NotImplementedError(type3, inp) raise NotImplementedError(type3, inp)
# Operators that work for i32, i64, f32, f64 # Operators that work for i32, i64, f32, f64
@ -229,7 +233,13 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.f64.const(inp.value) wgn.f64.const(inp.value)
return return
raise NotImplementedError(f'Constants with type {inp.type3}') raise NotImplementedError(f'Constants with type {inp.type3:s}')
if isinstance(inp, ourlang.ConstantBytes):
assert inp.data_block is not None, 'Bytes must be memory stored'
assert inp.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.data_block.address)
return
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
if isinstance(inp.variable, ourlang.FunctionParam): if isinstance(inp.variable, ourlang.FunctionParam):
@ -239,6 +249,12 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if isinstance(inp.variable, ourlang.ModuleConstantDef): if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp.type3 is type3types.bytes:
assert inp.variable.data_block is not None, 'Bytes must be memory stored'
assert inp.variable.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.variable.data_block.address)
return
if isinstance(inp.type3, type3types.StructType3): if isinstance(inp.type3, type3types.StructType3):
assert inp.variable.data_block is not None, 'Structs must be memory stored' assert inp.variable.data_block is not None, 'Structs must be memory stored'
assert inp.variable.data_block.address is not None, 'Value not allocated' assert inp.variable.data_block.address is not None, 'Value not allocated'
@ -557,6 +573,12 @@ def statement_if(wgn: WasmGenerator, inp: ourlang.StatementIf) -> None:
# for stat in inp.else_statements: # for stat in inp.else_statements:
# statement(wgn, stat) # statement(wgn, stat)
def statement_expression(wgn: WasmGenerator, inp: ourlang.StatementExpression) -> None:
"""
Compile: Expression whose result is ignored
"""
expression(wgn, inp.expr)
def statement(wgn: WasmGenerator, inp: ourlang.Statement) -> None: def statement(wgn: WasmGenerator, inp: ourlang.Statement) -> None:
""" """
Compile: any statement Compile: any statement
@ -572,6 +594,10 @@ def statement(wgn: WasmGenerator, inp: ourlang.Statement) -> None:
if isinstance(inp, ourlang.StatementPass): if isinstance(inp, ourlang.StatementPass):
return return
if isinstance(inp, ourlang.StatementExpression):
statement_expression(wgn, inp)
return
raise NotImplementedError(statement, inp) raise NotImplementedError(statement, inp)
def function_argument(inp: ourlang.FunctionParam) -> wasm.Param: def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
@ -587,7 +613,7 @@ def import_(inp: ourlang.Function) -> wasm.Import:
assert inp.imported assert inp.imported
return wasm.Import( return wasm.Import(
'imports', inp.imported,
inp.name, inp.name,
inp.name, inp.name,
[ [
@ -721,6 +747,12 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
data_list.append(module_data_f64(constant.value)) data_list.append(module_data_f64(constant.value))
continue continue
if constant.type3 == type3types.bytes:
assert isinstance(constant.value, bytes)
data_list.append(module_data_u32(len(constant.value)))
data_list.append(constant.value)
continue
raise NotImplementedError(constant, constant.type3, constant.value) raise NotImplementedError(constant, constant.type3, constant.value)
block_data = b''.join(data_list) block_data = b''.join(data_list)

View File

@ -47,15 +47,32 @@ class ConstantPrimitive(Constant):
def __repr__(self) -> str: def __repr__(self) -> str:
return f'ConstantPrimitive({repr(self.value)})' return f'ConstantPrimitive({repr(self.value)})'
class ConstantBytes(Constant):
"""
A bytes constant value expression within a statement
"""
__slots__ = ('value', 'data_block', )
value: bytes
data_block: 'ModuleDataBlock'
def __init__(self, value: bytes, data_block: 'ModuleDataBlock') -> None:
super().__init__()
self.value = value
self.data_block = data_block
def __repr__(self) -> str:
return f'ConstantBytes({repr(self.value)}, @{repr(self.data_block.address)})'
class ConstantTuple(Constant): class ConstantTuple(Constant):
""" """
A Tuple constant value expression within a statement A Tuple constant value expression within a statement
""" """
__slots__ = ('value', ) __slots__ = ('value', )
value: List[ConstantPrimitive] value: List[Union[ConstantPrimitive, ConstantBytes]]
def __init__(self, value: List[ConstantPrimitive]) -> None: # FIXME: Tuple of tuples? def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes]]) -> None: # FIXME: Tuple of tuples?
super().__init__() super().__init__()
self.value = value self.value = value
@ -69,9 +86,9 @@ class ConstantStruct(Constant):
__slots__ = ('struct_name', 'value', ) __slots__ = ('struct_name', 'value', )
struct_name: str struct_name: str
value: List[ConstantPrimitive] value: List[Union[ConstantPrimitive, ConstantBytes]]
def __init__(self, struct_name: str, value: List[ConstantPrimitive]) -> None: # FIXME: Struct of structs? def __init__(self, struct_name: str, value: List[Union[ConstantPrimitive, ConstantBytes]]) -> None: # FIXME: Struct of structs?
super().__init__() super().__init__()
self.struct_name = struct_name self.struct_name = struct_name
self.value = value self.value = value
@ -253,6 +270,19 @@ class StatementIf(Statement):
self.statements = [] self.statements = []
self.else_statements = [] self.else_statements = []
class StatementExpression(Statement):
"""
An expression within a function
The result of the expression is by the code.
"""
__slots__ = ('expr', )
expr: Expression
def __init__(self, expr: Expression) -> None:
self.expr = expr
class FunctionParam: class FunctionParam:
""" """
A parameter for a Function A parameter for a Function
@ -275,7 +305,7 @@ class Function:
name: str name: str
lineno: int lineno: int
exported: bool exported: bool
imported: bool imported: Optional[str]
statements: List[Statement] statements: List[Statement]
returns_type3: Type3 returns_type3: Type3
posonlyargs: List[FunctionParam] posonlyargs: List[FunctionParam]
@ -284,7 +314,7 @@ class Function:
self.name = name self.name = name
self.lineno = lineno self.lineno = lineno
self.exported = False self.exported = False
self.imported = False self.imported = None
self.statements = [] self.statements = []
self.returns_type3 = type3types.none # FIXME: This could be a placeholder self.returns_type3 = type3types.none # FIXME: This could be a placeholder
self.posonlyargs = [] self.posonlyargs = []
@ -348,10 +378,10 @@ class ModuleDataBlock:
""" """
__slots__ = ('data', 'address', ) __slots__ = ('data', 'address', )
data: List[ConstantPrimitive] data: List[Union[ConstantPrimitive, ConstantBytes]]
address: Optional[int] address: Optional[int]
def __init__(self, data: List[ConstantPrimitive]) -> None: def __init__(self, data: List[Union[ConstantPrimitive, ConstantBytes]]) -> None:
self.data = data self.data = data
self.address = None self.address = None

View File

@ -16,7 +16,7 @@ from .ourlang import (
Expression, Expression,
BinaryOp, BinaryOp,
ConstantPrimitive, ConstantTuple, ConstantStruct, ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct,
TupleInstantiation, TupleInstantiation,
FunctionCall, AccessStructMember, Subscript, FunctionCall, AccessStructMember, Subscript,
@ -27,6 +27,7 @@ from .ourlang import (
Statement, Statement,
StatementIf, StatementPass, StatementReturn, StatementIf, StatementPass, StatementReturn,
StatementExpression,
FunctionParam, FunctionParam,
ModuleConstantDef, ModuleConstantDef,
@ -133,16 +134,34 @@ class OurVisitor:
# Do stmts at the end so we have the return value # Do stmts at the end so we have the return value
for decorator in node.decorator_list: for decorator in node.decorator_list:
if not isinstance(decorator, ast.Name): if isinstance(decorator, ast.Call):
_raise_static_error(decorator, 'Function decorators must be string') if not isinstance(decorator.func, ast.Name):
if not isinstance(decorator.ctx, ast.Load): _raise_static_error(decorator, 'Function decorators must be string')
_raise_static_error(decorator, 'Must be load context') if not isinstance(decorator.func.ctx, ast.Load):
_not_implemented(decorator.id in ('exported', 'imported'), 'Custom decorators') _raise_static_error(decorator, 'Must be load context')
_not_implemented(decorator.func.id == 'imported', 'Custom decorators')
if decorator.id == 'exported': if 1 != len(decorator.args):
function.exported = True _raise_static_error(decorator, 'One argument expected')
if not isinstance(decorator.args[0], ast.Constant):
_raise_static_error(decorator, 'Service name must be a constant')
if not isinstance(decorator.args[0].value, str):
_raise_static_error(decorator, 'Service name must be a stirng')
if 0 != len(decorator.keywords): # TODO: Allow for namespace keyword
_raise_static_error(decorator, 'No keyword arguments expected')
function.imported = decorator.args[0].value
else: else:
function.imported = True if not isinstance(decorator, ast.Name):
_raise_static_error(decorator, 'Function decorators must be string')
if not isinstance(decorator.ctx, ast.Load):
_raise_static_error(decorator, 'Must be load context')
_not_implemented(decorator.id in ('exported', 'imported'), 'Custom decorators')
if decorator.id == 'exported':
function.exported = True
else:
function.imported = 'imports'
if node.returns is not None: # Note: `-> None` would be a ast.Constant if node.returns is not None: # Note: `-> None` would be a ast.Constant
function.returns_type3 = self.visit_type(module, node.returns) function.returns_type3 = self.visit_type(module, node.returns)
@ -190,12 +209,20 @@ class OurVisitor:
if isinstance(node.value, ast.Constant): if isinstance(node.value, ast.Constant):
type3 = self.visit_type(module, node.annotation) type3 = self.visit_type(module, node.annotation)
value_data = self.visit_Module_Constant(module, node.value)
if isinstance(value_data, ConstantBytes):
data_block = value_data.data_block
else:
data_block = None
return ModuleConstantDef( return ModuleConstantDef(
node.target.id, node.target.id,
node.lineno, node.lineno,
type3, type3,
self.visit_Module_Constant(module, node.value), value_data,
None, data_block,
) )
if isinstance(node.value, ast.Tuple): if isinstance(node.value, ast.Tuple):
@ -317,6 +344,9 @@ class OurVisitor:
if isinstance(node, ast.Pass): if isinstance(node, ast.Pass):
return StatementPass() return StatementPass()
if isinstance(node, ast.Expr):
return StatementExpression(self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value))
raise NotImplementedError(f'{node} as stmt in FunctionDef') raise NotImplementedError(f'{node} as stmt in FunctionDef')
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression: def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
@ -550,17 +580,39 @@ class OurVisitor:
return Subscript(varref, slice_expr) return Subscript(varref, slice_expr)
def visit_Module_Constant(self, module: Module, node: ast.Constant) -> ConstantPrimitive: def visit_Module_Constant(self, module: Module, node: ast.Constant) -> Union[ConstantPrimitive, ConstantBytes]:
del module
_not_implemented(node.kind is None, 'Constant.kind') _not_implemented(node.kind is None, 'Constant.kind')
if isinstance(node.value, (int, float, )): if isinstance(node.value, (int, float, )):
return ConstantPrimitive(node.value) return ConstantPrimitive(node.value)
if isinstance(node.value, bytes):
data_block = ModuleDataBlock([])
module.data.blocks.append(data_block)
result = ConstantBytes(node.value, data_block)
data_block.data.append(result)
return result
raise NotImplementedError(f'{node.value} as constant') raise NotImplementedError(f'{node.value} as constant')
def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3: def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3:
if isinstance(node, ast.Call):
if not isinstance(node.func, ast.Name):
_raise_static_error(node, 'Can only call Monads by name')
if node.keywords:
_raise_static_error(node, 'Monads cannot have keyword arguments')
monad_type = self.visit_type(module, node.func)
if not isinstance(monad_type, type3types.MonadType3):
_raise_static_error(node, 'Must be a Monad')
return type3types.AppliedType3(
monad_type,
(self.visit_type(module, x) for x in node.args)
)
if isinstance(node, ast.Constant): if isinstance(node, ast.Constant):
if node.value is None: if node.value is None:
return type3types.none return type3types.none

View File

@ -323,12 +323,12 @@ class LiteralFitsConstraint(ConstraintBase):
__slots__ = ('type3', 'literal', ) __slots__ = ('type3', 'literal', )
type3: types.Type3OrPlaceholder type3: types.Type3OrPlaceholder
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple, ourlang.ConstantStruct] literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct]
def __init__( def __init__(
self, self,
type3: types.Type3OrPlaceholder, type3: types.Type3OrPlaceholder,
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple, ourlang.ConstantStruct], literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct],
comment: Optional[str] = None, comment: Optional[str] = None,
) -> None: ) -> None:
super().__init__(comment=comment) super().__init__(comment=comment)
@ -380,6 +380,12 @@ class LiteralFitsConstraint(ConstraintBase):
return Error('Must be real') # FIXME: Add line information return Error('Must be real') # FIXME: Add line information
if self.type3 is types.bytes:
if isinstance(self.literal.value, bytes):
return None
return Error('Must be bytes') # FIXME: Add line information
res: NewConstraintList res: NewConstraintList
if isinstance(self.type3, types.AppliedType3): if isinstance(self.type3, types.AppliedType3):

View File

@ -25,7 +25,7 @@ def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase
return [*module(ctx, inp)] return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator: def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantTuple, ourlang.ConstantStruct)): if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
yield LiteralFitsConstraint(inp.type3, inp) yield LiteralFitsConstraint(inp.type3, inp)
return return
@ -183,6 +183,12 @@ def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf)
for stmt in inp.else_statements: for stmt in inp.else_statements:
yield from statement(ctx, fun, stmt) yield from statement(ctx, fun, stmt)
def statement_expression(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementExpression) -> ConstraintGenerator:
yield from expression(ctx, inp.expr)
yield SameTypeConstraint(fun.returns_type3, inp.expr.type3,
comment=f'TODO')
def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> ConstraintGenerator: def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> ConstraintGenerator:
if isinstance(inp, ourlang.StatementReturn): if isinstance(inp, ourlang.StatementReturn):
yield from statement_return(ctx, fun, inp) yield from statement_return(ctx, fun, inp)
@ -192,6 +198,10 @@ def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> Co
yield from statement_if(ctx, fun, inp) yield from statement_if(ctx, fun, inp)
return return
if isinstance(inp, ourlang.StatementExpression):
yield from statement_expression(ctx, fun, inp)
return
raise NotImplementedError(statement, fun, inp) raise NotImplementedError(statement, fun, inp)
def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator: def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:

View File

@ -69,6 +69,14 @@ class PrimitiveType3(Type3):
__slots__ = () __slots__ = ()
class MonadType3(Type3):
"""
A type method to indicate that actions have to be made
one after the other, usually because they affect outside state.
"""
__slots__ = ()
class IntType3(Type3): class IntType3(Type3):
""" """
Sometimes you can have an int as type, e.g. when using static arrays Sometimes you can have an int as type, e.g. when using static arrays
@ -146,7 +154,7 @@ class AppliedType3(Type3):
""" """
__slots__ = ('base', 'args', ) __slots__ = ('base', 'args', )
base: PrimitiveType3 base: Union[PrimitiveType3, MonadType3]
""" """
The base type The base type
""" """
@ -156,7 +164,7 @@ class AppliedType3(Type3):
The applied types (or placeholders there for) The applied types (or placeholders there for)
""" """
def __init__(self, base: PrimitiveType3, args: Iterable[Type3OrPlaceholder]) -> None: def __init__(self, base: Union[PrimitiveType3, MonadType3], args: Iterable[Type3OrPlaceholder]) -> None:
args = [*args] args = [*args]
assert args, 'Must at least one argument' assert args, 'Must at least one argument'
@ -307,6 +315,8 @@ It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different. determined length, and each argument can be different.
""" """
IO = MonadType3('IO')
LOOKUP_TABLE: Dict[str, Type3] = { LOOKUP_TABLE: Dict[str, Type3] = {
'none': none, 'none': none,
'bool': bool_, 'bool': bool_,
@ -319,4 +329,6 @@ LOOKUP_TABLE: Dict[str, Type3] = {
'f32': f32, 'f32': f32,
'f64': f64, 'f64': f64,
'bytes': bytes, 'bytes': bytes,
'IO': IO,
} }