| /* YACC parser for Go expressions, for GDB. |
| |
| Copyright (C) 2012-2021 Free Software Foundation, Inc. |
| |
| This file is part of GDB. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| /* This file is derived from c-exp.y, p-exp.y. */ |
| |
| /* Parse a Go expression from text in a string, |
| and return the result as a struct expression pointer. |
| That structure contains arithmetic operations in reverse polish, |
| with constants represented by operations that are followed by special data. |
| See expression.h for the details of the format. |
| What is important here is that it can be built up sequentially |
| during the process of parsing; the lower levels of the tree always |
| come first in the result. |
| |
| Note that malloc's and realloc's in this file are transformed to |
| xmalloc and xrealloc respectively by the same sed command in the |
| makefile that remaps any other malloc/realloc inserted by the parser |
| generator. Doing this with #defines and trying to control the interaction |
| with include files (<malloc.h> and <stdlib.h> for example) just became |
| too messy, particularly when such includes can be inserted at random |
| times by the parser generator. */ |
| |
| /* Known bugs or limitations: |
| |
| - Unicode |
| - &^ |
| - '_' (blank identifier) |
| - automatic deref of pointers |
| - method expressions |
| - interfaces, channels, etc. |
| |
| And lots of other things. |
| I'm sure there's some cleanup to do. |
| */ |
| |
| %{ |
| |
| #include "defs.h" |
| #include <ctype.h> |
| #include "expression.h" |
| #include "value.h" |
| #include "parser-defs.h" |
| #include "language.h" |
| #include "c-lang.h" |
| #include "go-lang.h" |
| #include "bfd.h" /* Required by objfiles.h. */ |
| #include "symfile.h" /* Required by objfiles.h. */ |
| #include "objfiles.h" /* For have_full_symbols and have_partial_symbols */ |
| #include "charset.h" |
| #include "block.h" |
| #include "expop.h" |
| |
| #define parse_type(ps) builtin_type (ps->gdbarch ()) |
| |
| /* Remap normal yacc parser interface names (yyparse, yylex, yyerror, |
| etc). */ |
| #define GDB_YY_REMAP_PREFIX go_ |
| #include "yy-remap.h" |
| |
| /* The state of the parser, used internally when we are parsing the |
| expression. */ |
| |
| static struct parser_state *pstate = NULL; |
| |
| int yyparse (void); |
| |
| static int yylex (void); |
| |
| static void yyerror (const char *); |
| |
| %} |
| |
| /* Although the yacc "value" of an expression is not used, |
| since the result is stored in the structure being created, |
| other node types do have values. */ |
| |
| %union |
| { |
| LONGEST lval; |
| struct { |
| LONGEST val; |
| struct type *type; |
| } typed_val_int; |
| struct { |
| gdb_byte val[16]; |
| struct type *type; |
| } typed_val_float; |
| struct stoken sval; |
| struct symtoken ssym; |
| struct type *tval; |
| struct typed_stoken tsval; |
| struct ttype tsym; |
| int voidval; |
| enum exp_opcode opcode; |
| struct internalvar *ivar; |
| struct stoken_vector svec; |
| } |
| |
| %{ |
| /* YYSTYPE gets defined by %union. */ |
| static int parse_number (struct parser_state *, |
| const char *, int, int, YYSTYPE *); |
| |
| using namespace expr; |
| %} |
| |
| %type <voidval> exp exp1 type_exp start variable lcurly |
| %type <lval> rcurly |
| %type <tval> type |
| |
| %token <typed_val_int> INT |
| %token <typed_val_float> FLOAT |
| |
| /* Both NAME and TYPENAME tokens represent symbols in the input, |
| and both convey their data as strings. |
| But a TYPENAME is a string that happens to be defined as a type |
| or builtin type name (such as int or char) |
| and a NAME is any other symbol. |
| Contexts where this distinction is not important can use the |
| nonterminal "name", which matches either NAME or TYPENAME. */ |
| |
| %token <tsval> RAW_STRING |
| %token <tsval> STRING |
| %token <tsval> CHAR |
| %token <ssym> NAME |
| %token <tsym> TYPENAME /* Not TYPE_NAME cus already taken. */ |
| %token <voidval> COMPLETE |
| /*%type <sval> name*/ |
| %type <svec> string_exp |
| %type <ssym> name_not_typename |
| |
| /* A NAME_OR_INT is a symbol which is not known in the symbol table, |
| but which would parse as a valid number in the current input radix. |
| E.g. "c" when input_radix==16. Depending on the parse, it will be |
| turned into a name or into a number. */ |
| %token <ssym> NAME_OR_INT |
| |
| %token <lval> TRUE_KEYWORD FALSE_KEYWORD |
| %token STRUCT_KEYWORD INTERFACE_KEYWORD TYPE_KEYWORD CHAN_KEYWORD |
| %token SIZEOF_KEYWORD |
| %token LEN_KEYWORD CAP_KEYWORD |
| %token NEW_KEYWORD |
| %token IOTA_KEYWORD NIL_KEYWORD |
| %token CONST_KEYWORD |
| %token DOTDOTDOT |
| %token ENTRY |
| %token ERROR |
| |
| /* Special type cases. */ |
| %token BYTE_KEYWORD /* An alias of uint8. */ |
| |
| %token <sval> DOLLAR_VARIABLE |
| |
| %token <opcode> ASSIGN_MODIFY |
| |
| %left ',' |
| %left ABOVE_COMMA |
| %right '=' ASSIGN_MODIFY |
| %right '?' |
| %left OROR |
| %left ANDAND |
| %left '|' |
| %left '^' |
| %left '&' |
| %left ANDNOT |
| %left EQUAL NOTEQUAL |
| %left '<' '>' LEQ GEQ |
| %left LSH RSH |
| %left '@' |
| %left '+' '-' |
| %left '*' '/' '%' |
| %right UNARY INCREMENT DECREMENT |
| %right LEFT_ARROW '.' '[' '(' |
| |
| |
| %% |
| |
| start : exp1 |
| | type_exp |
| ; |
| |
| type_exp: type |
| { pstate->push_new<type_operation> ($1); } |
| ; |
| |
| /* Expressions, including the comma operator. */ |
| exp1 : exp |
| | exp1 ',' exp |
| { pstate->wrap2<comma_operation> (); } |
| ; |
| |
| /* Expressions, not including the comma operator. */ |
| exp : '*' exp %prec UNARY |
| { pstate->wrap<unop_ind_operation> (); } |
| ; |
| |
| exp : '&' exp %prec UNARY |
| { pstate->wrap<unop_addr_operation> (); } |
| ; |
| |
| exp : '-' exp %prec UNARY |
| { pstate->wrap<unary_neg_operation> (); } |
| ; |
| |
| exp : '+' exp %prec UNARY |
| { pstate->wrap<unary_plus_operation> (); } |
| ; |
| |
| exp : '!' exp %prec UNARY |
| { pstate->wrap<unary_logical_not_operation> (); } |
| ; |
| |
| exp : '^' exp %prec UNARY |
| { pstate->wrap<unary_complement_operation> (); } |
| ; |
| |
| exp : exp INCREMENT %prec UNARY |
| { pstate->wrap<postinc_operation> (); } |
| ; |
| |
| exp : exp DECREMENT %prec UNARY |
| { pstate->wrap<postdec_operation> (); } |
| ; |
| |
| /* foo->bar is not in Go. May want as a gdb extension. Later. */ |
| |
| exp : exp '.' name_not_typename |
| { |
| pstate->push_new<structop_operation> |
| (pstate->pop (), copy_name ($3.stoken)); |
| } |
| ; |
| |
| exp : exp '.' name_not_typename COMPLETE |
| { |
| structop_base_operation *op |
| = new structop_operation (pstate->pop (), |
| copy_name ($3.stoken)); |
| pstate->mark_struct_expression (op); |
| pstate->push (operation_up (op)); |
| } |
| ; |
| |
| exp : exp '.' COMPLETE |
| { |
| structop_base_operation *op |
| = new structop_operation (pstate->pop (), ""); |
| pstate->mark_struct_expression (op); |
| pstate->push (operation_up (op)); |
| } |
| ; |
| |
| exp : exp '[' exp1 ']' |
| { pstate->wrap2<subscript_operation> (); } |
| ; |
| |
| exp : exp '(' |
| /* This is to save the value of arglist_len |
| being accumulated by an outer function call. */ |
| { pstate->start_arglist (); } |
| arglist ')' %prec LEFT_ARROW |
| { |
| std::vector<operation_up> args |
| = pstate->pop_vector (pstate->end_arglist ()); |
| pstate->push_new<funcall_operation> |
| (pstate->pop (), std::move (args)); |
| } |
| ; |
| |
| lcurly : '{' |
| { pstate->start_arglist (); } |
| ; |
| |
| arglist : |
| ; |
| |
| arglist : exp |
| { pstate->arglist_len = 1; } |
| ; |
| |
| arglist : arglist ',' exp %prec ABOVE_COMMA |
| { pstate->arglist_len++; } |
| ; |
| |
| rcurly : '}' |
| { $$ = pstate->end_arglist () - 1; } |
| ; |
| |
| exp : lcurly type rcurly exp %prec UNARY |
| { |
| pstate->push_new<unop_memval_operation> |
| (pstate->pop (), $2); |
| } |
| ; |
| |
| exp : type '(' exp ')' %prec UNARY |
| { |
| pstate->push_new<unop_cast_operation> |
| (pstate->pop (), $1); |
| } |
| ; |
| |
| exp : '(' exp1 ')' |
| { } |
| ; |
| |
| /* Binary operators in order of decreasing precedence. */ |
| |
| exp : exp '@' exp |
| { pstate->wrap2<repeat_operation> (); } |
| ; |
| |
| exp : exp '*' exp |
| { pstate->wrap2<mul_operation> (); } |
| ; |
| |
| exp : exp '/' exp |
| { pstate->wrap2<div_operation> (); } |
| ; |
| |
| exp : exp '%' exp |
| { pstate->wrap2<rem_operation> (); } |
| ; |
| |
| exp : exp '+' exp |
| { pstate->wrap2<add_operation> (); } |
| ; |
| |
| exp : exp '-' exp |
| { pstate->wrap2<sub_operation> (); } |
| ; |
| |
| exp : exp LSH exp |
| { pstate->wrap2<lsh_operation> (); } |
| ; |
| |
| exp : exp RSH exp |
| { pstate->wrap2<rsh_operation> (); } |
| ; |
| |
| exp : exp EQUAL exp |
| { pstate->wrap2<equal_operation> (); } |
| ; |
| |
| exp : exp NOTEQUAL exp |
| { pstate->wrap2<notequal_operation> (); } |
| ; |
| |
| exp : exp LEQ exp |
| { pstate->wrap2<leq_operation> (); } |
| ; |
| |
| exp : exp GEQ exp |
| { pstate->wrap2<geq_operation> (); } |
| ; |
| |
| exp : exp '<' exp |
| { pstate->wrap2<less_operation> (); } |
| ; |
| |
| exp : exp '>' exp |
| { pstate->wrap2<gtr_operation> (); } |
| ; |
| |
| exp : exp '&' exp |
| { pstate->wrap2<bitwise_and_operation> (); } |
| ; |
| |
| exp : exp '^' exp |
| { pstate->wrap2<bitwise_xor_operation> (); } |
| ; |
| |
| exp : exp '|' exp |
| { pstate->wrap2<bitwise_ior_operation> (); } |
| ; |
| |
| exp : exp ANDAND exp |
| { pstate->wrap2<logical_and_operation> (); } |
| ; |
| |
| exp : exp OROR exp |
| { pstate->wrap2<logical_or_operation> (); } |
| ; |
| |
| exp : exp '?' exp ':' exp %prec '?' |
| { |
| operation_up last = pstate->pop (); |
| operation_up mid = pstate->pop (); |
| operation_up first = pstate->pop (); |
| pstate->push_new<ternop_cond_operation> |
| (std::move (first), std::move (mid), |
| std::move (last)); |
| } |
| ; |
| |
| exp : exp '=' exp |
| { pstate->wrap2<assign_operation> (); } |
| ; |
| |
| exp : exp ASSIGN_MODIFY exp |
| { |
| operation_up rhs = pstate->pop (); |
| operation_up lhs = pstate->pop (); |
| pstate->push_new<assign_modify_operation> |
| ($2, std::move (lhs), std::move (rhs)); |
| } |
| ; |
| |
| exp : INT |
| { |
| pstate->push_new<long_const_operation> |
| ($1.type, $1.val); |
| } |
| ; |
| |
| exp : CHAR |
| { |
| struct stoken_vector vec; |
| vec.len = 1; |
| vec.tokens = &$1; |
| pstate->push_c_string ($1.type, &vec); |
| } |
| ; |
| |
| exp : NAME_OR_INT |
| { YYSTYPE val; |
| parse_number (pstate, $1.stoken.ptr, |
| $1.stoken.length, 0, &val); |
| pstate->push_new<long_const_operation> |
| (val.typed_val_int.type, |
| val.typed_val_int.val); |
| } |
| ; |
| |
| |
| exp : FLOAT |
| { |
| float_data data; |
| std::copy (std::begin ($1.val), std::end ($1.val), |
| std::begin (data)); |
| pstate->push_new<float_const_operation> ($1.type, data); |
| } |
| ; |
| |
| exp : variable |
| ; |
| |
| exp : DOLLAR_VARIABLE |
| { |
| pstate->push_dollar ($1); |
| } |
| ; |
| |
| exp : SIZEOF_KEYWORD '(' type ')' %prec UNARY |
| { |
| /* TODO(dje): Go objects in structs. */ |
| /* TODO(dje): What's the right type here? */ |
| struct type *size_type |
| = parse_type (pstate)->builtin_unsigned_int; |
| $3 = check_typedef ($3); |
| pstate->push_new<long_const_operation> |
| (size_type, (LONGEST) TYPE_LENGTH ($3)); |
| } |
| ; |
| |
| exp : SIZEOF_KEYWORD '(' exp ')' %prec UNARY |
| { |
| /* TODO(dje): Go objects in structs. */ |
| pstate->wrap<unop_sizeof_operation> (); |
| } |
| |
| string_exp: |
| STRING |
| { |
| /* We copy the string here, and not in the |
| lexer, to guarantee that we do not leak a |
| string. */ |
| /* Note that we NUL-terminate here, but just |
| for convenience. */ |
| struct typed_stoken *vec = XNEW (struct typed_stoken); |
| $$.len = 1; |
| $$.tokens = vec; |
| |
| vec->type = $1.type; |
| vec->length = $1.length; |
| vec->ptr = (char *) malloc ($1.length + 1); |
| memcpy (vec->ptr, $1.ptr, $1.length + 1); |
| } |
| |
| | string_exp '+' STRING |
| { |
| /* Note that we NUL-terminate here, but just |
| for convenience. */ |
| char *p; |
| ++$$.len; |
| $$.tokens = XRESIZEVEC (struct typed_stoken, |
| $$.tokens, $$.len); |
| |
| p = (char *) malloc ($3.length + 1); |
| memcpy (p, $3.ptr, $3.length + 1); |
| |
| $$.tokens[$$.len - 1].type = $3.type; |
| $$.tokens[$$.len - 1].length = $3.length; |
| $$.tokens[$$.len - 1].ptr = p; |
| } |
| ; |
| |
| exp : string_exp %prec ABOVE_COMMA |
| { |
| int i; |
| |
| /* Always utf8. */ |
| pstate->push_c_string (0, &$1); |
| for (i = 0; i < $1.len; ++i) |
| free ($1.tokens[i].ptr); |
| free ($1.tokens); |
| } |
| ; |
| |
| exp : TRUE_KEYWORD |
| { pstate->push_new<bool_operation> ($1); } |
| ; |
| |
| exp : FALSE_KEYWORD |
| { pstate->push_new<bool_operation> ($1); } |
| ; |
| |
| variable: name_not_typename ENTRY |
| { struct symbol *sym = $1.sym.symbol; |
| |
| if (sym == NULL |
| || !SYMBOL_IS_ARGUMENT (sym) |
| || !symbol_read_needs_frame (sym)) |
| error (_("@entry can be used only for function " |
| "parameters, not for \"%s\""), |
| copy_name ($1.stoken).c_str ()); |
| |
| pstate->push_new<var_entry_value_operation> (sym); |
| } |
| ; |
| |
| variable: name_not_typename |
| { struct block_symbol sym = $1.sym; |
| |
| if (sym.symbol) |
| { |
| if (symbol_read_needs_frame (sym.symbol)) |
| pstate->block_tracker->update (sym); |
| |
| pstate->push_new<var_value_operation> (sym); |
| } |
| else if ($1.is_a_field_of_this) |
| { |
| /* TODO(dje): Can we get here? |
| E.g., via a mix of c++ and go? */ |
| gdb_assert_not_reached ("go with `this' field"); |
| } |
| else |
| { |
| struct bound_minimal_symbol msymbol; |
| std::string arg = copy_name ($1.stoken); |
| |
| msymbol = |
| lookup_bound_minimal_symbol (arg.c_str ()); |
| if (msymbol.minsym != NULL) |
| pstate->push_new<var_msym_value_operation> |
| (msymbol); |
| else if (!have_full_symbols () |
| && !have_partial_symbols ()) |
| error (_("No symbol table is loaded. " |
| "Use the \"file\" command.")); |
| else |
| error (_("No symbol \"%s\" in current context."), |
| arg.c_str ()); |
| } |
| } |
| ; |
| |
| /* TODO |
| method_exp: PACKAGENAME '.' name '.' name |
| { |
| } |
| ; |
| */ |
| |
| type /* Implements (approximately): [*] type-specifier */ |
| : '*' type |
| { $$ = lookup_pointer_type ($2); } |
| | TYPENAME |
| { $$ = $1.type; } |
| /* |
| | STRUCT_KEYWORD name |
| { $$ = lookup_struct (copy_name ($2), |
| expression_context_block); } |
| */ |
| | BYTE_KEYWORD |
| { $$ = builtin_go_type (pstate->gdbarch ()) |
| ->builtin_uint8; } |
| ; |
| |
| /* TODO |
| name : NAME { $$ = $1.stoken; } |
| | TYPENAME { $$ = $1.stoken; } |
| | NAME_OR_INT { $$ = $1.stoken; } |
| ; |
| */ |
| |
| name_not_typename |
| : NAME |
| /* These would be useful if name_not_typename was useful, but it is just |
| a fake for "variable", so these cause reduce/reduce conflicts because |
| the parser can't tell whether NAME_OR_INT is a name_not_typename (=variable, |
| =exp) or just an exp. If name_not_typename was ever used in an lvalue |
| context where only a name could occur, this might be useful. |
| | NAME_OR_INT |
| */ |
| ; |
| |
| %% |
| |
| /* Take care of parsing a number (anything that starts with a digit). |
| Set yylval and return the token type; update lexptr. |
| LEN is the number of characters in it. */ |
| |
| /* FIXME: Needs some error checking for the float case. */ |
| /* FIXME(dje): IWBN to use c-exp.y's parse_number if we could. |
| That will require moving the guts into a function that we both call |
| as our YYSTYPE is different than c-exp.y's */ |
| |
| static int |
| parse_number (struct parser_state *par_state, |
| const char *p, int len, int parsed_float, YYSTYPE *putithere) |
| { |
| /* FIXME: Shouldn't these be unsigned? We don't deal with negative values |
| here, and we do kind of silly things like cast to unsigned. */ |
| LONGEST n = 0; |
| LONGEST prevn = 0; |
| ULONGEST un; |
| |
| int i = 0; |
| int c; |
| int base = input_radix; |
| int unsigned_p = 0; |
| |
| /* Number of "L" suffixes encountered. */ |
| int long_p = 0; |
| |
| /* We have found a "L" or "U" suffix. */ |
| int found_suffix = 0; |
| |
| ULONGEST high_bit; |
| struct type *signed_type; |
| struct type *unsigned_type; |
| |
| if (parsed_float) |
| { |
| const struct builtin_go_type *builtin_go_types |
| = builtin_go_type (par_state->gdbarch ()); |
| |
| /* Handle suffixes: 'f' for float32, 'l' for long double. |
| FIXME: This appears to be an extension -- do we want this? */ |
| if (len >= 1 && tolower (p[len - 1]) == 'f') |
| { |
| putithere->typed_val_float.type |
| = builtin_go_types->builtin_float32; |
| len--; |
| } |
| else if (len >= 1 && tolower (p[len - 1]) == 'l') |
| { |
| putithere->typed_val_float.type |
| = parse_type (par_state)->builtin_long_double; |
| len--; |
| } |
| /* Default type for floating-point literals is float64. */ |
| else |
| { |
| putithere->typed_val_float.type |
| = builtin_go_types->builtin_float64; |
| } |
| |
| if (!parse_float (p, len, |
| putithere->typed_val_float.type, |
| putithere->typed_val_float.val)) |
| return ERROR; |
| return FLOAT; |
| } |
| |
| /* Handle base-switching prefixes 0x, 0t, 0d, 0. */ |
| if (p[0] == '0') |
| switch (p[1]) |
| { |
| case 'x': |
| case 'X': |
| if (len >= 3) |
| { |
| p += 2; |
| base = 16; |
| len -= 2; |
| } |
| break; |
| |
| case 'b': |
| case 'B': |
| if (len >= 3) |
| { |
| p += 2; |
| base = 2; |
| len -= 2; |
| } |
| break; |
| |
| case 't': |
| case 'T': |
| case 'd': |
| case 'D': |
| if (len >= 3) |
| { |
| p += 2; |
| base = 10; |
| len -= 2; |
| } |
| break; |
| |
| default: |
| base = 8; |
| break; |
| } |
| |
| while (len-- > 0) |
| { |
| c = *p++; |
| if (c >= 'A' && c <= 'Z') |
| c += 'a' - 'A'; |
| if (c != 'l' && c != 'u') |
| n *= base; |
| if (c >= '0' && c <= '9') |
| { |
| if (found_suffix) |
| return ERROR; |
| n += i = c - '0'; |
| } |
| else |
| { |
| if (base > 10 && c >= 'a' && c <= 'f') |
| { |
| if (found_suffix) |
| return ERROR; |
| n += i = c - 'a' + 10; |
| } |
| else if (c == 'l') |
| { |
| ++long_p; |
| found_suffix = 1; |
| } |
| else if (c == 'u') |
| { |
| unsigned_p = 1; |
| found_suffix = 1; |
| } |
| else |
| return ERROR; /* Char not a digit */ |
| } |
| if (i >= base) |
| return ERROR; /* Invalid digit in this base. */ |
| |
| /* Portably test for overflow (only works for nonzero values, so make |
| a second check for zero). FIXME: Can't we just make n and prevn |
| unsigned and avoid this? */ |
| if (c != 'l' && c != 'u' && (prevn >= n) && n != 0) |
| unsigned_p = 1; /* Try something unsigned. */ |
| |
| /* Portably test for unsigned overflow. |
| FIXME: This check is wrong; for example it doesn't find overflow |
| on 0x123456789 when LONGEST is 32 bits. */ |
| if (c != 'l' && c != 'u' && n != 0) |
| { |
| if ((unsigned_p && (ULONGEST) prevn >= (ULONGEST) n)) |
| error (_("Numeric constant too large.")); |
| } |
| prevn = n; |
| } |
| |
| /* An integer constant is an int, a long, or a long long. An L |
| suffix forces it to be long; an LL suffix forces it to be long |
| long. If not forced to a larger size, it gets the first type of |
| the above that it fits in. To figure out whether it fits, we |
| shift it right and see whether anything remains. Note that we |
| can't shift sizeof (LONGEST) * HOST_CHAR_BIT bits or more in one |
| operation, because many compilers will warn about such a shift |
| (which always produces a zero result). Sometimes gdbarch_int_bit |
| or gdbarch_long_bit will be that big, sometimes not. To deal with |
| the case where it is we just always shift the value more than |
| once, with fewer bits each time. */ |
| |
| un = (ULONGEST)n >> 2; |
| if (long_p == 0 |
| && (un >> (gdbarch_int_bit (par_state->gdbarch ()) - 2)) == 0) |
| { |
| high_bit |
| = ((ULONGEST)1) << (gdbarch_int_bit (par_state->gdbarch ()) - 1); |
| |
| /* A large decimal (not hex or octal) constant (between INT_MAX |
| and UINT_MAX) is a long or unsigned long, according to ANSI, |
| never an unsigned int, but this code treats it as unsigned |
| int. This probably should be fixed. GCC gives a warning on |
| such constants. */ |
| |
| unsigned_type = parse_type (par_state)->builtin_unsigned_int; |
| signed_type = parse_type (par_state)->builtin_int; |
| } |
| else if (long_p <= 1 |
| && (un >> (gdbarch_long_bit (par_state->gdbarch ()) - 2)) == 0) |
| { |
| high_bit |
| = ((ULONGEST)1) << (gdbarch_long_bit (par_state->gdbarch ()) - 1); |
| unsigned_type = parse_type (par_state)->builtin_unsigned_long; |
| signed_type = parse_type (par_state)->builtin_long; |
| } |
| else |
| { |
| int shift; |
| if (sizeof (ULONGEST) * HOST_CHAR_BIT |
| < gdbarch_long_long_bit (par_state->gdbarch ())) |
| /* A long long does not fit in a LONGEST. */ |
| shift = (sizeof (ULONGEST) * HOST_CHAR_BIT - 1); |
| else |
| shift = (gdbarch_long_long_bit (par_state->gdbarch ()) - 1); |
| high_bit = (ULONGEST) 1 << shift; |
| unsigned_type = parse_type (par_state)->builtin_unsigned_long_long; |
| signed_type = parse_type (par_state)->builtin_long_long; |
| } |
| |
| putithere->typed_val_int.val = n; |
| |
| /* If the high bit of the worked out type is set then this number |
| has to be unsigned. */ |
| |
| if (unsigned_p || (n & high_bit)) |
| { |
| putithere->typed_val_int.type = unsigned_type; |
| } |
| else |
| { |
| putithere->typed_val_int.type = signed_type; |
| } |
| |
| return INT; |
| } |
| |
| /* Temporary obstack used for holding strings. */ |
| static struct obstack tempbuf; |
| static int tempbuf_init; |
| |
| /* Parse a string or character literal from TOKPTR. The string or |
| character may be wide or unicode. *OUTPTR is set to just after the |
| end of the literal in the input string. The resulting token is |
| stored in VALUE. This returns a token value, either STRING or |
| CHAR, depending on what was parsed. *HOST_CHARS is set to the |
| number of host characters in the literal. */ |
| |
| static int |
| parse_string_or_char (const char *tokptr, const char **outptr, |
| struct typed_stoken *value, int *host_chars) |
| { |
| int quote; |
| |
| /* Build the gdb internal form of the input string in tempbuf. Note |
| that the buffer is null byte terminated *only* for the |
| convenience of debugging gdb itself and printing the buffer |
| contents when the buffer contains no embedded nulls. Gdb does |
| not depend upon the buffer being null byte terminated, it uses |
| the length string instead. This allows gdb to handle C strings |
| (as well as strings in other languages) with embedded null |
| bytes */ |
| |
| if (!tempbuf_init) |
| tempbuf_init = 1; |
| else |
| obstack_free (&tempbuf, NULL); |
| obstack_init (&tempbuf); |
| |
| /* Skip the quote. */ |
| quote = *tokptr; |
| ++tokptr; |
| |
| *host_chars = 0; |
| |
| while (*tokptr) |
| { |
| char c = *tokptr; |
| if (c == '\\') |
| { |
| ++tokptr; |
| *host_chars += c_parse_escape (&tokptr, &tempbuf); |
| } |
| else if (c == quote) |
| break; |
| else |
| { |
| obstack_1grow (&tempbuf, c); |
| ++tokptr; |
| /* FIXME: this does the wrong thing with multi-byte host |
| characters. We could use mbrlen here, but that would |
| make "set host-charset" a bit less useful. */ |
| ++*host_chars; |
| } |
| } |
| |
| if (*tokptr != quote) |
| { |
| if (quote == '"') |
| error (_("Unterminated string in expression.")); |
| else |
| error (_("Unmatched single quote.")); |
| } |
| ++tokptr; |
| |
| value->type = (int) C_STRING | (quote == '\'' ? C_CHAR : 0); /*FIXME*/ |
| value->ptr = (char *) obstack_base (&tempbuf); |
| value->length = obstack_object_size (&tempbuf); |
| |
| *outptr = tokptr; |
| |
| return quote == '\'' ? CHAR : STRING; |
| } |
| |
| struct token |
| { |
| const char *oper; |
| int token; |
| enum exp_opcode opcode; |
| }; |
| |
| static const struct token tokentab3[] = |
| { |
| {">>=", ASSIGN_MODIFY, BINOP_RSH}, |
| {"<<=", ASSIGN_MODIFY, BINOP_LSH}, |
| /*{"&^=", ASSIGN_MODIFY, BINOP_BITWISE_ANDNOT}, TODO */ |
| {"...", DOTDOTDOT, OP_NULL}, |
| }; |
| |
| static const struct token tokentab2[] = |
| { |
| {"+=", ASSIGN_MODIFY, BINOP_ADD}, |
| {"-=", ASSIGN_MODIFY, BINOP_SUB}, |
| {"*=", ASSIGN_MODIFY, BINOP_MUL}, |
| {"/=", ASSIGN_MODIFY, BINOP_DIV}, |
| {"%=", ASSIGN_MODIFY, BINOP_REM}, |
| {"|=", ASSIGN_MODIFY, BINOP_BITWISE_IOR}, |
| {"&=", ASSIGN_MODIFY, BINOP_BITWISE_AND}, |
| {"^=", ASSIGN_MODIFY, BINOP_BITWISE_XOR}, |
| {"++", INCREMENT, OP_NULL}, |
| {"--", DECREMENT, OP_NULL}, |
| /*{"->", RIGHT_ARROW, OP_NULL}, Doesn't exist in Go. */ |
| {"<-", LEFT_ARROW, OP_NULL}, |
| {"&&", ANDAND, OP_NULL}, |
| {"||", OROR, OP_NULL}, |
| {"<<", LSH, OP_NULL}, |
| {">>", RSH, OP_NULL}, |
| {"==", EQUAL, OP_NULL}, |
| {"!=", NOTEQUAL, OP_NULL}, |
| {"<=", LEQ, OP_NULL}, |
| {">=", GEQ, OP_NULL}, |
| /*{"&^", ANDNOT, OP_NULL}, TODO */ |
| }; |
| |
| /* Identifier-like tokens. */ |
| static const struct token ident_tokens[] = |
| { |
| {"true", TRUE_KEYWORD, OP_NULL}, |
| {"false", FALSE_KEYWORD, OP_NULL}, |
| {"nil", NIL_KEYWORD, OP_NULL}, |
| {"const", CONST_KEYWORD, OP_NULL}, |
| {"struct", STRUCT_KEYWORD, OP_NULL}, |
| {"type", TYPE_KEYWORD, OP_NULL}, |
| {"interface", INTERFACE_KEYWORD, OP_NULL}, |
| {"chan", CHAN_KEYWORD, OP_NULL}, |
| {"byte", BYTE_KEYWORD, OP_NULL}, /* An alias of uint8. */ |
| {"len", LEN_KEYWORD, OP_NULL}, |
| {"cap", CAP_KEYWORD, OP_NULL}, |
| {"new", NEW_KEYWORD, OP_NULL}, |
| {"iota", IOTA_KEYWORD, OP_NULL}, |
| }; |
| |
| /* This is set if a NAME token appeared at the very end of the input |
| string, with no whitespace separating the name from the EOF. This |
| is used only when parsing to do field name completion. */ |
| static int saw_name_at_eof; |
| |
| /* This is set if the previously-returned token was a structure |
| operator -- either '.' or ARROW. This is used only when parsing to |
| do field name completion. */ |
| static int last_was_structop; |
| |
| /* Depth of parentheses. */ |
| static int paren_depth; |
| |
| /* Read one token, getting characters through lexptr. */ |
| |
| static int |
| lex_one_token (struct parser_state *par_state) |
| { |
| int c; |
| int namelen; |
| unsigned int i; |
| const char *tokstart; |
| int saw_structop = last_was_structop; |
| |
| last_was_structop = 0; |
| |
| retry: |
| |
| par_state->prev_lexptr = par_state->lexptr; |
| |
| tokstart = par_state->lexptr; |
| /* See if it is a special token of length 3. */ |
| for (i = 0; i < sizeof (tokentab3) / sizeof (tokentab3[0]); i++) |
| if (strncmp (tokstart, tokentab3[i].oper, 3) == 0) |
| { |
| par_state->lexptr += 3; |
| yylval.opcode = tokentab3[i].opcode; |
| return tokentab3[i].token; |
| } |
| |
| /* See if it is a special token of length 2. */ |
| for (i = 0; i < sizeof (tokentab2) / sizeof (tokentab2[0]); i++) |
| if (strncmp (tokstart, tokentab2[i].oper, 2) == 0) |
| { |
| par_state->lexptr += 2; |
| yylval.opcode = tokentab2[i].opcode; |
| /* NOTE: -> doesn't exist in Go, so we don't need to watch for |
| setting last_was_structop here. */ |
| return tokentab2[i].token; |
| } |
| |
| switch (c = *tokstart) |
| { |
| case 0: |
| if (saw_name_at_eof) |
| { |
| saw_name_at_eof = 0; |
| return COMPLETE; |
| } |
| else if (saw_structop) |
| return COMPLETE; |
| else |
| return 0; |
| |
| case ' ': |
| case '\t': |
| case '\n': |
| par_state->lexptr++; |
| goto retry; |
| |
| case '[': |
| case '(': |
| paren_depth++; |
| par_state->lexptr++; |
| return c; |
| |
| case ']': |
| case ')': |
| if (paren_depth == 0) |
| return 0; |
| paren_depth--; |
| par_state->lexptr++; |
| return c; |
| |
| case ',': |
| if (pstate->comma_terminates |
| && paren_depth == 0) |
| return 0; |
| par_state->lexptr++; |
| return c; |
| |
| case '.': |
| /* Might be a floating point number. */ |
| if (par_state->lexptr[1] < '0' || par_state->lexptr[1] > '9') |
| { |
| if (pstate->parse_completion) |
| last_was_structop = 1; |
| goto symbol; /* Nope, must be a symbol. */ |
| } |
| /* FALL THRU. */ |
| |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| /* It's a number. */ |
| int got_dot = 0, got_e = 0, toktype; |
| const char *p = tokstart; |
| int hex = input_radix > 10; |
| |
| if (c == '0' && (p[1] == 'x' || p[1] == 'X')) |
| { |
| p += 2; |
| hex = 1; |
| } |
| |
| for (;; ++p) |
| { |
| /* This test includes !hex because 'e' is a valid hex digit |
| and thus does not indicate a floating point number when |
| the radix is hex. */ |
| if (!hex && !got_e && (*p == 'e' || *p == 'E')) |
| got_dot = got_e = 1; |
| /* This test does not include !hex, because a '.' always indicates |
| a decimal floating point number regardless of the radix. */ |
| else if (!got_dot && *p == '.') |
| got_dot = 1; |
| else if (got_e && (p[-1] == 'e' || p[-1] == 'E') |
| && (*p == '-' || *p == '+')) |
| /* This is the sign of the exponent, not the end of the |
| number. */ |
| continue; |
| /* We will take any letters or digits. parse_number will |
| complain if past the radix, or if L or U are not final. */ |
| else if ((*p < '0' || *p > '9') |
| && ((*p < 'a' || *p > 'z') |
| && (*p < 'A' || *p > 'Z'))) |
| break; |
| } |
| toktype = parse_number (par_state, tokstart, p - tokstart, |
| got_dot|got_e, &yylval); |
| if (toktype == ERROR) |
| { |
| char *err_copy = (char *) alloca (p - tokstart + 1); |
| |
| memcpy (err_copy, tokstart, p - tokstart); |
| err_copy[p - tokstart] = 0; |
| error (_("Invalid number \"%s\"."), err_copy); |
| } |
| par_state->lexptr = p; |
| return toktype; |
| } |
| |
| case '@': |
| { |
| const char *p = &tokstart[1]; |
| size_t len = strlen ("entry"); |
| |
| while (isspace (*p)) |
| p++; |
| if (strncmp (p, "entry", len) == 0 && !isalnum (p[len]) |
| && p[len] != '_') |
| { |
| par_state->lexptr = &p[len]; |
| return ENTRY; |
| } |
| } |
| /* FALLTHRU */ |
| case '+': |
| case '-': |
| case '*': |
| case '/': |
| case '%': |
| case '|': |
| case '&': |
| case '^': |
| case '~': |
| case '!': |
| case '<': |
| case '>': |
| case '?': |
| case ':': |
| case '=': |
| case '{': |
| case '}': |
| symbol: |
| par_state->lexptr++; |
| return c; |
| |
| case '\'': |
| case '"': |
| case '`': |
| { |
| int host_len; |
| int result = parse_string_or_char (tokstart, &par_state->lexptr, |
| &yylval.tsval, &host_len); |
| if (result == CHAR) |
| { |
| if (host_len == 0) |
| error (_("Empty character constant.")); |
| else if (host_len > 2 && c == '\'') |
| { |
| ++tokstart; |
| namelen = par_state->lexptr - tokstart - 1; |
| goto tryname; |
| } |
| else if (host_len > 1) |
| error (_("Invalid character constant.")); |
| } |
| return result; |
| } |
| } |
| |
| if (!(c == '_' || c == '$' |
| || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) |
| /* We must have come across a bad character (e.g. ';'). */ |
| error (_("Invalid character '%c' in expression."), c); |
| |
| /* It's a name. See how long it is. */ |
| namelen = 0; |
| for (c = tokstart[namelen]; |
| (c == '_' || c == '$' || (c >= '0' && c <= '9') |
| || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));) |
| { |
| c = tokstart[++namelen]; |
| } |
| |
| /* The token "if" terminates the expression and is NOT removed from |
| the input stream. It doesn't count if it appears in the |
| expansion of a macro. */ |
| if (namelen == 2 |
| && tokstart[0] == 'i' |
| && tokstart[1] == 'f') |
| { |
| return 0; |
| } |
| |
| /* For the same reason (breakpoint conditions), "thread N" |
| terminates the expression. "thread" could be an identifier, but |
| an identifier is never followed by a number without intervening |
| punctuation. |
| Handle abbreviations of these, similarly to |
| breakpoint.c:find_condition_and_thread. |
| TODO: Watch for "goroutine" here? */ |
| if (namelen >= 1 |
| && strncmp (tokstart, "thread", namelen) == 0 |
| && (tokstart[namelen] == ' ' || tokstart[namelen] == '\t')) |
| { |
| const char *p = tokstart + namelen + 1; |
| |
| while (*p == ' ' || *p == '\t') |
| p++; |
| if (*p >= '0' && *p <= '9') |
| return 0; |
| } |
| |
| par_state->lexptr += namelen; |
| |
| tryname: |
| |
| yylval.sval.ptr = tokstart; |
| yylval.sval.length = namelen; |
| |
| /* Catch specific keywords. */ |
| std::string copy = copy_name (yylval.sval); |
| for (i = 0; i < sizeof (ident_tokens) / sizeof (ident_tokens[0]); i++) |
| if (copy == ident_tokens[i].oper) |
| { |
| /* It is ok to always set this, even though we don't always |
| strictly need to. */ |
| yylval.opcode = ident_tokens[i].opcode; |
| return ident_tokens[i].token; |
| } |
| |
| if (*tokstart == '$') |
| return DOLLAR_VARIABLE; |
| |
| if (pstate->parse_completion && *par_state->lexptr == '\0') |
| saw_name_at_eof = 1; |
| return NAME; |
| } |
| |
| /* An object of this type is pushed on a FIFO by the "outer" lexer. */ |
| struct token_and_value |
| { |
| int token; |
| YYSTYPE value; |
| }; |
| |
| /* A FIFO of tokens that have been read but not yet returned to the |
| parser. */ |
| static std::vector<token_and_value> token_fifo; |
| |
| /* Non-zero if the lexer should return tokens from the FIFO. */ |
| static int popping; |
| |
| /* Temporary storage for yylex; this holds symbol names as they are |
| built up. */ |
| static auto_obstack name_obstack; |
| |
| /* Build "package.name" in name_obstack. |
| For convenience of the caller, the name is NUL-terminated, |
| but the NUL is not included in the recorded length. */ |
| |
| static struct stoken |
| build_packaged_name (const char *package, int package_len, |
| const char *name, int name_len) |
| { |
| struct stoken result; |
| |
| name_obstack.clear (); |
| obstack_grow (&name_obstack, package, package_len); |
| obstack_grow_str (&name_obstack, "."); |
| obstack_grow (&name_obstack, name, name_len); |
| obstack_grow (&name_obstack, "", 1); |
| result.ptr = (char *) obstack_base (&name_obstack); |
| result.length = obstack_object_size (&name_obstack) - 1; |
| |
| return result; |
| } |
| |
| /* Return non-zero if NAME is a package name. |
| BLOCK is the scope in which to interpret NAME; this can be NULL |
| to mean the global scope. */ |
| |
| static int |
| package_name_p (const char *name, const struct block *block) |
| { |
| struct symbol *sym; |
| struct field_of_this_result is_a_field_of_this; |
| |
| sym = lookup_symbol (name, block, STRUCT_DOMAIN, &is_a_field_of_this).symbol; |
| |
| if (sym |
| && SYMBOL_CLASS (sym) == LOC_TYPEDEF |
| && SYMBOL_TYPE (sym)->code () == TYPE_CODE_MODULE) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Classify a (potential) function in the "unsafe" package. |
| We fold these into "keywords" to keep things simple, at least until |
| something more complex is warranted. */ |
| |
| static int |
| classify_unsafe_function (struct stoken function_name) |
| { |
| std::string copy = copy_name (function_name); |
| |
| if (copy == "Sizeof") |
| { |
| yylval.sval = function_name; |
| return SIZEOF_KEYWORD; |
| } |
| |
| error (_("Unknown function in `unsafe' package: %s"), copy.c_str ()); |
| } |
| |
| /* Classify token(s) "name1.name2" where name1 is known to be a package. |
| The contents of the token are in `yylval'. |
| Updates yylval and returns the new token type. |
| |
| The result is one of NAME, NAME_OR_INT, or TYPENAME. */ |
| |
| static int |
| classify_packaged_name (const struct block *block) |
| { |
| struct block_symbol sym; |
| struct field_of_this_result is_a_field_of_this; |
| |
| std::string copy = copy_name (yylval.sval); |
| |
| sym = lookup_symbol (copy.c_str (), block, VAR_DOMAIN, &is_a_field_of_this); |
| |
| if (sym.symbol) |
| { |
| yylval.ssym.sym = sym; |
| yylval.ssym.is_a_field_of_this = is_a_field_of_this.type != NULL; |
| } |
| |
| return NAME; |
| } |
| |
| /* Classify a NAME token. |
| The contents of the token are in `yylval'. |
| Updates yylval and returns the new token type. |
| BLOCK is the block in which lookups start; this can be NULL |
| to mean the global scope. |
| |
| The result is one of NAME, NAME_OR_INT, or TYPENAME. */ |
| |
| static int |
| classify_name (struct parser_state *par_state, const struct block *block) |
| { |
| struct type *type; |
| struct block_symbol sym; |
| struct field_of_this_result is_a_field_of_this; |
| |
| std::string copy = copy_name (yylval.sval); |
| |
| /* Try primitive types first so they win over bad/weird debug info. */ |
| type = language_lookup_primitive_type (par_state->language (), |
| par_state->gdbarch (), |
| copy.c_str ()); |
| if (type != NULL) |
| { |
| /* NOTE: We take advantage of the fact that yylval coming in was a |
| NAME, and that struct ttype is a compatible extension of struct |
| stoken, so yylval.tsym.stoken is already filled in. */ |
| yylval.tsym.type = type; |
| return TYPENAME; |
| } |
| |
| /* TODO: What about other types? */ |
| |
| sym = lookup_symbol (copy.c_str (), block, VAR_DOMAIN, &is_a_field_of_this); |
| |
| if (sym.symbol) |
| { |
| yylval.ssym.sym = sym; |
| yylval.ssym.is_a_field_of_this = is_a_field_of_this.type != NULL; |
| return NAME; |
| } |
| |
| /* If we didn't find a symbol, look again in the current package. |
| This is to, e.g., make "p global_var" work without having to specify |
| the package name. We intentionally only looks for objects in the |
| current package. */ |
| |
| { |
| char *current_package_name = go_block_package_name (block); |
| |
| if (current_package_name != NULL) |
| { |
| struct stoken sval = |
| build_packaged_name (current_package_name, |
| strlen (current_package_name), |
| copy.c_str (), copy.size ()); |
| |
| xfree (current_package_name); |
| sym = lookup_symbol (sval.ptr, block, VAR_DOMAIN, |
| &is_a_field_of_this); |
| if (sym.symbol) |
| { |
| yylval.ssym.stoken = sval; |
| yylval.ssym.sym = sym; |
| yylval.ssym.is_a_field_of_this = is_a_field_of_this.type != NULL; |
| return NAME; |
| } |
| } |
| } |
| |
| /* Input names that aren't symbols but ARE valid hex numbers, when |
| the input radix permits them, can be names or numbers depending |
| on the parse. Note we support radixes > 16 here. */ |
| if ((copy[0] >= 'a' && copy[0] < 'a' + input_radix - 10) |
| || (copy[0] >= 'A' && copy[0] < 'A' + input_radix - 10)) |
| { |
| YYSTYPE newlval; /* Its value is ignored. */ |
| int hextype = parse_number (par_state, copy.c_str (), |
| yylval.sval.length, 0, &newlval); |
| if (hextype == INT) |
| { |
| yylval.ssym.sym.symbol = NULL; |
| yylval.ssym.sym.block = NULL; |
| yylval.ssym.is_a_field_of_this = 0; |
| return NAME_OR_INT; |
| } |
| } |
| |
| yylval.ssym.sym.symbol = NULL; |
| yylval.ssym.sym.block = NULL; |
| yylval.ssym.is_a_field_of_this = 0; |
| return NAME; |
| } |
| |
| /* This is taken from c-exp.y mostly to get something working. |
| The basic structure has been kept because we may yet need some of it. */ |
| |
| static int |
| yylex (void) |
| { |
| token_and_value current, next; |
| |
| if (popping && !token_fifo.empty ()) |
| { |
| token_and_value tv = token_fifo[0]; |
| token_fifo.erase (token_fifo.begin ()); |
| yylval = tv.value; |
| /* There's no need to fall through to handle package.name |
| as that can never happen here. In theory. */ |
| return tv.token; |
| } |
| popping = 0; |
| |
| current.token = lex_one_token (pstate); |
| |
| /* TODO: Need a way to force specifying name1 as a package. |
| .name1.name2 ? */ |
| |
| if (current.token != NAME) |
| return current.token; |
| |
| /* See if we have "name1 . name2". */ |
| |
| current.value = yylval; |
| next.token = lex_one_token (pstate); |
| next.value = yylval; |
| |
| if (next.token == '.') |
| { |
| token_and_value name2; |
| |
| name2.token = lex_one_token (pstate); |
| name2.value = yylval; |
| |
| if (name2.token == NAME) |
| { |
| /* Ok, we have "name1 . name2". */ |
| std::string copy = copy_name (current.value.sval); |
| |
| if (copy == "unsafe") |
| { |
| popping = 1; |
| return classify_unsafe_function (name2.value.sval); |
| } |
| |
| if (package_name_p (copy.c_str (), pstate->expression_context_block)) |
| { |
| popping = 1; |
| yylval.sval = build_packaged_name (current.value.sval.ptr, |
| current.value.sval.length, |
| name2.value.sval.ptr, |
| name2.value.sval.length); |
| return classify_packaged_name (pstate->expression_context_block); |
| } |
| } |
| |
| token_fifo.push_back (next); |
| token_fifo.push_back (name2); |
| } |
| else |
| token_fifo.push_back (next); |
| |
| /* If we arrive here we don't have a package-qualified name. */ |
| |
| popping = 1; |
| yylval = current.value; |
| return classify_name (pstate, pstate->expression_context_block); |
| } |
| |
| /* See language.h. */ |
| |
| int |
| go_language::parser (struct parser_state *par_state) const |
| { |
| /* Setting up the parser state. */ |
| scoped_restore pstate_restore = make_scoped_restore (&pstate); |
| gdb_assert (par_state != NULL); |
| pstate = par_state; |
| |
| scoped_restore restore_yydebug = make_scoped_restore (&yydebug, |
| parser_debug); |
| |
| /* Initialize some state used by the lexer. */ |
| last_was_structop = 0; |
| saw_name_at_eof = 0; |
| paren_depth = 0; |
| |
| token_fifo.clear (); |
| popping = 0; |
| name_obstack.clear (); |
| |
| int result = yyparse (); |
| if (!result) |
| pstate->set_operation (pstate->pop ()); |
| return result; |
| } |
| |
| static void |
| yyerror (const char *msg) |
| { |
| if (pstate->prev_lexptr) |
| pstate->lexptr = pstate->prev_lexptr; |
| |
| error (_("A %s in expression, near `%s'."), msg, pstate->lexptr); |
| } |