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"
)
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)

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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")

View File

@ -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()
}