go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

commit e340210d8ff8203c9441611ceea387ce2e2b9b3b
parent 91ee0568cab19f319f507e8b84f28d93fca55187
Author: lash <dev@holbrook.no>
Date:   Wed, 12 Apr 2023 18:04:36 +0100

Add code comment documentation

Diffstat:
Mgo/asm/asm.go | 11+++++++++++
Mgo/asm/menu.go | 9+++++++++
Mgo/cache/cache.go | 11++++-------
Mgo/cache/memory.go | 1+
Mgo/engine/default.go | 2++
Mgo/engine/engine.go | 7+++----
Mgo/engine/loop.go | 18+++++++++++++-----
Mgo/render/menu.go | 24+++++++++++++++---------
Mgo/render/page.go | 63++++++++++++++++++++++++++++++++++++---------------------------
Dgo/render/render.go | 1-
Mgo/render/size.go | 26++++++++++++++++++++------
Mgo/testdata/testdata.go | 10+++++++++-
Mgo/vm/runner.go | 21+++++++++++----------
13 files changed, 134 insertions(+), 70 deletions(-)

diff --git a/go/asm/asm.go b/go/asm/asm.go @@ -17,10 +17,12 @@ import ( ) +// Asm assembles bytecode from the festive assembly mini-language. type Asm struct { Instructions []*Instruction `@@*` } +// Arg holds all parsed argument elements of a single line of assembly code. type Arg struct { Sym *string `(@Sym Whitespace?)?` Size *uint32 `(@Size Whitespace?)?` @@ -214,6 +216,7 @@ func parseOne(op vm.Opcode, instruction *Instruction, w io.Writer) (int, error) return flush(b, w) } +// String implements the String interface. func (a Arg) String() string { s := "[Arg]" if a.Sym != nil { @@ -235,12 +238,14 @@ func (a Arg) String() string { return fmt.Sprintf(s) } +// Instruction represents one full line of assembly code. type Instruction struct { OpCode string `@Ident` OpArg Arg `(Whitespace @@)?` Comment string `Comment? EOL` } +// String implement the String interface. func (i Instruction) String() string { return fmt.Sprintf("%s %s", i.OpCode, i.OpArg) } @@ -303,17 +308,20 @@ func writeSize(w *bytes.Buffer, n uint32) (int, error) { return w.Write(bn[c:]) } +// Batcher handles assembly commands that generates multiple instructions, such as menu navigation commands. type Batcher struct { menuProcessor MenuProcessor inMenu bool } +// NewBatcher creates a new Batcher objcet. func NewBatcher(mp MenuProcessor) Batcher { return Batcher{ menuProcessor: NewMenuProcessor(), } } +// MenuExit generates the instructions for the batch and writes them to the given io.Writer. func(bt *Batcher) MenuExit(w io.Writer) (int, error) { if !bt.inMenu { return 0, nil @@ -323,6 +331,7 @@ func(bt *Batcher) MenuExit(w io.Writer) (int, error) { return w.Write(b) } +// MenuAdd adds a new menu instruction to the batcher. func(bt *Batcher) MenuAdd(w io.Writer, code string, arg Arg) (int, error) { bt.inMenu = true var selector string @@ -343,10 +352,12 @@ func(bt *Batcher) MenuAdd(w io.Writer, code string, arg Arg) (int, error) { return 0, err } +// Exit is a synonym for MenuExit func(bt *Batcher) Exit(w io.Writer) (int, error) { return bt.MenuExit(w) } +// Parse one or more lines of assembly code, and write assembled bytecode to the provided writer. func Parse(s string, w io.Writer) (int, error) { rd := strings.NewReader(s) ast, err := asmParser.Parse("file", rd) diff --git a/go/asm/menu.go b/go/asm/menu.go @@ -6,6 +6,7 @@ import ( "git.defalsify.org/festive/vm" ) +// BatchCode defines quasi-opcodes that expand to mulitple individual vm instructions. type BatchCode uint16 const ( @@ -32,15 +33,22 @@ type menuItem struct { target string } +// MenuProcessor handles code lines with BatchCode quasi-opcodes that control menu generation. +// +// It creates vm instructions for display of menu and handling of input on either size of a vm.HALT instruction. type MenuProcessor struct { items []menuItem size uint32 } +// NewMenuProcessor creates a new MenuProcessor object. func NewMenuProcessor() MenuProcessor { return MenuProcessor{} } +// Add a menu batch instruction to be processed. +// +// Instructions will be rendered in the order in which they have been added. func(mp *MenuProcessor) Add(bop string, choice string, display string, target string) error { bopCode := batchCode[bop] if bopCode == 0 { @@ -59,6 +67,7 @@ func(mp *MenuProcessor) Add(bop string, choice string, display string, target st return nil } +// ToLines returns the generated bytecode from the added menu batch instructions. func (mp *MenuProcessor) ToLines() []byte { preLines := []byte{} postLines := []byte{} diff --git a/go/cache/cache.go b/go/cache/cache.go @@ -5,14 +5,12 @@ import ( "log" ) +// Cache stores loaded content, enforcing size limits and keeping track of size usage. type Cache struct { 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 - //outputSize uint32 // Max size of output sizes map[string]uint16 // Size limits for all loaded symbols. - //sink *string } // NewCache creates a new ready-to-use cache object @@ -21,7 +19,6 @@ func NewCache() *Cache { Cache: []map[string]string{make(map[string]string)}, sizes: make(map[string]uint16), } - //ca.resetCurrent() return ca } @@ -97,9 +94,6 @@ func(ca *Cache) Update(key string, value string) error { r := ca.Cache[checkFrame][key] l := uint32(len(r)) ca.Cache[checkFrame][key] = "" - //if ca.CacheMap[key] != "" { - // ca.CacheMap[key] = value - //} ca.CacheUseSize -= l sz := ca.checkCapacity(value) if sz == 0 { @@ -113,6 +107,9 @@ func(ca *Cache) Update(key string, value string) error { return nil } +// Get the content currently loaded for a single key, loaded at any level. +// +// Fails if key has not been loaded. func(ca *Cache) Get(key string) (string, error) { i := ca.frameOf(key) r, ok := ca.Cache[i][key] diff --git a/go/cache/memory.go b/go/cache/memory.go @@ -1,5 +1,6 @@ package cache +// Memory defines the interface for store of a symbol mapped content store. type Memory interface { Add(key string, val string, sizeLimit uint16) error Update(key string, val string) error diff --git a/go/engine/default.go b/go/engine/default.go @@ -6,6 +6,7 @@ import ( "git.defalsify.org/festive/state" ) +// NewDefaultEngine is a convenience function to instantiate a filesystem-backed engine with no output constraints. func NewDefaultEngine(dir string) Engine { st := state.NewState(0) rs := resource.NewFsResource(dir) @@ -13,6 +14,7 @@ func NewDefaultEngine(dir string) Engine { return NewEngine(Config{}, &st, &rs, ca) } +// NewSizedEngine is a convenience function to instantiate a filesystem-backed engine with a specified output constraint. func NewSizedEngine(dir string, size uint32) Engine { st := state.NewState(0) rs := resource.NewFsResource(dir) diff --git a/go/engine/engine.go b/go/engine/engine.go @@ -13,13 +13,12 @@ import ( "git.defalsify.org/festive/vm" ) +// Config globally defines behavior of all components driven by the engine. type Config struct { - OutputSize uint32 -// FlagCount uint32 -// CacheSize uint32 + OutputSize uint32 // Maximum size of output from a single rendered page } -// Engine is an execution engine that handles top-level errors when running user inputs against currently exposed bytecode. +// Engine is an execution engine that handles top-level errors when running client inputs against code in the bytecode buffer. type Engine struct { st *state.State rs resource.Resource diff --git a/go/engine/loop.go b/go/engine/loop.go @@ -2,7 +2,6 @@ package engine import ( "bufio" -// "bytes" "context" "fmt" "io" @@ -10,17 +9,26 @@ import ( "strings" ) +// Loop starts an engine execution loop with the given symbol as the starting node. +// +// The root reads inputs from the provided reader, one line at a time. +// +// It will execute until running out of bytecode in the buffer. +// +// Any error not handled by the engine will terminate the oop and return an error. +// +// Rendered output is written to the provided writer. func Loop(en *Engine, startSym string, ctx context.Context, reader io.Reader, writer io.Writer) error { err := en.Init(startSym, ctx) if err != nil { return fmt.Errorf("cannot init: %v\n", err) } - //b := bytes.NewBuffer(nil) - //en.WriteResult(b, ctx) - en.WriteResult(writer, ctx) + err = en.WriteResult(writer, ctx) + if err != nil { + return err + } writer.Write([]byte{0x0a}) - //fmt.Println(b.String()) running := true bufReader := bufio.NewReader(reader) diff --git a/go/render/menu.go b/go/render/menu.go @@ -4,11 +4,13 @@ import ( "fmt" ) +// BrowseError is raised when browsing outside the page range of a rendered node. type BrowseError struct { Idx uint16 PageCount uint16 } +// Error implements the Error interface. func(err *BrowseError) Error() string { return fmt.Sprintf("index is out of bounds: %v", err.Idx) } @@ -35,13 +37,14 @@ func DefaultBrowseConfig() BrowseConfig { } } +// Menu renders menus. May be included in a Page object to render menus for pages. type Menu struct { - menu [][2]string - browse BrowseConfig - pageCount uint16 - canNext bool - canPrevious bool - outputSize uint16 + menu [][2]string // selector and title for menu items. + browse BrowseConfig // browse definitions. + pageCount uint16 // number of pages the menu should represent. + canNext bool // availability flag for the "next" browse option. + canPrevious bool // availability flag for the "previous" browse option. + outputSize uint16 // maximum size constraint for the menu. } // NewMenu creates a new Menu with an explicit page count. @@ -83,6 +86,11 @@ func(m *Menu) Put(selector string, title string) error { return nil } +// ReservedSize returns the maximum render byte size of the menu. +func(m *Menu) ReservedSize() uint16 { + return m.outputSize +} + // Render returns the full current state of the menu as a string. // // After this has been executed, the state of the menu will be empty. @@ -160,6 +168,7 @@ func(m *Menu) shiftMenu() (string, string, error) { return r[0], r[1], nil } +// prepare menu object for re-use. func(m *Menu) reset() { if m.browse.NextAvailable { m.canNext = true @@ -170,6 +179,3 @@ func(m *Menu) reset() { } -func(m *Menu) ReservedSize() uint16 { - return m.outputSize -} diff --git a/go/render/page.go b/go/render/page.go @@ -11,16 +11,17 @@ import ( "git.defalsify.org/festive/resource" ) +// Page exectues output rendering into pages constrained by size. type Page struct { - cacheMap map[string]string // Mapped - cache cache.Memory - resource resource.Resource - menu *Menu - sink *string - sinkSize uint16 - sizer *Sizer + cacheMap map[string]string // Mapped content symbols + cache cache.Memory // Content store. + resource resource.Resource // Symbol resolver. + menu *Menu // Menu rendererer. + sink *string // Content symbol rendered by dynamic size. + sizer *Sizer // Process size constraints. } +// NewPage creates a new Page object. func NewPage(cache cache.Memory, rs resource.Resource) *Page { return &Page{ cache: cache, @@ -29,6 +30,7 @@ func NewPage(cache cache.Memory, rs resource.Resource) *Page { } } +// WithMenu sets a menu renderer for the page. func(pg *Page) WithMenu(menu *Menu) *Page { pg.menu = menu if pg.sizer != nil { @@ -37,6 +39,7 @@ func(pg *Page) WithMenu(menu *Menu) *Page { return pg } +// WithSizer sets a size constraints definition for the page. func(pg *Page) WithSizer(sizer *Sizer) *Page { pg.sizer = sizer if pg.menu != nil { @@ -45,7 +48,7 @@ func(pg *Page) WithSizer(sizer *Sizer) *Page { return pg } -// Size returns size used by values and menu, and remaining size available +// Usage returns size used by values and menu, and remaining size available func(pg *Page) Usage() (uint32, uint32, error) { var l int var c uint16 @@ -97,6 +100,8 @@ func(pg *Page) Map(key string) error { return nil } +// Val gets the mapped content for the given symbol. +// // Fails if key is not mapped. func(pg *Page) Val(key string) (string, error) { r := pg.cacheMap[key] @@ -106,7 +111,7 @@ func(pg *Page) Val(key string) (string, error) { return r, nil } -// Moved from cache, MAP should hook to this object +// Sizes returned the actual used bytes by each mapped symbol. func(pg *Page) Sizes() (map[string]uint16, error) { sizes := make(map[string]uint16) var haveSink bool @@ -121,12 +126,11 @@ func(pg *Page) Sizes() (map[string]uint16, error) { } haveSink = true } - pg.sinkSize = l } return sizes, nil } -// DefaultRenderTemplate is an adapter to implement the builtin golang text template renderer as resource.RenderTemplate. +// RenderTemplate is an adapter to implement the builtin golang text template renderer as resource.RenderTemplate. func(pg *Page) RenderTemplate(sym string, values map[string]string, idx uint16) (string, error) { tpl, err := pg.resource.GetTemplate(sym) if err != nil { @@ -156,6 +160,26 @@ func(pg *Page) RenderTemplate(sym string, values map[string]string, idx uint16) return b.String(), err } +// Render renders the current mapped content and menu state against the template associated with the symbol. +func(pg *Page) Render(sym string, idx uint16) (string, error) { + var err error + + values, err := pg.prepare(sym, pg.cacheMap, idx) + if err != nil { + return "", err + } + + return pg.render(sym, values, idx) +} + +// Reset prepared the Page object for re-use. +// +// It clears mappings and removes the sink definition. +func(pg *Page) Reset() { + pg.sink = nil + pg.cacheMap = make(map[string]string) +} + // render menu and all syms except sink, split sink into display chunks // TODO: Function too long, split up func(pg *Page) prepare(sym string, values map[string]string, idx uint16) (map[string]string, error) { @@ -279,6 +303,7 @@ func(pg *Page) prepare(sym string, values map[string]string, idx uint16) (map[st return noSinkValues, nil } +// render template, menu (if it exists), and audit size constraint (if it exists). func(pg *Page) render(sym string, values map[string]string, idx uint16) (string, error) { var ok bool r := "" @@ -309,19 +334,3 @@ func(pg *Page) render(sym string, values map[string]string, idx uint16) (string, return r, nil } -func(pg *Page) Render(sym string, idx uint16) (string, error) { - var err error - - values, err := pg.prepare(sym, pg.cacheMap, idx) - if err != nil { - return "", err - } - - return pg.render(sym, values, idx) -} - -func(pg *Page) Reset() { - pg.sink = nil - pg.sinkSize = 0 - pg.cacheMap = make(map[string]string) -} diff --git a/go/render/render.go b/go/render/render.go @@ -1 +0,0 @@ -package render diff --git a/go/render/size.go b/go/render/size.go @@ -7,15 +7,17 @@ import ( "strings" ) +// Sizer splits dynamic contents into individual segments for browseable pages. type Sizer struct { - outputSize uint32 - menuSize uint16 - memberSizes map[string]uint16 - totalMemberSize uint32 - crsrs []uint32 - sink string + outputSize uint32 // maximum output for a single page. + menuSize uint16 // actual menu size for the dynamic page being sized + memberSizes map[string]uint16 // individual byte sizes of all content to be rendered by template. + totalMemberSize uint32 // total byte size of all content to be rendered by template (sum of memberSizes) + crsrs []uint32 // byte offsets in the sink content for browseable pages indices. + sink string // sink symbol. } +// NewSizer creates a new Sizer object with the given output size constraint. func NewSizer(outputSize uint32) *Sizer { return &Sizer{ outputSize: outputSize, @@ -23,11 +25,13 @@ func NewSizer(outputSize uint32) *Sizer { } } +// WithMenuSize sets the size of the menu being used in the rendering context. func(szr *Sizer) WithMenuSize(menuSize uint16) *Sizer { szr.menuSize = menuSize return szr } +// Set adds a content symbol in the state it will be used by the renderer. func(szr *Sizer) Set(key string, size uint16) error { szr.memberSizes[key] = size if size == 0 { @@ -37,6 +41,7 @@ func(szr *Sizer) Set(key string, size uint16) error { return nil } +// Check audits whether the rendered string is within the output size constraint of the sizer. func(szr *Sizer) Check(s string) (uint32, bool) { l := uint32(len(s)) if szr.outputSize > 0 { @@ -49,6 +54,7 @@ func(szr *Sizer) Check(s string) (uint32, bool) { return l, true } +// String implements the String interface. func(szr *Sizer) String() string { var diff uint32 if szr.outputSize > 0 { @@ -57,6 +63,9 @@ func(szr *Sizer) String() string { return fmt.Sprintf("output: %v, member: %v, menu: %v, diff: %v", szr.outputSize, szr.totalMemberSize, szr.menuSize, diff) } +// Size gives the byte size of content for a single symbol. +// +// Fails if the symbol has not been registered using Set func(szr *Sizer) Size(s string) (uint16, error) { r, ok := szr.memberSizes[s] if !ok { @@ -65,15 +74,20 @@ func(szr *Sizer) Size(s string) (uint16, error) { return r, nil } +// Menusize returns the currently defined menu size. func(szr *Sizer) MenuSize() uint16 { return szr.menuSize } +// AddCursor adds a pagination cursor for the paged sink content. func(szr *Sizer) AddCursor(c uint32) { log.Printf("added cursor: %v", c) szr.crsrs = append(szr.crsrs, c) } +// GetAt the paged symbols for the current page index. +// +// Fails if index requested is out of range. func(szr *Sizer) GetAt(values map[string]string, idx uint16) (map[string]string, error) { if szr.sink == "" { return values, nil diff --git a/go/testdata/testdata.go b/go/testdata/testdata.go @@ -72,7 +72,6 @@ func foo() error { b = vm.NewLine(b, vm.INCMP, []string{"0", "_"}, nil, nil) b = vm.NewLine(b, vm.INCMP, []string{"1", "baz"}, nil, nil) b = vm.NewLine(b, vm.INCMP, []string{"2", "long"}, nil, nil) - //b = vm.NewLine(b, vm.CATCH, []string{"_catch"}, []byte{1}, []uint8{1}) data := make(map[string]string) data["inky"] = "one" @@ -164,6 +163,9 @@ func generate() error { return nil } +// Generate outputs bytecode, templates and content symbols to a temporary directory. +// +// This directory can in turn be used as data source for the the resource.FsResource object. func Generate() (string, error) { dir, err := ioutil.TempDir("", "festive_testdata_") if err != nil { @@ -175,6 +177,12 @@ func Generate() (string, error) { return dir, err } + +// Generate outputs bytecode, templates and content symbols to a specified directory. +// +// The directory must exist, and must not have been used already in the same code execution. +// +// This directory can in turn be used as data source for the the resource.FsResource object. func GenerateTo(dir string) error { if dirLock { return fmt.Errorf("directory already overridden") diff --git a/go/vm/runner.go b/go/vm/runner.go @@ -11,16 +11,17 @@ import ( "git.defalsify.org/festive/state" ) +// Vm holds sub-components mutated by the vm execution. type Vm struct { - st *state.State - rs resource.Resource - pg *render.Page - ca cache.Memory - mn *render.Menu - sizer *render.Sizer + st *state.State // Navigation and error states. + rs resource.Resource // Retrieves content, code, and templates for symbols. + ca cache.Memory // Loaded content. + mn *render.Menu // Menu component of page. + sizer *render.Sizer // Apply size constraints to output. + pg *render.Page // Render outputs with menues to size constraints. } - +// NewVm creates a new Vm. func NewVm(st *state.State, rs resource.Resource, ca cache.Memory, sizer *render.Sizer) *Vm { vmi := &Vm{ st: st, @@ -33,17 +34,16 @@ func NewVm(st *state.State, rs resource.Resource, ca cache.Memory, sizer *render return vmi } +// Reset re-initializes sub-components for output rendering. func(vmi *Vm) Reset() { vmi.mn = render.NewMenu() vmi.pg.Reset() - vmi.pg = vmi.pg.WithMenu(vmi.mn) //render.NewPage(vmi.ca, vmi.rs).WithMenu(vmi.mn) + vmi.pg = vmi.pg.WithMenu(vmi.mn) if vmi.sizer != nil { vmi.pg = vmi.pg.WithSizer(vmi.sizer) } } -//type Runner func(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) - // Run extracts individual op codes and arguments and executes them. // // Each step may update the state. @@ -394,6 +394,7 @@ func(vm *Vm) RunMPrev(b []byte, ctx context.Context) ([]byte, error) { return b, nil } +// Render wraps output rendering, and handles error when attempting to browse beyond the rendered page count. func(vm *Vm) Render(ctx context.Context) (string, error) { changed, err := vm.st.ResetFlag(state.FLAG_DIRTY) if err != nil {