snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
309
server/pkg/ql/parser.go
Normal file
309
server/pkg/ql/parser.go
Normal file
@@ -0,0 +1,309 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user