1998 note: As I've learned more about parsers and compilers, I realize that what I wrote here was a hand-crafted hybrid of a traditional recursive-descent parser and a shift/reduce table-driven parser for what is probably an LL(1) grammar. I haven't become so expert yet that I'm sure exactly just which kind of grammar this parses, but as far as I can tell, this could all be done with recursive-descent; I simply hacked in a cool shortcut to avoid lots of recursive functions that would merely serve to figure out precedence. -------- /* In the following description, I use the terms "number", "value" and "node" somewhat loosely, as they can all refer to the same things - a number (integer, floating point, fraction, "dimensioned"), or a whole tree representing an expression that would evaluate to one of the aformentioned. */ /* These three arrays contain all the info needed to drive the parser. Each operator has a corresponding priority (on an arbitrary scale), and a type: infix (binary), prefix, or postfix. Note that operators with the same symbol but different meanings must be carefully arranged in order to parse right. */ EXPORT char const operator[OPS][OPLEN] = { "Null operator", "+","-","*","/","^","AND","NOT","!","OR","MOD","-", "&&","&","~","||","|" }; EXPORT int const priority[OPS] = { 0, 120,120,130,130,135,50,140,132,40,130,140, 50,80,140,40,60 }; /* Operator type: 0=NULL, 1=infix (binary), 2=prefix, 3=postfix */ EXPORT int const optype[OPS] = { 0, 1,1,1,1,1,1,2,3,1,1,2, 1,1,2,1,1 }; /* Here is the pseudo-C algorithim that is the guts of the parser. Parenthesized sub-expressions are handled in a traditional recursive- descent manner, by a recursive call (from the value parsing function) */ parse(string to parse) { create local operand and number stacks; put NULL on operand and number stacks; /* NULL here is used as a kind of ultimate lowest priority operator */ forever { if next symbol could be a prefix operand, /* if it could be, it is */ push on operand stack; continue; /* else if next symbol is another kind of operand, there's an error */ n = parse a value (a number, dimensioned number, variable name, or recursively parse a parethesized sub-expression); forever { op = parse operator; /* op is actually an integer */ while (priority[op] <= priority[operator on top of stack]) { if (priority[op] == pririty[operator on top of stack] && op is not NULL) return n; /* Except for error handling (which I haven't included here), this is they only point of exit. It is reached after the algorithm has hit the end of the string or an unmatched right parenthesis and then having "reduced" - converted all the leftover operators and numbers on the stacks into an expression tree, n.*/ if (op on top of stack is a prefix operator) { n2 = new node representing the operator on the stack operating on n; n = n2; } else { /* Operator is binary (infix) */ n2 = new node representing the operator on the stack operating on the number on the stack and n; n = n2; pop number stack; } pop operator stack; } /* end while */ if (op is a postfix operator) { n2 = new node representing operator op operating on n; n = n2; } else { /* op is a lower priority binary (infix) operator */ push op on operator stack; push n on number stack; break; /* to outer forever loop */ } } /* end inner forever */ } /* end outer forever */ /* This point never reached */ }