summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2024-03-21 16:29:17 +0100
committerGitHub <noreply@github.com>2024-03-21 16:29:17 +0100
commitbf2d6438e95c60946c64a6692e3dae1d836364a6 (patch)
tree041f819cdeae47e3ed624dc2ce233cd2c1d143aa
parent5da3a651ba08859ccc1cdf1094603411696c8df2 (diff)
parent7a9ac73037f207e3895332e5ba2b30d465c9c339 (diff)
Merge pull request #7 from ajnavarro/feature/simple-memory-dump
feat: Add simple Dump creation and recovery.
-rw-r--r--main.go5
-rw-r--r--parser/compiler.go99
-rw-r--r--parser/dump_test.go48
-rw-r--r--parser/interpreter.go4
4 files changed, 150 insertions, 6 deletions
diff --git a/main.go b/main.go
index 5e0989b..f566510 100644
--- a/main.go
+++ b/main.go
@@ -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 {