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) -> (`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) -> (`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)
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)
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) -> (ARRAY)
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