From 173788333e6b14a1b2bdbb127874988bb62bce8d Mon Sep 17 00:00:00 2001 From: JP Appel Date: Tue, 24 Jun 2025 19:14:00 -0400 Subject: Add first pass of query compiler --- pkg/query/compiler.go | 147 +++++++++++++++++++++++++++++++++++++++++++++++ pkg/shell/interpreter.go | 59 +++++++++++++------ 2 files changed, 187 insertions(+), 19 deletions(-) create mode 100644 pkg/query/compiler.go (limited to 'pkg') 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_ (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_ (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 _") } -- cgit v1.2.3