| /* m68k.y -- bison grammar for m68k operand parsing |
| Copyright (C) 1995-2021 Free Software Foundation, Inc. |
| Written by Ken Raeburn and Ian Lance Taylor, Cygnus Support |
| |
| This file is part of GAS, the GNU Assembler. |
| |
| GAS 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, or (at your option) |
| any later version. |
| |
| GAS 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 GAS; see the file COPYING. If not, write to the Free |
| Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA |
| 02110-1301, USA. */ |
| |
| /* This file holds a bison grammar to parse m68k operands. The m68k |
| has a complicated operand syntax, and gas supports two main |
| variations of it. Using a grammar is probably overkill, but at |
| least it makes clear exactly what we do support. */ |
| |
| %{ |
| |
| #include "as.h" |
| #include "tc-m68k.h" |
| #include "m68k-parse.h" |
| #include "safe-ctype.h" |
| |
| /* Remap normal yacc parser interface names (yyparse, yylex, yyerror, |
| etc), as well as gratuitously global symbol names If other parser |
| generators (bison, byacc, etc) produce additional global names that |
| conflict at link time, then those parser generators need to be |
| fixed instead of adding those names to this list. */ |
| |
| #define yymaxdepth m68k_maxdepth |
| #define yyparse m68k_parse |
| #define yylex m68k_lex |
| #define yyerror m68k_error |
| #define yylval m68k_lval |
| #define yychar m68k_char |
| #define yydebug m68k_debug |
| #define yypact m68k_pact |
| #define yyr1 m68k_r1 |
| #define yyr2 m68k_r2 |
| #define yydef m68k_def |
| #define yychk m68k_chk |
| #define yypgo m68k_pgo |
| #define yyact m68k_act |
| #define yyexca m68k_exca |
| #define yyerrflag m68k_errflag |
| #define yynerrs m68k_nerrs |
| #define yyps m68k_ps |
| #define yypv m68k_pv |
| #define yys m68k_s |
| #define yy_yys m68k_yys |
| #define yystate m68k_state |
| #define yytmp m68k_tmp |
| #define yyv m68k_v |
| #define yy_yyv m68k_yyv |
| #define yyval m68k_val |
| #define yylloc m68k_lloc |
| #define yyreds m68k_reds /* With YYDEBUG defined */ |
| #define yytoks m68k_toks /* With YYDEBUG defined */ |
| #define yylhs m68k_yylhs |
| #define yylen m68k_yylen |
| #define yydefred m68k_yydefred |
| #define yydgoto m68k_yydgoto |
| #define yysindex m68k_yysindex |
| #define yyrindex m68k_yyrindex |
| #define yygindex m68k_yygindex |
| #define yytable m68k_yytable |
| #define yycheck m68k_yycheck |
| |
| #ifndef YYDEBUG |
| #define YYDEBUG 1 |
| #endif |
| |
| /* Internal functions. */ |
| |
| static enum m68k_register m68k_reg_parse (char **); |
| static int yylex (void); |
| static void yyerror (const char *); |
| |
| /* The parser sets fields pointed to by this global variable. */ |
| static struct m68k_op *op; |
| |
| %} |
| |
| %union |
| { |
| struct m68k_indexreg indexreg; |
| enum m68k_register reg; |
| struct m68k_exp exp; |
| unsigned long mask; |
| int onereg; |
| int trailing_ampersand; |
| } |
| |
| %token <reg> DR AR FPR FPCR LPC ZAR ZDR LZPC CREG |
| %token <indexreg> INDEXREG |
| %token <exp> EXPR |
| |
| %type <indexreg> zireg zdireg |
| %type <reg> zadr zdr apc zapc zpc optzapc optczapc |
| %type <exp> optcexpr optexprc |
| %type <mask> reglist ireglist reglistpair |
| %type <onereg> reglistreg |
| %type <trailing_ampersand> optional_ampersand |
| |
| %% |
| |
| /* An operand. */ |
| |
| operand: |
| generic_operand |
| | motorola_operand optional_ampersand |
| { |
| op->trailing_ampersand = $2; |
| } |
| | mit_operand optional_ampersand |
| { |
| op->trailing_ampersand = $2; |
| } |
| ; |
| |
| /* A trailing ampersand(for MAC/EMAC mask addressing). */ |
| optional_ampersand: |
| /* empty */ |
| { $$ = 0; } |
| | '&' |
| { $$ = 1; } |
| ; |
| |
| /* A generic operand. */ |
| |
| generic_operand: |
| '<' '<' |
| { |
| op->mode = LSH; |
| } |
| |
| | '>' '>' |
| { |
| op->mode = RSH; |
| } |
| |
| | DR |
| { |
| op->mode = DREG; |
| op->reg = $1; |
| } |
| | AR |
| { |
| op->mode = AREG; |
| op->reg = $1; |
| } |
| | FPR |
| { |
| op->mode = FPREG; |
| op->reg = $1; |
| } |
| | FPCR |
| { |
| op->mode = CONTROL; |
| op->reg = $1; |
| } |
| | CREG |
| { |
| op->mode = CONTROL; |
| op->reg = $1; |
| } |
| | EXPR |
| { |
| op->mode = ABSL; |
| op->disp = $1; |
| } |
| | '#' EXPR |
| { |
| op->mode = IMMED; |
| op->disp = $2; |
| } |
| | '&' EXPR |
| { |
| op->mode = IMMED; |
| op->disp = $2; |
| } |
| | reglist |
| { |
| op->mode = REGLST; |
| op->mask = $1; |
| } |
| ; |
| |
| /* An operand in Motorola syntax. This includes MRI syntax as well, |
| which may or may not be different in that it permits commutativity |
| of index and base registers, and permits an offset expression to |
| appear inside or outside of the parentheses. */ |
| |
| motorola_operand: |
| '(' AR ')' |
| { |
| op->mode = AINDR; |
| op->reg = $2; |
| } |
| | '(' AR ')' '+' |
| { |
| op->mode = AINC; |
| op->reg = $2; |
| } |
| | '-' '(' AR ')' |
| { |
| op->mode = ADEC; |
| op->reg = $3; |
| } |
| | '(' EXPR ',' zapc ')' |
| { |
| op->reg = $4; |
| op->disp = $2; |
| if (($4 >= ZADDR0 && $4 <= ZADDR7) |
| || $4 == ZPC) |
| op->mode = BASE; |
| else |
| op->mode = DISP; |
| } |
| | '(' zapc ',' EXPR ')' |
| { |
| op->reg = $2; |
| op->disp = $4; |
| if (($2 >= ZADDR0 && $2 <= ZADDR7) |
| || $2 == ZPC) |
| op->mode = BASE; |
| else |
| op->mode = DISP; |
| } |
| | EXPR '(' zapc ')' |
| { |
| op->reg = $3; |
| op->disp = $1; |
| if (($3 >= ZADDR0 && $3 <= ZADDR7) |
| || $3 == ZPC) |
| op->mode = BASE; |
| else |
| op->mode = DISP; |
| } |
| | '(' LPC ')' |
| { |
| op->mode = DISP; |
| op->reg = $2; |
| } |
| | '(' ZAR ')' |
| { |
| op->mode = BASE; |
| op->reg = $2; |
| } |
| | '(' LZPC ')' |
| { |
| op->mode = BASE; |
| op->reg = $2; |
| } |
| | '(' EXPR ',' zapc ',' zireg ')' |
| { |
| op->mode = BASE; |
| op->reg = $4; |
| op->disp = $2; |
| op->index = $6; |
| } |
| | '(' EXPR ',' zapc ',' zpc ')' |
| { |
| if ($4 == PC || $4 == ZPC) |
| yyerror (_("syntax error")); |
| op->mode = BASE; |
| op->reg = $6; |
| op->disp = $2; |
| op->index.reg = $4; |
| op->index.size = SIZE_UNSPEC; |
| op->index.scale = 1; |
| } |
| | '(' EXPR ',' zdireg optczapc ')' |
| { |
| op->mode = BASE; |
| op->reg = $5; |
| op->disp = $2; |
| op->index = $4; |
| } |
| | '(' zdireg ',' EXPR ')' |
| { |
| op->mode = BASE; |
| op->disp = $4; |
| op->index = $2; |
| } |
| | EXPR '(' zapc ',' zireg ')' |
| { |
| op->mode = BASE; |
| op->reg = $3; |
| op->disp = $1; |
| op->index = $5; |
| } |
| | '(' zapc ',' zireg ')' |
| { |
| op->mode = BASE; |
| op->reg = $2; |
| op->index = $4; |
| } |
| | EXPR '(' zapc ',' zpc ')' |
| { |
| if ($3 == PC || $3 == ZPC) |
| yyerror (_("syntax error")); |
| op->mode = BASE; |
| op->reg = $5; |
| op->disp = $1; |
| op->index.reg = $3; |
| op->index.size = SIZE_UNSPEC; |
| op->index.scale = 1; |
| } |
| | '(' zapc ',' zpc ')' |
| { |
| if ($2 == PC || $2 == ZPC) |
| yyerror (_("syntax error")); |
| op->mode = BASE; |
| op->reg = $4; |
| op->index.reg = $2; |
| op->index.size = SIZE_UNSPEC; |
| op->index.scale = 1; |
| } |
| | EXPR '(' zdireg optczapc ')' |
| { |
| op->mode = BASE; |
| op->reg = $4; |
| op->disp = $1; |
| op->index = $3; |
| } |
| | '(' zdireg optczapc ')' |
| { |
| op->mode = BASE; |
| op->reg = $3; |
| op->index = $2; |
| } |
| | '(' '[' EXPR optczapc ']' ',' zireg optcexpr ')' |
| { |
| op->mode = POST; |
| op->reg = $4; |
| op->disp = $3; |
| op->index = $7; |
| op->odisp = $8; |
| } |
| | '(' '[' EXPR optczapc ']' optcexpr ')' |
| { |
| op->mode = POST; |
| op->reg = $4; |
| op->disp = $3; |
| op->odisp = $6; |
| } |
| | '(' '[' zapc ']' ',' zireg optcexpr ')' |
| { |
| op->mode = POST; |
| op->reg = $3; |
| op->index = $6; |
| op->odisp = $7; |
| } |
| | '(' '[' zapc ']' optcexpr ')' |
| { |
| op->mode = POST; |
| op->reg = $3; |
| op->odisp = $5; |
| } |
| | '(' '[' EXPR ',' zapc ',' zireg ']' optcexpr ')' |
| { |
| op->mode = PRE; |
| op->reg = $5; |
| op->disp = $3; |
| op->index = $7; |
| op->odisp = $9; |
| } |
| | '(' '[' zapc ',' zireg ']' optcexpr ')' |
| { |
| op->mode = PRE; |
| op->reg = $3; |
| op->index = $5; |
| op->odisp = $7; |
| } |
| | '(' '[' EXPR ',' zapc ',' zpc ']' optcexpr ')' |
| { |
| if ($5 == PC || $5 == ZPC) |
| yyerror (_("syntax error")); |
| op->mode = PRE; |
| op->reg = $7; |
| op->disp = $3; |
| op->index.reg = $5; |
| op->index.size = SIZE_UNSPEC; |
| op->index.scale = 1; |
| op->odisp = $9; |
| } |
| | '(' '[' zapc ',' zpc ']' optcexpr ')' |
| { |
| if ($3 == PC || $3 == ZPC) |
| yyerror (_("syntax error")); |
| op->mode = PRE; |
| op->reg = $5; |
| op->index.reg = $3; |
| op->index.size = SIZE_UNSPEC; |
| op->index.scale = 1; |
| op->odisp = $7; |
| } |
| | '(' '[' optexprc zdireg optczapc ']' optcexpr ')' |
| { |
| op->mode = PRE; |
| op->reg = $5; |
| op->disp = $3; |
| op->index = $4; |
| op->odisp = $7; |
| } |
| ; |
| |
| /* An operand in MIT syntax. */ |
| |
| mit_operand: |
| optzapc '@' |
| { |
| /* We use optzapc to avoid a shift/reduce conflict. */ |
| if ($1 < ADDR0 || $1 > ADDR7) |
| yyerror (_("syntax error")); |
| op->mode = AINDR; |
| op->reg = $1; |
| } |
| | optzapc '@' '+' |
| { |
| /* We use optzapc to avoid a shift/reduce conflict. */ |
| if ($1 < ADDR0 || $1 > ADDR7) |
| yyerror (_("syntax error")); |
| op->mode = AINC; |
| op->reg = $1; |
| } |
| | optzapc '@' '-' |
| { |
| /* We use optzapc to avoid a shift/reduce conflict. */ |
| if ($1 < ADDR0 || $1 > ADDR7) |
| yyerror (_("syntax error")); |
| op->mode = ADEC; |
| op->reg = $1; |
| } |
| | optzapc '@' '(' EXPR ')' |
| { |
| op->reg = $1; |
| op->disp = $4; |
| if (($1 >= ZADDR0 && $1 <= ZADDR7) |
| || $1 == ZPC) |
| op->mode = BASE; |
| else |
| op->mode = DISP; |
| } |
| | optzapc '@' '(' optexprc zireg ')' |
| { |
| op->mode = BASE; |
| op->reg = $1; |
| op->disp = $4; |
| op->index = $5; |
| } |
| | optzapc '@' '(' EXPR ')' '@' '(' optexprc zireg ')' |
| { |
| op->mode = POST; |
| op->reg = $1; |
| op->disp = $4; |
| op->index = $9; |
| op->odisp = $8; |
| } |
| | optzapc '@' '(' EXPR ')' '@' '(' EXPR ')' |
| { |
| op->mode = POST; |
| op->reg = $1; |
| op->disp = $4; |
| op->odisp = $8; |
| } |
| | optzapc '@' '(' optexprc zireg ')' '@' '(' EXPR ')' |
| { |
| op->mode = PRE; |
| op->reg = $1; |
| op->disp = $4; |
| op->index = $5; |
| op->odisp = $9; |
| } |
| ; |
| |
| /* An index register, possibly suppressed, which need not have a size |
| or scale. */ |
| |
| zireg: |
| INDEXREG |
| | zadr |
| { |
| $$.reg = $1; |
| $$.size = SIZE_UNSPEC; |
| $$.scale = 1; |
| } |
| ; |
| |
| /* A register which may be an index register, but which may not be an |
| address register. This nonterminal is used to avoid ambiguity when |
| trying to parse something like (0,d5,a6) as compared to (0,a6,d5). */ |
| |
| zdireg: |
| INDEXREG |
| | zdr |
| { |
| $$.reg = $1; |
| $$.size = SIZE_UNSPEC; |
| $$.scale = 1; |
| } |
| ; |
| |
| /* An address or data register, or a suppressed address or data |
| register. */ |
| |
| zadr: |
| zdr |
| | AR |
| | ZAR |
| ; |
| |
| /* A data register which may be suppressed. */ |
| |
| zdr: |
| DR |
| | ZDR |
| ; |
| |
| /* Either an address register or the PC. */ |
| |
| apc: |
| AR |
| | LPC |
| ; |
| |
| /* Either an address register, or the PC, or a suppressed address |
| register, or a suppressed PC. */ |
| |
| zapc: |
| apc |
| | LZPC |
| | ZAR |
| ; |
| |
| /* An optional zapc. */ |
| |
| optzapc: |
| /* empty */ |
| { |
| $$ = ZADDR0; |
| } |
| | zapc |
| ; |
| |
| /* The PC, optionally suppressed. */ |
| |
| zpc: |
| LPC |
| | LZPC |
| ; |
| |
| /* ',' zapc when it may be omitted. */ |
| |
| optczapc: |
| /* empty */ |
| { |
| $$ = ZADDR0; |
| } |
| | ',' zapc |
| { |
| $$ = $2; |
| } |
| ; |
| |
| /* ',' EXPR when it may be omitted. */ |
| |
| optcexpr: |
| /* empty */ |
| { |
| $$.exp.X_op = O_absent; |
| $$.size = SIZE_UNSPEC; |
| } |
| | ',' EXPR |
| { |
| $$ = $2; |
| } |
| ; |
| |
| /* EXPR ',' when it may be omitted. */ |
| |
| optexprc: |
| /* empty */ |
| { |
| $$.exp.X_op = O_absent; |
| $$.size = SIZE_UNSPEC; |
| } |
| | EXPR ',' |
| { |
| $$ = $1; |
| } |
| ; |
| |
| /* A register list for the movem instruction. */ |
| |
| reglist: |
| reglistpair |
| | reglistpair '/' ireglist |
| { |
| $$ = $1 | $3; |
| } |
| | reglistreg '/' ireglist |
| { |
| $$ = (1 << $1) | $3; |
| } |
| ; |
| |
| /* We use ireglist when we know we are looking at a reglist, and we |
| can safely reduce a simple register to reglistreg. If we permitted |
| reglist to reduce to reglistreg, it would be ambiguous whether a |
| plain register were a DREG/AREG/FPREG or a REGLST. */ |
| |
| ireglist: |
| reglistreg |
| { |
| $$ = 1 << $1; |
| } |
| | reglistpair |
| | reglistpair '/' ireglist |
| { |
| $$ = $1 | $3; |
| } |
| | reglistreg '/' ireglist |
| { |
| $$ = (1 << $1) | $3; |
| } |
| ; |
| |
| reglistpair: |
| reglistreg '-' reglistreg |
| { |
| if ($1 <= $3) |
| $$ = (1 << ($3 + 1)) - 1 - ((1 << $1) - 1); |
| else |
| $$ = (1 << ($1 + 1)) - 1 - ((1 << $3) - 1); |
| } |
| ; |
| |
| reglistreg: |
| DR |
| { |
| $$ = $1 - DATA0; |
| } |
| | AR |
| { |
| $$ = $1 - ADDR0 + 8; |
| } |
| | FPR |
| { |
| $$ = $1 - FP0 + 16; |
| } |
| | FPCR |
| { |
| if ($1 == FPI) |
| $$ = 24; |
| else if ($1 == FPS) |
| $$ = 25; |
| else |
| $$ = 26; |
| } |
| ; |
| |
| %% |
| |
| /* The string to parse is stored here, and modified by yylex. */ |
| |
| static char *str; |
| |
| /* The original string pointer. */ |
| |
| static char *strorig; |
| |
| /* If *CCP could be a register, return the register number and advance |
| *CCP. Otherwise don't change *CCP, and return 0. */ |
| |
| static enum m68k_register |
| m68k_reg_parse (char **ccp) |
| { |
| char *start = *ccp; |
| char c; |
| char *p; |
| symbolS *symbolp; |
| |
| if (flag_reg_prefix_optional) |
| { |
| if (*start == REGISTER_PREFIX) |
| start++; |
| p = start; |
| } |
| else |
| { |
| if (*start != REGISTER_PREFIX) |
| return 0; |
| p = start + 1; |
| } |
| |
| if (! is_name_beginner (*p)) |
| return 0; |
| |
| p++; |
| while (is_part_of_name (*p) && *p != '.' && *p != ':' && *p != '*') |
| p++; |
| |
| c = *p; |
| *p = 0; |
| symbolp = symbol_find (start); |
| *p = c; |
| |
| if (symbolp != NULL && S_GET_SEGMENT (symbolp) == reg_section) |
| { |
| *ccp = p; |
| return S_GET_VALUE (symbolp); |
| } |
| |
| /* In MRI mode, something like foo.bar can be equated to a register |
| name. */ |
| while (flag_mri && c == '.') |
| { |
| ++p; |
| while (is_part_of_name (*p) && *p != '.' && *p != ':' && *p != '*') |
| p++; |
| c = *p; |
| *p = '\0'; |
| symbolp = symbol_find (start); |
| *p = c; |
| if (symbolp != NULL && S_GET_SEGMENT (symbolp) == reg_section) |
| { |
| *ccp = p; |
| return S_GET_VALUE (symbolp); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* The lexer. */ |
| |
| static int |
| yylex (void) |
| { |
| enum m68k_register reg; |
| char *s; |
| int parens; |
| int c = 0; |
| int tail = 0; |
| |
| if (*str == ' ') |
| ++str; |
| |
| if (*str == '\0') |
| return 0; |
| |
| /* Various special characters are just returned directly. */ |
| switch (*str) |
| { |
| case '@': |
| /* In MRI mode, this can be the start of an octal number. */ |
| if (flag_mri) |
| { |
| if (ISDIGIT (str[1]) |
| || ((str[1] == '+' || str[1] == '-') |
| && ISDIGIT (str[2]))) |
| break; |
| } |
| /* Fall through. */ |
| case '#': |
| case '&': |
| case ',': |
| case ')': |
| case '/': |
| case '[': |
| case ']': |
| case '<': |
| case '>': |
| return *str++; |
| case '+': |
| /* It so happens that a '+' can only appear at the end of an |
| operand, or if it is trailed by an '&'(see mac load insn). |
| If it appears anywhere else, it must be a unary. */ |
| if (str[1] == '\0' || (str[1] == '&' && str[2] == '\0')) |
| return *str++; |
| break; |
| case '-': |
| /* A '-' can only appear in -(ar), rn-rn, or ar@-. If it |
| appears anywhere else, it must be a unary minus on an |
| expression, unless it it trailed by a '&'(see mac load insn). */ |
| if (str[1] == '\0' || (str[1] == '&' && str[2] == '\0')) |
| return *str++; |
| s = str + 1; |
| if (*s == '(') |
| ++s; |
| if (m68k_reg_parse (&s) != 0) |
| return *str++; |
| break; |
| case '(': |
| /* A '(' can only appear in `(reg)', `(expr,...', `([', `@(', or |
| `)('. If it appears anywhere else, it must be starting an |
| expression. */ |
| if (str[1] == '[' |
| || (str > strorig |
| && (str[-1] == '@' |
| || str[-1] == ')'))) |
| return *str++; |
| s = str + 1; |
| if (m68k_reg_parse (&s) != 0) |
| return *str++; |
| /* Check for the case of '(expr,...' by scanning ahead. If we |
| find a comma outside of balanced parentheses, we return '('. |
| If we find an unbalanced right parenthesis, then presumably |
| the '(' really starts an expression. */ |
| parens = 0; |
| for (s = str + 1; *s != '\0'; s++) |
| { |
| if (*s == '(') |
| ++parens; |
| else if (*s == ')') |
| { |
| if (parens == 0) |
| break; |
| --parens; |
| } |
| else if (*s == ',' && parens == 0) |
| { |
| /* A comma can not normally appear in an expression, so |
| this is a case of '(expr,...'. */ |
| return *str++; |
| } |
| } |
| } |
| |
| /* See if it's a register. */ |
| |
| reg = m68k_reg_parse (&str); |
| if (reg != 0) |
| { |
| int ret; |
| |
| yylval.reg = reg; |
| |
| if (reg >= DATA0 && reg <= DATA7) |
| ret = DR; |
| else if (reg >= ADDR0 && reg <= ADDR7) |
| ret = AR; |
| else if (reg >= FP0 && reg <= FP7) |
| return FPR; |
| else if (reg == FPI |
| || reg == FPS |
| || reg == FPC) |
| return FPCR; |
| else if (reg == PC) |
| return LPC; |
| else if (reg >= ZDATA0 && reg <= ZDATA7) |
| ret = ZDR; |
| else if (reg >= ZADDR0 && reg <= ZADDR7) |
| ret = ZAR; |
| else if (reg == ZPC) |
| return LZPC; |
| else |
| return CREG; |
| |
| /* If we get here, we have a data or address register. We |
| must check for a size or scale; if we find one, we must |
| return INDEXREG. */ |
| |
| s = str; |
| |
| if (*s != '.' && *s != ':' && *s != '*') |
| return ret; |
| |
| yylval.indexreg.reg = reg; |
| |
| if (*s != '.' && *s != ':') |
| yylval.indexreg.size = SIZE_UNSPEC; |
| else |
| { |
| ++s; |
| switch (*s) |
| { |
| case 'w': |
| case 'W': |
| yylval.indexreg.size = SIZE_WORD; |
| ++s; |
| break; |
| case 'l': |
| case 'L': |
| yylval.indexreg.size = SIZE_LONG; |
| ++s; |
| break; |
| default: |
| yyerror (_("illegal size specification")); |
| yylval.indexreg.size = SIZE_UNSPEC; |
| break; |
| } |
| } |
| |
| yylval.indexreg.scale = 1; |
| |
| if (*s == '*' || *s == ':') |
| { |
| expressionS scale; |
| |
| ++s; |
| |
| temp_ilp (s); |
| expression (&scale); |
| s = input_line_pointer; |
| restore_ilp (); |
| |
| if (scale.X_op != O_constant) |
| yyerror (_("scale specification must resolve to a number")); |
| else |
| { |
| switch (scale.X_add_number) |
| { |
| case 1: |
| case 2: |
| case 4: |
| case 8: |
| yylval.indexreg.scale = scale.X_add_number; |
| break; |
| default: |
| yyerror (_("invalid scale value")); |
| break; |
| } |
| } |
| } |
| |
| str = s; |
| |
| return INDEXREG; |
| } |
| |
| /* It must be an expression. Before we call expression, we need to |
| look ahead to see if there is a size specification. We must do |
| that first, because otherwise foo.l will be treated as the symbol |
| foo.l, rather than as the symbol foo with a long size |
| specification. The grammar requires that all expressions end at |
| the end of the operand, or with ',', '(', ']', ')'. */ |
| |
| parens = 0; |
| for (s = str; *s != '\0'; s++) |
| { |
| if (*s == '(') |
| { |
| if (parens == 0 |
| && s > str |
| && (s[-1] == ')' || ISALNUM (s[-1]))) |
| break; |
| ++parens; |
| } |
| else if (*s == ')') |
| { |
| if (parens == 0) |
| break; |
| --parens; |
| } |
| else if (parens == 0 |
| && (*s == ',' || *s == ']')) |
| break; |
| } |
| |
| yylval.exp.size = SIZE_UNSPEC; |
| if (s <= str + 2 |
| || (s[-2] != '.' && s[-2] != ':')) |
| tail = 0; |
| else |
| { |
| switch (s[-1]) |
| { |
| case 's': |
| case 'S': |
| case 'b': |
| case 'B': |
| yylval.exp.size = SIZE_BYTE; |
| break; |
| case 'w': |
| case 'W': |
| yylval.exp.size = SIZE_WORD; |
| break; |
| case 'l': |
| case 'L': |
| yylval.exp.size = SIZE_LONG; |
| break; |
| default: |
| break; |
| } |
| if (yylval.exp.size != SIZE_UNSPEC) |
| tail = 2; |
| } |
| |
| #ifdef OBJ_ELF |
| { |
| /* Look for @PLTPC, etc. */ |
| char *cp; |
| |
| yylval.exp.pic_reloc = pic_none; |
| cp = s - tail; |
| if (cp - 7 > str && cp[-7] == '@') |
| { |
| if (startswith (cp - 7, "@TLSLDM")) |
| { |
| yylval.exp.pic_reloc = pic_tls_ldm; |
| tail += 7; |
| } |
| else if (startswith (cp - 7, "@TLSLDO")) |
| { |
| yylval.exp.pic_reloc = pic_tls_ldo; |
| tail += 7; |
| } |
| } |
| else if (cp - 6 > str && cp[-6] == '@') |
| { |
| if (startswith (cp - 6, "@PLTPC")) |
| { |
| yylval.exp.pic_reloc = pic_plt_pcrel; |
| tail += 6; |
| } |
| else if (startswith (cp - 6, "@GOTPC")) |
| { |
| yylval.exp.pic_reloc = pic_got_pcrel; |
| tail += 6; |
| } |
| else if (startswith (cp - 6, "@TLSGD")) |
| { |
| yylval.exp.pic_reloc = pic_tls_gd; |
| tail += 6; |
| } |
| else if (startswith (cp - 6, "@TLSIE")) |
| { |
| yylval.exp.pic_reloc = pic_tls_ie; |
| tail += 6; |
| } |
| else if (startswith (cp - 6, "@TLSLE")) |
| { |
| yylval.exp.pic_reloc = pic_tls_le; |
| tail += 6; |
| } |
| } |
| else if (cp - 4 > str && cp[-4] == '@') |
| { |
| if (startswith (cp - 4, "@PLT")) |
| { |
| yylval.exp.pic_reloc = pic_plt_off; |
| tail += 4; |
| } |
| else if (startswith (cp - 4, "@GOT")) |
| { |
| yylval.exp.pic_reloc = pic_got_off; |
| tail += 4; |
| } |
| } |
| } |
| #endif |
| |
| if (tail != 0) |
| { |
| c = s[-tail]; |
| s[-tail] = 0; |
| } |
| |
| temp_ilp (str); |
| expression (&yylval.exp.exp); |
| str = input_line_pointer; |
| restore_ilp (); |
| |
| if (tail != 0) |
| { |
| s[-tail] = c; |
| str = s; |
| } |
| |
| return EXPR; |
| } |
| |
| /* Parse an m68k operand. This is the only function which is called |
| from outside this file. */ |
| |
| int |
| m68k_ip_op (char *s, struct m68k_op *oparg) |
| { |
| memset (oparg, 0, sizeof *oparg); |
| oparg->error = NULL; |
| oparg->index.reg = ZDATA0; |
| oparg->index.scale = 1; |
| oparg->disp.exp.X_op = O_absent; |
| oparg->odisp.exp.X_op = O_absent; |
| |
| str = strorig = s; |
| op = oparg; |
| |
| return yyparse (); |
| } |
| |
| /* The error handler. */ |
| |
| static void |
| yyerror (const char *s) |
| { |
| op->error = s; |
| } |