You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
4.8 KiB

  1. package syntax
  2. import (
  3. "fmt"
  4. "io"
  5. "math"
  6. "strconv"
  7. "strings"
  8. "time"
  9. )
  10. // Parse parses the specified query string. It is shorthand for constructing a
  11. // parser for s and calling its Parse method.
  12. func Parse(s string) (Query, error) {
  13. return NewParser(strings.NewReader(s)).Parse()
  14. }
  15. // Query is the root of the parse tree for a query. A query is the conjunction
  16. // of one or more conditions.
  17. type Query []Condition
  18. func (q Query) String() string {
  19. ss := make([]string, len(q))
  20. for i, cond := range q {
  21. ss[i] = cond.String()
  22. }
  23. return strings.Join(ss, " AND ")
  24. }
  25. // A Condition is a single conditional expression, consisting of a tag, a
  26. // comparison operator, and an optional argument. The type of the argument
  27. // depends on the operator.
  28. type Condition struct {
  29. Tag string
  30. Op Token
  31. Arg *Arg
  32. opText string
  33. }
  34. func (c Condition) String() string {
  35. s := c.Tag + " " + c.opText
  36. if c.Arg != nil {
  37. return s + " " + c.Arg.String()
  38. }
  39. return s
  40. }
  41. // An Arg is the argument of a comparison operator.
  42. type Arg struct {
  43. Type Token
  44. text string
  45. }
  46. func (a *Arg) String() string {
  47. if a == nil {
  48. return ""
  49. }
  50. switch a.Type {
  51. case TString:
  52. return "'" + a.text + "'"
  53. case TTime:
  54. return "TIME " + a.text
  55. case TDate:
  56. return "DATE " + a.text
  57. default:
  58. return a.text
  59. }
  60. }
  61. // Number returns the value of the argument text as a number, or a NaN if the
  62. // text does not encode a valid number value.
  63. func (a *Arg) Number() float64 {
  64. if a == nil {
  65. return -1
  66. }
  67. v, err := strconv.ParseFloat(a.text, 64)
  68. if err == nil && v >= 0 {
  69. return v
  70. }
  71. return math.NaN()
  72. }
  73. // Time returns the value of the argument text as a time, or the zero value if
  74. // the text does not encode a timestamp or datestamp.
  75. func (a *Arg) Time() time.Time {
  76. var ts time.Time
  77. if a == nil {
  78. return ts
  79. }
  80. var err error
  81. switch a.Type {
  82. case TDate:
  83. ts, err = ParseDate(a.text)
  84. case TTime:
  85. ts, err = ParseTime(a.text)
  86. }
  87. if err == nil {
  88. return ts
  89. }
  90. return time.Time{}
  91. }
  92. // Value returns the value of the argument text as a string, or "".
  93. func (a *Arg) Value() string {
  94. if a == nil {
  95. return ""
  96. }
  97. return a.text
  98. }
  99. // Parser is a query expression parser. The grammar for query expressions is
  100. // defined in the syntax package documentation.
  101. type Parser struct {
  102. scanner *Scanner
  103. }
  104. // NewParser constructs a new parser that reads the input from r.
  105. func NewParser(r io.Reader) *Parser {
  106. return &Parser{scanner: NewScanner(r)}
  107. }
  108. // Parse parses the complete input and returns the resulting query.
  109. func (p *Parser) Parse() (Query, error) {
  110. cond, err := p.parseCond()
  111. if err != nil {
  112. return nil, err
  113. }
  114. conds := []Condition{cond}
  115. for p.scanner.Next() != io.EOF {
  116. if tok := p.scanner.Token(); tok != TAnd {
  117. return nil, fmt.Errorf("offset %d: got %v, want %v", p.scanner.Pos(), tok, TAnd)
  118. }
  119. cond, err := p.parseCond()
  120. if err != nil {
  121. return nil, err
  122. }
  123. conds = append(conds, cond)
  124. }
  125. return conds, nil
  126. }
  127. // parseCond parses a conditional expression: tag OP value.
  128. func (p *Parser) parseCond() (Condition, error) {
  129. var cond Condition
  130. if err := p.require(TTag); err != nil {
  131. return cond, err
  132. }
  133. cond.Tag = p.scanner.Text()
  134. if err := p.require(TLeq, TGeq, TLt, TGt, TEq, TContains, TExists); err != nil {
  135. return cond, err
  136. }
  137. cond.Op = p.scanner.Token()
  138. cond.opText = p.scanner.Text()
  139. var err error
  140. switch cond.Op {
  141. case TLeq, TGeq, TLt, TGt:
  142. err = p.require(TNumber, TTime, TDate)
  143. case TEq:
  144. err = p.require(TNumber, TTime, TDate, TString)
  145. case TContains:
  146. err = p.require(TString)
  147. case TExists:
  148. // no argument
  149. return cond, nil
  150. default:
  151. return cond, fmt.Errorf("offset %d: unexpected operator %v", p.scanner.Pos(), cond.Op)
  152. }
  153. if err != nil {
  154. return cond, err
  155. }
  156. cond.Arg = &Arg{Type: p.scanner.Token(), text: p.scanner.Text()}
  157. return cond, nil
  158. }
  159. // require advances the scanner and requires that the resulting token is one of
  160. // the specified token types.
  161. func (p *Parser) require(tokens ...Token) error {
  162. if err := p.scanner.Next(); err != nil {
  163. return fmt.Errorf("offset %d: %w", p.scanner.Pos(), err)
  164. }
  165. got := p.scanner.Token()
  166. for _, tok := range tokens {
  167. if tok == got {
  168. return nil
  169. }
  170. }
  171. return fmt.Errorf("offset %d: got %v, wanted %s", p.scanner.Pos(), got, tokLabel(tokens))
  172. }
  173. // tokLabel makes a human-readable summary string for the given token types.
  174. func tokLabel(tokens []Token) string {
  175. if len(tokens) == 1 {
  176. return tokens[0].String()
  177. }
  178. last := len(tokens) - 1
  179. ss := make([]string, len(tokens)-1)
  180. for i, tok := range tokens[:last] {
  181. ss[i] = tok.String()
  182. }
  183. return strings.Join(ss, ", ") + " or " + tokens[last].String()
  184. }
  185. // ParseDate parses s as a date string in the format used by DATE values.
  186. func ParseDate(s string) (time.Time, error) {
  187. return time.Parse("2006-01-02", s)
  188. }
  189. // ParseTime parses s as a timestamp in the format used by TIME values.
  190. func ParseTime(s string) (time.Time, error) {
  191. return time.Parse(time.RFC3339, s)
  192. }