Initial Contribution
diff --git a/amend/parser_y.y b/amend/parser_y.y
new file mode 100644
index 0000000..b634016
--- /dev/null
+++ b/amend/parser_y.y
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+%{
+#undef NDEBUG
+    #include <stdlib.h>
+    #include <string.h>
+    #include <assert.h>
+    #include <stdio.h>
+    #include "ast.h"
+    #include "lexer.h"
+    #include "commands.h"
+
+    void yyerror(const char *msg);
+    int yylex(void);
+
+#define STRING_COMPARISON(out, a1, sop, a2) \
+    do { \
+        out = (AmBooleanValue *)malloc(sizeof(AmBooleanValue)); \
+        if (out == NULL) { \
+            YYABORT; \
+        } \
+        out->type = AM_BVAL_STRING_COMPARISON; \
+        out->u.stringComparison.op = sop; \
+        out->u.stringComparison.arg1 = a1; \
+        out->u.stringComparison.arg2 = a2; \
+    } while (false)
+
+#define BOOLEAN_EXPRESSION(out, a1, bop, a2) \
+    do { \
+        out = (AmBooleanValue *)malloc(sizeof(AmBooleanValue)); \
+        if (out == NULL) { \
+            YYABORT; \
+        } \
+        out->type = AM_BVAL_EXPRESSION; \
+        out->u.expression.op = bop; \
+        out->u.expression.arg1 = a1; \
+        out->u.expression.arg2 = a2; \
+    } while (false)
+
+AmCommandList *gCommands = NULL;
+%}
+
+%start  lines
+
+%union  {
+        char *literalString;
+        AmFunctionArgumentBuilder *functionArgumentBuilder;
+        AmFunctionArguments *functionArguments;
+        AmFunctionCall *functionCall;
+        AmStringValue *stringValue;
+        AmBooleanValue *booleanValue;
+        AmWordListBuilder *wordListBuilder;
+        AmCommandArguments *commandArguments;
+        AmCommand *command;
+        AmCommandList *commandList;
+    }
+
+%token  TOK_AND TOK_OR TOK_EQ TOK_NE TOK_GE TOK_LE TOK_EOF TOK_EOL TOK_ERROR
+%token  <literalString> TOK_STRING TOK_IDENTIFIER TOK_WORD
+
+%type   <commandList> lines
+%type   <command> command line
+%type   <functionArgumentBuilder> function_arguments
+%type   <functionArguments> function_arguments_or_empty
+%type   <functionCall> function_call
+%type   <literalString> function_name
+%type   <stringValue> string_value
+%type   <booleanValue> boolean_expression
+%type   <wordListBuilder> word_list
+%type   <commandArguments> arguments
+
+/* Operator precedence, weakest to strongest.
+ * Same as C/Java precedence.
+ */
+
+%left   TOK_OR
+%left   TOK_AND
+%left   TOK_EQ TOK_NE
+%left   '<' '>' TOK_LE TOK_GE
+%right   '!'
+
+%%
+
+lines :     /* empty */
+                {
+                    $$ = (AmCommandList *)malloc(sizeof(AmCommandList));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+gCommands = $$;
+                    $$->arraySize = 64;
+                    $$->commandCount = 0;
+                    $$->commands = (AmCommand **)malloc(
+                            sizeof(AmCommand *) * $$->arraySize);
+                    if ($$->commands == NULL) {
+                        YYABORT;
+                    }
+                }
+        |   lines line
+                {
+                    if ($2 != NULL) {
+                        if ($1->commandCount >= $1->arraySize) {
+                            AmCommand **newArray;
+                            newArray = (AmCommand **)realloc($$->commands,
+                                sizeof(AmCommand *) * $$->arraySize * 2);
+                            if (newArray == NULL) {
+                                YYABORT;
+                            }
+                            $$->commands = newArray;
+                            $$->arraySize *= 2;
+                        }
+                        $1->commands[$1->commandCount++] = $2;
+                    }
+                }
+        ;
+
+line :      line_ending
+                {
+                    $$ = NULL;  /* ignore blank lines */
+                }
+        |   command arguments line_ending
+                {
+                    $$ = $1;
+                    $$->args = $2;
+                    setLexerArgumentType(AM_UNKNOWN_ARGS);
+                }
+        ;
+
+command :   TOK_IDENTIFIER
+                {
+                    Command *cmd = findCommand($1);
+                    if (cmd == NULL) {
+                        fprintf(stderr, "Unknown command \"%s\"\n", $1);
+                        YYABORT;
+                    }
+                    $$ = (AmCommand *)malloc(sizeof(AmCommand));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->line = getLexerLineNumber();
+                    $$->name = strdup($1);
+                    if ($$->name == NULL) {
+                        YYABORT;
+                    }
+                    $$->args = NULL;
+                    CommandArgumentType argType = getCommandArgumentType(cmd);
+                    if (argType == CMD_ARGS_BOOLEAN) {
+                        setLexerArgumentType(AM_BOOLEAN_ARGS);
+                    } else {
+                        setLexerArgumentType(AM_WORD_ARGS);
+                    }
+                    $$->cmd = cmd;
+                }
+        ;
+
+line_ending :
+            TOK_EOL
+        |   TOK_EOF
+        ;
+
+arguments : boolean_expression
+                {
+                    $$ = (AmCommandArguments *)malloc(
+                            sizeof(AmCommandArguments));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->booleanArgs = true;
+                    $$->u.b = $1;
+                }
+        |   word_list
+                {
+                    /* Convert the builder list into an array.
+                     * Do it in reverse order; the words were pushed
+                     * onto the list in LIFO order.
+                     */
+                    AmWordList *w = (AmWordList *)malloc(sizeof(AmWordList));
+                    if (w == NULL) {
+                        YYABORT;
+                    }
+                    if ($1 != NULL) {
+                        AmWordListBuilder *words = $1;
+
+                        w->argc = words->wordCount;
+                        w->argv = (const char **)malloc(w->argc *
+                                        sizeof(char *));
+                        if (w->argv == NULL) {
+                            YYABORT;
+                        }
+                        int i;
+                        for (i = w->argc; words != NULL && i > 0; --i) {
+                            AmWordListBuilder *f = words;
+                            w->argv[i-1] = words->word;
+                            words = words->next;
+                            free(f);
+                        }
+                        assert(i == 0);
+                        assert(words == NULL);
+                    } else {
+                        w->argc = 0;
+                        w->argv = NULL;
+                    }
+                    $$ = (AmCommandArguments *)malloc(
+                            sizeof(AmCommandArguments));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->booleanArgs = false;
+                    $$->u.w = w;
+                }
+        ;
+
+word_list : /* empty */
+                { $$ = NULL; }
+        |   word_list TOK_WORD
+                {
+                    if ($1 == NULL) {
+                        $$ = (AmWordListBuilder *)malloc(
+                                sizeof(AmWordListBuilder));
+                        if ($$ == NULL) {
+                            YYABORT;
+                        }
+                        $$->next = NULL;
+                        $$->wordCount = 1;
+                    } else {
+                        $$ = (AmWordListBuilder *)malloc(
+                                sizeof(AmWordListBuilder));
+                        if ($$ == NULL) {
+                            YYABORT;
+                        }
+                        $$->next = $1;
+                        $$->wordCount = $$->next->wordCount + 1;
+                    }
+                    $$->word = strdup($2);
+                    if ($$->word == NULL) {
+                        YYABORT;
+                    }
+                }
+        ;
+
+boolean_expression :
+            '!' boolean_expression
+                {
+                    $$ = (AmBooleanValue *)malloc(sizeof(AmBooleanValue));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->type = AM_BVAL_EXPRESSION;
+                    $$->u.expression.op = AM_BOP_NOT;
+                    $$->u.expression.arg1 = $2;
+                    $$->u.expression.arg2 = NULL;
+                }
+    /* TODO: if both expressions are literals, evaluate now */
+        |   boolean_expression TOK_AND boolean_expression
+                { BOOLEAN_EXPRESSION($$, $1, AM_BOP_AND, $3); }
+        |   boolean_expression TOK_OR boolean_expression
+                { BOOLEAN_EXPRESSION($$, $1, AM_BOP_OR, $3); }
+        |   boolean_expression TOK_EQ boolean_expression
+                { BOOLEAN_EXPRESSION($$, $1, AM_BOP_EQ, $3); }
+        |   boolean_expression TOK_NE boolean_expression
+                { BOOLEAN_EXPRESSION($$, $1, AM_BOP_NE, $3); }
+        |   '(' boolean_expression ')'
+                { $$ = $2; }
+    /* TODO: if both strings are literals, evaluate now */
+        |   string_value '<' string_value
+                { STRING_COMPARISON($$, $1, AM_SOP_LT, $3); }
+        |   string_value '>' string_value
+                { STRING_COMPARISON($$, $1, AM_SOP_GT, $3); }
+        |   string_value TOK_EQ string_value
+                { STRING_COMPARISON($$, $1, AM_SOP_EQ, $3); }
+        |   string_value TOK_NE string_value
+                { STRING_COMPARISON($$, $1, AM_SOP_NE, $3); }
+        |   string_value TOK_LE string_value
+                { STRING_COMPARISON($$, $1, AM_SOP_LE, $3); }
+        |   string_value TOK_GE string_value
+                { STRING_COMPARISON($$, $1, AM_SOP_GE, $3); }
+        ;
+
+string_value :
+            TOK_IDENTIFIER
+                {
+                    $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->type = AM_SVAL_LITERAL;
+                    $$->u.literal = strdup($1);
+                    if ($$->u.literal == NULL) {
+                        YYABORT;
+                    }
+                }
+        |   TOK_STRING
+                {
+                    $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->type = AM_SVAL_LITERAL;
+                    $$->u.literal = strdup($1);
+                    if ($$->u.literal == NULL) {
+                        YYABORT;
+                    }
+                }
+        |   function_call
+                {
+                    $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->type = AM_SVAL_FUNCTION;
+                    $$->u.function = $1;
+                }
+        ;
+
+        /* We can't just say
+         *  TOK_IDENTIFIER '(' function_arguments_or_empty ')'
+         * because parsing function_arguments_or_empty will clobber
+         * the underlying string that yylval.literalString points to.
+         */
+function_call :
+            function_name '(' function_arguments_or_empty ')'
+                {
+                    Function *fn = findFunction($1);
+                    if (fn == NULL) {
+                        fprintf(stderr, "Unknown function \"%s\"\n", $1);
+                        YYABORT;
+                    }
+                    $$ = (AmFunctionCall *)malloc(sizeof(AmFunctionCall));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->name = $1;
+                    if ($$->name == NULL) {
+                        YYABORT;
+                    }
+                    $$->fn = fn;
+                    $$->args = $3;
+                }
+        ;
+
+function_name :
+            TOK_IDENTIFIER
+                {
+                    $$ = strdup($1);
+                }
+        ;
+
+function_arguments_or_empty :
+            /* empty */
+                {
+                    $$ = (AmFunctionArguments *)malloc(
+                            sizeof(AmFunctionArguments));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->argc = 0;
+                    $$->argv = NULL;
+                }
+        |   function_arguments
+                {
+                    AmFunctionArgumentBuilder *args = $1;
+                    assert(args != NULL);
+
+                    /* Convert the builder list into an array.
+                     * Do it in reverse order; the args were pushed
+                     * onto the list in LIFO order.
+                     */
+                    $$ = (AmFunctionArguments *)malloc(
+                            sizeof(AmFunctionArguments));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->argc = args->argCount;
+                    $$->argv = (AmStringValue *)malloc(
+                            $$->argc * sizeof(AmStringValue));
+                    if ($$->argv == NULL) {
+                        YYABORT;
+                    }
+                    int i;
+                    for (i = $$->argc; args != NULL && i > 0; --i) {
+                        AmFunctionArgumentBuilder *f = args;
+                        $$->argv[i-1] = *args->arg;
+                        args = args->next;
+                        free(f->arg);
+                        free(f);
+                    }
+                    assert(i == 0);
+                    assert(args == NULL);
+                }
+        ;
+
+function_arguments :
+            string_value
+                {
+                    $$ = (AmFunctionArgumentBuilder *)malloc(
+                            sizeof(AmFunctionArgumentBuilder));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->next = NULL;
+                    $$->argCount = 1;
+                    $$->arg = $1;
+                }
+        |   function_arguments ',' string_value
+                {
+                    $$ = (AmFunctionArgumentBuilder *)malloc(
+                            sizeof(AmFunctionArgumentBuilder));
+                    if ($$ == NULL) {
+                        YYABORT;
+                    }
+                    $$->next = $1;
+                    $$->argCount = $$->next->argCount + 1;
+                    $$->arg = $3;
+                }
+        ;
+    /* xxx this whole tool needs to be hardened */