diff options
Diffstat (limited to '')
-rw-r--r-- | edify/Android.mk | 39 | ||||
-rw-r--r-- | edify/README | 108 | ||||
-rw-r--r-- | edify/expr.c | 432 | ||||
-rw-r--r-- | edify/expr.h | 126 | ||||
-rw-r--r-- | edify/lexer.l | 110 | ||||
-rw-r--r-- | edify/main.c | 213 | ||||
-rw-r--r-- | edify/parser.y | 130 | ||||
-rw-r--r-- | edify/yydefs.h | 36 |
8 files changed, 1194 insertions, 0 deletions
diff --git a/edify/Android.mk b/edify/Android.mk new file mode 100644 index 000000000..fac0ba712 --- /dev/null +++ b/edify/Android.mk @@ -0,0 +1,39 @@ +# Copyright 2009 The Android Open Source Project + +LOCAL_PATH := $(call my-dir) + +edify_src_files := \ + lexer.l \ + parser.y \ + expr.c + +# "-x c" forces the lex/yacc files to be compiled as c; +# the build system otherwise forces them to be c++. +edify_cflags := -x c + +# +# Build the host-side command line tool +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + $(edify_src_files) \ + main.c + +LOCAL_CFLAGS := $(edify_cflags) -g -O0 +LOCAL_MODULE := edify +LOCAL_YACCFLAGS := -v + +include $(BUILD_HOST_EXECUTABLE) + +# +# Build the device-side library +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(edify_src_files) + +LOCAL_CFLAGS := $(edify_cflags) +LOCAL_MODULE := libedify + +include $(BUILD_STATIC_LIBRARY) diff --git a/edify/README b/edify/README new file mode 100644 index 000000000..810455cca --- /dev/null +++ b/edify/README @@ -0,0 +1,108 @@ +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/expr.c b/edify/expr.c new file mode 100644 index 000000000..72e5100f3 --- /dev/null +++ b/edify/expr.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed 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. + */ + +#include <string.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> + +#include "expr.h" + +// Functions should: +// +// - return a malloc()'d string +// - if Evaluate() on any argument returns NULL, return NULL. + +int BooleanString(const char* s) { + return s[0] != '\0'; +} + +char* Evaluate(State* state, Expr* expr) { + return expr->fn(expr->name, state, expr->argc, expr->argv); +} + +char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc == 0) { + return strdup(""); + } + char** strings = malloc(argc * sizeof(char*)); + int i; + for (i = 0; i < argc; ++i) { + strings[i] = NULL; + } + char* result = NULL; + int length = 0; + for (i = 0; i < argc; ++i) { + strings[i] = Evaluate(state, argv[i]); + if (strings[i] == NULL) { + goto done; + } + length += strlen(strings[i]); + } + + result = malloc(length+1); + int p = 0; + for (i = 0; i < argc; ++i) { + strcpy(result+p, strings[i]); + p += strlen(strings[i]); + } + result[p] = '\0'; + + done: + for (i = 0; i < argc; ++i) { + free(strings[i]); + } + return result; +} + +char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2 && argc != 3) { + free(state->errmsg); + state->errmsg = strdup("ifelse expects 2 or 3 arguments"); + return NULL; + } + char* cond = Evaluate(state, argv[0]); + if (cond == NULL) { + return NULL; + } + + if (BooleanString(cond) == true) { + free(cond); + return Evaluate(state, argv[1]); + } else { + if (argc == 3) { + free(cond); + return Evaluate(state, argv[2]); + } else { + return cond; + } + } +} + +char* AbortFn(const char* name, State* state, int argc, Expr* argv[]) { + char* msg = NULL; + if (argc > 0) { + msg = Evaluate(state, argv[0]); + } + free(state->errmsg); + if (msg) { + state->errmsg = msg; + } else { + state->errmsg = strdup("called abort()"); + } + return NULL; +} + +char* AssertFn(const char* name, State* state, int argc, Expr* argv[]) { + int i; + for (i = 0; i < argc; ++i) { + char* v = Evaluate(state, argv[i]); + if (v == NULL) { + return NULL; + } + int b = BooleanString(v); + free(v); + if (!b) { + int prefix_len; + int len = argv[i]->end - argv[i]->start; + char* err_src = malloc(len + 20); + strcpy(err_src, "assert failed: "); + prefix_len = strlen(err_src); + memcpy(err_src + prefix_len, state->script + argv[i]->start, len); + err_src[prefix_len + len] = '\0'; + free(state->errmsg); + state->errmsg = err_src; + return NULL; + } + } + return strdup(""); +} + +char* SleepFn(const char* name, State* state, int argc, Expr* argv[]) { + char* val = Evaluate(state, argv[0]); + if (val == NULL) { + return NULL; + } + int v = strtol(val, NULL, 10); + sleep(v); + return val; +} + +char* StdoutFn(const char* name, State* state, int argc, Expr* argv[]) { + int i; + for (i = 0; i < argc; ++i) { + char* v = Evaluate(state, argv[i]); + if (v == NULL) { + return NULL; + } + fputs(v, stdout); + free(v); + } + return strdup(""); +} + +char* LogicalAndFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + if (BooleanString(left) == true) { + free(left); + return Evaluate(state, argv[1]); + } else { + return left; + } +} + +char* LogicalOrFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + if (BooleanString(left) == false) { + free(left); + return Evaluate(state, argv[1]); + } else { + return left; + } +} + +char* LogicalNotFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* val = Evaluate(state, argv[0]); + if (val == NULL) return NULL; + bool bv = BooleanString(val); + free(val); + if (bv) { + return strdup(""); + } else { + return strdup("t"); + } +} + +char* SubstringFn(const char* name, State* state, + int argc, Expr* argv[]) { + char* needle = Evaluate(state, argv[0]); + if (needle == NULL) return NULL; + char* haystack = Evaluate(state, argv[1]); + if (haystack == NULL) { + free(needle); + return NULL; + } + + char* result = strdup(strstr(haystack, needle) ? "t" : ""); + free(needle); + free(haystack); + return result; +} + +char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + char* right = Evaluate(state, argv[1]); + if (right == NULL) { + free(left); + return NULL; + } + + char* result = strdup(strcmp(left, right) == 0 ? "t" : ""); + free(left); + free(right); + return result; +} + +char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + char* right = Evaluate(state, argv[1]); + if (right == NULL) { + free(left); + return NULL; + } + + char* result = strdup(strcmp(left, right) != 0 ? "t" : ""); + free(left); + free(right); + return result; +} + +char* SequenceFn(const char* name, State* state, int argc, Expr* argv[]) { + char* left = Evaluate(state, argv[0]); + if (left == NULL) return NULL; + free(left); + return Evaluate(state, argv[1]); +} + +char* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + free(state->errmsg); + state->errmsg = strdup("less_than_int expects 2 arguments"); + return NULL; + } + + char* left; + char* right; + if (ReadArgs(state, argv, 2, &left, &right) < 0) return NULL; + + bool result = false; + char* end; + + long l_int = strtol(left, &end, 10); + if (left[0] == '\0' || *end != '\0') { + fprintf(stderr, "[%s] is not an int\n", left); + goto done; + } + + long r_int = strtol(right, &end, 10); + if (right[0] == '\0' || *end != '\0') { + fprintf(stderr, "[%s] is not an int\n", right); + goto done; + } + + result = l_int < r_int; + + done: + free(left); + free(right); + return strdup(result ? "t" : ""); +} + +char* GreaterThanIntFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + free(state->errmsg); + state->errmsg = strdup("greater_than_int expects 2 arguments"); + return NULL; + } + + Expr* temp[2]; + temp[0] = argv[1]; + temp[1] = argv[0]; + + return LessThanIntFn(name, state, 2, temp); +} + +char* Literal(const char* name, State* state, int argc, Expr* argv[]) { + return strdup(name); +} + +Expr* Build(Function fn, YYLTYPE loc, int count, ...) { + va_list v; + va_start(v, count); + Expr* e = malloc(sizeof(Expr)); + e->fn = fn; + e->name = "(operator)"; + e->argc = count; + e->argv = 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; + +void RegisterFunction(const char* name, Function fn) { + if (fn_entries >= fn_size) { + fn_size = fn_size*2 + 1; + fn_table = realloc(fn_table, fn_size * sizeof(NamedFunction)); + } + fn_table[fn_entries].name = name; + fn_table[fn_entries].fn = fn; + ++fn_entries; +} + +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 = bsearch(&key, fn_table, fn_entries, + sizeof(NamedFunction), fn_entry_compare); + if (nf == NULL) { + return NULL; + } + return nf->fn; +} + +void RegisterBuiltins() { + RegisterFunction("ifelse", IfElseFn); + RegisterFunction("abort", AbortFn); + RegisterFunction("assert", AssertFn); + RegisterFunction("concat", ConcatFn); + RegisterFunction("is_substring", SubstringFn); + RegisterFunction("stdout", StdoutFn); + RegisterFunction("sleep", SleepFn); + + RegisterFunction("less_than_int", LessThanIntFn); + RegisterFunction("greater_than_int", GreaterThanIntFn); +} + + +// ----------------------------------------------------------------- +// convenience methods for functions +// ----------------------------------------------------------------- + +// Evaluate the expressions in argv, giving 'count' char* (the ... is +// zero or more char** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadArgs(State* state, Expr* argv[], int count, ...) { + char** args = malloc(count * sizeof(char*)); + va_list v; + va_start(v, count); + int i; + for (i = 0; i < count; ++i) { + args[i] = Evaluate(state, argv[i]); + if (args[i] == NULL) { + va_end(v); + int j; + for (j = 0; j < i; ++j) { + free(args[j]); + } + return -1; + } + *(va_arg(v, char**)) = args[i]; + } + va_end(v); + return 0; +} + +// Evaluate the expressions in argv, returning an array of char* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// strings it contains. +char** ReadVarArgs(State* state, int argc, Expr* argv[]) { + char** args = (char**)malloc(argc * sizeof(char*)); + int i = 0; + for (i = 0; i < argc; ++i) { + args[i] = Evaluate(state, argv[i]); + if (args[i] == NULL) { + int j; + for (j = 0; j < i; ++j) { + free(args[j]); + } + free(args); + return NULL; + } + } + return args; +} + +// Use printf-style arguments to compose an error message to put into +// *state. Returns NULL. +char* ErrorAbort(State* state, char* format, ...) { + char* buffer = malloc(4096); + va_list v; + va_start(v, format); + vsnprintf(buffer, 4096, format, v); + va_end(v); + free(state->errmsg); + state->errmsg = buffer; + return NULL; +} diff --git a/edify/expr.h b/edify/expr.h new file mode 100644 index 000000000..d2e739201 --- /dev/null +++ b/edify/expr.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef _EXPRESSION_H +#define _EXPRESSION_H + +#include "yydefs.h" + +#define MAX_STRING_LEN 1024 + +typedef struct Expr Expr; + +typedef struct { + // Optional pointer to app-specific data; the core of edify never + // uses this value. + void* cookie; + + // The source of the original script. Must be NULL-terminated, + // and in writable memory (Evaluate may make temporary changes to + // it but will restore it when done). + char* script; + + // The error message (if any) returned if the evaluation aborts. + // Should be NULL initially, will be either NULL or a malloc'd + // pointer after Evaluate() returns. + char* errmsg; +} State; + +typedef char* (*Function)(const char* name, State* state, + int argc, Expr* argv[]); + +struct Expr { + Function fn; + char* name; + int argc; + Expr** argv; + int start, end; +}; + +char* Evaluate(State* state, Expr* expr); + +// Glue to make an Expr out of a literal. +char* Literal(const char* name, State* state, int argc, Expr* argv[]); + +// Functions corresponding to various syntactic sugar operators. +// ("concat" is also available as a builtin function, to concatenate +// more than two strings.) +char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]); +char* LogicalAndFn(const char* name, State* state, int argc, Expr* argv[]); +char* LogicalOrFn(const char* name, State* state, int argc, Expr* argv[]); +char* LogicalNotFn(const char* name, State* state, int argc, Expr* argv[]); +char* SubstringFn(const char* name, State* state, int argc, Expr* argv[]); +char* EqualityFn(const char* name, State* state, int argc, Expr* argv[]); +char* InequalityFn(const char* name, State* state, int argc, Expr* argv[]); +char* 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(). +char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]); +char* AssertFn(const char* name, State* state, int argc, Expr* argv[]); +char* 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); + +// 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); + + +// --- convenience functions for use in functions --- + +// Evaluate the expressions in argv, giving 'count' char* (the ... is +// zero or more char** to put them in). If any expression evaluates +// to NULL, free the rest and return -1. Return 0 on success. +int ReadArgs(State* state, Expr* argv[], int count, ...); + +// Evaluate the expressions in argv, returning an array of char* +// results. If any evaluate to NULL, free the rest and return NULL. +// The caller is responsible for freeing the returned array and the +// strings it contains. +char** ReadVarArgs(State* state, int argc, Expr* argv[]); + +// Use printf-style arguments to compose an error message to put into +// *state. Returns NULL. +char* ErrorAbort(State* state, char* format, ...); + + +#endif // _EXPRESSION_H diff --git a/edify/lexer.l b/edify/lexer.l new file mode 100644 index 000000000..2c4489cc6 --- /dev/null +++ b/edify/lexer.l @@ -0,0 +1,110 @@ +%{ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed 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. + */ + +#include "expr.h" +#include "yydefs.h" +#include "parser.h" + +int gLine = 1; +int gColumn = 1; +int gPos = 0; + +// TODO: enforce MAX_STRING_LEN during lexing +char string_buffer[MAX_STRING_LEN]; +char* string_pos; + +#define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \ + gColumn+=yyleng; gPos+=yyleng;} while(0) + +%} + +%x STR + +%option noyywrap + +%% + + +\" { + BEGIN(STR); + string_pos = string_buffer; + yylloc.start = gPos; + ++gColumn; + ++gPos; +} + +<STR>{ + \" { + ++gColumn; + ++gPos; + BEGIN(INITIAL); + *string_pos = '\0'; + yylval.str = strdup(string_buffer); + yylloc.end = gPos; + return STRING; + } + + \\n { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\n'; } + \\t { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\t'; } + \\\" { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\"'; } + \\\\ { gColumn += yyleng; gPos += yyleng; *string_pos++ = '\\'; } + + \\x[0-9a-fA-F]{2} { + gColumn += yyleng; + gPos += yyleng; + int val; + sscanf(yytext+2, "%x", &val); + *string_pos++ = val; + } + + \n { + ++gLine; + ++gPos; + gColumn = 1; + *string_pos++ = yytext[0]; + } + + . { + ++gColumn; + ++gPos; + *string_pos++ = yytext[0]; + } +} + +if ADVANCE; return IF; +then ADVANCE; return THEN; +else ADVANCE; return ELSE; +endif ADVANCE; return ENDIF; + +[a-zA-Z0-9_:/.]+ { + ADVANCE; + yylval.str = strdup(yytext); + return STRING; +} + +\&\& ADVANCE; return AND; +\|\| ADVANCE; return OR; +== ADVANCE; return EQ; +!= ADVANCE; return NE; + +[+(),!;] ADVANCE; return yytext[0]; + +[ \t]+ ADVANCE; + +(#.*)?\n gPos += yyleng; ++gLine; gColumn = 1; + +. return BAD; diff --git a/edify/main.c b/edify/main.c new file mode 100644 index 000000000..0e3610847 --- /dev/null +++ b/edify/main.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "expr.h" +#include "parser.h" + +extern int yyparse(Expr** root, int* error_count); + +int expect(const char* expr_str, const char* expected, int* errors) { + Expr* e; + int error; + char* result; + + printf("."); + + yy_scan_string(expr_str); + int error_count = 0; + error = yyparse(&e, &error_count); + if (error > 0 || error_count > 0) { + fprintf(stderr, "error parsing \"%s\" (%d errors)\n", + expr_str, error_count); + ++*errors; + return 0; + } + + State state; + state.cookie = NULL; + state.script = expr_str; + state.errmsg = NULL; + + result = Evaluate(&state, e); + free(state.errmsg); + if (result == NULL && expected != NULL) { + fprintf(stderr, "error evaluating \"%s\"\n", expr_str); + ++*errors; + return 0; + } + + if (result == NULL && expected == NULL) { + return 1; + } + + if (strcmp(result, expected) != 0) { + fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n", + expr_str, expected, result); + ++*errors; + free(result); + return 0; + } + + free(result); + return 1; +} + +int test() { + int errors = 0; + + expect("a", "a", &errors); + expect("\"a\"", "a", &errors); + expect("\"\\x61\"", "a", &errors); + expect("# this is a comment\n" + " a\n" + " \n", + "a", &errors); + + + // sequence operator + expect("a; b; c", "c", &errors); + + // string concat operator + expect("a + b", "ab", &errors); + expect("a + \n \"b\"", "ab", &errors); + expect("a + b +\nc\n", "abc", &errors); + + // string concat function + expect("concat(a, b)", "ab", &errors); + expect("concat(a,\n \"b\")", "ab", &errors); + expect("concat(a + b,\nc,\"d\")", "abcd", &errors); + expect("\"concat\"(a + b,\nc,\"d\")", "abcd", &errors); + + // logical and + expect("a && b", "b", &errors); + expect("a && \"\"", "", &errors); + expect("\"\" && b", "", &errors); + expect("\"\" && \"\"", "", &errors); + expect("\"\" && abort()", "", &errors); // test short-circuiting + expect("t && abort()", NULL, &errors); + + // logical or + expect("a || b", "a", &errors); + expect("a || \"\"", "a", &errors); + expect("\"\" || b", "b", &errors); + expect("\"\" || \"\"", "", &errors); + expect("a || abort()", "a", &errors); // test short-circuiting + expect("\"\" || abort()", NULL, &errors); + + // logical not + expect("!a", "", &errors); + expect("! \"\"", "t", &errors); + expect("!!a", "t", &errors); + + // precedence + expect("\"\" == \"\" && b", "b", &errors); + expect("a + b == ab", "t", &errors); + expect("ab == a + b", "t", &errors); + expect("a + (b == ab)", "a", &errors); + expect("(ab == a) + b", "b", &errors); + + // substring function + expect("is_substring(cad, abracadabra)", "t", &errors); + expect("is_substring(abrac, abracadabra)", "t", &errors); + expect("is_substring(dabra, abracadabra)", "t", &errors); + expect("is_substring(cad, abracxadabra)", "", &errors); + expect("is_substring(abrac, axbracadabra)", "", &errors); + expect("is_substring(dabra, abracadabrxa)", "", &errors); + + // ifelse function + expect("ifelse(t, yes, no)", "yes", &errors); + expect("ifelse(!t, yes, no)", "no", &errors); + expect("ifelse(t, yes, abort())", "yes", &errors); + expect("ifelse(!t, abort(), no)", "no", &errors); + + // if "statements" + expect("if t then yes else no endif", "yes", &errors); + expect("if \"\" then yes else no endif", "no", &errors); + expect("if \"\" then yes endif", "", &errors); + expect("if \"\"; t then yes endif", "yes", &errors); + + // numeric comparisons + expect("less_than_int(3, 14)", "t", &errors); + expect("less_than_int(14, 3)", "", &errors); + expect("less_than_int(x, 3)", "", &errors); + expect("less_than_int(3, x)", "", &errors); + expect("greater_than_int(3, 14)", "", &errors); + expect("greater_than_int(14, 3)", "t", &errors); + expect("greater_than_int(x, 3)", "", &errors); + expect("greater_than_int(3, x)", "", &errors); + + printf("\n"); + + return errors; +} + +void ExprDump(int depth, Expr* n, char* script) { + printf("%*s", depth*2, ""); + char temp = script[n->end]; + script[n->end] = '\0'; + printf("%s %p (%d-%d) \"%s\"\n", + n->name == NULL ? "(NULL)" : n->name, n->fn, n->start, n->end, + script+n->start); + script[n->end] = temp; + int i; + for (i = 0; i < n->argc; ++i) { + ExprDump(depth+1, n->argv[i], script); + } +} + +int main(int argc, char** argv) { + RegisterBuiltins(); + FinishRegistration(); + + if (argc == 1) { + return test() != 0; + } + + FILE* f = fopen(argv[1], "r"); + char buffer[8192]; + int size = fread(buffer, 1, 8191, f); + fclose(f); + buffer[size] = '\0'; + + Expr* root; + int error_count = 0; + yy_scan_bytes(buffer, size); + int error = yyparse(&root, &error_count); + printf("parse returned %d; %d errors encountered\n", error, error_count); + if (error == 0 || error_count > 0) { + + ExprDump(0, root, buffer); + + State state; + state.cookie = NULL; + state.script = buffer; + state.errmsg = NULL; + + char* result = Evaluate(&state, root); + if (result == NULL) { + printf("result was NULL, message is: %s\n", + (state.errmsg == NULL ? "(NULL)" : state.errmsg)); + free(state.errmsg); + } else { + printf("result is [%s]\n", result); + } + } + return 0; +} diff --git a/edify/parser.y b/edify/parser.y new file mode 100644 index 000000000..3f9ade144 --- /dev/null +++ b/edify/parser.y @@ -0,0 +1,130 @@ +%{ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "expr.h" +#include "yydefs.h" +#include "parser.h" + +extern int gLine; +extern int gColumn; + +void yyerror(Expr** root, int* error_count, const char* s); +int yyparse(Expr** root, int* error_count); + +%} + +%locations + +%union { + char* str; + Expr* expr; + struct { + int argc; + Expr** argv; + } args; +} + +%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF +%token <str> STRING BAD +%type <expr> expr +%type <args> arglist + +%parse-param {Expr** root} +%parse-param {int* error_count} +%error-verbose + +/* declarations in increasing order of precedence */ +%left ';' +%left ',' +%left OR +%left AND +%left EQ NE +%left '+' +%right '!' + +%% + +input: expr { *root = $1; } +; + +expr: STRING { + $$ = malloc(sizeof(Expr)); + $$->fn = Literal; + $$->name = $1; + $$->argc = 0; + $$->argv = NULL; + $$->start = @$.start; + $$->end = @$.end; +} +| '(' expr ')' { $$ = $2; $$->start=@$.start; $$->end=@$.end; } +| expr ';' { $$ = $1; $$->start=@1.start; $$->end=@1.end; } +| expr ';' expr { $$ = Build(SequenceFn, @$, 2, $1, $3); } +| error ';' expr { $$ = $3; $$->start=@$.start; $$->end=@$.end; } +| expr '+' expr { $$ = Build(ConcatFn, @$, 2, $1, $3); } +| expr EQ expr { $$ = Build(EqualityFn, @$, 2, $1, $3); } +| expr NE expr { $$ = Build(InequalityFn, @$, 2, $1, $3); } +| expr AND expr { $$ = Build(LogicalAndFn, @$, 2, $1, $3); } +| expr OR expr { $$ = Build(LogicalOrFn, @$, 2, $1, $3); } +| '!' expr { $$ = Build(LogicalNotFn, @$, 1, $2); } +| 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 ')' { + $$ = malloc(sizeof(Expr)); + $$->fn = FindFunction($1); + if ($$->fn == NULL) { + char buffer[256]; + snprintf(buffer, sizeof(buffer), "unknown function \"%s\"", $1); + yyerror(root, error_count, buffer); + YYERROR; + } + $$->name = $1; + $$->argc = $3.argc; + $$->argv = $3.argv; + $$->start = @$.start; + $$->end = @$.end; +} +; + +arglist: /* empty */ { + $$.argc = 0; + $$.argv = NULL; +} +| expr { + $$.argc = 1; + $$.argv = malloc(sizeof(Expr*)); + $$.argv[0] = $1; +} +| arglist ',' expr { + $$.argc = $1.argc + 1; + $$.argv = realloc($$.argv, $$.argc * sizeof(Expr*)); + $$.argv[$$.argc-1] = $3; +} +; + +%% + +void yyerror(Expr** root, int* error_count, const char* s) { + if (strlen(s) == 0) { + s = "syntax error"; + } + printf("line %d col %d: %s\n", gLine, gColumn, s); + ++*error_count; +} diff --git a/edify/yydefs.h b/edify/yydefs.h new file mode 100644 index 000000000..625786255 --- /dev/null +++ b/edify/yydefs.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed 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. + */ + +#ifndef _YYDEFS_H_ +#define _YYDEFS_H_ + +#define YYLTYPE YYLTYPE +typedef struct { + int start, end; +} YYLTYPE; + +#define YYLLOC_DEFAULT(Current, Rhs, N) \ + do { \ + if (N) { \ + (Current).start = YYRHSLOC(Rhs, 1).start; \ + (Current).end = YYRHSLOC(Rhs, N).end; \ + } else { \ + (Current).start = YYRHSLOC(Rhs, 0).start; \ + (Current).end = YYRHSLOC(Rhs, 0).end; \ + } \ + } while (0) + +#endif |