%//
%// MTOM written in java-MTOM using the ATerm Library from Loria
%//

%{
import java.util.*;
import java.io.*;

import aterm.api.*;
import aterm.pure.*;

public class jtom3 {
  private static String iSuffix = ".tfix";
  private static String oSuffix = ".tom.c";
  private static List emptyList = new ArrayList();
  
  private ATermFactory factory;
  private FileInputStream input;
  private OutputCode output;
  
  private AFun f_Tom;
  private AFun f_DeclGL;
  private AFun f_MainGL;
  private AFun f_DeclListTom;
  private AFun f_RuleListTom;
  
  private AFun f_Rule; 
  private AFun f_RhsGL; 
  private AFun f_SortDecl; 
  private AFun f_SortsToSort; 
  private AFun f_GetFunSymDecl;
  private AFun f_GetSubtermDecl;
  private AFun f_TermsEqualDecl;
  private AFun f_VariableDecl;
  private AFun f_Variable;
  private AFun f_GL;
  private AFun f_ApiGL;
  private AFun f_CodeGL;
  private AFun f_SymbolDecl;
  private AFun f_SymbolTom;
  private AFun f_Appl;

  private AFun f_empty;
  private AFun f_cons;
  private AFun f_sort;

    // Goal Language 
  private AFun f_TomGL;
  private AFun f_RuleSortedListTom;
  private AFun f_RuleSetGL;
  private AFun f_VariableList;
  private AFun f_IfThenElseGL;
 
  public ATermList empty() {
    return factory.makeList();
  }

  public ATermList cons(ATerm t, ATermList l) {
    return l.insert(t);
  }


  public jtom3(String name, ATermFactory factory) {
    this.factory = factory;

    try {
      this.input = new FileInputStream(name + iSuffix);
      this.output = new OutputCode(new BufferedWriter(new FileWriter(name + oSuffix)));
    } catch (java.io.FileNotFoundException e) {
      System.out.println("File " + input + " not found.");
    } catch (IOException e2) {
      System.out.println(e2);
    }

      // AFun initialisation
    f_Tom = factory.makeAFun("Tom", 4, false);
    f_DeclGL = factory.makeAFun("DeclGL", 1, false);
    f_MainGL = factory.makeAFun("MainGL", 1, false);
    
    f_DeclListTom = factory.makeAFun("DeclListTom", 1, false);
    f_RuleListTom = factory.makeAFun("RuleListTom", 1, false);
    
    f_Rule = factory.makeAFun("Rule", 2, false);
    f_RhsGL = factory.makeAFun("RhsGL", 1, false);
    f_SortDecl = factory.makeAFun("SortDecl", 1, false);
    f_SortsToSort = factory.makeAFun("SortsToSort", 2, false);
    
    f_GetFunSymDecl = factory.makeAFun("GetFunSymDecl", 2, false);
    f_GetSubtermDecl = factory.makeAFun("GetSubtermDecl", 3, false);
    f_TermsEqualDecl = factory.makeAFun("TermsEqualDecl", 3, false);
    f_VariableDecl = factory.makeAFun("VariableDecl", 1, false);
    f_Variable = factory.makeAFun("Variable", 2, false);
    f_GL = factory.makeAFun("GL", 1, false);
    f_ApiGL = factory.makeAFun("ApiGL", 1, false);
    f_CodeGL = factory.makeAFun("CodeGL", 1, false);
    f_SymbolDecl = factory.makeAFun("SymbolDecl", 1, false);
    f_SymbolTom = factory.makeAFun("SymbolTom", 4, false);
    f_Appl = factory.makeAFun("Appl", 2, false);
    
    f_empty = factory.makeAFun("empty", 0, false);
    f_cons = factory.makeAFun("cons", 2, false);
    f_sort = factory.makeAFun("sort", 1, false);
    
      // Goal Language
    f_TomGL = factory.makeAFun("TomGL", 4, false);
    f_RuleSortedListTom = factory.makeAFun("RuleSortedListTom", 2, false);
    f_RuleSetGL = factory.makeAFun("RuleSetGL", 3, false);
    f_VariableList = factory.makeAFun("VariableList", 1, false);
    f_IfThenElseGL = factory.makeAFun("IfThenElseGL", 3, false);
  }

%}

%type ATerm ATerm
%type ATermList ATermList
%type int int
%type void void
%type ATermTable Map
%type File OutputCode

%GET_FUN_SYM ATerm t (((ATermAppl)t).getAFun())
%GET_SUBTERM ATerm t n (((ATermAppl)t).getArgument(n))

%GET_FUN_SYM ATermList l ((((ATermList)l).isEmpty())?f_empty:f_cons)
%GET_SUBTERM ATermList l n ((n==0)?((ATermList)l).getFirst():(ATerm)(((ATermList)l).getNext()))

%// ------------------------------------------------------------

%sym ATerm compile(ATerm)
%sym void prettyPrint(File, ATerm)
%sym void prettyPrintList(File,ATermList)

%sym ATerm Tom(ATerm, ATerm, ATerm, ATerm)      % f_Tom
%sym ATerm DeclGL(ATerm)			% f_DeclGL
%sym ATerm MainGL(ATerm)			% f_MainGL

%sym ATerm DeclListTom(ATermList)	        % f_DeclListTom
%sym ATerm RuleListTom(ATermList)	        % f_RuleListTom

%sym ATerm Rule(ATerm, ATerm)	                % f_Rule
%sym ATerm RhsGL(ATerm)	                        % f_RhsGL
%sym ATerm SortDecl(ATerm)	                % f_SortDecl
%sym ATerm SortsToSort(ATermList,ATerm)         % f_SortsToSort

%sym ATerm GetFunSymDecl(ATerm,ATerm)	        % f_GetFunSymDecl
%sym ATerm GetSubtermDecl(ATerm,ATerm,ATerm)	% f_GetSubtermDecl
%sym ATerm TermsEqualDecl(ATerm,ATerm,ATerm)	% f_TermsEqualDecl
%sym ATerm VariableDecl(ATerm)	                % f_VariableDecl
%sym ATerm Variable(ATerm,ATerm)	        % f_Variable
%sym ATerm GL(ATerm)	                        % f_GL
%sym ATerm ApiGL(ATerm)	                        % f_ApiGL
%sym ATerm CodeGL(ATerm)                        % f_CodeGL
%sym ATerm SymbolDecl(ATerm)	                % f_SymbolDecl
%sym ATerm SymbolTom(ATerm,ATerm,ATerm,ATerm)   % f_SymbolTom
%sym ATerm Appl(ATerm,ATermList)                % f_Appl

%sym ATermList empty()                          % f_empty
%sym ATermList cons(ATerm, ATermList)           % f_cons
%sym ATermList sort(ATermList)                  % f_sort

%// Goal Language
%sym ATerm TomGL(ATerm, ATermList, ATermList, ATerm)    % f_TomGL
%sym ATerm RuleSortedListTom(ATerm, ATermList)	        % f_RuleSortedListTom
%sym ATerm RuleSetGL(ATerm, ATerm, ATermList)           % f_RuleSetGL
%sym ATerm VariableList(ATermList)                      % f_VariableList
%sym ATerm IfThenElseGL(ATerm, ATerm, ATerm)            % f_IfThenElseGL
%sym void collectVariable(ATermTable, ATerm)

%// ------------------------------------------------------------

%var ATerm t, v1, v2, v3, v4
%var ATermList l, l1, l2

%var ATerm vDeclGL, vMainGL
%var ATermList vDeclList, vRuleList
%var ATerm name1, sort1, name2, sort2
%var ATermTable vTable
%var File out
%// ------------------------------------------------------------

%rule compile(Tom(v1,v2,v3,v4)) %--> {
  ATerm declPart     = compile(v1);
  ATerm declListPart = compile(v2);
  ATerm ruleListPart = compile(v3);
  ATerm mainPart     = compile(v4);
  ATerm[] args = new ATerm[] {declPart,declListPart,ruleListPart,mainPart};
  return factory.makeAppl(f_TomGL,args);
}

%rule compile(DeclGL(t)) %--> return t;

%rule compile(DeclListTom(l)) %--> {
  ATermList res = empty();
  ATerm c;
  while(!l.isEmpty()) {
    c = compile(l.getFirst());
    if(c != null) {
      res = cons(c,res);
    }
    l = l.getNext();
  }
  return (ATerm) res;
}

%rule compile(RuleListTom(l)) %--> {
  Map table = new HashMap();
  ATerm c, rule, topSymbol;
  ATermList ruleList;
  ATermList res = empty();
  
  while(!l.isEmpty()) {
    rule = l.getFirst();
    topSymbol = getSymbolRule(rule);
    ruleList = (ATermList)table.get(topSymbol);
    if(ruleList == null) {
      ruleList = empty();
    }
    ruleList = ruleList.append(rule);
    table.put(topSymbol,(ATerm)ruleList);
    l = l.getNext();
  }

  Iterator it = table.keySet().iterator();
  while(it.hasNext()) {
      //ATprintf("key = %t\n",ATgetFirst(keyList));
    topSymbol = (ATerm)it.next();
    ruleList = (ATermList)table.get(topSymbol);
      //ATprintf("rules = %l\n",ruleList);

      /*
       * for each ruleList: build a matching automaton
       */
    c = compile((ATerm)factory.makeAppl(f_RuleSortedListTom,topSymbol,(ATerm)ruleList));
    if(c != null) {
      res = cons(c,res);
    }
  }
  return (ATerm)res;
}
 
%rule compile(RuleSortedListTom(t,l)) %--> {
    // t is the top-symbol
  ATerm lhs,rhs;
  ATermList result = empty();
  String path = "";
  String s;
  
  Map table = new HashMap();
  ATerm variableList;
  int nextlab = 0;

  List list;
  while(!l.isEmpty()) {
    list = l.getFirst().match("Rule(,RhsGL())");
    lhs = (ATerm)list.get(0);
    rhs = (ATerm)list.get(1);
    
    collectVariable(table,lhs);

    if(Flags.cCode) {
      result = genOneToOneMatching(result,lhs,rhs,path,nextlab+1,0);
      result = result.append(makeGL(decapString(rhs)));
      s = "lab" + (nextlab+1) + ":;";
      result = result.append(makeGL(s));
    } else if(Flags.jCode) {
      s = "ok=true; lab" + (nextlab+1) + ":";
      result = result.append(makeGL(s));
      result = result.append(makeGL("while(ok) {"));
      result = genOneToOneMatching(result,lhs,rhs,path,nextlab+1,1);
      result = result.append(makeGL(decapString(rhs)));
      result = result.append(makeGL("}"));
    }
    
    nextlab++;
    l = l.getNext();
  }

  ATermList al = empty();
  Iterator it = table.keySet().iterator();
  while(it.hasNext()) {
    al = al.insert((ATerm)it.next());
  }
  
  variableList = (ATerm)factory.makeAppl(f_VariableList,(ATerm)al);
    //ATprintf("keyList = %l\n",keyList);
  return (ATerm)factory.makeAppl(f_RuleSetGL,t,variableList,(ATerm)result);
}

%rule collectVariable(vTable,Appl(v1,empty)) %--> {
  return;
}

%rule collectVariable(vTable,Appl(v1,l)) %--> {
  while(!l.isEmpty()) {
    collectVariable(vTable,l.getFirst());
    l = l.getNext() ;
  }
  return;
}

%rule collectVariable(vTable,Variable(name1,sort1)) %--> {
  ATerm variable = _2;
  vTable.put(variable,variable);
  return;
}


%rule compile(MainGL(t)) %--> return t;

%rule compile(SortDecl(t)) %--> return null;
%rule compile(VariableDecl(t)) %--> return null;

%rule compile(GetFunSymDecl(Variable(name1,sort1),ApiGL(t))) %--> {
  String s = "";
  if(Flags.cCode) {
    s = "#define GET_FUN_SYM_" + decapString(((ATermAppl) sort1).getArgument(0)) +
      "(" + decapString(name1) + ") " + decapString(t);
  } else if(Flags.jCode) {
    s = "Object GET_FUN_SYM_" + decapString(((ATermAppl) sort1).getArgument(0)) +
      "(Object " + decapString(name1) + ") { return " + decapString(t) + "; }";
  }
    //printf("*** s= '%s'\n",s);
  return makeGL(s);
}

%rule compile(GetSubtermDecl(Variable(name1,sort1),Variable(name2,sort2),ApiGL(t))) %--> {
  String s = "";
  if(Flags.cCode) {
    s = "#define GET_SUBTERM_" + decapString(((ATermAppl) sort1).getArgument(0)) +
      "(" + decapString(name1) + "," + decapString(name2) + ") " + decapString(t);
  } else if(Flags.jCode) {
    s = "Object GET_SUBTERM_" + decapString(((ATermAppl) sort1).getArgument(0)) +
      "(Object " + decapString(name1) + ",int " + decapString(name2) + ") { return " + decapString(t) + "; }";
  }
  return makeGL(s);
}

%rule compile(TermsEqualDecl(Variable(name1,sort1),Variable(name2,sort2),ApiGL(t))) %--> {
  String s = "";
  if(Flags.cCode) {
    s = "#define TERMS_EQUAL_" + decapString(((ATermAppl) sort1).getArgument(0)) +
      "(" + decapString(name1) + "," + decapString(name2) + ") " + decapString(t);
  } else if(Flags.jCode) {
    s = "boolean TERMS_EQUAL_" + decapString(((ATermAppl) sort1).getArgument(0)) +
      "(Object " + decapString(name1) + ",Object " + decapString(name2) +
      ") { return " + decapString(t) + "; }";
  }
  return makeGL(s);
}

%rule compile(SymbolDecl(SymbolTom(name1,SortsToSort(l,sort1),v2,v3))) %--> {
  String s;
  int argno=1;
  if(Flags.cCode) {
    if(!l.isEmpty()) {
      s = getSortGL(sort1) + " " + decapString(name1);
      
      if(!l.isEmpty()) {
        s += "(";
        while (!l.isEmpty()) {
          s += getSortGL(l.getFirst()) + " _" + argno;
          argno++;
          l = l.getNext() ;
          if(!l.isEmpty()) {
            s += ",";
          }
        }
        s += ");";
      }
        //printf("%s\n",s);
      return makeGL(s);
    }
  }
  return null;
}


%rule compile(t) %--> {
  System.out.println("Cannot compile: " + t);
  System.exit(1);
}
 
%// ------------------------------------------------------------

%rule prettyPrint(out,TomGL(vDeclGL,vDeclList,vRuleList,vMainGL)) %--> {
  out.write(decapString(vDeclGL) + "\n"); 
  prettyPrintList(out,vDeclList); 
  prettyPrintList(out,vRuleList); 
  out.write(decapString(vMainGL) + "\n");
  return;
}

%rule prettyPrint(out,SortDecl(v1)) %--> {
  out.write("%type " + getSortTom(v1) + " " + getSortGL(v1) + "\n");
  return;
}

%rule prettyPrint(out,GetFunSymDecl(Variable(name1,sort1),ApiGL(v3))) %--> {
  out.write("%GET_FUN_SYM " + getSortTom(sort1) + " " + decapString(name1) + " " + decapString(v3) + "\n");
  return;
}

%rule prettyPrint(out,GetSubtermDecl(Variable(name1,sort1),Variable(name2,sort2),ApiGL(v3))) %--> {
  out.write("%GET_SUBTERM " + getSortTom(sort1) + " " + decapString(name1) + " " + decapString(name2) +
            " " + decapString(v3) + "\n");
  return;
}

%rule prettyPrint(out,TermsEqualDecl(Variable(name1,sort1),Variable(name2,sort2),ApiGL(v3))) %--> {
  out.write("%TERMS_EQUAL " + getSortTom(sort1) + " " + decapString(name1) + " " + decapString(name2) +
            " " + decapString(v3) + "\n");
  return;
}

%// removed here 

%rule prettyPrint(out,RuleSetGL(SymbolTom(name1,SortsToSort(l,sort1),v2,CodeGL(v3)),v1,l2)) %--> {
  String s;
  int argno=1;
  if(!l.isEmpty()) {
    s = getSortGL(sort1) + " " + decapString(name1);
    if(!l.isEmpty()) {
      s += "(";
      while (!l.isEmpty()) {
        s += getSortGL(l.getFirst()) + " _" + argno;
        argno++;
        l = l.getNext() ;
        if(!l.isEmpty()) {
          s += ",";
        }
      }
      s += ") {";
    }
    out.write(s + "\n");
  }

  if(Flags.jCode) {
    out.write("  boolean ok;\n");
  }

  prettyPrint(out,v1);
  prettyPrintList(out,l2);
  if(Flags.jCode) {
    if(getSortGL(sort1).equals("void")) {
      out.write("  return ;\n");
    } else {
      out.write("  return null;\n");
    }
  }
  out.write("}\n");
  return;
}

%rule prettyPrint(out,VariableList(empty)) %--> return;
%rule prettyPrint(out,VariableList(cons(Variable(name1,sort1),l))) %--> {
  out.write(getSortGL(sort1) + " " + decapString(name1) + ";\n");
  prettyPrint(out,factory.makeAppl(f_VariableList,l));
  return;
}

%rule prettyPrint(out,GL(t)) %--> {
  out.write(getString(t) + "\n");
  return;
}

%rule prettyPrint(out,t) %--> {
  System.out.println("Cannot print: " + t);
  System.exit(1);
}

%// ------------------------------------------------------------

%rule prettyPrintList(out,empty) %--> return;
%rule prettyPrintList(out,cons(t,l)) %--> {
  prettyPrint(out,t);
  prettyPrintList(out,l);
  return;
}
%rule prettyPrintList(out,l) %--> {
  System.out.println("Cannot print list: " + l);
  System.exit(1);
}

%// ------------------------------------------------------------

%%

String getString(ATerm t) {
  return ((ATermAppl) t).getAFun().getName();
}
 
String decapString(ATerm t) {
  return getString(((ATermAppl) t).getArgument(0));
}

String getSortTom(ATerm sort) {
  return decapString(((ATermAppl) sort).getArgument(0));
}

String getSortGL(ATerm sort) {
  return decapString(((ATermAppl)((ATermAppl) sort).getArgument(1)).getArgument(0));
}

ATerm getSymbolRule(ATerm rule) {
  return ((ATermAppl)((ATermAppl) rule).getArgument(0)).getArgument(0);
}

ATerm getSymbolName(ATerm symbol) {
  return ((ATermAppl) symbol).getArgument(0);
}

ATerm getSymbolSort(ATerm symbol) {
  return ((ATermAppl)((ATermAppl) symbol).getArgument(1)).getArgument(1); 
}

ATerm getSymbolCode(ATerm symbol) {
  return ((ATermAppl) symbol).getArgument(3);
}

ATerm makeGL(String s) {
    //return (ATerm)factory.make("GL(\"" + s + "\")",emptyList);
  ATerm str = (ATerm) factory.makeAppl(factory.makeAFun(s,0,true));
  return (ATerm) factory.makeAppl(f_GL,str);
}
 
ATermList genOneToOneMatching(ATermList result, ATerm lhs, ATerm rhs, String oldPath,
                              int nextlab, int deep) {
  int i,j;
  String s = "";
  ATerm symbol, subterm;
  ATermList args;
  ATerm subtermSymbol, subtermName, subtermSort, subtermOption, subtermCode;
  ATermList subtermArgs, subtermSortList;
  int indexSubterm = 0;
  List list;
  String path;
  
  list = lhs.match("Appl(,[])");
  symbol = (ATerm)list.get(0);
  args = (ATermList)list.get(1);

    //ATprintf("term = %t\n",term);
    //ATprintf("symb = %t\n",symbol);
    //ATprintf("args = %l\n",args);

  while(!args.isEmpty()) {
    ATerm name,sort;

    subterm = args.getFirst();
    indexSubterm++;

      //ATprintf("subterm = %t\n",subterm);

    path = oldPath + "_" + indexSubterm;

    list = subterm.match("Variable(,)");
    if(list != null) {
      name = (ATerm)list.get(0);
      sort = (ATerm)list.get(1);
      s = indent(deep) + decapString(name) + " = " + path + ";";
      result = result.append(makeGL(s));
    } else {
      list = subterm.match("Appl(SymbolTom(,SortsToSort([],),,CodeGL()),[])");
      if(list != null) {
        subtermName = (ATerm)list.get(0);
        subtermSortList = (ATermList)list.get(1);
        subtermSort = (ATerm)list.get(2);
        subtermOption = (ATerm)list.get(3);
        subtermCode = (ATerm)list.get(4);
        subtermArgs = (ATermList)list.get(5);
        int indexSubSubterm = 0;

        //ATprintf("subtermName = %t\n",subtermName);
        //ATprintf("subtermSort = %t\n",subtermSort);
        //ATprintf("subtermCode = %t\n",subtermCode);
        //ATprintf("subtermArgs = %t\n",subtermArgs);

        s = indent(deep) + "  if(GET_FUN_SYM_" + getSortTom(subtermSort) + "(" +
          path + ") != " + decapString(subtermCode) + ") {";
        result = result.append(makeGL(s));

        if(Flags.cCode) {
          s = indent(deep) + "    goto lab" + nextlab + ";";
          result = result.append(makeGL(s));
        } else if(Flags.jCode) {
          s = indent(deep) + "    break lab" + nextlab + ";";
          result = result.append(makeGL(s));
        }

        s = indent(deep) + "  } else {";
        result = result.append(makeGL(s));

        while(!subtermArgs.isEmpty()) {
        ATerm subSubterm = subtermArgs.getFirst();
        ATerm subSubtermSymbol, subSubtermSort = null, subSubtermName;
        ATermList subSubtermArgs;

          //ATprintf("*** subSubterm = %t\n",subSubterm);
        list = subSubterm.match("Appl(,[])");
          //System.out.println("*** subSubterm = " + subSubterm);
          //System.out.println("*** list       = " + list);
        if(list != null) {
          subSubtermSymbol = (ATerm)list.get(0);
          subSubtermArgs = (ATermList)list.get(1);
          subSubtermSort = getSymbolSort(subSubtermSymbol);
            //System.out.println("*** sort = " + subSubtermSort);
        } else {
          list = subSubterm.match("Variable(,)");
          if(list != null) {
            subSubtermName = (ATerm)list.get(0);
            subSubtermSort = (ATerm)list.get(1);
              //System.out.println("*** name = " + subSubtermName + "\tsort = " + subSubtermSort);
          } else {
            System.out.println("Strange term: " + subSubterm);
            System.exit(1);
          }
        }

        indexSubSubterm++;

        s = indent(deep) + "    " + getSortGL(subSubtermSort) + " " +
          path + "_" + indexSubSubterm + " = (" + getSortGL(subSubtermSort) +
          ") GET_SUBTERM_" + getSortTom(subtermSort) + "(" +
          path + "," + (indexSubSubterm-1) + ");";
        result = result.append(makeGL(s));
        subtermArgs = subtermArgs.getNext();
      }
      result = genOneToOneMatching(result,subterm, rhs, path, nextlab, deep+1);
      s = indent(deep) + "  }";
      result = result.append(makeGL(s));
      }
    }
    args = args.getNext();
  }
    //ATprintf("*** result = %l\n",result);
  return result;
}

public final static void main(String[] args) {
  String fileName = "";
    
  if(args.length >= 1) {
    for(int i=0; i < args.length; i++) { 
      if(args[i].charAt(0) == '-') {
        if(args[i].equals("--noOutput")) {
          Flags.printOutput = false;
        } else if(args[i].equals("--atermStat")) {
          Flags.atermStat = true;
        } else if(args[i].equals("--jCode") || args[i].equals("-j")) {
          Flags.jCode = true;
          Flags.cCode = false;
          oSuffix = ".java";
        } else {
          System.out.println("'" + args[i] + "' is not a valid option");
          usage();
        }
      } else {
        if(args[i].endsWith(iSuffix)) {
          fileName = args[i].substring(0,args[i].length()-(iSuffix.length()));
        } else {
          fileName = args[i];
        }
      }
    }
  } else {
    usage();
  }
  
  if(fileName.length() == 0) {
    System.out.println("no inputfile");
    usage();
  }
  
  jtom3 test = new jtom3(fileName,new PureFactory(16));
  test.run();
}

public void run() {
  ATerm t;
  try {
    t = factory.readFromTextFile(input);
      //System.out.println("result = " + t);

    ATerm compiledTerm = compile(t);
    prettyPrint(output,compiledTerm);
    output.close();
  } catch (java.io.FileNotFoundException e1) {
    System.out.println("File " + input + " not found.");
  } catch (IOException e2) {
    System.out.println(e2);
  }
}
 
private static void usage() {
  System.out.println("Tom Source usage:");
  System.out.println("\tjava jtom3 [options] inputfile");
  System.out.println("Options:");
  System.out.println("\t--noOutput");
  System.out.println("\t--atermStat");
  System.out.println("\t--jCode | -j");
  System.exit(0);
}

private static String indent(int deep) {
  StringBuffer s = new StringBuffer();
  for(int j=0 ; j
    
Last modified: Thu Jan 11 19:03:42 GMT 2001