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