aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg/shell/interpreter.go139
-rw-r--r--pkg/shell/shell.go14
2 files changed, 112 insertions, 41 deletions
diff --git a/pkg/shell/interpreter.go b/pkg/shell/interpreter.go
index 2752512..6c97619 100644
--- a/pkg/shell/interpreter.go
+++ b/pkg/shell/interpreter.go
@@ -3,7 +3,6 @@ package shell
import (
"errors"
"fmt"
- "index/suffixarray"
"io"
"slices"
"strconv"
@@ -11,22 +10,24 @@ import (
"unicode"
"github.com/jpappel/atlas/pkg/query"
+ "github.com/jpappel/atlas/pkg/util"
"golang.org/x/term"
)
const COMMENT_STR = "#"
+type keywords struct {
+ commands []string
+ variables []string
+ optimizations []string
+}
+
type Interpreter struct {
- State State
- Workers uint
- env map[string]string
- term *term.Terminal
- tab struct {
- inTabMode bool
- completions []string
- pos int
- index *suffixarray.Index
- }
+ State State
+ Workers uint
+ env map[string]string
+ term *term.Terminal
+ keywords keywords
}
type ITokType int
@@ -65,10 +66,38 @@ type IToken struct {
Text string
}
+var optimizations = []string{
+ "simplify",
+ "tighten",
+ "flatten",
+ "sort",
+ "tidy",
+ "contradictions",
+ "compact",
+ "strictEq",
+}
+
+var commands = []string{
+ "help",
+ "clear",
+ "let",
+ "del",
+ "slice",
+ "rematch",
+ "repattern",
+ "tokenize",
+ "parse",
+ "compile",
+}
+
func NewInterpreter(initialState State, env map[string]string, workers uint) *Interpreter {
return &Interpreter{
- State: initialState,
- env: env,
+ State: initialState,
+ env: env,
+ keywords: keywords{
+ commands: commands,
+ optimizations: optimizations,
+ },
Workers: workers,
}
}
@@ -85,11 +114,21 @@ func (inter *Interpreter) Eval(w io.Writer, tokens []IToken) (bool, error) {
if slices.ContainsFunc(tokens, func(token IToken) bool {
return token.Type == ITOK_INVALID
}) {
- b := strings.Builder{}
- b.WriteString("Unexpected token(s) at ")
- for i, t := range tokens {
+ b := &strings.Builder{}
+ b.WriteString("Unexpected token(s)\n")
+ for _, t := range tokens {
if t.Type == ITOK_INVALID {
- b.WriteString(fmt.Sprint(i, ", "))
+ b.WriteString(t.Text)
+ suggestion, goodSuggestion := util.Nearest(
+ t.Text,
+ inter.keywords.commands,
+ util.LevensteinDistance,
+ 5,
+ )
+ if goodSuggestion {
+ fmt.Fprintf(b, ": Did you mean '%s'?", suggestion)
+ }
+ b.WriteByte('\n')
}
}
return false, errors.New(b.String())
@@ -118,13 +157,16 @@ out:
} else {
v, ok := inter.env[t.Text]
if !ok {
- return false, fmt.Errorf("No env var: %s", t.Text)
+ return false, fmt.Errorf("No env var %s", t.Text)
}
fmt.Fprintln(w, t.Text, ":", v)
}
break out
case ITOK_CMD_LET:
if variableName != "" {
+ if _, ok := inter.State[variableName]; !ok {
+ inter.keywords.variables = append(inter.keywords.variables, variableName)
+ }
inter.State[variableName] = carryValue
carryValue.Type = VAL_INVALID
}
@@ -133,9 +175,16 @@ out:
if len(tokens) == 1 {
fmt.Fprintln(w, "Deleting all variables")
inter.State = make(State)
+ inter.keywords.variables = inter.keywords.variables[:0]
} else {
// HACK: variable name is not evaluated correctly so just look at the next token
- delete(inter.State, tokens[i+1].Text)
+ varName := tokens[i+1].Text
+ idx := slices.Index(inter.keywords.variables, varName)
+ if idx > 0 {
+ inter.keywords.variables[idx] = inter.keywords.variables[len(inter.keywords.variables)-1]
+ inter.keywords.variables = inter.keywords.variables[:len(inter.keywords.variables)-1]
+ }
+ delete(inter.State, varName)
}
carryValue.Type = VAL_INVALID
break out
@@ -146,12 +195,22 @@ out:
} else {
carryValue, ok = inter.State[tokens[1].Text]
if !ok {
- return false, fmt.Errorf("No variable %s", tokens[1].Text)
+ suggestion, ok := util.Nearest(
+ tokens[1].Text,
+ inter.keywords.variables,
+ util.LevensteinDistance,
+ 5,
+ )
+ suggestionText := ""
+ if ok {
+ suggestionText = fmt.Sprintf(": Did you mean '%s'?", suggestion)
+ }
+ return false, fmt.Errorf("No variable %s%s", tokens[1].Text, suggestionText)
}
}
case ITOK_CMD_REMATCH:
if carryValue.Type != VAL_STRING {
- return false, fmt.Errorf("Unable to march against argument of type: %s", carryValue.Type)
+ return false, fmt.Errorf("Unable to match against argument of type %s", carryValue.Type)
}
body, ok := carryValue.Val.(string)
@@ -231,14 +290,24 @@ out:
case "strictEq":
o.StrictEquality()
default:
- return false, fmt.Errorf("Unrecognized optimization: %s", t.Text)
+ suggestion, ok := util.Nearest(
+ t.Text,
+ inter.keywords.optimizations,
+ util.LevensteinDistance,
+ 4,
+ )
+ suggestionTxt := ""
+ if ok {
+ suggestionTxt = fmt.Sprintf(": Did you mean '%s'?", suggestion)
+ }
+ return false, fmt.Errorf("Unrecognized optimization %s%s", t.Text, suggestionTxt)
}
carryValue.Type = VAL_CLAUSE
carryValue.Val = clause
case ITOK_CMD_COMPILE:
if carryValue.Type != VAL_CLAUSE {
- return false, fmt.Errorf("Unable to compile argument of type: %s", carryValue)
+ return false, fmt.Errorf("Unable to compile argument of type %s", carryValue)
}
clause, ok := carryValue.Val.(*query.Clause)
@@ -259,7 +328,17 @@ out:
if i == len(tokens)-1 {
carryValue, ok = inter.State[t.Text]
if !ok {
- return false, fmt.Errorf("No variable: %s", t.Text)
+ suggestion, ok := util.Nearest(
+ t.Text,
+ inter.keywords.variables,
+ util.LevensteinDistance,
+ 4,
+ )
+ suggestionTxt := ""
+ if ok {
+ suggestionTxt = fmt.Sprintf(": Did you mean '%s'?", suggestion)
+ }
+ return false, fmt.Errorf("No variable %s%s", t.Text, suggestionTxt)
}
} else {
variableName = t.Text
@@ -270,7 +349,7 @@ out:
case ITOK_VAL_INT:
val, err := strconv.Atoi(t.Text)
if err != nil {
- return false, fmt.Errorf("Unable to parse as integer: %v", err)
+ return false, fmt.Errorf("Unable to parse as integer %v", err)
}
carryValue.Type = VAL_INT
carryValue.Val = val
@@ -290,7 +369,7 @@ out:
}
length = len(toks)
default:
- return false, fmt.Errorf("Unable to get length of argument with type: %s", carryValue.Type)
+ return false, fmt.Errorf("Unable to get length of argument with type %s", carryValue.Type)
}
carryValue.Type = VAL_INT
carryValue.Val = length
@@ -300,7 +379,7 @@ out:
case VAL_STRING:
case VAL_TOKENS:
default:
- return false, fmt.Errorf("Cannot slice argument: %v", cType)
+ return false, fmt.Errorf("Cannot slice argument %v", cType)
}
return false, fmt.Errorf("not implemented")
}
@@ -429,3 +508,9 @@ func printHelp(w io.Writer) {
fmt.Fprintln(w, "compile (clause|name) - compile clause into query")
fmt.Fprintln(w, "\nBare commands which return a value assign to an implicit variable _")
}
+
+func init() {
+ for _, opt := range optimizations {
+ commands = append(commands, "opt_"+opt)
+ }
+}
diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go
index f7dc350..34d2d40 100644
--- a/pkg/shell/shell.go
+++ b/pkg/shell/shell.go
@@ -9,20 +9,6 @@ import (
"golang.org/x/term"
)
-var commands = []string{
- "help",
- "clear",
- "let",
- "del",
- "slice",
- "rematch",
- "repattern",
- "tokenize",
- "opt_simplify", "opt_tighten", "opt_flatten", "opt_sort", "opt_tidy", "opt_contradictions", "opt_compact", "opt_strictEq",
- "parse",
- "compile",
-}
-
func (inter *Interpreter) runNonInteractive() error {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {