Merge change 3514 into donut
* changes: core of edify, an eventual replacement for amend
This commit is contained in:
commit
9b9c2114bd
7 changed files with 889 additions and 0 deletions
40
edify/Android.mk
Normal file
40
edify/Android.mk
Normal file
|
@ -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)
|
108
edify/README
Normal file
108
edify/README
Normal file
|
@ -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())
|
279
edify/expr.c
Normal file
279
edify/expr.c
Normal file
|
@ -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);
|
||||
}
|
80
edify/expr.h
Normal file
80
edify/expr.h
Normal file
|
@ -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
|
97
edify/lexer.l
Normal file
97
edify/lexer.l
Normal file
|
@ -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;
|
164
edify/main.c
Normal file
164
edify/main.c
Normal file
|
@ -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;
|
||||
}
|
121
edify/parser.y
Normal file
121
edify/parser.y
Normal file
|
@ -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);
|
||||
}
|
Loading…
Reference in a new issue