diff options
| author | Antonio Navarro Perez <antnavper@gmail.com> | 2024-03-20 11:15:48 +0100 |
|---|---|---|
| committer | Antonio Navarro Perez <antnavper@gmail.com> | 2024-03-20 11:15:48 +0100 |
| commit | 649ae829220d6ddd8758d24c3bc2ea7d43e788d6 (patch) | |
| tree | ca5bccce3a8ac04a96fc795b06bc40d08384286d | |
| parent | 5da3a651ba08859ccc1cdf1094603411696c8df2 (diff) | |
feat: Add simple Dump creation and recovery.
Memory Dump functionality that can restore the previous VM state.
It dumps *global* variables, the only ones defining the program state.
The dump depends on the program itself, and on the index system, which right now is defined by the variable order.
Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com>
| -rw-r--r-- | main.go | 5 | ||||
| -rw-r--r-- | parser/compiler.go | 93 | ||||
| -rw-r--r-- | parser/dump_test.go | 48 | ||||
| -rw-r--r-- | parser/interpreter.go | 4 |
4 files changed, 144 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..568ed4a 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,104 @@ 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 gets the execution state of global variables. +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 { |
