Function references; stubs for closures

This commit is contained in:
Reid D. 'arrdem' McKenzie 2022-04-21 00:17:59 -06:00
parent 4396ec6496
commit 0d6d3319d8
5 changed files with 191 additions and 88 deletions

View file

@ -4,71 +4,26 @@ from .keyword import Keyword
from .symbol import Symbol from .symbol import Symbol
from abc import ABC from abc import ABC
from typing import NamedTuple, List, Mapping from typing import NamedTuple, List, Mapping, Any
from uuid import UUID, uuid4 from uuid import UUID, uuid4
class TypeExpr(ABC): class TypeVariable(NamedTuple):
"""A type expression"""
bindings: []
class TypeVariable(TypeExpr):
name: str name: str
id: UUID = uuid4() id: UUID = uuid4()
class PrimitiveExpr(object): class ArrayExpr(NamedTuple):
class Nat(TypeExpr): pass child: Any
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 SumExpr(NamedTuple):
class ArrayExpr(TypeExpr): children: List[Any]
child: TypeExpr
class SumExpr(TypeExpr): class ProductExpr(NamedTuple):
children: List[TypeExpr] children: Mapping[str, Any]
class ProductExpr(TypeExpr):
children: Mapping[str, TypeExpr]
#################################################################################################### ####################################################################################################
@ -79,7 +34,7 @@ class Function(NamedTuple):
"""The type of a function; a subset of its signature.""" """The type of a function; a subset of its signature."""
class FunctionSignature(NamedTuple): class FunctionRef(NamedTuple):
raw: str raw: str
type_params: list type_params: list
name: str name: str

View file

@ -5,7 +5,7 @@ Hopefully no "real" interpreter ever uses this code, since it's obviously replac
""" """
from .isa import Module, Opcode from .isa import Module, Opcode
from shogoth.types import *
BOOTSTRAP = Module() BOOTSTRAP = Module()
@ -52,19 +52,32 @@ XOR = BOOTSTRAP.define_function(
[ [
Opcode.DUP(nargs=2), Opcode.DUP(nargs=2),
# !A && B # !A && B
Opcode.CALL(NOT), Opcode.CALLS(NOT),
Opcode.CALL(AND), Opcode.CALLS(AND),
Opcode.IF(target=6), Opcode.IF(target=6),
Opcode.TRUE(), Opcode.TRUE(),
Opcode.RETURN(1), Opcode.RETURN(1),
# !B && A # !B && A
Opcode.ROT(2), Opcode.ROT(2),
Opcode.CALL(NOT), Opcode.CALLS(NOT),
Opcode.CALL(AND), Opcode.CALLS(AND),
Opcode.IF(target=12), Opcode.IF(target=12),
Opcode.TRUE(), Opcode.TRUE(),
Opcode.RETURN(1), Opcode.RETURN(1),
Opcode.FALSE(), Opcode.FALSE(),
Opcode.RETURN(1), Opcode.RETURN(1),
], ],
) )
TRUE = BOOTSTRAP.define_type(
"true", ProductExpr([]),
)
FALSE = BOOTSTRAP.define_type(
"false", ProductExpr([]),
)
BOOL = BOOTSTRAP.define_type(
"bool", SumExpr([TRUE, FALSE])
)

View file

@ -18,7 +18,7 @@ context (a virtual machine) which DOES have an easily introspected and serialize
from copy import deepcopy from copy import deepcopy
from .isa import FunctionSignature, Opcode from .isa import FunctionRef, Opcode
def rotate(l): def rotate(l):
@ -38,7 +38,7 @@ class Stackframe(object):
def pop(self): def pop(self):
return self.stack.pop(0) return self.stack.pop(0)
def call(self, signature: FunctionSignature, ip): def call(self, signature: FunctionRef, ip):
print(signature) print(signature)
nargs = len(signature.args) nargs = len(signature.args)
args, self.stack = self.stack[:nargs], self.stack[nargs:] args, self.stack = self.stack[:nargs], self.stack[nargs:]
@ -135,9 +135,9 @@ class Interpreter(object):
stack.drop(n) stack.drop(n)
case Opcode.CALL(dest): case Opcode.CALLS(dest):
try: try:
sig = FunctionSignature.parse(dest) sig = FunctionRef.parse(dest)
except: except:
_error("Invalid target") _error("Invalid target")
@ -154,7 +154,7 @@ class Interpreter(object):
_error("Stack size violation") _error("Stack size violation")
if stack.parent: if stack.parent:
sig = FunctionSignature.parse(stack.name) sig = FunctionRef.parse(stack.name)
if (len(sig.ret) != n): if (len(sig.ret) != n):
_error("Signature violation") _error("Signature violation")
@ -169,6 +169,30 @@ class Interpreter(object):
stack.ip = n stack.ip = n
continue 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 _: case _:
raise Exception(f"Unhandled interpreter state {op}") raise Exception(f"Unhandled interpreter state {op}")

View file

@ -3,7 +3,7 @@
from typing import NamedTuple from typing import NamedTuple
from shogoth.types import Function, FunctionSignature from shogoth.types import Function, FunctionRef
class Opcode: class Opcode:
@ -27,6 +27,9 @@ class Opcode:
# not, and, or, xor etc. can all be functions given if. # not, and, or, xor etc. can all be functions given if.
####################################################################################################
# Stack manipulation
####################################################################################################
class DUP(NamedTuple): class DUP(NamedTuple):
"""(A, B, ...) -> (A, B, ...) """(A, B, ...) -> (A, B, ...)
Duplicate the top N items of the stack. Duplicate the top N items of the stack.
@ -48,19 +51,28 @@ class Opcode:
nargs: int = 1 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. Branch to `target` pushing the current point onto the call stack.
The callee will see a stack containg only the provided `nargs`. The callee will see a stack containg only the provided `nargs`.
A subsequent RETURN will return execution to the next point. A subsequent RETURN will return execution to the next point.
Executing a `CALL` pushes the name and module path of the current function. Executing a `CALL` pushes the name and module path of the current function.
.. note::
CALLS is equvalent to `FUNREF; CALLF`
""" """
funref: str funref: str
class RETURN(NamedTuple): class RETURN(NamedTuple):
"""(*) -> () """(... A) -> ()
Return to the source of the last `CALL`. Return to the source of the last `CALL`.
The returnee will see the top `nargs` values of the present stack appended to theirs. The returnee will see the top `nargs` values of the present stack appended to theirs.
All other values on the stack will be discarded. All other values on the stack will be discarded.
@ -82,22 +94,100 @@ class Opcode:
target: int target: int
anywhere: bool = False anywhere: bool = False
class STRUCT(NamedTuple): class FUNREF(NamedTuple):
"""(*) -> (T) """() -> (`FUNREF<... A to ... B>`)
Consume the top N items of the stack, producing a struct of the type `structref`. Construct a reference to a static codepoint.
The name and module path of the current function MUST match the name and module path of `structref`.
""" """
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 nargs: int
class FIELD(NamedTuple): class ALOAD(NamedTuple):
"""(A) -> (B) """(NAT, ARRAY<T>) -> (T)
Consume the struct reference at the top of the stack, producing the value of the referenced field. 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): class Module(NamedTuple):
@ -115,12 +205,19 @@ class Module(NamedTuple):
) )
@staticmethod @staticmethod
def translate(offset: int, i: "Opcode"): def translate(start: int, end: int, i: "Opcode"):
# FIXME: Consolidate bounds checks somehow
match i: match i:
case Opcode.IF(t): 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): case Opcode.GOTO(t, anywhere=False):
return Opcode.GOTO(t + offset) d = t + start
assert start <= d < end
return Opcode.GOTO(d)
case _: case _:
return i return i
@ -130,7 +227,7 @@ class Module(NamedTuple):
# How to consume capabilities? # How to consume capabilities?
try: try:
sig = FunctionSignature.parse(name) sig = FunctionRef.parse(name)
assert sig.name assert sig.name
except: except:
raise ValueError("Illegal name provided") raise ValueError("Illegal name provided")
@ -138,9 +235,9 @@ class Module(NamedTuple):
start = len(self.opcodes) start = len(self.opcodes)
self.functions[name] = start self.functions[name] = start
for op in opcodes: for op in opcodes:
self.opcodes.append(self.translate(start, op)) self.opcodes.append(self.translate(start, start + len(opcodes), op))
return name return name
def define_struct(self, name, signature): def define_type(self, name, signature):
# FIXME: What in TARNATION is this going to do # FIXME: What in TARNATION is this going to do
pass pass

View file

@ -11,7 +11,7 @@ from .fixtures import * # noqa
[[True], [False]], [[True], [False]],
]) ])
def test_not(vm, stack, ret): 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', [ @pytest.mark.parametrize('stack,ret', [
@ -21,7 +21,7 @@ def test_not(vm, stack, ret):
[[True, True], [True]], [[True, True], [True]],
]) ])
def test_or(vm, stack, ret): 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', [ @pytest.mark.parametrize('stack,ret', [
@ -31,7 +31,7 @@ def test_or(vm, stack, ret):
[[True, True], [True]], [[True, True], [True]],
]) ])
def test_and(vm, stack, ret): 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', [ @pytest.mark.parametrize('stack,ret', [
@ -41,4 +41,18 @@ def test_and(vm, stack, ret):
[[True, True], [False]], [[True, True], [False]],
]) ])
def test_xor(vm, stack, ret): 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