fixes to edify and updater script

A few more changes to edify:

  - fix write_raw_image(); my last change neglected to close the write
    context, so the written image was corrupt.

  - each expression tracks the span of the source code from which it
    was compiled, so that assert()'s error message can include the
    source of the expression that failed.

  - the 'cookie' argument to each Function is replaced with a State
    object, which contains the cookie, the source script (for use with
    the above spans), and the current error message (replacing the
    global variables that were used for this purpose).

  - in the recovery image, a new command "ui_print" can be sent back
    through the command pipe to cause text to appear on the screen.
    Add a new ui_print() function to print things from scripts.
    Rename existing "print" function to "stdout".
diff --git a/edify/expr.c b/edify/expr.c
index 5470a2b..406c67e 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -29,249 +29,241 @@
 //    - if Evaluate() on any argument returns NULL, return NULL.
 
 int BooleanString(const char* s) {
-  return s[0] != '\0';
+    return s[0] != '\0';
 }
 
-char* Evaluate(void* cookie, Expr* expr) {
-  return expr->fn(expr->name, cookie, expr->argc, expr->argv);
+char* Evaluate(State* state, Expr* expr) {
+    return expr->fn(expr->name, state, 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;
+char* ConcatFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc == 0) {
+        return strdup("");
     }
-    length += strlen(strings[i]);
-  }
+    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';
+    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;
+  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;
-  }
+char* IfElseFn(const char* name, State* state, int argc, Expr* argv[]) {
+    if (argc != 2 && argc != 3) {
+        return NULL;
+    }
+    char* cond = Evaluate(state, 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]);
+    if (BooleanString(cond) == true) {
+        free(cond);
+        return Evaluate(state, argv[1]);
     } else {
-      return cond;
+        if (argc == 3) {
+            free(cond);
+            return Evaluate(state, argv[2]);
+        } else {
+            return cond;
+        }
     }
-  }
 }
 
-char* AbortFn(const char* name, void* cookie, int argc, Expr* argv[]) {
-  char* msg = NULL;
-  if (argc > 0) {
-    msg = Evaluate(cookie, argv[0]);
-  }
-  SetError(msg == NULL ? "called abort()" : msg);
-  free(msg);
-  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;
+char* AbortFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char* msg = NULL;
+    if (argc > 0) {
+        msg = Evaluate(state, argv[0]);
     }
-    int b = BooleanString(v);
-    free(v);
-    if (!b) {
-      SetError("assert() failed");
-      return NULL;
+    free(state->errmsg);
+    if (msg) {
+        state->errmsg = msg;
+    } else {
+        state->errmsg = strdup("called abort()");
     }
-  }
-  return strdup("");
-}
-
-char* SleepFn(const char* name, void* cookie, int argc, Expr* argv[]) {
-  char* val = Evaluate(cookie, argv[0]);
-  if (val == NULL) {
     return NULL;
-  }
-  int v = strtol(val, NULL, 10);
-  sleep(v);
-  return val;
 }
 
-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;
+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;
+        }
     }
-    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,
+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* needle = Evaluate(cookie, argv[0]);
-  if (needle == NULL) return NULL;
-  char* haystack = Evaluate(cookie, argv[1]);
-  if (haystack == NULL) {
+    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);
-    return NULL;
-  }
-
-  char* result = strdup(strstr(haystack, needle) ? "t" : "");
-  free(needle);
-  free(haystack);
-  return result;
+    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) {
+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);
-    return NULL;
-  }
-
-  char* result = strdup(strcmp(left, right) == 0 ? "t" : "");
-  free(left);
-  free(right);
-  return result;
+    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) {
+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);
-    return NULL;
-  }
-
-  char* result = strdup(strcmp(left, right) != 0 ? "t" : "");
-  free(left);
-  free(right);
-  return result;
+    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* 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* Literal(const char* name, void* cookie, int argc, Expr* argv[]) {
-  return strdup(name);
+char* Literal(const char* name, State* state, 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;
-}
-
-// -----------------------------------------------------------------
-//   error reporting
-// -----------------------------------------------------------------
-
-static char* error_message = NULL;
-
-void SetError(const char* message) {
-  if (error_message) {
-    free(error_message);
-  }
-  error_message = strdup(message);
-}
-
-const char* GetError() {
-  return error_message;
-}
-
-void ClearError() {
-  free(error_message);
-  error_message = NULL;
+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;
 }
 
 // -----------------------------------------------------------------
@@ -283,44 +275,44 @@
 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;
+    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);
+    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);
+    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;
+    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);
-  RegisterFunction("sleep", SleepFn);
+    RegisterFunction("ifelse", IfElseFn);
+    RegisterFunction("abort", AbortFn);
+    RegisterFunction("assert", AssertFn);
+    RegisterFunction("concat", ConcatFn);
+    RegisterFunction("is_substring", SubstringFn);
+    RegisterFunction("stdout", StdoutFn);
+    RegisterFunction("sleep", SleepFn);
 }
 
 
@@ -331,44 +323,44 @@
 // 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(void* cookie, 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(cookie, argv[i]);
-    if (args[i] == NULL) {
-      va_end(v);
-      int j;
-      for (j = 0; j < i; ++j) {
-        free(args[j]);
-      }
-      return -1;
+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_arg(v, char**)) = args[i];
-  }
-  va_end(v);
-  return 0;
+    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(void* cookie, int argc, Expr* argv[]) {
-  char** args = (char**)malloc(argc * sizeof(char*));
-  int i = 0;
-  for (i = 0; i < argc; ++i) {
-    args[i] = Evaluate(cookie, argv[i]);
-    if (args[i] == NULL) {
-      int j;
-      for (j = 0; j < i; ++j) {
-        free(args[j]);
-      }
-      free(args);
-      return NULL;
+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;
+    return args;
 }
diff --git a/edify/expr.h b/edify/expr.h
index cfbef90..671b499 100644
--- a/edify/expr.h
+++ b/edify/expr.h
@@ -17,45 +17,64 @@
 #ifndef _EXPRESSION_H
 #define _EXPRESSION_H
 
+#include "yydefs.h"
+
 #define MAX_STRING_LEN 1024
 
 typedef struct Expr Expr;
 
-typedef char* (*Function)(const char* name, void* cookie,
+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;
+    Function fn;
+    char* name;
+    int argc;
+    Expr** argv;
+    int start, end;
 };
 
-char* Evaluate(void* cookie, Expr* expr);
+char* Evaluate(State* state, Expr* expr);
 
 // Glue to make an Expr out of a literal.
-char* Literal(const char* name, void* cookie, int argc, Expr* argv[]);
+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, 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[]);
+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, int count, ...);
+Expr* Build(Function fn, YYLTYPE loc, 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[]);
+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
@@ -91,13 +110,13 @@
 // 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(void* cookie, Expr* argv[], int count, ...);
+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(void* cookie, int argc, Expr* argv[]);
+char** ReadVarArgs(State* state, int argc, Expr* argv[]);
 
 
 #endif  // _EXPRESSION_H
diff --git a/edify/lexer.l b/edify/lexer.l
index cb5eb31..2c4489c 100644
--- a/edify/lexer.l
+++ b/edify/lexer.l
@@ -16,14 +16,20 @@
  */
 
 #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
@@ -34,27 +40,32 @@
 
 
 \" {
-    ++gColumn;
     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; *string_pos++ = '\n'; }
-  \\t   { gColumn += yyleng; *string_pos++ = '\t'; }
-  \\\"  { gColumn += yyleng; *string_pos++ = '\"'; }
-  \\\\  { gColumn += yyleng; *string_pos++ = '\\'; }
+  \\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;
@@ -62,36 +73,38 @@
 
   \n {
       ++gLine;
+      ++gPos;
       gColumn = 1;
       *string_pos++ = yytext[0];
   }
 
   . {
       ++gColumn;
+      ++gPos;
       *string_pos++ = yytext[0];
   }
 }
 
-if                { gColumn += yyleng; return IF; }
-then              { gColumn += yyleng; return THEN; }
-else              { gColumn += yyleng; return ELSE; }
-endif             { gColumn += yyleng; return ENDIF; }
+if                ADVANCE; return IF;
+then              ADVANCE; return THEN;
+else              ADVANCE; return ELSE;
+endif             ADVANCE; return ENDIF;
 
 [a-zA-Z0-9_:/.]+ {
-  gColumn += yyleng;
+  ADVANCE;
   yylval.str = strdup(yytext);
   return STRING;
 }
 
-\&\&              { gColumn += yyleng; return AND; }
-\|\|              { gColumn += yyleng; return OR; }
-==                { gColumn += yyleng; return EQ; }
-!=                { gColumn += yyleng; return NE; }
+\&\&              ADVANCE; return AND;
+\|\|              ADVANCE; return OR;
+==                ADVANCE; return EQ;
+!=                ADVANCE; return NE;
 
-[+(),!;]          { gColumn += yyleng; return yytext[0]; }
+[+(),!;]          ADVANCE; return yytext[0];
 
-[ \t]+            gColumn += yyleng;
+[ \t]+            ADVANCE;
 
-(#.*)?\n          { ++gLine; gColumn = 1; }
+(#.*)?\n          gPos += yyleng; ++gLine; gColumn = 1;
 
 .                 return BAD;
diff --git a/edify/main.c b/edify/main.c
index 7da89e2..03eefc6 100644
--- a/edify/main.c
+++ b/edify/main.c
@@ -21,152 +21,183 @@
 #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;
+    Expr* e;
+    int error;
+    char* result;
 
-  printf(".");
+    printf(".");
 
-  yy_scan_string(expr_str);
-  error = yyparse(&e);
-  if (error > 0) {
-    fprintf(stderr, "error parsing \"%s\"\n", expr_str);
-    ++*errors;
-    return 0;
-  }
+    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;
+    }
 
-  result = Evaluate(NULL, e);
-  if (result == NULL && expected != NULL) {
-    fprintf(stderr, "error evaluating \"%s\"\n", expr_str);
-    ++*errors;
-    return 0;
-  }
+    State state;
+    state.cookie = NULL;
+    state.script = expr_str;
+    state.errmsg = NULL;
 
-  if (result == NULL && expected == NULL) {
-    return 1;
-  }
+    result = Evaluate(&state, e);
+    free(state.errmsg);
+    if (result == NULL && expected != NULL) {
+        fprintf(stderr, "error evaluating \"%s\"\n", expr_str);
+        ++*errors;
+        return 0;
+    }
 
-  if (strcmp(result, expected) != 0) {
-    fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n",
-            expr_str, expected, result);
-    ++*errors;
+    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 0;
-  }
-
-  free(result);
-  return 1;
+    return 1;
 }
 
 int test() {
-  int errors = 0;
+    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);
+    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);
+    // 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 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);
+    // 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 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 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);
+    // 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);
+    // 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);
+    // 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);
+    // 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);
+    // 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");
+    printf("\n");
 
-  return errors;
+    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();
+    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) {
-    char* result = Evaluate(NULL, root);
-    if (result == NULL) {
-      char* errmsg = GetError();
-      printf("result was NULL, message is: %s\n",
-             (errmsg == NULL ? "(NULL)" : errmsg));
-      ClearError();
-    } else {
-      printf("result is [%s]\n", result);
+    if (argc == 1) {
+        return test() != 0;
     }
-  }
-  return 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
index cf163c0..3f9ade1 100644
--- a/edify/parser.y
+++ b/edify/parser.y
@@ -20,6 +20,7 @@
 #include <string.h>
 
 #include "expr.h"
+#include "yydefs.h"
 #include "parser.h"
 
 extern int gLine;
@@ -30,6 +31,8 @@
 
 %}
 
+%locations
+
 %union {
     char* str;
     Expr* expr;
@@ -68,19 +71,21 @@
     $$->name = $1;
     $$->argc = 0;
     $$->argv = NULL;
+    $$->start = @$.start;
+    $$->end = @$.end;
 }
-|  '(' 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); }
+|  '(' 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);
@@ -93,6 +98,8 @@
     $$->name = $1;
     $$->argc = $3.argc;
     $$->argv = $3.argv;
+    $$->start = @$.start;
+    $$->end = @$.end;
 }
 ;
 
diff --git a/edify/yydefs.h b/edify/yydefs.h
new file mode 100644
index 0000000..6257862
--- /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
diff --git a/install.c b/install.c
index cca9400..c2e1385 100644
--- a/install.c
+++ b/install.c
@@ -196,6 +196,9 @@
     //            arrange to install the contents of <filename> in the
     //            given partition on reboot.
     //
+    //        ui_print <string>
+    //            display <string> on the screen.
+    //
     //   - the name of the package zip file.
     //
 
@@ -248,6 +251,13 @@
                     firmware_filename = strdup(filename);
                 }
             }
+        } else if (strcmp(command, "ui_print") == 0) {
+            char* str = strtok(NULL, "\n");
+            if (str) {
+                ui_print(str);
+            } else {
+                ui_print("\n");
+            }
         } else {
             LOGE("unknown command [%s]\n", command);
         }
diff --git a/updater/install.c b/updater/install.c
index 2e965ce..616cb2c 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -32,13 +32,14 @@
 #include "mtdutils/mtdutils.h"
 #include "updater.h"
 
-char* ErrorAbort(void* cookie, char* format, ...) {
+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);
-    SetError(buffer);
+    free(state->errmsg);
+    state->errmsg = buffer;
     return NULL;
 }
 
@@ -47,28 +48,28 @@
 //
 //   what:  type="MTD"   location="<partition>"            to mount a yaffs2 filesystem
 //          type="vfat"  location="/dev/block/<whatever>"  to mount a device
-char* MountFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 3) {
-        return ErrorAbort(cookie, "%s() expects 3 args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 3 args, got %d", name, argc);
     }
     char* type;
     char* location;
     char* mount_point;
-    if (ReadArgs(cookie, argv, 3, &type, &location, &mount_point) < 0) {
+    if (ReadArgs(state, argv, 3, &type, &location, &mount_point) < 0) {
         return NULL;
     }
 
     if (strlen(type) == 0) {
-        ErrorAbort(cookie, "type argument to %s() can't be empty", name);
+        ErrorAbort(state, "type argument to %s() can't be empty", name);
         goto done;
     }
     if (strlen(location) == 0) {
-        ErrorAbort(cookie, "location argument to %s() can't be empty", name);
+        ErrorAbort(state, "location argument to %s() can't be empty", name);
         goto done;
     }
     if (strlen(mount_point) == 0) {
-        ErrorAbort(cookie, "mount_point argument to %s() can't be empty", name);
+        ErrorAbort(state, "mount_point argument to %s() can't be empty", name);
         goto done;
     }
 
@@ -109,17 +110,17 @@
 
 
 // is_mounted(mount_point)
-char* IsMountedFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* IsMountedFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 1) {
-        return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
     }
     char* mount_point;
-    if (ReadArgs(cookie, argv, 1, &mount_point) < 0) {
+    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
         return NULL;
     }
     if (strlen(mount_point) == 0) {
-        ErrorAbort(cookie, "mount_point argument to unmount() can't be empty");
+        ErrorAbort(state, "mount_point argument to unmount() can't be empty");
         goto done;
     }
 
@@ -137,17 +138,17 @@
 }
 
 
-char* UnmountFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 1) {
-        return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
     }
     char* mount_point;
-    if (ReadArgs(cookie, argv, 1, &mount_point) < 0) {
+    if (ReadArgs(state, argv, 1, &mount_point) < 0) {
         return NULL;
     }
     if (strlen(mount_point) == 0) {
-        ErrorAbort(cookie, "mount_point argument to unmount() can't be empty");
+        ErrorAbort(state, "mount_point argument to unmount() can't be empty");
         goto done;
     }
 
@@ -170,23 +171,23 @@
 // format(type, location)
 //
 //    type="MTD"  location=partition
-char* FormatFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     if (argc != 2) {
-        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
     }
     char* type;
     char* location;
-    if (ReadArgs(cookie, argv, 2, &type, &location) < 0) {
+    if (ReadArgs(state, argv, 2, &type, &location) < 0) {
         return NULL;
     }
 
     if (strlen(type) == 0) {
-        ErrorAbort(cookie, "type argument to %s() can't be empty", name);
+        ErrorAbort(state, "type argument to %s() can't be empty", name);
         goto done;
     }
     if (strlen(location) == 0) {
-        ErrorAbort(cookie, "location argument to %s() can't be empty", name);
+        ErrorAbort(state, "location argument to %s() can't be empty", name);
         goto done;
     }
 
@@ -228,11 +229,11 @@
 }
 
 
-char* DeleteFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) {
     char** paths = malloc(argc * sizeof(char*));
     int i;
     for (i = 0; i < argc; ++i) {
-        paths[i] = Evaluate(cookie, argv[i]);
+        paths[i] = Evaluate(state, argv[i]);
         if (paths[i] == NULL) {
             int j;
             for (j = 0; j < i; ++i) {
@@ -259,20 +260,20 @@
 }
 
 
-char* ShowProgressFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* ShowProgressFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc != 2) {
-        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
     }
     char* frac_str;
     char* sec_str;
-    if (ReadArgs(cookie, argv, 2, &frac_str, &sec_str) < 0) {
+    if (ReadArgs(state, argv, 2, &frac_str, &sec_str) < 0) {
         return NULL;
     }
 
     double frac = strtod(frac_str, NULL);
     int sec = strtol(sec_str, NULL, 10);
 
-    UpdaterInfo* ui = (UpdaterInfo*)cookie;
+    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
     fprintf(ui->cmd_pipe, "progress %f %d\n", frac, sec);
 
     free(frac_str);
@@ -281,16 +282,16 @@
 }
 
 // package_extract_dir(package_path, destination_path)
-char* PackageExtractDirFn(const char* name, void* cookie,
+char* PackageExtractDirFn(const char* name, State* state,
                           int argc, Expr* argv[]) {
     if (argc != 2) {
-        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
     }
     char* zip_path;
     char* dest_path;
-    if (ReadArgs(cookie, argv, 2, &zip_path, &dest_path) < 0) return NULL;
+    if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
 
-    ZipArchive* za = ((UpdaterInfo*)cookie)->package_zip;
+    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
 
     // To create a consistent system image, never use the clock for timestamps.
     struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
@@ -305,18 +306,18 @@
 
 
 // package_extract_file(package_path, destination_path)
-char* PackageExtractFileFn(const char* name, void* cookie,
+char* PackageExtractFileFn(const char* name, State* state,
                            int argc, Expr* argv[]) {
     if (argc != 2) {
-        return ErrorAbort(cookie, "%s() expects 2 args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
     }
     char* zip_path;
     char* dest_path;
-    if (ReadArgs(cookie, argv, 2, &zip_path, &dest_path) < 0) return NULL;
+    if (ReadArgs(state, argv, 2, &zip_path, &dest_path) < 0) return NULL;
 
     bool success = false;
 
-    ZipArchive* za = ((UpdaterInfo*)cookie)->package_zip;
+    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
     const ZipEntry* entry = mzFindZipEntry(za, zip_path);
     if (entry == NULL) {
         fprintf(stderr, "%s: no %s in package\n", name, zip_path);
@@ -340,15 +341,15 @@
 
 
 // symlink target src1 src2 ...
-char* SymlinkFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc == 0) {
-        return ErrorAbort(cookie, "%s() expects 1+ args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 1+ args, got %d", name, argc);
     }
     char* target;
-    target = Evaluate(cookie, argv[0]);
+    target = Evaluate(state, argv[0]);
     if (target == NULL) return NULL;
 
-    char** srcs = ReadVarArgs(cookie, argc-1, argv+1);
+    char** srcs = ReadVarArgs(state, argc-1, argv+1);
     if (srcs == NULL) {
         free(target);
         return NULL;
@@ -364,16 +365,16 @@
 }
 
 
-char* SetPermFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
     bool recursive = (strcmp(name, "set_perm_recursive") == 0);
 
     int min_args = 4 + (recursive ? 1 : 0);
     if (argc < min_args) {
-        return ErrorAbort(cookie, "%s() expects %d+ args, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects %d+ args, got %d", name, argc);
     }
 
-    char** args = ReadVarArgs(cookie, argc, argv);
+    char** args = ReadVarArgs(state, argc, argv);
     if (args == NULL) return NULL;
 
     char* end;
@@ -381,26 +382,26 @@
 
     int uid = strtoul(args[0], &end, 0);
     if (*end != '\0' || args[0][0] == 0) {
-        ErrorAbort(cookie, "%s: \"%s\" not a valid uid", name, args[0]);
+        ErrorAbort(state, "%s: \"%s\" not a valid uid", name, args[0]);
         goto done;
     }
 
     int gid = strtoul(args[1], &end, 0);
     if (*end != '\0' || args[1][0] == 0) {
-        ErrorAbort(cookie, "%s: \"%s\" not a valid gid", name, args[1]);
+        ErrorAbort(state, "%s: \"%s\" not a valid gid", name, args[1]);
         goto done;
     }
 
     if (recursive) {
         int dir_mode = strtoul(args[2], &end, 0);
         if (*end != '\0' || args[2][0] == 0) {
-            ErrorAbort(cookie, "%s: \"%s\" not a valid dirmode", name, args[2]);
+            ErrorAbort(state, "%s: \"%s\" not a valid dirmode", name, args[2]);
             goto done;
         }
 
         int file_mode = strtoul(args[3], &end, 0);
         if (*end != '\0' || args[3][0] == 0) {
-            ErrorAbort(cookie, "%s: \"%s\" not a valid filemode",
+            ErrorAbort(state, "%s: \"%s\" not a valid filemode",
                        name, args[3]);
             goto done;
         }
@@ -411,7 +412,7 @@
     } else {
         int mode = strtoul(args[2], &end, 0);
         if (*end != '\0' || args[2][0] == 0) {
-            ErrorAbort(cookie, "%s: \"%s\" not a valid mode", name, args[2]);
+            ErrorAbort(state, "%s: \"%s\" not a valid mode", name, args[2]);
             goto done;
         }
 
@@ -432,12 +433,12 @@
 }
 
 
-char* GetPropFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
     if (argc != 1) {
-        return ErrorAbort(cookie, "%s() expects 1 arg, got %d", name, argc);
+        return ErrorAbort(state, "%s() expects 1 arg, got %d", name, argc);
     }
     char* key;
-    key = Evaluate(cookie, argv[0]);
+    key = Evaluate(state, argv[0]);
     if (key == NULL) return NULL;
 
     char value[PROPERTY_VALUE_MAX];
@@ -457,21 +458,21 @@
 }
 
 // write_raw_image(file, partition)
-char* WriteRawImageFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
     char* result = NULL;
 
     char* partition;
     char* filename;
-    if (ReadArgs(cookie, argv, 2, &filename, &partition) < 0) {
+    if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
         return NULL;
     }
 
     if (strlen(partition) == 0) {
-        ErrorAbort(cookie, "partition argument to %s can't be empty", name);
+        ErrorAbort(state, "partition argument to %s can't be empty", name);
         goto done;
     }
     if (strlen(filename) == 0) {
-        ErrorAbort(cookie, "file argument to %s can't be empty", name);
+        ErrorAbort(state, "file argument to %s can't be empty", name);
         goto done;
     }
 
@@ -515,6 +516,13 @@
     free(buffer);
     fclose(f);
 
+    if (mtd_erase_blocks(ctx, -1) == -1) {
+        fprintf(stderr, "%s: error erasing blocks of %s\n", name, partition);
+    }
+    if (mtd_write_close(ctx) != 0) {
+        fprintf(stderr, "%s: error closing write of %s\n", name, partition);
+    }
+
     printf("%s %s partition from %s\n",
            success ? "wrote" : "failed to write", partition, filename);
 
@@ -532,26 +540,26 @@
 //    file is not used until after updater exits
 //
 // TODO: this should live in some HTC-specific library
-char* WriteFirmwareImageFn(const char* name, void* cookie,
+char* WriteFirmwareImageFn(const char* name, State* state,
                            int argc, Expr* argv[]) {
     char* result = NULL;
 
     char* partition;
     char* filename;
-    if (ReadArgs(cookie, argv, 2, &filename, &partition) < 0) {
+    if (ReadArgs(state, argv, 2, &filename, &partition) < 0) {
         return NULL;
     }
 
     if (strlen(partition) == 0) {
-        ErrorAbort(cookie, "partition argument to %s can't be empty", name);
+        ErrorAbort(state, "partition argument to %s can't be empty", name);
         goto done;
     }
     if (strlen(filename) == 0) {
-        ErrorAbort(cookie, "file argument to %s can't be empty", name);
+        ErrorAbort(state, "file argument to %s can't be empty", name);
         goto done;
     }
 
-    FILE* cmd = ((UpdaterInfo*)cookie)->cmd_pipe;
+    FILE* cmd = ((UpdaterInfo*)(state->cookie))->cmd_pipe;
     fprintf(cmd, "firmware %s %s\n", partition, filename);
 
     printf("will write %s firmware from %s\n", partition, filename);
@@ -569,7 +577,7 @@
 // apply_patch(srcfile, tgtfile, tgtsha1, tgtsize, sha1:patch, ...)
 // apply_patch_check(file, sha1, ...)
 // apply_patch_space(bytes)
-char* ApplyPatchFn(const char* name, void* cookie, int argc, Expr* argv[]) {
+char* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
     printf("in applypatchfn (%s)\n", name);
 
     char* prepend = NULL;
@@ -579,7 +587,7 @@
         prepend = "-s";
     }
 
-    char** args = ReadVarArgs(cookie, argc, argv);
+    char** args = ReadVarArgs(state, argc, argv);
     if (args == NULL) return NULL;
 
     // insert the "program name" argv[0] and a copy of the "prepend"
@@ -610,10 +618,42 @@
     switch (result) {
         case 0:   return strdup("t");
         case 1:   return strdup("");
-        default:  return ErrorAbort(cookie, "applypatch couldn't parse args");
+        default:  return ErrorAbort(state, "applypatch couldn't parse args");
     }
 }
 
+char* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
+    char** args = ReadVarArgs(state, argc, argv);
+    if (args == NULL) {
+        return NULL;
+    }
+
+    int size = 0;
+    int i;
+    for (i = 0; i < argc; ++i) {
+        size += strlen(args[i]);
+    }
+    char* buffer = malloc(size+1);
+    size = 0;
+    for (i = 0; i < argc; ++i) {
+        strcpy(buffer+size, args[i]);
+        size += strlen(args[i]);
+        free(args[i]);
+    }
+    free(args);
+    buffer[size] = '\0';
+
+    char* line = strtok(buffer, "\n");
+    while (line) {
+        fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe,
+                "ui_print %s\n", line);
+        line = strtok(NULL, "\n");
+    }
+    fprintf(((UpdaterInfo*)(state->cookie))->cmd_pipe, "ui_print\n");
+
+    return buffer;
+}
+
 
 void RegisterInstallFunctions() {
     RegisterFunction("mount", MountFn);
@@ -636,4 +676,6 @@
     RegisterFunction("apply_patch", ApplyPatchFn);
     RegisterFunction("apply_patch_check", ApplyPatchFn);
     RegisterFunction("apply_patch_space", ApplyPatchFn);
+
+    RegisterFunction("ui_print", UIPrintFn);
 }
diff --git a/updater/updater.c b/updater/updater.c
index 0977625..5a2ed2c 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -94,12 +94,26 @@
     updater_info.cmd_pipe = cmd_pipe;
     updater_info.package_zip = &za;
 
-    char* result = Evaluate(&updater_info, root);
+    State state;
+    state.cookie = &updater_info;
+    state.script = script;
+    state.errmsg = NULL;
+
+    char* result = Evaluate(&state, root);
     if (result == NULL) {
-        const char* errmsg = GetError();
-        fprintf(stderr, "script aborted with error: %s\n",
-                errmsg == NULL ? "(none)" : errmsg);
-        ClearError();
+        if (state.errmsg == NULL) {
+            fprintf(stderr, "script aborted (no error message)\n");
+            fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
+        } else {
+            fprintf(stderr, "script aborted: %s\n", state.errmsg);
+            char* line = strtok(state.errmsg, "\n");
+            while (line) {
+                fprintf(cmd_pipe, "ui_print %s\n", line);
+                line = strtok(NULL, "\n");
+            }
+            fprintf(cmd_pipe, "ui_print\n");
+        }
+        free(state.errmsg);
         return 7;
     } else {
         fprintf(stderr, "script result was [%s]\n", result);
@@ -107,6 +121,7 @@
     }
 
     mzCloseZipArchive(&za);
+    free(script);
 
     return 0;
 }