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 .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
|
||||||
|
|
|
@ -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])
|
||||||
|
)
|
||||||
|
|
|
@ -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}")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue