/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 *
 ************************************************************************/

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

// DVO: always use standard headers:
#include <istream>
#include <sstream>
using namespace std;

#include "mzstring.h"
#include "hwpeq.h"
#include <sal/types.h>
#include <sal/macros.h>
//#define TEST
//#define DEBUG


/* @Man: hwp수식을 LaTeX으로 바꾸기 */
#ifdef WIN32
# define ENDL  "\r\n"
#else /* !WIN32 */
# define ENDL  "\n"
#endif

#define WS	" \t\r\n\v\f"

#define EQ_CASE	0x01	// case sensitive cmd
#define EQ_ENV	0x02	// equiv to latex environment
#define EQ_ATOP	0x04	// must revert order

#define IS_WS(ch)	(strchr(WS, ch))
#define IS_BINARY(ch)	(strchr("+-<=>", ch))

#ifdef WIN32
#define STRICMP	stricmp
#else
#define STRICMP	strcasecmp
#endif

// sub and sup scipt script status
enum { SCRIPT_NONE, SCRIPT_SUB, SCRIPT_SUP, SCRIPT_ALL};

static int	eq_word(MzString& outs, istream *strm, int script = SCRIPT_NONE);
static bool	eq_sentence(MzString& outs, istream *strm, const char *end = 0);

struct hwpeq {
  const char	*key;		// hwp math keyword
  const char	*latex;		// corresponding latex keywork
  int		nargs;		// # of argument
  unsigned char	flag;		// case sensitive?
};

static hwpeq eq_tbl[] = {
  { "!=",		"\\equiv ",	0,	0	},
  { "#",		"\\\\",		0,	0	},
  { "+-",		"\\pm ",	0,	0	},
  { "-+",		"\\mp ",	0,	0	},
  { "<=",		"\\leq ",	0,	0	},
  { "==",		"\\equiv ",	0,	0	},
  { ">=",		"\\geq ",	0,	0	},
  { "Pr",		NULL,		0,	0	},
  { "^",		"^",		1,	0	},
  { "_",		"_",		1,	0	},
  { "`",		"\\;",		0,	0	},
  { "acute",		NULL,		1,	0	},
  { "aleph",		NULL,		0,	0	},
  { "alpha",		NULL,		0,	EQ_CASE	},
  { "amalg",		NULL,		0,	0	},
  { "and",		NULL,		0,	0	},
  { "angle",		NULL,		0,	0	},
  { "angstrom",		NULL,		0,	0	},
  { "approx",		NULL,		0,	0	},
  { "arc",		NULL,		0,	0	},
  { "arccos",		NULL,		0,	0	},
  { "arch",		NULL,		0,	0	},
  { "arcsin",		NULL,		0,	0	},
  { "arctan",		NULL,		0,	0	},
  { "arg",		NULL,		0,	0	},
  { "assert",		"\\vdash",	0,	0	},
  { "ast",		NULL,		0,	0	},
  { "asymp",		NULL,		0,	0	},
  { "atop",		NULL,		1,	EQ_ATOP	},
  { "backslash",	NULL,		0,	0	},
  { "bar",		NULL,		1,	0	},
  { "because",		NULL,		0,	0	},
  { "beta",		NULL,		0,	EQ_CASE	},
  { "big",		NULL,		0,	EQ_CASE	},
  { "bigcap",		NULL,		0,	0	},
  { "bigcirc",		NULL,		0,	0	},
  { "bigcup",		NULL,		0,	0	},
  { "bigg",		NULL,		0,	EQ_CASE	},
  { "bigodiv",		NULL,		0,	0	},
  { "bigodot",		NULL,		0,	0	},
  { "bigominus",	NULL,		0,	0	},
  { "bigoplus",		NULL,		0,	0	},
  { "bigotimes",	NULL,		0,	0	},
  { "bigsqcap",		NULL,		0,	0	},
  { "bigsqcup",		NULL,		0,	0	},
  { "biguplus",		NULL,		0,	0	},
  { "bigvee",		NULL,		0,	0	},
  { "bigwedge",		NULL,		0,	0	},
  { "binom",		NULL,		2,	0	},
  { "bmatrix",		NULL,		0,	EQ_ENV	},
  { "bold",		NULL,		0,	0	},
  { "bot",		NULL,		0,	0	},
  { "breve",		NULL,		1,	0	},
  { "buildrel",		NULL,		0,	0	}, // LATER
  { "bullet",		NULL,		0,	0	},
  { "cap",		NULL,		0,	0	},
  { "cases",		NULL,		0,	EQ_ENV	},
  { "ccol",     NULL,       0,  0   }, /* 세로로 가운데 */
  { "cdot",		NULL,		0,	0	},
  { "cdots",		NULL,		0,	0	},
  { "check",		NULL,		1,	0	},
  { "chi",		NULL,		0,	EQ_CASE	},
  { "choose",		NULL,		0,	EQ_ATOP	},
  { "circ",		NULL,		0,	0	},
  { "col",		NULL,		0,	0	}, // LATER
  { "cong",		NULL,		0,	0	},
  { "coprod",		NULL,		0,	0	},
  { "cos",		NULL,		0,	0	},
  { "cosec",		NULL,		0,	0	},
  { "cosh",		NULL,		0,	0	},
  { "cot",		NULL,		0,	0	},
  { "coth",		NULL,		0,	0	},
  { "cpile",		NULL,		0,	0	}, // LATER
  { "csc",		NULL,		0,	0	},
  { "cup",		NULL,		0,	0	},
  { "dagger",		NULL,		0,	0	},
  { "dashv",		NULL,		0,	0	},
  { "ddagger",		NULL,		0,	0	},
  { "ddot",		NULL,		1,	0	},
  { "ddots",		NULL,		0,	0	},
  { "def",		NULL,		0,	0	},
  { "deg",		NULL,		0,	0	},
  { "del",		NULL,		0,	0	},
  { "delta",		NULL,		0,	EQ_CASE	},
  { "diamond",		NULL,		0,	0	},
  { "dim",		NULL,		0,	0	},
  { "div",		NULL,		0,	0	},
  { "divide",		NULL,		0,	0	},
  { "dline",		NULL,		0,	0	},
  { "dmatrix",		NULL,		0,	EQ_ENV	},
  { "dot",		NULL,		1,	0	},
  { "doteq",		NULL,		0,	0	},
  { "dotsaxis",		NULL,		0,	0	},
  { "dotsdiag",		NULL,		0,	0	},
  { "dotslow",		"\\ldots",	0,	0	},
  { "dotsvert",		"\\vdots",	0,	0	},
  { "downarrow",	NULL,		0,	EQ_CASE	},
  { "dsum",		"+",		0,	0	},
  { "dyad",		NULL,		0,	0	}, // LATER
  { "ell",		NULL,		0,	0	},
  { "emptyset",		NULL,		0,	0	},
  { "epsilon",		NULL,		0,	EQ_CASE	},
  { "eqalign",		NULL,		0,	EQ_ENV	},
  { "equiv",		NULL,		0,	0	},
  { "eta",		NULL,		0,	EQ_CASE	},
  { "exarrow",		NULL,		0,	0	},
  { "exist",		"\\exists",	0,	0	},
  { "exists",		NULL,		0,	0	},
  { "exp",		NULL,		0,	EQ_CASE	},
  { "for",		NULL,		0,	0	},
  { "forall",		NULL,		0,	0	},
  { "from",		"_",		1,	0	},
  { "gamma",		NULL,		0,	EQ_CASE	},
  { "gcd",		NULL,		0,	0	},
  { "ge",		"\\geq",	0,	0	},
  { "geq",		NULL,		0,	0	},
  { "ggg",		NULL,		0,	0	},
  { "grad",		NULL,		0,	0	},
  { "grave",		NULL,		1,	0	},
  { "hat",		"\\widehat",	1,	0	},
  { "hbar",		NULL,		0,	0	},
  { "hom",		NULL,		0,	0	},
  { "hookleft",		NULL,		0,	0	},
  { "hookright",	NULL,		0,	0	},
  { "identical",	NULL,		0,	0	}, // LATER
  { "if",		NULL,		0,	0	},
  { "imag",		NULL,		0,	0	},
  { "image",		NULL,		0,	0	},
  { "imath",		NULL,		0,	0	},
  { "in",		NULL,		0,	0	},
  { "inf",		"\\infty",	0,	0	},
  { "infinity",		"\\infty",	0,	0	},
  { "infty",		NULL,		0,	0	},
  { "int",		NULL,		0,	0	},
  { "integral",		"\\int",	0,	0	},
  { "inter",		"\\bigcap",	0,	0	},
  { "iota",		NULL,		0,	EQ_CASE	},
  { "iso",		NULL,		0,	0	}, // ams
  { "it",		NULL,		0,	0	},
  { "jmath",		NULL,		0,	0	},
  { "kappa",		NULL,		0,	EQ_CASE	},
  { "ker",		NULL,		0,	0	},
  { "lambda",		NULL,		0,	EQ_CASE	},
  { "land",		NULL,		0,	0	}, // LATER
  { "langle",		NULL,		0,	0	},
  { "larrow",		"\\leftarrow",	0,	EQ_CASE	},
  { "lbrace",		NULL,		0,	0	},
  { "lbrack",		"[",		0,	0	},
  { "lceil",		NULL,		0,	0	},
  { "lcol",		NULL,		0,	0	}, // LATER
  { "ldots",		NULL,		0,	0	},
  { "le",		NULL,		0,	0	},
  { "left",		NULL,		0,	0	},
  { "leftarrow",	NULL,		0,	EQ_CASE	},
  { "leq",		NULL,		0,	0	},
  { "lfloor",		NULL,		0,	0	},
  { "lg",		NULL,		0,	0	},
  { "lim",		NULL,		0,	EQ_CASE	},
  { "line",		"\\vert",	0,	0	},
  { "liter",		"\\ell",	0,	0	},
  { "lll",		NULL,		0,	0	}, // ams
  { "ln",		NULL,		0,	0	},
  { "log",		NULL,		0,	0	},
  { "lor",		"\\vee",	0,	0	},
  { "lparen",		"(",		0,	0	},
  { "lpile",		NULL,		0,	0	}, // LATER
  { "lrarrow",	  "\\leftrightarrow",	0,	EQ_CASE	},
  { "lrharpoons", "\\leftrightharpoons",0,	0	},
  { "mapsto",		NULL,		0,	0	},
  { "massert",		"\\dashv",	0,	0	},
  { "matrix",		NULL,		0,	EQ_ENV	},
  { "max",		NULL,		0,	0	},
  { "mho",		NULL,		0,	0	}, // ams
  { "min",		NULL,		0,	0	},
  { "minusplus",	NULL,		0,	0	},
  { "mit",		"",		0,	0	}, // font
  { "mod",		"\\bmod",	0,	0	},
  { "models",		NULL,		0,	0	},
  { "msangle",		NULL,		0,	0	}, // LATER
  { "mu",		NULL,		0,	EQ_CASE	},
  { "nabla",		NULL,		0,	0	},
  { "ne",		NULL,		0,	0	},
  { "nearrow",		NULL,		0,	0	},
  { "neg",		NULL,		0,	0	},
  { "neq",		NULL,		0,	0	},
  { "nequiv",		NULL,		0,	0	},
  { "ni",		NULL,		0,	0	},
  { "not",		NULL,		0,	0	},
  { "notin",		NULL,		0,	0	},
  { "nu",		NULL,		0,	EQ_CASE	},
  { "nwarrow",		NULL,		0,	0	},
  { "odiv",		NULL,		0,	0	},
  { "odot",		NULL,		0,	0	},
  { "oint",		NULL,		0,	0	},
  { "omega",		NULL,		0,	EQ_CASE	},
  { "omicron",		NULL,		0,	EQ_CASE	},
  { "ominus",		NULL,		0,	0	},
  { "oplus",		NULL,		0,	0	},
  { "or ",		NULL,		0,	0	},
  { "oslash",		NULL,		0,	0	},
  { "otimes",		NULL,		0,	0	},
  { "over",		NULL,		1,	EQ_ATOP	},
  { "overline",		NULL,		1,	0	},
  { "owns",		"\\ni",		0,	0	},
  { "parallel",		NULL,		0,	0	},
  { "partial",		NULL,		0,	0	},
  { "phantom",		NULL,		0,	0	},
  { "phi",		NULL,		0,	EQ_CASE	},
  { "pi",		NULL,		0,	EQ_CASE	},
  { "pile",		NULL,		0,	0	}, // LATER
  { "plusminus",	"\\pm",		0,	0	},
  { "pmatrix",		NULL,		0,	EQ_ENV	},
  { "prec",		NULL,		0,	0	},
  { "prep",		NULL,		0,	0	},
  { "prime",		NULL,		0,	0	},
  { "prod",		NULL,		0,	0	},
  { "propto",		NULL,		0,	0	},
  { "psi",		NULL,		0,	EQ_CASE	},
  { "rangle",		NULL,		0,	0	},
  { "rarrow",		"\\rightarrow",	0,	EQ_CASE	},
  { "rbrace",		"]",		0,	0	},
  { "rbrace",		NULL,		0,	0	},
  { "rceil",		NULL,		0,	0	},
  { "rcol",		NULL,		0,	0	}, // LATER
  { "real",		"\\Re",		0,	0	},
  { "reimage",		NULL,		0,	0	},
  { "rel",		NULL,		0,	0	},
  { "rfloor",		NULL,		0,	0	},
  { "rho",		NULL,		0,	EQ_CASE	},
  { "right",		NULL,		0,	0	},
  { "rightarrow",	NULL,		0,	EQ_CASE	},
  { "rlharpoons",	NULL,		0,	0	},
  { "rm",		NULL,		0,	0	},
  { "root",		"\\sqrt",	1,	0	},
  { "rparen",		")",		0,	0	},
  { "rpile",		NULL,		0,	0	}, // LATER
  { "rtangle",		NULL,		0,	0	},
  { "sangle",		NULL,		0,	0	},
  { "scale",		NULL,		0,	0	},
  { "searrow",		NULL,		0,	0	},
  { "sec",		NULL,		0,	0	},
  { "sigma",		NULL,		0,	EQ_CASE	},
  { "sim",		NULL,		0,	0	},
  { "simeq",		NULL,		0,	0	},
  { "sin",		NULL,		0,	0	},
  { "sinh",		NULL,		0,	0	},
  { "slash",		NULL,		0,	0	},
  { "smallint",		NULL,		0,	0	},
  { "smallinter",	NULL,		0,	0	},
  { "smalloint",	NULL,		0,	0	},
  { "smallprod",	NULL,		0,	0	},
  { "smallsum",		NULL,		0,	0	},
  { "smallunion",	NULL,		0,	0	},
  { "smcoprod",		NULL,		0,	0	},
  { "sqcap",		NULL,		0,	0	},
  { "sqcup",		NULL,		0,	0	},
  { "sqrt",		NULL,		1,	0	},
  { "sqsubset",		NULL,		0,	0	},
  { "sqsubseteq",	NULL,		0,	0	},
  { "sqsupset",		NULL,		0,	0	},
  { "sqsupseteq",	NULL,		0,	0	},
  { "star",		NULL,		0,	0	},
  { "sub",		"_",		0,	0	},
  { "subset",		NULL,		0,	0	},
  { "subseteq",		NULL,		0,	0	},
  { "succ",		NULL,		0,	0	},
  { "sum",		NULL,		0,	0	},
  { "sup",		"^",		0,	0	},
  { "superset",		NULL,		0,	0	},
  { "supset",		NULL,		0,	0	},
  { "supseteq",		NULL,		0,	0	},
  { "swarrow",		NULL,		0,	0	},
  { "tan",		NULL,		0,	0	},
  { "tanh",		NULL,		0,	0	},
  { "tau",		NULL,		0,	EQ_CASE	},
  { "therefore",	NULL,		0,	0	},
  { "theta",		NULL,		0,	EQ_CASE	},
  { "tilde",		"\\widetilde",	1,	0	},
  { "times",		NULL,		0,	0	},
  { "to",		"^",		1,	0	},
  { "top",		NULL,		0,	0	},
  { "triangle",		NULL,		0,	0	},
  { "triangled",	NULL,		0,	0	},
  { "trianglel",	NULL,		0,	0	},
  { "triangler",	NULL,		0,	0	},
  { "triangleu",	NULL,		0,	0	},
  { "udarrow",		"\\updownarrow",0,	EQ_CASE	},
  { "under",		"\\underline",	1,	0	},
  { "underline",	"\\underline",	1,	0	},
  { "union",		"\\bigcup",	0,	0	},
  { "uparrow",		NULL,		0,	EQ_CASE	},
  { "uplus",		NULL,		0,	0	},
  { "upsilon",		NULL,		0,	EQ_CASE	},
  { "varepsilon",	NULL,		0,	0	},
  { "varphi",		NULL,		0,	0	},
  { "varpi",		NULL,		0,	0	},
  { "varrho",		NULL,		0,	0	},
  { "varsigma",		NULL,		0,	0	},
  { "vartheta",		NULL,		0,	0	},
  { "varupsilon",	NULL,		0,	0	},
  { "vdash",		NULL,		0,	0	},
  { "vdots",		NULL,		0,	0	},
  { "vec",		NULL,		1,	0	},
  { "vee",		NULL,		0,	0	},
  { "vert",		NULL,		0,	0	},
  { "wedge",		NULL,		0,	0	},
  { "wp",		NULL,		0,	0	},
  { "xi",		NULL,		0,	EQ_CASE	},
  { "xor",		NULL,		0,	0	},
  { "zeta",		NULL,		0,	EQ_CASE	}
};

static hwpeq *lookup_eqn(char *str)
{
  static int	eqCount = SAL_N_ELEMENTS(eq_tbl);
  int		m, k, l = 0, r = eqCount;
  hwpeq		*result = 0;

  while( l < r ) {
    m = (l + r) / 2;
    k = strcmp(eq_tbl[m].key, str);
    if( k == 0 ) {
      result = eq_tbl + m;;
      break;
    }
    else if( k < 0 )
      l = m + 1;
    else
      r = m;
  }
  return result;
}

/* 첫자만 대문자이거나 전부 대문자면 소문자로 바꾼다. */

static char *make_keyword( char *keyword, const char *token)
{
  assert(keyword);
  char	*ptr;
  int	result = true, len = strlen(token);

  if( 255 < len )
    strncpy(keyword, token, 255);
  else
    strcpy(keyword, token);

  if( (token[0] & 0x80) || islower(token[0]) ||
      strlen(token) < 2 )
    return keyword;

  int capital = isupper(keyword[1]);
  for( ptr = keyword + 2; *ptr && result; ptr++ )
    if( (*ptr & 0x80) ||
    (!capital && isupper(*ptr)) ||
    (capital && islower(*ptr)) )
      result = false;

  if( result ) {
    ptr = keyword;
    while( *ptr ) {
      if( isupper(*ptr) )
    *ptr = sal::static_int_cast<char>(tolower(*ptr));
      ptr++;
    }
  }
  return keyword;
}

// token reading funtion
struct eq_stack {
  MzString	white;
  MzString	token;
  istream	*strm;

  eq_stack() { strm = 0; };
  bool state(istream *s) {
    if( strm != s) { white = 0; token = 0; }
    return token.length() != 0;
  }
};

static eq_stack *stk = 0;

void push_token(MzString &white, MzString &token, istream *strm)
{
  // one time stack
  assert(stk->state(strm) == false);

  stk->white = white;
  stk->token = token;
  stk->strm = strm;
}

/*
 읽은 토큰의 길이를 반환한다. */
/* control char, control sequence, binary sequence,
   alphabet string, sigle character */
static int next_token(MzString &white, MzString &token, istream *strm)
{
  register int	ch = 0;

  if( stk->state(strm) ) {
    white = stk->white;
    token = stk->token;
    stk->token = 0;
    stk->white = 0;
    return token.length();
  }

  token = 0;
  white = 0;
  if( !strm->good() || (ch = strm->get()) == EOF )
    return 0;

  // read preceeding ws
  if( IS_WS(ch) ) {
    do white << (char) ch;
    while( IS_WS(ch = strm->get()) );
  }

  if( ch == '\\' || ch & 0x80 || isalpha(ch) ) {
    if( ch == '\\' ) {
      token << (char) ch;
      ch = strm->get();
    }
    do {
      token << (char) ch;
      ch = strm->get();
    } while( ch != EOF && (ch & 0x80 || isalpha(ch)) ) ;
    strm->putback(sal::static_int_cast<char>(ch));
    /*  sub, sub, over, atop 특수 처리
        그 이유는 next_state()에 영향을 미치기 때문이다.
     */
    if( !STRICMP("sub", token) || !STRICMP("from", token) ||
    !STRICMP("sup", token) || !STRICMP("to", token) ||
    !STRICMP("over", token) || !STRICMP("atop", token) ||
    !STRICMP("left", token) || !STRICMP("right", token) )
    {
      char buf[256];
      make_keyword(buf, token);
      token = buf;
    }
    if( !token.compare("sub") || !token.compare("from") )
      token = "_";
    if( !token.compare("sup") || !token.compare("to") )
      token = "^";
  }
  else if( IS_BINARY(ch) ) {
    do token << (char) ch;
    while( IS_BINARY(ch = strm->get()) );
    strm->putback(sal::static_int_cast<char>(ch));
  }
  else if( isdigit(ch) ) {
    do token << (char) ch;
    while( isdigit(ch = strm->get()) );
    strm->putback(sal::static_int_cast<char>(ch));
  }
  else
    token << (char) ch;
  // cout << "WHITE : " << "|" << white.c_str() << "|" << endl ;
  // cout << "TOKEN : " << "|" << token.c_str() << "|" << endl ;
  return token.length();
}

static int read_white_space(MzString& outs, istream *strm)
{
  int	ch, result;

  if( stk->state(strm) ) {
    outs << stk->white;
    stk->white = 0;
    result = stk->token[0];
  }
  else {
    while( IS_WS(ch = strm->get()) )
      outs << (char )ch;
    strm->putback(sal::static_int_cast<char>(ch));
    result = ch;
  }
  return result;
}

/* 인수가 필요하지 않은 경우 각 항목간의 구분은 space와 brace
     sqrt {ab}c = sqrt{ab} c
   (, }는 grouping
   ^, _ 는 앞뒤로 결합한다.

   sqrt 등과 같이 인수가 있는 형식 정리
     sqrt a  -> sqrt{a}
     sqrt {a}    -> sqrt{a}
   1 이상의 인수가 있는 경우 인수들간의 역백은 없앤다.
     \frac a b   -> frac{a}{b}
   over의 형식 정리
     a over b    -> {a}over{b}
 */

static int eq_word(MzString& outs, istream *strm, int status)
{
  MzString	token, white, state;
  int		ch, result, nargs;
  char		keyword[256];
  hwpeq		*eq;
#ifdef DEBUG
  static int	slevel = 0;

  slevel++;
#endif
  next_token(white, token, strm);
  if (token.length() <= 0)
      return 0;
  result = token[0];

  if( token.compare("{") == 0 ) {
    state << white << token;
    eq_sentence(state, strm, "}");
  }
  else if( token.compare("left") == 0 ) {
    state << white << token;
    next_token(white, token, strm);
    state << white << token;

    eq_sentence(state, strm, "right");

    next_token(white, token, strm);
    state << white << token;
  }
  else {
    /* 정상적인 token */
    int script_status = SCRIPT_NONE;
    while( 1 ) {
      state << white << token;
      make_keyword(keyword, token);
      if( token[0] == '^' )
    script_status |= SCRIPT_SUP;
      else if( token[0] == '_' )
    script_status |= SCRIPT_SUB;
      else
    script_status = SCRIPT_NONE;

      if( 0 != (eq = lookup_eqn(keyword)) ) {
    nargs = eq->nargs;
    while( nargs-- ) {
      ch = read_white_space(state, strm);
      if( ch != '{' ) state << '{';
      eq_word(state, strm, script_status);
      if( ch != '{' ) state << '}';
    }
      }

      if( !next_token(white, token, strm) )
    break;
      // end loop and restart with this
      if( (token[0] == '^' && status && !(status & SCRIPT_SUP)) ||
          (token[0] == '_' && status && !(status & SCRIPT_SUB)) ||
      strcmp("over", token) == 0 || strcmp("atop", token) == 0 ||
      strchr("{}#&`", token[0]) ||
      (!strchr("^_", token[0]) && white.length()) ) {
    push_token(white, token, strm);
    break;
      }
    }
  }
  outs << state;
#ifdef DEBUG
  //if( slevel == 1 )
   // cout << "LEVEL[" << slevel << "]" << state << "\n";
  slevel--;
#endif
  return result;
}

static bool eq_sentence(MzString& outs, istream *strm, const char *end)
{
  MzString	state;
  MzString	white, token;
  bool		multiline = false;

  read_white_space(outs, strm);
  while( eq_word(state, strm) ) {
    if( !next_token(white, token, strm) ||
    (end && strcmp(token.c_str(), end) == 0) )
    {
      state << white << token;
      break;
    }
    push_token(white, token, strm);
    if( !token.compare("atop") || !token.compare("over") )
      outs << '{' << state << '}';
    else {
      if( !token.compare("#") )
    multiline = true;
      outs << state;
    }
    state =  0;
    read_white_space(outs, strm);
  }
  outs << state;
  return multiline;
}

static char eq2ltxconv(MzString& sstr, istream *strm, const char *sentinel)
{
  MzString	white, token;
  char		key[20];
  int		ch, result;
  hwpeq		*eq = 0;

  while( 0 != (result = next_token(white, token, strm)) ) {
    if( sentinel && (result == 1) && strchr(sentinel, token[0]) )
      break;
    make_keyword(key, token);
    if( (eq = lookup_eqn(key)) != 0 ) {
      if( eq->latex )
    strcpy(key, eq->latex);
      else {
    key[0] = '\\';
    strcpy(key + 1, eq->key);
      }
      if( (eq->flag & EQ_CASE) && isupper(token[0]) )
    key[1] = sal::static_int_cast<char>(toupper(key[1]));
      token = key;
    }

    if( token[0] == '{' ) {	// grouping
      sstr << white << token;
      eq2ltxconv(sstr, strm, "}");
      sstr << '}';
    }
    else if( eq && (eq->flag & EQ_ENV) ) {
      next_token(white, token, strm);
      if( token[0] != '{' ) return 0;
      sstr << "\\begin" << "{" << eq->key << "}" << ENDL ;
      eq2ltxconv(sstr, strm, "}");
      if( sstr[sstr.length() - 1] != '\n' )
    sstr << ENDL ;
      sstr << "\\end" << "{" << eq->key << "}" << ENDL ;
    }
    else if( eq && (eq->flag & EQ_ATOP) ) {
      if( sstr.length() == 0 )
    sstr << '{';
      else {
    int pos  = sstr.rfind('}');
    if( 0 < pos)
      sstr.replace(pos, ' ');
      }
      sstr << token;
      while( (ch = strm->get()) != EOF && IS_WS(ch) )
    sstr << (char)ch;
      if( ch != '{' )
    sstr << "{}";
      else {
    eq2ltxconv(sstr, strm, "}");
    sstr << '}';
      }
    }
    else
      sstr << white << token;
  }
  return token[0];
}

void eq2latex(MzString& outs, char *s)
{
  assert(s);
  if( stk == 0 )
    stk = new eq_stack;

  MzString	tstr;

  istringstream	tstrm(s);
  bool eqnarray = eq_sentence(tstr, &tstrm);
#ifdef TEST
  cout << "Step1: " << endl << tstr.c_str() << endl;
#endif
  istringstream	strm(tstr.c_str());

  if( eqnarray )
    outs << "\\begin{array}{rllll}" << ENDL;
  eq2ltxconv(outs, &strm, 0);
  outs << ENDL;
  if( eqnarray )
    outs << "\\end{array}" << ENDL;
  delete stk;
  stk = 0;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
