Remove unused input from EntryFunc, add docs

This commit is contained in:
lash 2023-03-31 22:35:13 +01:00
parent b0a3324409
commit f7bcf8896b
Signed by untrusted user who does not match committer: lash
GPG Key ID: 21D2E7BB88C2A746
6 changed files with 167 additions and 51 deletions

View File

@ -4,9 +4,11 @@ import (
"context" "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) Get(sym string) (string, error)
Render(sym string, values map[string]string) (string, error) Render(sym string, values map[string]string) (string, error)
FuncFor(sym string) (EntryFunc, error) FuncFor(sym string) (EntryFunc, error)

View File

@ -4,23 +4,33 @@ import (
"fmt" "fmt"
) )
// Router contains and parses the routing section of the bytecode for a node.
type Router struct { type Router struct {
selectors []string selectors []string
symbols map[string]string symbols map[string]string
} }
// NewRouter creates a new Router object.
func NewRouter() Router { func NewRouter() Router {
return Router{ return Router{
symbols: make(map[string]string), 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 { func NewStaticRouter(symbol string) Router {
return Router{ return Router{
symbols: map[string]string{"_": symbol}, 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 { func(r *Router) Add(selector string, symbol string) error {
if r.symbols[selector] != "" { if r.symbols[selector] != "" {
return fmt.Errorf("selector %v already set to symbol %v", selector, symbol) 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 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 { func(r *Router) Get(selector string) string {
return r.symbols[selector] return r.symbols[selector]
} }
// Get the statically defined symbol destination.
//
// Returns an empty string if not a static router.
func(r *Router) Default() string { func(r *Router) Default() string {
return r.symbols["_"] 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 { func(r *Router) Next() []byte {
if len(r.selectors) == 0 { if len(r.selectors) == 0 {
return []byte{} return []byte{}
@ -70,6 +93,9 @@ func(r *Router) Next() []byte {
return b 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 { func(r *Router) ToBytes() []byte {
b := []byte{} b := []byte{}
for true { for true {
@ -82,6 +108,9 @@ func(r *Router) ToBytes() []byte {
return b return b
} }
// Restore a Router from bytecode.
//
// FromBytes(ToBytes()) creates an identical object.
func FromBytes(b []byte) Router { func FromBytes(b []byte) Router {
rb := NewRouter() rb := NewRouter()
navigable := true navigable := true

View File

@ -5,19 +5,33 @@ import (
"log" "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 { type State struct {
Flags []byte Flags []byte // Error state
CacheSize uint32 CacheSize uint32 // Total allowed cumulative size of values in cache
CacheUseSize uint32 CacheUseSize uint32 // Currently used bytes by all values in cache
Cache []map[string]string Cache []map[string]string // All loaded cache items
CacheMap map[string]string CacheMap map[string]string // Mapped
ExecPath []string execPath []string // Command symbols stack
Arg *string arg *string // Optional argument. Nil if not set.
sizes map[string]uint16 sizes map[string]uint16 // Size limits for all loaded symbols.
sink *string sink *string //
//sizeIdx uint16 //sizeIdx uint16
} }
// NewState creates a new State object with bitSize number of error condition states.
func NewState(bitSize uint64) State { func NewState(bitSize uint64) State {
if bitSize == 0 { if bitSize == 0 {
panic("bitsize cannot be 0") panic("bitsize cannot be 0")
@ -36,40 +50,61 @@ func NewState(bitSize uint64) State {
return st return st
} }
func(st State) Where() string { // WithCacheSize applies a cumulative cache size limitation for all cached items.
if len(st.ExecPath) == 0 {
return ""
}
l := len(st.ExecPath)
return st.ExecPath[l-1]
}
func(st State) WithCacheSize(cacheSize uint32) State { func(st State) WithCacheSize(cacheSize uint32) State {
st.CacheSize = cacheSize st.CacheSize = cacheSize
return st 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 { 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 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) { func(st *State) PopArg() (string, error) {
if st.Arg == nil { if st.arg == nil {
return "", fmt.Errorf("arg is not set") 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) { func(st *State) Down(input string) {
m := make(map[string]string) m := make(map[string]string)
st.Cache = append(st.Cache, m) st.Cache = append(st.Cache, m)
st.sizes = make(map[string]uint16) st.sizes = make(map[string]uint16)
st.ExecPath = append(st.ExecPath, input) st.execPath = append(st.execPath, input)
st.resetCurrent() 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 { func(st *State) Up() error {
l := len(st.Cache) l := len(st.Cache)
if l == 0 { 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) log.Printf("free frame %v key %v value size %v", l, k, sz)
} }
st.Cache = st.Cache[:l] st.Cache = st.Cache[:l]
st.ExecPath = st.ExecPath[:l] st.execPath = st.execPath[:l]
st.resetCurrent() st.resetCurrent()
return nil return nil
} }
func(st *State) Add(key string, value string, sizeHint uint16) error { // Add adds a cache value under a cache symbol key.
if sizeHint > 0 { //
// 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)) l := uint16(len(value))
if l > sizeHint { if l > sizeLimit {
return fmt.Errorf("value length %v exceeds value size limit %v", l, sizeHint) return fmt.Errorf("value length %v exceeds value size limit %v", l, sizeLimit)
} }
} }
checkFrame := st.frameOf(key) 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) log.Printf("add key %s value size %v", key, sz)
st.Cache[len(st.Cache)-1][key] = value st.Cache[len(st.Cache)-1][key] = value
st.CacheUseSize += sz st.CacheUseSize += sz
st.sizes[key] = sizeHint st.sizes[key] = sizeLimit
return nil 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 { func(st *State) Update(key string, value string) error {
sizeHint := st.sizes[key] sizeLimit := st.sizes[key]
if st.sizes[key] > 0 { if st.sizes[key] > 0 {
l := uint16(len(value)) l := uint16(len(value))
if l > sizeHint { if l > sizeLimit {
return fmt.Errorf("update value length %v exceeds value size limit %v", l, sizeHint) return fmt.Errorf("update value length %v exceeds value size limit %v", l, sizeLimit)
} }
} }
checkFrame := st.frameOf(key) checkFrame := st.frameOf(key)
@ -139,6 +190,11 @@ func(st *State) Update(key string, value string) error {
return nil 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 { func(st *State) Map(key string) error {
m, err := st.Get() m, err := st.Get()
if err != nil { if err != nil {
@ -155,10 +211,12 @@ func(st *State) Map(key string) error {
return nil return nil
} }
// Depth returns the current call stack depth.
func(st *State) Depth() uint8 { func(st *State) Depth() uint8 {
return uint8(len(st.Cache)) 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) { func(st *State) Get() (map[string]string, error) {
if len(st.Cache) == 0 { if len(st.Cache) == 0 {
return nil, fmt.Errorf("get at top frame") 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 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) { func(st *State) Val(key string) (string, error) {
r := st.CacheMap[key] r := st.CacheMap[key]
if len(r) == 0 { if len(r) == 0 {
@ -174,7 +235,7 @@ func(st *State) Val(key string) (string, error) {
return r, nil return r, nil
} }
// Reset flushes all state contents below the top level, and returns to the top level.
func(st *State) Reset() { func(st *State) Reset() {
if len(st.Cache) == 0 { if len(st.Cache) == 0 {
return return
@ -184,6 +245,7 @@ func(st *State) Reset() {
return return
} }
// Check returns true if a key already exists in the cache.
func(st *State) Check(key string) bool { func(st *State) Check(key string) bool {
return st.frameOf(key) == -1 return st.frameOf(key) == -1
} }

View File

@ -4,6 +4,7 @@ import (
"testing" "testing"
) )
// Check creation and testing of state flags
func TestNewStateFlags(t *testing.T) { func TestNewStateFlags(t *testing.T) {
st := NewState(5) st := NewState(5)
if len(st.Flags) != 1 { if len(st.Flags) != 1 {
@ -20,6 +21,7 @@ func TestNewStateFlags(t *testing.T) {
} }
} }
//
func TestNewStateCache(t *testing.T) { func TestNewStateCache(t *testing.T) {
st := NewState(17) st := NewState(17)
if st.CacheSize != 0 { if st.CacheSize != 0 {

View File

@ -11,7 +11,7 @@ import (
"git.defalsify.org/festive/state" "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) { func argFromBytes(input []byte) (string, []byte, error) {
if len(input) == 0 { if len(input) == 0 {
@ -22,7 +22,14 @@ func argFromBytes(input []byte) (string, []byte, error) {
return string(out), input[1+sz:], nil 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 var err error
arg, input, err := argFromBytes(input) 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 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 var err error
for len(instruction) > 0 { for len(instruction) > 0 {
log.Printf("instruction is now %v", instruction) 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 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) head, tail, err := instructionSplit(instruction)
if err != nil { if err != nil {
return st, instruction, err return st, instruction, err
@ -101,7 +114,8 @@ func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context
return st, tail, err 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) head, tail, err := instructionSplit(instruction)
if err != nil { if err != nil {
return st, instruction, err return st, instruction, err
@ -115,7 +129,8 @@ func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx conte
return st, []byte{}, nil 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) head, tail, err := instructionSplit(instruction)
if err != nil { if err != nil {
return st, instruction, err return st, instruction, err
@ -126,7 +141,8 @@ func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx conte
return st, []byte{}, nil 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) head, tail, err := instructionSplit(instruction)
if err != nil { if err != nil {
return st, instruction, err return st, instruction, err
@ -137,7 +153,7 @@ func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx contex
sz := uint16(tail[0]) sz := uint16(tail[0])
tail = tail[1:] tail = tail[1:]
r, err := refresh(head, tail, rs, ctx) r, err := refresh(head, rs, ctx)
if err != nil { if err != nil {
return st, tail, err return st, tail, err
} }
@ -145,12 +161,13 @@ func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx contex
return st, tail, err 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) head, tail, err := instructionSplit(instruction)
if err != nil { if err != nil {
return st, instruction, err return st, instruction, err
} }
r, err := refresh(head, tail, rs, ctx) r, err := refresh(head, rs, ctx)
if err != nil { if err != nil {
return st, tail, err return st, tail, err
} }
@ -158,7 +175,8 @@ func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx cont
return st, tail, nil 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) head, tail, err := instructionSplit(instruction)
if err != nil { if err != nil {
return st, instruction, err return st, instruction, err
@ -167,19 +185,22 @@ func RunMove(instruction []byte, st state.State, rs resource.Fetcher, ctx contex
return st, tail, nil 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() st.Up()
return st, instruction, nil 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) fn, err := rs.FuncFor(key)
if err != nil { if err != nil {
return "", err return "", err
} }
return fn(sym, ctx) return fn(ctx)
} }
// split instruction into symbol and arguments
func instructionSplit(b []byte) (string, []byte, error) { func instructionSplit(b []byte) (string, []byte, error) {
if len(b) == 0 { if len(b) == 0 {
return "", nil, fmt.Errorf("argument is empty") return "", nil, fmt.Errorf("argument is empty")

View File

@ -19,15 +19,15 @@ type TestResource struct {
state *state.State state *state.State
} }
func getOne(input []byte, ctx context.Context) (string, error) { func getOne(ctx context.Context) (string, error) {
return "one", nil return "one", nil
} }
func getTwo(input []byte, ctx context.Context) (string, error) { func getTwo(ctx context.Context) (string, error) {
return "two", nil return "two", nil
} }
func getDyn(input []byte, ctx context.Context) (string, error) { func getDyn(ctx context.Context) (string, error) {
return dynVal, nil 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() return r.state.PopArg()
} }