blob: f6aeba0fad16016b5bb53853f1e19eebcbbcd1b8 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package parse
import (
"fmt"
"mynewt.apache.org/newt/util"
)
// expr ::= <unary><expr> | "("<expr>")" |
// <expr><binary><expr> | <ident> | <literal>
// ident ::= <printable-char> { <printable-char> }
// literal ::= """ <printable-char> { <printable-char> } """
// unary ::= "!"
// binary ::= "&&" | "^^" | "||" | "==" | "!=" | "<" | "<=" | ">" | ">="
type ParseCode int
const (
PARSE_NOT_EQUALS ParseCode = iota
PARSE_NOT
PARSE_EQUALS
PARSE_LT
PARSE_LTE
PARSE_GT
PARSE_GTE
PARSE_AND
PARSE_OR
PARSE_XOR
PARSE_NUMBER
PARSE_STRING
PARSE_IDENT
)
type Node struct {
Code ParseCode
Data string
Left *Node
Right *Node
}
func (n *Node) String() string {
s := ""
if n.Left != nil {
s += n.Left.String()
}
s += fmt.Sprintf("%s", n.Data)
if n.Right != nil {
s += n.Right.String()
}
return s
}
func (n *Node) RpnString() string {
s := fmt.Sprintf("<%s>", n.Data)
if n.Left != nil {
s += " " + n.Left.RpnString()
}
if n.Right != nil {
s += " " + n.Right.RpnString()
}
return s
}
// Searches a tokenized expression. The location of the first token that
// matches a member of the supplied token set is returned. This function does
// not descend into parenthesized expressions.
func findAnyToken(tokens []Token, any []TokenCode) (int, error) {
pcount := 0
for _, a := range any {
for i, t := range tokens {
if t.Code == TOKEN_LPAREN {
pcount++
} else if t.Code == TOKEN_RPAREN {
pcount--
if pcount < 0 {
return -1, fmt.Errorf("imbalanced parenthesis")
}
} else if pcount == 0 && t.Code == a {
return i, nil
}
}
}
return -1, nil
}
func binTokenToParse(t TokenCode) ParseCode {
return map[TokenCode]ParseCode{
TOKEN_NOT_EQUALS: PARSE_NOT_EQUALS,
TOKEN_EQUALS: PARSE_EQUALS,
TOKEN_LT: PARSE_LT,
TOKEN_LTE: PARSE_LTE,
TOKEN_GT: PARSE_GT,
TOKEN_GTE: PARSE_GTE,
TOKEN_AND: PARSE_AND,
TOKEN_OR: PARSE_OR,
TOKEN_XOR: PARSE_XOR,
}[t]
}
// Removes the outer layer of parentheses from a tokenized expression.
func stripParens(tokens []Token) ([]Token, error) {
if tokens[0].Code != TOKEN_LPAREN {
panic("internal error: stripParens() received unparenthesized string")
}
pcount := 1
for i := 1; i < len(tokens); i++ {
switch tokens[i].Code {
case TOKEN_LPAREN:
pcount++
case TOKEN_RPAREN:
pcount--
if pcount == 0 {
return tokens[1:i], nil
}
default:
}
}
return nil, fmt.Errorf("unterminated parenthesis")
}
var binaryTokens = []TokenCode{
// Lowest precedence.
TOKEN_AND,
TOKEN_XOR,
TOKEN_OR,
TOKEN_EQUALS,
TOKEN_NOT_EQUALS,
TOKEN_LT,
TOKEN_LTE,
TOKEN_GT,
TOKEN_GTE,
// Highest precedence.
}
func FindBinaryToken(tokens []Token) int {
binIdx, err := findAnyToken(tokens, binaryTokens)
if err != nil {
return -1
}
return binIdx
}
// Recursively parses a tokenized expression.
//
// @param tokens The sequence of tokens representing the
// expression to parse. This is acquired by a
// call to `Lex()`.
//
// @return *Node The expression parse tree.
func Parse(tokens []Token) (*Node, error) {
if len(tokens) == 0 {
return nil, nil
}
////// Terminal symbols.
if len(tokens) == 1 {
switch tokens[0].Code {
case TOKEN_NUMBER:
return &Node{
Code: PARSE_NUMBER,
Data: tokens[0].Text,
}, nil
case TOKEN_STRING:
return &Node{
Code: PARSE_STRING,
Data: tokens[0].Text,
}, nil
case TOKEN_IDENT:
return &Node{
Code: PARSE_IDENT,
Data: tokens[0].Text,
}, nil
default:
return nil, fmt.Errorf("invalid expression: %s", tokens[0].Text)
}
}
////// Nonterminal symbols.
// <expr><binary><expr>
binIdx, err := findAnyToken(tokens, binaryTokens)
if err != nil {
return nil, err
}
if binIdx == 0 || binIdx == len(tokens)-1 {
return nil, fmt.Errorf("binary operator %s at start or end",
tokens[binIdx].Text)
}
if binIdx != -1 {
n := &Node{
Code: binTokenToParse(tokens[binIdx].Code),
Data: tokens[binIdx].Text,
}
l, err := Parse(tokens[0:binIdx])
if err != nil {
return nil, err
}
r, err := Parse(tokens[binIdx+1 : len(tokens)])
if err != nil {
return nil, err
}
n.Left = l
n.Right = r
return n, nil
}
// <unary><expr>
if tokens[0].Code == TOKEN_NOT {
n := &Node{
Code: PARSE_NOT,
Data: tokens[0].Text,
}
r, err := Parse(tokens[1:])
if err != nil {
return nil, err
}
n.Right = r
return n, nil
}
// "("<expr>")"
if tokens[0].Code == TOKEN_LPAREN {
stripped, err := stripParens(tokens)
if err != nil {
return nil, err
}
return Parse(stripped)
}
return nil, fmt.Errorf("invalid expression")
}
// Evaluates two expressions into boolean values.
func evalTwo(expr1 *Node, expr2 *Node,
settings map[string]string) (bool, bool, error) {
v1, err := Eval(expr1, settings)
if err != nil {
return false, false, err
}
v2, err := Eval(expr2, settings)
if err != nil {
return false, false, err
}
return v1, v2, nil
}
func coerceToInt(n *Node, settings map[string]string) (int, error) {
switch n.Code {
case PARSE_NUMBER:
num, ok := util.AtoiNoOctTry(n.Data)
if !ok {
return 0,
util.FmtNewtError("expression contains invalid number: `%s`",
n.Data)
}
return num, nil
case PARSE_IDENT:
val := settings[n.Data]
num, ok := util.AtoiNoOctTry(val)
if !ok {
return 0,
util.FmtNewtError("setting %s has value `%s`, "+
"which is not a number", n.Data, val)
}
return num, nil
default:
return 0,
util.FmtNewtError("expression `%s` is not a valid number",
n.String())
}
}
func coerceTwoInts(left *Node, right *Node,
settings map[string]string, opStr string) (int, int, error) {
lnum, err := coerceToInt(left, settings)
if err != nil {
return 0, 0, util.FmtNewtError("cannot apply %s to `%s`; "+
"operand not a number", opStr, left.String())
}
rnum, err := coerceToInt(right, settings)
if err != nil {
return 0, 0, util.FmtNewtError("cannot apply %s to `%s`; "+
"operand not a number", opStr, right.String())
}
return lnum, rnum, nil
}
type equalsFn func(left *Node, right *Node, settings map[string]string) bool
type equalsEntry struct {
LeftCode ParseCode
RightCode ParseCode
Fn equalsFn
}
var equalsDispatch = []equalsEntry{
// <ident1> == <ident2>
// True if both syscfg settings have identical text values.
equalsEntry{
LeftCode: PARSE_IDENT,
RightCode: PARSE_IDENT,
Fn: func(left *Node, right *Node, settings map[string]string) bool {
return settings[left.Data] == settings[right.Data]
},
},
// <ident> == <number>
// True if the syscfg setting's value is a valid representation of the
// number on the right.
equalsEntry{
LeftCode: PARSE_IDENT,
RightCode: PARSE_NUMBER,
Fn: func(left *Node, right *Node, settings map[string]string) bool {
lnum, ok := util.AtoiNoOctTry(settings[left.Data])
if !ok {
return false
}
rnum, ok := util.AtoiNoOctTry(right.Data)
if !ok {
return false
}
return lnum == rnum
},
},
// <ident> == <string>
// True if the syscfg setting's text value is identical to the string on
// the right.
equalsEntry{
LeftCode: PARSE_IDENT,
RightCode: PARSE_STRING,
Fn: func(left *Node, right *Node, settings map[string]string) bool {
return settings[left.Data] == right.Data
},
},
// <number1> == <number2>
// True if both numbers have the same value.
equalsEntry{
LeftCode: PARSE_NUMBER,
RightCode: PARSE_NUMBER,
Fn: func(left *Node, right *Node, settings map[string]string) bool {
lnum, ok := util.AtoiNoOctTry(left.Data)
if !ok {
return false
}
rnum, ok := util.AtoiNoOctTry(right.Data)
if !ok {
return false
}
return lnum == rnum
},
},
// <number> == <string>
// True if the string is a valid representation of the number.
equalsEntry{
LeftCode: PARSE_NUMBER,
RightCode: PARSE_STRING,
Fn: func(left *Node, right *Node, settings map[string]string) bool {
return left.Data == right.Data
},
},
// <string1> == <string2>
// True if both strings are identical (case-sensitive).
equalsEntry{
LeftCode: PARSE_STRING,
RightCode: PARSE_STRING,
Fn: func(left *Node, right *Node, settings map[string]string) bool {
return left.Data == right.Data
},
},
}
func evalEqualsOnce(
left *Node, right *Node, settings map[string]string) (bool, bool) {
for _, entry := range equalsDispatch {
if entry.LeftCode == left.Code && entry.RightCode == right.Code {
return entry.Fn(left, right, settings), true
}
}
return false, false
}
// Evaluates an equals expression (`x == y`)
//
// @param left The fully-parsed left operand.
// @param left The fully-parsed right operand.
// @param settings The map of syscfg settings.
//
// @return bool Whether the expression evaluates to true.
func evalEquals(
left *Node, right *Node, settings map[string]string) (bool, error) {
// The equals operator has special semantics. Rather than evaluating both
// operands as booleans and then comparing, the behavior of this operator
// varies based on the types of operands. Perform a table lookup using the
// operand types, and call the appropriate comparison function if a match
// is found.
val, ok := evalEqualsOnce(left, right, settings)
if ok {
return val, nil
}
val, ok = evalEqualsOnce(right, left, settings)
if ok {
return val, nil
}
// No special procedure identified. Fallback to evaluating both operands
// as booleans and comparing the results.
booll, boolr, err := evalTwo(left, right, settings)
if err != nil {
return false, err
}
return booll == boolr, nil
}
// Evaluates a fully-parsed expression.
//
// @param node The root of the expression to evaluate.
// @param settings The map of syscfg settings.
//
// @return bool Whether the expression evaluates to true.
func Eval(expr *Node, settings map[string]string) (bool, error) {
switch expr.Code {
case PARSE_NOT:
r, err := Eval(expr.Right, settings)
if err != nil {
return false, err
}
return !r, nil
case PARSE_EQUALS:
return evalEquals(expr.Left, expr.Right, settings)
case PARSE_NOT_EQUALS:
v, err := evalEquals(expr.Left, expr.Right, settings)
if err != nil {
return false, err
}
return !v, nil
case PARSE_LT:
l, r, err := coerceTwoInts(expr.Left, expr.Right, settings, "<")
if err != nil {
return false, err
}
return l < r, nil
case PARSE_LTE:
l, r, err := coerceTwoInts(expr.Left, expr.Right, settings, "<=")
if err != nil {
return false, err
}
return l <= r, nil
case PARSE_GT:
l, r, err := coerceTwoInts(expr.Left, expr.Right, settings, ">")
if err != nil {
return false, err
}
return l > r, nil
case PARSE_GTE:
l, r, err := coerceTwoInts(expr.Left, expr.Right, settings, ">=")
if err != nil {
return false, err
}
return l >= r, nil
case PARSE_AND:
l, r, err := evalTwo(expr.Left, expr.Right, settings)
if err != nil {
return false, err
}
return l && r, nil
case PARSE_OR:
l, r, err := evalTwo(expr.Left, expr.Right, settings)
if err != nil {
return false, err
}
return l || r, nil
case PARSE_XOR:
l, r, err := evalTwo(expr.Left, expr.Right, settings)
if err != nil {
return false, err
}
return (l && !r) || (!l && r), nil
case PARSE_NUMBER:
num, ok := util.AtoiNoOctTry(expr.Data)
return ok && num != 0, nil
case PARSE_STRING:
return ValueIsTrue(expr.Data), nil
case PARSE_IDENT:
val := settings[expr.Data]
return ValueIsTrue(val), nil
default:
return false, fmt.Errorf("invalid parse code: %d", expr.Code)
}
}
// Parses and evaluates string containing a syscfg expression.
//
// @param expr The expression to parse.
// @param settings The map of syscfg settings.
//
// @return bool Whether the expression evaluates to true.
func ParseAndEval(expr string, settings map[string]string) (bool, error) {
tokens, err := Lex(expr)
if err != nil {
return false, err
}
n, err := Parse(tokens)
if err != nil {
return false, fmt.Errorf("error parsing [%s]: %s\n", expr, err.Error())
}
v, err := Eval(n, settings)
return v, err
}
// Evaluates the truthfulness of a text expression.
func ValueIsTrue(val string) bool {
if val == "" {
return false
}
i, ok := util.AtoiNoOctTry(val)
if ok && i == 0 {
return false
}
return true
}