vise/go/vm/vm.go
2023-03-31 19:40:54 +01:00

195 lines
5.0 KiB
Go

package vm
import (
"encoding/binary"
"context"
"fmt"
"log"
"git.defalsify.org/festive/resource"
"git.defalsify.org/festive/router"
"git.defalsify.org/festive/state"
)
type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error)
func argFromBytes(input []byte) (string, []byte, error) {
if len(input) == 0 {
return "", input, fmt.Errorf("zero length input")
}
sz := input[0]
out := input[1:1+sz]
return string(out), input[1+sz:], nil
}
func Apply(input []byte, instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
var err error
arg, input, err := argFromBytes(input)
if err != nil {
return st, input, err
}
rt := router.FromBytes(input)
sym := rt.Get(arg)
if sym == "" {
sym = rt.Default()
st.PutArg(arg)
}
if sym == "" {
instruction = NewLine([]byte{}, MOVE, []string{"_catch"}, nil, nil)
} else {
new_instruction := NewLine([]byte{}, MOVE, []string{sym}, nil, nil)
instruction = append(new_instruction, instruction...)
}
st, instruction, err = Run(instruction, st, rs, ctx)
if err != nil {
return st, instruction, err
}
return st, instruction, nil
}
func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
var err error
for len(instruction) > 0 {
log.Printf("instruction is now %v", instruction)
op := binary.BigEndian.Uint16(instruction[:2])
if op > _MAX {
return st, instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX)
}
switch op {
case CATCH:
st, instruction, err = RunCatch(instruction[2:], st, rs, ctx)
break
case CROAK:
st, instruction, err = RunCroak(instruction[2:], st, rs, ctx)
break
case LOAD:
st, instruction, err = RunLoad(instruction[2:], st, rs, ctx)
break
case RELOAD:
st, instruction, err = RunReload(instruction[2:], st, rs, ctx)
break
case MAP:
st, instruction, err = RunMap(instruction[2:], st, rs, ctx)
break
case SINK:
st, instruction, err = RunSink(instruction[2:], st, rs, ctx)
break
case MOVE:
st, instruction, err = RunMove(instruction[2:], st, rs, ctx)
break
default:
err = fmt.Errorf("Unhandled state: %v", op)
}
if err != nil {
return st, instruction, err
}
}
return st, instruction, nil
}
func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
}
st.Map(head)
return st, tail, nil
}
func RunSink(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
return st, nil, nil
}
func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
}
r, err := rs.Get(head)
if err != nil {
return st, instruction, err
}
_ = tail
st.Add(head, r, uint32(len(r)))
return st, []byte{}, nil
}
func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
}
_ = head
_ = tail
st.Reset()
return st, []byte{}, nil
}
func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
}
if !st.Check(head) {
return st, instruction, fmt.Errorf("key %v already loaded", head)
}
sz := uint32(tail[0])
tail = tail[1:]
r, err := refresh(head, tail, rs, ctx)
if err != nil {
return st, tail, err
}
st.Add(head, r, sz)
return st, tail, nil
}
func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
}
r, err := refresh(head, tail, rs, ctx)
if err != nil {
return st, tail, err
}
st.Update(head, r)
return st, tail, nil
}
func RunMove(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
}
st.Down(head)
return st, tail, nil
}
func refresh(key string, sym []byte, rs resource.Fetcher, ctx context.Context) (string, error) {
fn, err := rs.FuncFor(key)
if err != nil {
return "", err
}
return fn(sym, ctx)
}
func instructionSplit(b []byte) (string, []byte, error) {
if len(b) == 0 {
return "", nil, fmt.Errorf("argument is empty")
}
sz := uint8(b[0])
if sz == 0 {
return "", nil, fmt.Errorf("zero-length argument")
}
tailSz := uint8(len(b))
if tailSz < sz {
return "", nil, fmt.Errorf("corrupt instruction, len %v less than symbol length: %v", tailSz, sz)
}
r := string(b[1:1+sz])
return r, b[1+sz:], nil
}