From 4241593b42bffac2f8fcb63f1e88621fe025e360 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 4 Sep 2023 16:58:15 +0200 Subject: codegen: add interpreter tests Also simplify project structure. The executable is now produced in the root directory. Work in progress. --- cmd/gint/main.go | 71 --------------------------------------------- codegen/compiler.go | 36 +++++++++++++++++++++-- codegen/compiler_test.go | 8 ++--- codegen/interpreter_test.go | 47 ++++++++++++++++++++++++++++++ main.go | 71 +++++++++++++++++++++++++++++++++++++++++++++ samples/add | 2 -- samples/fib | 6 ---- samples/p00 | 1 - samples/p01 | 2 -- samples/p02 | 7 ----- samples/p03 | 3 -- samples/p04 | 4 --- samples/p05 | 10 ------- samples/p06 | 7 ----- scanner/scan.go | 2 +- testdata/add | 2 ++ testdata/fib | 6 ++++ testdata/p00 | 1 + testdata/p01 | 2 ++ testdata/p02 | 7 +++++ testdata/p03 | 3 ++ testdata/p04 | 4 +++ testdata/p05 | 10 +++++++ testdata/p06 | 7 +++++ testdata/p07 | 5 ++++ vm1/vm.go | 3 +- 26 files changed, 205 insertions(+), 122 deletions(-) delete mode 100644 cmd/gint/main.go create mode 100644 codegen/interpreter_test.go create mode 100644 main.go delete mode 100644 samples/add delete mode 100644 samples/fib delete mode 100644 samples/p00 delete mode 100644 samples/p01 delete mode 100644 samples/p02 delete mode 100644 samples/p03 delete mode 100644 samples/p04 delete mode 100644 samples/p05 delete mode 100644 samples/p06 create mode 100644 testdata/add create mode 100644 testdata/fib create mode 100644 testdata/p00 create mode 100644 testdata/p01 create mode 100644 testdata/p02 create mode 100644 testdata/p03 create mode 100644 testdata/p04 create mode 100644 testdata/p05 create mode 100644 testdata/p06 create mode 100644 testdata/p07 diff --git a/cmd/gint/main.go b/cmd/gint/main.go deleted file mode 100644 index 037614c..0000000 --- a/cmd/gint/main.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "bufio" - "errors" - "fmt" - "io" - "log" - "os" - - "github.com/gnolang/parscan/codegen" - "github.com/gnolang/parscan/lang/golang" - "github.com/gnolang/parscan/scanner" - "github.com/gnolang/parscan/vm0" -) - -type Interpreter interface { - Eval(string) (any, error) -} - -func main() { - log.SetFlags(log.Lshortfile) - var interp Interpreter = vm0.New(golang.GoParser) - if len(os.Args) > 1 && os.Args[1] == "1" { - interp = codegen.NewInterpreter(golang.GoParser) - interp.(*codegen.Interpreter).AddSym(fmt.Println, "println") - } - in := os.Stdin - - if isatty(in) { - // Provide an interactive line oriented Read Eval Print Loop (REPL). - liner := bufio.NewScanner(in) - text, prompt := "", "> " - fmt.Print(prompt) - for liner.Scan() { - text += liner.Text() - res, err := interp.Eval(text + "\n") - if err == nil { - if res != nil { - fmt.Println(": ", res) - } - text, prompt = "", "> " - } else if errors.Is(err, scanner.ErrBlock) { - prompt = ">> " - } else { - fmt.Println("Error:", err) - text, prompt = "", "> " - } - fmt.Print(prompt) - } - return - } - - buf, err := io.ReadAll(in) - if err != nil { - log.Fatal(err) - } - if _, err := interp.Eval(string(buf)); err != nil { - log.Fatal(err) - } -} - -// isatty returns true if the input stream is a tty (i.e. a character device). -func isatty(in io.Reader) bool { - s, ok := in.(interface{ Stat() (os.FileInfo, error) }) - if !ok { - return false - } - stat, err := s.Stat() - return err == nil && stat.Mode()&os.ModeCharDevice != 0 -} diff --git a/codegen/compiler.go b/codegen/compiler.go index 3952166..30fc070 100644 --- a/codegen/compiler.go +++ b/codegen/compiler.go @@ -2,6 +2,7 @@ package codegen import ( "fmt" + "log" "strings" "github.com/gnolang/parscan/parser" @@ -84,8 +85,14 @@ func (c *Compiler) CodeGen(node *parser.Node) (err error) { c.Emit(n, vm1.Assign, int64(l)) case parser.DeclFunc: + fun := frameNode[len(frameNode)-1] + if len(fun.Child) == 3 { // no return values + if c.Code[len(c.Code)-1][1] != vm1.Return { + c.Emit(n, vm1.Return, 0, int64(len(fun.Child[1].Child))) + } + } scope = popScope(scope) - fnote = notes[frameNode[len(frameNode)-1]] + fnote = notes[fun] case parser.Ident: ident := n.Content() @@ -121,7 +128,15 @@ func (c *Compiler) CodeGen(node *parser.Node) (err error) { case parser.StmtReturn: fun := frameNode[len(frameNode)-1] - c.Emit(n, vm1.Return, int64(len(n.Child)), int64(len(fun.Child[1].Child))) + nret := 0 + if len(fun.Child) > 3 { + if ret := fun.Child[2]; ret.Kind == parser.BlockParen { + nret = len(ret.Child) + } else { + nret = 1 + } + } + c.Emit(n, vm1.Return, int64(nret), int64(len(fun.Child[1].Child))) case parser.BlockStmt: nd.ipend = len(c.Code) @@ -151,6 +166,17 @@ func (c *Compiler) CodeGen(node *parser.Node) (err error) { } return true }) + + log.Println("main:", c.symbols["main"]) + if s, _, ok := c.getSym("main", ""); ok { + if i, ok := c.codeIndex(s); ok { + // Internal call is always relative to instruction pointer. + c.Emit(nil, vm1.Call, int64(i-len(c.Code))) + c.Entry = len(c.Code) - 1 + } + log.Println(vm1.Disassemble(c.Code)) + } + return } @@ -168,7 +194,11 @@ func (c *Compiler) addSym(v any, name string, local bool, n *parser.Node) int { } func (c *Compiler) Emit(n *parser.Node, op ...int64) int { - op = append([]int64{int64(n.Pos())}, op...) + var pos int64 + if n != nil { + pos = int64(n.Pos()) + } + op = append([]int64{pos}, op...) l := len(c.Code) c.Code = append(c.Code, op) return l diff --git a/codegen/compiler_test.go b/codegen/compiler_test.go index a876d52..fbe8bb2 100644 --- a/codegen/compiler_test.go +++ b/codegen/compiler_test.go @@ -12,7 +12,7 @@ import ( func TestCodeGen(t *testing.T) { log.SetFlags(log.Lshortfile) - for _, test := range tests { + for _, test := range codeGenTests { test := test t.Run("", func(t *testing.T) { c := NewCompiler() @@ -32,13 +32,13 @@ func TestCodeGen(t *testing.T) { t.Log("data:", c.Data) t.Log("code:", vm1.Disassemble(c.Code)) if s := vm1.Disassemble(c.Code); s != test.asm { - t.Errorf("got error %#v, want error %#v", s, test.asm) + t.Errorf("got %#v, want %#v", s, test.asm) } }) } } -var tests = []struct { +var codeGenTests = []struct { src, asm, sym, err string }{{ // #00 src: "1+2", @@ -55,4 +55,4 @@ var tests = []struct { }, { // #04 src: "func add(a int, b int) int { return a + b }", asm: "Fdup -2\nFdup -3\nAdd\nReturn 1 2\n", -}, {}} +}} diff --git a/codegen/interpreter_test.go b/codegen/interpreter_test.go new file mode 100644 index 0000000..1d8a6b5 --- /dev/null +++ b/codegen/interpreter_test.go @@ -0,0 +1,47 @@ +package codegen + +import ( + "fmt" + "log" + "testing" + + "github.com/gnolang/parscan/lang/golang" +) + +func TestEval(t *testing.T) { + for _, test := range evalTests { + test := test + t.Run("", func(t *testing.T) { + interp := NewInterpreter(golang.GoParser) + errStr := "" + r, e := interp.Eval(test.src) + if e != nil { + errStr = e.Error() + } + if errStr != test.err { + t.Errorf("got error %#v, want error %#v", errStr, test.err) + } + res := fmt.Sprintf("%v", r) + if res != test.res { + t.Errorf("got %#v, want %#v", res, test.res) + } + log.Println(r, e) + }) + } +} + +var evalTests = []struct { + name, src, res, err string +}{{ /* #00 */ + src: "1 + 2", + res: "3", +}, { // #01 + src: "a := 2; a = a + 3", + res: "5", +}, { // #02 + src: "func f(a int) int { return a + 1 }; f(5)", + res: "6", +}, { // #03 + src: "func f(a int) (b int) { b = a + 1; return }; f(5)", + res: "6", +}} diff --git a/main.go b/main.go new file mode 100644 index 0000000..037614c --- /dev/null +++ b/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "io" + "log" + "os" + + "github.com/gnolang/parscan/codegen" + "github.com/gnolang/parscan/lang/golang" + "github.com/gnolang/parscan/scanner" + "github.com/gnolang/parscan/vm0" +) + +type Interpreter interface { + Eval(string) (any, error) +} + +func main() { + log.SetFlags(log.Lshortfile) + var interp Interpreter = vm0.New(golang.GoParser) + if len(os.Args) > 1 && os.Args[1] == "1" { + interp = codegen.NewInterpreter(golang.GoParser) + interp.(*codegen.Interpreter).AddSym(fmt.Println, "println") + } + in := os.Stdin + + if isatty(in) { + // Provide an interactive line oriented Read Eval Print Loop (REPL). + liner := bufio.NewScanner(in) + text, prompt := "", "> " + fmt.Print(prompt) + for liner.Scan() { + text += liner.Text() + res, err := interp.Eval(text + "\n") + if err == nil { + if res != nil { + fmt.Println(": ", res) + } + text, prompt = "", "> " + } else if errors.Is(err, scanner.ErrBlock) { + prompt = ">> " + } else { + fmt.Println("Error:", err) + text, prompt = "", "> " + } + fmt.Print(prompt) + } + return + } + + buf, err := io.ReadAll(in) + if err != nil { + log.Fatal(err) + } + if _, err := interp.Eval(string(buf)); err != nil { + log.Fatal(err) + } +} + +// isatty returns true if the input stream is a tty (i.e. a character device). +func isatty(in io.Reader) bool { + s, ok := in.(interface{ Stat() (os.FileInfo, error) }) + if !ok { + return false + } + stat, err := s.Stat() + return err == nil && stat.Mode()&os.ModeCharDevice != 0 +} diff --git a/samples/add b/samples/add deleted file mode 100644 index a403485..0000000 --- a/samples/add +++ /dev/null @@ -1,2 +0,0 @@ -func add(a int, b int) int { return a + b } -add(4, 3) diff --git a/samples/fib b/samples/fib deleted file mode 100644 index 654c5c0..0000000 --- a/samples/fib +++ /dev/null @@ -1,6 +0,0 @@ -func fib(i int) int { - if i < 2 { return i } - return fib(i-2) + fib(i-1) -} - -println(fib(6)) diff --git a/samples/p00 b/samples/p00 deleted file mode 100644 index 19f5084..0000000 --- a/samples/p00 +++ /dev/null @@ -1 +0,0 @@ -1+2 diff --git a/samples/p01 b/samples/p01 deleted file mode 100644 index baafaa9..0000000 --- a/samples/p01 +++ /dev/null @@ -1,2 +0,0 @@ -s := "Hello" -println(s, 5+2) diff --git a/samples/p02 b/samples/p02 deleted file mode 100644 index 1aeb4a9..0000000 --- a/samples/p02 +++ /dev/null @@ -1,7 +0,0 @@ -a := 1 - -if a < 2 { - println("yep") -} - -println("ok") diff --git a/samples/p03 b/samples/p03 deleted file mode 100644 index 39a3cda..0000000 --- a/samples/p03 +++ /dev/null @@ -1,3 +0,0 @@ -func f(a int, b int) int { return a + b } - -println("f:", f(3, 4), f(5, 6)) diff --git a/samples/p04 b/samples/p04 deleted file mode 100644 index f2a85c8..0000000 --- a/samples/p04 +++ /dev/null @@ -1,4 +0,0 @@ -func f() int { println(a) } - -a := 4 -f() diff --git a/samples/p05 b/samples/p05 deleted file mode 100644 index 51c2c9b..0000000 --- a/samples/p05 +++ /dev/null @@ -1,10 +0,0 @@ -func f(i int) int { - if i < 2 { - if i == 1 { - return - } } - println("i", i) -} - -f(1) -println("bye") diff --git a/samples/p06 b/samples/p06 deleted file mode 100644 index 88029cc..0000000 --- a/samples/p06 +++ /dev/null @@ -1,7 +0,0 @@ -func f(i int) { - if i < 2 { return } - println("i > 1:", i) -} - -f(1) -println("bye") diff --git a/scanner/scan.go b/scanner/scan.go index 914f4bc..1cc36a7 100644 --- a/scanner/scan.go +++ b/scanner/scan.go @@ -108,7 +108,7 @@ func (sc *Scanner) IsId(r rune) bool { } func (sc *Scanner) Init() { - // Build a regular expression to match all string delimiters. + // Build a regular expression to match all string start delimiters at once. re := "(" for s, p := range sc.BlockProp { if p&CharStr == 0 { diff --git a/testdata/add b/testdata/add new file mode 100644 index 0000000..a403485 --- /dev/null +++ b/testdata/add @@ -0,0 +1,2 @@ +func add(a int, b int) int { return a + b } +add(4, 3) diff --git a/testdata/fib b/testdata/fib new file mode 100644 index 0000000..654c5c0 --- /dev/null +++ b/testdata/fib @@ -0,0 +1,6 @@ +func fib(i int) int { + if i < 2 { return i } + return fib(i-2) + fib(i-1) +} + +println(fib(6)) diff --git a/testdata/p00 b/testdata/p00 new file mode 100644 index 0000000..19f5084 --- /dev/null +++ b/testdata/p00 @@ -0,0 +1 @@ +1+2 diff --git a/testdata/p01 b/testdata/p01 new file mode 100644 index 0000000..baafaa9 --- /dev/null +++ b/testdata/p01 @@ -0,0 +1,2 @@ +s := "Hello" +println(s, 5+2) diff --git a/testdata/p02 b/testdata/p02 new file mode 100644 index 0000000..1aeb4a9 --- /dev/null +++ b/testdata/p02 @@ -0,0 +1,7 @@ +a := 1 + +if a < 2 { + println("yep") +} + +println("ok") diff --git a/testdata/p03 b/testdata/p03 new file mode 100644 index 0000000..39a3cda --- /dev/null +++ b/testdata/p03 @@ -0,0 +1,3 @@ +func f(a int, b int) int { return a + b } + +println("f:", f(3, 4), f(5, 6)) diff --git a/testdata/p04 b/testdata/p04 new file mode 100644 index 0000000..f2a85c8 --- /dev/null +++ b/testdata/p04 @@ -0,0 +1,4 @@ +func f() int { println(a) } + +a := 4 +f() diff --git a/testdata/p05 b/testdata/p05 new file mode 100644 index 0000000..51c2c9b --- /dev/null +++ b/testdata/p05 @@ -0,0 +1,10 @@ +func f(i int) int { + if i < 2 { + if i == 1 { + return + } } + println("i", i) +} + +f(1) +println("bye") diff --git a/testdata/p06 b/testdata/p06 new file mode 100644 index 0000000..88029cc --- /dev/null +++ b/testdata/p06 @@ -0,0 +1,7 @@ +func f(i int) { + if i < 2 { return } + println("i > 1:", i) +} + +f(1) +println("bye") diff --git a/testdata/p07 b/testdata/p07 new file mode 100644 index 0000000..2fa7ee0 --- /dev/null +++ b/testdata/p07 @@ -0,0 +1,5 @@ +func f1() { println("in f1") } + +func f2() { println("in f2"); f1() } + +f2() diff --git a/vm1/vm.go b/vm1/vm.go index 572a171..01bba83 100644 --- a/vm1/vm.go +++ b/vm1/vm.go @@ -2,6 +2,7 @@ package vm1 import ( "fmt" // for tracing only + "log" // for tracing only "reflect" // for optional CallX only "strconv" // for tracing only ) @@ -79,7 +80,7 @@ func (m *Machine) Run() (err error) { op3 = strconv.Itoa(int(c[3])) } } - fmt.Printf("ip:%-4d sp:%-4d fp:%-4d op:[%-9s %-4s %-4s] mem:%v\n", ip, sp, fp, strop[c[1]], op2, op3, mem) + log.Printf("ip:%-4d sp:%-4d fp:%-4d op:[%-9s %-4s %-4s] mem:%v\n", ip, sp, fp, strop[c[1]], op2, op3, mem) } for { -- cgit v1.2.3