From d82a662b390b03f1eb1b2dcee680b17e3b87cb1f Mon Sep 17 00:00:00 2001
From: "Reid D. 'arrdem' McKenzie" <me@arrdem.com>
Date: Fri, 15 Apr 2022 00:31:58 -0600
Subject: [PATCH] Happy hacking; setting up for structs

---
 .../src/python/shogoth/types/__init__.py      |  97 +++++++++++++++-
 .../src/python/shogoth/types/function.py      |  30 +++++
 .../shogoth/src/python/shogoth/vm/__init__.py |   5 +-
 .../shogoth/src/python/shogoth/vm/impl.py     |   4 +-
 projects/shogoth/src/python/shogoth/vm/isa.py |  38 +++---
 .../test/python/shogoth/vm/fixtures.py        |  10 ++
 .../test/python/shogoth/vm/test_bootstrap.py  |  44 +++++++
 .../python/shogoth/vm/test_interpreter.py     | 109 +-----------------
 8 files changed, 200 insertions(+), 137 deletions(-)
 create mode 100644 projects/shogoth/src/python/shogoth/types/function.py
 create mode 100644 projects/shogoth/test/python/shogoth/vm/fixtures.py
 create mode 100644 projects/shogoth/test/python/shogoth/vm/test_bootstrap.py

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(";<entry>;;", opcodes)
         stack.ip = mod.functions[";<entry>;;"]
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)])