Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(repl): improve support of multi-line statements #1129

Merged
merged 6 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 34 additions & 53 deletions gnovm/cmd/gno/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package main

import (
"bufio"
"bytes"
"context"
"errors"
"flag"
"fmt"
"go/scanner"
"os"
"strings"

Expand Down Expand Up @@ -88,91 +89,71 @@ func execRepl(cfg *replCfg, args []string) error {
// gno> import "gno.land/p/demo/avl" // import the p/demo/avl package
// gno> func a() string { return "a" } // declare a new function named a
// gno> /src // print current generated source
// gno> /editor // enter in editor mode to add several lines
// gno> /reset // remove all previously inserted code
// gno> println(a()) // print the result of calling a()
// gno> /exit
// gno> /exit // alternative to <Ctrl-D>
`)
}

return runRepl(cfg)
}

func runRepl(cfg *replCfg) error {
// init repl state
r := repl.NewRepl()

if cfg.initialCommand != "" {
handleInput(r, cfg.initialCommand)
}

var multiline bool
for {
fmt.Fprint(os.Stdout, "gno> ")
fmt.Fprint(os.Stdout, "gno> ")

input, err := getInput(multiline)
if err != nil {
return err
prevline := ""
thehowl marked this conversation as resolved.
Show resolved Hide resolved
liner := bufio.NewScanner(os.Stdin)

for liner.Scan() {
line := liner.Text()
if prevline != "" {
line = prevline + "\n" + line
prevline = ""
}

if err := handleInput(r, line); err != nil {
var goScanError scanner.ErrorList
if errors.As(err, &goScanError) {
// We assume that a Go scanner error indicates an incomplete Go statement.
// Append next line and retry.
prevline = line
} else {
fmt.Fprintln(os.Stderr, err)
}
}

multiline = handleInput(r, input)
if prevline == "" {
fmt.Fprint(os.Stdout, "gno> ")
} else {
fmt.Fprint(os.Stdout, "... ")
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}
}
return nil
}

// handleInput reads the input string and parses it depending if it
// is a specific command, or source code. It returns true if the following
// input is expected to be on more than one line.
func handleInput(r *repl.Repl, input string) bool {
// handleInput executes specific "/" commands, or evaluates input as Gno source code.
func handleInput(r *repl.Repl, input string) error {
switch strings.TrimSpace(input) {
case "/reset":
r.Reset()
case "/src":
fmt.Fprintln(os.Stdout, r.Src())
case "/exit":
os.Exit(0)
case "/editor":
fmt.Fprintln(os.Stdout, "// Entering editor mode (^D to finish)")
return true
case "":
// avoid to increase the repl execution counter if sending empty content
fmt.Fprintln(os.Stdout, "")
return false
// Avoid to increase the repl execution counter if no input.
default:
out, err := r.Process(input)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return err
}
fmt.Fprintln(os.Stdout, out)
}

return false
}

const (
inputBreaker = "^D"
nl = "\n"
)

func getInput(ml bool) (string, error) {
s := bufio.NewScanner(os.Stdin)
var mlOut bytes.Buffer
for s.Scan() {
line := s.Text()
if !ml {
return line, nil
}

if line == inputBreaker {
break
}

mlOut.WriteString(line)
mlOut.WriteString(nl)
}

if err := s.Err(); err != nil {
return "", err
}

return mlOut.String(), nil
return nil
}
2 changes: 1 addition & 1 deletion gnovm/pkg/repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (r *Repl) Process(input string) (out string, err error) {
return r.handleExpression(exp)
}

return "", fmt.Errorf("error parsing code:\n\t- as expression (error: %q)\n\t- as declarations (error: %q)", expErr.Error(), declErr.Error())
return "", fmt.Errorf("error parsing code:\n\t- as expression: %w\n\t- as declarations: %w", expErr, declErr)
}

func (r *Repl) handleExpression(e *ast.File) (string, error) {
Expand Down