]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - gold/yyscript.y
Update year range in copyright notice of binutils files
[thirdparty/binutils-gdb.git] / gold / yyscript.y
index b78c09b5662f512a95281f95c5d01909217f1b0c..60205e9aeb02a5aca18916d1c13c7148dda89441 100644 (file)
@@ -1,6 +1,6 @@
-/* yyscript.y -- linker script grammer for gold.  */
+/* yyscript.y -- linker script grammar for gold.  */
 
-/* Copyright 2006, 2007 Free Software Foundation, Inc.
+/* Copyright (C) 2006-2021 Free Software Foundation, Inc.
    Written by Ian Lance Taylor <iant@google.com>.
 
    This file is part of gold.
@@ -29,6 +29,8 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
 
 #include "script-c.h"
 
 /* The values associated with tokens.  */
 
 %union {
-  const char* string;
-  int64_t integer;
+  /* A string.  */
+  struct Parser_string string;
+  /* A number.  */
+  uint64_t integer;
+  /* An expression.  */
+  Expression_ptr expr;
+  /* An output section header.  */
+  struct Parser_output_section_header output_section_header;
+  /* An output section trailer.  */
+  struct Parser_output_section_trailer output_section_trailer;
+  /* A section constraint.  */
+  enum Section_constraint constraint;
+  /* A complete input section specification.  */
+  struct Input_section_spec input_section_spec;
+  /* A list of wildcard specifications, with exclusions.  */
+  struct Wildcard_sections wildcard_sections;
+  /* A single wildcard specification.  */
+  struct Wildcard_section wildcard_section;
+  /* A list of strings.  */
+  String_list_ptr string_list;
+  /* Information for a program header.  */
+  struct Phdr_info phdr_info;
+  /* Used for version scripts and within VERSION {}.  */
+  struct Version_dependency_list* deplist;
+  struct Version_expression_list* versyms;
+  struct Version_tree* versnode;
+  enum Script_section_type section_type;
 }
 
 /* Operators, including a precedence table for expressions.  */
 %left '+' '-'
 %left '*' '/' '%'
 
+/* A fake operator used to indicate unary operator precedence.  */
+%right UNARY
+
 /* Constants.  */
 
 %token <string> STRING
+%token <string> QUOTED_STRING
 %token <integer> INTEGER
 
 /* Keywords.  This list is taken from ldgram.y and ldlex.l in the old
 %token ABSOLUTE
 %token ADDR
 %token ALIGN_K         /* ALIGN */
+%token ALIGNOF
 %token ASSERT_K                /* ASSERT */
 %token AS_NEEDED
 %token AT
 %token FORCE_COMMON_ALLOCATION
 %token GLOBAL          /* global */
 %token GROUP
+%token HIDDEN
 %token HLL
 %token INCLUDE
-%token INFO
 %token INHIBIT_COMMON_ALLOCATION
+%token INFO
 %token INPUT
 %token KEEP
+%token LEN
 %token LENGTH          /* LENGTH, l, len */
 %token LOADADDR
 %token LOCAL           /* local */
 %token NOLOAD
 %token ONLY_IF_RO
 %token ONLY_IF_RW
+%token ORG
 %token ORIGIN          /* ORIGIN, o, org */
 %token OUTPUT
 %token OUTPUT_ARCH
 %token SIZEOF
 %token SIZEOF_HEADERS  /* SIZEOF_HEADERS, sizeof_headers */
 %token SORT_BY_ALIGNMENT
+%token SORT_BY_INIT_PRIORITY
 %token SORT_BY_NAME
 %token SPECIAL
 %token SQUAD
 %token TRUNCATE
 %token VERSIONK                /* VERSION */
 
+/* Keywords, part 2.  These are keywords that are unique to gold,
+   and not present in the old GNU linker.  As before, unless the
+   comments say otherwise, the keyword is recognized as the token
+   name in upper case. */
+
+%token OPTION
+
+/* Special tokens used to tell the grammar what type of tokens we are
+   parsing.  The token stream always begins with one of these tokens.
+   We do this because version scripts can appear embedded within
+   linker scripts, and because --defsym uses the expression
+   parser.  */
+%token PARSING_LINKER_SCRIPT
+%token PARSING_VERSION_SCRIPT
+%token PARSING_DEFSYM
+%token PARSING_DYNAMIC_LIST
+%token PARSING_SECTIONS_BLOCK
+%token PARSING_SECTION_COMMANDS
+%token PARSING_MEMORY_DEF
+
+/* Non-terminal types, where needed.  */
+
+%type <expr> parse_exp exp
+%type <expr> opt_at opt_align opt_subalign opt_fill
+%type <output_section_header> section_header opt_address_and_section_type
+%type <section_type> section_type
+%type <output_section_trailer> section_trailer
+%type <constraint> opt_constraint
+%type <string_list> opt_phdr
+%type <integer> data_length
+%type <input_section_spec> input_section_no_keep
+%type <wildcard_sections> wildcard_sections
+%type <wildcard_section> wildcard_file wildcard_section
+%type <string_list> exclude_names
+%type <string> wildcard_name
+%type <integer> phdr_type memory_attr
+%type <phdr_info> phdr_info
+%type <versyms> vers_defns
+%type <versnode> vers_tag
+%type <deplist> verdep
+%type <string> string
+
 %%
 
-file_list:
-         file_list file_cmd
+/* Read the special token to see what to read next.  */
+top:
+         PARSING_LINKER_SCRIPT linker_script
+       | PARSING_VERSION_SCRIPT version_script
+       | PARSING_DEFSYM defsym_expr
+        | PARSING_DYNAMIC_LIST dynamic_list_expr
+        | PARSING_SECTIONS_BLOCK sections_block
+        | PARSING_SECTION_COMMANDS section_cmds
+        | PARSING_MEMORY_DEF memory_defs
+       ;
+
+/* A file contains a list of commands.  */
+linker_script:
+         linker_script file_cmd
        | /* empty */
        ;
 
+/* A command which may appear at top level of a linker script.  */
 file_cmd:
-         OUTPUT_FORMAT '(' STRING ')'
+         EXTERN '(' extern_name_list ')'
+       | FORCE_COMMON_ALLOCATION
+           { script_set_common_allocation(closure, 1); }
        | GROUP
            { script_start_group(closure); }
          '(' input_list ')'
            { script_end_group(closure); }
+       | INHIBIT_COMMON_ALLOCATION
+           { script_set_common_allocation(closure, 0); }
+       | INPUT '(' input_list ')'
+       | MEMORY '{' memory_defs '}'
+        | OPTION '(' string ')'
+           { script_parse_option(closure, $3.value, $3.length); }
+       | OUTPUT_FORMAT '(' string ')'
+           {
+             if (!script_check_output_format(closure, $3.value, $3.length,
+                                             NULL, 0, NULL, 0))
+               YYABORT;
+           }
+       | OUTPUT_FORMAT '(' string ',' string ',' string ')'
+           {
+             if (!script_check_output_format(closure, $3.value, $3.length,
+                                             $5.value, $5.length,
+                                             $7.value, $7.length))
+               YYABORT;
+           }
+       | PHDRS '{' phdrs_defs '}'
+       | SEARCH_DIR '(' string ')'
+           { script_add_search_dir(closure, $3.value, $3.length); }
+       | SECTIONS '{'
+           { script_start_sections(closure); }
+         sections_block '}'
+           { script_finish_sections(closure); }
+       | TARGET_K '(' string ')'
+           { script_set_target(closure, $3.value, $3.length); }
+        | VERSIONK '{'
+            { script_push_lex_into_version_mode(closure); }
+          version_script '}'
+            { script_pop_lex_mode(closure); }
+       | ENTRY '(' string ')'
+           { script_set_entry(closure, $3.value, $3.length); }
+       | assignment end
+       | ASSERT_K '(' parse_exp ',' string ')'
+           { script_add_assertion(closure, $3, $5.value, $5.length); }
+       | INCLUDE string
+           { script_include_directive(PARSING_LINKER_SCRIPT, closure,
+                                      $2.value, $2.length); }
+       | ignore_cmd
+       | ';'
+       ;
+
+/* Top level commands which we ignore.  The GNU linker uses these to
+   select the output format, but we don't offer a choice.  Ignoring
+   these is more-or-less OK since most scripts simply explicitly
+   choose the default.  */
+ignore_cmd:
+         OUTPUT_ARCH '(' string ')'
        ;
 
+/* A list of external undefined symbols.  We put the lexer into
+   expression mode so that commas separate names; this is what the GNU
+   linker does.  */
+
+extern_name_list:
+           { script_push_lex_into_expression_mode(closure); }
+         extern_name_list_body
+           { script_pop_lex_mode(closure); }
+       ;
+
+extern_name_list_body:
+         string
+           { script_add_extern(closure, $1.value, $1.length); }
+       | extern_name_list_body string
+           { script_add_extern(closure, $2.value, $2.length); }
+       | extern_name_list_body ',' string
+           { script_add_extern(closure, $3.value, $3.length); }
+       ;
+
+/* A list of input file names.  */
 input_list:
          input_list_element
        | input_list opt_comma input_list_element
        ;
 
+/* An input file name.  */
 input_list_element:
-         STRING
-           { script_add_file(closure, $1); }
+         string
+           { script_add_file(closure, $1.value, $1.length); }
+       | '-' STRING
+           { script_add_library(closure, $2.value, $2.length); }
        | AS_NEEDED
            { script_start_as_needed(closure); }
          '(' input_list ')'
            { script_end_as_needed(closure); }
        ;
 
+/* Commands in a SECTIONS block.  */
+sections_block:
+         sections_block section_block_cmd
+       | /* empty */
+       ;
+
+/* A command which may appear within a SECTIONS block.  */
+section_block_cmd:
+         ENTRY '(' string ')'
+           { script_set_entry(closure, $3.value, $3.length); }
+       | assignment end
+       | ASSERT_K '(' parse_exp ',' string ')'
+           { script_add_assertion(closure, $3, $5.value, $5.length); }
+       | INCLUDE string
+           { script_include_directive(PARSING_SECTIONS_BLOCK, closure,
+                                      $2.value, $2.length); }
+       | string section_header
+           { script_start_output_section(closure, $1.value, $1.length, &$2); }
+         '{' section_cmds '}' section_trailer
+           { script_finish_output_section(closure, &$7); }
+       ;
+
+/* The header of an output section in a SECTIONS block--everything
+   after the name.  */
+section_header:
+           { script_push_lex_into_expression_mode(closure); }
+         opt_address_and_section_type opt_at opt_align opt_subalign
+           { script_pop_lex_mode(closure); }
+         opt_constraint
+           {
+             $$.address = $2.address;
+             $$.section_type = $2.section_type;
+             $$.load_address = $3;
+             $$.align = $4;
+             $$.subalign = $5;
+             $$.constraint = $7;
+           }
+       ;
+
+/* The optional address followed by the optional section type.  This
+   is a separate nonterminal to avoid a shift/reduce conflict on
+   '(' in section_header.  */
+
+opt_address_and_section_type:
+       ':'
+           {
+             $$.address = NULL;
+             $$.section_type = SCRIPT_SECTION_TYPE_NONE;
+           }
+       | '(' ')' ':'
+           {
+             $$.address = NULL;
+             $$.section_type = SCRIPT_SECTION_TYPE_NONE;
+           }
+       | exp ':'
+           {
+             $$.address = $1;
+             $$.section_type = SCRIPT_SECTION_TYPE_NONE;
+           }
+       | exp '(' ')' ':'
+           {
+             $$.address = $1;
+             $$.section_type = SCRIPT_SECTION_TYPE_NONE;
+           }
+       | '(' section_type ')' ':'
+           {
+             $$.address = NULL;
+             $$.section_type = $2;
+           }
+       | exp '(' section_type ')' ':'
+           {
+             $$.address = $1;
+             $$.section_type = $3;
+           }
+       ;
+
+/* We only support NOLOAD.  */
+section_type:
+       NOLOAD
+           { $$ = SCRIPT_SECTION_TYPE_NOLOAD; }
+       | DSECT
+           {
+             yyerror(closure, "DSECT section type is unsupported");
+             $$ = SCRIPT_SECTION_TYPE_DSECT;
+           }
+       | COPY
+           {
+             yyerror(closure, "COPY section type is unsupported");
+             $$ = SCRIPT_SECTION_TYPE_COPY;
+           }
+       | INFO
+           {
+             yyerror(closure, "INFO section type is unsupported");
+             $$ = SCRIPT_SECTION_TYPE_INFO;
+           }
+       | OVERLAY
+           {
+             yyerror(closure, "OVERLAY section type is unsupported");
+             $$ = SCRIPT_SECTION_TYPE_OVERLAY;
+           }
+       ;
+
+/* The address at which an output section should be loaded.  */
+opt_at:
+         /* empty */
+           { $$ = NULL; }
+       | AT '(' exp ')'
+           { $$ = $3; }
+       ;
+
+/* The alignment of an output section.  */
+opt_align:
+         /* empty */
+           { $$ = NULL; }
+       | ALIGN_K '(' exp ')'
+           { $$ = $3; }
+       ;
+
+/* The input section alignment within an output section.  */
+opt_subalign:
+         /* empty */
+           { $$ = NULL; }
+       | SUBALIGN '(' exp ')'
+           { $$ = $3; }
+       ;
+
+/* A section constraint.  */
+opt_constraint:
+         /* empty */
+           { $$ = CONSTRAINT_NONE; }
+       | ONLY_IF_RO
+           { $$ = CONSTRAINT_ONLY_IF_RO; }
+       | ONLY_IF_RW
+           { $$ = CONSTRAINT_ONLY_IF_RW; }
+       | SPECIAL
+           { $$ = CONSTRAINT_SPECIAL; }
+       ;
+
+/* The trailer of an output section in a SECTIONS block.  */
+section_trailer:
+         opt_memspec opt_at_memspec opt_phdr opt_fill opt_comma
+           {
+             $$.fill = $4;
+             $$.phdrs = $3;
+           }
+       ;
+
+/* A memory specification for an output section.  */
+opt_memspec:
+         '>' string
+           { script_set_section_region(closure, $2.value, $2.length, 1); }
+       | /* empty */
+       ;
+
+/* A memory specification for where to load an output section.  */
+opt_at_memspec:
+         AT '>' string
+           { script_set_section_region(closure, $3.value, $3.length, 0); }
+       | /* empty */
+       ;
+
+/* The program segment an output section should go into.  */
+opt_phdr:
+         opt_phdr ':' string
+           { $$ = script_string_list_push_back($1, $3.value, $3.length); }
+       | /* empty */
+           { $$ = NULL; }
+       ;
+
+/* The value to use to fill an output section.  FIXME: This does not
+   handle a string of arbitrary length.  */
+opt_fill:
+         '=' parse_exp
+           { $$ = $2; }
+       | /* empty */
+           { $$ = NULL; }
+       ;
+
+/* Commands which may appear within the description of an output
+   section in a SECTIONS block.  */
+section_cmds:
+         /* empty */
+       | section_cmds section_cmd
+       ;
+
+/* A command which may appear within the description of an output
+   section in a SECTIONS block.  */
+section_cmd:
+         assignment end
+       | input_section_spec
+       | data_length '(' parse_exp ')'
+           { script_add_data(closure, $1, $3); }
+       | ASSERT_K '(' parse_exp ',' string ')'
+           { script_add_assertion(closure, $3, $5.value, $5.length); }
+       | FILL '(' parse_exp ')'
+           { script_add_fill(closure, $3); }
+       | CONSTRUCTORS
+           {
+             /* The GNU linker uses CONSTRUCTORS for the a.out object
+                file format.  It does nothing when using ELF.  Since
+                some ELF linker scripts use it although it does
+                nothing, we accept it and ignore it.  */
+           }
+       | SORT_BY_NAME '(' CONSTRUCTORS ')'
+       | INCLUDE string
+           { script_include_directive(PARSING_SECTION_COMMANDS, closure,
+                                      $2.value, $2.length); }
+       | ';'
+       ;
+
+/* The length of data which may appear within the description of an
+   output section in a SECTIONS block.  */
+data_length:
+         QUAD
+           { $$ = QUAD; }
+       | SQUAD
+           { $$ = SQUAD; }
+       | LONG
+           { $$ = LONG; }
+       | SHORT
+           { $$ = SHORT; }
+       | BYTE
+           { $$ = BYTE; }
+       ;
+
+/* An input section specification.  This may appear within the
+   description of an output section in a SECTIONS block.  */
+input_section_spec:
+         input_section_no_keep
+           { script_add_input_section(closure, &$1, 0); }
+       | KEEP '(' input_section_no_keep ')'
+           { script_add_input_section(closure, &$3, 1); }
+       ;
+
+/* An input section specification within a KEEP clause.  */
+input_section_no_keep:
+         string
+           {
+             $$.file.name = $1;
+             $$.file.sort = SORT_WILDCARD_NONE;
+             $$.input_sections.sections = NULL;
+             $$.input_sections.exclude = NULL;
+           }
+       | wildcard_file '(' wildcard_sections ')'
+           {
+             $$.file = $1;
+             $$.input_sections = $3;
+           }
+       ;
+
+/* A wildcard file specification.  */
+wildcard_file:
+         wildcard_name
+           {
+             $$.name = $1;
+             $$.sort = SORT_WILDCARD_NONE;
+           }
+       | SORT_BY_NAME '(' wildcard_name ')'
+           {
+             $$.name = $3;
+             $$.sort = SORT_WILDCARD_BY_NAME;
+           }
+       ;
+
+/* A list of wild card section specifications.  */
+wildcard_sections:
+         wildcard_sections opt_comma wildcard_section
+           {
+             $$.sections = script_string_sort_list_add($1.sections, &$3);
+             $$.exclude = $1.exclude;
+           }
+       | wildcard_section
+           {
+             $$.sections = script_new_string_sort_list(&$1);
+             $$.exclude = NULL;
+           }
+       | wildcard_sections opt_comma EXCLUDE_FILE '(' exclude_names ')'
+           {
+             $$.sections = $1.sections;
+             $$.exclude = script_string_list_append($1.exclude, $5);
+           }
+       | EXCLUDE_FILE '(' exclude_names ')'
+           {
+             $$.sections = NULL;
+             $$.exclude = $3;
+           }
+       ;
+
+/* A single wild card specification.  */
+wildcard_section:
+         wildcard_name
+           {
+             $$.name = $1;
+             $$.sort = SORT_WILDCARD_NONE;
+           }
+       | SORT_BY_NAME '(' wildcard_section ')'
+           {
+             $$.name = $3.name;
+             switch ($3.sort)
+               {
+               case SORT_WILDCARD_NONE:
+                 $$.sort = SORT_WILDCARD_BY_NAME;
+                 break;
+               case SORT_WILDCARD_BY_NAME:
+               case SORT_WILDCARD_BY_NAME_BY_ALIGNMENT:
+                 break;
+               case SORT_WILDCARD_BY_ALIGNMENT:
+               case SORT_WILDCARD_BY_ALIGNMENT_BY_NAME:
+                 $$.sort = SORT_WILDCARD_BY_NAME_BY_ALIGNMENT;
+                 break;
+               default:
+                 abort();
+               }
+           }
+       | SORT_BY_ALIGNMENT '(' wildcard_section ')'
+           {
+             $$.name = $3.name;
+             switch ($3.sort)
+               {
+               case SORT_WILDCARD_NONE:
+                 $$.sort = SORT_WILDCARD_BY_ALIGNMENT;
+                 break;
+               case SORT_WILDCARD_BY_ALIGNMENT:
+               case SORT_WILDCARD_BY_ALIGNMENT_BY_NAME:
+                 break;
+               case SORT_WILDCARD_BY_NAME:
+               case SORT_WILDCARD_BY_NAME_BY_ALIGNMENT:
+                 $$.sort = SORT_WILDCARD_BY_ALIGNMENT_BY_NAME;
+                 break;
+               default:
+                 abort();
+               }
+           }
+       | SORT_BY_INIT_PRIORITY '(' wildcard_name ')'
+           {
+             $$.name = $3;
+             $$.sort = SORT_WILDCARD_BY_INIT_PRIORITY;
+           }
+       ;
+
+/* A list of file names to exclude.  */
+exclude_names:
+         exclude_names opt_comma wildcard_name
+           { $$ = script_string_list_push_back($1, $3.value, $3.length); }
+       | wildcard_name
+           { $$ = script_new_string_list($1.value, $1.length); }
+       ;
+
+/* A single wildcard name.  We recognize '*' and '?' specially since
+   they are expression tokens.  */
+wildcard_name:
+         string
+           { $$ = $1; }
+       | '*'
+           {
+             $$.value = "*";
+             $$.length = 1;
+           }
+       | '?'
+           {
+             $$.value = "?";
+             $$.length = 1;
+           }
+       ;
+
+/* A list of MEMORY definitions.  */
+memory_defs:
+         memory_defs opt_comma memory_def
+       | /* empty */
+       ;
+
+/* A single MEMORY definition.  */
+memory_def:
+         string memory_attr ':' memory_origin '=' parse_exp opt_comma memory_length '=' parse_exp
+         { script_add_memory(closure, $1.value, $1.length, $2, $6, $10); }
+       |
+         INCLUDE string
+         { script_include_directive(PARSING_MEMORY_DEF, closure,
+                                    $2.value, $2.length); }
+       |
+       ;
+
+/* The (optional) attributes of a MEMORY region.  */
+memory_attr:
+         '(' string ')'
+         { $$ = script_parse_memory_attr(closure, $2.value, $2.length, 0); }
+        | /* Inverted attributes. */
+         '(' '!' string ')'
+         { $$ = script_parse_memory_attr(closure, $3.value, $3.length, 1); }
+       | /* empty */
+           { $$ = 0; }
+       ;
+
+memory_origin:
+          ORIGIN
+       |
+         ORG
+       |
+         'o'
+       ;
+
+memory_length:
+          LENGTH
+       |
+         LEN
+       |
+         'l'
+       ;
+
+/* A list of program header definitions.  */
+phdrs_defs:
+         phdrs_defs phdr_def
+       | /* empty */
+       ;
+
+/* A program header definition.  */
+phdr_def:
+         string phdr_type phdr_info ';'
+           { script_add_phdr(closure, $1.value, $1.length, $2, &$3); }
+       ;
+
+/* A program header type.  The GNU linker accepts a general expression
+   here, but that would be a pain because we would have to dig into
+   the expression structure.  It's unlikely that anybody uses anything
+   other than a string or a number here, so that is all we expect.  */
+phdr_type:
+         string
+           { $$ = script_phdr_string_to_type(closure, $1.value, $1.length); }
+       | INTEGER
+           { $$ = $1; }
+       ;
+
+/* Additional information for a program header.  */
+phdr_info:
+         /* empty */
+           { memset(&$$, 0, sizeof(struct Phdr_info)); }
+       | string phdr_info
+           {
+             $$ = $2;
+             if ($1.length == 7 && strncmp($1.value, "FILEHDR", 7) == 0)
+               $$.includes_filehdr = 1;
+             else
+               yyerror(closure, "PHDRS syntax error");
+           }
+       | PHDRS phdr_info
+           {
+             $$ = $2;
+             $$.includes_phdrs = 1;
+           }
+       | string '(' INTEGER ')' phdr_info
+           {
+             $$ = $5;
+             if ($1.length == 5 && strncmp($1.value, "FLAGS", 5) == 0)
+               {
+                 $$.is_flags_valid = 1;
+                 $$.flags = $3;
+               }
+             else
+               yyerror(closure, "PHDRS syntax error");
+           }
+       | AT '(' parse_exp ')' phdr_info
+           {
+             $$ = $5;
+             $$.load_address = $3;
+           }
+       ;
+
+/* Set a symbol to a value.  */
+assignment:
+         string '=' parse_exp
+           { script_set_symbol(closure, $1.value, $1.length, $3, 0, 0); }
+       | string PLUSEQ parse_exp
+           {
+             Expression_ptr s = script_exp_string($1.value, $1.length);
+             Expression_ptr e = script_exp_binary_add(s, $3);
+             script_set_symbol(closure, $1.value, $1.length, e, 0, 0);
+           }
+       | string MINUSEQ parse_exp
+           {
+             Expression_ptr s = script_exp_string($1.value, $1.length);
+             Expression_ptr e = script_exp_binary_sub(s, $3);
+             script_set_symbol(closure, $1.value, $1.length, e, 0, 0);
+           }
+       | string MULTEQ parse_exp
+           {
+             Expression_ptr s = script_exp_string($1.value, $1.length);
+             Expression_ptr e = script_exp_binary_mult(s, $3);
+             script_set_symbol(closure, $1.value, $1.length, e, 0, 0);
+           }
+       | string DIVEQ parse_exp
+           {
+             Expression_ptr s = script_exp_string($1.value, $1.length);
+             Expression_ptr e = script_exp_binary_div(s, $3);
+             script_set_symbol(closure, $1.value, $1.length, e, 0, 0);
+           }
+       | string LSHIFTEQ parse_exp
+           {
+             Expression_ptr s = script_exp_string($1.value, $1.length);
+             Expression_ptr e = script_exp_binary_lshift(s, $3);
+             script_set_symbol(closure, $1.value, $1.length, e, 0, 0);
+           }
+       | string RSHIFTEQ parse_exp
+           {
+             Expression_ptr s = script_exp_string($1.value, $1.length);
+             Expression_ptr e = script_exp_binary_rshift(s, $3);
+             script_set_symbol(closure, $1.value, $1.length, e, 0, 0);
+           }
+       | string ANDEQ parse_exp
+           {
+             Expression_ptr s = script_exp_string($1.value, $1.length);
+             Expression_ptr e = script_exp_binary_bitwise_and(s, $3);
+             script_set_symbol(closure, $1.value, $1.length, e, 0, 0);
+           }
+       | string OREQ parse_exp
+           {
+             Expression_ptr s = script_exp_string($1.value, $1.length);
+             Expression_ptr e = script_exp_binary_bitwise_or(s, $3);
+             script_set_symbol(closure, $1.value, $1.length, e, 0, 0);
+           }
+       | HIDDEN '(' string '=' parse_exp ')'
+           { script_set_symbol(closure, $3.value, $3.length, $5, 0, 1); }
+       | PROVIDE '(' string '=' parse_exp ')'
+           { script_set_symbol(closure, $3.value, $3.length, $5, 1, 0); }
+       | PROVIDE_HIDDEN '(' string '=' parse_exp ')'
+           { script_set_symbol(closure, $3.value, $3.length, $5, 1, 1); }
+       ;
+
+/* Parse an expression, putting the lexer into the right mode.  */
+parse_exp:
+           { script_push_lex_into_expression_mode(closure); }
+         exp
+           {
+             script_pop_lex_mode(closure);
+             $$ = $2;
+           }
+       ;
+
+/* An expression.  */
+exp:
+         '(' exp ')'
+           { $$ = $2; }
+       | '-' exp %prec UNARY
+           { $$ = script_exp_unary_minus($2); }
+       | '!' exp %prec UNARY
+           { $$ = script_exp_unary_logical_not($2); }
+       | '~' exp %prec UNARY
+           { $$ = script_exp_unary_bitwise_not($2); }
+       | '+' exp %prec UNARY
+           { $$ = $2; }
+       | exp '*' exp
+           { $$ = script_exp_binary_mult($1, $3); }
+       | exp '/' exp
+           { $$ = script_exp_binary_div($1, $3); }
+       | exp '%' exp
+           { $$ = script_exp_binary_mod($1, $3); }
+       | exp '+' exp
+           { $$ = script_exp_binary_add($1, $3); }
+       | exp '-' exp
+           { $$ = script_exp_binary_sub($1, $3); }
+       | exp LSHIFT exp
+           { $$ = script_exp_binary_lshift($1, $3); }
+       | exp RSHIFT exp
+           { $$ = script_exp_binary_rshift($1, $3); }
+       | exp EQ exp
+           { $$ = script_exp_binary_eq($1, $3); }
+       | exp NE exp
+           { $$ = script_exp_binary_ne($1, $3); }
+       | exp LE exp
+           { $$ = script_exp_binary_le($1, $3); }
+       | exp GE exp
+           { $$ = script_exp_binary_ge($1, $3); }
+       | exp '<' exp
+           { $$ = script_exp_binary_lt($1, $3); }
+       | exp '>' exp
+           { $$ = script_exp_binary_gt($1, $3); }
+       | exp '&' exp
+           { $$ = script_exp_binary_bitwise_and($1, $3); }
+       | exp '^' exp
+           { $$ = script_exp_binary_bitwise_xor($1, $3); }
+       | exp '|' exp
+           { $$ = script_exp_binary_bitwise_or($1, $3); }
+       | exp ANDAND exp
+           { $$ = script_exp_binary_logical_and($1, $3); }
+       | exp OROR exp
+           { $$ = script_exp_binary_logical_or($1, $3); }
+       | exp '?' exp ':' exp
+           { $$ = script_exp_trinary_cond($1, $3, $5); }
+       | INTEGER
+           { $$ = script_exp_integer($1); }
+       | string
+           { $$ = script_symbol(closure, $1.value, $1.length); }
+       | MAX_K '(' exp ',' exp ')'
+           { $$ = script_exp_function_max($3, $5); }
+       | MIN_K '(' exp ',' exp ')'
+           { $$ = script_exp_function_min($3, $5); }
+       | DEFINED '(' string ')'
+           { $$ = script_exp_function_defined($3.value, $3.length); }
+       | SIZEOF_HEADERS
+           { $$ = script_exp_function_sizeof_headers(); }
+       | ALIGNOF '(' string ')'
+           { $$ = script_exp_function_alignof($3.value, $3.length); }
+       | SIZEOF '(' string ')'
+           { $$ = script_exp_function_sizeof($3.value, $3.length); }
+       | ADDR '(' string ')'
+           { $$ = script_exp_function_addr($3.value, $3.length); }
+       | LOADADDR '(' string ')'
+           { $$ = script_exp_function_loadaddr($3.value, $3.length); }
+       | ORIGIN '(' string ')'
+           { $$ = script_exp_function_origin(closure, $3.value, $3.length); }
+       | LENGTH '(' string ')'
+           { $$ = script_exp_function_length(closure, $3.value, $3.length); }
+       | CONSTANT '(' string ')'
+           { $$ = script_exp_function_constant($3.value, $3.length); }
+       | ABSOLUTE '(' exp ')'
+           { $$ = script_exp_function_absolute($3); }
+       | ALIGN_K '(' exp ')'
+           { $$ = script_exp_function_align(script_exp_string(".", 1), $3); }
+       | ALIGN_K '(' exp ',' exp ')'
+           { $$ = script_exp_function_align($3, $5); }
+       | BLOCK '(' exp ')'
+           { $$ = script_exp_function_align(script_exp_string(".", 1), $3); }
+       | DATA_SEGMENT_ALIGN '(' exp ',' exp ')'
+           {
+             script_data_segment_align(closure);
+             $$ = script_exp_function_data_segment_align($3, $5);
+           }
+       | DATA_SEGMENT_RELRO_END '(' exp ',' exp ')'
+           {
+             script_data_segment_relro_end(closure);
+             $$ = script_exp_function_data_segment_relro_end($3, $5);
+           }
+       | DATA_SEGMENT_END '(' exp ')'
+           { $$ = script_exp_function_data_segment_end($3); }
+       | SEGMENT_START '(' string ',' exp ')'
+           {
+             $$ = script_exp_function_segment_start($3.value, $3.length, $5);
+             /* We need to take note of any SEGMENT_START expressions
+                because they change the behaviour of -Ttext, -Tdata and
+                -Tbss options.  */
+             script_saw_segment_start_expression(closure);
+           }
+       | ASSERT_K '(' exp ',' string ')'
+           { $$ = script_exp_function_assert($3, $5.value, $5.length); }
+       ;
+
+/* Handle the --defsym option.  */
+defsym_expr:
+         string '=' parse_exp
+           { script_set_symbol(closure, $1.value, $1.length, $3, 0, 0); }
+       ;
+
+/* Handle the --dynamic-list option.  A dynamic list has the format
+   { sym1; sym2; extern "C++" { namespace::sym3 }; };
+   We store the symbol we see in the "local" list; that is where
+   Command_line::in_dynamic_list() will look to do its check.
+   TODO(csilvers): More than one of these brace-lists can appear, and
+   should just be merged and treated as a single list.  */
+dynamic_list_expr: dynamic_list_nodes ;
+
+dynamic_list_nodes:
+         dynamic_list_node
+       | dynamic_list_nodes dynamic_list_node
+        ;
+
+dynamic_list_node:
+          '{' vers_defns ';' '}' ';'
+            { script_new_vers_node (closure, NULL, $2); }
+        ;
+
+/* A version script.  */
+version_script:
+         vers_nodes
+       ;
+
+vers_nodes:
+         vers_node
+       | vers_nodes vers_node
+       ;
+
+vers_node:
+         '{' vers_tag '}' ';'
+           {
+             script_register_vers_node (closure, NULL, 0, $2, NULL);
+           }
+       | string '{' vers_tag '}' ';'
+           {
+             script_register_vers_node (closure, $1.value, $1.length, $3,
+                                        NULL);
+           }
+       | string '{' vers_tag '}' verdep ';'
+           {
+             script_register_vers_node (closure, $1.value, $1.length, $3, $5);
+           }
+       ;
+
+verdep:
+         string
+           {
+             $$ = script_add_vers_depend (closure, NULL, $1.value, $1.length);
+           }
+       | verdep string
+           {
+             $$ = script_add_vers_depend (closure, $1, $2.value, $2.length);
+           }
+       ;
+
+vers_tag:
+         /* empty */
+           { $$ = script_new_vers_node (closure, NULL, NULL); }
+       | vers_defns ';'
+           { $$ = script_new_vers_node (closure, $1, NULL); }
+       | GLOBAL ':' vers_defns ';'
+           { $$ = script_new_vers_node (closure, $3, NULL); }
+       | LOCAL ':' vers_defns ';'
+           { $$ = script_new_vers_node (closure, NULL, $3); }
+       | GLOBAL ':' vers_defns ';' LOCAL ':' vers_defns ';'
+           { $$ = script_new_vers_node (closure, $3, $7); }
+       ;
+
+/* Here is one of the rare places we care about the distinction
+   between STRING and QUOTED_STRING.  For QUOTED_STRING, we do exact
+   matching on the pattern, so we pass in true for the exact_match
+   parameter.  For STRING, we do glob matching and pass in false.  */
+vers_defns:
+         STRING
+           {
+             $$ = script_new_vers_pattern (closure, NULL, $1.value,
+                                           $1.length, 0);
+           }
+       | QUOTED_STRING
+           {
+             $$ = script_new_vers_pattern (closure, NULL, $1.value,
+                                           $1.length, 1);
+           }
+       | vers_defns ';' STRING
+           {
+             $$ = script_new_vers_pattern (closure, $1, $3.value,
+                                            $3.length, 0);
+           }
+       | vers_defns ';' QUOTED_STRING
+           {
+             $$ = script_new_vers_pattern (closure, $1, $3.value,
+                                            $3.length, 1);
+           }
+        | /* Push string on the language stack. */
+          EXTERN string '{'
+           { version_script_push_lang (closure, $2.value, $2.length); }
+         vers_defns opt_semicolon '}'
+           {
+             $$ = $5;
+             version_script_pop_lang(closure);
+           }
+        | /* Push string on the language stack.  This is more complicated
+             than the other cases because we need to merge the linked-list
+             state from the pre-EXTERN defns and the post-EXTERN defns.  */
+          vers_defns ';' EXTERN string '{'
+           { version_script_push_lang (closure, $4.value, $4.length); }
+         vers_defns opt_semicolon '}'
+           {
+             $$ = script_merge_expressions ($1, $7);
+             version_script_pop_lang(closure);
+           }
+        | EXTERN  // "extern" as a symbol name
+           {
+             $$ = script_new_vers_pattern (closure, NULL, "extern",
+                                           sizeof("extern") - 1, 1);
+           }
+       | vers_defns ';' EXTERN
+           {
+             $$ = script_new_vers_pattern (closure, $1, "extern",
+                                           sizeof("extern") - 1, 1);
+           }
+       ;
+
+/* A string can be either a STRING or a QUOTED_STRING.  Almost all the
+   time we don't care, and we use this rule.  */
+string:
+          STRING
+           { $$ = $1; }
+       | QUOTED_STRING
+           { $$ = $1; }
+       ;
+
+/* Some statements require a terminator, which may be a semicolon or a
+   comma.  */
+end:
+         ';'
+       | ','
+       ;
+
+/* An optional semicolon.  */
+opt_semicolon:
+         ';'
+       |  /* empty */
+       ;
+
+/* An optional comma.  */
 opt_comma:
          ','
        | /* empty */