commit 4a9143932032033294d8223f1c43c1b99ea1422f
parent 397985f1ae421a7facd0e83b4261eaa5c91c1729
Author: lash <dev@holbrook.no>
Date: Thu, 6 Apr 2023 15:21:26 +0100
WIP Add size checker to resource render
Diffstat:
9 files changed, 279 insertions(+), 13 deletions(-)
diff --git a/go/engine/engine.go b/go/engine/engine.go
@@ -16,7 +16,7 @@ var (
inputRegexStr = "^[a-zA-Z0-9].*$"
inputRegex = regexp.MustCompile(inputRegexStr)
)
-//
+
//type Config struct {
// FlagCount uint32
// CacheSize uint32
diff --git a/go/resource/fs.go b/go/resource/fs.go
@@ -23,17 +23,13 @@ func NewFsResource(path string) (FsResource) {
}
}
-func(fs FsResource) GetTemplate(sym string) (string, error) {
+func(fs FsResource) GetTemplate(sym string, sizer *Sizer) (string, error) {
fp := path.Join(fs.Path, sym)
r, err := ioutil.ReadFile(fp)
s := string(r)
return strings.TrimSpace(s), err
}
-func(fs FsResource) RenderTemplate(sym string, values map[string]string) (string, error) {
- return DefaultRenderTemplate(&fs, sym, values)
-}
-
func(fs FsResource) GetCode(sym string) ([]byte, error) {
fb := sym + ".bin"
fp := path.Join(fs.Path, fb)
diff --git a/go/resource/render.go b/go/resource/render.go
@@ -5,9 +5,10 @@ import (
"text/template"
)
+
// DefaultRenderTemplate is an adapter to implement the builtin golang text template renderer as resource.RenderTemplate.
func DefaultRenderTemplate(r Resource, sym string, values map[string]string) (string, error) {
- v, err := r.GetTemplate(sym)
+ v, err := r.GetTemplate(sym, nil)
if err != nil {
return "", err
}
@@ -23,4 +24,3 @@ func DefaultRenderTemplate(r Resource, sym string, values map[string]string) (st
}
return b.String(), err
}
-
diff --git a/go/resource/resource.go b/go/resource/resource.go
@@ -8,16 +8,20 @@ import (
// EntryFunc is a function signature for retrieving value for a key
type EntryFunc func(ctx context.Context) (string, error)
+type CodeFunc func(sym string) ([]byte, error)
+type TemplateFunc func(sym string, sizer *Sizer) (string, error)
+type FuncForFunc func(sym string) (EntryFunc, error)
// Resource implementation are responsible for retrieving values and templates for symbols, and can render templates from value dictionaries.
type Resource interface {
- GetTemplate(sym string) (string, error) // Get the template for a given symbol.
+ GetTemplate(sym string, sizer *Sizer) (string, error) // Get the template for a given symbol.
GetCode(sym string) ([]byte, error) // Get the bytecode for the given symbol.
PutMenu(string, string) error // Add a menu item.
ShiftMenu() (string, string, error) // Remove and return the first menu item in list.
SetMenuBrowse(string, string, bool) error // Set menu browser display details.
RenderTemplate(sym string, values map[string]string) (string, error) // Render the given data map using the template of the symbol.
RenderMenu() (string, error) // Render the current state of menu
+ Render(sym string, values map[string]string, sizer *Sizer) (string, error) // Render full output.
FuncFor(sym string) (EntryFunc, error) // Resolve symbol content point for.
}
@@ -25,6 +29,28 @@ type MenuResource struct {
menu [][2]string
next [2]string
prev [2]string
+ codeFunc CodeFunc
+ templateFunc TemplateFunc
+ funcFunc FuncForFunc
+}
+
+func NewMenuResource() *MenuResource {
+ return &MenuResource{}
+}
+
+func(m *MenuResource) WithCodeGetter(codeGetter CodeFunc) *MenuResource {
+ m.codeFunc = codeGetter
+ return m
+}
+
+func(m *MenuResource) WithEntryFuncGetter(entryFuncGetter FuncForFunc) *MenuResource {
+ m.funcFunc = entryFuncGetter
+ return m
+}
+
+func(m *MenuResource) WithTemplateGetter(templateGetter TemplateFunc) *MenuResource {
+ m.templateFunc = templateGetter
+ return m
}
// SetMenuBrowse defines the how pagination menu options should be displayed.
@@ -79,3 +105,44 @@ func(m *MenuResource) RenderMenu() (string, error) {
}
return r, nil
}
+
+func(m *MenuResource) RenderTemplate(sym string, values map[string]string) (string, error) {
+ return DefaultRenderTemplate(m, sym, values)
+}
+
+func(m *MenuResource) FuncFor(sym string) (EntryFunc, error) {
+ return m.funcFunc(sym)
+}
+
+func(m *MenuResource) GetCode(sym string) ([]byte, error) {
+ return m.codeFunc(sym)
+}
+
+func(m *MenuResource) GetTemplate(sym string, sizer *Sizer) (string, error) {
+ return m.templateFunc(sym, sizer)
+}
+
+func(m *MenuResource) Render(sym string, values map[string]string, sizer *Sizer) (string, error) {
+ r := ""
+ s, err := m.RenderTemplate(sym, values)
+ if err != nil {
+ return "", err
+ }
+ log.Printf("rendered %v bytes for template", len(s))
+ r += s
+ l, ok := sizer.Check(r)
+ if !ok {
+ return "", fmt.Errorf("rendered size %v exceeds limits: %v", l, sizer)
+ }
+ s, err = m.RenderMenu()
+ if err != nil {
+ return "", err
+ }
+ log.Printf("rendered %v bytes for menu", len(s))
+ r += s
+ l, ok = sizer.Check(r)
+ if !ok {
+ return "", fmt.Errorf("rendered size %v exceeds limits: %v", l, sizer)
+ }
+ return r, nil
+}
diff --git a/go/resource/resource_test.go b/go/resource/resource_test.go
@@ -0,0 +1,9 @@
+package resource
+
+import (
+ "testing"
+)
+
+func TestRenderLimit(t *testing.T) {
+
+}
diff --git a/go/resource/size.go b/go/resource/size.go
@@ -0,0 +1,53 @@
+package resource
+
+import (
+ "fmt"
+ "log"
+
+ "git.defalsify.org/festive/state"
+)
+
+type Sizer struct {
+ outputSize uint32
+ menuSize uint16
+ memberSizes map[string]uint16
+ totalMemberSize uint32
+ sink string
+}
+
+func SizerFromState(st *state.State) (Sizer, error){
+ sz := Sizer{
+ outputSize: st.GetOutputSize(),
+ menuSize: st.GetMenuSize(),
+ memberSizes: make(map[string]uint16),
+ }
+ sizes, err := st.Sizes()
+ if err != nil {
+ return sz, err
+ }
+ for k, v := range sizes {
+ if v == 0 {
+ sz.sink = k
+ }
+ sz.memberSizes[k] = v
+ sz.totalMemberSize += uint32(v)
+ }
+ return sz, nil
+}
+
+func(szr *Sizer) Check(s string) (uint32, bool) {
+ l := uint32(len(s))
+ if szr.outputSize > 0 && l > szr.outputSize {
+ log.Printf("sizer check fails with length %v: %s", l, szr)
+ return l, false
+ }
+ return l, true
+}
+
+func(szr *Sizer) String() string {
+ var diff uint32
+ if szr.outputSize > 0 {
+ diff = szr.outputSize - szr.totalMemberSize - uint32(szr.menuSize)
+ }
+ return fmt.Sprintf("output: %v, member: %v, menu: %v, diff: %v", szr.outputSize, szr.totalMemberSize, szr.menuSize, diff)
+}
diff --git a/go/resource/size_test.go b/go/resource/size_test.go
@@ -0,0 +1,96 @@
+package resource
+
+import (
+ "fmt"
+ "context"
+ "testing"
+
+ "git.defalsify.org/festive/state"
+)
+
+type TestSizeResource struct {
+ *MenuResource
+}
+
+func getTemplate(sym string, sizer *Sizer) (string, error) {
+ var tpl string
+ switch sym {
+ case "small":
+ tpl = "one {{.foo}} two {{.bar}} three {{.baz}}"
+ case "toobig":
+ tpl = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus in mattis lorem. Aliquam erat volutpat. Ut vitae metus."
+
+ }
+ return tpl, nil
+}
+
+func funcFor(sym string) (EntryFunc, error) {
+ switch sym {
+ case "foo":
+ return getFoo, nil
+ case "bar":
+ return getBar, nil
+ case "baz":
+ return getBaz, nil
+ }
+ return nil, fmt.Errorf("unknown func: %s", sym)
+}
+
+func getFoo(ctx context.Context) (string, error) {
+ return "inky", nil
+}
+
+func getBar(ctx context.Context) (string, error) {
+ return "pinky", nil
+}
+
+func getBaz(ctx context.Context) (string, error) {
+ return "blinky", nil
+}
+
+
+func TestSizeLimit(t *testing.T) {
+ st := state.NewState(0).WithOutputSize(128)
+ mrs := NewMenuResource().WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate)
+ rs := TestSizeResource{
+ mrs,
+ }
+ st.Add("foo", "inky", 4)
+ st.Add("bar", "pinky", 10)
+ st.Add("baz", "blinky", 0)
+ st.Map("foo")
+ st.Map("bar")
+ st.Map("baz")
+ st.SetMenuSize(32)
+ szr, err := SizerFromState(&st)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rs.PutMenu("1", "foo the foo")
+ rs.PutMenu("2", "go to bar")
+
+ tpl, err := rs.GetTemplate("small", &szr)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ vals, err := st.Get()
+ if err != nil {
+ t.Fatal(err)
+ }
+ _ = tpl
+
+ _, err = rs.Render("small", vals, &szr)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rs.PutMenu("1", "foo the foo")
+ rs.PutMenu("2", "go to bar")
+
+ _, err = rs.Render("toobig", vals, &szr)
+ if err == nil {
+ t.Fatalf("expected size exceeded")
+ }
+}
diff --git a/go/state/state.go b/go/state/state.go
@@ -28,13 +28,15 @@ type State struct {
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.
- sink *string // Sink symbol set for level
bitSize uint32 // size of (32-bit capacity) bit flag byte array
+ sink *string
//sizeIdx uint16
}
@@ -195,6 +197,12 @@ func(st State) WithCacheSize(cacheSize uint32) State {
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 {
if len(st.execPath) == 0 {
@@ -268,7 +276,7 @@ func(st *State) Add(key string, value string, sizeLimit uint16) error {
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)
+ 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
@@ -346,6 +354,42 @@ func(st *State) Get() (map[string]string, error) {
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.
@@ -372,7 +416,7 @@ func(st *State) Check(key string) bool {
return st.frameOf(key) == -1
}
-// Size returns size used by values, and remaining size available
+// Size returns size used by values and menu, and remaining size available
func(st *State) Size() (uint32, uint32) {
var l int
var c uint16
@@ -381,6 +425,7 @@ func(st *State) Size() (uint32, uint32) {
c += st.sizes[k]
}
r := uint32(l)
+ r += uint32(st.menuSize)
return r, uint32(c)-r
}
diff --git a/go/vm/runner_test.go b/go/vm/runner_test.go
@@ -34,7 +34,7 @@ type TestStatefulResolver struct {
state *state.State
}
-func (r *TestResource) GetTemplate(sym string) (string, error) {
+func (r *TestResource) GetTemplate(sym string, sizer *resource.Sizer) (string, error) {
switch sym {
case "foo":
return "inky pinky blinky clyde", nil