diff --git a/projects/shogoth/src/python/shogoth/types/__init__.py b/projects/shogoth/src/python/shogoth/types/__init__.py index a4e1c4b..a645e01 100644 --- a/projects/shogoth/src/python/shogoth/types/__init__.py +++ b/projects/shogoth/src/python/shogoth/types/__init__.py @@ -3,5 +3,100 @@ from .keyword import Keyword from .symbol import Symbol +from abc import ABC +from typing import NamedTuple, List, Mapping -__all__ = ["Keyword", "Symbol"] +from uuid import UUID, uuid4 + + +class TypeExpr(ABC): + """A type expression""" + + bindings: [] + + +class TypeVariable(TypeExpr): + name: str + id: UUID = uuid4() + + +class PrimitiveExpr(object): + class Nat(TypeExpr): pass + class N8(Nat): pass + class N16(Nat): pass + class N32(Nat): pass + class N64(Nat): pass + class N128(Nat): pass + class N256(Nat): pass + class N512(Nat): pass + + class Unsigned(TypeExpr): pass + class U8(Unsigned): pass + class U16(Unsigned): pass + class U32(Unsigned): pass + class U64(Unsigned): pass + class U128(Unsigned): pass + class U256(Unsigned): pass + class U512(): pass + + class Integer(TypeExpr): pass + class I8(Integer): pass + class I16(Integer): pass + class I32(Integer): pass + class I64(Integer): pass + class I128(Integer): pass + class I256(Integer): pass + class I512(Integer): pass + + class Floating(TypeExpr): pass + class F8(Floating): pass + class F16(Floating): pass + class F32(Floating): pass + class F64(Floating): pass + class F128(Floating): pass + class F256(Floating): pass + class F512(Floating): pass + + + +class ArrayExpr(TypeExpr): + child: TypeExpr + + +class SumExpr(TypeExpr): + children: List[TypeExpr] + + +class ProductExpr(TypeExpr): + children: Mapping[str, TypeExpr] + + +#################################################################################################### +#################################################################################################### +#################################################################################################### + +class Function(NamedTuple): + """The type of a function; a subset of its signature.""" + + +class FunctionSignature(NamedTuple): + raw: str + type_params: list + name: str + args: list + ret: list + + @staticmethod + def parse_list(l): + return [e for e in l.split(",") if e] + + @classmethod + def parse(cls, raw: str): + vars, name, args, ret = raw.split(";") + return cls( + raw, + cls.parse_list(vars), + name, + cls.parse_list(args), + cls.parse_list(ret) + ) diff --git a/projects/shogoth/src/python/shogoth/types/function.py b/projects/shogoth/src/python/shogoth/types/function.py new file mode 100644 index 0000000..04fde96 --- /dev/null +++ b/projects/shogoth/src/python/shogoth/types/function.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +from typing import NamedTuple + + +class FunctionSignature(NamedTuple): + raw: str + type_params: list + name: str + args: list + ret: list + + @staticmethod + def parse_list(l): + return [e for e in l.split(",") if e] + + @classmethod + def parse(cls, raw: str): + vars, name, args, ret = raw.split(";") + return cls( + raw, + cls.parse_list(vars), + name, + cls.parse_list(args), + cls.parse_list(ret) + ) + + +class Function(NamedTuple): + """The type of a function; a subset of its signature.""" diff --git a/projects/shogoth/src/python/shogoth/vm/__init__.py b/projects/shogoth/src/python/shogoth/vm/__init__.py index 63f77b6..88637e3 100644 --- a/projects/shogoth/src/python/shogoth/vm/__init__.py +++ b/projects/shogoth/src/python/shogoth/vm/__init__.py @@ -1,2 +1,5 @@ -#!/usr/bin/env python3 +# noqa +from .isa import * # noqa +from .bootstrap import * # noqa +from .impl import * # noqa diff --git a/projects/shogoth/src/python/shogoth/vm/impl.py b/projects/shogoth/src/python/shogoth/vm/impl.py index 2393267..df63a5c 100644 --- a/projects/shogoth/src/python/shogoth/vm/impl.py +++ b/projects/shogoth/src/python/shogoth/vm/impl.py @@ -83,10 +83,10 @@ class Interpreter(object): def __init__(self, bootstrap_module): self.bootstrap = bootstrap_module - def run(self, opcodes): + def run(self, opcodes, stack=[]): """Directly interpret some opcodes in the configured environment.""" - stack = Stackframe() + stack = Stackframe(stack=stack) mod = self.bootstrap.copy() mod.define_function(";;;", opcodes) stack.ip = mod.functions[";;;"] diff --git a/projects/shogoth/src/python/shogoth/vm/isa.py b/projects/shogoth/src/python/shogoth/vm/isa.py index c449bc1..25fe8b9 100644 --- a/projects/shogoth/src/python/shogoth/vm/isa.py +++ b/projects/shogoth/src/python/shogoth/vm/isa.py @@ -3,6 +3,8 @@ from typing import NamedTuple +from shogoth.types import Function, FunctionSignature + class Opcode: class TRUE(NamedTuple): @@ -51,6 +53,8 @@ class Opcode: Branch to `target` pushing the current point onto the call stack. The callee will see a stack containg only the provided `nargs`. A subsequent RETURN will return execution to the next point. + + Executing a `CALL` pushes the name and module path of the current function. """ funref: str @@ -60,6 +64,9 @@ class Opcode: Return to the source of the last `CALL`. The returnee will see the top `nargs` values of the present stack appended to theirs. All other values on the stack will be discarded. + + Executing a `RETURN` pops (restores) the name and module path of the current function back to that of the caller. + If the call stack is empty, `RETURN` will exit the interpreter. """ @@ -68,6 +75,8 @@ class Opcode: class GOTO(NamedTuple): """() -> () Branch to another point within the same bytecode segment. + The target MUST be within the same module range as the current function. + Branching does NOT update the name or module of the current function. """ target: int @@ -75,11 +84,13 @@ class Opcode: class STRUCT(NamedTuple): """(*) -> (T) - Consume the top N items of the stack, producing a struct. + Consume the top N items of the stack, producing a struct of the type `structref`. + + The name and module path of the current function MUST match the name and module path of `structref`. """ - nargs: int structref: str + nargs: int class FIELD(NamedTuple): """(A) -> (B) @@ -89,29 +100,6 @@ class Opcode: fieldref: str -class FunctionSignature(NamedTuple): - raw: str - type_params: list - name: str - args: list - ret: list - - @staticmethod - def parse_list(l): - return [e for e in l.split(",") if e] - - @classmethod - def parse(cls, raw: str): - vars, name, args, ret = raw.split(";") - return cls( - raw, - cls.parse_list(vars), - name, - cls.parse_list(args), - cls.parse_list(ret) - ) - - class Module(NamedTuple): opcodes: list = [] functions: dict = {} diff --git a/projects/shogoth/test/python/shogoth/vm/fixtures.py b/projects/shogoth/test/python/shogoth/vm/fixtures.py new file mode 100644 index 0000000..cd9bd89 --- /dev/null +++ b/projects/shogoth/test/python/shogoth/vm/fixtures.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +import pytest +from shogoth.vm import * + +import pytest + +@pytest.fixture +def vm(): + return Interpreter(BOOTSTRAP) diff --git a/projects/shogoth/test/python/shogoth/vm/test_bootstrap.py b/projects/shogoth/test/python/shogoth/vm/test_bootstrap.py new file mode 100644 index 0000000..c10c512 --- /dev/null +++ b/projects/shogoth/test/python/shogoth/vm/test_bootstrap.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import pytest +from shogoth.vm import * + +from .fixtures import * # noqa + + +@pytest.mark.parametrize('stack,ret', [ + [[True], [False]], + [[True], [False]], +]) +def test_not(vm, stack, ret): + assert vm.run([Opcode.CALL(NOT)], stack = stack) == ret + + +@pytest.mark.parametrize('stack,ret', [ + [[False, False], [False]], + [[True, False], [True]], + [[False, True], [True]], + [[True, True], [True]], +]) +def test_or(vm, stack, ret): + assert vm.run([Opcode.CALL(OR)], stack = stack) == ret + + +@pytest.mark.parametrize('stack,ret', [ + [[False, False], [False]], + [[True, False], [False]], + [[False, True], [False]], + [[True, True], [True]], +]) +def test_and(vm, stack, ret): + assert vm.run([Opcode.CALL(AND)], stack = stack) == ret + + +@pytest.mark.parametrize('stack,ret', [ + [[False, False], [False]], + [[True, False], [True]], + [[False, True], [True]], + [[True, True], [False]], +]) +def test_xor(vm, stack, ret): + assert vm.run([Opcode.CALL(XOR)], stack = stack) == ret diff --git a/projects/shogoth/test/python/shogoth/vm/test_interpreter.py b/projects/shogoth/test/python/shogoth/vm/test_interpreter.py index 551f788..11697ed 100644 --- a/projects/shogoth/test/python/shogoth/vm/test_interpreter.py +++ b/projects/shogoth/test/python/shogoth/vm/test_interpreter.py @@ -5,10 +5,7 @@ Tests coverign the VM interpreter import pytest from shogoth.vm import * - -@pytest.fixture -def vm(): - return Interpreter(BOOTSTRAP) +from .fixtures import * # noqa def test_true(vm): @@ -67,110 +64,6 @@ def test_drop(vm): ]) == [True] -def test_not(vm): - assert vm.run([ - Opcode.TRUE(), - Opcode.CALL(NOT), - Opcode.RETURN(1) - ]) == [False] - - assert vm.run([ - Opcode.FALSE(), - Opcode.CALL(NOT), - Opcode.RETURN(1) - ]) == [True] - - -def test_or(vm): - assert vm.run([ - Opcode.FALSE(), - Opcode.FALSE(), - Opcode.CALL(OR), - Opcode.RETURN(1) - ]) == [False] - - assert vm.run([ - Opcode.TRUE(), - Opcode.FALSE(), - Opcode.CALL(OR), - Opcode.RETURN(1) - ]) == [True] - - assert vm.run([ - Opcode.FALSE(), - Opcode.TRUE(), - Opcode.CALL(OR), - Opcode.RETURN(1) - ]) == [True] - - assert vm.run([ - Opcode.TRUE(), - Opcode.TRUE(), - Opcode.CALL(OR), - Opcode.RETURN(1) - ]) == [True] - - -def test_and(vm): - assert vm.run([ - Opcode.FALSE(), - Opcode.FALSE(), - Opcode.CALL(AND), - Opcode.RETURN(1) - ]) == [False] - - assert vm.run([ - Opcode.TRUE(), - Opcode.FALSE(), - Opcode.CALL(AND), - Opcode.RETURN(1) - ]) == [False] - - assert vm.run([ - Opcode.FALSE(), - Opcode.TRUE(), - Opcode.CALL(AND), - Opcode.RETURN(1) - ]) == [False] - - assert vm.run([ - Opcode.TRUE(), - Opcode.TRUE(), - Opcode.CALL(AND), - Opcode.RETURN(1) - ]) == [True] - - -def test_xor(vm): - assert vm.run([ - Opcode.FALSE(), - Opcode.FALSE(), - Opcode.CALL(XOR), - Opcode.RETURN(1) - ]) == [False] - - assert vm.run([ - Opcode.TRUE(), - Opcode.FALSE(), - Opcode.CALL(XOR), - Opcode.RETURN(1) - ]) == [True] - - assert vm.run([ - Opcode.FALSE(), - Opcode.TRUE(), - Opcode.CALL(XOR), - Opcode.RETURN(1) - ]) == [True] - - assert vm.run([ - Opcode.TRUE(), - Opcode.TRUE(), - Opcode.CALL(XOR), - Opcode.RETURN(1) - ]) == [False] - - def test_dup_too_many(vm): with pytest.raises(InterpreterError): vm.run([Opcode.DUP(1)])