From 0d6d3319d8f9ad76cbc916759e880fd36f002521 Mon Sep 17 00:00:00 2001
From: "Reid D. 'arrdem' McKenzie" <me@arrdem.com>
Date: Thu, 21 Apr 2022 00:17:59 -0600
Subject: [PATCH] Function references; stubs for closures

---
 .../src/python/shogoth/types/__init__.py      |  63 ++------
 .../src/python/shogoth/vm/bootstrap.py        |  23 ++-
 .../shogoth/src/python/shogoth/vm/impl.py     |  34 ++++-
 projects/shogoth/src/python/shogoth/vm/isa.py | 137 +++++++++++++++---
 .../test/python/shogoth/vm/test_bootstrap.py  |  22 ++-
 5 files changed, 191 insertions(+), 88 deletions(-)

diff --git a/projects/shogoth/src/python/shogoth/types/__init__.py b/projects/shogoth/src/python/shogoth/types/__init__.py
index a645e01..91b3160 100644
--- a/projects/shogoth/src/python/shogoth/types/__init__.py
+++ b/projects/shogoth/src/python/shogoth/types/__init__.py
@@ -4,71 +4,26 @@ from .keyword import Keyword
 from .symbol import Symbol
 
 from abc import ABC
-from typing import NamedTuple, List, Mapping
+from typing import NamedTuple, List, Mapping, Any
 
 from uuid import UUID, uuid4
 
 
-class TypeExpr(ABC):
-    """A type expression"""
-
-    bindings: []
-
-
-class TypeVariable(TypeExpr):
+class TypeVariable(NamedTuple):
     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(NamedTuple):
+    child: Any
 
 
-
-class ArrayExpr(TypeExpr):
-    child: TypeExpr
+class SumExpr(NamedTuple):
+    children: List[Any]
 
 
-class SumExpr(TypeExpr):
-    children: List[TypeExpr]
-
-
-class ProductExpr(TypeExpr):
-    children: Mapping[str, TypeExpr]
+class ProductExpr(NamedTuple):
+    children: Mapping[str, Any]
 
 
 ####################################################################################################
@@ -79,7 +34,7 @@ class Function(NamedTuple):
     """The type of a function; a subset of its signature."""
 
 
-class FunctionSignature(NamedTuple):
+class FunctionRef(NamedTuple):
     raw: str
     type_params: list
     name: str
diff --git a/projects/shogoth/src/python/shogoth/vm/bootstrap.py b/projects/shogoth/src/python/shogoth/vm/bootstrap.py
index 58d9de7..563e3a1 100644
--- a/projects/shogoth/src/python/shogoth/vm/bootstrap.py
+++ b/projects/shogoth/src/python/shogoth/vm/bootstrap.py
@@ -5,7 +5,7 @@ Hopefully no "real" interpreter ever uses this code, since it's obviously replac
 """
 
 from .isa import Module, Opcode
-
+from shogoth.types import *
 
 BOOTSTRAP = Module()
 
@@ -52,19 +52,32 @@ XOR = BOOTSTRAP.define_function(
     [
         Opcode.DUP(nargs=2),
         # !A && B
-        Opcode.CALL(NOT),
-        Opcode.CALL(AND),
+        Opcode.CALLS(NOT),
+        Opcode.CALLS(AND),
         Opcode.IF(target=6),
         Opcode.TRUE(),
         Opcode.RETURN(1),
         # !B && A
         Opcode.ROT(2),
-        Opcode.CALL(NOT),
-        Opcode.CALL(AND),
+        Opcode.CALLS(NOT),
+        Opcode.CALLS(AND),
         Opcode.IF(target=12),
         Opcode.TRUE(),
         Opcode.RETURN(1),
         Opcode.FALSE(),
+
         Opcode.RETURN(1),
     ],
 )
+
+TRUE = BOOTSTRAP.define_type(
+    "true", ProductExpr([]),
+)
+
+FALSE = BOOTSTRAP.define_type(
+    "false", ProductExpr([]),
+)
+
+BOOL = BOOTSTRAP.define_type(
+    "bool", SumExpr([TRUE, FALSE])
+)
diff --git a/projects/shogoth/src/python/shogoth/vm/impl.py b/projects/shogoth/src/python/shogoth/vm/impl.py
index df63a5c..ceab3ca 100644
--- a/projects/shogoth/src/python/shogoth/vm/impl.py
+++ b/projects/shogoth/src/python/shogoth/vm/impl.py
@@ -18,7 +18,7 @@ context (a virtual machine) which DOES have an easily introspected and serialize
 
 from copy import deepcopy
 
-from .isa import FunctionSignature, Opcode
+from .isa import FunctionRef, Opcode
 
 
 def rotate(l):
@@ -38,7 +38,7 @@ class Stackframe(object):
     def pop(self):
         return self.stack.pop(0)
 
-    def call(self, signature: FunctionSignature, ip):
+    def call(self, signature: FunctionRef, ip):
         print(signature)
         nargs = len(signature.args)
         args, self.stack = self.stack[:nargs], self.stack[nargs:]
@@ -135,9 +135,9 @@ class Interpreter(object):
 
                     stack.drop(n)
 
-                case Opcode.CALL(dest):
+                case Opcode.CALLS(dest):
                     try:
-                        sig = FunctionSignature.parse(dest)
+                        sig = FunctionRef.parse(dest)
                     except:
                         _error("Invalid target")
 
@@ -154,7 +154,7 @@ class Interpreter(object):
                         _error("Stack size violation")
 
                     if stack.parent:
-                        sig = FunctionSignature.parse(stack.name)
+                        sig = FunctionRef.parse(stack.name)
                         if (len(sig.ret) != n):
                             _error("Signature violation")
 
@@ -169,6 +169,30 @@ class Interpreter(object):
                     stack.ip = n
                     continue
 
+                case Opcode.FUNREF(funref):
+                    try:
+                        # FIXME: Verify this statically
+                        stack.push(FunctionRef.parse(funref))
+                    except:
+                        _error("Invalid function ref")
+
+                case Opcode.CALLF(n):
+                    sig = stack.pop()
+                    if not isinstance(sig, FunctionRef):
+                        _error("CALLF requires a funref at top of stack")
+                    if not n == len(sig.args):
+                        _error("CALLF target violation; not enough arguments provided")
+                    if n > len(stack):
+                        _error("Stack size violation")
+
+                    try:
+                        ip = mod.functions[sig.raw]
+                    except KeyError:
+                        _error("Unknown target")
+
+                    stack = stack.call(sig, ip)
+                    continue
+
                 case _:
                     raise Exception(f"Unhandled interpreter state {op}")
 
diff --git a/projects/shogoth/src/python/shogoth/vm/isa.py b/projects/shogoth/src/python/shogoth/vm/isa.py
index 25fe8b9..92d0007 100644
--- a/projects/shogoth/src/python/shogoth/vm/isa.py
+++ b/projects/shogoth/src/python/shogoth/vm/isa.py
@@ -3,7 +3,7 @@
 
 from typing import NamedTuple
 
-from shogoth.types import Function, FunctionSignature
+from shogoth.types import Function, FunctionRef
 
 
 class Opcode:
@@ -27,6 +27,9 @@ class Opcode:
 
     # not, and, or, xor etc. can all be functions given if.
 
+    ####################################################################################################
+    # Stack manipulation
+    ####################################################################################################
     class DUP(NamedTuple):
         """(A, B, ...) -> (A, B, ...)
         Duplicate the top N items of the stack.
@@ -48,19 +51,28 @@ class Opcode:
 
         nargs: int = 1
 
-    class CALL(NamedTuple):
-        """(*) -> ()
+    ####################################################################################################
+    # Functional abstraction
+    ####################################################################################################
+    class CALLS(NamedTuple):
+        """(... A) -> (... B)
+        Call [static]
+
         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.
+
+        .. note::
+
+           CALLS is equvalent to `FUNREF; CALLF`
         """
 
         funref: str
 
     class RETURN(NamedTuple):
-        """(*) -> ()
+        """(... A) -> ()
         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.
@@ -82,22 +94,100 @@ class Opcode:
         target: int
         anywhere: bool = False
 
-    class STRUCT(NamedTuple):
-        """(*) -> (T)
-        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`.
+    class FUNREF(NamedTuple):
+        """() -> (`FUNREF<... A to ... B>`)
+        Construct a reference to a static codepoint.
         """
 
-        structref: str
+        funref: str
+
+    class CALLF(NamedTuple):
+        """(`FUNREF<... A to ... B>`, ... A) -> (... B)
+        Call [funref]
+
+        Make a dynamic call to the function reference at the top of 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.
+        """
+
+        nargs: int = 0
+
+    class CLOSUREF(NamedTuple):
+        """(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
+        Construct a closure over the function reference at the top of the stack.
+        This may produce nullary closures.
+        """
+
+    class CLOSUREC(NamedTuple):
+        """(`CLOSURE<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
+        Further close over the closure at the top of the stack.
+        This may produce nullary closures.
+        """
+
+    class CALLC(NamedTuple):
+        """(`CLOSURE<... A, ... B>`, ... A) -> (... B)
+        Call [closure]
+
+        Make a dynamic call to the closure at the top of stack.
+        The callee will see a stack containg only the provided `nargs` and closed-overs.
+        A subsequent RETURN will return execution to the next point.
+
+        Executing a `CALL` pushes the name and module path of the current function.
+        """
+
+        nargs: int = 0
+
+    # ####################################################################################################
+    # # Structures
+    # ####################################################################################################
+    # class STRUCT(NamedTuple):
+    #     """(*) -> (T)
+    #     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`.
+    #     """
+    #
+    #     structref: str
+    #     nargs: int
+
+    # class FLOAD(NamedTuple):
+    #     """(A) -> (B)
+    #     Consume the struct reference at the top of the stack, producing the value of the referenced field.
+    #     """
+    #
+    #     fieldref: str
+
+    # class FSTORE(NamedTuple):
+    #     """(A) -> (B)
+    #     Consume the struct reference at the top of the stack, producing the value of the referenced field.
+    #     """
+    #
+    #     fieldref: str
+
+    ####################################################################################################
+    # Arrays
+    ####################################################################################################
+    class ARRAY(NamedTuple):
+        """(*) -> (ARRAY<Y>)
+        Consume the top N items of the stack, producing an array of the type `typeref`.
+        """
+
+        typeref: str
         nargs: int
 
-    class FIELD(NamedTuple):
-        """(A) -> (B)
-        Consume the struct reference at the top of the stack, producing the value of the referenced field.
+    class ALOAD(NamedTuple):
+        """(NAT, ARRAY<T>) -> (T)
+        Consume a reference to an array and an index, producing the value at that index.
+        FIXME: Or a fault/signal.
         """
 
-        fieldref: str
+    class ASTORE(NamedTuple):
+        """(T, NAT, ARRAY<T>) -> (ARRAY<T>)
+        Consume a value T, storing it at an index in the given array.
+        Produces the updated array as the top of stack.
+        """
 
 
 class Module(NamedTuple):
@@ -115,12 +205,19 @@ class Module(NamedTuple):
         )
 
     @staticmethod
-    def translate(offset: int, i: "Opcode"):
+    def translate(start: int, end: int, i: "Opcode"):
+        # FIXME: Consolidate bounds checks somehow
         match i:
             case Opcode.IF(t):
-                return Opcode.IF(t + offset)
+                d = t + start
+                assert start <= d < end
+                return Opcode.IF(d)
+
             case Opcode.GOTO(t, anywhere=False):
-                return Opcode.GOTO(t + offset)
+                d = t + start
+                assert start <= d < end
+                return Opcode.GOTO(d)
+
             case _:
                 return i
 
@@ -130,7 +227,7 @@ class Module(NamedTuple):
         # How to consume capabilities?
 
         try:
-            sig = FunctionSignature.parse(name)
+            sig = FunctionRef.parse(name)
             assert sig.name
         except:
             raise ValueError("Illegal name provided")
@@ -138,9 +235,9 @@ class Module(NamedTuple):
         start = len(self.opcodes)
         self.functions[name] = start
         for op in opcodes:
-            self.opcodes.append(self.translate(start, op))
+            self.opcodes.append(self.translate(start, start + len(opcodes), op))
         return name
 
-    def define_struct(self, name, signature):
+    def define_type(self, name, signature):
         # FIXME: What in TARNATION is this going to do
         pass
diff --git a/projects/shogoth/test/python/shogoth/vm/test_bootstrap.py b/projects/shogoth/test/python/shogoth/vm/test_bootstrap.py
index c10c512..262196c 100644
--- a/projects/shogoth/test/python/shogoth/vm/test_bootstrap.py
+++ b/projects/shogoth/test/python/shogoth/vm/test_bootstrap.py
@@ -11,7 +11,7 @@ from .fixtures import * # noqa
     [[True], [False]],
 ])
 def test_not(vm, stack, ret):
-    assert vm.run([Opcode.CALL(NOT)], stack = stack) == ret
+    assert vm.run([Opcode.CALLS(NOT)], stack = stack) == ret
 
 
 @pytest.mark.parametrize('stack,ret', [
@@ -21,7 +21,7 @@ def test_not(vm, stack, ret):
     [[True, True], [True]],
 ])
 def test_or(vm, stack, ret):
-    assert vm.run([Opcode.CALL(OR)], stack = stack) == ret
+    assert vm.run([Opcode.CALLS(OR)], stack = stack) == ret
 
 
 @pytest.mark.parametrize('stack,ret', [
@@ -31,7 +31,7 @@ def test_or(vm, stack, ret):
     [[True, True], [True]],
 ])
 def test_and(vm, stack, ret):
-    assert vm.run([Opcode.CALL(AND)], stack = stack) == ret
+    assert vm.run([Opcode.CALLS(AND)], stack = stack) == ret
 
 
 @pytest.mark.parametrize('stack,ret', [
@@ -41,4 +41,18 @@ def test_and(vm, stack, ret):
     [[True, True], [False]],
 ])
 def test_xor(vm, stack, ret):
-    assert vm.run([Opcode.CALL(XOR)], stack = stack) == ret
+    assert vm.run([Opcode.CALLS(XOR)], stack = stack) == ret
+
+
+@pytest.mark.parametrize('stack,ret', [
+    [[], [FunctionRef.parse(NOT)]]
+])
+def test_funref(vm, stack, ret):
+    assert vm.run([Opcode.FUNREF(NOT), Opcode.RETURN(1)], stack = stack) == ret
+
+
+@pytest.mark.parametrize('stack,ret', [
+    [[], [True]]
+])
+def test_callf(vm, stack, ret):
+    assert vm.run([Opcode.FALSE(), Opcode.FUNREF(NOT), Opcode.CALLF(1)], stack = stack) == ret