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. type State struct { 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 // Sink symbol set for level bitSize uint32 // size of (32-bit capacity) bit flag byte array //sizeIdx uint16 } // NewState creates a new State object with bitSize number of error condition states. func NewState(bitSize uint32) State { if bitSize == 0 { panic("bitsize cannot be 0") } n := bitSize % 8 if n > 0 { bitSize += (8 - n) } st := State{ Flags: make([]byte, bitSize / 8), CacheSize: 0, CacheUseSize: 0, bitSize: bitSize, } 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 :=st.getFlag(bitIndex) 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 :=st.getFlag(bitIndex) 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 st.getFlag(bitIndex), nil } // 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 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 { return "", fmt.Errorf("arg is not set") } 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.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 { 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", key, sz) 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 } // 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 } // Returns size used by values, 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) return r, uint32(c)-r } // return 0-indexed frame number where key is defined. -1 if not defined func(st *State) frameOf(key string) int { log.Printf("--- %s", key) 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) } // Retrieve the state of a state flag func(st *State) getFlag(bitIndex uint32) bool { byteIndex := bitIndex / 8 localBitIndex := bitIndex % 8 b := st.Flags[byteIndex] return (b & (1 << localBitIndex)) > 0 }