|
|
- 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)
- }
|