0c1740e6f5e1784aff4a2dddf2bb6c96184059c3 kent Wed Aug 7 12:01:51 2019 -0700 Got parser working mostly. Evaluator starting to work. diff --git src/hg/oneShot/freen/freen.c src/hg/oneShot/freen/freen.c index 195fc5c..054bb5b 100644 --- src/hg/oneShot/freen/freen.c +++ src/hg/oneShot/freen/freen.c @@ -1,167 +1,833 @@ /* freen - My Pet Freen. A pet freen is actually more dangerous than a wild one. */ /* Copyright (C) 2014 The Regents of the University of California * See README in this or parent directory for licensing information. */ #include "common.h" #include "linefile.h" #include "hash.h" #include "options.h" #include "dystring.h" #include "cheapcgi.h" #include "jksql.h" #include "portable.h" #include "obscure.h" -#include "jsonWrite.h" -#include "tagSchema.h" +#include "localmem.h" +#include "csv.h" +#include "tokenizer.h" /* Command line validation table. */ static struct optionSpec options[] = { {NULL, 0}, }; void usage() { errAbort("freen - test some hairbrained thing.\n" "usage: freen input\n"); } -static int nonDotSize(char *s) -/* Return number of chars up to next dot or end of string */ +/* strex -String Expression - stuff to implement a relatively simple string + * processing expression parser. */ + + +enum strexType +/* A type */ { -char c; -int size = 0; -for (;;) + strexTypeBoolean = 1, + strexTypeString = 2, + strexTypeInt = 3, + strexTypeDouble = 4, + }; + +union strexVal +/* Some value of arbirary type that can be of any type corresponding to strexType */ + { + boolean b; + char *s; + long long i; + double x; + }; + +struct strexEval +/* Result of evaluation of parse tree. */ { - c = *s++; - if (c == '.' || c == 0) - return size; - ++size; + enum strexType type; + union strexVal val; + }; + +enum strexOp +/* An operation in the parse tree. */ + { + strexOpUnknown, /* Should not occur */ + strexOpLiteral, /* Literal string or number. */ + strexOpSymbol, /* A symbol name. */ + + strexOpBuiltInCall, /* Call a built in function */ + strexOpArrayIx, /* An array with an index. */ + + strexOpUnaryMinusInt, + strexOpUnaryMinusDouble, + + /* Binary operations. */ + strexOpAdd, + + /* Type conversions */ + strexOpStringToBoolean, + strexOpIntToBoolean, + strexOpDoubleToBoolean, + strexOpStringToInt, + strexOpDoubleToInt, + strexOpBooleanToInt, + strexOpStringToDouble, + strexOpBooleanToDouble, + strexOpIntToDouble, + strexOpBooleanToString, + strexOpIntToString, + strexOpDoubleToString, + }; + + +struct strexParse +/* A strex parse-tree. */ + { + struct strexParse *next; /* Points to younger sibling if any. */ + struct strexParse *children; /* Points to oldest child if any. */ + enum strexOp op; /* Operation at this node. */ + enum strexType type; /* Return type of this operation. */ + union strexVal val; /* Return value of this operation. */ + }; + +typedef char* (*StrexEvalLookup)(void *record, char *key); +/* Callback for strexEvalOnRecord to lookup a variable value. */ + +void strexValDump(union strexVal val, enum strexType type, FILE *f) +/* Dump out value to file. */ +{ +switch (type) + { + case strexTypeBoolean: + fprintf(f, "%s", (val.b ? "true" : "false") ); + break; + case strexTypeString: + fprintf(f, "%s", (val.s == NULL ? "(null)" : val.s)); + break; + case strexTypeInt: + fprintf(f, "%lld", val.i); + break; + case strexTypeDouble: + fprintf(f, "%f", val.x); + break; } } -int tagSchemaParseIndexes(char *input, int indexes[], int maxIndexCount) -/* This will parse out something that looks like: - * this.array.2.subfield.subarray.3.name - * into - * 2,3 - * Where input is what we parse, indexes is an array maxIndexCount long - * where we put the numbers. */ +char *strexOpToString(enum strexOp op) +/* Return string representation of parse op. */ { -char dot = '.'; -char *s = input; -int indexCount = 0; -int maxMinusOne = maxIndexCount - 1; -for (;;) +switch (op) { - /* Check for end of string */ - char firstChar = *s; - if (firstChar == 0) - break; + case strexOpLiteral: + return "strexOpLiteral"; + case strexOpSymbol: + return "strexOpSymbol"; + + case strexOpStringToBoolean: + return "strexOpStringToBoolean"; + case strexOpIntToBoolean: + return "strexOpIntToBoolean"; + case strexOpDoubleToBoolean: + return "strexOpDoubleToBoolean"; + case strexOpStringToInt: + return "strexOpStringToInt"; + case strexOpDoubleToInt: + return "strexOpDoubleToInt"; + case strexOpBooleanToInt: + return "strexOpBooleanToInt"; + case strexOpStringToDouble: + return "strexOpStringToDouble"; + case strexOpBooleanToDouble: + return "strexOpBooleanToDouble"; + case strexOpIntToDouble: + return "strexOpIntToDouble"; + case strexOpBooleanToString: + return "strexOpBooleanToString"; + case strexOpIntToString: + return "strexOpIntToString"; + case strexOpDoubleToString: + return "strexOpDoubleToString"; + + case strexOpUnaryMinusInt: + return "strexOpUnaryMinusInt"; + case strexOpUnaryMinusDouble: + return "strexOpUnaryMinusDouble"; + + + case strexOpAdd: + return "strexOpAdd"; + + case strexOpBuiltInCall: + return "strexOpBuiltInCall"; + + case strexOpArrayIx: + return "strexOpArrayIx"; + + default: + return "strexOpUnknown"; + } +} + +void strexParseDump(struct strexParse *p, int depth, FILE *f) +/* Dump out strexParse tree and children. */ +{ +spaceOut(f, 3*depth); +fprintf(f, "%s ", strexOpToString(p->op)); +strexValDump(p->val, p->type, f); +fprintf(f, "\n"); +struct strexParse *child; +for (child = p->children; child != NULL; child= child->next) + strexParseDump(child, depth+1, f); +} + - /* If leading char is a dot and if so skip it. */ - boolean startsWithDot = (firstChar == dot); - if (startsWithDot) - ++s; - int numSize = tagSchemaDigitsUpToDot(s); - if (numSize > 0) +static void expectingGot(struct tokenizer *tkz, char *expecting, char *got) +/* Print out error message about unexpected input. */ { - if (indexCount > maxMinusOne) - errAbort("Too many array indexes in %s, maxIndexCount is %d", - input, maxIndexCount); - indexes[indexCount] = atoi(s); - indexCount += 1; - s += numSize; +errAbort("Expecting %s, got %s, line %d of %s", expecting, got, tkz->lf->lineIx, + tkz->lf->fileName); +} + +static void skipOverRequired(struct tokenizer *tkz, char *expecting) +/* Make sure that next token is tok, and skip over it. */ +{ +tokenizerMustHaveNext(tkz); +if (!sameWord(tkz->string, expecting)) + expectingGot(tkz, expecting, tkz->string); +} + + +struct strexParse *strexParseExpression(struct tokenizer *tkz); +/* Parse out an expression with a single value */ + +static struct strexParse *strexParseAtom(struct tokenizer *tkz) +/* Return low level (symbol or literal) */ +{ +char *tok = tokenizerMustHaveNext(tkz); +struct strexParse *p; +AllocVar(p); +char c = tok[0]; +if (c == '\'' || c == '"') + { + p->op = strexOpLiteral; + p->type = strexTypeString; + int len = strlen(tok+1); + p->val.s = cloneStringZ(tok+1, len-1); + } +else if (isalpha(c) || c == '_') + { + p->op = strexOpSymbol; + p->type = strexTypeString; /* String until promoted at least. */ + struct dyString *dy = dyStringNew(64); + for (;;) // Join together . separated things into single symbol */ + { + dyStringAppend(dy, tok); + if ((tok = tokenizerNext(tkz)) == NULL) + break; + if (tok[0] != '.') + { + tokenizerReuse(tkz); + break; + } + dyStringAppend(dy, tok); + if ((tok = tokenizerNext(tkz)) == NULL) + break; + } + p->val.s = dyStringCannibalize(&dy); + } +else if (isdigit(c)) + { + p->op = strexOpLiteral; + p->type = strexTypeInt; + p->val.i = sqlUnsigned(tok); + if ((tok = tokenizerNext(tkz)) != NULL) + { + if (tok[0] == '.') + { + char buf[32]; + tok = tokenizerMustHaveNext(tkz); + safef(buf, sizeof(buf), "%lld.%s", p->val.i, tok); + p->type = strexTypeDouble; + p->val.x = sqlDouble(buf); } else + tokenizerReuse(tkz); + } + } +else if (c == '(') { - int partSize = nonDotSize(s); - s += partSize; + p = strexParseExpression(tkz); + skipOverRequired(tkz, ")"); + } +else + { + errAbort("Unexpected %s line %d of %s", tok, tkz->lf->lineIx, tkz->lf->fileName); + } +return p; } + +static enum strexType commonTypeForBop(enum strexType left, enum strexType right) +/* Return type that will work for a binary operation. */ +{ +if (left == right) + return left; +else if (left == strexTypeString || right == strexTypeString) + return strexTypeString; +else if (left == strexTypeDouble || right == strexTypeDouble) + return strexTypeDouble; +else if (left == strexTypeInt || right == strexTypeInt) + return strexTypeInt; +else if (left == strexTypeBoolean || right == strexTypeBoolean) + return strexTypeBoolean; +else + { + errAbort("Can't find commonTypeForBop"); + return strexTypeString; } -return indexCount; } -char *tagSchemaInsertIndexes(char *bracketed, int indexes[], int indexCount, - struct dyString *scratch) -/* Convert something that looks like: - * this.array.[].subfield.subarray.[].name and 2,3 - * into - * this.array.2.subfield.subarray.3.name - * The scratch string holds the return value. */ + +static enum strexOp booleanCastOp(enum strexType oldType) +/* Return op to convert oldType to boolean. */ +{ +switch (oldType) { -char *pos = bracketed; -int indexPos = 0; -dyStringClear(scratch); + case strexTypeString: + return strexOpStringToBoolean; + case strexTypeInt: + return strexOpIntToBoolean; + case strexTypeDouble: + return strexOpDoubleToBoolean; + default: + internalErr(); + return strexOpUnknown; + } +} -/* Handle special case of leading "[]" */ -if (startsWith("[]", pos)) +static enum strexOp intCastOp(enum strexType oldType) +/* Return op to convert oldType to int. */ +{ +switch (oldType) { - dyStringPrintf(scratch, "%d", indexes[indexPos]); - ++indexPos; - pos += 2; + case strexTypeString: + return strexOpStringToInt; + case strexTypeBoolean: + return strexOpBooleanToInt; + case strexTypeDouble: + return strexOpDoubleToInt; + default: + internalErr(); + return strexOpUnknown; + } } -char *aStart; -for (;;) +static enum strexOp doubleCastOp(enum strexType oldType) +/* Return op to convert oldType to double. */ +{ +switch (oldType) + { + case strexTypeString: + return strexOpStringToDouble; + case strexTypeBoolean: + return strexOpBooleanToDouble; + case strexTypeInt: + return strexOpIntToDouble; + default: + internalErr(); + return strexOpUnknown; + } +} + +static enum strexOp stringCastOp(enum strexType oldType) +/* Return op to convert oldType to double. */ +{ +switch (oldType) + { + case strexTypeDouble: + return strexOpDoubleToString; + case strexTypeBoolean: + return strexOpBooleanToString; + case strexTypeInt: + return strexOpIntToString; + default: + internalErr(); + return strexOpUnknown; + } +} + + + +static struct strexParse *strexParseCoerce(struct strexParse *p, enum strexType type) +/* If p is not of correct type, wrap type conversion node around it. */ +{ +if (p->type == type) + return p; +else + { + struct strexParse *cast; + AllocVar(cast); + cast->children = p; + cast->type = type; + switch (type) + { + case strexTypeString: + cast->op = stringCastOp(p->type); + break; + case strexTypeBoolean: + cast->op = booleanCastOp(p->type); + break; + case strexTypeInt: + cast->op = intCastOp(p->type); + break; + case strexTypeDouble: + cast->op = doubleCastOp(p->type); + break; + default: + internalErr(); + break; + } + return cast; + } +} + +static struct strexParse *strexParseFunction(struct tokenizer *tkz) +/* Handle the (a,b,c) in funcCall(a,b,c). Convert it into tree: +* strexOpBuiltInCall +* strexParse(a) +* strexParse(b) +* strexParse(c) +* or something like that. With no parameters +* strexParseFunction */ +{ +struct strexParse *function = strexParseAtom(tkz); +char *tok = tokenizerNext(tkz); +if (tok == NULL) + tokenizerReuse(tkz); +else if (tok[0] == '(') { - aStart = strchr(pos, '['); - if (aStart == NULL) + uglyf("Making function I hope\n"); + /* Check that the current op, is a pure symbol. */ + if (function->op != strexOpSymbol) + errAbort("Unexpected '(' line %d of %s", tkz->lf->lineIx, tkz->lf->fileName); + + /* We're going to reuse this current op */ + function->op = strexOpBuiltInCall; + function->type = strexTypeString; + + tok = tokenizerMustHaveNext(tkz); + if (tok[0] != ')') { - dyStringAppend(scratch, pos); + tokenizerReuse(tkz); + for (;;) + { + struct strexParse *param = strexParseExpression(tkz); + // struct strexParse *param = strexParseAtom(tkz); + slAddHead(&function->children, param); + tok = tokenizerMustHaveNext(tkz); + uglyf("in function got %s\n", tok); + if (tok[0] == ')') break; + else if (tok[0] != ',') + errAbort("Error in parameter list for %s line %d of %s", function->val.s, + tkz->lf->lineIx, tkz->lf->fileName); + } + slReverse(&function->children); + } } else + tokenizerReuse(tkz); +return function; +} + +static struct strexParse *strexParseIndex(struct tokenizer *tkz) +/* Handle the [] in this[6]. Convert it into tree: +* strexOpArrayIx +* strexParseFunction +* strexParseFunction */ +{ +struct strexParse *collection = strexParseFunction(tkz); +struct strexParse *p = collection; +char *tok = tokenizerNext(tkz); +if (tok == NULL) + tokenizerReuse(tkz); +else if (tok[0] == '[') { - if (indexPos >= indexCount) - errAbort("Expecting %d '[]' in %s, got more.", indexCount, bracketed); - dyStringAppendN(scratch, pos, aStart-pos); - dyStringPrintf(scratch, "%d", indexes[indexPos]); - ++indexPos; - pos = aStart + 2; + struct strexParse *index = strexParseExpression(tkz); + // struct strexParse *index = strexParseFunction(tkz); + index = strexParseCoerce(index, strexTypeInt); + skipOverRequired(tkz, "]"); + AllocVar(p); + p->op = strexOpArrayIx; + p->type = strexTypeString; + p->children = collection; + p->val.s = cloneString(""); + collection->next = index; } +else + tokenizerReuse(tkz); +return p; +} + + +static struct strexParse *strexParseUnaryMinus(struct tokenizer *tkz) +/* Return unary minus sort of parse tree if there's a leading '-' */ +{ +char *tok = tokenizerMustHaveNext(tkz); +if (tok[0] == '-') + { + struct strexParse *c = strexParseIndex(tkz); + struct strexParse *p; + AllocVar(p); + if (c->type == strexTypeInt) + { + p->op = strexOpUnaryMinusInt; + p->type = strexTypeInt; + } + else + { + c = strexParseCoerce(c, strexTypeDouble); + p->op = strexOpUnaryMinusDouble; + p->type = strexTypeDouble; + } + p->children = c; + return p; + } +else + { + tokenizerReuse(tkz); + return strexParseIndex(tkz); } -return scratch->string; } -void freen(char *input) -/* Test something */ +static struct strexParse *strexParseSum(struct tokenizer *tkz) +/* Parse out plus or minus. */ { -struct dyString *withBrackets = dyStringNew(0); -tagSchemaFigureArrayName(input, withBrackets); -int maxIndexCount = 10; -int indexes[maxIndexCount]; -int indexCount = tagSchemaParseIndexes(input, indexes, maxIndexCount); +struct strexParse *p = strexParseUnaryMinus(tkz); +for (;;) { -uglyf("Got index count of %d, values:", indexCount); -int i; -for (i=0; i 0) + /* What we've parsed so far becomes left side of binary op, next term ends up on right. */ + struct strexParse *l = p; + struct strexParse *r = strexParseUnaryMinus(tkz); + + /* Make left and right side into a common type */ + enum strexType childType = commonTypeForBop(l->type, r->type); + l = strexParseCoerce(l, childType); + r = strexParseCoerce(r, childType); + + /* Create the binary operation */ + AllocVar(p); + p->op = strexOpAdd; + p->type = childType; + + /* Now hang children onto node. */ + p->children = l; + l->next = r; + } +} + + +struct strexParse *strexParseExpression(struct tokenizer *tkz) +/* Parse out a clause, usually a where clause. */ +{ +return strexParseSum(tkz); +} + +/* ~~~ start of strexEval */ + +static struct strexEval strexLocalEval(struct strexParse *p, void *record, StrexEvalLookup lookup, + struct lm *lm); +/* Evaluate self on parse tree, allocating memory if needed from lm. */ + + +struct strexEval strexEvalCoerceToString(struct strexEval r, char *buf, int bufSize) +/* Return a version of r with .val.s filled in with something reasonable even + * if r input is not a string */ +{ +assert(bufSize >= 32); +switch (r.type) + { + case strexTypeBoolean: + r.val.s = (r.val.b ? "true" : "false"); + case strexTypeString: + break; /* It's already done. */ + case strexTypeInt: + safef(buf, bufSize, "%lld", r.val.i); + r.val.s = buf; + break; + case strexTypeDouble: + safef(buf, bufSize, "%g", r.val.x); + r.val.s = buf; + break; + default: + internalErr(); + r.val.s = NULL; + break; + } +r.type = strexTypeString; +return r; +} + +static struct strexEval strexEvalAdd(struct strexParse *p, void *record, StrexEvalLookup lookup, + struct lm *lm) +/* Return a + b. */ +{ +struct strexParse *lp = p->children; +struct strexParse *rp = lp->next; +struct strexEval lv = strexLocalEval(lp, record, lookup, lm); +struct strexEval rv = strexLocalEval(rp, record, lookup, lm); +struct strexEval res; +switch (lv.type) + { + case strexTypeInt: + res.val.i = (lv.val.i + rv.val.i); + break; + case strexTypeDouble: + res.val.x = (lv.val.x + rv.val.x); + break; + case strexTypeString: + { + char numBuf[32]; + if (rv.type != strexTypeString) // Perhaps later could coerce to string + { + rv = strexEvalCoerceToString(rv, numBuf, sizeof(numBuf)); + } + int lLen = strlen(lv.val.s); + int rLen = strlen(rv.val.s); + char *s = lmAlloc(lm, lLen + rLen + 1); + memcpy(s, lv.val.s, lLen); + memcpy(s+lLen, rv.val.s, rLen); + res.val.s = s; + break; + } + default: + internalErr(); + res.val.b = FALSE; + break; + } +res.type = lv.type; +return res; +} + + +char *csvParseOneOut(char *csvIn, int ix, struct dyString *scratch) +/* Return csv value of given index or NULL if at end */ { +char *pos = csvIn; int i; - for (i=0; istring, - indexes, indexCount, withIncs); - printf("%s -> %s\n", input, incString); +for (i=0; ichildren; +struct strexParse *index = array->next; +struct strexEval arrayVal = strexLocalEval(array, record, lookup, lm); +struct strexEval indexVal = strexLocalEval(index, record, lookup, lm); +struct strexEval res; +struct dyString *scratch = dyStringNew(0); +char *val = emptyForNull(csvParseOneOut(arrayVal.val.s, indexVal.val.i, scratch)); +res.val.s = cloneString(val); +res.type = strexTypeString; +return res; +} + +static struct strexEval strexEvalCallBuiltIn(struct strexParse *p, void *record, StrexEvalLookup lookup, + struct lm *lm) +/* Handle parse tree generated by an indexed array. */ +{ +char buf[256]; +safef(buf, sizeof(buf), "%s(%d parames)", p->val.s, slCount(p->children)); +struct strexEval res; +res.val.s = cloneString(buf); +res.type = strexTypeString; +return res; } + + +/* ~~~ evaluation part of strex */ + +static struct strexEval strexLocalEval(struct strexParse *p, void *record, StrexEvalLookup lookup, + struct lm *lm) +/* Evaluate self on parse tree, allocating memory if needed from lm. */ +{ +struct strexEval res; +switch (p->op) + { + case strexOpLiteral: + res.val = p->val; + res.type = p->type; + break; + case strexOpSymbol: + res.type = strexTypeString; + char *s = lookup(record, p->val.s); + if (s == NULL) + res.val.s = ""; else - printf("%s has no array index subfields\n", input); + res.val.s = s; + break; + + /* Type casts. */ + case strexOpStringToBoolean: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeBoolean; + res.val.b = (res.val.s[0] != 0); + break; + case strexOpIntToBoolean: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeBoolean; + res.val.b = (res.val.i != 0); + break; + case strexOpDoubleToBoolean: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeBoolean; + res.val.b = (res.val.x != 0.0); + break; + case strexOpStringToInt: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeInt; + res.val.i = atoll(res.val.s); + break; + case strexOpDoubleToInt: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeInt; + res.val.i = res.val.x; + break; + + case strexOpStringToDouble: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeDouble; + res.val.x = atof(res.val.s); + break; + case strexOpBooleanToInt: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeInt; + res.val.i = res.val.b; + break; + case strexOpBooleanToDouble: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeDouble; + res.val.x = res.val.b; + break; + case strexOpIntToDouble: + res = strexLocalEval(p->children, record, lookup, lm); + res.type = strexTypeDouble; + res.val.x = res.val.b; + break; + + /* Arithmetical negation. */ + case strexOpUnaryMinusInt: + res = strexLocalEval(p->children, record, lookup, lm); + res.val.i = -res.val.i; + break; + case strexOpUnaryMinusDouble: + res = strexLocalEval(p->children, record, lookup, lm); + res.val.x = -res.val.x; + break; + + case strexOpArrayIx: + res = strexEvalArrayIx(p, record, lookup, lm); + break; + + case strexOpBuiltInCall: + res = strexEvalCallBuiltIn(p, record, lookup, lm); + break; + + /* Mathematical ops, simple binary type */ + case strexOpAdd: + res = strexEvalAdd(p, record, lookup, lm); + break; + + default: + errAbort("Unknown op %s\n", strexOpToString(p->op)); + res.type = strexTypeInt; // Keep compiler from complaining. + res.val.i = 0; // Keep compiler from complaining. + break; + } +return res; +} + +struct strexEval strexEvalOnRecord(struct strexParse *p, void *record, StrexEvalLookup lookup, + struct lm *lm) +/* Evaluate parse tree on record, using lm for memory for string operations. */ +{ +return strexLocalEval(p, record, lookup, lm); +} + +static struct strexEval strexLocalEval(struct strexParse *p, void *record, StrexEvalLookup lookup, + struct lm *lm); +/* Evaluate self on parse tree, allocating memory if needed from lm. */ + +/* ~~~ end of strex */ + +struct hash *hashFromFile(char *fileName) +/* Given a two column file (key, value) return a hash. */ +{ +struct lineFile *lf = lineFileOpen(fileName, TRUE); +struct hash *hash = hashNew(16); +char *line; +while (lineFileNextReal(lf, &line)) + { + char *word = nextWord(&line); + line = trimSpaces(line); + hashAdd(hash, word, lmCloneString(hash->lm, line)); + } +lineFileClose(&lf); +return hash; +} + +static char *symLookup(void *record, char *key) +/* Lookup symbol in hash */ +{ +struct hash *hash = record; +return hashFindVal(hash, key); } +void freen(char *symbols, char *expression) +/* Test something */ +{ +struct hash *symHash = hashFromFile(symbols); +verbose(1, "Got %d symbols from %s\n", symHash->elCount, symbols); +struct lineFile *lf = lineFileOnString(expression, TRUE, cloneString(expression)); +struct tokenizer *tkz = tokenizerOnLineFile(lf); +tkz->leaveQuotes = TRUE; +struct strexParse *parseTree = strexParseExpression(tkz); +strexParseDump(parseTree, 0, uglyOut); +struct lm *evalLm = lmInit(0); +struct strexEval res =strexEvalOnRecord(parseTree, symHash, symLookup, evalLm); +uglyf("res = %s\n", res.val.s); +} + + int main(int argc, char *argv[]) /* Process command line. */ { optionInit(&argc, argv, options); -if (argc != 2) +if (argc != 3) usage(); -freen(argv[1]); +freen(argv[1], argv[2]); return 0; }