Enable model expressions in SQL parser

The SQL parser now supports model expressions for defining ranges using the |table:0,0.5..100| notation syntax.
This commit is contained in:
Tobie Morgan Hitchcock 2017-04-14 13:21:08 +01:00
parent 2de5a8fa3f
commit aa7e4ac561
4 changed files with 176 additions and 1 deletions

View file

@ -551,6 +551,22 @@ type ContentExpression struct {
Data Expr Data Expr
} }
// --------------------------------------------------
// Model
// --------------------------------------------------
// Model comment
type Model struct {
TB string
MIN float64
INC float64
MAX float64
}
func NewModel(TB string, MIN, INC, MAX float64) *Model {
return &Model{TB: TB, MIN: MIN, INC: INC, MAX: MAX}
}
// -------------------------------------------------- // --------------------------------------------------
// Param // Param
// -------------------------------------------------- // --------------------------------------------------

View file

@ -28,7 +28,7 @@ func (p *parser) parseWhat() (mul []Expr, err error) {
for { for {
tok, lit, err := p.shouldBe(IDENT, THING, PARAM) tok, lit, err := p.shouldBe(IDENT, THING, PARAM, MODEL)
if err != nil { if err != nil {
return nil, &ParseError{Found: lit, Expected: []string{"table, or thing"}} return nil, &ParseError{Found: lit, Expected: []string{"table, or thing"}}
} }
@ -48,6 +48,11 @@ func (p *parser) parseWhat() (mul []Expr, err error) {
mul = append(mul, one) mul = append(mul, one)
} }
if p.is(tok, MODEL) {
one, _ := p.declare(MODEL, lit)
mul = append(mul, one)
}
// Check to see if the next token is a comma // Check to see if the next token is a comma
// and if not, then break out of the loop, // and if not, then break out of the loop,
// otherwise repeat until we find no comma. // otherwise repeat until we find no comma.

View file

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -136,6 +137,7 @@ func (s *scanner) scan() (tok Token, lit string, val interface{}) {
return OR, "OR", val return OR, "OR", val
default: default:
s.undo() s.undo()
return s.scanModel(ch)
} }
case '&': case '&':
chn := s.next() chn := s.next()
@ -461,6 +463,151 @@ func (s *scanner) scanThing(chp ...rune) (tok Token, lit string, val interface{}
} }
func (s *scanner) scanModel(chp ...rune) (tok Token, lit string, val interface{}) {
var com bool
var dot bool
var tbv string
var min float64 = 0
var inc float64 = 0
var max float64 = 0
// Create a buffer
var buf bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
if tok, tbv, _ = s.part(); tok == ILLEGAL {
buf.WriteString(tbv)
return ILLEGAL, buf.String(), val
} else {
buf.WriteString(tbv)
}
if ch := s.next(); ch == ':' {
buf.WriteRune(ch)
} else {
return ILLEGAL, buf.String(), val
}
if ch := s.next(); isSignal(ch) {
tok, lit, _ = s.scanSignal(ch)
buf.WriteString(lit)
max, _ = strconv.ParseFloat(lit, 64)
} else {
return ILLEGAL, buf.String(), val
}
if ch := s.next(); ch == ',' {
com = true
buf.WriteRune(ch)
if ch := s.next(); isSignal(ch) {
tok, lit, _ = s.scanSignal(ch)
buf.WriteString(lit)
inc, _ = strconv.ParseFloat(lit, 64)
} else {
return ILLEGAL, buf.String(), val
}
} else {
s.undo()
}
if ch := s.next(); ch == '.' {
dot = true
buf.WriteRune(ch)
if ch := s.next(); ch == '.' {
buf.WriteRune(ch)
if ch := s.next(); isSignal(ch) {
tok, lit, _ = s.scanSignal(ch)
buf.WriteString(lit)
min = max
max, _ = strconv.ParseFloat(lit, 64)
} else {
return ILLEGAL, buf.String(), val
}
} else {
return ILLEGAL, buf.String(), val
}
} else {
s.undo()
}
if ch := s.next(); ch == '|' {
buf.WriteRune(ch)
} else {
return ILLEGAL, buf.String(), val
}
// If we have a comma, but the
// value is below zero, we will
// error as this will cause an
// infinite loop in db.
if com == true && inc <= 0 {
return ILLEGAL, buf.String(), val
}
// If we have a min, and a max
// with .. notation, but no `inc`
// is specified, set the `inc` to
// a default of `1`.
if dot == true && inc <= 0 {
inc = 1
}
// If we have a comma, but no
// max value is specified then
// error, as we need a max with
// incrementing integer ids.
if com == true && dot == false {
return ILLEGAL, buf.String(), val
}
return MODEL, buf.String(), NewModel(tbv, min, inc, max)
}
func (s *scanner) scanSignal(chp ...rune) (tok Token, lit string, val interface{}) {
// Create a buffer
var buf bytes.Buffer
// Read passed in runes
for _, ch := range chp {
buf.WriteRune(ch)
}
// Read subsequent characters
for {
if ch := s.next(); ch == eof {
break
} else if isNumber(ch) {
buf.WriteRune(ch)
} else if ch == '.' {
if s.next() == '.' {
s.undo()
s.undo()
break
} else {
s.undo()
buf.WriteRune(ch)
}
} else {
s.undo()
break
}
}
return NUMBER, buf.String(), nil
}
func (s *scanner) scanNumber(chp ...rune) (tok Token, lit string, val interface{}) { func (s *scanner) scanNumber(chp ...rune) (tok Token, lit string, val interface{}) {
tok = NUMBER tok = NUMBER
@ -783,6 +930,11 @@ func isNumber(ch rune) bool {
return (ch >= '0' && ch <= '9') return (ch >= '0' && ch <= '9')
} }
// isSignal returns true if the rune is a number.
func isSignal(ch rune) bool {
return (ch >= '0' && ch <= '9') || ch == '-' || ch == '+'
}
// isLetter returns true if the rune is a letter. // isLetter returns true if the rune is a letter.
func isLetter(ch rune) bool { func isLetter(ch rune) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == 'µ' return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == 'µ'

View file

@ -37,6 +37,7 @@ const (
EXPR // something[0].value EXPR // something[0].value
IDENT // something IDENT // something
THING // @class:id THING // @class:id
MODEL // [person|1..1000]
STRING // "something" STRING // "something"
REGION // "a multiline \n string" REGION // "a multiline \n string"
NUMBER // 123456 NUMBER // 123456
@ -214,6 +215,7 @@ var tokens = [...]string{
EXPR: "EXPR", EXPR: "EXPR",
IDENT: "IDENT", IDENT: "IDENT",
THING: "THING", THING: "THING",
MODEL: "MODEL",
STRING: "STRING", STRING: "STRING",
REGION: "REGION", REGION: "REGION",
NUMBER: "NUMBER", NUMBER: "NUMBER",