Function references; stubs for closures
This commit is contained in:
parent
d82a662b39
commit
352f7b8184
5 changed files with 191 additions and 88 deletions
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
)
|
||||
|
|
|
@ -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}")
|
||||
|
||||
|
|
|
@ -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, ... 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
|
||||
|
||||
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>) -> (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<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):
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue