package state import ( "fmt" "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. // // 8 first flags are reserved. // // TODO factor out cache type State struct { Flags []byte // Error state CacheSize uint32 // Total allowed cumulative size of values (not code) in cache CacheUseSize uint32 // Currently used bytes by all values (not code) in cache Cache []map[string]string // All loaded cache items CacheMap map[string]string // Mapped menuSize uint16 // Max size of menu outputSize uint32 // Max size of output input []byte // Last input code []byte // Pending bytecode to execute execPath []string // Command symbols stack arg *string // Optional argument. Nil if not set. sizes map[string]uint16 // Size limits for all loaded symbols. bitSize uint32 // size of (32-bit capacity) bit flag byte array sink *string sizeIdx uint16 } func toByteSize(bitSize uint32) uint8 { if bitSize == 0 { return 0 } n := bitSize % 8 if n > 0 { bitSize += (8 - n) } return uint8(bitSize / 8) } // Retrieve the state of a state flag func getFlag(bitIndex uint32, bitField []byte) bool { byteIndex := bitIndex / 8 localBitIndex := bitIndex % 8 b := bitField[byteIndex] return (b & (1 << localBitIndex)) > 0 } // NewState creates a new State object with bitSize number of error condition states in ADDITION to the 8 builtin flags. func NewState(bitSize uint32) State { st := State{ CacheSize: 0, CacheUseSize: 0, bitSize: bitSize + 8, } byteSize := toByteSize(bitSize + 8) if byteSize > 0 { st.Flags = make([]byte, byteSize) } else { st.Flags = []byte{} } st.Down("") return st } // SetFlag sets the flag at the given bit field index // // Returns true if bit state was changed. // // Fails if bitindex is out of range. func(st *State) SetFlag(bitIndex uint32) (bool, error) { if bitIndex + 1 > st.bitSize { return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.bitSize) } r := getFlag(bitIndex, st.Flags) if r { return false, nil } byteIndex := bitIndex / 8 localBitIndex := bitIndex % 8 b := st.Flags[byteIndex] st.Flags[byteIndex] = b | (1 << localBitIndex) return true, nil } // ResetFlag resets the flag at the given bit field index. // // Returns true if bit state was changed. // // Fails if bitindex is out of range. func(st *State) ResetFlag(bitIndex uint32) (bool, error) { if bitIndex + 1 > st.bitSize { return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.bitSize) } r := getFlag(bitIndex, st.Flags) if !r { return false, nil } byteIndex := bitIndex / 8 localBitIndex := bitIndex % 8 b := st.Flags[byteIndex] st.Flags[byteIndex] = b & (^(1 << localBitIndex)) return true, nil } // GetFlag returns the state of the flag at the given bit field index. // // Fails if bit field index is out of range. func(st *State) GetFlag(bitIndex uint32) (bool, error) { if bitIndex + 1 > st.bitSize { return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.bitSize) } return getFlag(bitIndex, st.Flags), nil } // FlagBitSize reports the amount of bits available in the bit field index. func(st *State) FlagBitSize() uint32 { return st.bitSize } // FlagBitSize reports the amount of bits available in the bit field index. func(st *State) FlagByteSize() uint8 { return uint8(len(st.Flags)) } // MatchFlag matches the current state of the given flag. // // The flag is specified given its bit index in the bit field. // // If invertMatch is set, a positive result will be returned if the flag is not set. func(st *State) MatchFlag(sig uint32, invertMatch bool) (bool, error) { r, err := st.GetFlag(sig) if err != nil { return false, err } if invertMatch { if !r { return true, nil } } else if r { return true, nil } return false, nil } // GetIndex scans a byte slice in same order as in storage, and returns the index of the first set bit. // // If the given byte slice is too small for the bit field bitsize, the check will terminate at end-of-data without error. func(st *State) GetIndex(flags []byte) bool { var globalIndex uint32 if st.bitSize == 0 { return false } if len(flags) == 0 { return false } var byteIndex uint8 var localIndex uint8 l := uint8(len(flags)) var i uint32 for i = 0; i < st.bitSize; i++ { testVal := flags[byteIndex] & (1 << localIndex) if (testVal & st.Flags[byteIndex]) > 0 { return true } globalIndex += 1 if globalIndex % 8 == 0 { byteIndex += 1 localIndex = 0 if byteIndex > (l - 1) { return false } } else { localIndex += 1 } } return false } // WithCacheSize applies a cumulative cache size limitation for all cached items. func(st State) WithCacheSize(cacheSize uint32) State { st.CacheSize = cacheSize return st } // WithCacheSize applies a cumulative cache size limitation for all cached items. func(st State) WithOutputSize(outputSize uint32) State { st.outputSize = outputSize return st } // Where returns the current active rendering symbol. func(st State) Where() (string, uint16) { if len(st.execPath) == 0 { return "", 0 } l := len(st.execPath) return st.execPath[l-1], st.sizeIdx } // 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.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 { return fmt.Errorf("exit called beyond top frame") } l -= 1 m := st.Cache[l] for k, v := range m { sz := len(v) st.CacheUseSize -= uint32(sz) log.Printf("free frame %v key %v value size %v", l, k, sz) } st.Cache = st.Cache[:l] st.execPath = st.execPath[:l] st.resetCurrent() return nil } // 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 > sizeLimit { return fmt.Errorf("value length %v exceeds value size limit %v", l, sizeLimit) } } checkFrame := st.frameOf(key) if checkFrame > -1 { if checkFrame == len(st.execPath) - 1 { log.Printf("Ignoring load request on frame that has symbol already loaded") return nil } return fmt.Errorf("key %v already defined in frame %v", key, checkFrame) } sz := st.checkCapacity(value) if sz == 0 { return fmt.Errorf("Cache capacity exceeded %v of %v", st.CacheUseSize + sz, st.CacheSize) } log.Printf("add key %s value size %v limit %v", key, sz, sizeLimit) st.Cache[len(st.Cache)-1][key] = value st.CacheUseSize += sz 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 { sizeLimit := st.sizes[key] if st.sizes[key] > 0 { l := uint16(len(value)) if l > sizeLimit { return fmt.Errorf("update value length %v exceeds value size limit %v", l, sizeLimit) } } checkFrame := st.frameOf(key) if checkFrame == -1 { return fmt.Errorf("key %v not defined", key) } r := st.Cache[checkFrame][key] l := uint32(len(r)) st.Cache[checkFrame][key] = "" if st.CacheMap[key] != "" { st.CacheMap[key] = value } st.CacheUseSize -= l sz := st.checkCapacity(value) if sz == 0 { baseUseSize := st.CacheUseSize st.Cache[checkFrame][key] = r st.CacheUseSize += l return fmt.Errorf("Cache capacity exceeded %v of %v", baseUseSize + sz, st.CacheSize) } 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 { return err } l := st.sizes[key] if l == 0 { if st.sink != nil { return fmt.Errorf("sink already set to symbol '%v'", *st.sink) } st.sink = &key } st.CacheMap[key] = m[key] 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") } return st.Cache[len(st.Cache)-1], nil } func(st *State) Sizes() (map[string]uint16, error) { if len(st.Cache) == 0 { return nil, fmt.Errorf("get at top frame") } sizes := make(map[string]uint16) var haveSink bool for k, _ := range st.CacheMap { l, ok := st.sizes[k] if !ok { panic(fmt.Sprintf("missing size for %v", k)) } if l == 0 { if haveSink { panic(fmt.Sprintf("duplicate sink for %v", k)) } haveSink = true } sizes[k] = l } return sizes, nil } func(st *State) SetMenuSize(size uint16) error { st.menuSize = size log.Printf("menu size changed to %v", st.menuSize) return nil } func(st *State) GetMenuSize() uint16 { return st.menuSize } func(st *State) GetOutputSize() uint32 { return st.outputSize } // 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 { return "", fmt.Errorf("key %v not mapped", key) } 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 } st.Cache = st.Cache[:1] st.CacheUseSize = 0 return } // Check returns true if a key already exists in the cache. func(st *State) Check(key string) bool { return st.frameOf(key) == -1 } // Size returns size used by values and menu, and remaining size available func(st *State) Size() (uint32, uint32) { var l int var c uint16 for k, v := range st.CacheMap { l += len(v) c += st.sizes[k] } r := uint32(l) r += uint32(st.menuSize) return r, uint32(c)-r } // Appendcode adds the given bytecode to the end of the existing code. func(st *State) AppendCode(b []byte) error { st.code = append(st.code, b...) log.Printf("code changed to 0x%x", b) return nil } // SetCode replaces the current bytecode with the given bytecode. func(st *State) SetCode(b []byte) { log.Printf("code set to 0x%x", b) st.code = b } // Get the remaning cached bytecode func(st *State) GetCode() ([]byte, error) { b := st.code st.code = []byte{} return b, nil } // GetInput gets the most recent client input. func(st *State) GetInput() ([]byte, error) { if st.input == nil { return nil, fmt.Errorf("no input has been set") } return st.input, nil } // SetInput is used to record the latest client input. func(st *State) SetInput(input []byte) error { // if input == nil { // log.Printf("clearing input") // st.input = nil // return nil // } l := len(input) if l > 255 { return fmt.Errorf("input size %v too large (limit %v)", l, 255) } st.input = input return nil } // return 0-indexed frame number where key is defined. -1 if not defined func(st *State) frameOf(key string) int { for i, m := range st.Cache { for k, _ := range m { if k == key { return i } } } return -1 } // bytes that will be added to cache use size for string // returns 0 if capacity would be exceeded func(st *State) checkCapacity(v string) uint32 { sz := uint32(len(v)) if st.CacheSize == 0 { return sz } if st.CacheUseSize + sz > st.CacheSize { return 0 } return sz } // flush relveant properties for level change func(st *State) resetCurrent() { st.sink = nil st.CacheMap = make(map[string]string) }