680 lines
20 KiB
C
680 lines
20 KiB
C
/*****************************************************************************
|
|
*
|
|
* eval.c
|
|
*
|
|
* Arithmetical evaluation.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include "m4.h"
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* First, a warm-up: Increment and decrement.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* opIncr
|
|
* opDecr
|
|
*
|
|
* Returns the value of its argument, augmented or diminished by unity.
|
|
* The extra ptokNil covers us in the case where $# is zero.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void STDCALL
|
|
opAddItokDat(ARGV argv, DAT dat)
|
|
{
|
|
AT at = atTraditionalPtok(ptokArgv(1));
|
|
#ifdef STRICT_M4
|
|
if (ctokArgv != 1) {
|
|
Warn("wrong number of arguments to %P", ptokArgv(0));
|
|
}
|
|
#endif
|
|
PushAt(at+dat);
|
|
}
|
|
|
|
DeclareOp(opIncr)
|
|
{
|
|
opAddItokDat(argv, 1);
|
|
}
|
|
|
|
DeclareOp(opDecr)
|
|
{
|
|
opAddItokDat(argv, -1);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Now the gross part: Eval.
|
|
*
|
|
* Expression evaluation is performed by a parser which is a mix of
|
|
* shift-reduce and recursive-descent. (Worst of both worlds.)
|
|
*
|
|
* The precedence table for our expression language reads
|
|
*
|
|
* (...) grouping > primary
|
|
* + - unary
|
|
* ** exponentiation
|
|
* * / % multiplicative
|
|
* + - additive
|
|
* << >> shift
|
|
* == != > >= < <= relational
|
|
* ! logical-negate
|
|
* ~ bit-negate
|
|
* & bit-and
|
|
* ^ bit-xor
|
|
* | bit-or
|
|
* && logical-and
|
|
* || logical-or
|
|
*
|
|
*
|
|
* COMPAT -- AT&T style uses ^ for exponentiation; we use it for xor
|
|
*
|
|
* NOTE: "the rest is bogus" went the original comment. I forget what
|
|
* I meant by that.
|
|
*
|
|
* The precedence table for the C-style expression language reads
|
|
*
|
|
* (...) grouping \ primary
|
|
* + - ~ ! unary /
|
|
* * / % multiplative \
|
|
* + - additive \
|
|
* << >> shift |
|
|
* < > <= >= relational |
|
|
* == != equality \ secondary
|
|
* & bit-and /
|
|
* ^ bit-xor |
|
|
* | bit-or |
|
|
* && logical-and /
|
|
* || logical-or /
|
|
* ? : ternary > tertiary
|
|
*
|
|
* Recursive descent is performed on the primary/secondary/tertiary
|
|
* scale, but shift-reduce is performed within the secondary phase.
|
|
*
|
|
* The reason is that the operators in the secondary phase are
|
|
* (1) binary, and (2) non-recursive. These two properties
|
|
* make shift-reduce easy to implement.
|
|
*
|
|
* Primaries are recursive, so they are easier to implement in
|
|
* recursive-descent. Tertiaries would clog up the shift-reduce
|
|
* grammar, so they've been moved to recursive-descent as well.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* EachEop
|
|
*
|
|
* Before calling this macro, define the following macros, each of
|
|
* which will be called with three arguments,
|
|
*
|
|
* nm = operator name as a C identifier (e.g., "Add")
|
|
* op = operator name as a bare token (e.g., "+")
|
|
* cb = length of operator name
|
|
*
|
|
* The macros should be
|
|
*
|
|
* x1 -- for native C unary operators
|
|
* x1a -- for native C unary operators which have binary aliases
|
|
* x2 -- for native C binary operators
|
|
* x2a -- for native C binary operators which have unary aliases
|
|
* x2n -- for non-native C binary operators
|
|
* xp -- for phantom operators,
|
|
* in which case op and cb are useless
|
|
*
|
|
* The order in which operators appear is important for the purpose
|
|
* of tokenization. Longer operators must precede shorter ones.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#define EachEop() \
|
|
x2(Shl, <<, 2) \
|
|
x2(Shr, >>, 2) \
|
|
x2(Le, <=, 2) \
|
|
x2(Ge, >=, 2) \
|
|
x2(Eq, ==, 2) \
|
|
x2(Ne, !=, 2) \
|
|
x2(Land, &&, 2) \
|
|
x2(Lor, ||, 2) \
|
|
x2n(Exp, **, 2) \
|
|
x2(Mul, *, 1) \
|
|
x2(Div, /, 1) \
|
|
x2(Mod, %, 1) \
|
|
x2a(Add, +, 1) /* These two must be */ \
|
|
x2a(Sub, -, 1) /* in exactly this order */ \
|
|
x2(Lt, <, 1) \
|
|
x2(Gt, >, 1) \
|
|
x2(Band, &, 1) \
|
|
x2(Bxor, ^, 1) \
|
|
x2(Bor, |, 1) \
|
|
x1(Lnot, !, 1) \
|
|
x1(Bnot, ~, 1) \
|
|
x1a(Plu, +, x) /* These two must be */ \
|
|
x1a(Neg, -, x) /* in exactly this order */ \
|
|
xp(Flush, @, 0) \
|
|
xp(Boe, @, 0) \
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* MakeEop
|
|
*
|
|
* Each binary operator has a handler which returns the combined
|
|
* value.
|
|
*
|
|
* Each unary operator has a handler which returns the operator
|
|
* applied to its single argument.
|
|
*
|
|
* All the operators are C native, except for Exp, which is handled
|
|
* directly.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
typedef AT (STDCALL *EOP1)(AT at);
|
|
typedef AT (STDCALL *EOP2)(AT a, AT b);
|
|
|
|
#define x1(nm, op, cb) AT STDCALL at##nm##At(AT at) { return op at; }
|
|
#define x1a(nm, op, cb) AT STDCALL at##nm##At(AT at) { return op at; }
|
|
#define x2(nm, op, cb) AT STDCALL at##nm##AtAt(AT a, AT b) { return a op b; }
|
|
#define x2a(nm, op, cb) AT STDCALL at##nm##AtAt(AT a, AT b) { return a op b; }
|
|
#define x2n(nm, op, cb)
|
|
#define xp(nm, op, cb)
|
|
|
|
EachEop()
|
|
|
|
#undef x1
|
|
#undef x1a
|
|
#undef x2
|
|
#undef x2a
|
|
#undef x2n
|
|
#undef xp
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* atExpAtAt
|
|
*
|
|
* Implement the exponentiation operator.
|
|
*
|
|
* QUIRK! AT&T returns 1 if $2 < 0. GNU raises an error.
|
|
* I side with AT&T on this, only out of laziness.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
AT STDCALL
|
|
atExpAtAt(AT a, AT b)
|
|
{
|
|
AT at = 1;
|
|
while (b > 0) {
|
|
if (b & 1) {
|
|
at = at * a;
|
|
}
|
|
a = a * a;
|
|
b = b / 2;
|
|
}
|
|
return at;
|
|
}
|
|
|
|
TOK tokExpr; /* Current expression context */
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* MakeEopTab
|
|
*
|
|
* Table of operators and operator precedence. Each entry in the
|
|
* table contains the name, length, handler, precedence, and
|
|
* flags that describe what kind of operator it is.
|
|
*
|
|
* Items are listed in precedence order here; the EachBop will
|
|
* emit the table corrctly.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
typedef enum EOPFL {
|
|
eopflUn = 1,
|
|
eopflBin = 2,
|
|
eopflAmb = 4,
|
|
} EOPFL;
|
|
|
|
typedef UINT PREC; /* Operator precedence */
|
|
|
|
typedef struct EOPI {
|
|
PTCH ptch;
|
|
CTCH ctch;
|
|
union {
|
|
EOP1 eop1;
|
|
EOP2 eop2;
|
|
} u;
|
|
PREC prec;
|
|
EOPFL eopfl;
|
|
} EOPI, *PEOPI;
|
|
|
|
#define MakeEopi(nm, ctch, pfn, prec, eopfl) \
|
|
{ TEXT(nm), ctch, { (EOP1)pfn }, prec, eopfl },
|
|
|
|
enum {
|
|
m4precNeg = 14, m4precPlu = 14,
|
|
m4precExp = 13,
|
|
m4precMul = 12, m4precDiv = 12, m4precMod = 12,
|
|
m4precAdd = 11, m4precSub = 11,
|
|
m4precShl = 10, m4precShr = 10,
|
|
m4precEq = 9, m4precNe = 9,
|
|
m4precGt = 9, m4precGe = 9,
|
|
m4precLt = 9, m4precLe = 9,
|
|
m4precLnot = 8,
|
|
m4precBnot = 7,
|
|
m4precBand = 6,
|
|
m4precBxor = 5,
|
|
m4precBor = 4,
|
|
m4precLand = 3,
|
|
m4precLor = 2,
|
|
m4precFlush = 1, /* Flushing out everything but Boe */
|
|
m4precBoe = 0, /* Beginning-of-expression */
|
|
};
|
|
|
|
#define x1(nm, op, cb) static TCH rgtch##nm[cb] = #op;
|
|
#define x1a(nm, op, cb)
|
|
#define x2(nm, op, cb) static TCH rgtch##nm[cb] = #op;
|
|
#define x2a(nm, op, cb) static TCH rgtch##nm[cb] = #op;
|
|
#define x2n(nm, op, cb) static TCH rgtch##nm[cb] = #op;
|
|
#define xp(nm, op, cb)
|
|
|
|
EachEop()
|
|
|
|
#undef x1
|
|
#undef x1a
|
|
#undef x2
|
|
#undef x2a
|
|
#undef x2n
|
|
#undef xp
|
|
|
|
#define x1(nm, op, cb) MakeEopi(rgtch##nm, cb, at##nm##At, m4prec##nm, eopflUn)
|
|
#define x1a(nm, op, cb) MakeEopi(0, 0, at##nm##At, m4prec##nm, eopflUn)
|
|
#define x2(nm, op, cb) MakeEopi(rgtch##nm, cb, at##nm##AtAt, m4prec##nm, eopflBin)
|
|
#define x2a(nm, op, cb) MakeEopi(rgtch##nm, cb, at##nm##AtAt, m4prec##nm, eopflAmb + eopflBin) /* initially bin */
|
|
#define x2n(nm, op, cb) MakeEopi(rgtch##nm, cb, at##nm##AtAt, m4prec##nm, eopflBin)
|
|
#define xp(nm, op, cb) MakeEopi(0, 0, 0, m4prec##nm, 0)
|
|
|
|
EOPI rgeopi[] = {
|
|
EachEop()
|
|
};
|
|
|
|
#undef x1
|
|
#undef x1a
|
|
#undef x2
|
|
#undef x2a
|
|
#undef x2n
|
|
#undef xp
|
|
|
|
#define x1(nm, op, cb) ieopi##nm,
|
|
#define x1a(nm, op, cb) ieopi##nm,
|
|
#define x2(nm, op, cb) ieopi##nm,
|
|
#define x2a(nm, op, cb) ieopi##nm,
|
|
#define x2n(nm, op, cb) ieopi##nm,
|
|
#define xp(nm, op, cb) ieopi##nm,
|
|
|
|
typedef enum IEOPI {
|
|
EachEop()
|
|
ieopMax,
|
|
} IEOPI;
|
|
|
|
#undef x1
|
|
#undef x1a
|
|
#undef x2
|
|
#undef x2a
|
|
#undef x2n
|
|
|
|
#define peopiBoe (&rgeopi[ieopiBoe])
|
|
#define peopiFlush (&rgeopi[ieopiFlush])
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* fPrimary, fSecondary, fTertiary
|
|
*
|
|
* Forward declarations for the recursive-descent parser.
|
|
*
|
|
* Each parses a token/expression of the appropriate class
|
|
* and leaves it on the top of the expression stack, or
|
|
* returns 0 if the value could not be parsed.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
F STDCALL fPrimary(void);
|
|
F STDCALL fSecondary(void);
|
|
#define fTertiary fSecondary
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Cells
|
|
*
|
|
* The expression stack consists of structures which, for lack of
|
|
* a better name, are called `cells'. Each cell can hold either
|
|
* an expression operator or an integer, distinguished by the fEopi
|
|
* field.
|
|
*
|
|
* In keeping with parser terminology, the act of pushing something
|
|
* onto the stack is called `shifting'. Collapsing objects is called
|
|
* `reducing'.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
typedef struct CELL {
|
|
F fEopi;
|
|
union {
|
|
PEOPI peopi;
|
|
AT at;
|
|
} u;
|
|
} CELL, *PCELL;
|
|
typedef UINT CCELL, ICELL;
|
|
|
|
PCELL rgcellEstack; /* The expression stack */
|
|
PCELL pcellMax; /* End of the stack */
|
|
PCELL pcellCur; /* Next free cell */
|
|
|
|
INLINE PCELL
|
|
pcellTos(ICELL icell)
|
|
{
|
|
Assert(pcellCur - 1 - icell >= rgcellEstack);
|
|
return pcellCur - 1 - icell;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Stack munging
|
|
*
|
|
* Quickie routines that poke at the top-of-stack.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
INLINE F fWantOp(void) { return !pcellTos(1)->fEopi; }
|
|
|
|
INLINE F fOpTos(ICELL icell) { return pcellTos(icell)->fEopi; }
|
|
|
|
INLINE PEOPI
|
|
peopiTos(ICELL icell)
|
|
{
|
|
Assert(fOpTos(icell));
|
|
return pcellTos(icell)->u.peopi;
|
|
}
|
|
|
|
INLINE AT
|
|
atTos(ICELL icell)
|
|
{
|
|
Assert(!fOpTos(icell));
|
|
return pcellTos(icell)->u.at;
|
|
}
|
|
|
|
INLINE F fBinTos(ICELL icell) { return peopiTos(icell)->eopfl & eopflBin; }
|
|
INLINE F fUnTos(ICELL icell) { return peopiTos(icell)->eopfl & eopflUn; }
|
|
INLINE F fAmbTos(ICELL icell) { return peopiTos(icell)->eopfl & eopflAmb; }
|
|
INLINE PREC precTos(ICELL icell) { return peopiTos(icell)->prec; }
|
|
|
|
INLINE void
|
|
UnFromAmb(ICELL icell)
|
|
{
|
|
Assert(fOpTos(icell));
|
|
pcellTos(icell)->u.peopi += (ieopiPlu - ieopiAdd);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* ShiftCell
|
|
*
|
|
* Shift a cell onto the expression stack.
|
|
*
|
|
* QShiftCell shifts in a cell assuming that the stack is already
|
|
* big enough to handle it.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void STDCALL
|
|
QShiftCell(UINT_PTR uiObj, F fEopi)
|
|
{
|
|
Assert(pcellCur < pcellMax);
|
|
pcellCur->fEopi = fEopi;
|
|
if (fEopi) {
|
|
pcellCur->u.peopi = (PEOPI)uiObj;
|
|
} else {
|
|
pcellCur->u.at = (INT)uiObj;
|
|
}
|
|
pcellCur++;
|
|
}
|
|
|
|
void STDCALL
|
|
ShiftCell(UINT_PTR uiObj, F fEopi)
|
|
{
|
|
if (pcellCur >= pcellMax) {
|
|
CCELL ccell = (CCELL)(pcellMax - rgcellEstack + 128); /* Should be enough */
|
|
rgcellEstack = pvReallocPvCb(rgcellEstack, ccell * sizeof(CELL));
|
|
pcellCur = rgcellEstack + ccell - 128;
|
|
pcellMax = rgcellEstack + ccell;
|
|
}
|
|
QShiftCell(uiObj, fEopi);
|
|
}
|
|
|
|
#define ShiftPeopi(peopi) ShiftCell((UINT_PTR)(peopi), 1)
|
|
#define ShiftAt(at) ShiftCell((UINT_PTR)(at), 0)
|
|
|
|
#define QShiftPeopi(peopi) QShiftCell((UINT_PTR)(peopi), 1)
|
|
#define QShiftAt(at) QShiftCell((UINT_PTR)(at), 0)
|
|
|
|
#define Drop(icell) (pcellCur -= (icell))
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* ReducePrec
|
|
*
|
|
* Reduce until everything with higher precedence has been cleaned off.
|
|
*
|
|
* Tos(0) should be a fresh operator.
|
|
* Everything underneath should be a valid partial evaluation.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void STDCALL
|
|
Reduce(void)
|
|
{
|
|
PEOPI peopi;
|
|
|
|
Assert(fOpTos(0)); /* Tos(0) should be an op */
|
|
Assert(!fOpTos(1)); /* Tos(1) should be an int */
|
|
Assert(fOpTos(2)); /* Tos(2) should be an op */
|
|
|
|
peopi = peopiTos(0); /* Save this */
|
|
Drop(1); /* before we drop it */
|
|
|
|
while (precTos(1) > peopi->prec) {
|
|
AT at;
|
|
if (fUnTos(1)) {
|
|
at = peopiTos(1)->u.eop1(atTos(0));
|
|
Drop(2); /* Drop the op and the arg */
|
|
} else {
|
|
Assert(fBinTos(1));
|
|
Assert(!fOpTos(2));
|
|
at = peopiTos(1)->u.eop2(atTos(2), atTos(0));
|
|
Drop(3); /* Drop the op and two args */
|
|
}
|
|
QShiftAt(at); /* Shift the answer back on */
|
|
Assert(!fOpTos(0)); /* Tos(0) should be an int */
|
|
Assert(fOpTos(1)); /* Tos(1) should be an op */
|
|
}
|
|
QShiftPeopi(peopi); /* Restore the original op */
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* fPrimary
|
|
*
|
|
* Parse the next expression token and shift it onto the expression
|
|
* stack. Zero is returned if there is no next token, or the token
|
|
* is invalid.
|
|
*
|
|
* Here is where parenthesized expressions are handled, in a
|
|
* recursive-descent manner.
|
|
*
|
|
* Ambiguous operators (ones which can be either unary or binary)
|
|
* are returned as binary.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
F STDCALL
|
|
fPrimary(void)
|
|
{
|
|
SkipWhitePtok(&tokExpr); /* Skip leading whitespace */
|
|
|
|
|
|
/*
|
|
* First see if we can find an operator.
|
|
*/
|
|
{
|
|
PEOPI peopi;
|
|
for (peopi = rgeopi; peopi < &rgeopi[ieopiPlu]; peopi++) {
|
|
if (peopi->ctch <= ctchSPtok(&tokExpr) &&
|
|
fEqPtchPtchCtch(ptchPtok(&tokExpr), peopi->ptch,
|
|
peopi->ctch)) {
|
|
EatHeadPtokCtch(&tokExpr, peopi->ctch); /* Eat the op */
|
|
ShiftPeopi(peopi);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Didn't find an operator. Look for an integer.
|
|
*/
|
|
{
|
|
AT at;
|
|
if (fEvalPtokPat(&tokExpr, &at)) {
|
|
ShiftAt(at);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Not an integer either. Maybe a parenthesized expression.
|
|
*/
|
|
{
|
|
if (ptchPtok(&tokExpr)[0] == '(') {
|
|
EatHeadPtokCtch(&tokExpr, 1); /* Eat the paren */
|
|
if (fTertiary()) { /* Leaves answer on top of stack */
|
|
if (ptchPtok(&tokExpr)[0] == ')') {
|
|
EatHeadPtokCtch(&tokExpr, 1); /* Eat the paren */
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
return 0; /* Trouble down below */
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unrecognized token. Return failure.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* fSecondary
|
|
*
|
|
* Parse an expression from the expression stream, leaving the
|
|
* result on the top of the expression stack.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
F STDCALL
|
|
fSecondary(void)
|
|
{
|
|
ShiftPeopi(peopiBoe); /* Beginning-of-expression marker */
|
|
|
|
while (fPrimary()) {
|
|
if (fWantOp()) {
|
|
if (fOpTos(0)) {
|
|
if (fBinTos(0)) {
|
|
Reduce();
|
|
} else {
|
|
return 0; /* Unary operator unexpected */
|
|
}
|
|
} else {
|
|
return 0; /* Integer unexpected */
|
|
}
|
|
} else { /* Integer expected */
|
|
if (fOpTos(0)) {
|
|
if (fAmbTos(0)) {
|
|
UnFromAmb(0); /* Disambiguify */
|
|
; /* Unary operator already shifted */
|
|
} else if (fUnTos(0)) {
|
|
; /* Unary operator already shifted */
|
|
} else {
|
|
return 0; /* Binary operator unexpected */
|
|
}
|
|
} else {
|
|
; /* Integer already shifted */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fOpTos(0)) {
|
|
return 0; /* Ended in partial expression */
|
|
}
|
|
|
|
{
|
|
AT at;
|
|
|
|
ShiftPeopi(peopiFlush); /* Flush out the rest of the expr */
|
|
Reduce(); /* to get a single number back */
|
|
Assert(peopiTos(0) == peopiFlush);
|
|
at = atTos(1);
|
|
Assert(peopiTos(2) == peopiBoe); /* Should be back to start */
|
|
Drop(3);
|
|
ShiftAt(at);
|
|
}
|
|
return 1;
|
|
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* opEval
|
|
*
|
|
* Evaluate the first expr.
|
|
*
|
|
* QUIRK! AT&T m4 considers a consisting entirely of whitespace to
|
|
* evaluate to zero. (Probably due to a default accumulator in the
|
|
* initial state of the evaluator.) GNU considers it an error.
|
|
* I side with GNU on this one.
|
|
*
|
|
* QUIRK! If a negative width is passed, AT&T silently treats it
|
|
* as zero. GNU raises an error. I side with A&T out of laziness.
|
|
*
|
|
* QUIRK! If a width greater than around 8000 is passed, AT&T
|
|
* silently treats it as zero. GNU uses the full value. I side
|
|
* with GNU on this one.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
DeclareOp(opEval)
|
|
{
|
|
if (ctokArgv) {
|
|
SetStaticPtokPtchCtch(&tokExpr, ptchArgv(1), ctchArgv(1));
|
|
D(tokExpr.tsfl |= tsflScratch);
|
|
if (fTertiary()) {
|
|
PushAtRadixCtch(atTos(0), (unsigned)atTraditionalPtok(ptokArgv(2)),
|
|
ctokArgv >= 3 ? atTraditionalPtok(ptokArgv(3)) :0);
|
|
Drop(1);
|
|
Assert(pcellCur == rgcellEstack);
|
|
} else {
|
|
TOK tokPre;
|
|
SetStaticPtokPtchCtch(&tokPre, ptchArgv(1),
|
|
ctchArgv(1) - ctchSPtok(&tokExpr));
|
|
Die("Expression error at %P <<error>> %P", &tokPre, &tokExpr);
|
|
}
|
|
}
|
|
}
|