commit e340210d8ff8203c9441611ceea387ce2e2b9b3b
parent 91ee0568cab19f319f507e8b84f28d93fca55187
Author: lash <dev@holbrook.no>
Date: Wed, 12 Apr 2023 18:04:36 +0100
Add code comment documentation
Diffstat:
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 {