From aef6cc088d8f06dd2ad9ccc04363dc6005563192 Mon Sep 17 00:00:00 2001
From: "Reid D. 'arrdem' McKenzie" <me@arrdem.com>
Date: Tue, 31 May 2022 23:54:11 -0600
Subject: [PATCH] Get CLOSUREF/CALLC working

---
 .../shoggoth/src/python/ichor/__main__.py     |  8 ++--
 projects/shoggoth/src/python/ichor/impl.py    | 48 +++++++++++++++++--
 projects/shoggoth/src/python/ichor/isa.py     | 47 +++++++++---------
 projects/shoggoth/src/python/ichor/typing.py  |  5 ++
 .../test/python/ichor/test_bootstrap.py       | 17 ++++++-
 5 files changed, 94 insertions(+), 31 deletions(-)

diff --git a/projects/shoggoth/src/python/ichor/__main__.py b/projects/shoggoth/src/python/ichor/__main__.py
index 8f6c156..b05259f 100644
--- a/projects/shoggoth/src/python/ichor/__main__.py
+++ b/projects/shoggoth/src/python/ichor/__main__.py
@@ -11,10 +11,12 @@ def main():
     vm = Interpreter(BOOTSTRAP)
     ret = vm.run(
         [
-            Opcode.CALLS(XOR3),
-            Opcode.RETURN(1)
+            Opcode.FUNREF(XOR2),
+            Opcode.CLOSUREF(1),
+            Opcode.CALLC(1),
+            Opcode.RETURN(1),
         ],
-        stack = [True, False, False]
+        stack = [True, False]
     )
     print(ret)
 
diff --git a/projects/shoggoth/src/python/ichor/impl.py b/projects/shoggoth/src/python/ichor/impl.py
index 1f15b18..9f49a5c 100644
--- a/projects/shoggoth/src/python/ichor/impl.py
+++ b/projects/shoggoth/src/python/ichor/impl.py
@@ -11,12 +11,11 @@ context (a virtual machine) which DOES have an easily introspected and serialize
 
 import sys
 
-
 assert sys.version_info > (3, 10, 0), "`match` support is required"
 
 from copy import deepcopy
 
-from .isa import FunctionRef, Opcode
+from .isa import FunctionRef, Opcode, Closure
 
 
 def rotate(l):
@@ -183,8 +182,8 @@ class Interpreter(object):
                     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(sig.args):
+                        _error("CALLF target violation; argument count missmatch")
                     if n > len(stack):
                         _error("Stack size violation")
 
@@ -196,6 +195,47 @@ class Interpreter(object):
                     stack = stack.call(sig, ip)
                     continue
 
+                case Opcode.CLOSUREF(n):
+                    sig = stack.pop()
+                    if not isinstance(sig, FunctionRef):
+                        _error("CLOSUREF requires a funref at top of stack")
+                    if not n <= len(sig.args):
+                        _error("CLOSUREF target violation; too many parameters provided")
+                    if n > len(stack):
+                        _error("Stack size violation")
+
+                    c = Closure(
+                        sig,
+                        stack.stack[:n]
+                    )
+                    stack.drop(n)
+                    stack.push(c)
+
+                case Opcode.CALLC(n):
+                    c = stack.pop()
+                    if not isinstance(c, Closure):
+                        _error("CALLC requires a closure at top of stack")
+                    if n + len(c.frag) != len(c.funref.args):
+                        _error("CALLC target vionation; argument count missmatch")
+                    if n > len(stack):
+                        _error("Stack size vionation")
+
+                    # Extract the function signature
+                    sig = c.funref
+
+                    # Push the closure's stack fragment
+                    stack.stack = c.frag + stack.stack
+
+                    # Perform a "normal" funref call
+                    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/shoggoth/src/python/ichor/isa.py b/projects/shoggoth/src/python/ichor/isa.py
index 542b0b1..e101a49 100644
--- a/projects/shoggoth/src/python/ichor/isa.py
+++ b/projects/shoggoth/src/python/ichor/isa.py
@@ -1,26 +1,26 @@
 """The instruction set for Shogoth."""
 
 
-from typing import NamedTuple
+import typing as t
 
-from .typing import FunctionRef
+from .typing import *
 
 
 class Opcode:
     ####################################################################################################
     # Logic
     ####################################################################################################
-    class TRUE(NamedTuple):
+    class TRUE(t.NamedTuple):
         """() -> (bool)
         Push the constant TRUE onto the stack.
         """
 
-    class FALSE(NamedTuple):
+    class FALSE(t.NamedTuple):
         """() -> (bool)
         Push the constant FALSE onto the stack.
         """
 
-    class IF(NamedTuple):
+    class IF(t.NamedTuple):
         """(bool) -> ()
         Branch to another point if the top item of the stack is TRUE.
         Otherwise fall through.
@@ -33,21 +33,21 @@ class Opcode:
     ####################################################################################################
     # Stack manipulation
     ####################################################################################################
-    class DUP(NamedTuple):
+    class DUP(t.NamedTuple):
         """(A, B, ...) -> (A, B, ...)
         Duplicate the top N items of the stack.
         """
 
         nargs: int = 1
 
-    class ROT(NamedTuple):
+    class ROT(t.NamedTuple):
         """(A, B, ... Z) -> (Z, A, B, ...)
         Rotate the top N elements of the stack.
         """
 
         nargs: int = 2
 
-    class DROP(NamedTuple):
+    class DROP(t.NamedTuple):
         """(*) -> ()
         Drop the top N items of the stack.
         """
@@ -57,7 +57,7 @@ class Opcode:
     ####################################################################################################
     # Functional abstraction
     ####################################################################################################
-    class CALLS(NamedTuple):
+    class CALLS(t.NamedTuple):
         """(... A) -> (... B)
         Call [static]
 
@@ -74,7 +74,7 @@ class Opcode:
 
         funref: str
 
-    class RETURN(NamedTuple):
+    class RETURN(t.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.
@@ -87,7 +87,7 @@ class Opcode:
 
         nargs: int
 
-    class GOTO(NamedTuple):
+    class GOTO(t.NamedTuple):
         """() -> ()
         Branch to another point within the same bytecode segment.
         The target MUST be within the same module range as the current function.
@@ -97,14 +97,14 @@ class Opcode:
         target: int
         anywhere: bool = False
 
-    class FUNREF(NamedTuple):
+    class FUNREF(t.NamedTuple):
         """() -> (`FUNREF<... A to ... B>`)
         Construct a reference to a static codepoint.
         """
 
         funref: str
 
-    class CALLF(NamedTuple):
+    class CALLF(t.NamedTuple):
         """(`FUNREF<... A to ... B>`, ... A) -> (... B)
         Call [funref]
 
@@ -117,19 +117,20 @@ class Opcode:
 
         nargs: int = 0
 
-    class CLOSUREF(NamedTuple):
+    class CLOSUREF(t.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.
         """
+        nargs: int = 0
 
-    class CLOSUREC(NamedTuple):
+    class CLOSUREC(t.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):
+    class CALLC(t.NamedTuple):
         """(`CLOSURE<... A to ... B>`, ... A) -> (... B)
         Call [closure]
 
@@ -145,7 +146,7 @@ class Opcode:
     ####################################################################################################
     # Structures
     ####################################################################################################
-    class STRUCT(NamedTuple):
+    class STRUCT(t.NamedTuple):
         """(*) -> (T)
         Consume the top N items of the stack, producing a struct of the type `structref`.
 
@@ -155,14 +156,14 @@ class Opcode:
         structref: str
         nargs: int
 
-    class FLOAD(NamedTuple):
+    class FLOAD(t.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):
+    class FSTORE(t.NamedTuple):
         """(A) -> (B)
         Consume the struct reference at the top of the stack, producing the value of the referenced field.
         """
@@ -172,7 +173,7 @@ class Opcode:
     ####################################################################################################
     # Arrays
     ####################################################################################################
-    class ARRAY(NamedTuple):
+    class ARRAY(t.NamedTuple):
         """(*) -> (ARRAY<Y>)
         Consume the top N items of the stack, producing an array of the type `typeref`.
         """
@@ -180,13 +181,13 @@ class Opcode:
         typeref: str
         nargs: int
 
-    class ALOAD(NamedTuple):
+    class ALOAD(t.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.
         """
 
-    class ASTORE(NamedTuple):
+    class ASTORE(t.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.
@@ -205,7 +206,7 @@ class Opcode:
     ####################################################################################################
 
 
-class Module(NamedTuple):
+class Module(t.NamedTuple):
     opcodes: list = []
     functions: dict = {}
     types: dict = {}
diff --git a/projects/shoggoth/src/python/ichor/typing.py b/projects/shoggoth/src/python/ichor/typing.py
index e3a753c..0ccddf2 100644
--- a/projects/shoggoth/src/python/ichor/typing.py
+++ b/projects/shoggoth/src/python/ichor/typing.py
@@ -75,3 +75,8 @@ class FunctionSignature(t.NamedTuple):
             cls.parse_list(args),
             cls.parse_list(ret)
         )
+
+
+class Closure(t.NamedTuple):
+    funref: FunctionRef
+    frag: t.List[t.Any]
diff --git a/projects/shoggoth/test/python/ichor/test_bootstrap.py b/projects/shoggoth/test/python/ichor/test_bootstrap.py
index b2d7a8e..6d7714b 100644
--- a/projects/shoggoth/test/python/ichor/test_bootstrap.py
+++ b/projects/shoggoth/test/python/ichor/test_bootstrap.py
@@ -3,7 +3,6 @@
 from .fixtures import *  # noqa
 
 from ichor import *
-from ichor.isa import Opcode
 import pytest
 
 
@@ -44,6 +43,7 @@ def test_and(vm, stack, ret):
 def test_xor2(vm, stack, ret):
     assert vm.run([Opcode.CALLS(XOR2), Opcode.RETURN(1)], stack = stack) == ret
 
+
 @pytest.mark.parametrize("stack,ret", [
     [[False, False, False], [False]],
     [[True,  False, False], [True]],
@@ -69,3 +69,18 @@ def test_funref(vm, stack, ret):
 ])
 def test_callf(vm, stack, ret):
     assert vm.run([Opcode.FALSE(), Opcode.FUNREF(NOT1), Opcode.CALLF(1), Opcode.RETURN(1)], stack = stack) == ret
+
+
+@pytest.mark.parametrize("stack,ret", [
+    [[False, False], [False]],
+    [[True, False], [True]],
+    [[False, True], [True]],
+    [[True, True], [False]],
+])
+def test_callc(vm, stack, ret):
+    assert vm.run([
+        Opcode.FUNREF(XOR2),
+        Opcode.CLOSUREF(1),
+        Opcode.CALLC(1),
+        Opcode.RETURN(1),
+    ], stack = stack) == ret