summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/gint/main.go45
-rw-r--r--codegen/interpreter.go15
-rw-r--r--parser/dot.go17
-rw-r--r--vm1/vm.go10
-rw-r--r--vm1/vm_test.go4
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
}
diff --git a/vm1/vm.go b/vm1/vm.go
index f7113f2..c2f4882 100644
--- a/vm1/vm.go
+++ b/vm1/vm.go
@@ -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 {