2023-03-31 11:52:04 +02:00
|
|
|
package vm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"context"
|
2023-03-31 16:24:29 +02:00
|
|
|
"log"
|
2023-03-31 11:52:04 +02:00
|
|
|
|
|
|
|
"git.defalsify.org/festive/state"
|
|
|
|
"git.defalsify.org/festive/resource"
|
|
|
|
)
|
|
|
|
|
2023-03-31 16:03:54 +02:00
|
|
|
type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error)
|
2023-03-31 11:52:04 +02:00
|
|
|
|
2023-03-31 18:27:10 +02:00
|
|
|
func Apply(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
|
|
|
|
var err error
|
|
|
|
st, instruction, err = Run(instruction, st, rs, ctx)
|
|
|
|
if err != nil {
|
|
|
|
return st, instruction, err
|
|
|
|
}
|
|
|
|
return st, instruction, nil
|
|
|
|
}
|
|
|
|
|
2023-03-31 16:03:54 +02:00
|
|
|
func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
|
|
|
|
var err error
|
|
|
|
for len(instruction) > 0 {
|
2023-03-31 16:24:29 +02:00
|
|
|
log.Printf("instruction is now %v", instruction)
|
2023-03-31 16:03:54 +02:00
|
|
|
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)
|
2023-03-31 16:24:29 +02:00
|
|
|
break
|
2023-03-31 16:03:54 +02:00
|
|
|
case CROAK:
|
|
|
|
st, instruction, err = RunCroak(instruction[2:], st, rs, ctx)
|
2023-03-31 16:24:29 +02:00
|
|
|
break
|
2023-03-31 16:03:54 +02:00
|
|
|
case LOAD:
|
|
|
|
st, instruction, err = RunLoad(instruction[2:], st, rs, ctx)
|
2023-03-31 16:24:29 +02:00
|
|
|
break
|
2023-03-31 16:03:54 +02:00
|
|
|
case RELOAD:
|
|
|
|
st, instruction, err = RunReload(instruction[2:], st, rs, ctx)
|
2023-03-31 16:24:29 +02:00
|
|
|
break
|
2023-03-31 16:03:54 +02:00
|
|
|
case MAP:
|
|
|
|
st, instruction, err = RunMap(instruction[2:], st, rs, ctx)
|
2023-03-31 16:24:29 +02:00
|
|
|
break
|
2023-03-31 16:03:54 +02:00
|
|
|
case SINK:
|
|
|
|
st, instruction, err = RunSink(instruction[2:], st, rs, ctx)
|
2023-03-31 16:24:29 +02:00
|
|
|
break
|
2023-03-31 16:03:54 +02:00
|
|
|
default:
|
|
|
|
err = fmt.Errorf("Unhandled state: %v", op)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return st, instruction, err
|
|
|
|
}
|
2023-03-31 11:52:04 +02:00
|
|
|
}
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, instruction, nil
|
2023-03-31 11:52:04 +02:00
|
|
|
}
|
|
|
|
|
2023-03-31 16:03:54 +02:00
|
|
|
func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
|
2023-03-31 11:59:55 +02:00
|
|
|
head, tail, err := instructionSplit(instruction)
|
|
|
|
if err != nil {
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, instruction, err
|
2023-03-31 11:59:55 +02:00
|
|
|
}
|
2023-03-31 14:24:14 +02:00
|
|
|
st.Map(head)
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, tail, nil
|
2023-03-31 11:52:04 +02:00
|
|
|
}
|
|
|
|
|
2023-03-31 16:03:54 +02:00
|
|
|
func RunSink(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
|
|
|
|
return st, nil, nil
|
2023-03-31 11:59:55 +02:00
|
|
|
}
|
|
|
|
|
2023-03-31 16:03:54 +02:00
|
|
|
func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
|
2023-03-31 15:04:08 +02:00
|
|
|
head, tail, err := instructionSplit(instruction)
|
|
|
|
if err != nil {
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, instruction, err
|
2023-03-31 15:04:08 +02:00
|
|
|
}
|
|
|
|
r, err := rs.Get(head)
|
|
|
|
if err != nil {
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, instruction, err
|
2023-03-31 15:04:08 +02:00
|
|
|
}
|
2023-03-31 16:03:54 +02:00
|
|
|
st.Add(head, r, uint32(len(r)))
|
|
|
|
return st, tail, nil
|
2023-03-31 11:59:55 +02:00
|
|
|
}
|
2023-03-31 11:52:04 +02:00
|
|
|
|
2023-03-31 16:03:54 +02:00
|
|
|
func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
|
2023-03-31 15:04:08 +02:00
|
|
|
head, tail, err := instructionSplit(instruction)
|
|
|
|
if err != nil {
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, instruction, err
|
2023-03-31 15:04:08 +02:00
|
|
|
}
|
|
|
|
_ = head
|
|
|
|
st.Reset()
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, tail, nil
|
2023-03-31 11:52:04 +02:00
|
|
|
}
|
|
|
|
|
2023-03-31 16:03:54 +02:00
|
|
|
func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
|
2023-03-31 14:24:14 +02:00
|
|
|
head, tail, err := instructionSplit(instruction)
|
|
|
|
if err != nil {
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, instruction, err
|
2023-03-31 14:24:14 +02:00
|
|
|
}
|
2023-03-31 15:04:08 +02:00
|
|
|
if !st.Check(head) {
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, instruction, fmt.Errorf("key %v already loaded", head)
|
2023-03-31 15:04:08 +02:00
|
|
|
}
|
2023-03-31 16:03:54 +02:00
|
|
|
sz := uint32(tail[0])
|
|
|
|
tail = tail[1:]
|
|
|
|
|
|
|
|
r, err := refresh(head, tail, rs, ctx)
|
2023-03-31 14:24:14 +02:00
|
|
|
if err != nil {
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, tail, err
|
2023-03-31 14:24:14 +02:00
|
|
|
}
|
2023-03-31 16:03:54 +02:00
|
|
|
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)
|
2023-03-31 14:24:14 +02:00
|
|
|
if err != nil {
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, instruction, err
|
2023-03-31 14:24:14 +02:00
|
|
|
}
|
2023-03-31 16:03:54 +02:00
|
|
|
r, err := refresh(head, tail, rs, ctx)
|
|
|
|
if err != nil {
|
|
|
|
return st, tail, err
|
|
|
|
}
|
2023-03-31 17:12:14 +02:00
|
|
|
st.Update(head, r)
|
2023-03-31 16:03:54 +02:00
|
|
|
return st, tail, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func RunMove(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
|
|
|
|
return st, nil, 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)
|
2023-03-31 11:52:04 +02:00
|
|
|
}
|
|
|
|
|
2023-03-31 16:03:54 +02:00
|
|
|
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
|
2023-03-31 11:52:04 +02:00
|
|
|
}
|