Files
expr/parser.go
T
2018-07-30 08:46:56 +03:00

522 lines
11 KiB
Go

package expr
import (
"fmt"
"strconv"
"unicode/utf8"
)
type associativity int
const (
left associativity = iota + 1
right
)
type info struct {
precedence int
associativity associativity
}
var unaryOperators = map[string]info{
"not": {50, left},
"!": {50, left},
"-": {500, left},
"+": {500, left},
}
var binaryOperators = map[string]info{
"or": {10, left},
"||": {10, left},
"and": {15, left},
"&&": {15, left},
"|": {16, left},
"^": {17, left},
"&": {18, left},
"==": {20, left},
"!=": {20, left},
"<": {20, left},
">": {20, left},
">=": {20, left},
"<=": {20, left},
"not in": {20, left},
"in": {20, left},
"matches": {20, left},
"..": {25, left},
"+": {30, left},
"-": {30, left},
"~": {40, left},
"*": {60, left},
"/": {60, left},
"%": {60, left},
"**": {200, right},
}
type parser struct {
input string
tokens []token
position int
current token
options *options
}
type options struct {
names map[string]struct{}
funcs map[string]struct{}
}
// OptionFn for configuring parser.
type OptionFn func(p *options)
// Parse parses input into ast.
func Parse(input string, ops ...OptionFn) (Node, error) {
tokens, err := lex(input)
if err != nil {
return nil, err
}
p := &parser{
input: input,
tokens: tokens,
current: tokens[0],
options: &options{},
}
for _, op := range ops {
op(p.options)
}
node, err := p.parseExpression(0)
if err != nil {
return nil, err
}
if !p.isEOF() {
return nil, p.errorf("unexpected token %v", p.current)
}
return node, nil
}
// Names sets list of allowed names.
func Names(names ...string) OptionFn {
return func(o *options) {
set := make(map[string]struct{})
for _, name := range names {
set[name] = struct{}{}
}
o.names = set
}
}
// Funcs sets list of allowed function.
func Funcs(funcs ...string) OptionFn {
return func(o *options) {
set := make(map[string]struct{})
for _, fn := range funcs {
set[fn] = struct{}{}
}
o.funcs = set
}
}
func (p *parser) errorf(format string, args ...interface{}) error {
return &syntaxError{
message: fmt.Sprintf(format, args...),
input: p.input,
pos: p.current.pos,
}
}
func (p *parser) next() error {
p.position++
if p.position >= len(p.tokens) {
return p.errorf("unexpected end of expression")
}
p.current = p.tokens[p.position]
return nil
}
func (p *parser) expect(kind tokenKind, values ...string) error {
if p.current.is(kind, values...) {
return p.next()
}
return p.errorf("unexpected token %v", p.current)
}
func (p *parser) isEOF() bool {
return p.current.is(eof)
}
// parse functions
func (p *parser) parseExpression(precedence int) (Node, error) {
node, err := p.parsePrimary()
if err != nil {
return nil, err
}
token := p.current
for token.is(operator) {
if op, ok := binaryOperators[token.value]; ok {
if op.precedence >= precedence {
if err = p.next(); err != nil {
return nil, err
}
var expr Node
if op.associativity == left {
expr, err = p.parseExpression(op.precedence + 1)
if err != nil {
return nil, err
}
} else {
expr, err = p.parseExpression(op.precedence)
if err != nil {
return nil, err
}
}
node = binaryNode{operator: token.value, left: node, right: expr}
token = p.current
continue
}
}
break
}
if precedence == 0 {
node, err = p.parseConditionalExpression(node)
if err != nil {
return nil, err
}
}
return node, nil
}
func (p *parser) parsePrimary() (Node, error) {
token := p.current
if token.is(operator) {
if op, ok := unaryOperators[token.value]; ok {
if err := p.next(); err != nil {
return nil, err
}
expr, err := p.parseExpression(op.precedence)
if err != nil {
return nil, err
}
return p.parsePostfixExpression(unaryNode{operator: token.value, node: expr})
}
}
if token.is(punctuation, "(") {
if err := p.next(); err != nil {
return nil, err
}
expr, err := p.parseExpression(0)
if err != nil {
return nil, err
}
err = p.expect(punctuation, ")")
if err != nil {
return nil, p.errorf("an opened parenthesis is not properly closed")
}
return p.parsePostfixExpression(expr)
}
return p.parsePrimaryExpression()
}
func (p *parser) parseConditionalExpression(node Node) (Node, error) {
var err error
var expr1, expr2 Node
for p.current.is(punctuation, "?") {
if err := p.next(); err != nil {
return nil, err
}
if !p.current.is(punctuation, ":") {
expr1, err = p.parseExpression(0)
if err != nil {
return nil, err
}
if err := p.expect(punctuation, ":"); err != nil {
return nil, err
}
expr2, err = p.parseExpression(0)
if err != nil {
return nil, err
}
} else {
if err := p.next(); err != nil {
return nil, err
}
expr1 = node
expr2, err = p.parseExpression(0)
if err != nil {
return nil, err
}
}
node = conditionalNode{node, expr1, expr2}
}
return node, nil
}
func (p *parser) parsePrimaryExpression() (Node, error) {
var err error
var node Node
token := p.current
switch token.kind {
case name:
if err := p.next(); err != nil {
return nil, err
}
switch token.value {
case "true":
return boolNode{value: true}, nil
case "false":
return boolNode{value: false}, nil
case "nil":
return nilNode{}, nil
default:
if p.current.is(punctuation, "(") {
if p.options.funcs != nil {
if _, ok := p.options.funcs[token.value]; !ok {
return nil, p.errorf("unknown func %v", token.value)
}
}
arguments, err := p.parseArguments()
if err != nil {
return nil, err
}
node = functionNode{name: token.value, arguments: arguments}
} else {
if p.options.names != nil {
if _, ok := p.options.names[token.value]; !ok {
return nil, p.errorf("unknown name %v", token.value)
}
}
node = nameNode{name: token.value}
}
}
case number:
if err := p.next(); err != nil {
return nil, err
}
number, err := strconv.ParseFloat(token.value, 64)
if err != nil {
return nil, p.errorf("%v", err)
}
return numberNode{value: number}, nil
case text:
if err := p.next(); err != nil {
return nil, err
}
return textNode{value: token.value}, nil
default:
if token.is(punctuation, "[") {
node, err = p.parseArrayExpression()
if err != nil {
return nil, err
}
} else if token.is(punctuation, "{") {
node, err = p.parseMapExpression()
if err != nil {
return nil, err
}
} else {
return nil, p.errorf("unexpected token %v", token)
}
}
return p.parsePostfixExpression(node)
}
func (p *parser) parseArrayExpression() (Node, error) {
nodes, err := p.parseList("array items", "[", "]")
if err != nil {
return nil, err
}
return arrayNode{nodes}, nil
}
func (p *parser) parseMapExpression() (Node, error) {
err := p.expect(punctuation, "{")
if err != nil {
return nil, err
}
nodes := make([]pairNode, 0)
for !p.current.is(punctuation, "}") {
if len(nodes) > 0 {
err = p.expect(punctuation, ",")
if err != nil {
return nil, p.errorf("a map value must be followed by a comma")
}
}
var key Node
// a map key can be:
// * a number
// * a text
// * a name, which is equivalent to a string
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if p.current.is(number) || p.current.is(text) || p.current.is(name) {
key = identifierNode{p.current.value}
if err := p.next(); err != nil {
return nil, err
}
} else if p.current.is(punctuation, "(") {
key, err = p.parseExpression(0)
if err != nil {
return nil, err
}
} else {
return nil, p.errorf("a map key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token %v)", p.current)
}
err = p.expect(punctuation, ":")
if err != nil {
return nil, p.errorf("a map key must be followed by a colon (:)")
}
node, err := p.parseExpression(0)
if err != nil {
return nil, err
}
nodes = append(nodes, pairNode{key, node})
}
err = p.expect(punctuation, "}")
if err != nil {
return nil, err
}
return mapNode{nodes}, nil
}
func (p *parser) parsePostfixExpression(node Node) (Node, error) {
token := p.current
for token.is(punctuation) {
if token.value == "." {
if err := p.next(); err != nil {
return nil, err
}
token = p.current
if err := p.next(); err != nil {
return nil, err
}
if token.kind != name &&
// Operators like "not" and "matches" are valid method or property names,
//
// In other words, besides name token kind, operator kind could also be parsed as a property or method.
// This is because operators are processed by the lexer prior to names. So "not" in "foo.not()"
// or "matches" in "foo.matches" will be recognized as an operator first. But in fact, "not"
// and "matches" in such expressions shall be parsed as method or property names.
//
// And this ONLY works if the operator consists of valid characters for a property or method name.
//
// Other types, such as text kind and number kind, can't be parsed as property nor method names.
//
// As a result, if token is NOT an operator OR token.value is NOT a valid property or method name,
// an error shall be returned.
(token.kind != operator || !isValidIdentifier(token.value)) {
return nil, p.errorf("expected name")
}
property := identifierNode{value: token.value}
if p.current.is(punctuation, "(") {
arguments, err := p.parseArguments()
if err != nil {
return nil, err
}
node = methodNode{node: node, property: property, arguments: arguments}
} else {
node = propertyNode{node: node, property: property}
}
} else if token.value == "[" {
if err := p.next(); err != nil {
return nil, err
}
arg, err := p.parseExpression(0)
if err != nil {
return err, nil
}
node = propertyNode{node: node, property: arg}
err = p.expect(punctuation, "]")
if err != nil {
return nil, err
}
} else {
break
}
token = p.current
}
return node, nil
}
func isValidIdentifier(str string) bool {
if len(str) == 0 {
return false
}
h, w := utf8.DecodeRuneInString(str)
if !isAlphabetic(h) {
return false
}
for _, r := range str[w:] {
if !isAlphaNumeric(r) {
return false
}
}
return true
}
func (p *parser) parseArguments() ([]Node, error) {
return p.parseList("arguments", "(", ")")
}
func (p *parser) parseList(what, start, end string) ([]Node, error) {
err := p.expect(punctuation, start)
if err != nil {
return nil, err
}
nodes := make([]Node, 0)
for !p.current.is(punctuation, end) {
if len(nodes) > 0 {
err = p.expect(punctuation, ",")
if err != nil {
return nil, p.errorf("%v must be separated by a comma", what)
}
}
node, err := p.parseExpression(0)
if err != nil {
return nil, err
}
nodes = append(nodes, node)
}
err = p.expect(punctuation, end)
if err != nil {
return nil, err
}
return nodes, nil
}