From 39119ad8ecd00a9c19fb173c78cb4a8d22a4540a Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 10 Oct 2016 22:52:18 -0700 Subject: edify: Some clean-ups to libedify. - Remove dead declarations in expr.h: SetError(), GetError(), ClearError(). - Remove the declaration of Build() out of expr.h. - Use std::unordered_map to implement RegisterFunction() and FindFunction(); kill FinishRegistration(). - Add a testcase for calling unknown functions. Test: mmma bootable/recovery; recovery_component_test passes. Change-Id: I9af6825ae677f92b22d716a4a5682f58522af03b --- edify/README | 108 ------------------------------------- edify/README.md | 111 +++++++++++++++++++++++++++++++++++++++ edify/edify_parser.cpp | 1 - edify/expr.cpp | 65 +++++------------------ edify/expr.h | 40 +++----------- edify/parser.yy | 29 ++++++++-- tests/component/edify_test.cpp | 28 ++++++++-- tests/component/updater_test.cpp | 1 - updater/updater.cpp | 1 - 9 files changed, 178 insertions(+), 206 deletions(-) delete mode 100644 edify/README create mode 100644 edify/README.md diff --git a/edify/README b/edify/README deleted file mode 100644 index 810455cca..000000000 --- a/edify/README +++ /dev/null @@ -1,108 +0,0 @@ -Update scripts (from donut onwards) are written in a new little -scripting language ("edify") that is superficially somewhat similar to -the old one ("amend"). This is a brief overview of the new language. - -- The entire script is a single expression. - -- All expressions are string-valued. - -- String literals appear in double quotes. \n, \t, \", and \\ are - understood, as are hexadecimal escapes like \x4a. - -- String literals consisting of only letters, numbers, colons, - underscores, slashes, and periods don't need to be in double quotes. - -- The following words are reserved: - - if then else endif - - They have special meaning when unquoted. (In quotes, they are just - string literals.) - -- When used as a boolean, the empty string is "false" and all other - strings are "true". - -- All functions are actually macros (in the Lisp sense); the body of - the function can control which (if any) of the arguments are - evaluated. This means that functions can act as control - structures. - -- Operators (like "&&" and "||") are just syntactic sugar for builtin - functions, so they can act as control structures as well. - -- ";" is a binary operator; evaluating it just means to first evaluate - the left side, then the right. It can also appear after any - expression. - -- Comments start with "#" and run to the end of the line. - - - -Some examples: - -- There's no distinction between quoted and unquoted strings; the - quotes are only needed if you want characters like whitespace to - appear in the string. The following expressions all evaluate to the - same string. - - "a b" - a + " " + b - "a" + " " + "b" - "a\x20b" - a + "\x20b" - concat(a, " ", "b") - "concat"(a, " ", "b") - - As shown in the last example, function names are just strings, - too. They must be string *literals*, however. This is not legal: - - ("con" + "cat")(a, " ", b) # syntax error! - - -- The ifelse() builtin takes three arguments: it evaluates exactly - one of the second and third, depending on whether the first one is - true. There is also some syntactic sugar to make expressions that - look like if/else statements: - - # these are all equivalent - ifelse(something(), "yes", "no") - if something() then yes else no endif - if something() then "yes" else "no" endif - - The else part is optional. - - if something() then "yes" endif # if something() is false, - # evaluates to false - - ifelse(condition(), "", abort()) # abort() only called if - # condition() is false - - The last example is equivalent to: - - assert(condition()) - - -- The && and || operators can be used similarly; they evaluate their - second argument only if it's needed to determine the truth of the - expression. Their value is the value of the last-evaluated - argument: - - file_exists("/data/system/bad") && delete("/data/system/bad") - - file_exists("/data/system/missing") || create("/data/system/missing") - - get_it() || "xxx" # returns value of get_it() if that value is - # true, otherwise returns "xxx" - - -- The purpose of ";" is to simulate imperative statements, of course, - but the operator can be used anywhere. Its value is the value of - its right side: - - concat(a;b;c, d, e;f) # evaluates to "cdf" - - A more useful example might be something like: - - ifelse(condition(), - (first_step(); second_step();), # second ; is optional - alternative_procedure()) diff --git a/edify/README.md b/edify/README.md new file mode 100644 index 000000000..b3330e23a --- /dev/null +++ b/edify/README.md @@ -0,0 +1,111 @@ +edify +===== + +Update scripts (from donut onwards) are written in a new little +scripting language ("edify") that is superficially somewhat similar to +the old one ("amend"). This is a brief overview of the new language. + +- The entire script is a single expression. + +- All expressions are string-valued. + +- String literals appear in double quotes. \n, \t, \", and \\ are + understood, as are hexadecimal escapes like \x4a. + +- String literals consisting of only letters, numbers, colons, + underscores, slashes, and periods don't need to be in double quotes. + +- The following words are reserved: + + if then else endif + + They have special meaning when unquoted. (In quotes, they are just + string literals.) + +- When used as a boolean, the empty string is "false" and all other + strings are "true". + +- All functions are actually macros (in the Lisp sense); the body of + the function can control which (if any) of the arguments are + evaluated. This means that functions can act as control + structures. + +- Operators (like "&&" and "||") are just syntactic sugar for builtin + functions, so they can act as control structures as well. + +- ";" is a binary operator; evaluating it just means to first evaluate + the left side, then the right. It can also appear after any + expression. + +- Comments start with "#" and run to the end of the line. + + + +Some examples: + +- There's no distinction between quoted and unquoted strings; the + quotes are only needed if you want characters like whitespace to + appear in the string. The following expressions all evaluate to the + same string. + + "a b" + a + " " + b + "a" + " " + "b" + "a\x20b" + a + "\x20b" + concat(a, " ", "b") + "concat"(a, " ", "b") + + As shown in the last example, function names are just strings, + too. They must be string *literals*, however. This is not legal: + + ("con" + "cat")(a, " ", b) # syntax error! + + +- The ifelse() builtin takes three arguments: it evaluates exactly + one of the second and third, depending on whether the first one is + true. There is also some syntactic sugar to make expressions that + look like if/else statements: + + # these are all equivalent + ifelse(something(), "yes", "no") + if something() then yes else no endif + if something() then "yes" else "no" endif + + The else part is optional. + + if something() then "yes" endif # if something() is false, + # evaluates to false + + ifelse(condition(), "", abort()) # abort() only called if + # condition() is false + + The last example is equivalent to: + + assert(condition()) + + +- The && and || operators can be used similarly; they evaluate their + second argument only if it's needed to determine the truth of the + expression. Their value is the value of the last-evaluated + argument: + + file_exists("/data/system/bad") && delete("/data/system/bad") + + file_exists("/data/system/missing") || create("/data/system/missing") + + get_it() || "xxx" # returns value of get_it() if that value is + # true, otherwise returns "xxx" + + +- The purpose of ";" is to simulate imperative statements, of course, + but the operator can be used anywhere. Its value is the value of + its right side: + + concat(a;b;c, d, e;f) # evaluates to "cdf" + + A more useful example might be something like: + + ifelse(condition(), + (first_step(); second_step();), # second ; is optional + alternative_procedure()) diff --git a/edify/edify_parser.cpp b/edify/edify_parser.cpp index 15342d3fa..1b89cf21d 100644 --- a/edify/edify_parser.cpp +++ b/edify/edify_parser.cpp @@ -45,7 +45,6 @@ static void ExprDump(int depth, const Expr* n, const std::string& script) { int main(int argc, char** argv) { RegisterBuiltins(); - FinishRegistration(); if (argc != 2) { printf("Usage: %s \n", argv[0]); diff --git a/edify/expr.cpp b/edify/expr.cpp index aa3a55a87..4000bc4db 100644 --- a/edify/expr.cpp +++ b/edify/expr.cpp @@ -14,20 +14,20 @@ * limitations under the License. */ -#include -#include +#include "expr.h" + +#include #include #include -#include +#include #include #include +#include #include #include -#include "expr.h" - // Functions should: // // - return a malloc()'d string @@ -319,61 +319,22 @@ Value* Literal(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(strdup(name)); } -Expr* Build(Function fn, YYLTYPE loc, int count, ...) { - va_list v; - va_start(v, count); - Expr* e = reinterpret_cast(malloc(sizeof(Expr))); - e->fn = fn; - e->name = "(operator)"; - e->argc = count; - e->argv = reinterpret_cast(malloc(count * sizeof(Expr*))); - int i; - for (i = 0; i < count; ++i) { - e->argv[i] = va_arg(v, Expr*); - } - va_end(v); - e->start = loc.start; - e->end = loc.end; - return e; -} - // ----------------------------------------------------------------- // the function table // ----------------------------------------------------------------- -static int fn_entries = 0; -static int fn_size = 0; -NamedFunction* fn_table = NULL; +static std::unordered_map fn_table; -void RegisterFunction(const char* name, Function fn) { - if (fn_entries >= fn_size) { - fn_size = fn_size*2 + 1; - fn_table = reinterpret_cast(realloc(fn_table, fn_size * sizeof(NamedFunction))); - } - fn_table[fn_entries].name = name; - fn_table[fn_entries].fn = fn; - ++fn_entries; +void RegisterFunction(const std::string& name, Function fn) { + fn_table[name] = fn; } -static int fn_entry_compare(const void* a, const void* b) { - const char* na = ((const NamedFunction*)a)->name; - const char* nb = ((const NamedFunction*)b)->name; - return strcmp(na, nb); -} - -void FinishRegistration() { - qsort(fn_table, fn_entries, sizeof(NamedFunction), fn_entry_compare); -} - -Function FindFunction(const char* name) { - NamedFunction key; - key.name = name; - NamedFunction* nf = reinterpret_cast(bsearch(&key, fn_table, fn_entries, - sizeof(NamedFunction), fn_entry_compare)); - if (nf == NULL) { - return NULL; +Function FindFunction(const std::string& name) { + if (fn_table.find(name) == fn_table.end()) { + return nullptr; + } else { + return fn_table[name]; } - return nf->fn; } void RegisterBuiltins() { diff --git a/edify/expr.h b/edify/expr.h index f045d9386..cd6139a18 100644 --- a/edify/expr.h +++ b/edify/expr.h @@ -21,11 +21,6 @@ #include #include "error_code.h" -#include "yydefs.h" - -#define MAX_STRING_LEN 1024 - -typedef struct Expr Expr; struct State { State(const std::string& script, void* cookie); @@ -56,14 +51,15 @@ struct State { #define VAL_STRING 1 // data will be NULL-terminated; size doesn't count null #define VAL_BLOB 2 -typedef struct { +struct Value { int type; ssize_t size; char* data; -} Value; +}; -typedef Value* (*Function)(const char* name, State* state, - int argc, Expr* argv[]); +struct Expr; + +using Function = Value* (*)(const char* name, State* state, int argc, Expr* argv[]); struct Expr { Function fn; @@ -100,43 +96,21 @@ Value* EqualityFn(const char* name, State* state, int argc, Expr* argv[]); Value* InequalityFn(const char* name, State* state, int argc, Expr* argv[]); Value* SequenceFn(const char* name, State* state, int argc, Expr* argv[]); -// Convenience function for building expressions with a fixed number -// of arguments. -Expr* Build(Function fn, YYLTYPE loc, int count, ...); - // Global builtins, registered by RegisterBuiltins(). Value* IfElseFn(const char* name, State* state, int argc, Expr* argv[]); Value* AssertFn(const char* name, State* state, int argc, Expr* argv[]); Value* AbortFn(const char* name, State* state, int argc, Expr* argv[]); - -// For setting and getting the global error string (when returning -// NULL from a function). -void SetError(const char* message); // makes a copy -const char* GetError(); // retains ownership -void ClearError(); - - -typedef struct { - const char* name; - Function fn; -} NamedFunction; - // Register a new function. The same Function may be registered under // multiple names, but a given name should only be used once. -void RegisterFunction(const char* name, Function fn); +void RegisterFunction(const std::string& name, Function fn); // Register all the builtins. void RegisterBuiltins(); -// Call this after all calls to RegisterFunction() but before parsing -// any scripts to finish building the function table. -void FinishRegistration(); - // Find the Function for a given name; return NULL if no such function // exists. -Function FindFunction(const char* name); - +Function FindFunction(const std::string& name); // --- convenience functions for use in functions --- diff --git a/edify/parser.yy b/edify/parser.yy index 098a6370a..58a8dec65 100644 --- a/edify/parser.yy +++ b/edify/parser.yy @@ -33,6 +33,25 @@ struct yy_buffer_state; void yy_switch_to_buffer(struct yy_buffer_state* new_buffer); struct yy_buffer_state* yy_scan_string(const char* yystr); +// Convenience function for building expressions with a fixed number +// of arguments. +static Expr* Build(Function fn, YYLTYPE loc, size_t count, ...) { + va_list v; + va_start(v, count); + Expr* e = static_cast(malloc(sizeof(Expr))); + e->fn = fn; + e->name = "(operator)"; + e->argc = count; + e->argv = static_cast(malloc(count * sizeof(Expr*))); + for (size_t i = 0; i < count; ++i) { + e->argv[i] = va_arg(v, Expr*); + } + va_end(v); + e->start = loc.start; + e->end = loc.end; + return e; +} + %} %locations @@ -70,7 +89,7 @@ input: expr { *root = $1; } ; expr: STRING { - $$ = reinterpret_cast(malloc(sizeof(Expr))); + $$ = static_cast(malloc(sizeof(Expr))); $$->fn = Literal; $$->name = $1; $$->argc = 0; @@ -91,9 +110,9 @@ expr: STRING { | IF expr THEN expr ENDIF { $$ = Build(IfElseFn, @$, 2, $2, $4); } | IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); } | STRING '(' arglist ')' { - $$ = reinterpret_cast(malloc(sizeof(Expr))); + $$ = static_cast(malloc(sizeof(Expr))); $$->fn = FindFunction($1); - if ($$->fn == NULL) { + if ($$->fn == nullptr) { char buffer[256]; snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1); yyerror(root, error_count, buffer); @@ -113,12 +132,12 @@ arglist: /* empty */ { } | expr { $$.argc = 1; - $$.argv = reinterpret_cast(malloc(sizeof(Expr*))); + $$.argv = static_cast(malloc(sizeof(Expr*))); $$.argv[0] = $1; } | arglist ',' expr { $$.argc = $1.argc + 1; - $$.argv = reinterpret_cast(realloc($$.argv, $$.argc * sizeof(Expr*))); + $$.argv = static_cast(realloc($$.argv, $$.argc * sizeof(Expr*))); $$.argv[$$.argc-1] = $3; } ; diff --git a/tests/component/edify_test.cpp b/tests/component/edify_test.cpp index ede2ecb0a..a4dbb9fbe 100644 --- a/tests/component/edify_test.cpp +++ b/tests/component/edify_test.cpp @@ -22,17 +22,18 @@ static void expect(const char* expr_str, const char* expected) { Expr* e; - int error_count; - EXPECT_EQ(parse_string(expr_str, &e, &error_count), 0); + int error_count = 0; + EXPECT_EQ(0, parse_string(expr_str, &e, &error_count)); + EXPECT_EQ(0, error_count); State state(expr_str, nullptr); char* result = Evaluate(&state, e); if (expected == nullptr) { - EXPECT_EQ(result, nullptr); + EXPECT_EQ(nullptr, result); } else { - EXPECT_STREQ(result, expected); + EXPECT_STREQ(expected, result); } free(result); @@ -42,7 +43,6 @@ class EdifyTest : public ::testing::Test { protected: virtual void SetUp() { RegisterBuiltins(); - FinishRegistration(); } }; @@ -149,3 +149,21 @@ TEST_F(EdifyTest, big_string) { expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str()); } +TEST_F(EdifyTest, unknown_function) { + // unknown function + const char* script1 = "unknown_function()"; + Expr* expr; + int error_count = 0; + EXPECT_EQ(1, parse_string(script1, &expr, &error_count)); + EXPECT_EQ(1, error_count); + + const char* script2 = "abc; unknown_function()"; + error_count = 0; + EXPECT_EQ(1, parse_string(script2, &expr, &error_count)); + EXPECT_EQ(1, error_count); + + const char* script3 = "unknown_function1() || yes"; + error_count = 0; + EXPECT_EQ(1, parse_string(script3, &expr, &error_count)); + EXPECT_EQ(1, error_count); +} diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp index bd6534b3b..64a6b37ce 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -54,7 +54,6 @@ class UpdaterTest : public ::testing::Test { virtual void SetUp() { RegisterBuiltins(); RegisterInstallFunctions(); - FinishRegistration(); } }; diff --git a/updater/updater.cpp b/updater/updater.cpp index 45e31e097..c752ebbf3 100644 --- a/updater/updater.cpp +++ b/updater/updater.cpp @@ -111,7 +111,6 @@ int main(int argc, char** argv) { RegisterInstallFunctions(); RegisterBlockImageFunctions(); RegisterDeviceExtensions(); - FinishRegistration(); // Parse the script. -- cgit v1.2.3