// Package bql implements parsing and pluggable interpretation // for the Brief query language (abbreviated "BQL"), // a small LISP-style language to specify mail queries. // // Example: // (or (and date=yesterday from=person1) from=person2) // Equivalently written (to be more shell-friendly): // grp or grp and date=yesterday from=person1 prg from=person2 prg package bql import ( "errors" "fmt" "strings" ) // TODO add semantics to the bql package var ErrBadVerb = errors.New("bad verb") // brief query language type BQL interface { isBQL(); String() string } type BQLEmpty struct {} func (_ BQLEmpty) isBQL() {} func (empty BQLEmpty) String() string { return "()" } type BQLQry struct { Raw string } func (_ BQLQry) isBQL() {} func (qry BQLQry) String() string { return escapePlain(qry.Raw) } type BQLNot struct { Qs []BQL } func (_ BQLNot) isBQL() {} func (not BQLNot) String() string { return wrap("not", not.Qs) } type BQLAnd struct { Qs []BQL } func (_ BQLAnd) isBQL() {} func (and BQLAnd) String() string { return wrap("and", and.Qs) } type BQLOr struct { Qs []BQL } func (_ BQLOr) isBQL() {} func (or BQLOr) String() string { return wrap("or", or.Qs) } func wrap(head string, qs []BQL) string { b := new(strings.Builder) b.WriteString("(") b.WriteString(head) for _, inner := range qs { b.WriteString(" ") b.WriteString(inner.String()) } b.WriteString(")") return b.String() } // escapePlain escapes byte-wise with in the well-known "\xFE" style. // escapePlain does not add quote markers. // escapePlain is injective. // Deprecated: Function will be removed in the future. func escapePlain(raw string) string { b := new(strings.Builder) for _, c := range []byte(raw) { if ' ' <= c && c != '"' && c != '\\' && c <= '~' { b.WriteRune(rune(c)) } else { fmt.Fprintf(b, "\\x%02X", int(c)) } } return b.String() }