mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
this affects the scanner/parser and makes it produce signed ints rather than uints, and so it affects all code that is expecting unsighed ints to come out of the parser.
305 lines
6.9 KiB
Go
305 lines
6.9 KiB
Go
package pql
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// TimeFormat is the go-style time format used to parse string dates.
|
|
const TimeFormat = "2006-01-02T15:04"
|
|
|
|
// Parser represents a parser for the PQL language.
|
|
type Parser struct {
|
|
scanner *bufScanner
|
|
}
|
|
|
|
// NewParser returns a new instance of Parser.
|
|
func NewParser(r io.Reader) *Parser {
|
|
return &Parser{
|
|
scanner: newBufScanner(r),
|
|
}
|
|
}
|
|
|
|
// ParseString parses s into a query.
|
|
func ParseString(s string) (*Query, error) {
|
|
return NewParser(strings.NewReader(s)).Parse()
|
|
}
|
|
|
|
// Parse parses the next node in the query.
|
|
func (p *Parser) Parse() (*Query, error) {
|
|
q := &Query{}
|
|
for {
|
|
call, err := p.parseCall()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
q.Calls = append(q.Calls, call)
|
|
}
|
|
|
|
// Require at least one call.
|
|
if len(q.Calls) == 0 {
|
|
return nil, io.ErrUnexpectedEOF
|
|
}
|
|
|
|
return q, nil
|
|
}
|
|
|
|
// parseCall parses the next function call.
|
|
func (p *Parser) parseCall() (*Call, error) {
|
|
var c Call
|
|
|
|
// Read call name.
|
|
tok, pos, lit := p.scanIgnoreWhitespace()
|
|
if tok == EOF {
|
|
return nil, io.EOF
|
|
} else if tok != IDENT {
|
|
return nil, &ParseError{Message: fmt.Sprintf("expected identifier, found: %s", lit), Pos: pos}
|
|
}
|
|
c.Name = lit
|
|
|
|
// Scan opening parenthesis.
|
|
if err := p.expect(LPAREN); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse children first.
|
|
children, err := p.parseChildren()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.Children = children
|
|
|
|
// If next token is a closing paren then exit.
|
|
if tok, pos, lit := p.scanIgnoreWhitespace(); tok == RPAREN {
|
|
return &c, nil
|
|
} else if tok == IDENT {
|
|
p.unscan(1)
|
|
} else if tok != COMMA {
|
|
return nil, parseErrorf(pos, "expected comma, right paren, or identifier, found %q", lit)
|
|
}
|
|
|
|
// Parse key/value arguments.
|
|
args, err := p.parseArgs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.Args = args
|
|
|
|
// Scan closing parenthesis.
|
|
if err := p.expect(RPAREN); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &c, nil
|
|
}
|
|
|
|
// parseChildren parses call children.
|
|
func (p *Parser) parseChildren() ([]*Call, error) {
|
|
var offset int
|
|
var children []*Call
|
|
for {
|
|
// Ensure next two tokens are IDENT+LPAREN.
|
|
if tok, _, _ := p.scanIgnoreWhitespace(); tok != IDENT {
|
|
p.unscanIgnoreWhitespace(1 + offset)
|
|
return children, nil
|
|
}
|
|
if tok, _, _ := p.scan(); tok != LPAREN {
|
|
p.unscanIgnoreWhitespace(2 + offset)
|
|
return children, nil
|
|
}
|
|
|
|
// Push tokens back on scanner and parse as a call.
|
|
p.unscan(2)
|
|
child, err := p.parseCall()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
children = append(children, child)
|
|
|
|
// Exit if closing paren.
|
|
if tok, pos, lit := p.scanIgnoreWhitespace(); tok == RPAREN {
|
|
p.unscan(1)
|
|
return children, nil
|
|
} else if tok != COMMA {
|
|
return nil, parseErrorf(pos, "expected comma or right paren, found %q", lit)
|
|
}
|
|
|
|
// Make sure comma is unscanned.
|
|
offset = 1
|
|
}
|
|
}
|
|
|
|
// parseArgs parses key/value arguments.
|
|
func (p *Parser) parseArgs() (map[string]interface{}, error) {
|
|
args := make(map[string]interface{})
|
|
for {
|
|
// Parse key.
|
|
tok, pos, lit := p.scanIgnoreWhitespace()
|
|
if tok == RPAREN {
|
|
p.unscan(1)
|
|
return args, nil
|
|
} else if tok != IDENT {
|
|
return nil, parseErrorf(pos, "expected argument key, found %q", lit)
|
|
}
|
|
key := lit
|
|
|
|
// Expect '=' next.
|
|
if tok, pos, lit := p.scanIgnoreWhitespace(); tok != EQ {
|
|
return nil, parseErrorf(pos, "expected equals sign, found %q", lit)
|
|
}
|
|
|
|
// Parse value.
|
|
var value interface{}
|
|
tok, pos, lit = p.scanIgnoreWhitespace()
|
|
switch tok {
|
|
case IDENT:
|
|
if lit == "true" {
|
|
value = true
|
|
} else if lit == "false" {
|
|
value = false
|
|
} else if lit == "null" {
|
|
value = nil
|
|
} else {
|
|
value = lit
|
|
}
|
|
case STRING:
|
|
value = lit
|
|
case INTEGER:
|
|
v, err := strconv.ParseInt(lit, 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value = v
|
|
case FLOAT:
|
|
v, err := strconv.ParseFloat(lit, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value = v
|
|
case LBRACK:
|
|
v, err := p.parseList()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value = v
|
|
default:
|
|
return nil, parseErrorf(pos, "invalid argument value: %q", lit)
|
|
}
|
|
|
|
// Ensure key doesn't already exist.
|
|
if _, ok := args[key]; ok {
|
|
return nil, parseErrorf(pos, "argument key already used: %s", key)
|
|
}
|
|
|
|
// Add key/value pair to arguments.
|
|
args[key] = value
|
|
|
|
// Exit if closing paren.
|
|
if tok, pos, lit := p.scanIgnoreWhitespace(); tok == RPAREN {
|
|
p.unscan(1)
|
|
return args, nil
|
|
} else if tok != COMMA {
|
|
return nil, parseErrorf(pos, "expected comma or right paren, found %q", lit)
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseList parses a list of primitives. This is used by the TopN() filters.
|
|
func (p *Parser) parseList() ([]interface{}, error) {
|
|
var values []interface{}
|
|
for {
|
|
// Read next value.
|
|
tok, pos, lit := p.scanIgnoreWhitespace()
|
|
switch tok {
|
|
case IDENT:
|
|
if lit == "true" {
|
|
values = append(values, true)
|
|
} else if lit == "false" {
|
|
values = append(values, false)
|
|
} else {
|
|
values = append(values, lit)
|
|
}
|
|
case STRING:
|
|
values = append(values, lit)
|
|
case INTEGER:
|
|
v, err := strconv.ParseInt(lit, 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
values = append(values, v)
|
|
default:
|
|
return nil, parseErrorf(pos, "invalid list value: %q", lit)
|
|
}
|
|
|
|
// Expect a comma or closing bracket next.
|
|
if tok, pos, lit := p.scanIgnoreWhitespace(); tok == RBRACK {
|
|
break
|
|
} else if tok != COMMA {
|
|
return nil, parseErrorf(pos, "expected comma, found %q", lit)
|
|
}
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
// scan returns the next token from the scanner.
|
|
func (p *Parser) scan() (tok Token, pos Pos, lit string) { return p.scanner.Scan() }
|
|
|
|
// scanIgnoreWhitespace returns the next non-whitespace token from the scanner.
|
|
func (p *Parser) scanIgnoreWhitespace() (tok Token, pos Pos, lit string) {
|
|
tok, pos, lit = p.scan()
|
|
if tok == WS {
|
|
tok, pos, lit = p.scan()
|
|
}
|
|
return
|
|
}
|
|
|
|
// unscan returns the last n tokens back to the scanner.
|
|
func (p *Parser) unscan(n int) {
|
|
for i := 0; i < n; i++ {
|
|
p.scanner.unscan()
|
|
}
|
|
}
|
|
|
|
// unscanIgnoreWhitespace returns the last n non-WS tokens back to the scanner.
|
|
func (p *Parser) unscanIgnoreWhitespace(n int) {
|
|
for i := 0; i < n; {
|
|
p.scanner.unscan()
|
|
if tok, _, _ := p.scanner.curr(); tok != WS {
|
|
i++
|
|
}
|
|
}
|
|
}
|
|
|
|
// expect returns an error if the next token is not exp.
|
|
func (p *Parser) expect(exp Token) error {
|
|
if tok, pos, lit := p.scan(); tok != exp {
|
|
return parseErrorf(pos, "expected %s, found %q", exp.String(), lit)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pos returns the current position.
|
|
func (p *Parser) pos() Pos { return p.scanner.pos() }
|
|
|
|
// ParseError represents an error that occurred while parsing a PQL query.
|
|
type ParseError struct {
|
|
Message string
|
|
Pos Pos
|
|
}
|
|
|
|
// Error returns a string representation of e.
|
|
func (e *ParseError) Error() string {
|
|
return fmt.Sprintf("%s occurred at line %d, char %d", e.Message, e.Pos.Line+1, e.Pos.Char+1)
|
|
}
|
|
|
|
// parseErrorf returns a formatted parse error.
|
|
func parseErrorf(pos Pos, format string, args ...interface{}) *ParseError {
|
|
return &ParseError{
|
|
Message: fmt.Sprintf(format, args...),
|
|
Pos: pos,
|
|
}
|
|
}
|