// FILE. . . . . d:/hak/hlt/src/hlt/fot/fuz/syntax/sources/FuzzyFOTLatticeTokenizer.java
// EDIT BY . . . Hassan Ait-Kaci
// ON MACHINE. . Hak-Laptop
// STARTED ON. . Sun Jul 15 08:11:51 2018

/**
 * @version     Last modified on Wed Oct 09 23:01:48 2019 by hak
 * @author      <a href="mailto:hak@acm.org">Hassan A&iuml;t-Kaci</a>
 * @copyright   &copy; <a href="http://www.hassan-ait-kaci.net/">by the author</a>
 */

import java.io.*;

import java.util.Date;

import hlt.language.syntax.*;
import hlt.language.util.Location;
import hlt.language.io.StreamTokenizer;
import hlt.language.io.IncludeReader;
import hlt.language.io.CircularInclusionException;

/**
 * This defines a tokenizer for an interactive language for a specifier
 * of first-order terms and an evaluator of lattice operations on
 * first-order terms modulo a similarity (<i>i.e.</i>, a fuzzy
 * equivalence) relation on the term's operator <a
 * href=../../../../../../../doc/hlt/code/fot/Signature.html>Signature</a>,
 * a set of <a
 * href=../../../../../../../doc/hlt/code/fot/Functor.html>Functor</a>.
 * a language. This similarity is represented as <a
 * href=../../../../../../../doc/hlt/code/fot/fuz/SignatureSimilarity.html>SignatureSimilarity</a>
 * and is meant to provide all the structure and methods of such an
 * algebra.
 *
 * As usual, the details are in the method <tt>nextToken()</tt>.
 */
public class FuzzyFOTLatticeTokenizer implements Tokenizer
{
  IncludeReader reader;
  StreamTokenizer input;
  String file = "stdin";

  public static boolean interactive;  
  static String prompt = "FFF";
  static int lineNumber = 1;

  FuzzyFOTLatticeTokenizer () throws IOException
    {
      setReader(new InputStreamReader(System.in));
      interactive = true;
      banner();
      prompt();
    }

  FuzzyFOTLatticeTokenizer (File file) throws IOException
    {
      setReader(new FileReader(file));
      this.file = file.toString();
      interactive = false;
      banner();
      System.out.println("*** Parsing file: "+file);
    }

  public final void setReader (Reader rd)
    {
      setupReader(rd);
      input = new StreamTokenizer(reader);
      input.ordinaryChars("+-/\\;:~");
      input.setType('.',StreamTokenizer.NUMERIC);
      input.parseNumbers(true);
      input.wordChar('#'); // to identify pragma strings
      input.quoteChar('\'');
      input.quoteChar('"');
      input.wordChar('_');
    }

  public final int lineNumber()
    {
      return input.getLineNumber();
    }

  public final void setupReader (Reader rd)
    {
      reader = rd instanceof IncludeReader ? (IncludeReader)rd : new IncludeReader(rd);
    }

  public final Reader getReader ()
    {
      return reader;
    }

  final void setFile (String file)
    {
      reader.setFile(file);
    }

  public final void include (String file) throws FileNotFoundException, CircularInclusionException
    {
      reader.include(file);
    }

  public static final void prompt ()
    {
      if (interactive)
        {
          System.out.print("\n"+
			   prompt+
			   " "+
			   Integer.toString(lineNumber++)+
			   "> ");
        }
    }

  // static public final void setPrompt(String p)
  //   {
  //     prompt = p;
  //   }

  // The following is an ugly hack to get javac to remember the compile
  // date by extracting it from the system's class file converted to a
  // URI (!!!). There is no simple way and I am too lazy to worry about
  // why the Java designers thought this idiotic shortcoming of precious
  // compile-time info is cool! This hack below is the simplest one I
  // could find on the Net. (See
  // https://stackoverflow.com/questions/3336392/java-print-time-of-last-compilation)
  final Date compileDate ()
  {
    Date date = new Date();

    try
      {
	date = new Date(new File(getClass()
				 .getClassLoader()
				 .getResource(getClass()
					      .getCanonicalName()
					      .replace('.', '/')
					      + ".class")
				 .toURI())
			.lastModified());
      }

    catch (java.net.URISyntaxException e)
      // thrown by toURI()
      {
	System.err.println("*** Bad URI syntax in compileDate in tokenizer: "+e);
      }

    return date;
  }

// see: https://www.hassan-ait-kaci.net/hlt/src/hlt/fot/fuz/syntax/docs/FuzzyFOTLatticeDoc/000StartHere.html

  String banner =
    "\n"+
    "************************************************************************\n"+
    "***\n"+
    "*** This is FFF: HAK's Fuzzy Fun Facility \n"+
    "*** A Java tool for fuzzy lattice operations over similar data structures\n"+
    "*** Implemented by Hassan Aït-Kaci - HAK Language Technologies (hak@acm.com)\n"+
    "*** \n"+
//  "************************************************************************\n"+
    "*** Version compiled on "+compileDate()+"\n"+
    "*** \n"+
    "*** Upon the FFF prompt, enter '#info;' for info, or 'quit;' to quit\n"+
    "*** [enter ';' if no FFF prompt shows it ready for next user input]\n"+
    "***\n"+
    "************************************************************************\n"+
    "\n"+
    "------------------------------------------------------------------------\n"+
    "*** Starting FFF session on "+ (new Date())+"\n"+
    "------------------------------------------------------------------------";

  final void banner ()
    {
      System.out.println(banner);
    }

  final boolean isOtherChar (int c)
    {
      return input.isOrdinaryChar(c)
        && !(input.isWhitespaceChar(c) || c == '(' || c == ')' || c == ',' || c == '.');
    }

  final ParseNode locate (ParseNode node)
    {
      return ((ParseNode)node.setStart(input.tokenStart()).setEnd(input.tokenEnd()))
	.setFile(file);
    }

  final ParseNode locate (ParseNode node, Location start)
    {
      return ((ParseNode)node.setStart(start).setEnd(input.tokenEnd()))
	.setFile(file);
    }

  private boolean isPragma (String symbol)
  {
    return symbol.charAt(0) == '#';
  }

  private ParseNode pragmaToken (String symbol)
  {
    // set the parser's current pragma
    FuzzyFOTLatticeMain.parser.setPragma(symbol);
    
    switch (symbol)
      {
      case "info":
	return GenericParser.symbolToken("INFO",symbol);
      case "fun":
	return GenericParser.symbolToken("FUN",symbol);
      case "sig":
	return GenericParser.symbolToken("SIG",symbol);
      case "sim":
	return GenericParser.symbolToken("SIM",symbol);
      case "close":
	return GenericParser.symbolToken("CLOSE",symbol);
      case "show":
	return GenericParser.symbolToken("SHOW",symbol);
      case "eqs":
	return GenericParser.symbolToken("EQS",symbol);
      case "funclass":
	return GenericParser.symbolToken("FUNCLASS",symbol);
      case "termclass":
	return GenericParser.symbolToken("TERMCLASS",symbol);
      case "funrep":
	return GenericParser.symbolToken("FUNREP",symbol);
      case "termrep":
	return GenericParser.symbolToken("TERMREP",symbol);
      case "map":
	return GenericParser.symbolToken("MAP",symbol);
      case "comp":
	return GenericParser.symbolToken("COMP",symbol);
      case "load":
	return GenericParser.symbolToken("LOAD",symbol);
      case "trace":
	return GenericParser.symbolToken("TRACE",symbol);
      case "reset":
	return GenericParser.symbolToken("RESET",symbol);
      }
    return GenericParser.error("'#"+symbol+"': unknown pragma");
  }

  private boolean isVariable (String symbol)
  {
    char start = symbol.charAt(0);
    if (Character.isUpperCase(start) || start == '_')
      return true;

    return false;
  }

  // void doneLoading ()
  // {
  //   System.out.println("*** Done reading from file: "+FuzzyFOTLatticeMain.parser.currentFile());
  //   FuzzyFOTLatticeMain.parser.fileStack.pop();
  //   System.out.println("<<< fileStack = "+FuzzyFOTLatticeMain.parser.fileStack);
  //   if (FuzzyFOTLatticeMain.parser.fileStack.isEmpty())
  //     interactive = true;
  //   System.err.println(">>> #load pragma done");
  // }

  public ParseNode nextToken () throws IOException
    {
      ParseNode t = null;
      Location tokenStart = null;
      
      switch (input.nextTokenType())
        {
        case StreamTokenizer.TT_EOF:
	  // doneLoading();
	  // System.out.println("*** Closing reader");
          reader.close();
          locate(t = GenericParser.E_O_I);
          break;
        case '\'': case '"':
	  t = GenericParser.symbolToken("STRING",input.sval);
	  locate(t);
	  break;
        case StreamTokenizer.TT_WORD:
	  if (input.sval == "exit" || input.sval == "quit" || input.sval == "halt")
	    t = GenericParser.literalToken("exit");
	  else
	    if (isPragma(input.sval))
	      t = pragmaToken(input.sval.substring(1));
	    else
	      if (isVariable(input.sval))
		t = GenericParser.symbolToken("VARIABLE",input.sval);
	      else
		t = GenericParser.symbolToken("FUNCTOR",input.sval);
	  locate(t);
          break;
        case StreamTokenizer.TT_NUMBER:
          if (input.isInteger)
	    {
	      int n = (int)input.nval;
	      if (n < 0)
		t = GenericParser.error("negative arity or argument position ('"+n+"'))");
	      else
		t = GenericParser.numberToken("NATURAL",n);
	    }
          else
	    {
	      double value = input.nval;
	      if (value < 0 || value > 1)
		t = GenericParser.error(value+": a fuzzy degree must be within [0.0,1.0]");
	      else
		t = GenericParser.numberToken("FUZZYVAL",value);
	    }
	  locate(t);
          break;
        case '~':
	  locate(t = GenericParser.literalToken("EQS"));
          break;
        case '/':
	  tokenStart = input.tokenStart();
	  if (input.nextToken() == '\\')
	    t = GenericParser.literalToken("INF");
	  else
	    {
	      input.pushBack();
	      t = GenericParser.literalToken("/");
	    }
	  locate(t,tokenStart);
	  break;
	case '\\':
	  tokenStart = input.tokenStart();
	  if (input.nextToken() == '/')
	    t = GenericParser.literalToken("SUP");
	  else
	    {
	      input.pushBack();
	      t = GenericParser.literalToken("\\");
	    }
	  locate(t,tokenStart);
	  break;
        case '(': case ')': case ',': case '.': case ';': case ':': case '=':
          locate(t = GenericParser.literalToken(String.valueOf((char)input.ttype)));
          break;
        default: // read the longest possible token and return it as a symbol
	  tokenStart = input.tokenStart();
          StringBuffer functor = new StringBuffer(String.valueOf((char)input.ttype));
          input.spaceIsSignificant(true);
	  while (isOtherChar(input.peek()))
	    functor.append(String.valueOf((char)input.nextToken()));	  
          input.spaceIsSignificant(false);
          t = GenericParser.symbolToken("FUNCTOR",functor.toString());
	  locate(t,tokenStart);
          break;
        }

      //      System.out.println(">>> Read token: "+t);
      return t;
    }
}
