vise/go/vm/vm.go

232 lines
6.0 KiB
Go
Raw Normal View History

2023-03-31 11:52:04 +02:00
package vm
import (
"encoding/binary"
"context"
"fmt"
2023-03-31 16:24:29 +02:00
"log"
2023-03-31 11:52:04 +02:00
"git.defalsify.org/festive/resource"
"git.defalsify.org/festive/state"
2023-03-31 11:52:04 +02:00
)
//type Runner func(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error)
2023-03-31 11:52:04 +02:00
2023-03-31 20:24:30 +02:00
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
}
// Run extracts individual op codes and arguments and executes them.
//
// Each step may update the state.
//
// On error, the remaining instructions will be returned. State will not be rolled back.
2023-04-01 15:47:03 +02:00
func Run(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
2023-03-31 16:03:54 +02:00
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 {
2023-04-01 15:47:03 +02:00
return instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX)
2023-03-31 16:03:54 +02:00
}
switch op {
case CATCH:
2023-04-01 15:47:03 +02:00
instruction, err = RunCatch(instruction[2:], st, rs, ctx)
2023-03-31 16:03:54 +02:00
case CROAK:
2023-04-01 15:47:03 +02:00
instruction, err = RunCroak(instruction[2:], st, rs, ctx)
2023-03-31 16:03:54 +02:00
case LOAD:
2023-04-01 15:47:03 +02:00
instruction, err = RunLoad(instruction[2:], st, rs, ctx)
2023-03-31 16:03:54 +02:00
case RELOAD:
2023-04-01 15:47:03 +02:00
instruction, err = RunReload(instruction[2:], st, rs, ctx)
2023-03-31 16:03:54 +02:00
case MAP:
2023-04-01 15:47:03 +02:00
instruction, err = RunMap(instruction[2:], st, rs, ctx)
2023-03-31 20:24:30 +02:00
case MOVE:
2023-04-01 15:47:03 +02:00
instruction, err = RunMove(instruction[2:], st, rs, ctx)
2023-03-31 21:05:57 +02:00
case BACK:
2023-04-01 15:47:03 +02:00
instruction, err = RunBack(instruction[2:], st, rs, ctx)
case INCMP:
instruction, err = RunIncmp(instruction[2:], st, rs, ctx)
2023-04-01 22:25:20 +02:00
case HALT:
return RunHalt(instruction[2:], st, rs, ctx)
2023-03-31 16:03:54 +02:00
default:
err = fmt.Errorf("Unhandled state: %v", op)
}
if err != nil {
2023-04-01 15:47:03 +02:00
return instruction, err
2023-03-31 16:03:54 +02:00
}
2023-03-31 11:52:04 +02:00
}
2023-04-01 15:47:03 +02:00
return instruction, nil
2023-03-31 11:52:04 +02:00
}
// RunMap executes the MAP opcode
2023-04-01 15:47:03 +02:00
func RunMap(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
2023-03-31 11:59:55 +02:00
head, tail, err := instructionSplit(instruction)
if err != nil {
2023-04-01 15:47:03 +02:00
return instruction, err
2023-03-31 11:59:55 +02:00
}
2023-03-31 22:08:06 +02:00
err = st.Map(head)
2023-04-01 15:47:03 +02:00
return tail, err
2023-03-31 11:59:55 +02:00
}
// RunMap executes the CATCH opcode
2023-04-01 15:47:03 +02:00
func RunCatch(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
2023-03-31 15:04:08 +02:00
head, tail, err := instructionSplit(instruction)
if err != nil {
2023-04-01 15:47:03 +02:00
return instruction, err
2023-03-31 15:04:08 +02:00
}
2023-04-01 10:03:03 +02:00
bitFieldSize := tail[0]
bitField := tail[1:1+bitFieldSize]
tail = tail[1+bitFieldSize:]
matchMode := tail[0] // matchmode 1 is match NOT set bit
tail = tail[1:]
match := false
if matchMode > 0 {
if !st.GetIndex(bitField) {
match = true
}
} else if st.GetIndex(bitField) {
match = true
}
if match {
2023-04-01 10:03:03 +02:00
log.Printf("catch at flag %v, moving to %v", bitField, head)
st.Down(head)
tail = []byte{}
}
2023-04-01 15:47:03 +02:00
return tail, nil
2023-03-31 11:59:55 +02:00
}
2023-03-31 11:52:04 +02:00
// RunMap executes the CROAK opcode
2023-04-01 15:47:03 +02:00
func RunCroak(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
2023-03-31 15:04:08 +02:00
head, tail, err := instructionSplit(instruction)
if err != nil {
2023-04-01 15:47:03 +02:00
return instruction, err
2023-03-31 15:04:08 +02:00
}
_ = head
2023-03-31 20:24:30 +02:00
_ = tail
2023-03-31 15:04:08 +02:00
st.Reset()
2023-04-01 15:47:03 +02:00
return []byte{}, nil
2023-03-31 11:52:04 +02:00
}
// RunLoad executes the LOAD opcode
2023-04-01 15:47:03 +02:00
func RunLoad(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
2023-03-31 14:24:14 +02:00
head, tail, err := instructionSplit(instruction)
if err != nil {
2023-04-01 15:47:03 +02:00
return instruction, err
2023-03-31 14:24:14 +02:00
}
2023-03-31 15:04:08 +02:00
if !st.Check(head) {
2023-04-01 15:47:03 +02:00
return instruction, fmt.Errorf("key %v already loaded", head)
2023-03-31 15:04:08 +02:00
}
2023-03-31 21:23:45 +02:00
sz := uint16(tail[0])
2023-03-31 16:03:54 +02:00
tail = tail[1:]
r, err := refresh(head, rs, ctx)
2023-03-31 14:24:14 +02:00
if err != nil {
2023-04-01 15:47:03 +02:00
return tail, err
2023-03-31 14:24:14 +02:00
}
2023-03-31 21:23:45 +02:00
err = st.Add(head, r, sz)
2023-04-01 15:47:03 +02:00
return tail, err
2023-03-31 16:03:54 +02:00
}
// RunLoad executes the RELOAD opcode
2023-04-01 15:47:03 +02:00
func RunReload(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
2023-03-31 16:03:54 +02:00
head, tail, err := instructionSplit(instruction)
2023-03-31 14:24:14 +02:00
if err != nil {
2023-04-01 15:47:03 +02:00
return instruction, err
2023-03-31 14:24:14 +02:00
}
r, err := refresh(head, rs, ctx)
2023-03-31 16:03:54 +02:00
if err != nil {
2023-04-01 15:47:03 +02:00
return tail, err
2023-03-31 16:03:54 +02:00
}
st.Update(head, r)
2023-04-01 15:47:03 +02:00
return tail, nil
2023-03-31 16:03:54 +02:00
}
// RunLoad executes the MOVE opcode
2023-04-01 15:47:03 +02:00
func RunMove(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
2023-03-31 20:24:30 +02:00
head, tail, err := instructionSplit(instruction)
if err != nil {
2023-04-01 15:47:03 +02:00
return instruction, err
2023-03-31 20:24:30 +02:00
}
st.Down(head)
2023-04-01 15:47:03 +02:00
return tail, nil
2023-03-31 16:03:54 +02:00
}
// RunLoad executes the BACK opcode
2023-04-01 15:47:03 +02:00
func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
2023-03-31 21:05:57 +02:00
st.Up()
2023-04-01 15:47:03 +02:00
return instruction, nil
2023-03-31 21:05:57 +02:00
}
// RunIncmp executes the INCMP opcode
func RunIncmp(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return instruction, err
}
2023-04-02 00:56:02 +02:00
sym, tail, err := instructionSplit(tail)
if err != nil {
return instruction, err
}
v, err := st.GetFlag(state.FLAG_INMATCH)
if err != nil {
return tail, err
}
if v {
2023-04-02 00:56:02 +02:00
return tail, nil
}
input, err := st.GetInput()
if err != nil {
return tail, err
}
log.Printf("checking input %v %v", input, head)
if head == string(input) {
log.Printf("input match for '%s'", input)
_, err = st.SetFlag(state.FLAG_INMATCH)
2023-04-02 00:56:02 +02:00
st.Down(sym)
}
return tail, err
}
// RunHalt executes the HALT opcode
func RunHalt(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
log.Printf("found HALT, stopping")
_, err := st.ResetFlag(state.FLAG_INMATCH)
return instruction, err
}
// retrieve data for key
func refresh(key string, rs resource.Resource, ctx context.Context) (string, error) {
2023-03-31 16:03:54 +02:00
fn, err := rs.FuncFor(key)
if err != nil {
return "", err
}
2023-04-01 15:47:03 +02:00
if fn == nil {
return "", fmt.Errorf("no retrieve function for external symbol %v", key)
}
return fn(ctx)
2023-03-31 11:52:04 +02:00
}
// split instruction into symbol and arguments
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
}