%{
/* This file is part of the Project Athena Zephyr Notification System.
 * It is one of the source files comprising zwgc, the Zephyr WindowGram
 * client.
 *
 *      Created by:     Marc Horowitz <marc@athena.mit.edu>
 *
 *      $Id: 30f4088d5ca5f679e328f7a84d64b968ac5a42fa $
 *
 *      Copyright (c) 1989 by the Massachusetts Institute of Technology.
 *      For copying and distribution information, see the file
 *      "mit-copyright.h".
 */

#include <sysdep.h>

#if (!defined(lint) && !defined(SABER))
static const char rcsid_parser_y[] = "$Id: 30f4088d5ca5f679e328f7a84d64b968ac5a42fa $";
#endif

#include <zephyr/mit-copyright.h>

/* Saber-C suppressions because yacc loses */

/*SUPPRESS 288*/
/*SUPPRESS 287*/

#include <stdio.h>
#include "lexer.h"
#include "parser.h"
#include "node.h"
#include "zwgc.h"

static void yyerror(char *);

/*
 * the_program - local variable used to communicate the program's node
 *               representation from the program action to the parse_file
 *               function.
 */

static Node *the_program;
%}

%union{
    char *text;
    struct _Node *node;
}

%start program

%token  ERROR
%token  <text>    VARNAME VARREF STRING SHOW

%token  APPENDPORT BUFFER BREAK CLOSEINPUT CLOSEOUTPUT
%token  CLOSEPORT CASE CLEARBUF DEFAULT DISPLAY DO DOWNCASE
%token  ELSE ELSEIF ENDCASE ENDIF ENDWHILE EXEC EXECPORT EXIT
%token  FIELDS GET GETENV IF INPUTPORT LANY LBREAK LSPAN
%token  MATCH NOOP NOT OUTPUTPORT PRINT PROTECT VERBATIM PUT RANY RBREAK
%token  RSPAN SET SUBSTITUTE THEN UPCASE WHILE ZVAR STYLESTRIP

%type <node> expr varname string
%type <node> exprlist comma_exprlist varnamelist
%type <node> statement statements program elseparts elseifparts
%type <node> match matchlist

%left '|'
%left '&'
%left EQ NEQ REGEQ REGNEQ
%left '+'
%left '!'

%%

/*
 * A program is simply a list of statements: (may be NULL if no statements...)
 */
program : statements
        { the_program = reverse_list_of_nodes($1);
	  $$ = the_program; }
        ;

varname : VARNAME
       { $$ = node_create_string_constant(VARNAME_OPCODE, $1); }
        ;

string : STRING
       { $$ = node_create_string_constant(STRING_CONSTANT_OPCODE, $1); }
       ;

expr : '(' expr ')'
        { $$ = $2; }

     | string
        { $$ = $1; }
     | VARREF
       { $$ = node_create_string_constant(VARREF_OPCODE, $1); }

     | '!' expr
        { $$ = node_create_unary(NOT_OPCODE, $2); }

     | expr '+' expr
        { $$ = node_create_binary(PLUS_OPCODE, $1, $3); }
     | expr '|' expr                             /* note "or" == '|' */
        { $$ = node_create_binary(OR_OPCODE, $1, $3); }
     | expr '&' expr                             /* note "and" == '&' */
        { $$ = node_create_binary(AND_OPCODE, $1, $3); }
     | expr EQ expr
        { $$ = node_create_binary(EQ_OPCODE, $1, $3); }
     | expr NEQ expr
        { $$ = node_create_binary(NEQ_OPCODE, $1, $3); }
     | expr REGEQ expr
        { $$ = node_create_binary(REGEQ_OPCODE, $1, $3); }
     | expr REGNEQ expr
        { $$ = node_create_binary(REGNEQ_OPCODE, $1, $3); }

     | BUFFER '(' ')'
        { $$ = node_create_noary(BUFFER_OPCODE); }

     | SUBSTITUTE '(' expr ')'
        { $$ = node_create_unary(SUBSTITUTE_OPCODE, $3); }
     | PROTECT '(' expr ')'
	{ $$ = node_create_unary(PROTECT_OPCODE, $3); }
     | VERBATIM '(' expr ')'
	{ $$ = node_create_unary(VERBATIM_OPCODE, $3); }
     | GETENV '(' expr ')'
        { $$ = node_create_unary(GETENV_OPCODE, $3); }
     | UPCASE '(' expr ')'
        { $$ = node_create_unary(UPCASE_OPCODE, $3); }
     | DOWNCASE '(' expr ')'
        { $$ = node_create_unary(DOWNCASE_OPCODE, $3); }
     | ZVAR '(' expr ')'
        { $$ = node_create_unary(ZVAR_OPCODE, $3); }
     | GET '(' expr ')'
        { $$ = node_create_unary(GET_OPCODE, $3); }
     | STYLESTRIP '(' expr ')'
        { $$ = node_create_unary(STYLESTRIP_OPCODE, $3); }

     | LANY '(' expr ',' expr ')'
        { $$ = node_create_binary(LANY_OPCODE, $3, $5 ); }
     | RANY '(' expr ',' expr ')'
        { $$ = node_create_binary(RANY_OPCODE, $3, $5 ); }
     | LBREAK '(' expr ',' expr ')'
        { $$ = node_create_binary(LBREAK_OPCODE, $3, $5 ); }
     | RBREAK '(' expr ',' expr ')'
        { $$ = node_create_binary(RBREAK_OPCODE, $3, $5 ); }
     | LSPAN '(' expr ',' expr ')'
        { $$ = node_create_binary(LSPAN_OPCODE, $3, $5 ); }
     | RSPAN '(' expr ',' expr ')'
        { $$ = node_create_binary(RSPAN_OPCODE, $3, $5 ); }
     ;

statement : NOOP
              { $$ = node_create_noary(NOOP_OPCODE); }
          | SET varname '=' expr
              { $$ = node_create_binary(SET_OPCODE, $2, $4); }
	  | FIELDS varnamelist
              { $$ = node_create_unary(FIELDS_OPCODE,
				       reverse_list_of_nodes($2)); }

         /*
  	  * Output to & control of output buffer statements:
	  */
 	  | PRINT exprlist
              { $$ = node_create_unary(PRINT_OPCODE,
				       reverse_list_of_nodes($2)); }
	  | SHOW
              { $$ = node_create_unary(PRINT_OPCODE,
		       node_create_unary(SUBSTITUTE_OPCODE,
			 node_create_string_constant(STRING_CONSTANT_OPCODE,
						     $1))); }
          | CLEARBUF
	      { $$ = node_create_noary(CLEARBUF_OPCODE); }

          /*
	   * Statements to manage ports:
	   */
          | APPENDPORT expr expr
              { $$ = node_create_binary(APPENDPORT_OPCODE, $2, $3); }
          | EXECPORT expr expr exprlist
              { $3->next = reverse_list_of_nodes($4);
		$$ = node_create_binary(EXECPORT_OPCODE, $2, $3); }
          | INPUTPORT expr expr
              { $$ = node_create_binary(INPUTPORT_OPCODE, $2, $3); }
          | OUTPUTPORT expr expr
              { $$ = node_create_binary(OUTPUTPORT_OPCODE, $2, $3); }
	  | PUT expr exprlist
              { $$ = node_create_binary(PUT_OPCODE, $2,
					reverse_list_of_nodes($3)); }
	  | PUT
              { $$ = node_create_binary(PUT_OPCODE, 0, 0); }
          | CLOSEINPUT expr
              { $$ = node_create_unary(CLOSEINPUT_OPCODE, $2); }
          | CLOSEOUTPUT expr
              { $$ = node_create_unary(CLOSEOUTPUT_OPCODE, $2); }
          | CLOSEPORT expr
              { $$ = node_create_unary(CLOSEPORT_OPCODE, $2); }

          /*
	   * Statements to run subprocesses without I/O to them:
	   */
	  | EXEC expr exprlist
              { $2->next = reverse_list_of_nodes($3);
		$$ = node_create_unary(EXEC_OPCODE, $2); }

          /*
	   * Control statements:
	   */
          | IF expr THEN statements elseparts ENDIF
              { Node *n = node_create_binary(IF_OPCODE, $2,
					     reverse_list_of_nodes($4));
		n->next = $5;
		$$ = node_create_unary(IF_STMT_OPCODE, n); }
	  | CASE expr matchlist ENDCASE
              { $$ = node_create_binary(CASE_OPCODE, $2,
					reverse_list_of_nodes($3)); }
	  | WHILE expr DO statements ENDWHILE
              { $$ = node_create_binary(WHILE_OPCODE, $2,
					reverse_list_of_nodes($4)); }
          | BREAK
              { $$ = node_create_noary(BREAK_OPCODE); }
          | EXIT
              { $$ = node_create_noary(EXIT_OPCODE); }
	  ;

elseparts : elseifparts
                { $$ = reverse_list_of_nodes($1); }
          | elseifparts ELSE statements
                { $$ = node_create_binary(ELSE_OPCODE, 0,
					  reverse_list_of_nodes($3));
		  $$->next = $1;
	          $$ = reverse_list_of_nodes($$); }
          ;

/* elseifparts needs to be reversed before using... */
elseifparts : /* empty */
                { $$ = 0; }
            | elseifparts ELSEIF expr THEN statements
                { $$ = node_create_binary(ELSEIF_OPCODE, $3,
					  reverse_list_of_nodes($5));
		  $$->next = $1; }
            ;

match : MATCH comma_exprlist statements
                { $$ = node_create_binary(MATCHLIST_OPCODE,
					  reverse_list_of_nodes($2),
					  reverse_list_of_nodes($3)); }
      | DEFAULT statements
                { $$ = node_create_binary(DEFAULT_OPCODE, 0,
					  reverse_list_of_nodes($2)); }
      ;

/*
 * Various lists of non-terminals like expr's and varname's.  Each is
 * built up as a linked list using the nodes' next fields.  To prevent
 * Yacc stack overflow on long lists, these are put on the linked list
 * BACKWARDS.  The user of these must first call reverse_list_of_nodes
 * on one of these before using it.  All except comma_exprlist
 * allow 0 elements on the list in which case their value is NULL.
 * (comma_exprlist requires at least one element)
 */

exprlist : /* empty */
             { $$ = 0; }
	 | exprlist expr
             { $$ = $2;
	       $$->next = $1; }
	 ;

comma_exprlist : expr
                 { $$ = $1; }
               | comma_exprlist ',' expr
                 { $$ = $3;
		   $$->next = $1; }
	       ;

varnamelist : /* empty */
             { $$ = 0; }
            | varnamelist varname
             { $$ = $2;
	       $$->next = $1; }
	    ;

matchlist : /* empty */
                { $$ = 0; }
          | matchlist match
                { $$ = $2;
		  $$->next = $1; }
          ;

statements : /* empty */
        { $$ = 0; }
           | statements statement
        { $$ = $2;
	  $$->next = $1; }
           ;

%%

/*
 * error_occured - Set to true when a parse error is reported.  If it is false
 *                 at the time a parse error is reported, a message is
 *                 printed on stderr.  See report_parse_error for more
 *                 details.
 */

static int error_occured = 0;

/*
 *  Parser-Lexer Internal Routine:
 *
 *    void report_parse_error(char *error_message, int line_number)
 *        Modifies: error_occured, stderr
 *        Effects: This routine is called to report a parser or lexer
 *                 error.  Error_message is the error message and line_number
 *                 the line number it occured on.  The reported error message
 *                 is of the form "....<error_message> on line <line #>.\n".
 *                 This routine sets error_occured (local to parser.y) to
 *                 true.  If it was previously false, the error message
 *                 is reported to the user via stderr. 
 */

void
report_parse_error(char *error_message,
		   int line_number)
{
    if (error_occured)
      return;
    error_occured = 1;

    fprintf(stderr, "zwgc: error in description file: %s on line %d.\n",
	    error_message, line_number);
    fflush(stderr);
}

/*
 *  yyerror - internal routine - used by yacc to report syntax errors and
 *            stack overflow errors.
 */
 
static void yyerror(char *message)
{
    report_parse_error(message, yylineno);
}

/*
 *    struct _Node *parse_file(FILE *input_file)
 *        Requires: input_file is opened for reading, no pointers to
 *                  existing nodes will ever be dereferened.
 *        Modifies: *input_file, stderr, all existing nodes
 *        Effects: First this routine destroys all nodes.  Then it parses
 *                 input_file as a zwgc description langauge file.  If
 *                 an error is encountered, an error message is printed
 *                 on stderr and NULL is returned.  If no error is
 *                 encountered, a pointer to the node representation of
 *                 the parsed program is returned, suitable for passing to
 *                 exec.c.  Note that NULL will also be returned for a
 *                 empty file & is a valid program.  Either way, input_file
 *                 is closed before this routine returns.
 */

struct _Node *
parse_file(FILE *input_file)
{
    the_program = NULL;
    error_occured = 0;
    node_DestroyAllNodes();

    lex_open(input_file);
    yyparse();
    fclose(input_file);

    if (error_occured) {
	node_DestroyAllNodes();
	the_program = NULL;
    }

#ifdef DEBUG
    if (zwgc_debug) {
	printf("****************************************************************************\n");
	node_display(the_program);
	printf("****************************************************************************\n");
    }
#endif
    
    return(the_program);
}
