go-vise

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

commit f06bca7abf19b787b23b193301700cf39d2e7684
parent bf1d6344744b012cfa95512d80fa17cf8c9f8441
Author: lash <dev@holbrook.no>
Date:   Sun, 16 Apr 2023 12:15:57 +0100

Add nomove state transition option, session partitioned interactive example

Diffstat:
MMakefile | 7+++++--
Masm/asm.go | 2+-
Mdev/interactive/main.go | 8++------
Mengine/default.go | 4++--
Mengine/engine.go | 13++++++++-----
Mengine/engine_test.go | 21++++++---------------
Mengine/loop_test.go | 21++++++---------------
Mengine/persist.go | 5+----
Mexamples/profile/main.go | 5+++--
Aexamples/session/data.txt.orig | 0
Aexamples/session/input | 2++
Aexamples/session/input.vis | 4++++
Aexamples/session/main.go | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexamples/session/root.vis | 2++
Mstate/flag.go | 14+++++++++-----
Mstate/state.go | 6+++++-
Mvm/input.go | 8++++++--
Mvm/runner.go | 42+++++++++++++++++++++++++++++-------------
Mvm/runner_test.go | 1+
19 files changed, 177 insertions(+), 73 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,9 @@ -examples: profile +examples: profile session -.PHONY: profile +.PHONY: examples profile: bash examples/compile.bash examples/profile + +session: + bash examples/compile.bash examples/session diff --git a/asm/asm.go b/asm/asm.go @@ -260,7 +260,7 @@ var ( {"Comment", `(?:#)[^\n]*`}, {"Ident", `^[A-Z]+`}, {"Size", `[0-9]+`}, - {"Sym", `[a-zA-Z_\*][a-zA-Z0-9_]*`}, + {"Sym", `[a-zA-Z_\*\.][a-zA-Z0-9_]*`}, {"Whitespace", `[ \t]+`}, {"EOL", `[\n\r]+`}, {"Quote", `["']`}, diff --git a/dev/interactive/main.go b/dev/interactive/main.go @@ -22,12 +22,8 @@ func main() { fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, dir) ctx := context.Background() - en, err := engine.NewSizedEngine(dir, uint32(size)) - if err != nil { - fmt.Fprintf(os.Stderr, "engine create fail: %v\n", err) - os.Exit(1) - } - err = engine.Loop(&en, os.Stdin, os.Stdout, ctx) + en := engine.NewSizedEngine(dir, uint32(size)) + err := engine.Loop(&en, os.Stdin, os.Stdout, ctx) if err != nil { fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err) os.Exit(1) diff --git a/engine/default.go b/engine/default.go @@ -9,7 +9,7 @@ import ( ) // NewDefaultEngine is a convenience function to instantiate a filesystem-backed engine with no output constraints. -func NewDefaultEngine(dir string) (Engine, error) { +func NewDefaultEngine(dir string) Engine { st := state.NewState(0) rs := resource.NewFsResource(dir) ca := cache.NewCache() @@ -21,7 +21,7 @@ func NewDefaultEngine(dir string) (Engine, error) { } // NewSizedEngine is a convenience function to instantiate a filesystem-backed engine with a specified output constraint. -func NewSizedEngine(dir string, size uint32) (Engine, error) { +func NewSizedEngine(dir string, size uint32) Engine { st := state.NewState(0) rs := resource.NewFsResource(dir) ca := cache.NewCache() diff --git a/engine/engine.go b/engine/engine.go @@ -33,7 +33,7 @@ type Engine struct { } // NewEngine creates a new Engine -func NewEngine(cfg Config, st *state.State, rs resource.Resource, ca cache.Memory, ctx context.Context) (Engine, error) { +func NewEngine(cfg Config, st *state.State, rs resource.Resource, ca cache.Memory, ctx context.Context) Engine { var szr *render.Sizer if cfg.OutputSize > 0 { szr = render.NewSizer(cfg.OutputSize) @@ -46,17 +46,19 @@ func NewEngine(cfg Config, st *state.State, rs resource.Resource, ca cache.Memor vm: vm.NewVm(st, rs, ca, szr), } engine.root = cfg.Root - return engine, nil + + return engine } // Init must be explicitly called before using the Engine instance. // // It loads and executes code for the start node. -func(en *Engine) Init(sym string, ctx context.Context) error { +func(en *Engine) Init(ctx context.Context) error { if en.initd { log.Printf("already initialized") return nil } + sym := en.root if sym == "" { return fmt.Errorf("start sym empty") } @@ -97,7 +99,7 @@ func(en *Engine) Init(sym string, ctx context.Context) error { func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) { var err error if en.st.Moves == 0 { - err = en.Init(en.root, ctx) + err = en.Init(ctx) if err != nil { return false, err } @@ -165,6 +167,7 @@ func(en *Engine) WriteResult(w io.Writer, ctx context.Context) (int, error) { return io.WriteString(w, r) } +// start execution over at top node while keeping current state of client error flags. func(en *Engine) reset(ctx context.Context) error { var err error var isTop bool @@ -181,5 +184,5 @@ func(en *Engine) reset(ctx context.Context) error { } en.st.Restart() en.initd = false - return en.Init(en.root, ctx) + return en.Init(ctx) } diff --git a/engine/engine_test.go b/engine/engine_test.go @@ -81,14 +81,11 @@ func TestEngineInit(t *testing.T) { rs := NewFsWrapper(dataDir, &st) ca := cache.NewCache().WithCacheSize(1024) - en, err := NewEngine(Config{ + en := NewEngine(Config{ Root: "root", }, &st, &rs, ca, ctx) - if err != nil { - t.Fatal(err) - } - err = en.Init("root", ctx) + err = en.Init(ctx) if err != nil { t.Fatal(err) } @@ -141,13 +138,10 @@ func TestEngineExecInvalidInput(t *testing.T) { ca := cache.NewCache().WithCacheSize(1024) - en, err := NewEngine(Config{ + en := NewEngine(Config{ Root: "root", }, &st, &rs, ca, ctx) - if err != nil { - t.Fatal(err) - } - err = en.Init("root", ctx) + err := en.Init(ctx) if err != nil { t.Fatal(err) } @@ -164,13 +158,10 @@ func TestEngineResumeTerminated(t *testing.T) { rs := NewFsWrapper(dataDir, &st) ca := cache.NewCache().WithCacheSize(1024) - en, err := NewEngine(Config{ + en := NewEngine(Config{ Root: "root", }, &st, &rs, ca, ctx) - if err != nil { - t.Fatal(err) - } - err = en.Init("root", ctx) + err := en.Init(ctx) if err != nil { t.Fatal(err) } diff --git a/engine/loop_test.go b/engine/loop_test.go @@ -23,11 +23,8 @@ func TestLoopTop(t *testing.T) { cfg := Config{ Root: "root", } - en, err := NewEngine(cfg, &st, &rs, ca, ctx) - if err != nil { - t.Fatal(err) - } - err = en.Init("root", ctx) + en := NewEngine(cfg, &st, &rs, ca, ctx) + err := en.Init(ctx) if err != nil { t.Fatal(err) } @@ -62,11 +59,8 @@ func TestLoopBackForth(t *testing.T) { cfg := Config{ Root: "root", } - en, err := NewEngine(cfg, &st, &rs, ca, ctx) - if err != nil { - t.Fatal(err) - } - err = en.Init("root", ctx) + en := NewEngine(cfg, &st, &rs, ca, ctx) + err := en.Init(ctx) if err != nil { t.Fatal(err) } @@ -99,11 +93,8 @@ func TestLoopBrowse(t *testing.T) { OutputSize: 68, Root: "root", } - en, err := NewEngine(cfg, &st, &rs, ca, ctx) - if err != nil { - t.Fatal(err) - } - err = en.Init("root", ctx) + en := NewEngine(cfg, &st, &rs, ca, ctx) + err := en.Init(ctx) if err != nil { t.Fatal(err) } diff --git a/engine/persist.go b/engine/persist.go @@ -24,10 +24,7 @@ func RunPersisted(cfg Config, rs resource.Resource, pr persist.Persister, input return err } - en, err := NewEngine(cfg, pr.GetState(), rs, pr.GetMemory(), ctx) - if err != nil { - return err - } + en := NewEngine(cfg, pr.GetState(), rs, pr.GetMemory(), ctx) c, err := en.WriteResult(w, ctx) if err != nil { diff --git a/examples/profile/main.go b/examples/profile/main.go @@ -122,9 +122,10 @@ func main() { OutputSize: uint32(size), } ctx := context.Background() - en, err := engine.NewEngine(cfg, &st, rs, ca, ctx) + en := engine.NewEngine(cfg, &st, rs, ca, ctx) + err := en.Init(ctx) if err != nil { - fmt.Fprintf(os.Stderr, "engine create fail: %v\n", err) + fmt.Fprintf(os.Stderr, "engine init fail: %v\n", err) os.Exit(1) } diff --git a/examples/session/data.txt.orig b/examples/session/data.txt.orig diff --git a/examples/session/input b/examples/session/input @@ -0,0 +1,2 @@ +hey hey hey +your data is {{.do_save}} diff --git a/examples/session/input.vis b/examples/session/input.vis @@ -0,0 +1,4 @@ +MAP do_save +HALT +RELOAD do_save +INCMP * . diff --git a/examples/session/main.go b/examples/session/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path" + + testdataloader "github.com/peteole/testdata-loader" + + "git.defalsify.org/vise/cache" + "git.defalsify.org/vise/engine" + "git.defalsify.org/vise/resource" + "git.defalsify.org/vise/state" +) + +var ( + baseDir = testdataloader.GetBasePath() + scriptDir = path.Join(baseDir, "examples", "session") + emptyResult = resource.Result{} +) + +func save(sym string, input []byte, ctx context.Context) (resource.Result, error) { + sessionId := ctx.Value("SessionId").(string) + sessionDir := path.Join(scriptDir, sessionId) + err := os.MkdirAll(sessionDir, 0700) + if err != nil { + return emptyResult, err + } + fp := path.Join(sessionDir, "data.txt") + if len(input) > 0 { + log.Printf("write data %s session %s", input, sessionId) + err = ioutil.WriteFile(fp, input, 0600) + if err != nil { + return emptyResult, err + } + } + r, err := ioutil.ReadFile(fp) + if err != nil { + err = ioutil.WriteFile(fp, []byte("(not set)"), 0600) + if err != nil { + return emptyResult, err + } + } + return resource.Result{ + Content: string(r), + }, nil +} + +func main() { + var root string + var size uint + var sessionId string + flag.UintVar(&size, "s", 0, "max size of output") + flag.StringVar(&root, "root", "root", "entry point symbol") + flag.StringVar(&sessionId, "session-id", "default", "session id") + flag.Parse() + fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, scriptDir) + + st := state.NewState(0) + rs := resource.NewFsResource(scriptDir) + rs.AddLocalFunc("do_save", save) + ca := cache.NewCache() + cfg := engine.Config{ + Root: "root", + SessionId: sessionId, + OutputSize: uint32(size), + } + ctx := context.Background() + ctx = context.WithValue(ctx, "SessionId", sessionId) + en := engine.NewEngine(cfg, &st, rs, ca, ctx) + err := en.Init(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "engine init fail: %v\n", err) + os.Exit(1) + } + err = engine.Loop(&en, os.Stdin, os.Stdout, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err) + os.Exit(1) + } +} diff --git a/examples/session/root.vis b/examples/session/root.vis @@ -0,0 +1,2 @@ +LOAD do_save 0 +MOVE input diff --git a/state/flag.go b/state/flag.go @@ -1,11 +1,12 @@ package state const ( - FLAG_READIN = 1 - FLAG_INMATCH = 2 - FLAG_TERMINATE = 3 - FLAG_DIRTY = 4 - FLAG_LOADFAIL = 5 + FLAG_READIN = iota + FLAG_INMATCH + FLAG_TERMINATE + FLAG_DIRTY + FLAG_WAIT + FLAG_LOADFAIL ) func IsWriteableFlag(flag uint32) bool { @@ -17,3 +18,6 @@ func IsWriteableFlag(flag uint32) bool { //} return false } + +type FlagDebugger struct { +} diff --git a/state/state.go b/state/state.go @@ -208,6 +208,10 @@ func(st *State) Next() (uint16, error) { return st.SizeIdx, nil } +func(st *State) Same() { + st.Moves += 1 +} + // Previous moves to the next sink page index. // // Fails if try to move beyond index 0. @@ -338,7 +342,7 @@ func(st *State) Restart() error { // String implements String interface func(st State) String() string { - return fmt.Sprintf("moves %v idx %v path: %s", st.Moves, st.SizeIdx, strings.Join(st.ExecPath, "/")) + return fmt.Sprintf("moves %v idx %v flags: 0x%x path: %s", st.Moves, st.SizeIdx, st.Flags, strings.Join(st.ExecPath, "/")) } // initializes all flags not in control of client. diff --git a/vm/input.go b/vm/input.go @@ -12,7 +12,7 @@ import ( var ( inputRegexStr = "^[a-zA-Z0-9].*$" inputRegex = regexp.MustCompile(inputRegexStr) - ctrlRegexStr = "^[><_^]$" + ctrlRegexStr = "^[><_^.]$" ctrlRegex = regexp.MustCompile(ctrlRegexStr) symRegexStr = "^[a-zA-Z0-9][a-zA-Z0-9_]+$" symRegex = regexp.MustCompile(symRegexStr) @@ -76,7 +76,7 @@ func CheckTarget(target []byte, st *state.State) (bool, error) { switch target[0] { case '_': topOk, err := st.Top() - if err!= nil { + if err != nil { return false, err } return topOk, nil @@ -137,6 +137,10 @@ func applyTarget(target []byte, st *state.State, ca cache.Memory, ctx context.Co return sym, idx, err } } + case '.': + st.Same() + location, idx := st.Where() + return location, idx, nil default: sym = string(target) err := st.Down(sym) diff --git a/vm/runner.go b/vm/runner.go @@ -31,6 +31,7 @@ func NewVm(st *state.State, rs resource.Resource, ca cache.Memory, sizer *render sizer: sizer, } vmi.Reset() + log.Printf("vm created with state: %v", st) return vmi } @@ -65,6 +66,19 @@ func(vm *Vm) Run(b []byte, ctx context.Context) ([]byte, error) { if err != nil { panic(err) } + + waitChange, err := vm.st.ResetFlag(state.FLAG_WAIT) + if err != nil { + panic(err) + } + if waitChange { + log.Printf("waitchange") + _, err = vm.st.ResetFlag(state.FLAG_INMATCH) + if err != nil { + panic(err) + } + } + _, err = vm.st.SetFlag(state.FLAG_DIRTY) if err != nil { panic(err) @@ -295,12 +309,7 @@ func(vm *Vm) RunInCmp(b []byte, ctx context.Context) ([]byte, error) { panic(err) } if have { - if !reading { - _, err = vm.st.ResetFlag(state.FLAG_INMATCH) - if err != nil { - panic(err) - } - } else { + if reading { log.Printf("ignoring input %s, already have match", sym) return b, nil } @@ -320,11 +329,11 @@ func(vm *Vm) RunInCmp(b []byte, ctx context.Context) ([]byte, error) { log.Printf("input wildcard match ('%s'), target '%s'", input, target) } else { if sym != string(input) { + log.Printf("foo") return b, nil } log.Printf("input match for '%s', target '%s'", input, target) } - _, err = vm.st.SetFlag(state.FLAG_INMATCH) if err != nil { panic(err) @@ -334,13 +343,10 @@ func(vm *Vm) RunInCmp(b []byte, ctx context.Context) ([]byte, error) { panic(err) } - target, _, err = applyTarget([]byte(target), vm.st, vm.ca, ctx) + newTarget, _, err := applyTarget([]byte(target), vm.st, vm.ca, ctx) + _, ok := err.(*state.IndexError) if ok { - _, err = vm.st.ResetFlag(state.FLAG_INMATCH) - if err != nil { - panic(err) - } _, err = vm.st.SetFlag(state.FLAG_READIN) if err != nil { panic(err) @@ -349,12 +355,16 @@ func(vm *Vm) RunInCmp(b []byte, ctx context.Context) ([]byte, error) { } else if err != nil { return b, err } + + target = newTarget + vm.Reset() code, err := vm.rs.GetCode(target) if err != nil { return b, err } + log.Printf("bar") log.Printf("loaded additional code for target '%s': %x", target, code) b = append(b, code...) return b, err @@ -368,7 +378,12 @@ func(vm *Vm) RunHalt(b []byte, ctx context.Context) ([]byte, error) { return b, err } log.Printf("found HALT, stopping") - return b, err + + _, err = vm.st.SetFlag(state.FLAG_WAIT) + if err != nil { + panic(err) + } + return b, nil } // RunMSize executes the MSIZE opcode @@ -454,6 +469,7 @@ func(vm *Vm) refresh(key string, rs resource.Resource, ctx context.Context) (str input, _ := vm.st.GetInput() r, err := fn(key, input, ctx) if err != nil { + log.Printf("loadfail %v", err) var perr error _, perr = vm.st.SetFlag(state.FLAG_LOADFAIL) if perr != nil { diff --git a/vm/runner_test.go b/vm/runner_test.go @@ -541,6 +541,7 @@ func TestInputIgnore(t *testing.T) { b := NewLine(nil, INCMP, []string{"foo", "one"}, nil, nil) b = NewLine(b, INCMP, []string{"bar", "two"}, nil, nil) + b = NewLine(b, HALT, nil, nil, nil) ctx := context.TODO()