/*
 *  File:        math_parser.C
 *  Purpose:     Parser for mathed
 *  Author:      Alejandro Aguilar Sierra <asierra@servidor.unam.mx> 
 *  Created:     January 1996
 *  Description: Parse LaTeX2e math mode code.
 *
 *  Dependencies: Xlib, XForms
 *
 *  Copyright: (c) 1996, Alejandro Aguilar Sierra
 *
 *   Version: 0.5beta.
 *
 *   You are free to use and modify this code under the terms of
 *   the GNU General Public Licence version 2 or later.
 */

#include "config.h"

#include "math_parser.h"
#include "math_inset.h"

#define FLAG_BRACE       1	//  A { needed
#define FLAG_BRACE_ARG   2	//  Next { is argument
#define FLAG_BRACE_OPT   4	//  Optional {
#define FLAG_BRACE_LAST  8	//  Last } ends the parsing process
#define FLAG_BRACK_ARG  16	//  Optional [
#define FLAG_RIGHT	32      //  Next right ends the parsing process
#define FLAG_END	64      //  Next end ends the parsing process
#define FLAG_BRACE_FONT 128	//  Next } closes a font 

YYSTYPE yylval;

static void panic ();

static short mathed_env = LM_EN_INTEXT;

char *mathed_label = NULL;

const char *latex_mathenv[] = { 
   "math", 
   "displaymath", 
   "equation", 
   "eqnarray*",
   "eqnarray",
   "array"
};


char *latex_mathspace[] = {
   "!", ",", ":", ";", "quad", "qquad"
};
   

// These are lexical codes, not semantic
enum lexcode_enum {
   LexNone,
   LexESC,
   LexAlpha,
   LexDigit,
   LexBOP,         // Binary operators or relations
   LexMathSpace,
   LexOpen,
   LexClose,
   LexComment,
   LexSpace,
   LexNewLine,
   LexOther,
   LexSelf
};

static lexcode_enum lexcode[256];  
static char yytext[256];
FILE *yyin;

inline
char *strnew(const char* s)
{
   char *s1 = new char[strlen(s)+1];
   return strcpy(s1, s);
}
   
static void LexInitCodes()
{
   int i;
   
   for (i=0;  i<=255; i++) lexcode[i] = LexNone; 
   for (i='A'; i<='Z'; i++) lexcode[i] = LexAlpha;
   for (i='a'; i<='z'; i++) lexcode[i] = LexAlpha;
   for (i='0'; i<='9'; i++) lexcode[i] = LexDigit;
   lexcode['\t'] = lexcode['\f'] = lexcode[' '] = LexSpace;
   lexcode['\n'] = LexNewLine;
   lexcode['%'] = LexComment;
   lexcode['+'] = lexcode['-'] = lexcode['*'] = lexcode['/'] = 
   lexcode['<'] = lexcode['>'] = lexcode['='] = LexBOP;
   
   lexcode['!'] = lexcode[','] = lexcode[':'] = lexcode[';'] = LexMathSpace;
   lexcode['('] = lexcode[')'] = lexcode['|'] = lexcode['.'] = LexOther; 
   lexcode['\'']= LexAlpha;
   
   lexcode['['] = lexcode[']'] = lexcode['^'] = lexcode['_'] = 
   lexcode['&'] = LexSelf;  
   
   lexcode['\\'] = LexESC;
   lexcode['{'] = LexOpen;
   lexcode['}'] = LexClose;
}

static char LexGetArg(char lf)
{
   char c, rg, *p = &yytext[0];
   int bcnt =1;
   
   while (!feof(yyin)) {
      c = getc(yyin); 
      if (c>' ') {
	 if (!lf) lf = c; else
	 if (c!=lf)
	   fprintf(stderr, "Math parse error: unexpected '%c'\n", c);
	 break;
      }
   }
   rg = (lf=='{') ? '}': ((lf=='[') ? ']': ((lf=='(') ? ')': 0));
   if (!rg) {
      fprintf(stderr, "Math parse error: unknown bracket '%c'\n", lf);
      return '\0';
   } 
   do {
      c = getc(yyin); 
      if (c==lf) bcnt++;
      if (c==rg) bcnt--;
      if (c>' ' && bcnt>0) *(p++) = c;
   } while (bcnt>0 && !feof(yyin));
   *p = '\0';
   return rg;
}

static int yylex(void)
{
   static int init_done = 0;
   char c;
   
   if (!init_done) LexInitCodes();
   
   while (!feof(yyin)) { 
      c = getc(yyin);
      if (lexcode[c]==LexComment) 
	do c = getc(yyin); while (c!='\n' % !feof(yyin));  // eat comments
    
      if (lexcode[c]==LexDigit || lexcode[c]==LexOther || lexcode[c]==LexMathSpace) 
        { yylval.i= c; return LM_TK_STR; }
      if (lexcode[c]==LexAlpha) { yylval.i=c; return LM_TK_ALPHA; }
      if (lexcode[c]==LexBOP)   { yylval.i=c; return LM_TK_BOP; }
      if (lexcode[c]==LexSelf)  { return c; }   

      if (lexcode[c]==LexOpen)   { return LM_TK_OPEN; }
      if (lexcode[c]==LexClose)   { return LM_TK_CLOSE; }
      
      if (lexcode[c]==LexESC)   {
	 c = getc(yyin);
	 if (c=='\\')	{ return LM_TK_NEWLINE; }
	 if (c=='(')	{ yylval.i = LM_EN_INTEXT; return LM_TK_BEGIN; }
	 if (c==')')	{ yylval.i = LM_EN_INTEXT; return LM_TK_END; }
	 if (c=='[')	{ yylval.i = LM_EN_DISPLAY; return LM_TK_BEGIN; }
	 if (c==']')	{ yylval.i = LM_EN_DISPLAY; return LM_TK_END; }
	 if (c=='{')	{ yylval.i = '{'; return LM_TK_STR; }
	 if (c=='}')	{ yylval.i = '}'; return LM_TK_STR; }
	 if (lexcode[c]==LexMathSpace) {
	    int i;
	    for (i=0; i<4 && c!=latex_mathspace[i][0]; i++);
	    yylval.i = (i<4) ? i: 0; 
	    return LM_TK_SPACE; 
	 }
	 if (lexcode[c]==LexAlpha) {
	    char* p = &yytext[0];
	    while (lexcode[c]==LexAlpha) {
	       *p = c;
	       c = getc(yyin);
	       p++;
	    }
	    *p = '\0';
	    if (!feof(yyin)) ungetc(c, yyin);
	    latexkeys *l = in_word_set (yytext, strlen(yytext));
	    if (l) {
	       if (l->token==LM_TK_BEGIN || l->token==LM_TK_END) { 
		  int i;
		  LexGetArg('{');
//		  for (i=0; i<5 && strncmp(yytext, latex_mathenv[i],
//				strlen(latex_mathenv[i])); i++);
		  
		  for (i=0; i<5 && strcmp(yytext, latex_mathenv[i]); i++);
		  yylval.i = i;
	       } else
	       if (l->token==LM_TK_SPACE) 
		 yylval.i = l->id;
	       else
		 yylval.l = l;
	       return l->token;
	    } else { 
	       yylval.s = yytext;
	       return LM_TK_UNDEF;
	    }
	 }
      }
   }
   return 0;
}

int parse_align(char *hor, char *ver)
{    
   char *c;

   int nc = 0;
   for (c=hor; c && *c>' '; c++) nc++;
   return nc;
}
   
LyxArrayBase *mathed_parse(unsigned flags, LyxArrayBase *array, MathParInset **mtx)
{
   int t = yylex();
   bool panic = false;
   static int plevel = -1;
   static int size = LM_ST_TEXT;
   LyxMathTextCodes varcode = LM_TC_VAR;
   LyxMathInset* binset=NULL;
   
   int brace = 0;
   int acc_code = 0;
   int acc_brace = 0;
   MathParInset *mt = (mtx) ? *mtx: (MathParInset*)NULL;
   
   plevel++;
   if (!array) array = new LyxArrayBase();
   LyxMathIter data(array);
   while (t) {
      if ((flags & FLAG_BRACE) && t != LM_TK_OPEN) {
	 if ((flags & FLAG_BRACK_ARG) && t=='[') {
	 }
	 else {
	    fprintf (stderr, "Math Parse error: '{' expected[%d %c]", t, t);
	    panic = true;
	    break;
	 }
      }
    switch (t) {
    case LM_TK_ALPHA:
      {
	 if (acc_code) {
	    data.Insert(new MathAccentInset(yylval.i, varcode, acc_code));
	    acc_code = 0;
	 } else
	   data.Insert (yylval.i, varcode);  //LM_TC_VAR);
	 break;
      }
    case LM_TK_STR:
      {
	 data.Insert (yylval.i, LM_TC_CONST);
	 break;
      }
    case LM_TK_OPEN:
      {
	brace++;
	if  (acc_code && !acc_brace) {
	    acc_brace = brace;
	    break;
	}
	if (flags & FLAG_BRACE_OPT) {
	   flags &= ~FLAG_BRACE_OPT;
	   flags |= FLAG_BRACE;
	}
	 	 
	if (flags & FLAG_BRACE)
	  flags &= ~FLAG_BRACE;
	 else {
	    data.Insert ('{', LM_TC_TEX);
	 }
	break;
      }
    case LM_TK_CLOSE:
      {
	 brace--;	 
	 if (brace < 0) {
	    fprintf (stderr, "Math Parse error: Unmatching braces");
	    panic = true;
	    break;
	 }
	 if (acc_brace && brace==acc_brace-1) {
	     acc_brace = 0;
	     break;
	 }
	 if (flags & FLAG_BRACE_FONT) {
	    varcode = LM_TC_VAR; 
	    flags &= ~FLAG_BRACE_FONT;
	    break;
	 }
	 if (brace == 0 && (flags & FLAG_BRACE_LAST)) {
	  //  printf("Fin");
	    plevel--;
	    return array;
	 } else {
	    data.Insert ('}', LM_TC_TEX);
	 }
	 break;
      }

    case '[':
      {
	 if (flags & FLAG_BRACK_ARG) {
	   flags &= ~FLAG_BRACK_ARG;
	   char rg=LexGetArg('[');
	   if (rg!=']') {
	      fprintf (stderr, "Math Parse error: ']' expected");
	      panic = true;
	      break;
	   }	   
//	   if (arg) strcpy(arg, yytext);
	} else
	  data.Insert ('[');
	break;
      }
    case ']':
      {
	data.Insert (']');
	break;
      }

    case '^':
      {  
	 MathParInset *p = new MathParInset(size, "", LM_OT_SCRIPT);
	 LyxArrayBase * ar = mathed_parse(FLAG_BRACE_OPT|FLAG_BRACE_LAST, NULL);
	 p->SetData(ar);
//	 fprintf(stderr, "UP[%d]", p->GetStyle());
	 data.Insert (p, LM_TC_UP);
	 break;
      }
    case '_':
      {
	 MathParInset *p = new MathParInset(size, "", LM_OT_SCRIPT);
	 LyxArrayBase * ar = mathed_parse(FLAG_BRACE_OPT|FLAG_BRACE_LAST, NULL);
	 p->SetData(ar);
	 data.Insert (p, LM_TC_DOWN);
	 break;
      }

    case LM_TK_LIMIT:
      {
	 if (binset) {
	    binset->SetLimits((bool)(yylval.l->id));
	    binset = NULL;
	 }
	 break;
      }
      
    case '&':    // Tab
      {
	 if (flags & FLAG_END) {
	   // data.Insert (' ', LM_TC_TAB);
	   data.Next ();
	 } else 
	    fprintf (stderr, "Math Parse error: Unexpected tab\n");
	break;
      }
    case LM_TK_NEWLINE:
      {
	 if (mt && (flags & FLAG_END)) {
	    if (mt->GetType()>LM_EN_EQUATION)
	      ((MathMatrixInset*)mt)->AddRow();
	    array = mt->GetData(); 
	    data.SetData(array);
	 } else 
	    fprintf (stderr, "Math Parse error: Unexpected newline\n");
	break;
      }
    case LM_TK_BIGSYM:  
      {
	 binset = new MathBigopInset(yylval.l->name,yylval.l->id);
	 data.Insert(binset);	
	 break;
      }
    case LM_TK_SYM:
      {
	 if (yylval.l->id < 256) {
	    LyxMathTextCodes tc = MathIsBOPS(yylval.l->id) ? LM_TC_BOPS: LM_TC_SYMB; 
	    if (acc_code) {
		data.Insert(new MathAccentInset(yylval.l->id, tc, acc_code));
		acc_code = 0;
	    } else
	    data.Insert (yylval.l->id, tc);
	 } else {
	    MathFuncInset *bg = new MathFuncInset(yylval.l->name);
	    data.Insert(bg, true);	
	 }
	 break;
      }
    case LM_TK_BOP:
      {
	 data.Insert (yylval.i, LM_TC_BOP);
	 break;
      }
    case LM_TK_STY:
      {
	  if (mt) {
	      mt->UserSetSize(yylval.l->id);
	  }
	  break; 
      }
    case LM_TK_SPACE:
      {
	 if (yylval.i>=0) {
	    MathSpaceInset *sp = new MathSpaceInset(yylval.i);
	    data.Insert(sp);
	 }
	 break;
      }	   
    case LM_TK_DOTS:
      {
	 MathDotsInset *p = new MathDotsInset(yylval.l->name, yylval.l->id);
	 data.Insert(p);
	 break;
      }     
    case LM_TK_FRAC:
      {
	 MathFracInset *fc = new MathFracInset(size);
	 LyxArrayBase* num = mathed_parse(FLAG_BRACE|FLAG_BRACE_LAST);
	 LyxArrayBase* den = mathed_parse(FLAG_BRACE|FLAG_BRACE_LAST);
	 fc->SetData(num, den);
	 data.Insert(fc, LM_TC_ACTIVE_INSET);
	 break;
      }
    case LM_TK_SQRT:
      {
	 MathSqrtInset *sq = new MathSqrtInset(size);
	 sq->SetData(mathed_parse(FLAG_BRACE|FLAG_BRACE_LAST));
	 data.Insert(sq, LM_TC_ACTIVE_INSET);
	 break;
      }
       
    case LM_TK_LEFT:
      {
	 int lfd, rgd;
	 lfd=yylex();
	 if (lfd==LM_TK_SYM || lfd==LM_TK_STR || lfd==LM_TK_BOP)
	   lfd = (lfd==LM_TK_SYM) ? yylval.l->id: yylval.i;
	 LyxArrayBase* a = mathed_parse(FLAG_RIGHT);
	 rgd=yylex();
//	 fprintf(stderr, "R[%d]", rgd);
	 if (rgd==LM_TK_SYM || rgd==LM_TK_STR || rgd==LM_TK_BOP)
	   rgd = (rgd==LM_TK_SYM) ? yylval.l->id: yylval.i;	 
	 MathDelimInset *dl = new MathDelimInset(lfd, rgd);
	 dl->SetData(a);
	 data.Insert(dl, LM_TC_ACTIVE_INSET);
//	 fprintf(stderr, "RL[%d %d]", lfd, rgd);
  	 break;
      }
    case LM_TK_RIGHT:
      {
	 if (flags & FLAG_RIGHT) { 
	    plevel--;
	    return array;
	 } else {
	    fprintf (stderr, "Math Parse error: Unmatched right");
//	    panic = true;
	 }
	 break;
      }

    case LM_TK_FONT:
      {
	 varcode = (LyxMathTextCodes)yylval.l->id;
	 flags |= (FLAG_BRACE|FLAG_BRACE_FONT);
	break;
      }
    case LM_TK_WIDE:
      {  
	 MathDecorationInset *sq = new MathDecorationInset(yylval.l->id, size);
	 sq->SetData(mathed_parse(FLAG_BRACE|FLAG_BRACE_LAST));
	 data.Insert(sq, LM_TC_ACTIVE_INSET);
	 break;
      }
      
    case LM_TK_ACCENT: acc_code = yylval.l->id; break;
	  
    case LM_TK_NONUM:
      {
	  if (mt && mt->GetType()==LM_OT_MPARN) 
	    ((MathMatrixInset*)mt)->SetNumbered(false);
	  break;
      }
       
    case LM_TK_PMOD:
    case LM_TK_FUNC:
      {
	 data.Insert(new MathFuncInset(yylval.l->name));
	 break;
      }
    case LM_TK_FUNCLIM:
      {
	 data.Insert(new MathFuncInset(yylval.l->name, LM_OT_FUNCLIM));
	 break;
      }
    case LM_TK_UNDEF:
      {
	 data.Insert(new MathFuncInset(strnew(yylval.s), LM_OT_UNDEF));
	 fprintf (stderr, "Math Parse Warning: Unrecognized name \"%s\"\n", yylval.s);
	 break;
      }
    case LM_TK_END:
      {
         if (mathed_env != yylval.i && yylval.i!=LM_EN_ARRAY)
	   fprintf (stderr, "Math Parse error: Unmatched environment[%d]", yylval.i);
	 plevel--;
	 if (mt) { // && (flags & FLAG_END)) {
	    mt->SetData(array);
	    array = NULL;
	 }
	 return array;
	 break;
      }
    case LM_TK_BEGIN:
      {
	 if (yylval.i==LM_EN_ARRAY) {
	    char ar[120], ar2[8];
	    ar[0] = ar2[0] = '\0'; 
            char rg=LexGetArg(0);
	    if (rg=='[') {
	       strcpy(ar2, yytext);
	       rg = LexGetArg('{');
	    }
	    strcpy(ar, yytext);
	    int nc = parse_align(ar, ar2);
	    MathParInset* mm = new MathMatrixInset(nc);
	    mm->SetAlign(ar2[0], ar);
       	    data.Insert(mm, LM_TC_ACTIVE_INSET);
            (void)mathed_parse(FLAG_END, mm->GetData(), &mm);
	 } else {
	    if (plevel!=0) {
	       fprintf (stderr, "Math Parse error: Misplaced environment\n");
	       break;
	    }
	    if (!mt) {
	       fprintf (stderr, "Math Parse error: NULL paragraph.");
	       panic = true;
	    }
	    
	    mathed_env = yylval.i;
	    if (mathed_env>=LM_EN_DISPLAY) {
	       size = LM_ST_DISPLAY;
	       if (mathed_env>LM_EN_EQUATION) {
		  mt = new MathMatrixInset(3);
		  mt->SetAlign(' ', "rcl");
		  if (mtx) *mtx = mt;
		  delete array;
		  array = mt->GetData();
		  data.SetData(array);
		  flags |= FLAG_END;
	       }
	       mt->SetStyle(size);
	       mt->SetType(mathed_env);
	    }
	       	       	       
#ifdef DEBUG
	       fprintf(stderr, "MATH BEGIN[%d]", mathed_env);
#endif
	 }
	 break;
      }
     case LM_TK_LABEL:
       {	   
	  char rg = LexGetArg('\0');
	  if (rg != '}') {
	     fprintf (stderr, "Math Parse error: '{' expected[%s]", yytext); fflush(stderr);
	     panic = true;
	     break;
	  } 
	  if (mt && mt->GetType()==LM_OT_MPARN) 
	     ((MathMatrixInset*)mt)->SetLabel(strnew(yytext));
	  else 
	     mathed_label = strnew(yytext);
#ifdef DEBUG
	  fprintf(stderr, "Label[%d]", mathed_label);
#endif
	  break;
	} 
     default:
      fprintf (stderr, "Math Parse Error: Unrecognized token [%d %s]\n", t, yytext);
       break;
    }
    if (panic) {
       fprintf(stderr, " Panic!\n"); 
       //   Search for the end command. 
       do t = yylex (); while (t != LM_TK_END && t);
    } else
     t = yylex ();
   
   if ((flags & FLAG_BRACE_OPT)/* && t!='^' && t!='_'*/) {
        flags &= ~FLAG_BRACE_OPT;
       //data.Insert (LM_TC_CLOSE);
       break;
    }
  }
   plevel--;
   return array;
}


void mathed_parser_file(FILE* file)
{
   yyin = file;
}

