package chocopy.pa1;
import java_cup.runtime.*;
import java.util.ArrayList;
import java.util.Iterator;

%%

/*** Do not change the flags below unless you know what you are doing. ***/

%unicode
%line
%column
%states AFTER, STR
%class ChocoPyLexer
%public

%cupsym ChocoPyTokens
%cup
%cupdebug

%eofclose false

/*** Do not change the flags above unless you know what you are doing. ***/

/* The following code section is copied verbatim to the
 * generated lexer class. */
%{
    /* The code below includes some convenience methods to create tokens
     * of a given type and optionally a value that the CUP parser can
     * understand. Specifically, a lot of the logic below deals with
     * embedded information about where in the source code a given token
     * was recognized, so that the parser can report errors accurately.
     * (It need not be modified for this project.) */

    /** Producer of token-related values for the parser. */
    final ComplexSymbolFactory symbolFactory = new ComplexSymbolFactory();
    private int currIndent = 0; //Current Indentation Level
    private String currString = "";
    private int str_l = 0, str_c = 0; //Start location of a string.
    /*A stack that keeps track of the spaces in each Indentation Level*/
    private ArrayList<Integer> stack = new ArrayList<Integer>(20); 
    private boolean indentErrorUnchecked = true;
    /** Return a terminal symbol of syntactic category TYPE and no
     *  semantic value at the current source location. */
    private Symbol symbol(int type) {
        return symbol(type, yytext());
    }

    /** Return a terminal symbol of syntactic category TYPE and semantic
     *  value VALUE at the current source location. */
    private Symbol symbol(int type, Object value) {
        return symbolFactory.newSymbol(ChocoPyTokens.terminalNames[type], type,
            new ComplexSymbolFactory.Location(yyline + 1, yycolumn + 1),
            new ComplexSymbolFactory.Location(yyline + 1,yycolumn + yylength()),
            value);
    }

    private void push(int indent){
        stack.add(indent);
    }
    private int pop(){
        if(stack.isEmpty()) return 0;
        return stack.remove(stack.size() - 1);
    }
    private int top(){
        if(stack.isEmpty()) return 0;
        return stack.get(stack.size() - 1);
    }
    private boolean find(int indent){
      if(indent == 0) return true;
      Iterator<Integer> it = stack.iterator();
      while(it.hasNext()){
         if(it.next() == indent)
            return true;
      }
      return false;
    }
%}

/* Macros (regexes used in rules below) */

WhiteSpace = [ \t]
LineBreak  = \r|\n|\r\n


IntegerLiteral = 0|[1-9][0-9]* // Accroding to the manual, 00+ is illeagal
StringLiteral = ([^\"\\]|(\\\")|(\\t)|(\\r)|(\\n)|(\\\\))+ // \n, \r, \t, \\, \" and Anything except \ and " 
Identifiers = (_|[a-z]|[A-Z])(_|[a-z]|[A-Z]|[0-9])* 
Comments = #[^\r\n]*
%%
//YYINITIAL state is where we're dealing with indentations.
//We will set the state to YYINITIAL when starting a 
//new line unless this line is within a string, e.g.:
/*
"this is \
a string across \
multiple lines\
"
*/
<YYINITIAL>{
  {WhiteSpace}                
  { 
      /*Add indentation */
      if(yytext() == "\t")
        currIndent += 8; //'\t' = 8 spaces
      else 
        currIndent ++;
  }
/*
# This python code will test if '\t' is 8 spaces
# It will run and print '1\n2'
# Please tell me if your Python reports an error
# Or you find documentations that says otherwise

if True:
    print(1) # \t
        print(2) # 8 spaces
*/
  
  {LineBreak} 
  {
      /*
      If this is a blank line, start over on the next line.
      An empty line should just be ignored, therefore we don't 
      pass a NEWLINE to Cup.
      */
      currIndent = 0;
  }
  {Comments}           { /* ignored */ } //Ignore blank lines

  /*If it's not a blank line (Current character isn't a 
    Whitespace/linebreak/comment), deal with indentation here and 
    start accepting whatever is on this line in `AFTER' state*/
  [^ \t\r\n#] 
  {
      //rewind the current character.
      yypushback(1);
      if(top() > currIndent)
      {   
          /*
          If the indentation of the line is less than number of 
          indents current level should have,
          keep dedenting until it reaches the level with the same 
          number of indents.
          It's like a loop, because we're not changing the state 
          and we rewinded the current character. So it will keep 
          going until top()<= currIndent and it will switch to 
          AFTER state.
          */ 
          pop();
          if(top() < currIndent)
          {
            currIndent = top();
            return symbolFactory.newSymbol("<bad indentation>", ChocoPyTokens.UNRECOGNIZED,
              new ComplexSymbolFactory.Location(yyline + 1, yycolumn - 1),
              new ComplexSymbolFactory.Location(yyline + 1,yycolumn + yylength()),
              currIndent);
          }
          return symbolFactory.newSymbol(ChocoPyTokens.terminalNames[ChocoPyTokens.DEDENT], ChocoPyTokens.DEDENT,
            new ComplexSymbolFactory.Location(yyline + 1, yycolumn - 1),
            new ComplexSymbolFactory.Location(yyline + 1,yycolumn + yylength()),
            currIndent);
      }
      /*Otherwise, we will start dealing with the rest 
        of the line after indentation in AFTER state. */
      yybegin(AFTER);
      if(top()< currIndent)
      {   
          /*
          If current indentation is more than the number of indents 
          current level should have, start a new level which will have 
          `currIndent' indents.
          */

          push(currIndent);
          return symbolFactory.newSymbol(ChocoPyTokens.terminalNames[ChocoPyTokens.INDENT], ChocoPyTokens.INDENT,
            new ComplexSymbolFactory.Location(yyline + 1, yycolumn - 1),
            new ComplexSymbolFactory.Location(yyline + 1,yycolumn + yylength()),
            currIndent);
      }
  }
}


<AFTER> {

  /* Delimiters. */
  {LineBreak}                    { yybegin(YYINITIAL); currIndent = 0;indentErrorUnchecked = true; return symbol(ChocoPyTokens.NEWLINE);}
  ":"                            { return symbol(ChocoPyTokens.COLON); }
  ","                            { return symbol(ChocoPyTokens.COMMA); }

  /* Literals. */
  {IntegerLiteral}               { return symbol(ChocoPyTokens.NUMBER,
                                                 Integer.parseInt(yytext())); }

  "\""                           { yybegin(STR); str_l = yyline + 1; str_c = yycolumn + 1; currString = ""; } //Start taking a string when see a "
  "False"                        { return symbol(ChocoPyTokens.BOOL, false); }
  "True"                         { return symbol(ChocoPyTokens.BOOL, true); }
  "None"                         { return symbol(ChocoPyTokens.NONE); }

  /*Keywords*/
  "if"                           { return symbol(ChocoPyTokens.IF); }
  "else"                         { return symbol(ChocoPyTokens.ELSE); }
  "elif"                         { return symbol(ChocoPyTokens.ELIF); }
  "while"                        { return symbol(ChocoPyTokens.WHILE); }
  "class"                        { return symbol(ChocoPyTokens.CLASS); }
  "def"                          { return symbol(ChocoPyTokens.DEF); }
  "lambda"                       { return symbol(ChocoPyTokens.LAMBDA); }
  "as"                           { return symbol(ChocoPyTokens.AS); }
  "for"                          { return symbol(ChocoPyTokens.FOR); }
  "global"                       { return symbol(ChocoPyTokens.GLOBAL); }
  "in"                           { return symbol(ChocoPyTokens.IN); }
  "nonlocal"                     { return symbol(ChocoPyTokens.NONLOCAL); }
  "pass"                         { return symbol(ChocoPyTokens.PASS); }
  "return"                       { return symbol(ChocoPyTokens.RETURN); }
  "assert"                       { return symbol(ChocoPyTokens.ASSERT); }
  "await"                        { return symbol(ChocoPyTokens.AWAIT); }
  "break"                        { return symbol(ChocoPyTokens.BREAK); }
  "continue"                     { return symbol(ChocoPyTokens.CONTINUE); }
  "del"                          { return symbol(ChocoPyTokens.DEL); }
  "except"                       { return symbol(ChocoPyTokens.EXCEPT); }
  "finally"                      { return symbol(ChocoPyTokens.FINALLY); }
  "from"                         { return symbol(ChocoPyTokens.FROM); }
  "import"                       { return symbol(ChocoPyTokens.IMPORT); }
  "raise"                        { return symbol(ChocoPyTokens.RAISE); }
  "try"                          { return symbol(ChocoPyTokens.TRY); }
  "with"                         { return symbol(ChocoPyTokens.WITH); }
  "yield"                        { return symbol(ChocoPyTokens.YIELD); }


  /* Operators. */
  "+"                            { return symbol(ChocoPyTokens.PLUS); }
  "-"                            { return symbol(ChocoPyTokens.MINUS); }
  "*"                            { return symbol(ChocoPyTokens.MUL); }
  "//"                           { return symbol(ChocoPyTokens.DIV); }  
  "/"                            { return symbol(ChocoPyTokens.DIV); }  //Accroding to manual, chocopy don't have fp division, '/', '//' should be integr division
  "%"                            { return symbol(ChocoPyTokens.MOD); }  
  ">"                            { return symbol(ChocoPyTokens.GT); }
  "<"                            { return symbol(ChocoPyTokens.LT); }
  "=="                           { return symbol(ChocoPyTokens.EQUAL); }
  "!="                           { return symbol(ChocoPyTokens.NEQ); }
  ">="                           { return symbol(ChocoPyTokens.GEQ); }
  "<="                           { return symbol(ChocoPyTokens.LEQ); }
  "="                            { return symbol(ChocoPyTokens.ASSIGN); }
  "and"                          { return symbol(ChocoPyTokens.AND); }
  "or"                           { return symbol(ChocoPyTokens.OR); }
  "not"                          { return symbol(ChocoPyTokens.NOT); }
  "."                            { return symbol(ChocoPyTokens.DOT); }
  "("                            { return symbol(ChocoPyTokens.LPAR); }
  ")"                            { return symbol(ChocoPyTokens.RPAR); }
  "["                            { return symbol(ChocoPyTokens.LBR); }
  "]"                            { return symbol(ChocoPyTokens.RBR); }
  "->"                           { return symbol(ChocoPyTokens.ARROW); }
  "is"                           { return symbol(ChocoPyTokens.IS); }
  
 
  /*Identifiers*/
  {Identifiers}                  { return symbol(ChocoPyTokens.ID, yytext()); }
  
  /* Whitespace. */
  {WhiteSpace}                   { /* ignore */ }
  
  /* Comment. */
  {Comments}                     { /* ignore */ }
}
<STR>{
    {StringLiteral}              { currString += yytext(); }
    
    \\$                          { /*'\' at the end of line, do nothing.*/ }
    
    "\""                         { yybegin(AFTER); return symbolFactory.newSymbol(ChocoPyTokens.terminalNames[ChocoPyTokens.STRING], ChocoPyTokens.STRING,
                                   new ComplexSymbolFactory.Location(str_l, str_c),
                                   new ComplexSymbolFactory.Location(yyline + 1,yycolumn + yylength()),
                                   currString); } // accepted a ", return to AFTER state
}
<<EOF>>                          { if(!stack.isEmpty()){ return symbol(ChocoPyTokens.DEDENT, pop());} return symbol(ChocoPyTokens.EOF); }

/* Error fallback. */
[^]                              { return symbol(ChocoPyTokens.UNRECOGNIZED); }
