3
0
corteza/server/pkg/ql/parser.go
2022-11-14 09:26:39 +01:00

310 lines
6.4 KiB
Go

package ql
import (
"fmt"
"strings"
)
type (
// Parser represents a parser.
Parser struct {
lexer *Lexer
tokbuf []Token
OnIdent IdentHandler
OnFunction FunctionHandler
// parenthesis level control
level uint
}
IdentHandler func(ident Ident) (Ident, error)
FunctionHandler func(ident function) (parserNode, error)
)
// NewParser returns a new instance of Parser.
func NewParser() *Parser {
p := &Parser{
OnIdent: func(ident Ident) (Ident, error) { return ident, nil },
OnFunction: func(ident function) (parserNode, error) { return ident, nil },
}
return p
}
// Removes oldest token in the buffer, adds new one and returns 2nd oldest
func (p *Parser) nextToken() Token {
var t Token
for {
t = p.lexer.Scan()
if !t.Is(WS) {
p.tokbuf = append(p.tokbuf[1:], t)
return p.tokbuf[0]
}
}
}
func (p *Parser) peekToken(s int) Token {
return p.tokbuf[s]
}
func (p *Parser) initLexer(s string) {
p.lexer = NewLexer(strings.NewReader(s))
p.tokbuf = make([]Token, 3)
for c := 1; c < cap(p.tokbuf); c++ {
// Fill the buffer
p.nextToken()
}
}
// Parse parses the given expression and returns the generated AST
func (p *Parser) Parse(s string) (*ASTNode, error) {
p.initLexer(s)
if set, err := p.parse(p.nextToken()); err != nil {
return nil, err
} else if len(set) == 1 {
err := set[0].Validate()
if err != nil {
return nil, err
}
return set[0].ToAST(), nil
} else {
err := set.Validate()
if err != nil {
return nil, err
}
return set.ToAST(), nil
}
}
// Peek ahead if there is an alias ident (<IDENT:AS>)
//
// @todo remove this; not used anywhere after report builder gets removed
func (p *Parser) peekIfAlias() bool {
var f, s = p.peekToken(1), p.peekToken(2)
return f.Is(IDENT) && strings.ToUpper(f.literal) == "AS" && s.Is(IDENT)
}
func (p *Parser) parse(t Token) (list parserNodes, err error) {
goto checkToken
next:
if p.peekToken(1).Is(COMMA) || p.peekIfAlias() {
// Peek ahead and exit on comma
return
}
if p.peekToken(1).Is(PARENTHESIS_CLOSE) {
// Peek ahead and exit on closed parenthesis
if p.level == 0 {
return nil, fmt.Errorf("closing unopened parenthesis in expression")
}
p.level--
return
}
t = p.nextToken()
checkToken:
switch t.code {
case EOF:
break
case WS:
// Ignore ws... next token...
goto next
case ILLEGAL:
return nil, fmt.Errorf("found an illegal token (%+v)", t)
case IDENT:
var ident parserNode
if ident, err = p.parseIdent(t); err != nil {
return nil, err
} else {
list = append(list, ident)
goto next
}
case LNULL:
list = append(list, lNull{})
goto next
case LBOOL:
list = append(list, lBoolean{value: evalBool(t.literal)})
goto next
case OPERATOR:
if len(list) > 0 {
// Merge with previous operator node
if prevOp, ok := list[len(list)-1].(operator); ok {
list[len(list)-1] = operator{kind: prevOp.kind + " " + t.literal}
goto next
}
}
list = append(list, operator{kind: t.literal})
goto next
case KEYWORD:
if keyword, err := p.parseKeyword(t); err != nil {
return nil, err
} else {
list = append(list, keyword)
}
goto next
case LNUMBER:
list = append(list, lNumber{value: t.literal})
goto next
case LSTRING:
list = append(list, lString{value: t.literal})
goto next
case PARENTHESIS_OPEN:
depth := p.level
p.level++
if sub, err := p.parse(p.nextToken()); err != nil {
return nil, err
} else {
list = append(list, sub)
}
// Allow parent level to continue parsing.
// Example: ((A) AND (B))
// +1 since PARENTHESIS_CLOSE decrease level
if (p.level + 1) != depth {
p.nextToken()
goto next
}
default:
return nil, fmt.Errorf("unexpected token while parsing expression (%v)", t)
}
return list, nil
}
func (p *Parser) parseIdent(t Token) (list parserNode, err error) {
if p.peekToken(1).Is(PARENTHESIS_OPEN) {
// Handle function calls: <IDENT><PARENTHESIS_OPEN>...
f := function{name: t.literal}
if f.arguments, err = p.parseSet(); err != nil {
return nil, err
} else {
return p.OnFunction(f)
}
}
i := Ident{Value: t.literal}
if p.peekToken(1).Is(DOT) {
p.nextToken()
i.Value += "."
l2 := p.nextToken()
if l2.Is(IDENT) {
i.Value += l2.literal
}
}
return p.OnIdent(i)
}
func (p *Parser) parseSet() (list parserNodeSet, err error) {
var expr parserNodes
var parenthesisOpened = false
next:
t := p.nextToken()
if p.peekIfAlias() {
return
}
switch t.code {
case WS:
goto next
case PARENTHESIS_OPEN:
p.level++
parenthesisOpened = true
goto next
case LNUMBER:
list = append(list, lNumber{value: t.literal})
goto next
case LSTRING:
list = append(list, lString{value: t.literal})
goto next
case OPERATOR:
list = append(list, operator{kind: t.literal})
goto next
case KEYWORD:
if keyword, err := p.parseKeyword(t); err != nil {
return nil, err
} else {
list = append(list, keyword)
}
goto next
case IDENT:
if p.peekToken(1).Is(OPERATOR) {
// Looks like we have an expression ahead of us
if parenthesisOpened {
// Expression will find closing parenthesis and dec. the level
// so, lets bump up the number
p.level++
}
if expr, err = p.parse(t); err != nil {
return
}
list = append(list, expr)
goto next
}
var ident parserNode
if ident, err = p.parseIdent(t); err != nil {
return nil, err
} else {
list = append(list, ident)
goto next
}
case COMMA:
goto next
case EOF:
return
case PARENTHESIS_CLOSE:
// Peek ahead and exit on closed parenthesis
if p.level == 0 {
return nil, fmt.Errorf("closing unopened parenthesis in set")
}
p.level--
return
default:
return nil, fmt.Errorf("unexpected token while parsing set (%v)", t)
}
}
func (p *Parser) parseKeyword(t Token) (list parserNode, err error) {
switch strings.ToUpper(t.literal) {
case "INTERVAL":
i := interval{value: p.nextToken().literal}
u := p.nextToken()
if u.code != IDENT {
return nil, fmt.Errorf("expecting identifier, got %v", t)
} else {
switch strings.ToUpper(u.literal) {
case "MICROSECOND", "SECOND", "MINUTE", "HOUR",
"DAY", "WEEK", "MONTH", "QUARTER", "YEAR",
"SECOND_MICROSECOND", "MINUTE_MICROSECOND", "MINUTE_SECOND", "HOUR_MICROSECOND", "HOUR_SECOND",
"HOUR_MINUTE", "DAY_MICROSECOND", "DAY_SECOND", "DAY_MINUTE", "DAY_HOUR", "YEAR_MONTH":
// All good
break
default:
return nil, fmt.Errorf("expecting interval unit, got %v", u.literal)
}
}
i.unit = u.literal
return i, nil
default:
return keyword{keyword: t.literal}, nil
}
}