repl.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. package main
  2. // reference: github.com/glycerine/zygomys/repl/repl.go
  3. //
  4. // much detail elided for instructional purposes...
  5. func main() {
  6. // env represents one interpreter:
  7. // It will have one parsing and one execution goroutine.
  8. env = NewGlisp()
  9. // cfg configures the env
  10. cfg := &GlsipConfig{
  11. // some of the options:
  12. ExitOnFailure: false, // action to take on error
  13. Sandboxed: false, // restrict scripts to sandobox?
  14. Quiet: false, // display startup banner?
  15. Trace: false, // for debugging, print actions step-by-step
  16. }
  17. // start a repl
  18. Repl(env, cfg)
  19. }
  20. func Repl(env *Glisp, cfg *GlispConfig) {
  21. // Prompter prints a prompt and returns a single line
  22. // of input from stdin when pr.getExpressionWithLiner(env)
  23. // is invoked.
  24. pr := NewPrompter()
  25. defer pr.Close()
  26. // the LOOP of the REPL. REPL stands for
  27. //
  28. // (1) READ, (2) EVAL, (3) PRINT, (4) LOOP
  29. //
  30. for {
  31. // (1) READ
  32. line, exprsInput, err := pr.getExpressionWithLiner(env)
  33. if err != nil {
  34. fmt.Println(err)
  35. if err == io.EOF {
  36. os.Exit(0)
  37. }
  38. env.Clear()
  39. continue
  40. }
  41. // (2) EVAL
  42. expr, err := env.Eval(exprsInput)
  43. switch err {
  44. case nil:
  45. case NoExpressionsFound:
  46. env.Clear()
  47. continue
  48. default:
  49. // display error. reset.
  50. fmt.Print(env.GetStackTrace(err))
  51. env.Clear()
  52. continue
  53. }
  54. if expr != SexpNull {
  55. // (3) PRINT expr to stdout here
  56. // ...
  57. }
  58. }
  59. }
  60. // continuationPrompt is displayed when the parser needs more input
  61. var continuationPrompt = "... "
  62. // reads Stdin only
  63. func (pr *Prompter) getExpressionWithLiner(env *Glisp) (readin string, xs []Sexp, err error) {
  64. line, err := pr.Getline(nil)
  65. if err != nil {
  66. return "", nil, err
  67. }
  68. err = UnexpectedEnd
  69. var x []Sexp
  70. // parse and pause the parser if we need more input.
  71. env.parser.ResetAddNewInput(bytes.NewBuffer([]byte(line + "\n")))
  72. x, err = env.parser.ParseTokens()
  73. if len(x) > 0 {
  74. xs = append(xs, x...)
  75. }
  76. for err == ErrMoreInputNeeded || err == UnexpectedEnd || err == ResetRequested {
  77. nextline, err := pr.Getline(&continuationPrompt)
  78. if err != nil {
  79. return "", nil, err
  80. }
  81. // provide more input
  82. env.parser.NewInput(bytes.NewBuffer([]byte(nextline + "\n")))
  83. // get parsed expression tree(s) back in x
  84. x, err = env.parser.ParseTokens()
  85. if len(x) > 0 {
  86. for i := range x {
  87. if x[i] == SexpEnd {
  88. P("found an SexpEnd token, omitting it")
  89. continue
  90. }
  91. xs = append(xs, x[i])
  92. }
  93. }
  94. switch err {
  95. case nil:
  96. line += "\n" + nextline
  97. // no problem, parsing went fine.
  98. return line, xs, nil
  99. case ResetRequested:
  100. continue
  101. case ErrMoreInputNeeded:
  102. continue
  103. default:
  104. return "", nil, fmt.Errorf("Error on line %d: %v\n", env.parser.lexer.Linenum(), err)
  105. }
  106. }
  107. return line, xs, nil
  108. }