aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/query/compiler.go147
-rw-r--r--pkg/shell/interpreter.go59
2 files changed, 187 insertions, 19 deletions
diff --git a/pkg/query/compiler.go b/pkg/query/compiler.go
new file mode 100644
index 0000000..d566b05
--- /dev/null
+++ b/pkg/query/compiler.go
@@ -0,0 +1,147 @@
+package query
+
+import (
+ "fmt"
+ "strings"
+)
+
+const MAX_CLAUSE_DEPTH int = 16
+
+func (stmt Statement) Compile(b *strings.Builder) (*string, error) {
+ if stmt.Negated {
+ b.WriteString("NOT ")
+ }
+
+ switch stmt.Category {
+ case CAT_TITLE:
+ b.WriteString("title ")
+ case CAT_AUTHOR:
+ b.WriteString("author ")
+ case CAT_DATE:
+ b.WriteString("date ")
+ case CAT_FILETIME:
+ b.WriteString("fileTime ")
+ case CAT_TAGS:
+ b.WriteString("tags ")
+ case CAT_LINKS:
+ b.WriteString("links ")
+ default:
+ return nil, &CompileError{
+ fmt.Sprint("unknown or invalid category ", stmt.Category.String()),
+ }
+ }
+ switch stmt.Operator {
+ case OP_EQ:
+ if stmt.Category.IsSet() {
+ b.WriteString("IN ")
+ } else {
+ b.WriteString("= ")
+ }
+ case OP_AP:
+ b.WriteString("LIKE ")
+ case OP_NE:
+ b.WriteString("!= ")
+ case OP_LT:
+ b.WriteString("< ")
+ case OP_LE:
+ b.WriteString("<= ")
+ case OP_GE:
+ b.WriteString(">= ")
+ case OP_GT:
+ b.WriteString("> ")
+ default:
+ return nil, &CompileError{
+ fmt.Sprint("unknown or invalid operand ", stmt.Operator.String()),
+ }
+ }
+
+ switch stmt.Value.Type() {
+ case VAL_STR:
+ s, ok := stmt.Value.(StringValue)
+ if !ok {
+ panic(CompileError{"type corruption in string value"})
+ }
+ b.WriteString("(?) ")
+ return &s.S, nil
+ case VAL_DATETIME:
+ dt, ok := stmt.Value.(DatetimeValue)
+ if !ok {
+ panic(CompileError{"type corruption in datetime value"})
+ }
+ fmt.Fprint(b, dt.D.Unix(), " ")
+ default:
+ return nil, &CompileError{
+ fmt.Sprint("unknown or invalid value type ", stmt.Value.Type()),
+ }
+ }
+ return nil, nil
+}
+
+func (stmts Statements) Compile(b *strings.Builder, delim string) ([]string, error) {
+ var args []string
+
+ // TODO: handle meta category
+ for i, stmt := range stmts {
+ if i != 0 {
+ b.WriteString(delim)
+ }
+ b.WriteByte(' ')
+
+ arg, err := stmt.Compile(b)
+ if err != nil {
+ return nil, err
+ } else if arg != nil {
+ args = append(args, *arg)
+ }
+ }
+
+ return args, nil
+}
+
+func (root Clause) Compile() (string, []string, error) {
+ if d := root.Depth(); d > MAX_CLAUSE_DEPTH {
+ return "", nil, &CompileError{
+ fmt.Sprint("exceeded maximum clause depth of 8: ", d),
+ }
+ }
+
+ b := strings.Builder{}
+ args, err := root.buildCompile(&b)
+ if err != nil {
+ return "", nil, err
+ }
+ return b.String(), args, nil
+}
+
+func (c Clause) buildCompile(b *strings.Builder) ([]string, error) {
+ b.WriteString("( ")
+
+ var delim string
+ switch cop := c.Operator; cop {
+ case COP_AND:
+ delim = "AND"
+ case COP_OR:
+ delim = "OR"
+ default:
+ return nil, &CompileError{fmt.Sprint("invalid clause operator ", cop)}
+ }
+
+ args, err := c.Statements.Compile(b, delim)
+ if err != nil {
+ return nil, err
+ }
+ for _, clause := range c.Clauses {
+ b.WriteString(delim)
+ b.WriteByte(' ')
+
+ newArgs, err := clause.buildCompile(b)
+ if err != nil {
+ return nil, err
+ } else if newArgs != nil {
+ args = append(args, newArgs...)
+ }
+ }
+ b.WriteString(") ")
+
+ return args, nil
+}
diff --git a/pkg/shell/interpreter.go b/pkg/shell/interpreter.go
index 87b7748..14b2081 100644
--- a/pkg/shell/interpreter.go
+++ b/pkg/shell/interpreter.go
@@ -48,6 +48,7 @@ const (
ITOK_CMD_OPTIMIZE
ITOK_CMD_TOKENIZE
ITOK_CMD_PARSE
+ ITOK_CMD_COMPILE
)
type IToken struct {
@@ -88,21 +89,22 @@ func (interpreter *Interpreter) Eval(tokens []IToken) (bool, error) {
var variableName string
var carryValue Value
var ok bool
+out:
for i := len(tokens) - 1; i >= 0; i-- {
t := tokens[i]
switch t.Type {
case ITOK_CMD_HELP:
printHelp()
- break
+ break out
case ITOK_CMD_CLEAR:
fmt.Println("\033[H\033[J")
- break
+ break out
case ITOK_CMD_LET:
if variableName != "" {
interpreter.State[variableName] = carryValue
carryValue.Type = VAL_INVALID
}
- break
+ break out
case ITOK_CMD_DEL:
if len(tokens) == 1 {
fmt.Println("Deleting all variables")
@@ -112,7 +114,7 @@ func (interpreter *Interpreter) Eval(tokens []IToken) (bool, error) {
delete(interpreter.State, tokens[i+1].Text)
}
carryValue.Type = VAL_INVALID
- break
+ break out
case ITOK_CMD_PRINT:
if len(tokens) == 1 {
fmt.Println("Variables:")
@@ -148,7 +150,7 @@ func (interpreter *Interpreter) Eval(tokens []IToken) (bool, error) {
carryValue.Val = b.String()
case ITOK_CMD_REPATTERN:
fmt.Println(query.LexRegexPattern)
- break
+ break out
case ITOK_CMD_TOKENIZE:
if carryValue.Type != VAL_STRING {
return false, fmt.Errorf("Unable to tokenize argument of type: %s", carryValue.Type)
@@ -178,12 +180,12 @@ func (interpreter *Interpreter) Eval(tokens []IToken) (bool, error) {
carryValue.Val = clause
case ITOK_CMD_OPTIMIZE:
if carryValue.Type != VAL_CLAUSE {
- return false, fmt.Errorf("Unable to flatten argument of type: %s", carryValue)
+ return false, fmt.Errorf("Unable to optimize argument of type: %s", carryValue)
}
clause, ok := carryValue.Val.(*query.Clause)
if !ok {
- return true, errors.New("Type corruption during flatten, expected *query.Clause")
+ return true, errors.New("Type corruption during optimization, expected *query.Clause")
}
o := query.NewOptimizer(clause, interpreter.Workers)
@@ -210,6 +212,24 @@ func (interpreter *Interpreter) Eval(tokens []IToken) (bool, error) {
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)
+ }
+
+ clause, ok := carryValue.Val.(*query.Clause)
+ if !ok {
+ return true, errors.New("Type corruption during compilation, expected *query.Clause")
+ }
+
+ query, params, err := clause.Compile()
+ if err != nil {
+ return false, err
+ }
+
+ fmt.Printf("query:\n%s\n--------\nparams:\n%s\n", query, params)
+ carryValue.Type = VAL_INVALID
+ break out
case ITOK_VAR_NAME:
// NOTE: very brittle, only allows expansion of a single variable
if i == len(tokens)-1 {
@@ -259,7 +279,7 @@ func (interpreter *Interpreter) Eval(tokens []IToken) (bool, error) {
return false, fmt.Errorf("Cannot slice argument: %v", cType)
}
fmt.Println("not implemented yet ;)")
- break
+ break out
}
}
@@ -308,6 +328,8 @@ func (interpreter Interpreter) Tokenize(line string) []IToken {
tokens = append(tokens, IToken{Type: ITOK_CMD_PARSE})
} else if l := len("opt_"); len(trimmedWord) > l && trimmedWord[:l] == "opt_" {
tokens = append(tokens, IToken{ITOK_CMD_OPTIMIZE, trimmedWord[l:]})
+ } else if trimmedWord == "compile" {
+ tokens = append(tokens, IToken{Type: ITOK_CMD_COMPILE})
} else if prevType == ITOK_CMD_LET {
tokens = append(tokens, IToken{ITOK_VAR_NAME, trimmedWord})
} else if prevType == ITOK_CMD_DEL {
@@ -328,9 +350,7 @@ func (interpreter Interpreter) Tokenize(line string) []IToken {
} else {
tokens = append(tokens, IToken{ITOK_VAR_NAME, trimmedWord})
}
- } else if prevType == ITOK_CMD_PARSE {
- tokens = append(tokens, IToken{ITOK_VAR_NAME, trimmedWord})
- } else if prevType == ITOK_CMD_OPTIMIZE {
+ } else if prevType == ITOK_CMD_PARSE || prevType == ITOK_CMD_OPTIMIZE || prevType == ITOK_CMD_COMPILE {
tokens = append(tokens, IToken{ITOK_VAR_NAME, trimmedWord})
} else if prevType == ITOK_VAR_NAME && trimmedWord[0] == '`' {
_, strLiteral, _ := strings.Cut(word, "`")
@@ -405,13 +425,14 @@ func printHelp() {
fmt.Println("tokenize (string|name) - tokenize a string")
fmt.Println(" ex. tokenize `author:me")
fmt.Println("parse (tokens|name) - parse tokens into a clause")
- fmt.Println("opt_<subcommand> (clause|name) - optimize clause tree")
- fmt.Println(" sortStatements - sort statements")
- fmt.Println(" flatten - flatten clauses")
- fmt.Println(" compact - compact equivalent statements")
- fmt.Println(" tidy - remove zero statements and `AND` clauses containing any")
- fmt.Println(" contradictions - zero contradicting statements and clauses")
- fmt.Println(" strictEq - zero fuzzy/range statements when an eq is present")
- fmt.Println(" tighten - zero redundant fuzzy/range statements when another mathes the same values")
+ fmt.Println("opt_<subcommand> (clause|name) - optimize clause tree")
+ fmt.Println(" sortStatements - sort statements")
+ fmt.Println(" flatten - flatten clauses")
+ fmt.Println(" compact - compact equivalent statements")
+ fmt.Println(" tidy - remove zero statements and `AND` clauses containing any")
+ fmt.Println(" contradictions - zero contradicting statements and clauses")
+ fmt.Println(" strictEq - zero fuzzy/range statements when an eq is present")
+ fmt.Println(" tighten - zero redundant fuzzy/range statements when another mathes the same values")
+ fmt.Println("compile (clause|name) - compile clause into query")
fmt.Println("\nBare commands which return a value assign to an implicit variable _")
}