Skip to content

Commit 6739584

Browse files
committed
lib: Add generic boolean expression parser and eval
Add a simple and generic boolean expression parser and evaluator. This is a first step in order to implement more complex kconfig expressions, but since we are likely to reuse the parser for other purposes in the future it's implemented as a generic boolean parser. Signed-off-by: Cyril Hrubis <[email protected]> Reviewed-by: Richard Palethorpe <[email protected]> Reviewed-by: Xiao Yang <[email protected]>
1 parent 54d1358 commit 6739584

File tree

4 files changed

+612
-0
lines changed

4 files changed

+612
-0
lines changed

include/tst_bool_expr.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (c) 2020 Cyril Hrubis <[email protected]>
4+
*/
5+
6+
#ifndef TST_BOOL_EXPR_H__
7+
#define TST_BOOL_EXPR_H__
8+
9+
enum tst_op {
10+
TST_OP_NOT,
11+
TST_OP_AND,
12+
TST_OP_OR,
13+
TST_OP_VAR,
14+
/* Used only internally */
15+
TST_OP_LPAR,
16+
TST_OP_RPAR,
17+
};
18+
19+
struct tst_expr_tok {
20+
enum tst_op op;
21+
const char *tok;
22+
size_t tok_len;
23+
struct tst_expr_tok *next;
24+
const void *priv;
25+
};
26+
27+
struct tst_expr {
28+
struct tst_expr_tok *rpn;
29+
struct tst_expr_tok buf[];
30+
};
31+
32+
/*
33+
* Parses an boolean expression and returns a simplified RPN version.
34+
*
35+
* If expression is not valid the call prints error into stderr and returns
36+
* NULL. On success pointer to an expression is returned which can be evaluated
37+
* by the tst_bool_expr_eval() function and has to be later freed by the
38+
* caller.
39+
*
40+
* The boolean expression can consists of:
41+
*
42+
* - unary negation opeartion !
43+
* - two binary operations & and |
44+
* - correct sequence of parentheses ()
45+
* - strings that are treated as boolean variables
46+
*
47+
* e.g. '(A | B) & C' or 'Variable_1 & Variable_2' are both a valid boolean
48+
* expressions.
49+
*
50+
* @expr String containing a boolean expression to be parsed.
51+
* @return Pointer to an RPN expression.
52+
*/
53+
struct tst_expr *tst_bool_expr_parse(const char *expr);
54+
55+
/*
56+
* Prints an string representation of the expression into a FILE.
57+
*
58+
* @param A FILE to print to.
59+
* @expr An expression to print.
60+
*/
61+
void tst_bool_expr_print(FILE *f, struct tst_expr *expr);
62+
63+
/*
64+
* Evaluates an expression given a map for variables.
65+
*
66+
* The call will fail if:
67+
* - map function returns -1 which indicates undefined variable
68+
* - the eval function runs out of stack
69+
*
70+
* @param expr Boolean expression in RPN.
71+
* @param map Mapping function for boolean variables.
72+
*
73+
* @return Returns 0 or 1 if expression was evaluated correctly and -1 on error.
74+
*/
75+
int tst_bool_expr_eval(struct tst_expr *expr,
76+
int (*map)(struct tst_expr_tok *var));
77+
78+
/*
79+
* Frees the memory allocated by the tst_bool_expr_parse().
80+
*
81+
* @param Boolean expression.
82+
*/
83+
void tst_bool_expr_free(struct tst_expr *expr);
84+
85+
#endif /* TST_BOOL_EXPR_H__ */

lib/newlib_tests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ test_exec_child
3333
test_kconfig
3434
variant
3535
test_guarded_buf
36+
tst_bool_expr

lib/newlib_tests/tst_bool_expr.c

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (c) 2017 Cyril Hrubis <[email protected]>
4+
*/
5+
6+
/*
7+
* Basic unit test for the bolean expression parser and evaluator.
8+
*/
9+
10+
#include <string.h>
11+
#include <stdio.h>
12+
#include "tst_test.h"
13+
#include "tst_bool_expr.h"
14+
15+
static int a, b, c;
16+
17+
static int map(struct tst_expr_tok *var)
18+
{
19+
if (!strncmp(var->tok, "A", var->tok_len))
20+
return a;
21+
22+
if (!strncmp(var->tok, "B", var->tok_len))
23+
return b;
24+
25+
if (!strncmp(var->tok, "C", var->tok_len))
26+
return c;
27+
28+
if (!strncmp(var->tok, "True", var->tok_len))
29+
return 1;
30+
31+
if (!strncmp(var->tok, "False", var->tok_len))
32+
return 0;
33+
34+
return -1;
35+
}
36+
37+
static void parse_fail(const char *expr)
38+
{
39+
struct tst_expr *res;
40+
41+
tst_res(TINFO, "Parsing '%s'", expr);
42+
43+
res = tst_bool_expr_parse(expr);
44+
45+
if (res) {
46+
printf("In RPN: ");
47+
tst_bool_expr_print(stdout, res);
48+
printf("\n");
49+
tst_bool_expr_free(res);
50+
tst_res(TFAIL, "Expression was parsed");
51+
} else {
52+
tst_res(TPASS, "Parser returned an error");
53+
}
54+
}
55+
56+
static void do_eval_test(const char *expr_str, int set_a, int set_b, int set_c, int exp_res)
57+
{
58+
struct tst_expr *expr;
59+
int res;
60+
61+
a = set_a;
62+
b = set_b;
63+
c = set_c;
64+
65+
tst_res(TINFO, "'%s' A=%i B=%i C=%i == %i", expr_str, a, b, c, exp_res);
66+
67+
expr = tst_bool_expr_parse(expr_str);
68+
69+
if (!expr) {
70+
tst_res(TFAIL, "Parser returned error");
71+
return;
72+
}
73+
74+
printf("In RPN: ");
75+
tst_bool_expr_print(stdout, expr);
76+
printf("\n");
77+
78+
res = tst_bool_expr_eval(expr, map);
79+
80+
if (res == exp_res)
81+
tst_res(TPASS, "Got %i", res);
82+
else
83+
tst_res(TFAIL, "Got %i", res);
84+
85+
tst_bool_expr_free(expr);
86+
}
87+
88+
static void do_test(void)
89+
{
90+
do_eval_test("(A | B) & !!C", 0, 0, 0, 0);
91+
do_eval_test("(A | B) & !!C", 1, 0, 1, 1);
92+
do_eval_test("!A & B", 1, 0, 0, 0);
93+
do_eval_test("!A & B", 0, 1, 0, 1);
94+
do_eval_test("A & !B", 1, 0, 0, 1);
95+
do_eval_test("!!A & !!B", 0, 1, 0, 0);
96+
do_eval_test("!!A & !!B", 1, 1, 0, 1);
97+
do_eval_test("!(A & B) & C", 1, 1, 0, 0);
98+
do_eval_test("A & (B | C)", 1, 1, 0, 1);
99+
do_eval_test("A & B | C", 1, 1, 0, 1);
100+
do_eval_test("((((A)))&(B))", 1, 1, 0, 1);
101+
do_eval_test(" A \t", 0, 0, 0, 0);
102+
do_eval_test("False & A", 1, 0, 0, 0);
103+
do_eval_test("! Undefined", 0, 0, 0, -1);
104+
105+
parse_fail("A!");
106+
parse_fail("A &");
107+
parse_fail("A B");
108+
parse_fail("A ) B");
109+
parse_fail("A ( B");
110+
parse_fail("A ( B )");
111+
parse_fail("A |");
112+
parse_fail("A ! B");
113+
parse_fail("A! & B");
114+
parse_fail("A & | B");
115+
parse_fail("A & (B |)");
116+
parse_fail("A & ( | B)");
117+
parse_fail("A & B &");
118+
parse_fail("((A )");
119+
parse_fail("& A");
120+
parse_fail("! &");
121+
parse_fail(")");
122+
parse_fail("| A");
123+
parse_fail("");
124+
}
125+
126+
static struct tst_test test = {
127+
.test_all = do_test,
128+
};

0 commit comments

Comments
 (0)