1989-02-07 11:04:05 +00:00
|
|
|
/*
|
|
|
|
* (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
|
|
|
|
* See the copyright notice in the ACK home directory, in the file "Copyright".
|
|
|
|
*/
|
|
|
|
/* $Header$ */
|
|
|
|
/* EXPRESSION TREE HANDLING */
|
|
|
|
|
|
|
|
#include "lint.h"
|
1989-09-19 16:13:23 +00:00
|
|
|
#include "assert.h"
|
1989-02-07 11:04:05 +00:00
|
|
|
#include "botch_free.h"
|
|
|
|
#include <alloc.h>
|
|
|
|
#include "idf.h"
|
1989-09-19 16:13:23 +00:00
|
|
|
#include <flt_arith.h>
|
1989-02-07 11:04:05 +00:00
|
|
|
#include "arith.h"
|
|
|
|
#include "def.h"
|
|
|
|
#include "type.h"
|
|
|
|
#include "label.h"
|
|
|
|
#include "expr.h"
|
|
|
|
#include "LLlex.h"
|
|
|
|
#include "Lpars.h"
|
|
|
|
#include "decspecs.h"
|
|
|
|
#include "declar.h"
|
|
|
|
#include "sizes.h"
|
|
|
|
#include "level.h"
|
1989-11-08 16:52:34 +00:00
|
|
|
#include "use_tmp.h"
|
1989-02-07 11:04:05 +00:00
|
|
|
|
|
|
|
extern char *symbol2str();
|
|
|
|
extern char options[];
|
1989-09-19 16:13:23 +00:00
|
|
|
extern int InSizeof;
|
1989-02-07 11:04:05 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
rank_of(oper)
|
|
|
|
int oper;
|
|
|
|
{
|
|
|
|
/* The rank of the operator oper is returned.
|
|
|
|
*/
|
|
|
|
switch (oper) {
|
|
|
|
default:
|
|
|
|
return 0; /* INT2INT etc. */
|
|
|
|
case '[':
|
|
|
|
case '(':
|
|
|
|
case '.':
|
|
|
|
case ARROW:
|
|
|
|
case PARCOMMA:
|
|
|
|
return 1;
|
|
|
|
case '!':
|
|
|
|
case PLUSPLUS:
|
|
|
|
case MINMIN:
|
|
|
|
case CAST:
|
|
|
|
case SIZEOF:
|
|
|
|
return 2; /* monadic */
|
|
|
|
case '*':
|
|
|
|
case '/':
|
|
|
|
case '%':
|
|
|
|
return 3;
|
|
|
|
case '+':
|
|
|
|
case '-':
|
|
|
|
return 4;
|
|
|
|
case LEFT:
|
|
|
|
case RIGHT:
|
|
|
|
return 5;
|
|
|
|
case '<':
|
|
|
|
case '>':
|
|
|
|
case LESSEQ:
|
|
|
|
case GREATEREQ:
|
|
|
|
return 6;
|
|
|
|
case EQUAL:
|
|
|
|
case NOTEQUAL:
|
|
|
|
return 7;
|
|
|
|
case '&':
|
|
|
|
return 8;
|
|
|
|
case '^':
|
|
|
|
return 9;
|
|
|
|
case '|':
|
|
|
|
return 10;
|
|
|
|
case AND:
|
|
|
|
return 11;
|
|
|
|
case OR:
|
|
|
|
return 12;
|
|
|
|
case '?':
|
|
|
|
case ':':
|
|
|
|
return 13;
|
|
|
|
case '=':
|
|
|
|
case PLUSAB:
|
|
|
|
case MINAB:
|
|
|
|
case TIMESAB:
|
|
|
|
case DIVAB:
|
|
|
|
case MODAB:
|
|
|
|
case RIGHTAB:
|
|
|
|
case LEFTAB:
|
|
|
|
case ANDAB:
|
|
|
|
case XORAB:
|
|
|
|
case ORAB:
|
|
|
|
return 14;
|
|
|
|
case ',':
|
|
|
|
return 15;
|
|
|
|
}
|
|
|
|
/*NOTREACHED*/
|
|
|
|
}
|
|
|
|
|
|
|
|
dot2expr(expp)
|
|
|
|
struct expr **expp;
|
|
|
|
{
|
|
|
|
/* The token in dot is converted into an expression, a
|
|
|
|
pointer to which is stored in *expp.
|
|
|
|
*/
|
|
|
|
register struct expr *ex = new_expr();
|
|
|
|
|
|
|
|
*expp = ex;
|
|
|
|
ex->ex_file = dot.tk_file;
|
|
|
|
ex->ex_line = dot.tk_line;
|
|
|
|
switch (DOT) {
|
|
|
|
case IDENTIFIER:
|
|
|
|
idf2expr(ex);
|
|
|
|
break;
|
|
|
|
case INTEGER:
|
|
|
|
int2expr(ex);
|
|
|
|
break;
|
|
|
|
case FLOATING:
|
|
|
|
float2expr(ex);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
crash("bad conversion to expression");
|
|
|
|
/*NOTREACHED*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
idf2expr(expr)
|
|
|
|
register struct expr *expr;
|
|
|
|
{
|
|
|
|
/* Dot contains an identifier which is turned into an
|
|
|
|
expression.
|
|
|
|
Note that this constitutes an applied occurrence of
|
|
|
|
the identifier.
|
|
|
|
*/
|
|
|
|
register struct idf *idf = dot.tk_idf; /* != 0*/
|
|
|
|
register struct def *def = idf->id_def;
|
|
|
|
|
|
|
|
if (def == 0) {
|
1989-09-19 16:13:23 +00:00
|
|
|
if (AHEAD == '(') {
|
|
|
|
/* function call, declare name IMPLICITly (3.3.2.2) */
|
1989-10-27 13:33:10 +00:00
|
|
|
if (!options['o'])
|
|
|
|
warning("implicit declaration of function %s"
|
|
|
|
, idf->id_text);
|
1989-09-19 16:13:23 +00:00
|
|
|
add_def(idf, IMPLICIT, funint_type, level);
|
|
|
|
} else {
|
1989-02-07 11:04:05 +00:00
|
|
|
if (!is_anon_idf(idf))
|
|
|
|
error("%s undefined", idf->id_text);
|
|
|
|
/* declare idf anyway */
|
|
|
|
add_def(idf, 0, error_type, level);
|
|
|
|
}
|
|
|
|
def = idf->id_def;
|
|
|
|
}
|
|
|
|
/* now def != 0 */
|
|
|
|
if (def->df_type->tp_fund == LABEL) {
|
|
|
|
expr_error(expr, "illegal use of label %s", idf->id_text);
|
|
|
|
expr->ex_type = error_type;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
#ifndef LINT
|
1989-11-08 16:52:34 +00:00
|
|
|
if (!InSizeof) {
|
|
|
|
if (! def->df_used) {
|
|
|
|
#ifndef PREPEND_SCOPES
|
|
|
|
code_scope(idf->id_text, def);
|
|
|
|
#endif /* PREPEND_SCOPES */
|
|
|
|
def->df_used = 1;
|
|
|
|
}
|
|
|
|
}
|
1989-02-07 11:04:05 +00:00
|
|
|
#endif LINT
|
|
|
|
expr->ex_type = def->df_type;
|
1989-09-19 16:13:23 +00:00
|
|
|
if (expr->ex_type == error_type) {
|
1989-02-07 11:04:05 +00:00
|
|
|
expr->ex_flags |= EX_ERROR;
|
1989-09-19 16:13:23 +00:00
|
|
|
}
|
1989-02-07 11:04:05 +00:00
|
|
|
}
|
|
|
|
expr->ex_lvalue =
|
|
|
|
( def->df_type->tp_fund == FUNCTION ||
|
|
|
|
def->df_type->tp_fund == ARRAY ||
|
|
|
|
def->df_sc == ENUM
|
|
|
|
) ? 0 : 1;
|
|
|
|
if (def->df_type->tp_typequal & TQ_CONST)
|
|
|
|
expr->ex_flags |= EX_READONLY;
|
|
|
|
if (def->df_type->tp_typequal & TQ_VOLATILE)
|
|
|
|
expr->ex_flags |= EX_VOLATILE;
|
|
|
|
expr->ex_class = Value;
|
|
|
|
if (def->df_sc == ENUM) {
|
|
|
|
expr->VL_CLASS = Const;
|
|
|
|
expr->VL_VALUE = def->df_address;
|
|
|
|
}
|
|
|
|
#ifndef LINT
|
|
|
|
else
|
|
|
|
if (def->df_sc == STATIC && def->df_level >= L_LOCAL) {
|
|
|
|
expr->VL_CLASS = Label;
|
|
|
|
expr->VL_LBL = def->df_address;
|
|
|
|
expr->VL_VALUE = (arith)0;
|
|
|
|
}
|
|
|
|
#endif LINT
|
|
|
|
else {
|
|
|
|
expr->VL_CLASS = Name;
|
|
|
|
expr->VL_IDF = idf;
|
|
|
|
expr->VL_VALUE = (arith)0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1989-09-19 16:13:23 +00:00
|
|
|
string2expr(expp, str, len)
|
1989-02-07 11:04:05 +00:00
|
|
|
register struct expr **expp;
|
1989-09-19 16:13:23 +00:00
|
|
|
int len;
|
1989-02-07 11:04:05 +00:00
|
|
|
char *str;
|
|
|
|
{
|
|
|
|
/* The string in the argument is converted into an expression,
|
|
|
|
a pointer to which is stored in *expp.
|
|
|
|
*/
|
|
|
|
register struct expr *ex = new_expr();
|
|
|
|
|
|
|
|
*expp = ex;
|
|
|
|
ex->ex_file = dot.tk_file;
|
|
|
|
ex->ex_line = dot.tk_line;
|
|
|
|
ex->ex_type = string_type;
|
1989-09-19 16:13:23 +00:00
|
|
|
/* ex->ex_type = qualifier_type(ex->ex_type, TQ_CONST); */
|
1989-02-07 11:04:05 +00:00
|
|
|
ex->ex_flags |= EX_READONLY;
|
1989-10-20 16:16:06 +00:00
|
|
|
/* ex->ex_lvalue = 0; */
|
1989-02-07 11:04:05 +00:00
|
|
|
ex->ex_class = String;
|
|
|
|
ex->SG_VALUE = str;
|
|
|
|
ex->SG_LEN = len;
|
1989-10-20 16:16:06 +00:00
|
|
|
/* ex->SG_DATLAB = 0; */
|
1989-02-07 11:04:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int2expr(expr)
|
|
|
|
struct expr *expr;
|
|
|
|
{
|
|
|
|
/* Dot contains an integer constant which is turned
|
|
|
|
into an expression.
|
|
|
|
*/
|
|
|
|
fill_int_expr(expr, dot.tk_ival, dot.tk_fund);
|
|
|
|
}
|
|
|
|
|
|
|
|
float2expr(expr)
|
|
|
|
register struct expr *expr;
|
|
|
|
{
|
|
|
|
/* Dot contains a floating point constant which is turned
|
|
|
|
into an expression.
|
|
|
|
*/
|
|
|
|
register int fund;
|
|
|
|
|
|
|
|
fund = dot.tk_fund;
|
|
|
|
switch (fund) {
|
|
|
|
case FLOAT:
|
|
|
|
expr->ex_type = float_type;
|
|
|
|
break;
|
|
|
|
case DOUBLE:
|
|
|
|
expr->ex_type = double_type;
|
|
|
|
break;
|
|
|
|
case LNGDBL:
|
|
|
|
expr->ex_type = lngdbl_type;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
crash("(float2expr) bad fund %s\n", symbol2str(fund));
|
|
|
|
}
|
|
|
|
expr->ex_class = Float;
|
|
|
|
expr->FL_VALUE = dot.tk_fval;
|
1989-09-19 16:13:23 +00:00
|
|
|
flt_str2flt(expr->FL_VALUE, &(expr->FL_ARITH));
|
|
|
|
ASSERT(flt_status != FLT_NOFLT);
|
|
|
|
if (flt_status == FLT_OVFL)
|
|
|
|
expr_warning(expr,"internal floating point overflow");
|
1989-02-07 11:04:05 +00:00
|
|
|
expr->FL_DATLAB = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct expr*
|
|
|
|
intexpr(ivalue, fund)
|
|
|
|
arith ivalue;
|
|
|
|
int fund;
|
|
|
|
{
|
|
|
|
/* The value ivalue is turned into an integer expression of
|
|
|
|
the size indicated by fund.
|
|
|
|
*/
|
|
|
|
register struct expr *expr = new_expr();
|
1989-09-19 16:13:23 +00:00
|
|
|
|
1989-02-07 11:04:05 +00:00
|
|
|
expr->ex_file = dot.tk_file;
|
|
|
|
expr->ex_line = dot.tk_line;
|
|
|
|
fill_int_expr(expr, ivalue, fund);
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
fill_int_expr(ex, ivalue, fund)
|
|
|
|
register struct expr *ex;
|
|
|
|
arith ivalue;
|
|
|
|
int fund;
|
|
|
|
{
|
|
|
|
/* Details derived from ivalue and fund are put into the
|
|
|
|
constant integer expression ex.
|
|
|
|
*/
|
|
|
|
switch (fund) {
|
|
|
|
case INT:
|
|
|
|
ex->ex_type = int_type;
|
|
|
|
break;
|
|
|
|
case INTEGER:
|
|
|
|
if (ivalue >= 0 && ivalue <= max_int) {
|
|
|
|
ex->ex_type = int_type;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/*FALL THROUGH*/
|
|
|
|
case LONG:
|
|
|
|
ex->ex_type =
|
|
|
|
(ivalue & (1L << (8*long_size - 1))) ? ulong_type
|
|
|
|
: long_type;
|
|
|
|
break;
|
|
|
|
case ULONG:
|
|
|
|
ex->ex_type = ulong_type;
|
|
|
|
break;
|
|
|
|
case UNSIGNED:
|
|
|
|
/* We cannot make a test like
|
|
|
|
ivalue <= max_unsigned
|
|
|
|
because, if
|
1989-09-19 16:13:23 +00:00
|
|
|
sizeof(arith) == int_size
|
|
|
|
holds, max_unsigned may be a negative arith in
|
1989-02-07 11:04:05 +00:00
|
|
|
which case the comparison results in an unexpected
|
|
|
|
answer.
|
|
|
|
*/
|
|
|
|
ex->ex_type =
|
|
|
|
(ivalue & ~max_int) ?
|
|
|
|
( (ivalue & ~max_unsigned) ?
|
|
|
|
( ivalue & (1L<<(8*long_size-1)) ?
|
|
|
|
ulong_type : long_type
|
|
|
|
) : uint_type
|
|
|
|
) : int_type;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
crash("(intexpr) bad fund %s\n", symbol2str(fund));
|
|
|
|
/*NOTREACHED*/
|
|
|
|
}
|
|
|
|
ex->ex_class = Value;
|
|
|
|
ex->VL_CLASS = Const;
|
|
|
|
ex->VL_VALUE = ivalue;
|
|
|
|
cut_size(ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct expr *
|
|
|
|
new_oper(tp, e1, oper, e2)
|
|
|
|
struct type *tp;
|
|
|
|
register struct expr *e1, *e2;
|
|
|
|
{
|
|
|
|
/* A new expression is constructed which consists of the
|
|
|
|
operator oper which has e1 and e2 as operands; for a
|
|
|
|
monadic operator e1 == NILEXPR.
|
|
|
|
During the construction of the right recursive initialisation
|
|
|
|
tree it is possible for e2 to be NILEXPR.
|
|
|
|
*/
|
|
|
|
register struct expr *expr = new_expr();
|
|
|
|
register struct oper *op;
|
|
|
|
|
|
|
|
if (e2) {
|
|
|
|
register struct expr *e = e2;
|
|
|
|
|
|
|
|
while (e->ex_class == Oper && e->OP_LEFT)
|
|
|
|
e = e->OP_LEFT;
|
|
|
|
expr->ex_file = e->ex_file;
|
|
|
|
expr->ex_line = e->ex_line;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (e1) {
|
|
|
|
register struct expr *e = e1;
|
|
|
|
|
|
|
|
while (e->ex_class == Oper && e->OP_RIGHT)
|
|
|
|
e = e->OP_RIGHT;
|
|
|
|
expr->ex_file = e->ex_file;
|
|
|
|
expr->ex_line = e->ex_line;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
expr->ex_file = dot.tk_file;
|
|
|
|
expr->ex_line = dot.tk_line;
|
|
|
|
}
|
|
|
|
|
|
|
|
expr->ex_type = tp;
|
|
|
|
expr->ex_class = Oper;
|
|
|
|
/* combine depths and flags of both expressions */
|
|
|
|
if (e2) {
|
|
|
|
int e1_depth = e1 ? e1->ex_depth : 0;
|
|
|
|
int e1_flags = e1 ? e1->ex_flags : 0;
|
|
|
|
|
|
|
|
expr->ex_depth =
|
|
|
|
(e1_depth > e2->ex_depth ? e1_depth : e2->ex_depth) + 1;
|
1989-09-19 16:13:23 +00:00
|
|
|
expr->ex_flags = (e1_flags | e2->ex_flags)
|
1989-09-25 14:28:10 +00:00
|
|
|
& ~(EX_PARENS | EX_READONLY | EX_VOLATILE );
|
1989-02-07 11:04:05 +00:00
|
|
|
}
|
|
|
|
op = &expr->ex_object.ex_oper;
|
|
|
|
op->op_type = tp;
|
|
|
|
op->op_oper = oper;
|
|
|
|
op->op_left = e1;
|
|
|
|
op->op_right = e2;
|
|
|
|
#ifdef LINT
|
|
|
|
lint_new_oper(expr);
|
|
|
|
#endif LINT
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
chk_cst_expr(expp)
|
1989-11-09 14:06:35 +00:00
|
|
|
struct expr **expp;
|
1989-02-07 11:04:05 +00:00
|
|
|
{
|
|
|
|
/* The expression expr is checked for constancy.
|
|
|
|
|
|
|
|
There are 6 places where constant expressions occur in C:
|
|
|
|
1. after #if
|
|
|
|
2. in a global initialization
|
|
|
|
3. as size in an array declaration
|
|
|
|
4. as value in an enum declaration
|
|
|
|
5. as width in a bit field
|
|
|
|
6. as case value in a switch
|
|
|
|
|
|
|
|
The constant expression in a global initialization is
|
|
|
|
handled separately (by IVAL()).
|
|
|
|
|
|
|
|
There are various disparate restrictions on each of
|
|
|
|
the others in the various C compilers. I have tried some
|
|
|
|
hypotheses to unify them, but all have failed.
|
|
|
|
|
|
|
|
Special problems (of which there is only one, sizeof in
|
|
|
|
Preprocessor #if) have to be dealt with locally
|
|
|
|
*/
|
|
|
|
register struct expr *expr = *expp;
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
print_expr("constant_expression", expr);
|
|
|
|
#endif DEBUG
|
1989-11-09 14:06:35 +00:00
|
|
|
switch(expr->ex_type->tp_fund) {
|
|
|
|
case CHAR:
|
|
|
|
case SHORT:
|
|
|
|
case INT:
|
|
|
|
case ENUM:
|
|
|
|
case LONG:
|
|
|
|
if (is_ld_cst(expr)) {
|
|
|
|
return;
|
|
|
|
}
|
1989-09-19 16:13:23 +00:00
|
|
|
expr_error(expr, "expression is not constant");
|
1989-11-09 14:06:35 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
expr_error(expr, "non-numerical constant expression");
|
|
|
|
break;
|
1989-02-07 11:04:05 +00:00
|
|
|
}
|
1989-11-09 14:06:35 +00:00
|
|
|
erroneous2int(expp);
|
1989-02-07 11:04:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
init_expression(eppp, expr)
|
1989-11-09 14:06:35 +00:00
|
|
|
register struct expr ***eppp;
|
|
|
|
struct expr *expr;
|
1989-02-07 11:04:05 +00:00
|
|
|
{
|
|
|
|
/* The expression expr is added to the tree designated
|
|
|
|
indirectly by **eppp.
|
|
|
|
The natural form of a tree representing an
|
|
|
|
initial_value_list is right-recursive, ie. with the
|
|
|
|
left-most comma as main operator. The iterative grammar in
|
|
|
|
expression.g, however, tends to produce a left-recursive
|
|
|
|
tree, ie. one with the right-most comma as its main
|
|
|
|
operator.
|
|
|
|
To produce a right-recursive tree from the iterative
|
|
|
|
grammar, we keep track of the address of the pointer where
|
|
|
|
the next expression must be hooked in.
|
|
|
|
*/
|
|
|
|
**eppp = new_oper(void_type, expr, INITCOMMA, NILEXPR);
|
|
|
|
*eppp = &(**eppp)->OP_RIGHT;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
is_ld_cst(expr)
|
|
|
|
register struct expr *expr;
|
|
|
|
{
|
|
|
|
/* An expression is a `load-time constant' if it is of the form
|
|
|
|
<idf> +/- <integral> or <integral>.
|
|
|
|
*/
|
|
|
|
#ifdef LINT
|
|
|
|
if (expr->ex_class == String)
|
|
|
|
return 1;
|
|
|
|
#endif LINT
|
|
|
|
return expr->ex_lvalue == 0 && expr->ex_class == Value;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
is_cp_cst(expr)
|
1989-09-19 16:13:23 +00:00
|
|
|
struct expr *expr;
|
1989-02-07 11:04:05 +00:00
|
|
|
{
|
|
|
|
/* An expression is a `compile-time constant' if it is a
|
|
|
|
load-time constant, and the idf is not there.
|
|
|
|
*/
|
|
|
|
return is_ld_cst(expr) && expr->VL_CLASS == Const;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
is_fp_cst(expr)
|
1989-09-19 16:13:23 +00:00
|
|
|
struct expr *expr;
|
1989-02-07 11:04:05 +00:00
|
|
|
{
|
|
|
|
/* An expression is a `floating-point constant' if it consists
|
|
|
|
of the float only.
|
|
|
|
*/
|
|
|
|
return expr->ex_class == Float;
|
|
|
|
}
|
1989-09-19 16:13:23 +00:00
|
|
|
|
|
|
|
int
|
|
|
|
is_zero_cst(expr)
|
|
|
|
register struct expr *expr;
|
|
|
|
{
|
|
|
|
flt_arith var;
|
|
|
|
|
|
|
|
switch(expr->ex_class) {
|
|
|
|
case Value:
|
|
|
|
return expr->VL_VALUE == 0;
|
|
|
|
case Float:
|
|
|
|
flt_arith2flt((arith) 0, &var);
|
|
|
|
return flt_cmp(&var, &(expr->FL_ARITH)) == 0;
|
|
|
|
}
|
|
|
|
/*NOTREACHED*/
|
|
|
|
}
|
1989-02-07 11:04:05 +00:00
|
|
|
|
|
|
|
free_expression(expr)
|
|
|
|
register struct expr *expr;
|
|
|
|
{
|
|
|
|
/* The expression expr is freed recursively.
|
|
|
|
*/
|
|
|
|
if (expr) {
|
1989-09-19 16:13:23 +00:00
|
|
|
if (expr->ex_class == Float && expr->FL_VALUE)
|
|
|
|
free(expr->FL_VALUE);
|
1989-02-07 11:04:05 +00:00
|
|
|
if (expr->ex_class == Oper) {
|
|
|
|
free_expression(expr->OP_LEFT);
|
|
|
|
free_expression(expr->OP_RIGHT);
|
|
|
|
}
|
|
|
|
free_expr(expr);
|
|
|
|
}
|
|
|
|
}
|