mirror of
https://github.com/cc65/cc65.git
synced 2024-12-24 11:31:31 +00:00
Reusing code from parseadd() for ArrayRef().
Now index[ptr] works in constant expressions. Fixed codegen for cast type in constant expressions. Calls on swapstk in 'i[ptr]' is avoided when possible.
This commit is contained in:
parent
f5972dfd08
commit
99c7fe0ada
423
src/cc65/expr.c
423
src/cc65/expr.c
@ -74,11 +74,20 @@ static GenDesc GenOASGN = { TOK_OR_ASSIGN, GEN_NOPUSH, g_or };
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Helper functions */
|
||||
/* Forward declarations */
|
||||
/*****************************************************************************/
|
||||
|
||||
|
||||
|
||||
static void parseadd (ExprDesc* Expr, int DoArrayRef);
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Helper functions */
|
||||
/*****************************************************************************/
|
||||
|
||||
|
||||
static unsigned GlobalModeFlags (const ExprDesc* Expr)
|
||||
/* Return the addressing mode flags for the given expression */
|
||||
{
|
||||
@ -1333,294 +1342,6 @@ static void Primary (ExprDesc* E)
|
||||
|
||||
|
||||
|
||||
static void ArrayRef (ExprDesc* Expr)
|
||||
/* Handle an array reference. This function needs a rewrite. */
|
||||
{
|
||||
int ConstBaseAddr;
|
||||
ExprDesc Subscript;
|
||||
CodeMark Mark1;
|
||||
CodeMark Mark2;
|
||||
TypeCode Qualifiers;
|
||||
Type* ElementType;
|
||||
Type* tptr1;
|
||||
|
||||
ED_Init (&Subscript);
|
||||
Subscript.Flags |= Expr->Flags & E_MASK_KEEP_SUBEXPR;
|
||||
|
||||
/* Skip the bracket */
|
||||
NextToken ();
|
||||
|
||||
/* Get the type of left side */
|
||||
tptr1 = Expr->Type;
|
||||
|
||||
/* We can apply a special treatment for arrays that have a const base
|
||||
** address. This is true for most arrays and will produce a lot better
|
||||
** code. Check if this is a "quasi-const base" address.
|
||||
*/
|
||||
ConstBaseAddr = ED_IsRVal (Expr) && ED_IsLocQuasiConst (Expr);
|
||||
|
||||
/* If we have a quasi-const base address, we delay the address fetch */
|
||||
GetCodePos (&Mark1);
|
||||
if (!ConstBaseAddr) {
|
||||
/* Get a pointer to the array into the primary */
|
||||
LoadExpr (CF_NONE, Expr);
|
||||
|
||||
/* Get the array pointer on stack. Do not push more than 16
|
||||
** bit, even if this value is greater, since we cannot handle
|
||||
** other than 16bit stuff when doing indexing.
|
||||
*/
|
||||
GetCodePos (&Mark2);
|
||||
g_push (CF_PTR, 0);
|
||||
}
|
||||
|
||||
/* TOS now contains ptr to array elements. Get the subscript. */
|
||||
MarkedExprWithCheck (hie0, &Subscript);
|
||||
|
||||
/* Check the types of array and subscript. We can either have a
|
||||
** pointer/array to the left, in which case the subscript must be of an
|
||||
** integer type, or we have an integer to the left, in which case the
|
||||
** subscript must be a pointer/array.
|
||||
** Since we do the necessary checking here, we can rely later on the
|
||||
** correct types.
|
||||
*/
|
||||
Qualifiers = T_QUAL_NONE;
|
||||
if (IsClassPtr (Expr->Type)) {
|
||||
if (!IsClassInt (Subscript.Type)) {
|
||||
Error ("Array subscript is not an integer");
|
||||
/* To avoid any compiler errors, make the expression a valid int */
|
||||
ED_MakeConstAbsInt (&Subscript, 0);
|
||||
}
|
||||
if (IsTypeArray (Expr->Type)) {
|
||||
Qualifiers = GetQualifier (Expr->Type);
|
||||
}
|
||||
ElementType = Indirect (Expr->Type);
|
||||
} else if (IsClassInt (Expr->Type)) {
|
||||
if (!IsClassPtr (Subscript.Type)) {
|
||||
Error ("Subscripted value is neither array nor pointer");
|
||||
/* To avoid compiler errors, make the subscript a char[] at
|
||||
** address 0.
|
||||
*/
|
||||
ED_MakeConstAbs (&Subscript, 0, GetCharArrayType (1));
|
||||
} else if (IsTypeArray (Subscript.Type)) {
|
||||
Qualifiers = GetQualifier (Subscript.Type);
|
||||
}
|
||||
ElementType = Indirect (Subscript.Type);
|
||||
} else {
|
||||
Error ("Cannot subscript");
|
||||
/* To avoid compiler errors, fake both the array and the subscript, so
|
||||
** we can just proceed.
|
||||
*/
|
||||
ED_MakeConstAbs (Expr, 0, GetCharArrayType (1));
|
||||
ED_MakeConstAbsInt (&Subscript, 0);
|
||||
ElementType = Indirect (Expr->Type);
|
||||
}
|
||||
|
||||
/* The element type has the combined qualifiers from itself and the array,
|
||||
** it is a member of (if any).
|
||||
*/
|
||||
if (GetQualifier (ElementType) != (GetQualifier (ElementType) | Qualifiers)) {
|
||||
ElementType = TypeDup (ElementType);
|
||||
ElementType->C |= Qualifiers;
|
||||
}
|
||||
|
||||
/* If the subscript is a bit-field, load it and make it an rvalue */
|
||||
if (ED_IsBitField (&Subscript)) {
|
||||
LoadExpr (CF_NONE, &Subscript);
|
||||
ED_FinalizeRValLoad (&Subscript);
|
||||
}
|
||||
|
||||
/* Make the address of the array element from the base and subscript */
|
||||
if (ED_IsConstAbs (&Subscript) && ED_CodeRangeIsEmpty (&Subscript)) {
|
||||
|
||||
/* The array subscript is a constant. Since we can have the element
|
||||
** address directly as base+offset, we can remove the array address
|
||||
** push onto the stack before if loading subscript doesn't tamper that
|
||||
** address in the primary.
|
||||
*/
|
||||
if (!ConstBaseAddr) {
|
||||
RemoveCode (&Mark2);
|
||||
} else {
|
||||
/* Get an array pointer into the primary */
|
||||
LoadExpr (CF_NONE, Expr);
|
||||
}
|
||||
|
||||
if (IsClassPtr (Expr->Type)) {
|
||||
|
||||
/* Lhs is pointer/array. Scale the subscript value according to
|
||||
** the element size.
|
||||
*/
|
||||
Subscript.IVal *= CheckedSizeOf (ElementType);
|
||||
|
||||
/* Remove the address load code */
|
||||
RemoveCode (&Mark1);
|
||||
|
||||
/* In case of an array, we can adjust the offset of the expression
|
||||
** already in Expr. If the base address was a constant, we can even
|
||||
** remove the code that loaded the address into the primary.
|
||||
*/
|
||||
if (!IsTypeArray (Expr->Type)) {
|
||||
|
||||
/* It's a pointer, so we do have to load it into the primary
|
||||
** first (if it's not already there).
|
||||
*/
|
||||
if (!ConstBaseAddr && ED_IsLVal (Expr)) {
|
||||
LoadExpr (CF_NONE, Expr);
|
||||
ED_FinalizeRValLoad (Expr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust the offset */
|
||||
Expr->IVal += Subscript.IVal;
|
||||
|
||||
} else {
|
||||
|
||||
/* Scale the lhs value according to the element type */
|
||||
g_scale (TypeOf (tptr1), CheckedSizeOf (ElementType));
|
||||
|
||||
/* Add the subscript. Since arrays are indexed by integers,
|
||||
** we will ignore the true type of the subscript here and
|
||||
** use always an int. #### Use offset but beware of LoadExpr!
|
||||
*/
|
||||
g_inc (CF_INT | CF_CONST, Subscript.IVal);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* Array subscript is not constant. Load it into the primary */
|
||||
GetCodePos (&Mark2);
|
||||
LoadExpr (CF_NONE, &Subscript);
|
||||
|
||||
/* Do scaling */
|
||||
if (IsClassPtr (Expr->Type)) {
|
||||
|
||||
/* Indexing is based on unsigneds, so we will just use the integer
|
||||
** portion of the index (which is in (e)ax, so there's no further
|
||||
** action required).
|
||||
*/
|
||||
g_scale (CF_INT, CheckedSizeOf (ElementType));
|
||||
|
||||
} else {
|
||||
|
||||
/* Get the int value on top. If we come here, we're sure, both
|
||||
** values are 16 bit (the first one was truncated if necessary
|
||||
** and the second one is a pointer). Note: If ConstBaseAddr is
|
||||
** true, we don't have a value on stack, so to "swap" both, just
|
||||
** push the subscript.
|
||||
*/
|
||||
if (ConstBaseAddr) {
|
||||
g_push (CF_INT, 0);
|
||||
LoadExpr (CF_NONE, Expr);
|
||||
ConstBaseAddr = 0;
|
||||
} else {
|
||||
g_swap (CF_INT);
|
||||
}
|
||||
|
||||
/* Scale it */
|
||||
g_scale (TypeOf (tptr1), CheckedSizeOf (ElementType));
|
||||
|
||||
}
|
||||
|
||||
/* The offset is now in the primary register. It we didn't have a
|
||||
** constant base address for the lhs, the lhs address is already
|
||||
** on stack, and we must add the offset. If the base address was
|
||||
** constant, we call special functions to add the address to the
|
||||
** offset value.
|
||||
*/
|
||||
if (!ConstBaseAddr) {
|
||||
|
||||
/* The array base address is on stack and the subscript is in the
|
||||
** primary. Add both.
|
||||
*/
|
||||
g_add (CF_INT, 0);
|
||||
|
||||
} else {
|
||||
|
||||
/* The subscript is in the primary, and the array base address is
|
||||
** in Expr. If the subscript has itself a constant address, it is
|
||||
** often a better idea to reverse again the order of the
|
||||
** evaluation. This will generate better code if the subscript is
|
||||
** a byte sized variable. But beware: This is only possible if the
|
||||
** subscript was not scaled, that is, if this was a byte array
|
||||
** or pointer.
|
||||
*/
|
||||
if (ED_IsLocQuasiConst (&Subscript) &&
|
||||
CheckedSizeOf (ElementType) == SIZEOF_CHAR) {
|
||||
|
||||
unsigned Flags;
|
||||
|
||||
/* Reverse the order of evaluation */
|
||||
if (CheckedSizeOf (Subscript.Type) == SIZEOF_CHAR) {
|
||||
Flags = CF_CHAR;
|
||||
} else {
|
||||
Flags = CF_INT;
|
||||
}
|
||||
RemoveCode (&Mark2);
|
||||
|
||||
/* Get a pointer to the array into the primary. */
|
||||
LoadExpr (CF_NONE, Expr);
|
||||
|
||||
/* Add the variable */
|
||||
if (ED_IsLocStack (&Subscript)) {
|
||||
g_addlocal (Flags, Subscript.IVal);
|
||||
} else {
|
||||
Flags |= GlobalModeFlags (&Subscript);
|
||||
g_addstatic (Flags, Subscript.Name, Subscript.IVal);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (ED_IsLocNone (Expr) ||
|
||||
(ED_IsLocAbs (Expr) && ED_IsAddrExpr (Expr))) {
|
||||
/* Constant numeric address. Just add it */
|
||||
g_inc (CF_INT, Expr->IVal);
|
||||
} else if (ED_IsLocStack (Expr)) {
|
||||
/* Base address is a local variable address */
|
||||
if (ED_IsAddrExpr (Expr)) {
|
||||
g_addaddr_local (CF_INT, Expr->IVal);
|
||||
} else {
|
||||
g_addlocal (CF_PTR, Expr->IVal);
|
||||
}
|
||||
} else {
|
||||
/* Base address is a static variable address */
|
||||
unsigned Flags = CF_INT | GlobalModeFlags (Expr);
|
||||
if (ED_IsAddrExpr (Expr)) {
|
||||
/* Add the address of the location */
|
||||
g_addaddr_static (Flags, Expr->Name, Expr->IVal);
|
||||
} else {
|
||||
/* Add the contents of the location */
|
||||
g_addstatic (Flags, Expr->Name, Expr->IVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The address of the element is an rvalue in the primary */
|
||||
ED_FinalizeRValLoad (Expr);
|
||||
|
||||
}
|
||||
|
||||
/* The final result is usually an lvalue expression of element type
|
||||
** referenced in the primary, unless it is once again an array. We can just
|
||||
** assume the usual case first, and change it later if necessary.
|
||||
*/
|
||||
ED_IndExpr (Expr);
|
||||
Expr->Type = ElementType;
|
||||
|
||||
/* An array element is actually a variable. So the rules for variables with
|
||||
** respect to the reference type apply: If it's an array, it is virtually
|
||||
** an rvalue address, otherwise it's an lvalue reference. (A function would
|
||||
** also be an rvalue address, but an array cannot contain functions).
|
||||
*/
|
||||
if (IsTypeArray (Expr->Type)) {
|
||||
ED_AddrExpr (Expr);
|
||||
}
|
||||
|
||||
/* Consume the closing bracket */
|
||||
ConsumeRBrack ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void StructRef (ExprDesc* Expr)
|
||||
/* Process struct/union field after . or ->. */
|
||||
{
|
||||
@ -1793,7 +1514,7 @@ static void hie11 (ExprDesc *Expr)
|
||||
|
||||
case TOK_LBRACK:
|
||||
/* Array reference */
|
||||
ArrayRef (Expr);
|
||||
parseadd (Expr, 1);
|
||||
break;
|
||||
|
||||
case TOK_LPAREN:
|
||||
@ -3034,10 +2755,11 @@ static void hie9 (ExprDesc *Expr)
|
||||
|
||||
|
||||
|
||||
static void parseadd (ExprDesc* Expr)
|
||||
/* Parse an expression with the binary plus operator. Expr contains the
|
||||
** unprocessed left hand side of the expression and will contain the
|
||||
** result of the expression on return.
|
||||
static void parseadd (ExprDesc* Expr, int DoArrayRef)
|
||||
/* Parse an expression with the binary plus or subscript operator. Expr contains
|
||||
** the unprocessed left hand side of the expression and will contain the result
|
||||
** of the expression on return. If DoArrayRef is zero, this evaluates the binary
|
||||
** plus operation. Otherwise, this evaluates the subscript operation.
|
||||
*/
|
||||
{
|
||||
ExprDesc Expr2;
|
||||
@ -3052,7 +2774,7 @@ static void parseadd (ExprDesc* Expr)
|
||||
ED_Init (&Expr2);
|
||||
Expr2.Flags |= Expr->Flags & E_MASK_KEEP_SUBEXPR;
|
||||
|
||||
/* Skip the PLUS token */
|
||||
/* Skip the PLUS or opening bracket token */
|
||||
NextToken ();
|
||||
|
||||
/* Get the left hand side type, initialize operation flags */
|
||||
@ -3077,7 +2799,7 @@ static void parseadd (ExprDesc* Expr)
|
||||
if (ED_IsQuasiConst (Expr)) {
|
||||
|
||||
/* The left hand side is a constant of some sort. Good. Get rhs */
|
||||
ExprWithCheck (hie9, &Expr2);
|
||||
ExprWithCheck (DoArrayRef ? hie0 : hie9, &Expr2);
|
||||
|
||||
/* Right hand side is constant. Get the rhs type */
|
||||
rhst = Expr2.Type;
|
||||
@ -3094,7 +2816,7 @@ static void parseadd (ExprDesc* Expr)
|
||||
lscale = CheckedPSizeOf (rhst);
|
||||
/* Operate on pointers, result type is a pointer */
|
||||
flags = CF_PTR;
|
||||
} else if (IsClassInt (lhst) && IsClassInt (rhst)) {
|
||||
} else if (!DoArrayRef && IsClassInt (lhst) && IsClassInt (rhst)) {
|
||||
/* Integer addition */
|
||||
flags = CF_INT;
|
||||
} else {
|
||||
@ -3123,20 +2845,22 @@ static void parseadd (ExprDesc* Expr)
|
||||
}
|
||||
|
||||
if (AddDone) {
|
||||
/* Adjust the result */
|
||||
if (IsClassPtr (lhst)) {
|
||||
/* Result type is a pointer */
|
||||
Expr->Type = PtrConversion (lhst);
|
||||
} else if (IsClassPtr (rhst)) {
|
||||
/* Result type is a pointer */
|
||||
Expr->Type = PtrConversion (rhst);
|
||||
} else {
|
||||
/* Limit the calculated value to the range of its type */
|
||||
LimitExprValue (Expr);
|
||||
}
|
||||
/* Adjust the result for addition */
|
||||
if (!DoArrayRef) {
|
||||
if (IsClassPtr (lhst)) {
|
||||
/* Result type is a pointer */
|
||||
Expr->Type = lhst;
|
||||
} else if (IsClassPtr (rhst)) {
|
||||
/* Result type is a pointer */
|
||||
Expr->Type = rhst;
|
||||
} else {
|
||||
/* Limit the calculated value to the range of its type */
|
||||
LimitExprValue (Expr);
|
||||
}
|
||||
|
||||
/* The result is always an rvalue */
|
||||
ED_MarkExprAsRVal (Expr);
|
||||
/* The result is always an rvalue */
|
||||
ED_MarkExprAsRVal (Expr);
|
||||
}
|
||||
} else {
|
||||
/* Decide the order */
|
||||
if (!ED_IsAbs (&Expr2) && rscale > 1) {
|
||||
@ -3214,7 +2938,7 @@ static void parseadd (ExprDesc* Expr)
|
||||
/* Operate on pointers, result type is a pointer */
|
||||
flags |= CF_PTR;
|
||||
Expr->Type = Expr2.Type;
|
||||
} else if (IsClassInt (lhst) && IsClassInt (rhst)) {
|
||||
} else if (!DoArrayRef && IsClassInt (lhst) && IsClassInt (rhst)) {
|
||||
/* Integer addition */
|
||||
flags |= typeadjust (Expr, &Expr2, 1);
|
||||
} else {
|
||||
@ -3270,8 +2994,6 @@ static void parseadd (ExprDesc* Expr)
|
||||
g_add (CF_PTR, 0);
|
||||
}
|
||||
}
|
||||
/* Array and function types must be converted to pointer types */
|
||||
Expr->Type = PtrConversion (Expr->Type);
|
||||
|
||||
/* Result is an rvalue in primary register */
|
||||
ED_FinalizeRValLoad (Expr);
|
||||
@ -3286,7 +3008,7 @@ static void parseadd (ExprDesc* Expr)
|
||||
g_push (flags, 0); /* --> stack */
|
||||
|
||||
/* Evaluate the rhs */
|
||||
MarkedExprWithCheck (hie9, &Expr2);
|
||||
MarkedExprWithCheck (DoArrayRef ? hie0 : hie9, &Expr2);
|
||||
|
||||
/* Get the rhs type */
|
||||
rhst = Expr2.Type;
|
||||
@ -3309,7 +3031,7 @@ static void parseadd (ExprDesc* Expr)
|
||||
/* Operate on pointers, result type is a pointer */
|
||||
flags = CF_PTR;
|
||||
Expr->Type = Expr2.Type;
|
||||
} else if (IsClassInt (lhst) && IsClassInt (rhst)) {
|
||||
} else if (!DoArrayRef && IsClassInt (lhst) && IsClassInt (rhst)) {
|
||||
/* Integer addition */
|
||||
flags = typeadjust (Expr, &Expr2, 1);
|
||||
} else {
|
||||
@ -3356,7 +3078,7 @@ static void parseadd (ExprDesc* Expr)
|
||||
/* Operate on pointers, result type is a pointer */
|
||||
flags = CF_PTR;
|
||||
Expr->Type = Expr2.Type;
|
||||
} else if (IsClassInt (lhst) && IsClassInt (rhst)) {
|
||||
} else if (!DoArrayRef && IsClassInt (lhst) && IsClassInt (rhst)) {
|
||||
/* Integer addition. Note: Result is never constant.
|
||||
** Problem here is that typeadjust does not know if the
|
||||
** variable is an rvalue or lvalue, so if both operands
|
||||
@ -3380,18 +3102,69 @@ static void parseadd (ExprDesc* Expr)
|
||||
|
||||
}
|
||||
|
||||
/* Array and function types must be converted to pointer types */
|
||||
Expr->Type = PtrConversion (Expr->Type);
|
||||
|
||||
/* Result is an rvalue in primary register */
|
||||
ED_FinalizeRValLoad (Expr);
|
||||
}
|
||||
|
||||
if (AddDone < 0) {
|
||||
Error ("Invalid operands for binary operator '+'");
|
||||
/* Deal with array ref */
|
||||
if (DoArrayRef) {
|
||||
TypeCode Qualifiers = T_QUAL_NONE;
|
||||
Type* ElementType;
|
||||
|
||||
/* Check the types of array and subscript */
|
||||
if (IsClassPtr (lhst)) {
|
||||
if (!IsClassInt (rhst)) {
|
||||
Error ("Array subscript is not an integer");
|
||||
ED_MakeConstAbs (Expr, 0, GetCharArrayType (1));
|
||||
} else if (IsTypeArray (lhst)) {
|
||||
Qualifiers = GetQualifier (lhst);
|
||||
}
|
||||
} else if (IsClassInt (lhst)) {
|
||||
if (!IsClassPtr (rhst)) {
|
||||
Error ("Subscripted value is neither array nor pointer");
|
||||
ED_MakeConstAbs (Expr, 0, GetCharArrayType (1));
|
||||
} else if (IsTypeArray (rhst)) {
|
||||
Qualifiers = GetQualifier (rhst);
|
||||
}
|
||||
} else {
|
||||
Error ("Cannot subscript");
|
||||
ED_MakeConstAbs (Expr, 0, GetCharArrayType (1));
|
||||
}
|
||||
|
||||
/* The element type has the combined qualifiers from itself and the array,
|
||||
** it is a member of (if any).
|
||||
*/
|
||||
ElementType = Indirect (Expr->Type);
|
||||
if (GetQualifier (ElementType) != (GetQualifier (ElementType) | Qualifiers)) {
|
||||
ElementType = TypeDup (ElementType);
|
||||
ElementType->C |= Qualifiers;
|
||||
}
|
||||
|
||||
/* The final result is usually an lvalue expression of element type
|
||||
** referenced in the primary, unless it is once again an array. We can just
|
||||
** assume the usual case first, and change it later if necessary.
|
||||
*/
|
||||
ED_IndExpr (Expr);
|
||||
Expr->Type = ElementType;
|
||||
|
||||
/* An array element is actually a variable. So the rules for variables with
|
||||
** respect to the reference type apply: If it's an array, it is virtually
|
||||
** an rvalue address, otherwise it's an lvalue reference. (A function would
|
||||
** also be an rvalue address, but an array cannot contain functions).
|
||||
*/
|
||||
if (IsTypeArray (Expr->Type)) {
|
||||
ED_AddrExpr (Expr);
|
||||
}
|
||||
|
||||
/* Consume the closing bracket */
|
||||
ConsumeRBrack ();
|
||||
} else {
|
||||
/* Array and function types must be converted to pointer types */
|
||||
Expr->Type = PtrConversion (Expr->Type);
|
||||
if (AddDone < 0) {
|
||||
Error ("Invalid operands for binary operator '+'");
|
||||
} else {
|
||||
/* Array and function types must be converted to pointer types */
|
||||
Expr->Type = PtrConversion (Expr->Type);
|
||||
}
|
||||
}
|
||||
|
||||
/* Condition code not set */
|
||||
@ -3436,7 +3209,7 @@ static void parsesub (ExprDesc* Expr)
|
||||
|
||||
/* Remember the output queue position, then bring the value onto the stack */
|
||||
GetCodePos (&Mark1);
|
||||
LoadExpr (CF_NONE, Expr); /* --> primary register */
|
||||
LoadExpr (CF_NONE, Expr); /* --> primary register */
|
||||
GetCodePos (&Mark2);
|
||||
g_push (TypeOf (lhst), 0); /* --> stack */
|
||||
|
||||
@ -3658,7 +3431,7 @@ void hie8 (ExprDesc* Expr)
|
||||
ExprWithCheck (hie9, Expr);
|
||||
while (CurTok.Tok == TOK_PLUS || CurTok.Tok == TOK_MINUS) {
|
||||
if (CurTok.Tok == TOK_PLUS) {
|
||||
parseadd (Expr);
|
||||
parseadd (Expr, 0);
|
||||
} else {
|
||||
parsesub (Expr);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user