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 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

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 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])
)

View file

@ -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}")

View file

@ -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

View file

@ -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