summaryrefslogtreecommitdiffstats
path: root/grub-core/script/parser.y
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/script/parser.y')
-rw-r--r--grub-core/script/parser.y356
1 files changed, 356 insertions, 0 deletions
diff --git a/grub-core/script/parser.y b/grub-core/script/parser.y
new file mode 100644
index 0000000..4a18ab7
--- /dev/null
+++ b/grub-core/script/parser.y
@@ -0,0 +1,356 @@
+/* parser.y - The scripting parser. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2005,2006,2007,2008,2009,2010 Free Software Foundation, Inc.
+ *
+ * GRUB 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.
+ *
+ * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%{
+#include <grub/script_sh.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/i18n.h>
+
+#define YYFREE grub_free
+#define YYMALLOC grub_malloc
+#define YYLTYPE_IS_TRIVIAL 0
+#define YYENABLE_NLS 0
+
+#include "grub_script.tab.h"
+
+#pragma GCC diagnostic ignored "-Wmissing-declarations"
+
+%}
+
+%union {
+ struct grub_script_cmd *cmd;
+ struct grub_script_arglist *arglist;
+ struct grub_script_arg *arg;
+ char *string;
+ struct {
+ unsigned offset;
+ struct grub_script_mem *memory;
+ struct grub_script *scripts;
+ };
+}
+
+%token GRUB_PARSER_TOKEN_BAD
+%token GRUB_PARSER_TOKEN_EOF 0 "end-of-input"
+
+%token GRUB_PARSER_TOKEN_NEWLINE "\n"
+%token GRUB_PARSER_TOKEN_AND "&&"
+%token GRUB_PARSER_TOKEN_OR "||"
+%token GRUB_PARSER_TOKEN_SEMI2 ";;"
+%token GRUB_PARSER_TOKEN_PIPE "|"
+%token GRUB_PARSER_TOKEN_AMP "&"
+%token GRUB_PARSER_TOKEN_SEMI ";"
+%token GRUB_PARSER_TOKEN_LBR "{"
+%token GRUB_PARSER_TOKEN_RBR "}"
+%token GRUB_PARSER_TOKEN_NOT "!"
+%token GRUB_PARSER_TOKEN_LSQBR2 "["
+%token GRUB_PARSER_TOKEN_RSQBR2 "]"
+%token GRUB_PARSER_TOKEN_LT "<"
+%token GRUB_PARSER_TOKEN_GT ">"
+
+%token <arg> GRUB_PARSER_TOKEN_CASE "case"
+%token <arg> GRUB_PARSER_TOKEN_DO "do"
+%token <arg> GRUB_PARSER_TOKEN_DONE "done"
+%token <arg> GRUB_PARSER_TOKEN_ELIF "elif"
+%token <arg> GRUB_PARSER_TOKEN_ELSE "else"
+%token <arg> GRUB_PARSER_TOKEN_ESAC "esac"
+%token <arg> GRUB_PARSER_TOKEN_FI "fi"
+%token <arg> GRUB_PARSER_TOKEN_FOR "for"
+%token <arg> GRUB_PARSER_TOKEN_IF "if"
+%token <arg> GRUB_PARSER_TOKEN_IN "in"
+%token <arg> GRUB_PARSER_TOKEN_SELECT "select"
+%token <arg> GRUB_PARSER_TOKEN_THEN "then"
+%token <arg> GRUB_PARSER_TOKEN_UNTIL "until"
+%token <arg> GRUB_PARSER_TOKEN_WHILE "while"
+%token <arg> GRUB_PARSER_TOKEN_FUNCTION "function"
+%token <arg> GRUB_PARSER_TOKEN_NAME "name"
+%token <arg> GRUB_PARSER_TOKEN_WORD "word"
+
+%type <arg> block block0
+%type <arglist> word argument arguments0 arguments1
+
+%type <cmd> script_init script
+%type <cmd> grubcmd ifclause ifcmd forcmd whilecmd untilcmd
+%type <cmd> command commands1 statement
+
+%pure-parser
+%lex-param { struct grub_parser_param *state };
+%parse-param { struct grub_parser_param *state };
+
+%start script_init
+
+%%
+/* It should be possible to do this in a clean way... */
+script_init: { state->err = 0; } script { state->parsed = $2; state->err = 0; }
+;
+
+script: newlines0
+ {
+ $$ = 0;
+ }
+ | script statement delimiter newlines0
+ {
+ $$ = grub_script_append_cmd (state, $1, $2);
+ }
+ | error
+ {
+ $$ = 0;
+ yyerror (state, N_("Incorrect command"));
+ yyerrok;
+ }
+;
+
+newlines0: /* Empty */ | newlines1 ;
+newlines1: newlines0 "\n" ;
+
+delimiter: ";"
+ | "\n"
+;
+delimiters0: /* Empty */ | delimiters1 ;
+delimiters1: delimiter
+ | delimiters1 "\n"
+;
+
+word: GRUB_PARSER_TOKEN_NAME { $$ = grub_script_add_arglist (state, 0, $1); }
+ | GRUB_PARSER_TOKEN_WORD { $$ = grub_script_add_arglist (state, 0, $1); }
+;
+
+statement: command { $$ = $1; }
+ | function { $$ = 0; }
+;
+
+argument : "case" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "do" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "done" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "elif" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "else" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "esac" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "fi" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "for" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "if" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "in" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "select" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "then" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "until" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "while" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | "function" { $$ = grub_script_add_arglist (state, 0, $1); }
+ | word { $$ = $1; }
+;
+
+/*
+ Block parameter is passed to commands in two forms: as unparsed
+ string and as pre-parsed grub_script object. Passing as grub_script
+ object makes memory management difficult, because:
+
+ (1) Command may want to keep a reference to grub_script objects for
+ later use, so script framework may not free the grub_script
+ object after command completes.
+
+ (2) Command may get called multiple times with same grub_script
+ object under loops, so we should not let command implementation
+ to free the grub_script object.
+
+ To solve above problems, we rely on reference counting for
+ grub_script objects. Commands that want to keep the grub_script
+ object must take a reference to it.
+
+ Other complexity comes with arbitrary nesting of grub_script
+ objects: a grub_script object may have commands with several block
+ parameters, and each block parameter may further contain multiple
+ block parameters nested. We use temporary variable, state->scripts
+ to collect nested child scripts (that are linked by siblings and
+ children members), and will build grub_scripts tree from bottom.
+ */
+block: "{"
+ {
+ grub_script_lexer_ref (state->lexerstate);
+ $<offset>$ = grub_script_lexer_record_start (state);
+ $<memory>$ = grub_script_mem_record (state);
+
+ /* save currently known scripts. */
+ $<scripts>$ = state->scripts;
+ state->scripts = 0;
+ }
+ commands1 delimiters0 "}"
+ {
+ char *p;
+ struct grub_script_mem *memory;
+ struct grub_script *s = $<scripts>2;
+
+ memory = grub_script_mem_record_stop (state, $<memory>2);
+ if ((p = grub_script_lexer_record_stop (state, $<offset>2)))
+ *grub_strrchr (p, '}') = '\0';
+
+ $$ = grub_script_arg_add (state, 0, GRUB_SCRIPT_ARG_TYPE_BLOCK, p);
+ if (! $$ || ! ($$->script = grub_script_create ($3, memory)))
+ grub_script_mem_free (memory);
+
+ else {
+ /* attach nested scripts to $$->script as children */
+ $$->script->children = state->scripts;
+
+ /* restore old scripts; append $$->script to siblings. */
+ state->scripts = $<scripts>2 ?: $$->script;
+ if (s) {
+ while (s->next_siblings)
+ s = s->next_siblings;
+ s->next_siblings = $$->script;
+ }
+ }
+
+ grub_script_lexer_deref (state->lexerstate);
+ }
+;
+block0: /* Empty */ { $$ = 0; }
+ | block { $$ = $1; }
+;
+
+arguments0: /* Empty */ { $$ = 0; }
+ | arguments1 { $$ = $1; }
+;
+arguments1: argument arguments0
+ {
+ if ($1 && $2)
+ {
+ $1->next = $2;
+ $1->argcount += $2->argcount;
+ $2->argcount = 0;
+ }
+ $$ = $1;
+ }
+;
+
+grubcmd: word arguments0 block0
+ {
+ struct grub_script_arglist *x = $2;
+
+ if ($3)
+ x = grub_script_add_arglist (state, $2, $3);
+
+ if ($1 && x) {
+ $1->next = x;
+ $1->argcount += x->argcount;
+ x->argcount = 0;
+ }
+ $$ = grub_script_create_cmdline (state, $1);
+ }
+;
+
+/* A single command. */
+command: grubcmd { $$ = $1; }
+ | ifcmd { $$ = $1; }
+ | forcmd { $$ = $1; }
+ | whilecmd { $$ = $1; }
+ | untilcmd { $$ = $1; }
+;
+
+/* A list of commands. */
+commands1: newlines0 command
+ {
+ $$ = grub_script_append_cmd (state, 0, $2);
+ }
+ | commands1 delimiters1 command
+ {
+ $$ = grub_script_append_cmd (state, $1, $3);
+ }
+;
+
+function: "function" "name"
+ {
+ grub_script_lexer_ref (state->lexerstate);
+ state->func_mem = grub_script_mem_record (state);
+
+ $<scripts>$ = state->scripts;
+ state->scripts = 0;
+ }
+ newlines0 "{" commands1 delimiters1 "}"
+ {
+ struct grub_script *script;
+ state->func_mem = grub_script_mem_record_stop (state,
+ state->func_mem);
+ script = grub_script_create ($6, state->func_mem);
+ if (! script)
+ grub_script_mem_free (state->func_mem);
+ else {
+ script->children = state->scripts;
+ if (!grub_script_function_create ($2, script))
+ grub_script_free (script);
+ }
+
+ state->scripts = $<scripts>3;
+ grub_script_lexer_deref (state->lexerstate);
+ }
+;
+
+ifcmd: "if"
+ {
+ grub_script_lexer_ref (state->lexerstate);
+ }
+ ifclause "fi"
+ {
+ $$ = $3;
+ grub_script_lexer_deref (state->lexerstate);
+ }
+;
+ifclause: commands1 delimiters1 "then" commands1 delimiters1
+ {
+ $$ = grub_script_create_cmdif (state, $1, $4, 0);
+ }
+ | commands1 delimiters1 "then" commands1 delimiters1 "else" commands1 delimiters1
+ {
+ $$ = grub_script_create_cmdif (state, $1, $4, $7);
+ }
+ | commands1 delimiters1 "then" commands1 delimiters1 "elif" ifclause
+ {
+ $$ = grub_script_create_cmdif (state, $1, $4, $7);
+ }
+;
+
+forcmd: "for" "name"
+ {
+ grub_script_lexer_ref (state->lexerstate);
+ }
+ "in" arguments0 delimiters1 "do" commands1 delimiters1 "done"
+ {
+ $$ = grub_script_create_cmdfor (state, $2, $5, $8);
+ grub_script_lexer_deref (state->lexerstate);
+ }
+;
+
+whilecmd: "while"
+ {
+ grub_script_lexer_ref (state->lexerstate);
+ }
+ commands1 delimiters1 "do" commands1 delimiters1 "done"
+ {
+ $$ = grub_script_create_cmdwhile (state, $3, $6, 0);
+ grub_script_lexer_deref (state->lexerstate);
+ }
+;
+
+untilcmd: "until"
+ {
+ grub_script_lexer_ref (state->lexerstate);
+ }
+ commands1 delimiters1 "do" commands1 delimiters1 "done"
+ {
+ $$ = grub_script_create_cmdwhile (state, $3, $6, 1);
+ grub_script_lexer_deref (state->lexerstate);
+ }
+;