go-vise

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

commit 218086c8a3ca500940df4023ce8aabf5e331c535
parent 840d1abf3fe15f7c114790c27af92c0ae8f2a86c
Author: lash <dev@holbrook.no>
Date:   Fri, 21 Apr 2023 20:20:02 +0100

Implement language

Diffstat:
Mengine/engine.go | 23++++++++++++++++++++++-
Mengine/engine_test.go | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgo.mod | 1+
Alang/lang.go | 31+++++++++++++++++++++++++++++++
Alang/lang_test.go | 20++++++++++++++++++++
Mrender/page.go | 1+
Mresource/fs.go | 15+++++++++++----
Mresource/fs_test.go | 27+++++++++++++++++++++++++++
Mresource/resource.go | 1-
Mstate/debug_test.go | 2+-
Mstate/flag.go | 4+++-
Mstate/state.go | 29++++++++++++++++++++++++++---
Mvm/input.go | 1-
Mvm/runner.go | 24+++++++++++++++++++++---
Mvm/runner_test.go | 34++++++++++++++++++++++++++++++++++
15 files changed, 277 insertions(+), 15 deletions(-)

diff --git a/engine/engine.go b/engine/engine.go @@ -27,6 +27,7 @@ type Config struct { Root string FlagCount uint32 CacheSize uint32 + Language string } // Engine is an execution engine that handles top-level errors when running client inputs against code in the bytecode buffer. @@ -56,6 +57,20 @@ func NewEngine(cfg Config, st *state.State, rs resource.Resource, ca cache.Memor engine.root = cfg.Root engine.session = cfg.SessionId + var err error + if st.Language != nil { + if cfg.Language != "" { + err = st.SetLanguage(cfg.Language) + panic(err) + } + } + + if st.Language != nil { + _, err = st.SetFlag(state.FLAG_LANG) + if err != nil { + panic(err) + } + } return engine } @@ -86,6 +101,7 @@ func(en *Engine) Init(ctx context.Context) (bool, error) { Logg.DebugCtxf(ctx, "already initialized") return true, nil } + sym := en.root if sym == "" { return false, fmt.Errorf("start sym empty") @@ -123,6 +139,9 @@ func(en *Engine) Init(ctx context.Context) (bool, error) { // - input processing against bytcode failed func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) { var err error + if en.st.Language != nil { + ctx = context.WithValue(ctx, "Language", *en.st.Language) + } if en.st.Moves == 0 { cont, err := en.Init(ctx) if err != nil { @@ -176,7 +195,6 @@ func(en *Engine) exec(input []byte, ctx context.Context) (bool, error) { _, err = en.reset(ctx) return false, err } - return true, nil } @@ -187,6 +205,9 @@ func(en *Engine) exec(input []byte, ctx context.Context) (bool, error) { // - the template for the given node point is note available for retrieval using the resource.Resource implementer. // - the supplied writer fails to process the writes. func(en *Engine) WriteResult(w io.Writer, ctx context.Context) (int, error) { + if en.st.Language != nil { + ctx = context.WithValue(ctx, "Language", *en.st.Language) + } r, err := en.vm.Render(ctx) if err != nil { return 0, err diff --git a/engine/engine_test.go b/engine/engine_test.go @@ -9,9 +9,11 @@ import ( "testing" "git.defalsify.org/vise.git/cache" + "git.defalsify.org/vise.git/lang" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/testdata" + "git.defalsify.org/vise.git/vm" ) var ( @@ -33,6 +35,8 @@ func NewFsWrapper(path string, st *state.State) FsWrapper { wr.AddLocalFunc("one", wr.one) wr.AddLocalFunc("inky", wr.inky) wr.AddLocalFunc("pinky", wr.pinky) + wr.AddLocalFunc("set_lang", wr.set_lang) + wr.AddLocalFunc("translate", wr.translate) return wr } @@ -55,6 +59,29 @@ func(fs FsWrapper) pinky(sym string, input []byte, ctx context.Context) (resourc }, nil } +func(fs FsWrapper) translate(sym string, input []byte, ctx context.Context) (resource.Result, error) { + r := "cool" + v := ctx.Value("Language") + code := "" + lang, ok := v.(lang.Language) + if ok { + code = lang.Code + } + if code == "nor" { + r = "fett" + } + return resource.Result{ + Content: r, + }, nil +} + +func(fs FsWrapper) set_lang(sym string, input []byte, ctx context.Context) (resource.Result, error) { + return resource.Result{ + Content: string(input), + FlagSet: []uint32{state.FLAG_LANG}, + }, nil +} + func(fs FsWrapper) GetCode(sym string) ([]byte, error) { sym += ".bin" fp := path.Join(fs.Path, sym) @@ -186,3 +213,55 @@ func TestEngineResumeTerminated(t *testing.T) { t.Fatalf("expected idx '0', got %v", idx) } } + +func TestLanguageSet(t *testing.T) { + generateTestData(t) + ctx := context.TODO() + st := state.NewState(0) + rs := NewFsWrapper(dataDir, &st) + ca := cache.NewCache().WithCacheSize(1024) + + en := NewEngine(Config{ + Root: "root", + }, &st, &rs, ca, ctx) + + var err error + _, err = en.Init(ctx) + if err != nil { + t.Fatal(err) + } + + b := vm.NewLine(nil, vm.LOAD, []string{"translate"}, []byte{0x01, 0xff}, nil) + b = vm.NewLine(b, vm.LOAD, []string{"set_lang"}, []byte{0x01, 0x00}, nil) + b = vm.NewLine(b, vm.MOVE, []string{"."}, nil, nil) + st.SetCode(b) + + _, err = en.Exec([]byte("no"), ctx) + if err != nil { + t.Fatal(err) + } + r, err := ca.Get("translate") + if err != nil { + t.Fatal(err) + } + if r != "cool" { + t.Fatalf("expected 'cool', got '%s'", r) + } + + + b = vm.NewLine(nil, vm.RELOAD, []string{"translate"}, nil, nil) + b = vm.NewLine(b, vm.MOVE, []string{"."}, nil, nil) + st.SetCode(b) + + _, err = en.Exec([]byte("no"), ctx) + if err != nil { + t.Fatal(err) + } + r, err = ca.Get("translate") + if err != nil { + t.Fatal(err) + } + if r != "fett" { + t.Fatalf("expected 'fett', got '%s'", r) + } +} diff --git a/go.mod b/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( github.com/alecthomas/participle/v2 v2.0.0 + github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c github.com/fxamacker/cbor/v2 v2.4.0 github.com/peteole/testdata-loader v0.3.0 ) diff --git a/lang/lang.go b/lang/lang.go @@ -0,0 +1,31 @@ +package lang + +import ( + "fmt" + + "github.com/barbashov/iso639-3" +) + +var ( + Default = "eng" // ISO639-3 +) + +type Language struct { + Code string + Name string +} + +func LanguageFromCode(code string) (Language, error) { + r := iso639_3.FromAnyCode(code) + if r == nil { + return Language{}, fmt.Errorf("invalid language code: %s", code) + } + return Language{ + Code: r.Part3, + Name: r.Name, + }, nil +} + +func(l Language) String() string { + return fmt.Sprintf("%s (%s)", l.Code, l.Name) +} diff --git a/lang/lang_test.go b/lang/lang_test.go @@ -0,0 +1,20 @@ +package lang + +import ( + "testing" +) + +func TestLang(t *testing.T) { + var err error + _, err = LanguageFromCode("xxx") + if err == nil { + t.Fatalf("expected error") + } + l, err := LanguageFromCode("en") + if err != nil { + t.Fatal(err) + } + if l.Code != "eng" { + t.Fatalf("expected 'eng', got '%s'", l.Code) + } +} diff --git a/render/page.go b/render/page.go @@ -181,6 +181,7 @@ func(pg *Page) Reset() { } } + // 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) { diff --git a/resource/fs.go b/resource/fs.go @@ -7,6 +7,8 @@ import ( "path" "path/filepath" "strings" + + "git.defalsify.org/vise.git/lang" ) type FsResource struct { @@ -50,7 +52,7 @@ func(fs FsResource) FuncFor(sym string) (EntryFunc, error) { if ok { return fn, nil } - _, err := fs.getFuncNoCtx(sym, nil) + _, err := fs.getFuncNoCtx(sym, nil, nil) if err != nil { return nil, fmt.Errorf("unknown sym: %s", sym) } @@ -62,13 +64,18 @@ func(fs FsResource) String() string { } func(fs FsResource) getFunc(sym string, input []byte, ctx context.Context) (Result, error) { - return fs.getFuncNoCtx(sym, input) + v := ctx.Value("language") + if v == nil { + return fs.getFuncNoCtx(sym, input, nil) + } + language := v.(*lang.Language) + return fs.getFuncNoCtx(sym, input, language) } -func(fs FsResource) getFuncNoCtx(sym string, input []byte) (Result, error) { +func(fs FsResource) getFuncNoCtx(sym string, input []byte, language *lang.Language) (Result, error) { fb := sym + ".txt" fp := path.Join(fs.Path, fb) - Logg.Debugf("getfunc search dir", "dir", fs.Path, "path", fp, "sym", sym) + Logg.Debugf("getfunc search dir", "dir", fs.Path, "path", fp, "sym", sym, "language", language) r, err := ioutil.ReadFile(fp) if err != nil { return Result{}, fmt.Errorf("failed getting data for sym '%s': %v", sym, err) diff --git a/resource/fs_test.go b/resource/fs_test.go @@ -8,3 +8,30 @@ func TestNewFs(t *testing.T) { n := NewFsResource("./testdata") _ = n } +// +//func TestResourceLanguage(t *testing.T) { +// var err error +// generateTestData(t) +// ctx := context.TODO() +// st := state.NewState(0) +// rs := NewFsWrapper(dataDir, &st) +// ca := cache.NewCache() +// +// cfg := Config{ +// Root: "root", +// } +// +// en := NewEngine(cfg, &st, &rs, ca, ctx) +// _, err = en.Init(ctx) +// if err == nil { +// t.Fatalf("expected error") +// } +// cfg = Config{ +// Root: "root", +// } +// en = NewEngine(cfg, &st, &rs, ca, ctx) +// _, err = en.Init(ctx) +// if err != nil { +// t.Fatal(err) +// } +//} diff --git a/resource/resource.go b/resource/resource.go @@ -71,4 +71,3 @@ func(m *MenuResource) GetCode(sym string) ([]byte, error) { func(m *MenuResource) GetTemplate(sym string) (string, error) { return m.templateFunc(sym) } - diff --git a/state/debug_test.go b/state/debug_test.go @@ -50,7 +50,7 @@ func TestDebugState(t *testing.T) { st.Down("root") r := fmt.Sprintf("%s", st) - expect := "moves: 1 idx: 0 flags: INTERNAL_DIRTY(3),FOO(8) path: root" + expect := "moves: 1 idx: 0 flags: INTERNAL_DIRTY(3),FOO(8) path: root lang: (default)" if r != expect { t.Fatalf("expected '%s', got '%s'", expect, r) } diff --git a/state/flag.go b/state/flag.go @@ -7,11 +7,13 @@ const ( FLAG_DIRTY FLAG_WAIT FLAG_LOADFAIL + FLAG_RESERVED + FLAG_LANG FLAG_USERSTART = 8 ) func IsWriteableFlag(flag uint32) bool { - if flag > 7 { + if flag > 6 { return true } //if flag & FLAG_WRITEABLE > 0 { diff --git a/state/state.go b/state/state.go @@ -3,6 +3,8 @@ package state import ( "fmt" "strings" + + "git.defalsify.org/vise.git/lang" ) type IndexError struct { @@ -30,10 +32,11 @@ func(err *IndexError) Error() string { type State struct { Code []byte // Pending bytecode to execute ExecPath []string // Command symbols stack - BitSize uint32 // size of (32-bit capacity) bit flag byte array - SizeIdx uint16 + BitSize uint32 // Size of (32-bit capacity) bit flag byte array + SizeIdx uint16 // Lateral page browse index in current frame Flags []byte // Error state Moves uint32 // Number of times navigation has been performed + Language *lang.Language // Language selector for rendering input []byte // Last input debug bool // Make string representation more human friendly } @@ -345,6 +348,20 @@ func(st *State) Restart() error { return nil } +// SetLanguage validates and sets language according to the given ISO639 language code. +func(st *State) SetLanguage(code string) error { + if code == "" { + st.Language = nil + } + l, err := lang.LanguageFromCode(code) + if err != nil { + return err + } + st.Language = &l + Logg.Infof("language set", "language", l) + return nil +} + // String implements String interface func(st State) String() string { var flags string @@ -353,7 +370,13 @@ func(st State) String() string { } else { flags = fmt.Sprintf("0x%x", st.Flags) } - return fmt.Sprintf("moves: %v idx: %v flags: %s path: %s", st.Moves, st.SizeIdx, flags, strings.Join(st.ExecPath, "/")) + var lang string + if st.Language == nil { + lang = "(default)" + } else { + lang = fmt.Sprintf("%s", *st.Language) + } + return fmt.Sprintf("moves: %v idx: %v flags: %s path: %s lang: %s", st.Moves, st.SizeIdx, flags, strings.Join(st.ExecPath, "/"), lang) } // initializes all flags not in control of client. diff --git a/vm/input.go b/vm/input.go @@ -156,7 +156,6 @@ func applyTarget(target []byte, st *state.State, ca cache.Memory, ctx context.Co return sym, idx, err } idx = 0 - Logg.Debugf("inputsss", "sym", sym) } return sym, idx, nil } diff --git a/vm/runner.go b/vm/runner.go @@ -11,6 +11,7 @@ import ( ) // Vm holds sub-components mutated by the vm execution. +// TODO: Renderer should be passed to avoid proxy methods not strictly related to vm operation type Vm struct { st *state.State // Navigation and error states. rs resource.Resource // Retrieves content, code, and templates for symbols. @@ -30,7 +31,7 @@ func NewVm(st *state.State, rs resource.Resource, ca cache.Memory, sizer *render sizer: sizer, } vmi.Reset() - Logg.Infof("vm created with state", "state", st) + Logg.Infof("vm created with state", "state", st, "renderer", vmi.pg) return vmi } @@ -57,16 +58,24 @@ func(vm *Vm) Run(b []byte, ctx context.Context) ([]byte, error) { panic(err) } if r { - //log.Printf("terminate set! bailing!") Logg.InfoCtxf(ctx, "terminate set! bailing") return []byte{}, nil } - //vm.st.ResetBaseFlags() + _, err = vm.st.ResetFlag(state.FLAG_TERMINATE) if err != nil { panic(err) } + change, err := vm.st.ResetFlag(state.FLAG_LANG) + if err != nil { + panic(err) + } + if change { + ctx = context.WithValue(ctx, "Language", *vm.st.Language) + } + + waitChange, err := vm.st.ResetFlag(state.FLAG_WAIT) if err != nil { panic(err) @@ -497,5 +506,14 @@ func(vm *Vm) refresh(key string, rs resource.Resource, ctx context.Context) (str } } + haveLang, err := vm.st.MatchFlag(state.FLAG_LANG, false) + if err != nil { + panic(err) + } + if haveLang { + vm.st.SetLanguage(r.Content) + } + + return r.Content, err } diff --git a/vm/runner_test.go b/vm/runner_test.go @@ -62,6 +62,13 @@ func setFlag(sym string, input []byte, ctx context.Context) (resource.Result, er } +func set_lang(sym string, input []byte, ctx context.Context) (resource.Result, error) { + return resource.Result{ + Content: string(input), + FlagSet: []uint32{state.FLAG_LANG}, + }, nil +} + type TestStatefulResolver struct { state *state.State } @@ -103,6 +110,8 @@ func (r TestResource) FuncFor(sym string) (resource.EntryFunc, error) { return getEcho, nil case "setFlagOne": return setFlag, nil + case "set_lang": + return set_lang, nil } return nil, fmt.Errorf("invalid function: '%s'", sym) } @@ -626,3 +635,28 @@ func TestCatchCleanMenu(t *testing.T) { } fmt.Printf("Result:\n%s", r) } + +func TestSetLang(t *testing.T) { + st := state.NewState(0) + rs := TestResource{} + ca := cache.NewCache() + vm := NewVm(&st, &rs, ca, nil) + + var err error + + st.Down("root") + + st.SetInput([]byte("no")) + b := NewLine(nil, LOAD, []string{"set_lang"}, []byte{0x01, 0x00}, nil) + b = NewLine(b, HALT, nil, nil, nil) + + ctx := context.TODO() + b, err = vm.Run(b, ctx) + if err != nil { + t.Fatal(err) + } + lang := *st.Language + if lang.Code != "nor" { + t.Fatalf("expected language 'nor',, got %s", lang.Code) + } +}