/*
** parse.y -- the bison parser for lparse
** Copyright (C) 1999-2000 Tommi Syrjänen <Tommi.Syrjanen@hut.fi>
**  
** This program 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 2 of the License, or
** (at your option) any later version.
**  
** This program 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 this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**  
*/

%{

#include "../config.h"
#include "parsetree.h"
#include "debug.h"
#include "term.h"
#include "symbol.h"
#include "global.h"
#ifndef ERROR_H
#include "error.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

extern int yylex(void);
extern ParseNode *logic_program;

// Turn YYDEBUG on while debugging
#if DEBUG & DBG_PARSE
#define YYDEBUG 1
#else
#define YYDEBUG 0
#endif

//#define YYERROR_VERBOSE

long lineno = 1;
long lval1 = 0, lval2 = 0;
int return_symbol = 0;
int return_funcsymbol = 1;

extern char *yytext;

void yyerror(char *st)
{
  sys_data.num_errors++;
  fprintf(stderr, "%s: %s", error_file_and_line(lineno), st);
  if (strlen(yytext))
    fprintf(stderr, " before '%s'", yytext);
  fprintf(stderr, "\n");
}

%}

%union {
   char *s;
   long l;
   ParseNode *pn;
}

%token <l> NUMBER, WEIGHT, MINIMIZE, MAXIMIZE, HIDE
%token <s> FUNCTION, FUNCSYMBOL, IF, NOT, COMPUTE, IDENTIFIER,
%token <s> VARIABLE,  CONST,  STRING, EXTERNAL, SHOW
%token <s> LE, GE, EQ, NEQ, DOTS, LT, GT, ASSIGN, MOD, COMMAS
%type <pn> program, rules, statement, declaration, rule, head, tail, 
%type <pn> basic_tail, special_tail, special_head, literal, atom,
%type <pn> function, arguments, arglist, term, constant, range, expr,
%type <pn> literal_list, compute_list, compute_stmt, weight_def, nc_expr
%type <pn> basic_literal, headlist, condition_list, optimize_stmt

%type <l> models, bound


%left ASSIGN
%left '&', '|', '^'
%left EQ, LE, GE, LT, GT, NEQ
%left '+', '-'
%left '*', '/', MOD
%right UMINUS, '~'
%right '('
%left  ')'
%left ':'
%left ','


%%

program : rules
	{ logic_program = $1; }
	;

rules : rules rule '.'
      { $$ = new ParseNode(PT_RULES, NULL, $2, $1, lineno); }
      | rules statement '.'
      { $$ = new ParseNode(PT_RULES, NULL, $2, $1, lineno); }
      | rules error '.'
      { yyerrok; }
      |
      { $$ = NULL; }
      ;

statement : declaration
	  { $$ = $1; }
	  | compute_stmt
	  { $$ = $1; }
	  | optimize_stmt
	  { $$ = $1; }
	  ;

declaration : FUNCTION IDENTIFIER 
	    { function_table->Insert($2); $$ = NULL; free($2);}
	    | WEIGHT literal_list
	    { $$ = new ParseNode(PT_DECLARATION, NULL, NULL, $2, 
	                         lineno); }
	    | HIDE literal_list 
            { $$ = new ParseNode(PT_HIDE, NULL, $2, NULL, lineno); }
	    | HIDE
	    { $$ = NULL; sys_data.hide_all = 1; }
	    | CONST IDENTIFIER ASSIGN expr 
	    { numeric_constant_table->Insert($2);
              lval1 = $4->Eval();
              if (lval1 == ERROR_NUMBER) {
                 error(USR_ERR, "%s: Invalid constant declaration: ",
                       error_file_and_line(lineno));
                 fprintf(stderr, "\t\tconst %s = ", $2);
                 $4->PrintExpression();
                 fprintf(stderr, ".\n   The expression is not a"
    	                 " valid constant expression\n");
              }	 
              numeric_constant_table->SetValue($2, lval1);
	      free($2);
              delete $4;
              $$ = NULL; }
	    | CONST constant expr 
	    { yyerror("numeric constant declarations now need a '='");
	      $$ = NULL;
	      free($2);
              delete $3;
	    }
	    | CONST VARIABLE ASSIGN expr 
	    {
              error(USR_ERR, "%s: Invalid constant declaration: ",
                    error_file_and_line(lineno));
              fprintf(stderr, "\t\tconst %s = ", $2);
              $4->PrintExpression();
              fprintf(stderr, ".\n   The symbol '%s' is a variable, not"
	              " a constant\n", $2);
              $$ = NULL;
              free($2);
              delete $4;
            }
	    | EXTERNAL literal_list
	    { $$ = new ParseNode(PT_EXTERNAL, NULL, $2, NULL, lineno);
	    }
	    | SHOW literal_list
	    { $$ = new ParseNode(PT_SHOW, NULL, $2, NULL, lineno); }
	    ;

rule : head 
     { $$ = new ParseNode(PT_RULE, NULL, $1, NULL, lineno); }
     | head IF tail 
     { $$ = new ParseNode(PT_RULE, NULL, $1, $3, lineno); }
     | IF tail 
     { $$ = new ParseNode(PT_RULE, NULL, NULL, $2, lineno); }
     ;

head : atom
     { $$ = $1; }
     | special_head
     { $$ = $1; }
     | 
     { $$ = NULL; }
     ;

tail : special_tail ',' tail
     { $$ = new ParseNode(PT_TAIL, NULL, $1, $3, lineno); }
     | basic_tail
     { $$ = new ParseNode(PT_TAIL, NULL, $1, NULL, lineno); }
     | special_tail
     { $$ = new ParseNode(PT_TAIL, NULL, $1, NULL, lineno); }
     ;

basic_tail : basic_tail ',' literal
	   { $$ = new ParseNode(PT_BASIC_TAIL, NULL, $3, $1, lineno);}
	   | literal
	   { $$ = new ParseNode(PT_BASIC_TAIL, NULL, $1, NULL, lineno);}
	   | basic_tail ',' nc_expr
	   { $$ = new ParseNode(PT_BASIC_TAIL, NULL, $3, $1, lineno);
             if ($3->type == PT_CONSTANT) $3->type = PT_ATOM; }
	   | nc_expr
	   { $$ = new ParseNode(PT_BASIC_TAIL, NULL, $1, NULL,
                                lineno); }
	   ;

special_tail : bound  '[' basic_tail ']' bound
	     { $$ = new ParseNode(PT_SPECIAL_TAIL, NULL, NULL, $3,
  	                          lineno, $1, $5, 1); }
             | bound  '{' basic_tail '}' bound
	     { $$ = new ParseNode(PT_SPECIAL_TAIL, NULL, NULL, $3,
  	                          lineno, $1, $5); }
	     ;

special_head : bound '{' literal_list '}' bound
	     { $$ = new ParseNode(PT_SPECIAL_HEAD, NULL, NULL, $3,
  	                          lineno, $1, $5); }
	     | bound '[' literal_list ']' bound
	     { $$ = new ParseNode(PT_SPECIAL_HEAD, NULL, NULL, $3,
  	                          lineno, $1, $5,1); }
             | headlist
	     { 
	        if (!sys_data.dlp_semantics) {
                  $$ = new ParseNode(PT_SPECIAL_HEAD, NULL, NULL, $1, 
  	                             lineno, 1, 1); 
                } else {
                  $$ = new ParseNode(PT_SPECIAL_HEAD, NULL, NULL, $1, 
  	                             lineno, 0, ERROR_NUMBER); 
                }
             } 
	     ;

headlist : headlist '|' atom
	 { $$ = new ParseNode(PT_LITERAL_LIST, NULL, $3, $1, lineno); }
	 | atom
	 { $$ = new ParseNode(PT_LITERAL_LIST, NULL, $1, NULL,
	   lineno);  }
	 ;

basic_literal : atom
	{ $$ = $1; }
	| NOT atom
	{ $2->l2.negative = 1; $$ = $2; }
	| atom ':' condition_list
	{ $$ = new ParseNode(PT_CONDITION, NULL, $1, $3, lineno); }
	| NOT atom ':' condition_list
	{ $$ = new ParseNode(PT_CONDITION, NULL, $2, $4, lineno);
	  $$->l2.negative = 1;}
	;

literal : basic_literal
	{ $$ = $1; }
	| basic_literal ASSIGN weight_def
	{ $$ = $1; if ($1->type != PT_CONDITION) $$->left = $3;
                   else $$->left->left = $3; }
	;

condition_list : condition_list ':' atom
	 { $$ = new ParseNode(PT_LITERAL_LIST, NULL, $3, $1, lineno); }
	 | atom
	 { $$ = new ParseNode(PT_LITERAL_LIST, NULL, $1, NULL,
	   lineno);  }
	 | nc_expr
	 { $$ = new ParseNode(PT_LITERAL_LIST, NULL, $1, NULL,
	   lineno);  }
	 | condition_list ':' nc_expr
	 { $$ = new ParseNode(PT_LITERAL_LIST, NULL, $3, $1, lineno); }
	 ;


weight_def : expr
	   { $$ =$1; }
	   ;

atom : IDENTIFIER '(' arguments ')'
     { $$ = new ParseNode(PT_ATOM, $1, NULL, $3, lineno); }
     | IDENTIFIER
     { $$ = new ParseNode(PT_ATOM, $1, NULL, NULL, lineno); } 
     ;

bound : 
       expr
      { lval1 = $1->Eval();
        if (lval1 == ERROR_NUMBER) {
            error(USR_ERR, "%s: the bound expression: ", 
	          error_file_and_line(lineno));
            fprintf(stderr, "\t\t");
            $1->PrintExpression();
            fprintf(stderr, "\n   is not a valid constant valued"
                    " expression.\n");
            fprintf(stderr, "   It is also possible that you "
                    "have accidentally used an upper case\n"
                    "   initial letter in a predicate name\n");
          }
        $$ = lval1; 
        delete $1;}
      |
      { $$ = ERROR_NUMBER; }
      ;


function : FUNCSYMBOL '(' arglist ')'
	 { $$ = new ParseNode(PT_FUNCTION, $1, NULL, $3, lineno);}
	 | NOT FUNCSYMBOL '(' arglist ')'
	 { $$ = new ParseNode(PT_FUNCTION, $2, NULL, $4, lineno);
           $$->l2.negative = 1; }
	 ;

arguments : arglist
	  { $$ = new ParseNode(PT_ARGUMENTS, NULL, $1, NULL, lineno);}
	  | arguments ';' arglist
	  { $$ = new ParseNode(PT_ARGUMENTS, NULL, $3, $1, lineno); }
	  ;

arglist : arglist ','  term
	{ $$ = new ParseNode(PT_ARGLIST, NULL, $3, $1, lineno); }
	| term
	{ $$ = new ParseNode(PT_ARGLIST, NULL, $1, NULL , lineno); }
	;

term : range
     { $$ = $1; }
     | expr
     { lval1 = $1->Eval();
       if (lval1 != ERROR_NUMBER) {
           $$ = new ParseNode(PT_NUMBER, NULL, NULL, NULL,
                lineno,lval1);
           delete $1;
       } else {
         $$ = $1;
       }
     }
     | atom 
     { $$ = $1;
       if ($$->right == NULL) {
         lval1 = numeric_constant_table->Lookup($$->sval);
         if (lval1 != ERROR_NUMBER) {
            $$->type = PT_NUMBER;
            $$->l1.val = lval1;
         } else {
          $$->type = PT_CONSTANT;
         }
       }
     }
     | atom '|' expr
     {
      /* crude workaround for situations like a | 1, where 'a' is
	 actually a numeric constant */
       if ( $1->right ) {
	error(USR_ERR, "%s: parse error before '|'\n",
              error_file_and_line(lineno));
       }
       $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_OR);
       lval1 = numeric_constant_table->Lookup($1->sval);
       if (lval1 != ERROR_NUMBER) {
         delete $$->left;
         $$->left = new ParseNode(PT_NUMBER, NULL, NULL, NULL, lineno,
                                  lval1);
       } else {
         $1->type = PT_CONSTANT;
       }
     }
     ;

constant :
	 IDENTIFIER
	{ lval1 = numeric_constant_table->Lookup($1);
	  if (lval1 == ERROR_NUMBER)  
		$$ = new ParseNode(PT_CONSTANT, $1, NULL, NULL,
                                   lineno);
	  else {
	     $$ = new ParseNode(PT_NUMBER, NULL, NULL, NULL, lineno,lval1);
             free($1); }
	}
	| NUMBER
	{ $$ = new ParseNode(PT_NUMBER, NULL, NULL, NULL, lineno,
	 $1);}
	| STRING
	{ $$ = new ParseNode(PT_CONSTANT, $1, NULL, NULL, lineno); }	
	;

range : expr DOTS expr
      { lval1 = $1->Eval();
        lval2 = $3->Eval();

	if ($1->type != PT_NUMBER)
            lval1 = ERROR_NUMBER;
        if ($3->type != PT_NUMBER)
            lval2 = ERROR_NUMBER;	
        if ((lval1 == ERROR_NUMBER) || (lval2 == ERROR_NUMBER)) {
            error(USR_ERR, "%s: invalid range definition: ", 
                  error_file_and_line(lineno));
            fprintf(stderr, "\t");
	    $1->PrintExpression();
            fprintf(stderr, " .. ");
            $3->PrintExpression();
            fprintf(stderr, "\n");
	    if (lval1 == ERROR_NUMBER) {
              fprintf(stderr, "   expression '");
              $1->PrintExpression();
              fprintf(stderr, "' is not a constant valued expression.\n");
            } 
	    if (lval2 == ERROR_NUMBER) {
              fprintf(stderr, "   expression '");
              $3->PrintExpression();
              fprintf(stderr, "' is not a constant valued expression.\n");
            } 

	}
        delete $1;
        delete $3;
  	$$ = new ParseNode(PT_RANGE, NULL, NULL, NULL, lineno, lval1,
                           lval2); } 
      | expr COMMAS expr
      {
        $$ = NULL;
	yyerror("conjunctive ranges are no longer supported, "
	        "please use conditions instead");
        /* lval1 = $1->Eval();
        lval2 = $3->Eval();
  	$$ = new ParseNode(PT_RANGE, NULL, NULL, NULL, lineno, lval1,
                           lval2, 1); */} 
      ;



expr : '(' expr ')'
     { $$ = $2; $$->i1.paren =1}
     | '|' expr '|'     
     { $$ = new ParseNode(PT_EXPR, NULL, NULL, $2, lineno,
	                  FUN_ABS); $$->i1.paren = 1;}

     | '-' expr %prec UMINUS
     { $$ = new ParseNode(PT_EXPR, NULL, NULL, $2 , lineno,
                          FUN_MINUS);
       $$->left = new ParseNode(PT_NUMBER, NULL, NULL, NULL, lineno, 0);}
     | expr '+' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_PLUS); }
     | expr '-' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_MINUS); }
     | expr '*' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_TIMES); }
     | expr '/' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_DIV); }
     | expr MOD expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_MOD); }
     | expr EQ expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_EQ); }
     | expr NEQ expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_NEQ); }
     | expr LT expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_LT); }
     | expr LE expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_LE); }
     | expr GT expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_GT); }
     | expr GE expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_GE); }
     | '~' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $2 ,NULL , lineno,
                          FUN_NOT);
       $$->right = new ParseNode(PT_NUMBER, NULL, NULL, NULL, lineno,
                                 0);}
     | expr '&' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_AND); }
     | expr '|' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_OR); }
     | expr '^' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_XOR); }
     | VARIABLE ASSIGN expr
     { $$ = new ParseNode(PT_EXPR, NULL,  NULL, $3, lineno,
                          FUN_ASSIGN);
       $$->left = new ParseNode(PT_VARIABLE, $1, NULL, NULL,
                                lineno);} 
     | function
     { $$ = $1; }
     | constant
     { $$ = $1; }
     | VARIABLE
     { $$ = new ParseNode(PT_VARIABLE, $1, NULL, NULL, lineno); }
     ;

nc_expr : '(' expr ')'
     { $$ = $2; }
     | '|' expr '|'     
     { $$ = new ParseNode(PT_EXPR, NULL, NULL, $2, lineno,
	                  FUN_ABS);} 
     | '-' expr %prec UMINUS
     { $$ = new ParseNode(PT_EXPR, NULL, NULL, $2 , lineno,
                          FUN_MINUS);
       $$->left = new ParseNode(PT_NUMBER, NULL, NULL, NULL, lineno, 0);}
     | expr '+' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_PLUS); }
     | expr '-' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_MINUS); }
     | expr '*' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_TIMES); }
     | expr '/' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_DIV); }
     | expr MOD expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_MOD); }
     | expr EQ expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_EQ); }
     | expr NEQ expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_NEQ); }
     | expr LT expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_LT); }
     | expr LE expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_LE); }
     | expr GT expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_GT); }
     | expr GE expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_GE); }
     | '~' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $2 ,NULL , lineno,
                          FUN_NOT);
       $$->right = new ParseNode(PT_NUMBER, NULL, NULL, NULL, lineno,
                                 0);}
     | expr '&' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_AND); }
     | expr '|' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_OR); }
     | expr '^' expr
     { $$ = new ParseNode(PT_EXPR, NULL, $1, $3, lineno, FUN_XOR); }
     | VARIABLE ASSIGN expr
     { $$ = new ParseNode(PT_EXPR, NULL,  NULL, $3, lineno,
                          FUN_ASSIGN);
       $$->left = new ParseNode(PT_VARIABLE, $1, NULL, NULL,
                                lineno);} 
     | function
     { $$ = $1; }
     | VARIABLE
     { $$ = new ParseNode(PT_VARIABLE, $1, NULL, NULL, lineno); }
     ;

/* op	: '+'
	{ $$ = FUN_PLUS ; }
	| '-'
	{ $$ = FUN_MINUS ; }
	| MOD
	{ $$ = FUN_MOD ; }
	| '/'
	{ $$ = FUN_DIV ; }
	| '*'
	{ $$ = FUN_TIMES ; }
	| compare_op
	{ $$ = $1; }
	; 

compare_op : EQ
	   { $$ = FUN_EQ ; }
	   | LE
	   { $$ = FUN_LE ; }
	   | LT
	   { $$ = FUN_LT ; }
	   | GE
	   { $$ = FUN_GE ; }
	   | GT
	   { $$ = FUN_GT ; }
	   | NEQ
	   { $$ = FUN_NEQ ; }
	   ;
*/
literal_list : literal_list ',' literal
	     { $$ = new ParseNode(PT_LITERAL_LIST, NULL, $3, $1, lineno);}
	     | literal
	     { $$ = new ParseNode(PT_LITERAL_LIST, NULL, $1, NULL, lineno);}
	     ;

compute_list : literal_list
	     { $$ = $1; }
	     |
	     { $$ = 0; }
	     ;


compute_stmt : COMPUTE models '{' compute_list '}'
	     { $$ = new ParseNode(PT_COMPUTE, NULL, NULL, $4,
	                          lineno, $2);}
             ;

optimize_stmt : MAXIMIZE '{' compute_list '}'
	      { $$ = new ParseNode(PT_MAXIMIZE, NULL, NULL, $3,
	                          lineno); }
	      | MINIMIZE '{' compute_list '}'
	      { $$ = new ParseNode(PT_MINIMIZE, NULL, NULL, $3,
	                          lineno); }
	      ;

models : expr
       {
         if ($1->type == PT_CONSTANT) {
           if (!strcmp($1->sval, "all")) {
             $$ = 0;
           } else {
             error(USR_ERR, "%s: invalid number of models. Use a "
                   "positive integer, a constant valued expression, "
                   "or \"all\"", 
                   error_file_and_line(lineno));
                   $$ = 1;
           }
         } else {
           $$ = $1->Eval();
           if ($$ == ERROR_NUMBER || $$ < 0) {
             error(USR_ERR, "%s: invalid number of models. Use a "
                  "positive integer, a constant valued expression, or "
	          "\"all\"", error_file_and_line(lineno));
           }
         }
         delete $1;
       }
       |
       { $$ = 1; }
       ;

