commit f7bcf8896b3ee4ca75071300bd1e8563219d862d
parent b0a3324409eee2a8c52093cb397a6f02790f90ef
Author: lash <dev@holbrook.no>
Date: Fri, 31 Mar 2023 22:35:13 +0100
Remove unused input from EntryFunc, add docs
Diffstat:
6 files changed, 167 insertions(+), 51 deletions(-)
diff --git a/go/resource/resource.go b/go/resource/resource.go
@@ -4,9 +4,11 @@ import (
"context"
)
-type EntryFunc func(input []byte, ctx context.Context) (string, error)
+// EntryFunc is a function signature for retrieving value for a key
+type EntryFunc func(ctx context.Context) (string, error)
-type Fetcher interface {
+// Resource implementation are responsible for retrieving values and templates for symbols, and can render templates from value dictionaries.
+type Resource interface {
Get(sym string) (string, error)
Render(sym string, values map[string]string) (string, error)
FuncFor(sym string) (EntryFunc, error)
diff --git a/go/router/router.go b/go/router/router.go
@@ -4,23 +4,33 @@ import (
"fmt"
)
+// Router contains and parses the routing section of the bytecode for a node.
type Router struct {
selectors []string
symbols map[string]string
}
+// NewRouter creates a new Router object.
func NewRouter() Router {
return Router{
symbols: make(map[string]string),
}
}
+// NewStaticRouter creates a new Router object with a single destination.
+//
+// Used for routes that consume input value instead of navigation choices.
func NewStaticRouter(symbol string) Router {
return Router{
symbols: map[string]string{"_": symbol},
}
}
+// Add associates a selector with a destination symbol.
+//
+// Fails if:
+// - selector or symbol value is invalid
+// - selector already exists
func(r *Router) Add(selector string, symbol string) error {
if r.symbols[selector] != "" {
return fmt.Errorf("selector %v already set to symbol %v", selector, symbol)
@@ -41,14 +51,27 @@ func(r *Router) Add(selector string, symbol string) error {
return nil
}
+// Get retrieve symbol for selector.
+//
+// Returns an empty string if selector does not exist.
+//
+// Will always return an empty string if the router is static.
func(r *Router) Get(selector string) string {
return r.symbols[selector]
}
+// Get the statically defined symbol destination.
+//
+// Returns an empty string if not a static router.
func(r *Router) Default() string {
return r.symbols["_"]
}
+// Next removes one selector from the list of registered selectors.
+//
+// It returns it together with it associated value in bytecode form.
+//
+// Returns an empty byte array if no more selectors remain.
func(r *Router) Next() []byte {
if len(r.selectors) == 0 {
return []byte{}
@@ -70,6 +93,9 @@ func(r *Router) Next() []byte {
return b
}
+// ToBytes consume all selectors and values and returns them in sequence in bytecode form.
+//
+// This is identical to concatenating all returned values from non-empty Next() results.
func(r *Router) ToBytes() []byte {
b := []byte{}
for true {
@@ -82,6 +108,9 @@ func(r *Router) ToBytes() []byte {
return b
}
+// Restore a Router from bytecode.
+//
+// FromBytes(ToBytes()) creates an identical object.
func FromBytes(b []byte) Router {
rb := NewRouter()
navigable := true
diff --git a/go/state/state.go b/go/state/state.go
@@ -5,19 +5,33 @@ import (
"log"
)
+// State holds the command stack, error condition of a unique execution session.
+//
+// It also holds cached values for all results of executed symbols.
+//
+// Cached values are linked to the command stack level it which they were loaded. When they go out of scope they are freed.
+//
+// Values must be mapped to a level in order to be available for retrieval and count towards size
+//
+// It can hold a single argument, which is freed once it is read
+//
+// Symbols are loaded with individual size limitations. The limitations apply if a load symbol is updated. Symbols may be added with a 0-value for limits, called a "sink." If mapped, the sink will consume all net remaining size allowance unused by other symbols. Only one sink may be mapped per level.
+//
+// Symbol keys do not count towards cache size limitations.
type State struct {
- Flags []byte
- CacheSize uint32
- CacheUseSize uint32
- Cache []map[string]string
- CacheMap map[string]string
- ExecPath []string
- Arg *string
- sizes map[string]uint16
- sink *string
+ Flags []byte // Error state
+ CacheSize uint32 // Total allowed cumulative size of values in cache
+ CacheUseSize uint32 // Currently used bytes by all values in cache
+ Cache []map[string]string // All loaded cache items
+ CacheMap map[string]string // Mapped
+ execPath []string // Command symbols stack
+ arg *string // Optional argument. Nil if not set.
+ sizes map[string]uint16 // Size limits for all loaded symbols.
+ sink *string //
//sizeIdx uint16
}
+// NewState creates a new State object with bitSize number of error condition states.
func NewState(bitSize uint64) State {
if bitSize == 0 {
panic("bitsize cannot be 0")
@@ -36,40 +50,61 @@ func NewState(bitSize uint64) State {
return st
}
-func(st State) Where() string {
- if len(st.ExecPath) == 0 {
- return ""
- }
- l := len(st.ExecPath)
- return st.ExecPath[l-1]
-}
-
+// WithCacheSize applies a cumulative cache size limitation for all cached items.
func(st State) WithCacheSize(cacheSize uint32) State {
st.CacheSize = cacheSize
return st
}
+// Where returns the current active rendering symbol.
+func(st State) Where() string {
+ if len(st.execPath) == 0 {
+ return ""
+ }
+ l := len(st.execPath)
+ return st.execPath[l-1]
+}
+
+// PutArg adds the optional argument.
+//
+// Fails if arg already set.
func(st *State) PutArg(input string) error {
- st.Arg = &input
+ st.arg = &input
+ if st.arg != nil {
+ return fmt.Errorf("arg already set to %s", *st.arg)
+ }
return nil
}
+// PopArg retrieves the optional argument. Will be freed upon retrieval.
+//
+// Fails if arg not set (or already freed).
func(st *State) PopArg() (string, error) {
- if st.Arg == nil {
+ if st.arg == nil {
return "", fmt.Errorf("arg is not set")
}
- return *st.Arg, nil
+ return *st.arg, nil
}
+// Down adds the given symbol to the command stack.
+//
+// Clears mapping and sink.
func(st *State) Down(input string) {
m := make(map[string]string)
st.Cache = append(st.Cache, m)
st.sizes = make(map[string]uint16)
- st.ExecPath = append(st.ExecPath, input)
+ st.execPath = append(st.execPath, input)
st.resetCurrent()
}
+// Up removes the latest symbol to the command stack, and make the previous symbol current.
+//
+// Frees all symbols and associated values loaded at the previous stack level. Cache capacity is increased by the corresponding amount.
+//
+// Clears mapping and sink.
+//
+// Fails if called at top frame.
func(st *State) Up() error {
l := len(st.Cache)
if l == 0 {
@@ -83,16 +118,24 @@ func(st *State) Up() error {
log.Printf("free frame %v key %v value size %v", l, k, sz)
}
st.Cache = st.Cache[:l]
- st.ExecPath = st.ExecPath[:l]
+ st.execPath = st.execPath[:l]
st.resetCurrent()
return nil
}
-func(st *State) Add(key string, value string, sizeHint uint16) error {
- if sizeHint > 0 {
+// Add adds a cache value under a cache symbol key.
+//
+// Also stores the size limitation of for key for later updates.
+//
+// Fails if:
+// - key already defined
+// - value is longer than size limit
+// - adding value exceeds cumulative cache capacity
+func(st *State) Add(key string, value string, sizeLimit uint16) error {
+ if sizeLimit > 0 {
l := uint16(len(value))
- if l > sizeHint {
- return fmt.Errorf("value length %v exceeds value size limit %v", l, sizeHint)
+ if l > sizeLimit {
+ return fmt.Errorf("value length %v exceeds value size limit %v", l, sizeLimit)
}
}
checkFrame := st.frameOf(key)
@@ -106,16 +149,24 @@ func(st *State) Add(key string, value string, sizeHint uint16) error {
log.Printf("add key %s value size %v", key, sz)
st.Cache[len(st.Cache)-1][key] = value
st.CacheUseSize += sz
- st.sizes[key] = sizeHint
+ st.sizes[key] = sizeLimit
return nil
}
+// Update sets a new value for an existing key.
+//
+// Uses the size limitation from when the key was added.
+//
+// Fails if:
+// - key not defined
+// - value is longer than size limit
+// - replacing value exceeds cumulative cache capacity
func(st *State) Update(key string, value string) error {
- sizeHint := st.sizes[key]
+ sizeLimit := st.sizes[key]
if st.sizes[key] > 0 {
l := uint16(len(value))
- if l > sizeHint {
- return fmt.Errorf("update value length %v exceeds value size limit %v", l, sizeHint)
+ if l > sizeLimit {
+ return fmt.Errorf("update value length %v exceeds value size limit %v", l, sizeLimit)
}
}
checkFrame := st.frameOf(key)
@@ -139,6 +190,11 @@ func(st *State) Update(key string, value string) error {
return nil
}
+// Map marks the given key for retrieval.
+//
+// After this, Val() will return the value for the key, and Size() will include the value size and limitations in its calculations.
+//
+// Only one symbol with no size limitation may be mapped at the current level.
func(st *State) Map(key string) error {
m, err := st.Get()
if err != nil {
@@ -155,10 +211,12 @@ func(st *State) Map(key string) error {
return nil
}
+// Depth returns the current call stack depth.
func(st *State) Depth() uint8 {
return uint8(len(st.Cache))
}
+// Get returns the full key-value mapping for all mapped keys at the current cache level.
func(st *State) Get() (map[string]string, error) {
if len(st.Cache) == 0 {
return nil, fmt.Errorf("get at top frame")
@@ -166,6 +224,9 @@ func(st *State) Get() (map[string]string, error) {
return st.Cache[len(st.Cache)-1], nil
}
+// Val returns value for key
+//
+// Fails if key is not mapped.
func(st *State) Val(key string) (string, error) {
r := st.CacheMap[key]
if len(r) == 0 {
@@ -174,7 +235,7 @@ func(st *State) Val(key string) (string, error) {
return r, nil
}
-
+// Reset flushes all state contents below the top level, and returns to the top level.
func(st *State) Reset() {
if len(st.Cache) == 0 {
return
@@ -184,6 +245,7 @@ func(st *State) Reset() {
return
}
+// Check returns true if a key already exists in the cache.
func(st *State) Check(key string) bool {
return st.frameOf(key) == -1
}
diff --git a/go/state/state_test.go b/go/state/state_test.go
@@ -4,6 +4,7 @@ import (
"testing"
)
+// Check creation and testing of state flags
func TestNewStateFlags(t *testing.T) {
st := NewState(5)
if len(st.Flags) != 1 {
@@ -20,6 +21,7 @@ func TestNewStateFlags(t *testing.T) {
}
}
+//
func TestNewStateCache(t *testing.T) {
st := NewState(17)
if st.CacheSize != 0 {
diff --git a/go/vm/vm.go b/go/vm/vm.go
@@ -11,7 +11,7 @@ import (
"git.defalsify.org/festive/state"
)
-//type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error)
+//type Runner func(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error)
func argFromBytes(input []byte) (string, []byte, error) {
if len(input) == 0 {
@@ -22,7 +22,14 @@ func argFromBytes(input []byte) (string, []byte, error) {
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) {
+// Apply applies input to router bytecode to resolve the node symbol to execute.
+//
+// The execution byte code is initialized with the appropriate MOVE
+//
+// If the router indicates an argument input, the optional argument is set on the state.
+//
+// TODO: the bytecode load is a separate step so Run should be run separately.
+func Apply(input []byte, instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
var err error
arg, input, err := argFromBytes(input)
@@ -52,7 +59,12 @@ func Apply(input []byte, instruction []byte, st state.State, rs resource.Fetcher
return st, instruction, nil
}
-func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+// 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.
+func Run(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
var err error
for len(instruction) > 0 {
log.Printf("instruction is now %v", instruction)
@@ -92,7 +104,8 @@ func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Co
return st, instruction, nil
}
-func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+// RunMap executes the MAP opcode
+func RunMap(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
@@ -101,7 +114,8 @@ func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context
return st, tail, err
}
-func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+// RunMap executes the CATCH opcode
+func RunCatch(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
@@ -115,7 +129,8 @@ func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx conte
return st, []byte{}, nil
}
-func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+// RunMap executes the CROAK opcode
+func RunCroak(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
@@ -126,7 +141,8 @@ func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx conte
return st, []byte{}, nil
}
-func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+// RunLoad executes the LOAD opcode
+func RunLoad(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
@@ -137,7 +153,7 @@ func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx contex
sz := uint16(tail[0])
tail = tail[1:]
- r, err := refresh(head, tail, rs, ctx)
+ r, err := refresh(head, rs, ctx)
if err != nil {
return st, tail, err
}
@@ -145,12 +161,13 @@ func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx contex
return st, tail, err
}
-func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+// RunLoad executes the RELOAD opcode
+func RunReload(instruction []byte, st state.State, rs resource.Resource, 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)
+ r, err := refresh(head, rs, ctx)
if err != nil {
return st, tail, err
}
@@ -158,7 +175,8 @@ func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx cont
return st, tail, nil
}
-func RunMove(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+// RunLoad executes the MOVE opcode
+func RunMove(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
@@ -167,19 +185,22 @@ func RunMove(instruction []byte, st state.State, rs resource.Fetcher, ctx contex
return st, tail, nil
}
-func RunBack(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
+// RunLoad executes the BACK opcode
+func RunBack(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
st.Up()
return st, instruction, nil
}
-func refresh(key string, sym []byte, rs resource.Fetcher, ctx context.Context) (string, error) {
+// retrieve data for key
+func refresh(key string, rs resource.Resource, ctx context.Context) (string, error) {
fn, err := rs.FuncFor(key)
if err != nil {
return "", err
}
- return fn(sym, ctx)
+ return fn(ctx)
}
+// split instruction into symbol and arguments
func instructionSplit(b []byte) (string, []byte, error) {
if len(b) == 0 {
return "", nil, fmt.Errorf("argument is empty")
diff --git a/go/vm/vm_test.go b/go/vm/vm_test.go
@@ -19,15 +19,15 @@ type TestResource struct {
state *state.State
}
-func getOne(input []byte, ctx context.Context) (string, error) {
+func getOne(ctx context.Context) (string, error) {
return "one", nil
}
-func getTwo(input []byte, ctx context.Context) (string, error) {
+func getTwo(ctx context.Context) (string, error) {
return "two", nil
}
-func getDyn(input []byte, ctx context.Context) (string, error) {
+func getDyn(ctx context.Context) (string, error) {
return dynVal, nil
}
@@ -36,7 +36,7 @@ type TestStatefulResolver struct {
}
-func (r *TestResource) getEachArg(input []byte, ctx context.Context) (string, error) {
+func (r *TestResource) getEachArg(ctx context.Context) (string, error) {
return r.state.PopArg()
}