package G4ip;

import java.util.*;
import pizza.lang.Pair;
import G4ip.Form.*;
import G4ip.Token.*;

public class Token {
  public static final int AND = 0;
  public static final int OR  = 1;
  public static final int IMP = 2;
  public static final int EQU = 3;

  /** End-of-Input */
  public case EOI();             // end-of-input
  public case LPAREN();
  public case RPAREN();
  public case ID(String id);
  public case FALSE();
  public case BINOP(int op);     // binary operation
  public case COMMA();
}

/** A left-to-right, rightmost-derivation parser.<p> 
  * The following grammar is implemented:<p>
  * <dl>
  * <dd><u>Formula</u> ::= <u>Id</u>                             </dd>
  * <dd><code>| false</code>                                     </dd>
  * <dd><code>|</code> ( <u>Formula</u> )                        </dd>         
  * <dd><code>|</code> <u>Formula</u> <u>Binop</u> <u>Formula</u></dd>  
  * </dl><p>
  * <dl>
  * <dd><u>Id</u> is a string of lower case letters</dd>
  * </dl><p>
  * <dl>
  * <dd><u>Binop</u> is either <code>&, v, -></code> or <code><-></code></dd>
  * </dl><p>
  * <dl>
  * <dd><u>FormulaList</u> ::=  <em>empty</em></dd>
  * <dd><code>|</code> [ <u>FormulaList</u> ,]* <u>Formula</u></dd>
  * <dl><p>
  * The parser uses a stack where two actions are performed:
  * <dl>
  * <dd><b>shift</b> moves the next token to the top of the stack (getNextToken)</dd>
  * <dd><b>reduce</b> chooses a grammar rule, X -> A B C; pops A, B, C from the 
  *                   top of the stack and pushes X onto the stack  
  * </dl>
  * @author      Christian Urban
  */       
public class Parser {
  final Pair<String,Token> keywords[] = 
          { new Pair("(",    LPAREN()),
	    new Pair(")",    RPAREN()),
	    new Pair(",",    COMMA()),
	    new Pair("&",    BINOP(Token.AND)),
	    new Pair("v ",   BINOP(Token.OR)),
	    new Pair("->",   BINOP(Token.IMP)),
            new Pair("<->",  BINOP(Token.EQU)),
	    new Pair("false",FALSE()) };
  String in;     // string to be parsed
  int index;     // position of the current input
  Stack stack;   // stack for the left-to-right parser 'LR(0)'
  
  public Parser(String init_in){
    index = 0;
    in = init_in;
    stack = new Stack();
  }

  /** tokens are: identifiers<code>( ) , & v -> <-> false</code> 
    *             and <code>EOI</code> <em>(end of input)</em> 
    */
  public Token getNextToken() throws Exception {
    while ( index < in.length() && Character.isSpace(in.charAt(index)) ) 
          { index++; }                                //delete  white-spaces  
    if (index == in.length()) { return EOI(); }       //end-of-string  
    for (int i=0;i<keywords.length;i++)               //keywords
      { if (in.startsWith(keywords[i].fst,index)) {
	   index=index+keywords[i].fst.length();
	   return keywords[i].snd;
        }
      }
    if (Character.isLowerCase(in.charAt(index)) || Character.isDigit(in.charAt(index))) 
      {     //reads identifiers
      String s = ""; 
      while ( index < in.length() && 
              (    Character.isLowerCase(in.charAt(index))
                || Character.isDigit(in.charAt(index))
              ) 
            ) 
	{ s=s.concat(in.substring(index,index+1)); 
          index++;
        }
      return ID(s);
    }
    throw new Exception("Syntax error at: '" + in.charAt(index)  + "'");   
               // no match at all: probably an unknown character
    return null;
  }

  /** Implements the grammar rules. */
  public void reduce()
    { boolean again = false;
      /*   ID  -> Atm(string)  */
      if (stack.size() > 0 && 
	 (stack.elementAt(stack.size()-1) instanceof ID))
	    { ID id = (ID)stack.pop();
	      stack.push(Atm(id.id));
	      again = true;
	    } 
      /*  FALSE -> False()   */
      if (stack.size() > 0 && 
	  (stack.elementAt(stack.size()-1) instanceof FALSE))
	    { stack.pop();
	      stack.push(False());
	      again = true;
	    } 
      /*   ( Formula )  -> Formula  */ 
      if (stack.size() > 2 &&
	  (stack.elementAt(stack.size()-3) instanceof LPAREN) &&
	  (stack.elementAt(stack.size()-2) instanceof Form)   &&
	  (stack.elementAt(stack.size()-1) instanceof RPAREN))
	    {  stack.pop();
               Form form = (Form)stack.pop(); 
               stack.pop();
               stack.push(form);
               again = true;
            }   
      /*   Formula BINOP Formula  -> Formula  */
      if (stack.size() > 2 &&
	  (stack.elementAt(stack.size()-3) instanceof Form)  &&
	  (stack.elementAt(stack.size()-2) instanceof BINOP) &&
	  (stack.elementAt(stack.size()-1) instanceof Form))
	    {  Form  c2 = (Form)stack.pop();
               BINOP op = (BINOP)stack.pop();
               Form  c1 = (Form)stack.pop(); 
               switch(op.op) {
	       case Token.AND: stack.push(new And(c1,c2)); again = true;break;
               case Token.OR:  stack.push(new Or(c1,c2));  again = true;break; 
	       case Token.IMP: stack.push(new Imp(c1,c2)); again = true;break;
               case Token.EQU: stack.push(new And(Imp(c1,c2),Imp(c2,c1))); again = true;break;
	       }
            }  
      if (again == true) { reduce(); }  // do as many "reduces" as possible 
    }

  /** parses a single formula
    */
  public Form parseFormula() throws Exception {
    Token tok;
    while (!((tok = getNextToken()) instanceof EOI)) {
      stack.push(tok);
      reduce();
    }
    if (stack.size() == 1 &&
       (stack.elementAt(stack.size()-1) instanceof Form))
       { return (Form)stack.pop(); } 
    else throw new Exception("Grammar error");
    return null;  
  }

  /** parses a list of formulae separated by commas
    */
  public Context parseFormulae() throws Exception {
    Token tok;
    Context erg = new Context();
    while (!((tok = getNextToken()) instanceof EOI)) {
      stack.push(tok);
      reduce();
    }
    if (stack.empty()) return erg;  // LHS can be empty  !!
    stack.push(new COMMA());
    for(int i=0;i<stack.size()-1;i=i+2){
      if (stack.elementAt(i)  instanceof Form)
	{ erg.addElement(stack.elementAt(i));
	}  else throw new Exception("Grammar error");
      if (stack.elementAt(i+1) instanceof COMMA)
        {} else throw new Exception("Grammar error");
    }
    return erg;
  }

}
