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