""" This module contains the code to convert parsed Ourlang into WebAssembly code """ from typing import Generator, Tuple from . import ourlang from . import wasm Statements = Generator[wasm.Statement, None, None] def type_(inp: ourlang.OurType) -> wasm.OurType: if isinstance(inp, ourlang.OurTypeInt32): return wasm.OurTypeInt32() if isinstance(inp, ourlang.OurTypeInt64): return wasm.OurTypeInt64() if isinstance(inp, ourlang.OurTypeFloat32): return wasm.OurTypeFloat32() if isinstance(inp, ourlang.OurTypeFloat64): return wasm.OurTypeFloat64() if isinstance(inp, ourlang.Struct): # Structs are passed as pointer # And pointers are i32 return wasm.OurTypeInt32() raise NotImplementedError(type_, inp) OPERATOR_MAP = { '+': 'add', '-': 'sub', } I32_OPERATOR_MAP = { # TODO: Introduce UInt32 type '<': 'lt_s', '>': 'gt_s', '<=': 'le_s', '>=': 'ge_s', } def expression(inp: ourlang.Expression) -> Statements: if isinstance(inp, ourlang.ConstantInt32): yield wasm.Statement('i32.const', str(inp.value)) return if isinstance(inp, ourlang.ConstantInt64): yield wasm.Statement('i64.const', str(inp.value)) return if isinstance(inp, ourlang.ConstantFloat32): yield wasm.Statement('f32.const', str(inp.value)) return if isinstance(inp, ourlang.ConstantFloat64): yield wasm.Statement('f64.const', str(inp.value)) return if isinstance(inp, ourlang.VariableReference): yield wasm.Statement('local.get', '${}'.format(inp.name)) return if isinstance(inp, ourlang.BinaryOp): yield from expression(inp.left) yield from expression(inp.right) if isinstance(inp.type, ourlang.OurTypeInt32): if operator := OPERATOR_MAP.get(inp.operator, None): yield wasm.Statement(f'i32.{operator}') return if operator := I32_OPERATOR_MAP.get(inp.operator, None): yield wasm.Statement(f'i32.{operator}') return if isinstance(inp.type, ourlang.OurTypeInt64): if operator := OPERATOR_MAP.get(inp.operator, None): yield wasm.Statement(f'i64.{operator}') return if isinstance(inp.type, ourlang.OurTypeFloat32): if operator := OPERATOR_MAP.get(inp.operator, None): yield wasm.Statement(f'f32.{operator}') return if isinstance(inp.type, ourlang.OurTypeFloat64): if operator := OPERATOR_MAP.get(inp.operator, None): yield wasm.Statement(f'f64.{operator}') return raise NotImplementedError(expression, inp.type, inp.operator) if isinstance(inp, ourlang.UnaryOp): yield from expression(inp.right) if isinstance(inp.type, ourlang.OurTypeFloat32): if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS: yield wasm.Statement(f'f32.{inp.operator}') return if isinstance(inp.type, ourlang.OurTypeFloat64): if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS: yield wasm.Statement(f'f64.{inp.operator}') return raise NotImplementedError(expression, inp.type, inp.operator) if isinstance(inp, ourlang.FunctionCall): for arg in inp.arguments: yield from expression(arg) yield wasm.Statement('call', '${}'.format(inp.function.name)) return if isinstance(inp, ourlang.AccessStructMember): # FIXME: Properly implement this # inp.type.render() is also a hack that doesn't really work consistently if not isinstance(inp.type, ( ourlang.OurTypeInt32, ourlang.OurTypeFloat32, ourlang.OurTypeInt64, ourlang.OurTypeFloat64, )): raise NotImplementedError(inp, inp.type) yield from expression(inp.varref) yield wasm.Statement(inp.type.render() + '.load', 'offset=' + str(inp.member.offset)) return raise NotImplementedError(expression, inp) def statement_return(inp: ourlang.StatementReturn) -> Statements: yield from expression(inp.value) yield wasm.Statement('return') def statement_if(inp: ourlang.StatementIf) -> Statements: yield from expression(inp.test) yield wasm.Statement('if') for stat in inp.statements: yield from statement(stat) if inp.else_statements: yield wasm.Statement('else') for stat in inp.else_statements: yield from statement(stat) yield wasm.Statement('end') def statement(inp: ourlang.Statement) -> Statements: if isinstance(inp, ourlang.StatementReturn): yield from statement_return(inp) return if isinstance(inp, ourlang.StatementIf): yield from statement_if(inp) return raise NotImplementedError(statement, inp) def function_argument(inp: Tuple[str, ourlang.OurType]) -> wasm.Param: return (inp[0], type_(inp[1]), ) def function(inp: ourlang.Function) -> wasm.Function: if isinstance(inp, ourlang.StructConstructor): statements = [ *_generate_struct_constructor(inp) ] locals_ = [ ('___new_reference___addr', wasm.OurTypeInt32(), ), ] else: statements = [ x for y in inp.statements for x in statement(y) ] locals_ = [] # TODO return wasm.Function( inp.name, inp.exported, [ function_argument(x) for x in inp.posonlyargs ], locals_, type_(inp.returns), statements ) def module(inp: ourlang.Module) -> wasm.Module: result = wasm.Module() result.functions = [ _generate_allocator(inp), ] + [ function(x) for x in inp.functions.values() ] return result def _generate_allocator(mod: ourlang.Module) -> wasm.Function: return wasm.Function( '___new_reference___', False, [ ('alloc_size', type_(mod.types['i32']), ), ], [ ('result', type_(mod.types['i32']), ), ], type_(mod.types['i32']), [ wasm.Statement('i32.const', '0'), wasm.Statement('i32.const', '0'), wasm.Statement('i32.load'), wasm.Statement('local.tee', '$result', comment='Address for this call'), wasm.Statement('local.get', '$alloc_size'), wasm.Statement('i32.add'), wasm.Statement('i32.store', comment='Address for the next call'), wasm.Statement('local.get', '$result'), ], ) def _generate_struct_constructor(inp: ourlang.StructConstructor) -> Statements: yield wasm.Statement('i32.const', str(inp.struct.alloc_size())) yield wasm.Statement('call', '$___new_reference___') yield wasm.Statement('local.set', '$___new_reference___addr') for member in inp.struct.members: # FIXME: Properly implement this # inp.type.render() is also a hack that doesn't really work consistently if not isinstance(member.type, ( ourlang.OurTypeInt32, ourlang.OurTypeFloat32, ourlang.OurTypeInt64, ourlang.OurTypeFloat64, )): raise NotImplementedError yield wasm.Statement('local.get', '$___new_reference___addr') yield wasm.Statement('local.get', f'${member.name}') yield wasm.Statement(f'{member.type.render()}.store', 'offset=' + str(member.offset)) yield wasm.Statement('local.get', '$___new_reference___addr')