diff options
| author | Marc Vertes <mvertes@free.fr> | 2024-03-21 16:29:17 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-21 16:29:17 +0100 |
| commit | bf2d6438e95c60946c64a6692e3dae1d836364a6 (patch) | |
| tree | 041f819cdeae47e3ed624dc2ce233cd2c1d143aa | |
| parent | 5da3a651ba08859ccc1cdf1094603411696c8df2 (diff) | |
| parent | 7a9ac73037f207e3895332e5ba2b30d465c9c339 (diff) | |
Merge pull request #7 from ajnavarro/feature/simple-memory-dump
feat: Add simple Dump creation and recovery.
| -rw-r--r-- | main.go | 5 | ||||
| -rw-r--r-- | parser/compiler.go | 99 | ||||
| -rw-r--r-- | parser/dump_test.go | 48 | ||||
| -rw-r--r-- | parser/interpreter.go | 4 |
4 files changed, 150 insertions, 6 deletions
@@ -8,6 +8,7 @@ import ( "io" "log" "os" + "reflect" "github.com/mvertes/parscan/lang/golang" "github.com/mvertes/parscan/parser" @@ -15,7 +16,7 @@ import ( ) type Interpreter interface { - Eval(string) (any, error) + Eval(string) (reflect.Value, error) } func main() { @@ -44,7 +45,7 @@ func repl(interp Interpreter, in io.Reader) (err error) { text += liner.Text() res, err := interp.Eval(text + "\n") if err == nil { - if res != nil { + if !res.IsNil() { fmt.Println(": ", res) } text, prompt = "", "> " diff --git a/parser/compiler.go b/parser/compiler.go index 4594ab9..fb79720 100644 --- a/parser/compiler.go +++ b/parser/compiler.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "reflect" "strconv" "github.com/mvertes/parscan/lang" @@ -328,18 +329,110 @@ type entry struct { *symbol } +func (e entry) String() string { + if e.symbol != nil { + return fmt.Sprintf("name: %s,local: %t, i: %d, k: %d, t: %s, v: %v", + e.name, + e.symbol.local, + e.symbol.index, + e.symbol.kind, + e.symbol.Type, + e.symbol.value, + ) + } + + return e.name +} + func (c *Compiler) PrintData() { + dict := c.symbolsByIndex() + + fmt.Fprintln(os.Stderr, "# Data:") + for i, d := range c.Data { + fmt.Fprintf(os.Stderr, "%4d %T %v %v\n", i, d.Data.Interface(), d.Data, dict[i]) + } +} + +func (c *Compiler) symbolsByIndex() map[int]entry { dict := map[int]entry{} for name, sym := range c.symbols { - if !sym.used || sym.local || sym.kind == symLabel { + if sym.index == unsetAddr { continue } dict[sym.index] = entry{name, sym} } - fmt.Fprintln(os.Stderr, "# Data:") + + return dict +} + +type Dump struct { + Values []*DumpValue +} + +type DumpValue struct { + Index int + Name string + Kind int + Type string + Value any +} + +// Dump creates a snapshot of the execution state of global variables. +// This method is specifically implemented in the Compiler to minimize the coupling between +// the dump format and other components. By situating the dump logic in the Compiler, +// it relies solely on the program being executed and the indexing algorithm used for ordering variables +// (currently, this is an integer that corresponds to the order of variables in the program). +// This design choice allows the Virtual Machine (VM) to evolve its memory management strategies +// without compromising backward compatibility with dumps generated by previous versions. +func (c *Compiler) Dump() *Dump { + var dv []*DumpValue + dict := c.symbolsByIndex() for i, d := range c.Data { - fmt.Fprintf(os.Stderr, "%4d %T %v %v\n", i, d.Data.Interface(), d.Data, dict[i]) + e := dict[i] + dv = append(dv, &DumpValue{ + Index: e.index, + Name: e.name, + Kind: int(e.kind), + Type: e.Type.Name, + Value: d.Data.Interface(), + }) } + + return &Dump{Values: dv} +} + +// ApplyDump sets previously saved dump, restoring the state of global variables. +func (c *Compiler) ApplyDump(d *Dump) error { + dict := c.symbolsByIndex() + for _, dv := range d.Values { + // do all the checks to be sure we are applying the correct values + e, ok := dict[dv.Index] + if !ok { + return fmt.Errorf("entry not found on index %d", dv.Index) + } + + if dv.Name != e.name || + dv.Type != e.Type.Name || + dv.Kind != int(e.kind) { + return fmt.Errorf("entry with index %d does not match with provided entry. "+ + "dumpValue: %s, %s, %d. memoryValue: %s, %s, %d", + dv.Index, + dv.Name, dv.Type, dv.Kind, + e.name, e.Type, e.kind) + } + + if dv.Index >= len(c.Data) { + return fmt.Errorf("index (%d) bigger than memory (%d)", dv.Index, len(c.Data)) + } + + if !c.Data[dv.Index].Data.CanSet() { + return fmt.Errorf("value %v cannot be set", dv.Value) + } + + c.Data[dv.Index].Data.Set(reflect.ValueOf(dv.Value)) + } + + return nil } func (c *Compiler) typeSym(t *vm.Type) *symbol { diff --git a/parser/dump_test.go b/parser/dump_test.go new file mode 100644 index 0000000..db86c48 --- /dev/null +++ b/parser/dump_test.go @@ -0,0 +1,48 @@ +package parser_test + +import ( + "testing" + + "github.com/mvertes/parscan/parser" +) + +func TestDump(t *testing.T) { + initProgram := "var a int = 2+1; a" + interp := parser.NewInterpreter(GoScanner) + r, e := interp.Eval(initProgram) + t.Log(r, e) + if e != nil { + t.Fatal(e) + } + + r, e = interp.Eval("a = 100") + t.Log(r, e) + if e != nil { + t.Fatal(e) + } + + d := interp.Dump() + t.Log(d) + + interp = parser.NewInterpreter(GoScanner) + r, e = interp.Eval(initProgram) + t.Log(r, e) + if e != nil { + t.Fatal(e) + } + + e = interp.ApplyDump(d) + if e != nil { + t.Fatal(e) + } + + r, e = interp.Eval("a = a + 1;a") + t.Log(r, e) + if e != nil { + t.Fatal(e) + } + + if r.Interface() != int(101) { + t.Fatalf("unexpected result: %v", r) + } +} diff --git a/parser/interpreter.go b/parser/interpreter.go index 3ecbc46..d820416 100644 --- a/parser/interpreter.go +++ b/parser/interpreter.go @@ -1,6 +1,8 @@ package parser import ( + "reflect" + "github.com/mvertes/parscan/scanner" "github.com/mvertes/parscan/vm" ) @@ -16,7 +18,7 @@ func NewInterpreter(s *scanner.Scanner) *Interpreter { return &Interpreter{NewCompiler(s), &vm.Machine{}} } -func (i *Interpreter) Eval(src string) (res any, err error) { +func (i *Interpreter) Eval(src string) (res reflect.Value, err error) { codeOffset := len(i.Code) dataOffset := 0 if codeOffset > 0 { |
