Happy hacking; setting up for structs

This commit is contained in:
Reid D. 'arrdem' McKenzie 2022-04-15 00:31:58 -06:00
parent ef3f3f5c1a
commit 4396ec6496
8 changed files with 200 additions and 137 deletions

View file

@ -3,5 +3,100 @@
from .keyword import Keyword
from .symbol import Symbol
from abc import ABC
from typing import NamedTuple, List, Mapping
__all__ = ["Keyword", "Symbol"]
from uuid import UUID, uuid4
class TypeExpr(ABC):
"""A type expression"""
bindings: []
class TypeVariable(TypeExpr):
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(TypeExpr):
child: TypeExpr
class SumExpr(TypeExpr):
children: List[TypeExpr]
class ProductExpr(TypeExpr):
children: Mapping[str, TypeExpr]
####################################################################################################
####################################################################################################
####################################################################################################
class Function(NamedTuple):
"""The type of a function; a subset of its signature."""
class FunctionSignature(NamedTuple):
raw: str
type_params: list
name: str
args: list
ret: list
@staticmethod
def parse_list(l):
return [e for e in l.split(",") if e]
@classmethod
def parse(cls, raw: str):
vars, name, args, ret = raw.split(";")
return cls(
raw,
cls.parse_list(vars),
name,
cls.parse_list(args),
cls.parse_list(ret)
)

View file

@ -0,0 +1,30 @@
#!/usr/bin/env python3
from typing import NamedTuple
class FunctionSignature(NamedTuple):
raw: str
type_params: list
name: str
args: list
ret: list
@staticmethod
def parse_list(l):
return [e for e in l.split(",") if e]
@classmethod
def parse(cls, raw: str):
vars, name, args, ret = raw.split(";")
return cls(
raw,
cls.parse_list(vars),
name,
cls.parse_list(args),
cls.parse_list(ret)
)
class Function(NamedTuple):
"""The type of a function; a subset of its signature."""

View file

@ -1,2 +1,5 @@
#!/usr/bin/env python3
# noqa
from .isa import * # noqa
from .bootstrap import * # noqa
from .impl import * # noqa

View file

@ -83,10 +83,10 @@ class Interpreter(object):
def __init__(self, bootstrap_module):
self.bootstrap = bootstrap_module
def run(self, opcodes):
def run(self, opcodes, stack=[]):
"""Directly interpret some opcodes in the configured environment."""
stack = Stackframe()
stack = Stackframe(stack=stack)
mod = self.bootstrap.copy()
mod.define_function(";<entry>;;", opcodes)
stack.ip = mod.functions[";<entry>;;"]

View file

@ -3,6 +3,8 @@
from typing import NamedTuple
from shogoth.types import Function, FunctionSignature
class Opcode:
class TRUE(NamedTuple):
@ -51,6 +53,8 @@ class Opcode:
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.
"""
funref: str
@ -60,6 +64,9 @@ class Opcode:
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.
Executing a `RETURN` pops (restores) the name and module path of the current function back to that of the caller.
If the call stack is empty, `RETURN` will exit the interpreter.
"""
@ -68,6 +75,8 @@ class Opcode:
class GOTO(NamedTuple):
"""() -> ()
Branch to another point within the same bytecode segment.
The target MUST be within the same module range as the current function.
Branching does NOT update the name or module of the current function.
"""
target: int
@ -75,11 +84,13 @@ class Opcode:
class STRUCT(NamedTuple):
"""(*) -> (T)
Consume the top N items of the stack, producing a struct.
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`.
"""
nargs: int
structref: str
nargs: int
class FIELD(NamedTuple):
"""(A) -> (B)
@ -89,29 +100,6 @@ class Opcode:
fieldref: str
class FunctionSignature(NamedTuple):
raw: str
type_params: list
name: str
args: list
ret: list
@staticmethod
def parse_list(l):
return [e for e in l.split(",") if e]
@classmethod
def parse(cls, raw: str):
vars, name, args, ret = raw.split(";")
return cls(
raw,
cls.parse_list(vars),
name,
cls.parse_list(args),
cls.parse_list(ret)
)
class Module(NamedTuple):
opcodes: list = []
functions: dict = {}

View file

@ -0,0 +1,10 @@
#!/usr/bin/env python3
import pytest
from shogoth.vm import *
import pytest
@pytest.fixture
def vm():
return Interpreter(BOOTSTRAP)

View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
import pytest
from shogoth.vm import *
from .fixtures import * # noqa
@pytest.mark.parametrize('stack,ret', [
[[True], [False]],
[[True], [False]],
])
def test_not(vm, stack, ret):
assert vm.run([Opcode.CALL(NOT)], stack = stack) == ret
@pytest.mark.parametrize('stack,ret', [
[[False, False], [False]],
[[True, False], [True]],
[[False, True], [True]],
[[True, True], [True]],
])
def test_or(vm, stack, ret):
assert vm.run([Opcode.CALL(OR)], stack = stack) == ret
@pytest.mark.parametrize('stack,ret', [
[[False, False], [False]],
[[True, False], [False]],
[[False, True], [False]],
[[True, True], [True]],
])
def test_and(vm, stack, ret):
assert vm.run([Opcode.CALL(AND)], stack = stack) == ret
@pytest.mark.parametrize('stack,ret', [
[[False, False], [False]],
[[True, False], [True]],
[[False, True], [True]],
[[True, True], [False]],
])
def test_xor(vm, stack, ret):
assert vm.run([Opcode.CALL(XOR)], stack = stack) == ret

View file

@ -5,10 +5,7 @@ Tests coverign the VM interpreter
import pytest
from shogoth.vm import *
@pytest.fixture
def vm():
return Interpreter(BOOTSTRAP)
from .fixtures import * # noqa
def test_true(vm):
@ -67,110 +64,6 @@ def test_drop(vm):
]) == [True]
def test_not(vm):
assert vm.run([
Opcode.TRUE(),
Opcode.CALL(NOT),
Opcode.RETURN(1)
]) == [False]
assert vm.run([
Opcode.FALSE(),
Opcode.CALL(NOT),
Opcode.RETURN(1)
]) == [True]
def test_or(vm):
assert vm.run([
Opcode.FALSE(),
Opcode.FALSE(),
Opcode.CALL(OR),
Opcode.RETURN(1)
]) == [False]
assert vm.run([
Opcode.TRUE(),
Opcode.FALSE(),
Opcode.CALL(OR),
Opcode.RETURN(1)
]) == [True]
assert vm.run([
Opcode.FALSE(),
Opcode.TRUE(),
Opcode.CALL(OR),
Opcode.RETURN(1)
]) == [True]
assert vm.run([
Opcode.TRUE(),
Opcode.TRUE(),
Opcode.CALL(OR),
Opcode.RETURN(1)
]) == [True]
def test_and(vm):
assert vm.run([
Opcode.FALSE(),
Opcode.FALSE(),
Opcode.CALL(AND),
Opcode.RETURN(1)
]) == [False]
assert vm.run([
Opcode.TRUE(),
Opcode.FALSE(),
Opcode.CALL(AND),
Opcode.RETURN(1)
]) == [False]
assert vm.run([
Opcode.FALSE(),
Opcode.TRUE(),
Opcode.CALL(AND),
Opcode.RETURN(1)
]) == [False]
assert vm.run([
Opcode.TRUE(),
Opcode.TRUE(),
Opcode.CALL(AND),
Opcode.RETURN(1)
]) == [True]
def test_xor(vm):
assert vm.run([
Opcode.FALSE(),
Opcode.FALSE(),
Opcode.CALL(XOR),
Opcode.RETURN(1)
]) == [False]
assert vm.run([
Opcode.TRUE(),
Opcode.FALSE(),
Opcode.CALL(XOR),
Opcode.RETURN(1)
]) == [True]
assert vm.run([
Opcode.FALSE(),
Opcode.TRUE(),
Opcode.CALL(XOR),
Opcode.RETURN(1)
]) == [True]
assert vm.run([
Opcode.TRUE(),
Opcode.TRUE(),
Opcode.CALL(XOR),
Opcode.RETURN(1)
]) == [False]
def test_dup_too_many(vm):
with pytest.raises(InterpreterError):
vm.run([Opcode.DUP(1)])