package syntax
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Parse parses the specified query string. It is shorthand for constructing a
|
|
// parser for s and calling its Parse method.
|
|
func Parse(s string) (Query, error) {
|
|
return NewParser(strings.NewReader(s)).Parse()
|
|
}
|
|
|
|
// Query is the root of the parse tree for a query. A query is the conjunction
|
|
// of one or more conditions.
|
|
type Query []Condition
|
|
|
|
func (q Query) String() string {
|
|
ss := make([]string, len(q))
|
|
for i, cond := range q {
|
|
ss[i] = cond.String()
|
|
}
|
|
return strings.Join(ss, " AND ")
|
|
}
|
|
|
|
// A Condition is a single conditional expression, consisting of a tag, a
|
|
// comparison operator, and an optional argument. The type of the argument
|
|
// depends on the operator.
|
|
type Condition struct {
|
|
Tag string
|
|
Op Token
|
|
Arg *Arg
|
|
|
|
opText string
|
|
}
|
|
|
|
func (c Condition) String() string {
|
|
s := c.Tag + " " + c.opText
|
|
if c.Arg != nil {
|
|
return s + " " + c.Arg.String()
|
|
}
|
|
return s
|
|
}
|
|
|
|
// An Arg is the argument of a comparison operator.
|
|
type Arg struct {
|
|
Type Token
|
|
text string
|
|
}
|
|
|
|
func (a *Arg) String() string {
|
|
if a == nil {
|
|
return ""
|
|
}
|
|
switch a.Type {
|
|
case TString:
|
|
return "'" + a.text + "'"
|
|
case TTime:
|
|
return "TIME " + a.text
|
|
case TDate:
|
|
return "DATE " + a.text
|
|
default:
|
|
return a.text
|
|
}
|
|
}
|
|
|
|
// Number returns the value of the argument text as a number, or a NaN if the
|
|
// text does not encode a valid number value.
|
|
func (a *Arg) Number() float64 {
|
|
if a == nil {
|
|
return -1
|
|
}
|
|
v, err := strconv.ParseFloat(a.text, 64)
|
|
if err == nil && v >= 0 {
|
|
return v
|
|
}
|
|
return math.NaN()
|
|
}
|
|
|
|
// Time returns the value of the argument text as a time, or the zero value if
|
|
// the text does not encode a timestamp or datestamp.
|
|
func (a *Arg) Time() time.Time {
|
|
var ts time.Time
|
|
if a == nil {
|
|
return ts
|
|
}
|
|
var err error
|
|
switch a.Type {
|
|
case TDate:
|
|
ts, err = ParseDate(a.text)
|
|
case TTime:
|
|
ts, err = ParseTime(a.text)
|
|
}
|
|
if err == nil {
|
|
return ts
|
|
}
|
|
return time.Time{}
|
|
}
|
|
|
|
// Value returns the value of the argument text as a string, or "".
|
|
func (a *Arg) Value() string {
|
|
if a == nil {
|
|
return ""
|
|
}
|
|
return a.text
|
|
}
|
|
|
|
// Parser is a query expression parser. The grammar for query expressions is
|
|
// defined in the syntax package documentation.
|
|
type Parser struct {
|
|
scanner *Scanner
|
|
}
|
|
|
|
// NewParser constructs a new parser that reads the input from r.
|
|
func NewParser(r io.Reader) *Parser {
|
|
return &Parser{scanner: NewScanner(r)}
|
|
}
|
|
|
|
// Parse parses the complete input and returns the resulting query.
|
|
func (p *Parser) Parse() (Query, error) {
|
|
cond, err := p.parseCond()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
conds := []Condition{cond}
|
|
for p.scanner.Next() != io.EOF {
|
|
if tok := p.scanner.Token(); tok != TAnd {
|
|
return nil, fmt.Errorf("offset %d: got %v, want %v", p.scanner.Pos(), tok, TAnd)
|
|
}
|
|
cond, err := p.parseCond()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
conds = append(conds, cond)
|
|
}
|
|
return conds, nil
|
|
}
|
|
|
|
// parseCond parses a conditional expression: tag OP value.
|
|
func (p *Parser) parseCond() (Condition, error) {
|
|
var cond Condition
|
|
if err := p.require(TTag); err != nil {
|
|
return cond, err
|
|
}
|
|
cond.Tag = p.scanner.Text()
|
|
if err := p.require(TLeq, TGeq, TLt, TGt, TEq, TContains, TExists); err != nil {
|
|
return cond, err
|
|
}
|
|
cond.Op = p.scanner.Token()
|
|
cond.opText = p.scanner.Text()
|
|
|
|
var err error
|
|
switch cond.Op {
|
|
case TLeq, TGeq, TLt, TGt:
|
|
err = p.require(TNumber, TTime, TDate)
|
|
case TEq:
|
|
err = p.require(TNumber, TTime, TDate, TString)
|
|
case TContains:
|
|
err = p.require(TString)
|
|
case TExists:
|
|
// no argument
|
|
return cond, nil
|
|
default:
|
|
return cond, fmt.Errorf("offset %d: unexpected operator %v", p.scanner.Pos(), cond.Op)
|
|
}
|
|
if err != nil {
|
|
return cond, err
|
|
}
|
|
cond.Arg = &Arg{Type: p.scanner.Token(), text: p.scanner.Text()}
|
|
return cond, nil
|
|
}
|
|
|
|
// require advances the scanner and requires that the resulting token is one of
|
|
// the specified token types.
|
|
func (p *Parser) require(tokens ...Token) error {
|
|
if err := p.scanner.Next(); err != nil {
|
|
return fmt.Errorf("offset %d: %w", p.scanner.Pos(), err)
|
|
}
|
|
got := p.scanner.Token()
|
|
for _, tok := range tokens {
|
|
if tok == got {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("offset %d: got %v, wanted %s", p.scanner.Pos(), got, tokLabel(tokens))
|
|
}
|
|
|
|
// tokLabel makes a human-readable summary string for the given token types.
|
|
func tokLabel(tokens []Token) string {
|
|
if len(tokens) == 1 {
|
|
return tokens[0].String()
|
|
}
|
|
last := len(tokens) - 1
|
|
ss := make([]string, len(tokens)-1)
|
|
for i, tok := range tokens[:last] {
|
|
ss[i] = tok.String()
|
|
}
|
|
return strings.Join(ss, ", ") + " or " + tokens[last].String()
|
|
}
|
|
|
|
// ParseDate parses s as a date string in the format used by DATE values.
|
|
func ParseDate(s string) (time.Time, error) {
|
|
return time.Parse("2006-01-02", s)
|
|
}
|
|
|
|
// ParseTime parses s as a timestamp in the format used by TIME values.
|
|
func ParseTime(s string) (time.Time, error) {
|
|
return time.Parse(time.RFC3339, s)
|
|
}
|