customBashShell / src / shell-grammar.y
shell-grammar.y
Raw
/*
 * Grammar for csh-like shell
 *
 * Updated Summer 2020
 *
 * Developed by Godmar Back for CS 3214 Fall 2009
 * Virginia Tech.
 *
 * This is based on an assignment as an undergraduate in 1993 
 * as an undergraduate student at Technische Universitaet Berlin.
 *
 * Known bugs: leaks memory when parse errors occur.
 */
%{
#include <stdio.h>
#include <stdlib.h>
#define YYDEBUG	1
int yydebug;
void yyerror(const char *msg);
int yylex(void);

/*
 * Error messages, csh-style
 */
#define MISRED	"Missing name for redirect."
#define INVNUL  "Invalid null command."
#define AMBINP  "Ambiguous input redirect."
#define AMBOUT  "Ambiguous output redirect."

#include "shell-ast.h"
#include <obstack.h>
#include <assert.h>

#define obstack_chunk_alloc malloc
#define obstack_chunk_free free

struct cmd_helper {
    struct obstack words;   /* an obstack of char * to collect argv */
    char *iored_input;
    char *iored_output;
    bool append_to_output;
    bool redirect_stderr;
    struct list_elem elem;
};

struct pipe_helper {
    struct list commands;
};

static struct pipe_helper *
init_pipe()
{
    struct pipe_helper * pipe = calloc(1, sizeof *pipe);
    list_init(&pipe->commands);
    return pipe;
}

/* Initialize cmd_helper and, optionally, set first argv */
static struct cmd_helper *
init_cmd(char *firstcmd, 
         char *iored_input, char *iored_output, 
         bool append_to_output, bool include_stderr)
{
    struct cmd_helper * cmd = malloc(sizeof *cmd);
    obstack_init(&cmd->words);
    if (firstcmd)
        obstack_ptr_grow(&cmd->words, firstcmd);

    cmd->iored_output = iored_output;
    cmd->iored_input = iored_input;
    cmd->append_to_output = append_to_output;
    cmd->redirect_stderr = include_stderr;
    return cmd;
}

/* print error message */
static void p_error(char *msg);

/* Convert cmd_helper to ast_command.
 * Ensures NULL-terminated argv[] array
 */
static struct ast_command * 
make_ast_command(struct cmd_helper *cmd)
{
    obstack_ptr_grow(&cmd->words, NULL);

    int sz = obstack_object_size(&cmd->words);
    char **argv = malloc(sz);
    memcpy(argv, obstack_finish(&cmd->words), sz);
    obstack_free(&cmd->words, NULL);

    if (*argv == NULL) {
        free(argv);
        return NULL; 
    }

    return ast_command_create(argv, cmd->redirect_stderr);
}

static bool
add_to_pipeline(struct pipe_helper *pipe,
                struct cmd_helper *cmd,
                bool redirect_stderr)
{
    if (!list_empty(&pipe->commands)) {
        struct cmd_helper * last;
        last = list_entry(list_back(&pipe->commands), 
                          struct cmd_helper, elem);
        /* Error: 'ls >x | wc' */
        if (last->iored_output) { p_error(AMBOUT); return false; }
        last->redirect_stderr = redirect_stderr;

        /* Error: 'ls | <x wc' */
        if (cmd->iored_input) { p_error(AMBINP); return false; }
    }

    int sz = obstack_object_size(&cmd->words);
    if (sz == 0) { p_error(INVNUL); return false; }

    list_push_back(&pipe->commands, &cmd->elem);
    return true;
}

/* Called by parser when command line is complete */
static void cmdline_complete(struct ast_command_line *);

/* work-around for bug in flex 2.31 and later */
static void yyunput (int c,char *buf_ptr  ) __attribute__((unused));

%}

/* LALR stack types */
%union {
  struct cmd_helper *command;
  struct pipe_helper *pipe;
  struct ast_pipeline *ast_pipe;
  struct ast_command_line *cmdline;
  char *word;
}

/* Nonterminals */
%type <command> input output
%type <command> command
%type <pipe> pipeline
%type <ast_pipe> ast_pipeline
%type <cmdline> cmd_list

/* Terminals */
%token <word> WORD
%token GREATER_GREATER GREATER_AMPERSAND PIPE_AMPERSAND

%%
cmd_line: cmd_list { cmdline_complete($1); }

cmd_list:	/* Null Command */ { $$ = ast_command_line_create_empty(); }
|		ast_pipeline { 
            $$ = ast_command_line_create($1);
        } 
|		cmd_list ';'
|		cmd_list '&' {
            $$ = $1;
            if (!list_empty(&$1->pipes)) {
                struct ast_pipeline * last;
                last = list_entry(list_back(&$1->pipes),
                                  struct ast_pipeline, elem);
                last->bg_job = true;
            }
        }
|		cmd_list ';' ast_pipeline	{ 
            $$ = $1;
            list_push_back(&$$->pipes, &$3->elem);
        }
|		cmd_list '&' ast_pipeline	{ 
            if (!list_empty(&$1->pipes)) {
                struct ast_pipeline * last;
                last = list_entry(list_back(&$1->pipes),
                                  struct ast_pipeline, elem);
                last->bg_job = true;
            }

            $$ = $1;
            list_push_back(&$$->pipes, &$3->elem);
        }

ast_pipeline: pipeline {
            struct pipe_helper * pipe = $1;
            assert (!list_empty(&pipe->commands));
            struct cmd_helper * first;
            first = list_entry(list_front(&pipe->commands), struct cmd_helper, elem);
            struct cmd_helper * last;
            last = list_entry(list_back(&pipe->commands), struct cmd_helper, elem);

            $$ = ast_pipeline_create(
                first->iored_input,
                last->iored_output,
                last->append_to_output
            );
            for (struct list_elem * e = list_begin(&pipe->commands);
                                    e != list_end(&pipe->commands);) {
                struct cmd_helper * cmd = list_entry(e, struct cmd_helper, elem);
                ast_pipeline_add_command($$, make_ast_command(cmd));
                e = list_remove(e);
                free(cmd);
            }
            free(pipe);
        }

pipeline: command {
            $$ = init_pipe();
            if (!add_to_pipeline($$, $1, false))
                YYABORT;
		}
|		pipeline '|' command {
            if (!add_to_pipeline($1, $3, false))
                YYABORT;
            $$ = $1;
		}
|		pipeline PIPE_AMPERSAND command {
            if (!add_to_pipeline($1, $3, true))
                YYABORT;
            $$ = $1;
		}
|		'|' error 	   { p_error(INVNUL); YYABORT; }
|		pipeline '|' error { p_error(INVNUL); YYABORT; }

command:   WORD { 
            $$ = init_cmd($1, NULL, NULL, false, false);
        }
|		input   
|		output
|		command WORD {
            $$ = $1;
            obstack_ptr_grow(&$$->words, $2);
		}
|		command input {
            obstack_free(&$2->words, NULL);
            /* Error: ambiguous redirect 'a <b <c' */
            if ($1->iored_input)   { p_error(AMBINP); YYABORT; }
            $$ = $1; 
            $$->iored_input = $2->iored_input;
            free($2);
		}
|		command output {
            obstack_free(&$2->words, NULL);
            /* Error: ambiguous redirect 'a >b >c' */
            if ($1->iored_output) { p_error(AMBOUT); YYABORT; }
            $$ = $1; 
            $$->iored_output = $2->iored_output;
            $$->append_to_output = $2->append_to_output;
            $$->redirect_stderr = $2->redirect_stderr;
            free($2);
		}

input:	'<' WORD { 
            $$ = init_cmd(NULL, $2, NULL, false, false);
        }
|		'<' error	  { p_error(MISRED); YYABORT; }

output:	'>' WORD { 
            $$ = init_cmd(NULL, NULL, $2, false, false);
        }
|		GREATER_AMPERSAND WORD { 
            $$ = init_cmd(NULL, NULL, $2, false, true);
        }
|		GREATER_GREATER WORD { 
            $$ = init_cmd(NULL, NULL, $2, true, false);
        }
		/* Error: missing redirect */
|		'>' error 	  { p_error(MISRED); YYABORT; }
|		GREATER_GREATER error { p_error(MISRED); YYABORT; }

%%
static char * inputline;    /* currently processed input line */
#define YY_INPUT(buf,result,max_size) \
    { \
        result = *inputline ? (buf[0] = *inputline++, 1) : YY_NULL; \
    }

#define YY_NO_INPUT
#include "lex.yy.c"

static void
p_error(char *msg) 
{ 
    /* print error */
    fprintf(stderr, "%s\n", msg); 
}

extern int yyparse (void);

/* do not use default error handling since errors are handled above. */
void 
yyerror(const char *msg) { }

static struct ast_command_line * commandline;
static void cmdline_complete(struct ast_command_line *cline)
{
    commandline = cline;
}

/* 
 * parse a commandline.
 */
struct ast_command_line *
ast_parse_command_line(char * line)
{
    inputline = line;
    commandline = NULL;

    int error = yyparse();

    return error ? NULL : commandline;
}