Remove unused input from EntryFunc, add docs
This commit is contained in:
parent
b0a3324409
commit
f7bcf8896b
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
49
go/vm/vm.go
49
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")
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user