diff options
| -rw-r--r-- | cmd/gint/main.go | 45 | ||||
| -rw-r--r-- | codegen/interpreter.go | 15 | ||||
| -rw-r--r-- | parser/dot.go | 17 | ||||
| -rw-r--r-- | vm1/vm.go | 10 | ||||
| -rw-r--r-- | vm1/vm_test.go | 4 |
5 files changed, 76 insertions, 15 deletions
diff --git a/cmd/gint/main.go b/cmd/gint/main.go index 3ddddc8..5b2b3a2 100644 --- a/cmd/gint/main.go +++ b/cmd/gint/main.go @@ -1,12 +1,16 @@ 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" ) @@ -16,16 +20,49 @@ type Interpreter interface { func main() { log.SetFlags(log.Lshortfile) - buf, err := os.ReadFile("/dev/stdin") - if err != nil { - log.Fatal(err) - } 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", false) } + in := os.Stdin + + if isatty(in) { + // Provide an interactive line oriented Read Eval Print Loop (REPL). + liner := bufio.NewScanner(in) + text, prompt := "", "> " + fmt.Printf(prompt) + for liner.Scan() { + text += liner.Text() + err := interp.Eval(text + "\n") + if err == nil { + text, prompt = "", "> " + } else if errors.Is(err, scanner.ErrBlock) { + prompt = ">> " + } else { + text, prompt = "", "> " + fmt.Println("Error:", err) + } + fmt.Printf(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/interpreter.go b/codegen/interpreter.go index 8527631..b26b403 100644 --- a/codegen/interpreter.go +++ b/codegen/interpreter.go @@ -27,12 +27,21 @@ func (i *Interpreter) Eval(src string) (err error) { if debug { n.Dot(os.Getenv("DOT"), "") } + offset := len(i.Code) + i.PopExit() // Remove last exit from previous run. if err = i.CodeGen(n); err != nil { return err } - i.Emit(n, vm1.Exit) i.Push(i.Data...) - i.PushCode(i.Code) - i.SetIP(i.Entry) + i.PushCode(i.Code[offset:]...) + i.PushCode([]int64{0, vm1.Exit}) + i.SetIP(max(offset, i.Entry)) return i.Run() } + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/parser/dot.go b/parser/dot.go index cef1acb..aae5c5f 100644 --- a/parser/dot.go +++ b/parser/dot.go @@ -17,7 +17,13 @@ func (*Parser) Adot(nodes []*Node, c string) { n.Dot(c, "") } -func (n *Node) Dot(c, s string) { n.astDot(dotWriter(c), s) } +func (n *Node) Dot(c, s string) { + dw, cmd := dotWriter(c) + n.astDot(dw, s) + if cmd != nil { + cmd.Wait() + } +} func (n *Node) Sdot(s string) string { var buf bytes.Buffer @@ -49,6 +55,9 @@ func (n *Node) astDot(out io.Writer, label string) { return true }, nil) fmt.Fprintf(out, "}") + if c, ok := out.(io.Closer); ok { + c.Close() + } } type nopCloser struct { @@ -57,9 +66,9 @@ type nopCloser struct { func (nopCloser) Close() error { return nil } -func dotWriter(dotCmd string) io.WriteCloser { +func dotWriter(dotCmd string) (io.WriteCloser, *exec.Cmd) { if dotCmd == "" { - return nopCloser{io.Discard} + return nopCloser{io.Discard}, nil } fields := strings.Fields(dotCmd) cmd := exec.Command(fields[0], fields[1:]...) @@ -70,5 +79,5 @@ func dotWriter(dotCmd string) io.WriteCloser { if err = cmd.Start(); err != nil { log.Fatal(err) } - return dotin + return dotin, cmd } @@ -6,7 +6,7 @@ import ( "strconv" // for tracing only ) -const debug = false +const debug = true // Byte-code instruction set. const ( @@ -155,7 +155,7 @@ func (m *Machine) Run() (err error) { } } -func (m *Machine) PushCode(code [][]int64) (p int) { +func (m *Machine) PushCode(code ...[]int64) (p int) { p = len(m.code) m.code = append(m.code, code...) return p @@ -165,6 +165,12 @@ func (m *Machine) SetIP(ip int) { m.ip = ip } func (m *Machine) Push(v ...any) (l int) { l = len(m.mem); m.mem = append(m.mem, v...); return } func (m *Machine) Pop() (v any) { l := len(m.mem) - 1; v = m.mem[l]; m.mem = m.mem[:l]; return } +func (m *Machine) PopExit() { + if l := len(m.code); l > 0 && m.code[l-1][1] == Exit { + m.code = m.code[:l-1] + } +} + // Disassemble returns the code as a readable string. func Disassemble(code [][]int64) (asm string) { for _, op := range code { diff --git a/vm1/vm_test.go b/vm1/vm_test.go index 257ac44..272a321 100644 --- a/vm1/vm_test.go +++ b/vm1/vm_test.go @@ -13,7 +13,7 @@ func TestVM(t *testing.T) { for _, v := range test.sym { m.Push(v) } - m.PushCode(test.code) + m.PushCode(test.code...) if err := m.Run(); err != nil { t.Errorf("run error: %v", err) } @@ -33,7 +33,7 @@ func BenchmarkVM(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() m := &Machine{} - m.PushCode(test.code) + m.PushCode(test.code...) b.StartTimer() if err := m.Run(); err != nil { |
