package ppdz;
import java_cup.runtime.*;
import java.io.*;


parser code {:
    
    boolean greska=false; // da li je prijavljena greska u toku prevodjenja
        
    // glavni program, kao metod klase parser
    public static void main(String args[]) throws Exception {
    		FileReader r=null;
    		try {
    		   r = new FileReader(args[0]);
    		} catch(Exception e) {
    		    System.err.println("Greska pri otvaranju ulaznog fajla");
    		    System.exit(1);
    		}
    		Yylex skener = new Yylex(r);
    		parser p = new parser(skener);
        /******************************************************************
        * Staticka metoda init tabele simbola sluzi za pocetno pravljenje 
        * tabele simbola, odnosno "universe" opsega.
        ******************************************************************/
    	  Tab.init();
    		Symbol s = p.parse();  //pocetak parsiranja
        /*****************************************************************
        * Po zavrsetku analize, pozivom metode dump(), vrsimo ispis 
        * sadrzaja tabele simbola.
        *****************************************************************/ 
    		Tab.dump();
    		try {
    		   if ( !p.greska && !Tab.greska && !Code.greska ) 
    		      Code.write(new FileOutputStream(args[1]));
    		} catch(Exception e) {
    		    System.err.println("Greska pri kreiranju objektnog fajla");
    		}
    }
  
    /***********************************************************************
    * slede redefinisani metodi za prijavu gresaka radi izmene teksta poruke
    ***********************************************************************/ 
    public void report_fatal_error(String message, Object   info) throws java.lang.Exception {
      done_parsing();
      report_error(message, info);
    }
  
    public void syntax_error(Symbol cur_token) {
        report_error("\nSintaksna greska", cur_token);
    }
  
    public void unrecovered_syntax_error(Symbol cur_token) throws java.lang.Exception {
        report_fatal_error("Fatalna greska, parsiranje se ne moze nastaviti", cur_token);
    }

  public void report_error(String message, Object info)
    {
      greska=true;
      System.err.print(message);
      System.err.flush();
      if (info instanceof Symbol)
	        System.err.println(" u liniji " + ((Symbol)info).left);
      else System.err.println("");
    }

:}

action code {:
    /*****************************************************************
    * Sledece promenljive ce biti vidljive iz akcija u svim 
    * smenama. var_type sluzi za cuvanje cvora Struct koji 
    * odgovara tipu na pocetku deklaracije grupe promenljivih.
    * method_type cuva povratni tip tekuceg metoda za semanticke provere  
    * kod return. promenljiva returnExists ima vrednost true ako telo metoda ima 
    * return iskaz.
    *****************************************************************/ 
    Struct var_type=Tab.noType;
    Struct method_type=Tab.noType;
    boolean returnExists=false; 
:}

terminal PLUS, MINUS, TIMES, DIV;
terminal EQUAL, SEMI, COMMA, LPAREN;
terminal RPAREN, LSQUARE, RSQUARE, LBRACE, RBRACE, CLASS;
terminal PRINT, RETURN, VOID, FINAL, NEW;
terminal Integer NUMBER;
terminal Character CHARCONST;
terminal String IDENT;

non terminal program, declaration_list, method_declaration_list;
non terminal var_dec, rhs, var_list, var_part, const_dec;
non terminal method_dec, form_pars, stmt_list, parameter;
non terminal statement, act_pars, local_var_list;
non terminal addop, mulop;
non terminal parameter_list, declaration_part;
non terminal Struct type;
non terminal Struct expr, expr_list, term_list, term, factor, factor_list;
non terminal Obj designator, ident_expr_list, prog_id, return_type_ident;

/*****************************************************************
* o predstavlja Obj cvor glavne klase. Na kraju analize programa
* se unutrasnji opseg zatvara, a njegovi cvorovi vezuju za
* cvor glavne klase, tj. programa. 
*****************************************************************/ 
program ::= CLASS prog_id:o declaration_list LBRACE method_declaration_list RBRACE
	            {: o.locals=Tab.topScope.locals;
	               Code.dataSize=Tab.topScope.nVars;
                 Tab.closeScope();
		          :};

/*****************************************************************
* Kada prepoznamo glavni program, u tabelu simbola ubacujemo 
* cvor koji mu odgovara i otvaramo novi opseg u koji cemo ubacivati 
* simbole glavnog programa.
*****************************************************************/ 
prog_id ::= IDENT: id
                {: RESULT = Tab.insert(Obj.Prog,id,idleft,Tab.noType);
                   Tab.openScope(); 
                :};

declaration_list ::= declaration_list declaration_part
			   |
			   /* epsilon */;

declaration_part ::= const_dec 
	               |
	               var_dec;
			
const_dec ::= FINAL type IDENT EQUAL rhs SEMI;

/*****************************************************************
* Kada parser prepozna da je naisao na identifikator tipa,
* neterminalu type se dodeljuje odgovarajuci Struct cvor.
* U deklarativnom delu za neterminale smo deklarisali da je 
* type tipa Struct, pa mu objekat tog tipa dodeljujemo koristeci
* predefinisanu promenljivu RESULT, koja implicitno oznacava 
* vrednost leve strane svake smene (left hand side).
*****************************************************************/ 
type ::= IDENT: id
             {: Obj novi=Tab.find(id, idleft);
                if (novi.kind == Obj.Type )
                   RESULT = novi.type;
                else {
                   parser.report_error("Greska u liniji "+idleft+" ("+id+") nije tip",null);
                   RESULT=Tab.noType;
               }
             :} 
;

rhs ::= NUMBER 
	  | 
	  CHARCONST; 

/*****************************************************************
* Da bi nam i pri uparivanju neterminala var_list bio dostupan
* tip grupe promenljivih koje se deklarisu, sacuvacemo ga u 
* promenljivoj var_type.
*****************************************************************/ 
var_dec ::= type: t {: var_type=t; :} var_list SEMI;

var_list ::= var_list COMMA var_part 
		 | 
 		 var_part;

/*****************************************************************
* Kada u deklaraciji dodjemo do imena pojedinacne promenljive,
* ubacujemo je u tabelu simbola (cak i kada je tip noType, da bismo u
* oporavku izbegli greske nedeklarisane promenljive)
*****************************************************************/ 
var_part ::= IDENT: id 
                  {: Tab.insert(Obj.Var, id, idleft, var_type);
                  :} 
		 |
		 IDENT: id LSQUARE RSQUARE
                  {: Tab.insert(Obj.Var, id, idleft, new Struct(Struct.Arr, var_type));
                  :};

local_var_list ::= local_var_list var_dec
	         	   |
	             /* epsilon */;

method_declaration_list ::= method_declaration_list method_dec
		      	    |
		      	    /* epsilon */;

return_type_ident ::= type:t IDENT:id
		             {: method_type = t;
		                RESULT = Tab.insert( Obj.Meth, id, idleft, t); 
                    Tab.openScope(); 
		             :} 
		        | 
		        VOID IDENT:id
                  {: method_type=Tab.noType; 
		                RESULT = Tab.insert( Obj.Meth, id, idleft, Tab.noType); 
                    Tab.openScope(); 
                  :};

/*****************************************************************
* Na kraju obrade metoda proveravamo da li se radi o metodu
* koji nije void, a u njegovom telu nema return iskaza. 
* Ako je to slucaj, prijavljuje se greska.
*****************************************************************/ 
method_dec ::= return_type_ident:o LPAREN form_pars RPAREN local_var_list LBRACE 
                 {: // generisanje koda
                    if (o.name.equals("main")) {
												Code.mainPc = Code.pc;
												if (method_type!=Tab.noType) parser.report_error("Greska u liniji "+oleft+": metod main mora biti void",null);
										}
                    o.level = 0; // za metode, broj parametara, ovde ignorisemo parametre
                    o.adr = Code.pc;
                    Code.put(Code.enter);
                    Code.put(o.level);
                    Code.put(Tab.topScope.nVars);
                 :}
               stmt_list RBRACE
		             {:
		                  if (method_type!=Tab.noType && !returnExists) { 
			                    parser.report_error("Greska u liniji "+oleft+": Metod mora imati return iskaz jer nije deklarisan sa void",null);
			                }
                      returnExists=false;
                      o.locals = Tab.topScope.locals; Tab.closeScope();
                      // generisanje koda
                      if (method_type==Tab.noType) {
                          Code.put(Code.exit); Code.put(Code.return_);
                      } else { // end of function reached without a return statement
                          Code.put(Code.trap); Code.put(1);
                      }
	               :}
           ;
           
form_pars ::= parameter_list
	        |
	        /* epsilon */;
		
parameter_list ::= parameter_list COMMA parameter
	             |
	             parameter;

parameter ::= type IDENT
	  	  | 
		  type IDENT LSQUARE RSQUARE;


stmt_list ::= stmt_list statement
	        |
	        /* epsilon */;

/*****************************************************************
* Pri prepoznavanju return iskaza postavljamo promenljivu 
* returnExists. Ako metoda koja nije void ima prazan return 
* iskaz, prijavljuje se greska. Ako void metoda im return 
* iskaz koji vraca vrednost, prijavljuje se greska.
*****************************************************************/
statement ::= designator:o EQUAL expr:t SEMI 
                 {: if ( t.assignableTo(o.type) )
                         Code.store(o);
                    else
                        parser.report_error("Greska u liniji "+oleft+": nekompatibilni tipovi za dodelu",null);
                 :}
		      |
	        designator:o LPAREN RPAREN SEMI      
                    {: 
                       if (o.kind!=Obj.Meth )
                           parser.report_error("Greska u liniji "+oleft+": Ocekivan metod",null);
                       int dest_adr=o.adr-Code.pc; // racunanje relativne adrese 
                       Code.put(Code.call); 
                       Code.put2(dest_adr);
                       if (o.type!=Tab.noType )
                           Code.put(Code.pop); // rezultat poziva nece biti koriscen
                     :}
	        |
	        designator:o LPAREN act_pars RPAREN SEMI
                    {: // obrada stvarnih parametara nije implementirana
                       if (o.kind!=Obj.Meth )
                           parser.report_error("Greska u liniji "+oleft+": Ocekivan metod",null);
                       int dest_adr=o.adr-Code.pc; // racunanje relativne adrese 
                       Code.put(Code.call); 
                       Code.put2(dest_adr);
                       if (o.type!=Tab.noType )
                           Code.put(Code.pop); // rezultat poziva nece biti koriscen
                     :}
	        |
	        RETURN:dummy SEMI // dummy samo zbog broja linije, nema semant. vrednost
		         {: 
	               returnExists=true;
		             if (method_type!=Tab.noType)
			               parser.report_error("Greska u liniji "+dummyleft+": metod mora imati return iskaz sa izrazom jer nije deklarisan sa void",null);
	               // generisanje koda
	               Code.put(Code.exit);
	               Code.put(Code.return_);
	           :}
	        |
	        RETURN:dummy expr:t SEMI // dummy samo zbog broja linije, nema semant. vrednost
		         {: 
	               returnExists=true;
		             if (method_type==Tab.noType)
			                parser.report_error("Greska u liniji "+dummyleft+": metod ne sme imati return sa izrazom jer je deklarisan sa void",null);
	               if (!t.assignableTo(method_type))
	                    parser.report_error("Greska u liniji "+dummyleft+": tip izraza nekompatibilan sa deklaracijom metoda",null);
	               Code.put(Code.exit);
	               Code.put(Code.return_);
	           :}
	        |
	        PRINT LPAREN expr:t RPAREN SEMI
	           {:  if (t != Tab.intType && t!=Tab.charType ) 
	                   parser.report_error("Greska u liniji "+tleft+": tip izraza mora biti int ili char",null);
	               // generisanje koda
	               if (t == Tab.intType) {
	                   Code.loadConst(5); // sirina ispisa na e-stek, expr je vec na e-steku
	                   Code.put(Code.print);
	               } else {
	                   Code.loadConst(1); // sirina ispisa na e-stek, expr je vec na e-steku
	                   Code.put(Code.bprint);
	               }
	           :};

designator ::= ident_expr_list:o 
             {: RESULT=o; :};

/*****************************************************************
* Kada u telu metode detektujemo koriscenje nekog imena, 
* upotrebom metode find, proveravamo da li se objekat koji 
* odgovara tom imenu nalazi u tabeli simbola. Ako se ne nalazi
* prijavljujemo gresku. Ako se radi o koriscenju niza, moramo 
* proveriti i da li je objekat u tabeli simbola tipa niza.
*****************************************************************/ 
ident_expr_list ::= ident_expr_list: o  LSQUARE 
                       {: if (o.type.kind==Struct.Arr) {
                              // generisanje koda 
                              Code.load(o);
                              // dalje prenosimo element niza
                              RESULT=new Obj(Obj.Elem, "", o.type.elemType);
                          }
                       :}
                    expr RSQUARE
                       {: if (o.type.kind!=Struct.Arr) {
                              parser.report_error("Greska u liniji "+oleft+": Ocekivan niz",null);
                              RESULT=o;
                          }
                       :}
	              |
	              IDENT: id
                       {:
    		                   Obj ob=Tab.find(id, idleft); // find prijavljuje gresku u slucaju da ne nadje
                           if (ob != Tab.noObj) 
                               System.out.println("Pretraga "+idleft+" ("+id+"), nadjeno "+ob);
                           RESULT=ob; 
                       :}
                ;

addop ::= PLUS 
	    | 
	    MINUS;

mulop ::= TIMES 
	    | 
       DIV;

act_pars ::= expr_list;

expr_list ::= expr_list COMMA expr {: Code.put(Code.pop); :} // stvarni parametri nisu implementirani, pa da ne ostanu na steku
	        |
	        expr {: Code.put(Code.pop); :}; // stvarni parametri nisu implementirani, pa da ne ostanu na steku

expr ::= term_list:t {: RESULT=t; :} ;
		
term_list ::= term_list addop term
                    {: // nije implementirano
                       RESULT=Tab.noType; 
                    :}
	        |
	        term:t {: RESULT=t; :} ;

term ::= factor_list:t {: RESULT=t; :};

factor_list ::= factor_list:t mulop factor
                    {: // nije implementirano
                       RESULT=Tab.noType; 
                    :}
	          |
	          factor:t {: RESULT=t; :}
            |
            MINUS factor
                    {: // nije implementirano
                       RESULT=Tab.noType; 
                    :};

factor ::= designator:o LPAREN act_pars RPAREN   
                    {: // obrada stvarnih parametara nije implementirana
                       if (o.kind!=Obj.Meth )
                           parser.report_error("Greska u liniji "+oleft+": Ocekivan metod",null);
                       if (o.type==Tab.noType )
                           parser.report_error("Greska u liniji "+oleft+": Procedura pozvana kao funkcija",null);
                       int dest_adr=o.adr-Code.pc; // racunanje relativne adrese 
                       Code.put(Code.call); 
                       Code.put2(dest_adr);
                       RESULT=o.type;                     
                     :}
           |                                                                              
           designator:o 
                   {:  if ( o.kind!=Obj.Var && o.kind!=Obj.Elem )
                             parser.report_error("Greska u liniji "+oleft+": Ocekivana promenljiva ili element niza",null);
                       Code.load(o);
                       RESULT=o.type;
                   :}                          
           |
           designator:o LPAREN RPAREN      
                    {: if (o.kind!=Obj.Meth )
                           parser.report_error("Greska u liniji "+oleft+": Ocekivan metod",null);
                       if (o.type==Tab.noType )
                           parser.report_error("Greska u liniji "+oleft+": Procedura pozvana kao funkcija",null);
                       int dest_adr=o.adr-Code.pc; // racunanje relativne adrese
                       Code.put(Code.call);
                       Code.put2(dest_adr);
                       RESULT=o.type; 
                    :}
           |
           NUMBER:i {: Obj c = new Obj(Obj.Con, "", Tab.intType);
                       c.adr=i.intValue(); Code.load(c); 
                       RESULT=Tab.intType; 
                    :}           
           |
           CHARCONST:c 
                    {: Obj o = new Obj(Obj.Con, "", Tab.charType);
                       o.adr=c.charValue(); Code.load(o); 
                       RESULT=Tab.charType; 
                    :}
           |
           NEW type:t LSQUARE expr:t1 RSQUARE
                    {:  if ( t1 != Tab.intType)
                            parser.report_error("Greska u liniji "+tleft+": velicina niza mora biti tipa int",null);
                        Code.put(Code.newarray);
                        if ( t == Tab.charType ) Code.put(0); 
                        else Code.put(1);
                        RESULT=new Struct(Struct.Arr, t);
                    :}
           |
           LPAREN expr:t RPAREN {: RESULT=t; :} ; 
