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 all 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
101 changes: 48 additions & 53 deletions gnovm/cmd/gno/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

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

Expand Down Expand Up @@ -88,91 +89,85 @@
// 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> /editor // enter in multi-line mode, end with ';'

Check warning on line 92 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L92

Added line #L92 was not covered by tests
// gno> /reset // remove all previously inserted code
// gno> println(a()) // print the result of calling a()
// gno> /exit
// gno> /exit // alternative to <Ctrl-D>

Check warning on line 95 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L95

Added line #L95 was not covered by tests
`)
}

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> ")

Check warning on line 109 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L109

Added line #L109 was not covered by tests

input, err := getInput(multiline)
if err != nil {
return err
inEdit := false
prev := ""
liner := bufio.NewScanner(os.Stdin)

for liner.Scan() {
line := liner.Text()

if l := strings.TrimSpace(line); l == ";" {
line, inEdit = "", false
} else if l == "/editor" {
line, inEdit = "", true
fmt.Fprintln(os.Stdout, "// enter a single ';' to quit and commit")
}
if prev != "" {
line = prev + "\n" + line
prev = ""
}
if inEdit {
fmt.Fprint(os.Stdout, "... ")
prev = line
continue

Check warning on line 131 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L111-L131

Added lines #L111 - L131 were not covered by tests
}

multiline = handleInput(r, input)
if err := handleInput(r, line); err != nil {
var goScanError scanner.ErrorList
if errors.As(err, &goScanError) {
// We assune that a Go scanner error indicates an incomplete Go statement.
mvertes marked this conversation as resolved.
Show resolved Hide resolved
// Append next line and retry.
prev = line
} else {
fmt.Fprintln(os.Stderr, err)
}

Check warning on line 142 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L134-L142

Added lines #L134 - L142 were not covered by tests
}

if prev == "" {
fmt.Fprint(os.Stdout, "gno> ")
} else {
fmt.Fprint(os.Stdout, "... ")
}

Check warning on line 149 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L145-L149

Added lines #L145 - L149 were not covered by tests
}
return nil

Check warning on line 151 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L151

Added line #L151 was not covered by tests
}

// 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 {

Check warning on line 155 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L155

Added line #L155 was not covered by tests
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

Check warning on line 168 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L168

Added line #L168 was not covered by tests
}
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

Check warning on line 172 in gnovm/cmd/gno/repl.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/repl.go#L172

Added line #L172 was not covered by tests
}
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
Loading