core of edify, an eventual replacement for amend

Edify is a simple scripting language for OTA installation, to be used
when we move to OTAs being installed via binaries shipped with the
package.
diff --git a/edify/Android.mk b/edify/Android.mk
new file mode 100644
index 0000000..803fba2
--- /dev/null
+++ b/edify/Android.mk
@@ -0,0 +1,40 @@
+# 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_SRC_FILES += $(edify_test_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 0000000..5ccb582
--- /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, and slashes 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 0000000..b3b8927
--- /dev/null
+++ b/edify/expr.c
@@ -0,0 +1,279 @@
+/*
+ * 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 "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(void* cookie, Expr* expr) {
+  return expr->fn(expr->name, cookie, expr->argc, expr->argv);
+}
+
+char* ConcatFn(const char* name, void* cookie, 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(cookie, 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, void* cookie, int argc, Expr* argv[]) {
+  if (argc != 2 && argc != 3) {
+    return NULL;
+  }
+  char* cond = Evaluate(cookie, argv[0]);
+  if (cond == NULL) {
+    return NULL;
+  }
+
+  if (BooleanString(cond) == true) {
+    free(cond);
+    return Evaluate(cookie, argv[1]);
+  } else {
+    if (argc == 3) {
+      free(cond);
+      return Evaluate(cookie, argv[2]);
+    } else {
+      return cond;
+    }
+  }
+}
+
+char* AbortFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+  return NULL;
+}
+
+char* AssertFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+  int i;
+  for (i = 0; i < argc; ++i) {
+    char* v = Evaluate(cookie, argv[i]);
+    if (v == NULL) {
+      return NULL;
+    }
+    int b = BooleanString(v);
+    free(v);
+    if (!b) {
+      return NULL;
+    }
+  }
+  return strdup("");
+}
+
+char* PrintFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+  int i;
+  for (i = 0; i < argc; ++i) {
+    char* v = Evaluate(cookie, argv[i]);
+    if (v == NULL) {
+      return NULL;
+    }
+    fputs(v, stdout);
+    free(v);
+  }
+  return strdup("");
+}
+
+char* LogicalAndFn(const char* name, void* cookie,
+                   int argc, Expr* argv[]) {
+  char* left = Evaluate(cookie, argv[0]);
+  if (left == NULL) return NULL;
+  if (BooleanString(left) == true) {
+    free(left);
+    return Evaluate(cookie, argv[1]);
+  } else {
+    return left;
+  }
+}
+
+char* LogicalOrFn(const char* name, void* cookie,
+                  int argc, Expr* argv[]) {
+  char* left = Evaluate(cookie, argv[0]);
+  if (left == NULL) return NULL;
+  if (BooleanString(left) == false) {
+    free(left);
+    return Evaluate(cookie, argv[1]);
+  } else {
+    return left;
+  }
+}
+
+char* LogicalNotFn(const char* name, void* cookie,
+                  int argc, Expr* argv[]) {
+  char* val = Evaluate(cookie, 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, void* cookie,
+                  int argc, Expr* argv[]) {
+  char* needle = Evaluate(cookie, argv[0]);
+  if (needle == NULL) return NULL;
+  char* haystack = Evaluate(cookie, 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, void* cookie, int argc, Expr* argv[]) {
+  char* left = Evaluate(cookie, argv[0]);
+  if (left == NULL) return NULL;
+  char* right = Evaluate(cookie, 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, void* cookie, int argc, Expr* argv[]) {
+  char* left = Evaluate(cookie, argv[0]);
+  if (left == NULL) return NULL;
+  char* right = Evaluate(cookie, 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, void* cookie, int argc, Expr* argv[]) {
+  char* left = Evaluate(cookie, argv[0]);
+  if (left == NULL) return NULL;
+  free(left);
+  return Evaluate(cookie, argv[1]);
+}
+
+char* Literal(const char* name, void* cookie, int argc, Expr* argv[]) {
+  return strdup(name);
+}
+
+Expr* Build(Function fn, 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);
+  return e;
+}
+
+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("print", PrintFn);
+}
diff --git a/edify/expr.h b/edify/expr.h
new file mode 100644
index 0000000..ac5df18
--- /dev/null
+++ b/edify/expr.h
@@ -0,0 +1,80 @@
+/*
+ * 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
+
+#define MAX_STRING_LEN 1024
+
+typedef struct Expr Expr;
+
+typedef char* (*Function)(const char* name, void* cookie,
+                          int argc, Expr* argv[]);
+
+struct Expr {
+  Function fn;
+  char* name;
+  int argc;
+  Expr** argv;
+};
+
+char* Evaluate(void* cookie, Expr* expr);
+
+// Glue to make an Expr out of a literal.
+char* Literal(const char* name, void* cookie, 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, void* cookie, int argc, Expr* argv[]);
+char* LogicalAndFn(const char* name, void* cookie, int argc, Expr* argv[]);
+char* LogicalOrFn(const char* name, void* cookie, int argc, Expr* argv[]);
+char* LogicalNotFn(const char* name, void* cookie, int argc, Expr* argv[]);
+char* SubstringFn(const char* name, void* cookie, int argc, Expr* argv[]);
+char* EqualityFn(const char* name, void* cookie, int argc, Expr* argv[]);
+char* InequalityFn(const char* name, void* cookie, int argc, Expr* argv[]);
+char* SequenceFn(const char* name, void* cookie, int argc, Expr* argv[]);
+
+// Convenience function for building expressions with a fixed number
+// of arguments.
+Expr* Build(Function fn, int count, ...);
+
+// Global builtins, registered by RegisterBuiltins().
+char* IfElseFn(const char* name, void* cookie, int argc, Expr* argv[]);
+char* AssertFn(const char* name, void* cookie, int argc, Expr* argv[]);
+char* AbortFn(const char* name, void* cookie, int argc, Expr* argv[]);
+
+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);
+
+#endif  // _EXPRESSION_H
diff --git a/edify/lexer.l b/edify/lexer.l
new file mode 100644
index 0000000..4faef5d
--- /dev/null
+++ b/edify/lexer.l
@@ -0,0 +1,97 @@
+%{
+/*
+ * 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 "parser.h"
+
+int gLine = 1;
+int gColumn = 1;
+
+// TODO: enforce MAX_STRING_LEN during lexing
+char string_buffer[MAX_STRING_LEN];
+char* string_pos;
+%}
+
+%x STR
+
+%option noyywrap
+
+%%
+
+
+\" {
+    ++gColumn;
+    BEGIN(STR);
+    string_pos = string_buffer;
+}
+
+<STR>{
+  \" {
+      ++gColumn;
+      BEGIN(INITIAL);
+      *string_pos = '\0';
+      yylval.str = strdup(string_buffer);
+      return STRING;
+  }
+
+  \\n   { gColumn += yyleng; *string_pos++ = '\n'; }
+  \\t   { gColumn += yyleng; *string_pos++ = '\t'; }
+  \\\"  { gColumn += yyleng; *string_pos++ = '\"'; }
+  \\\\  { gColumn += yyleng; *string_pos++ = '\\'; }
+
+  \\x[0-9a-fA-F]{2} {
+      gColumn += yyleng;
+      int val;
+      sscanf(yytext+2, "%x", &val);
+      *string_pos++ = val;
+  }
+
+  \n {
+      ++gLine;
+      gColumn = 1;
+      *string_pos++ = yytext[0];
+  }
+
+  . {
+      ++gColumn;
+      *string_pos++ = yytext[0];
+  }
+}
+
+if                { gColumn += yyleng; return IF; }
+then              { gColumn += yyleng; return THEN; }
+else              { gColumn += yyleng; return ELSE; }
+endif             { gColumn += yyleng; return ENDIF; }
+
+[a-zA-Z0-9_:/]+ {
+  gColumn += yyleng;
+  yylval.str = strdup(yytext);
+  return STRING;
+}
+
+\&\&              { gColumn += yyleng; return AND; }
+\|\|              { gColumn += yyleng; return OR; }
+==                { gColumn += yyleng; return EQ; }
+!=                { gColumn += yyleng; return NE; }
+
+[+(),!;]          { gColumn += yyleng; return yytext[0]; }
+
+[ \t]+            gColumn += yyleng;
+
+(#.*)?\n          { ++gLine; gColumn = 1; }
+
+.                 return BAD;
diff --git a/edify/main.c b/edify/main.c
new file mode 100644
index 0000000..4d65da2
--- /dev/null
+++ b/edify/main.c
@@ -0,0 +1,164 @@
+/*
+ * 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"
+
+int expect(const char* expr_str, const char* expected, int* errors) {
+  Expr* e;
+  int error;
+  char* result;
+
+  printf(".");
+
+  yy_scan_string(expr_str);
+  error = yyparse(&e);
+  if (error > 0) {
+    fprintf(stderr, "error parsing \"%s\"\n", expr_str);
+    ++*errors;
+    return 0;
+  }
+
+  result = Evaluate(NULL, e);
+  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);
+
+  printf("\n");
+
+  return errors;
+}
+
+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;
+  yy_scan_bytes(buffer, size);
+  int error = yyparse(&root);
+  printf("parse returned %d\n", error);
+  if (error == 0) {
+    char* result = Evaluate(NULL, root);
+    printf("result is [%s]\n", result == NULL ? "(NULL)" : result);
+  }
+  return 0;
+}
diff --git a/edify/parser.y b/edify/parser.y
new file mode 100644
index 0000000..67a210f
--- /dev/null
+++ b/edify/parser.y
@@ -0,0 +1,121 @@
+%{
+/*
+ * 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 gLine;
+extern int gColumn;
+
+void yyerror(Expr** root, const char* s);
+int yyparse(Expr** root);
+
+%}
+
+%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}
+%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;
+}
+|  '(' expr ')'                      { $$ = $2; }
+|  expr ';'                          { $$ = $1; }
+|  expr ';' expr                     { $$ = Build(SequenceFn, 2, $1, $3); }
+|  error ';' expr                    { $$ = $3; }
+|  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, buffer);
+        YYERROR;
+    }
+    $$->name = $1;
+    $$->argc = $3.argc;
+    $$->argv = $3.argv;
+}
+;
+
+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, const char* s) {
+  if (strlen(s) == 0) {
+    s = "syntax error";
+  }
+  printf("line %d col %d: %s\n", gLine, gColumn, s);
+}