summaryrefslogtreecommitdiffstats
path: root/edify
diff options
context:
space:
mode:
Diffstat (limited to 'edify')
-rw-r--r--edify/Android.mk39
-rw-r--r--edify/README108
-rw-r--r--edify/expr.c432
-rw-r--r--edify/expr.h126
-rw-r--r--edify/lexer.l110
-rw-r--r--edify/main.c213
-rw-r--r--edify/parser.y130
-rw-r--r--edify/yydefs.h36
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