Logo Search packages:      
Sourcecode: jabref version File versions  Download package

VM.java

package net.sf.jabref.bst;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.jabref.AuthorList;
import net.sf.jabref.BibtexDatabase;
import net.sf.jabref.BibtexEntry;

import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.Tree;

/**
 * 
 * A Bibtex Virtual machine that can execute .bst files.
 * 
 * Documentation can be found in the original bibtex distribution:
 * 
 * http://texcatalogue.sarovar.org/entries/bibtex.html#Download
 * 
 * @author $Author: coezbek $
 * @version $Revision: 2488 $ ($Date: 2007-11-14 01:25:31 +0100 (Wed, 14 Nov 2007) $)
 * 
 */

00043 public class VM implements Warn {

      PrintStream out = System.out;

      public class Identifier {
            public String name;

            public Identifier(String name) {
                  this.name = name;
            }

            public String getName() {
                  return name;
            }
      }

      public class Variable {
            public String name;

            public Variable(String name) {
                  this.name = name;
            }

            public String getName() {
                  return name;
            }
      }

      public interface BstFunction {
            public void execute(BstEntry context);
      }

      public static final Integer FALSE = new Integer(0);

      public static final Integer TRUE = new Integer(1);

      private HashMap<String,BstFunction> buildInFunctions;

      public File file;

      public VM(File f) throws RecognitionException, IOException {
            this(new ANTLRFileStream(f.getPath()));
            this.file = f;
      }

      public VM(String s) throws RecognitionException {
            this(new ANTLRStringStream(s));
      }

      public static CommonTree charStream2CommonTree(CharStream bst) throws RecognitionException {
            BstLexer lex = new BstLexer(bst);
            CommonTokenStream tokens = new CommonTokenStream(lex);
            BstParser parser = new BstParser(tokens);
            BstParser.program_return r = parser.program();
            return (CommonTree) r.getTree();
      }

      public VM(CharStream bst) throws RecognitionException {
            this(charStream2CommonTree(bst));
      }

00104       public VM(CommonTree tree) {
            this.tree = tree;

            this.buildInFunctions = new HashMap<String, BstFunction>(37);

            buildInFunctions.put(">", new BstFunction() {
                  /**
                   * Pops the top two (integer) literals, compares them, and pushes
                   * the integer 1 if the second is greater than the first, 0
                   * otherwise.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Not enough operands on stack for operation >");
                        }
                        Object o2 = stack.pop();
                        Object o1 = stack.pop();

                        if (!(o1 instanceof Integer && o2 instanceof Integer)) {
                              throw new VMException("Can only compare two integers with >");
                        }

                        if (o1 == o2) {
                              stack.push(VM.FALSE);
                              return;
                        }

                        stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? VM.TRUE : VM.FALSE);
                  }
            });

            buildInFunctions.put("<", new BstFunction() {
                  /** Analogous. */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Not enough operands on stack for operation <");
                        }
                        Object o2 = stack.pop();
                        Object o1 = stack.pop();

                        if (!(o1 instanceof Integer && o2 instanceof Integer)) {
                              throw new VMException("Can only compare two integers with <");
                        }

                        if (o1 == o2) {
                              stack.push(VM.FALSE);
                              return;
                        }

                        stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? VM.TRUE : VM.FALSE);

                  }
            });

            buildInFunctions.put("=", new BstFunction() {
                  /**
                   * Pops the top two (both integer or both string) literals, compares
                   * them, and pushes the integer 1 if they're equal, 0 otherwise.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Not enough operands on stack for operation =");
                        }
                        Object o1 = stack.pop();
                        Object o2 = stack.pop();

                        if (o1 == null ^ o2 == null) {
                              stack.push(VM.FALSE);
                              return;
                        }

                        if (o1 == o2) {
                              stack.push(VM.TRUE);
                              return;
                        }

                        stack.push(o1.equals(o2) ? VM.TRUE : VM.FALSE);
                  }
            });

            buildInFunctions.put("+", new BstFunction() {
                  /** Pops the top two (integer) literals and pushes their sum. */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Not enough operands on stack for operation +");
                        }
                        Object o2 = stack.pop();
                        Object o1 = stack.pop();

                        if (!(o1 instanceof Integer && o2 instanceof Integer)) {
                              throw new VMException("Can only compare two integers with +");
                        }

                        stack.push(new Integer(((Integer) o1).intValue() + ((Integer) o2).intValue()));
                  }
            });

            buildInFunctions.put("-", new BstFunction() {
                  /**
                   * Pops the top two (integer) literals and pushes their difference
                   * (the first subtracted from the second).
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Not enough operands on stack for operation -");
                        }
                        Object o2 = stack.pop();
                        Object o1 = stack.pop();

                        if (!(o1 instanceof Integer && o2 instanceof Integer)) {
                              throw new VMException("Can only subtract two integers with -");
                        }

                        stack.push(new Integer(((Integer) o1).intValue() - ((Integer) o2).intValue()));
                  }
            });

            buildInFunctions.put("*", new BstFunction() {
                  /**
                   * Pops the top two (string) literals, concatenates them (in reverse
                   * order, that is, the order in which pushed), and pushes the
                   * resulting string.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Not enough operands on stack for operation *");
                        }
                        Object o2 = stack.pop();
                        Object o1 = stack.pop();

                        if (!(o1 instanceof String && o2 instanceof String)) {
                              throw new VMException("Can only concatenate two String with *");
                        }

                        stack.push(((String) o1) + ((String) o2));
                  }
            });

            buildInFunctions.put(":=", new BstFunction() {
                  /**
                   * Pops the top two literals and assigns to the first (which must be
                   * a global or entry variable) the value of the second.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Invalid call to operation :=");
                        }
                        Object o1 = stack.pop();
                        Object o2 = stack.pop();
                        assign(context, o1, o2);

                  }
            });

            buildInFunctions.put("add.period$", new BstFunction() {

                  Pattern p = Pattern.compile("([^\\.\\?\\!\\}\\s])(\\}|\\s)*$");

                  /**
                   * Pops the top (string) literal, adds a `.' to it if the last non
                   * '}' character isn't a `.', `?', or `!', and pushes this resulting
                   * string.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation add.period$");
                        }
                        Object o1 = stack.pop();

                        if (!(o1 instanceof String)) {
                              throw new VMException("Can only add a period to a string for add.period$");
                        }

                        String s = (String) o1;
                        Matcher m = p.matcher(s);

                        if (m.find()) {
                              StringBuffer sb = new StringBuffer();
                              m.appendReplacement(sb, m.group(1));
                              sb.append('.');
                              String group2 = m.group(2);
                              if (group2 != null)
                                    sb.append(m.group(2));
                              stack.push(sb.toString());
                        } else {
                              stack.push(s);
                        }
                  }
            });

            buildInFunctions.put("call.type$", new BstFunction() {
                  /**
                   * Executes the function whose name is the entry type of an entry.
                   * For example if an entry is of type book, this function executes
                   * the book function. When given as an argument to the ITERATE
                   * command, call.type$ actually produces the output for the entries.
                   * For an entry with an unknown type, it executes the function
                   * default.type. Thus you should define (before the READ command)
                   * one function for each standard entry type as well as a
                   * default.type function.
                   */
                  public void execute(BstEntry context) {

                        if (context == null) {
                              throw new VMException(
                                    "Call.type$ can only be called from within a context (ITERATE or REVERSE).");
                        }
                        VM.this.execute(context.entry.getType().getName().toLowerCase(), context);
                  }
            });

            buildInFunctions.put("change.case$", new ChangeCaseFunction(this));

            buildInFunctions.put("chr.to.int$", new BstFunction() {
                  /**
                   * Pops the top (string) literal, makes sure it's a single
                   * character, converts it to the corresponding ASCII integer, and
                   * pushes this integer.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation chr.to.int$");
                        }
                        Object o1 = stack.pop();

                        if (!(o1 instanceof String && ((String) o1).length() == 1)) {
                              throw new VMException("Can only perform chr.to.int$ on string with length 1");
                        }

                        String s = (String) o1;

                        stack.push(new Integer(s.charAt(0)));
                  }
            });

            buildInFunctions.put("cite$", new BstFunction() {
                  /**
                   * Pushes the string that was the \cite-command argument for this
                   * entry.
                   */
                  public void execute(BstEntry context) {
                        stack.push(context.entry.getCiteKey());
                  }
            });

            buildInFunctions.put("duplicate$", new BstFunction() {
                  /**
                   * Pops the top literal from the stack and pushes two copies of it.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation duplicate$");
                        }
                        Object o1 = stack.pop();

                        stack.push(o1);
                        stack.push(o1);
                  }
            });

            buildInFunctions.put("empty$", new BstFunction() {
                  /**
                   * Pops the top literal and pushes the integer 1 if it's a missing
                   * field or a string having no non-white-space characters, 0
                   * otherwise.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation empty$");
                        }
                        Object o1 = stack.pop();

                        if (o1 == null) {
                              stack.push(VM.TRUE);
                              return;
                        }

                        if (!(o1 instanceof String)) {
                              throw new VMException("Operand does not match function empty$");
                        }

                        String s = (String) o1;

                        stack.push(s.trim().equals("") ? VM.TRUE : VM.FALSE);
                  }
            });

            buildInFunctions.put("format.name$", new FormatNameFunction(this));

            buildInFunctions.put("if$", new BstFunction() {
                  /**
                   * Pops the top three literals (they are two function literals and
                   * an integer literal, in that order); if the integer is greater
                   * than 0, it executes the second literal, else it executes the
                   * first.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 3) {
                              throw new VMException("Not enough operands on stack for operation =");
                        }
                        Object f1 = stack.pop();
                        Object f2 = stack.pop();
                        Object i = stack.pop();

                        if (!(f1 instanceof Identifier || f1 instanceof Tree)
                              && (f2 instanceof Identifier || f2 instanceof Tree) && (i instanceof Integer)) {
                              throw new VMException("Expecting two functions and an integer for if$.");
                        }

                        Object toExe;
                        if (((Integer) i).intValue() > 0) {
                              toExe = f2;
                        } else {
                              toExe = f1;
                        }
                        VM.this.executeInContext(toExe, context);
                  }
            });

            buildInFunctions.put("int.to.chr$", new BstFunction() {
                  /**
                   * Pops the top (integer) literal, interpreted as the ASCII integer
                   * value of a single character, converts it to the corresponding
                   * single-character string, and pushes this string.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation int.to.chr$");
                        }
                        Object o1 = stack.pop();

                        if (!(o1 instanceof Integer)) {
                              throw new VMException("Can only perform operation int.to.chr$ on an Integer");
                        }

                        Integer i = (Integer) o1;

                        stack.push(String.valueOf((char) i.intValue()));
                  }
            });

            buildInFunctions.put("int.to.str$", new BstFunction() {
                  /**
                   * Pops the top (integer) literal, converts it to its (unique)
                   * string equivalent, and pushes this string.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation int.to.str$");
                        }
                        Object o1 = stack.pop();

                        if (!(o1 instanceof Integer)) {
                              throw new VMException(
                                    "Can only transform an integer to an string using int.to.str$");
                        }

                        stack.push(((Integer) o1).toString());
                  }
            });

            buildInFunctions.put("missing$", new BstFunction() {
                  /**
                   * Pops the top literal and pushes the integer 1 if it's a missing
                   * field, 0 otherwise.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation missing$");
                        }
                        Object o1 = stack.pop();

                        if (o1 == null) {
                              stack.push(VM.TRUE);
                              return;
                        }

                        if (!(o1 instanceof String)) {
                              warn("Not a string or missing field in operation missing$");
                              stack.push(VM.TRUE);
                              return;
                        }

                        stack.push(VM.FALSE);
                  }
            });

            buildInFunctions.put("newline$", new BstFunction() {
                  /**
                   * Writes onto the bbl file what's accumulated in the output buffer.
                   * It writes a blank line if and only if the output buffer is empty.
                   * Since write$ does reasonable line breaking, you should use this
                   * function only when you want a blank line or an explicit line
                   * break.
                   */
                  public void execute(BstEntry context) {
                        VM.this.bbl.append('\n');
                  }
            });

            buildInFunctions.put("num.names$", new BstFunction() {
                  /**
                   * Pops the top (string) literal and pushes the number of names the
                   * string represents one plus the number of occurrences of the
                   * substring "and" (ignoring case differences) surrounded by
                   * non-null white-space at the top brace level.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation num.names$");
                        }
                        Object o1 = stack.pop();

                        if (!(o1 instanceof String)) {
                              throw new VMException("Need a string at the top of the stack for num.names$");
                        }
                        String s = (String) o1;

                        stack.push(new Integer(AuthorList.getAuthorList(s).size()));
                  }
            });

            buildInFunctions.put("pop$", new BstFunction() {
                  /**
                   * Pops the top of the stack but doesn't print it; this gets rid of
                   * an unwanted stack literal.
                   */
                  public void execute(BstEntry context) {
                        stack.pop();
                  }
            });

            buildInFunctions.put("preamble$", new BstFunction() {
                  /**
                   * The |built_in| function {\.{preamble\$}} pushes onto the stack
                   * the concatenation of all the \.{preamble} strings read from the
                   * database files. (or the empty string if there where none)
                   * 
                   * @PREAMBLE strings read from the database files.
                   */
                  public void execute(BstEntry context) {
                        if (preamble != null) {
                              stack.push(preamble);
                        } else {
                              stack.push("");
                        }

                  }
            });

            /**
             * Pops the top (string) literal, removes nonalphanumeric characters
             * except for white-space characters and hyphens and ties (these all get
             * converted to a space), removes certain alphabetic characters
             * contained in the control sequences associated with a \special
             * character", and pushes the resulting string.
             */
            buildInFunctions.put("purify$", new PurifyFunction(this));

            buildInFunctions.put("quote$", new BstFunction() {
                  /**
                   * Pushes the string consisting of the double-quote character.
                   */
                  public void execute(BstEntry context) {
                        stack.push("\"");
                  }
            });

            buildInFunctions.put("skip$", new BstFunction() {
                  /**
                   * Is a no-op.
                   */
                  public void execute(BstEntry context) {
                        // Nothing to do! Yeah!
                  }
            });

            buildInFunctions.put("stack$", new BstFunction() {
                  /**
                   * Pops and prints the whole stack; it's meant to be used for style
                   * designers while debugging.
                   */
                  public void execute(BstEntry context) {
                        while (!stack.empty()) {
                              System.out.println(stack.pop());
                        }
                  }
            });

            buildInFunctions.put("substring$", new BstFunction() {
                  /**
                   * Pops the top three literals (they are the two integers literals
                   * len and start, and a string literal, in that order). It pushes
                   * the substring of the (at most) len consecutive characters
                   * starting at the startth character (assuming 1-based indexing) if
                   * start is positive, and ending at the start-th character
                   * (including) from the end if start is negative (where the first
                   * character from the end is the last character).
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 3) {
                              throw new VMException("Not enough operands on stack for operation substring$");
                        }
                        Object o1 = stack.pop();
                        Object o2 = stack.pop();
                        Object o3 = stack.pop();

                        if (!((o1 instanceof Integer) && (o2 instanceof Integer) && (o3 instanceof String))) {
                              throw new VMException("Expecting two integers and a string for substring$");
                        }

                        Integer len = (Integer) o1;
                        Integer start = (Integer) o2;

                        int lenI = len.intValue();
                        int startI = start.intValue();

                        if (lenI > Integer.MAX_VALUE / 2)
                              lenI = Integer.MAX_VALUE / 2;

                        if (startI > Integer.MAX_VALUE / 2)
                              startI = Integer.MAX_VALUE / 2;

                        if (startI < Integer.MIN_VALUE / 2)
                              startI = -Integer.MIN_VALUE / 2;

                        String s = (String) o3;

                        if (startI < 0) {
                              startI += s.length() + 1;
                              startI = Math.max(1, startI + 1 - lenI);
                        }
                        stack.push(s.substring(startI - 1, Math.min(startI - 1 + lenI, s.length())));
                  }
            });

            buildInFunctions.put("swap$", new BstFunction() {
                  /**
                   * Swaps the top two literals on the stack. text.length$ Pops the
                   * top (string) literal, and pushes the number of text char- acters
                   * it contains, where an accented character (more precisely, a
                   * \special character", defined in Section 4) counts as a single
                   * text character, even if it's missing its matching right brace,
                   * and where braces don't count as text characters.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Not enough operands on stack for operation swap$");
                        }
                        Object f1 = stack.pop();
                        Object f2 = stack.pop();

                        stack.push(f1);
                        stack.push(f2);
                  }
            });

            buildInFunctions.put("text.length$", new BstFunction() {
                  /**
                   * text.length$ Pops the top (string) literal, and pushes the number
                   * of text characters it contains, where an accented character (more
                   * precisely, a "special character", defined in Section 4) counts as
                   * a single text character, even if it's missing its matching right
                   * brace, and where braces don't count as text characters.
                   * 
                   * From BibTeXing: For the purposes of counting letters in labels,
                   * BibTEX considers everything contained inside the braces as a
                   * single letter.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 1) {
                              throw new VMException("Not enough operands on stack for operation text.length$");
                        }
                        Object o1 = stack.pop();

                        if (!(o1 instanceof String)) {
                              throw new VMException("Can only perform operation on a string text.length$");
                        }

                        String s = (String) o1;
                        char[] c = s.toCharArray();
                        int result = 0;

                        // Comments from bibtex.web:

                        // sp_ptr := str_start[pop_lit1];
                        int i = 0;

                        // sp_end := str_start[pop_lit1+1];
                        int n = s.length();

                        // sp_brace_level := 0;
                        int braceLevel = 0;

                        // while (sp_ptr < sp_end) do begin
                        while (i < n) {
                              // incr(sp_ptr);
                              i++;
                              // if (str_pool[sp_ptr-1] = left_brace) then
                              // begin
                              if (c[i - 1] == '{') {
                                    // incr(sp_brace_level);
                                    braceLevel++;
                                    // if ((sp_brace_level = 1) and (sp_ptr < sp_end)) then
                                    if (braceLevel == 1 && i < n)
                                          // if (str_pool[sp_ptr] = backslash) then
                                          // begin
                                          if (c[i] == '\\') {
                                                // incr(sp_ptr); {skip over the |backslash|}
                                                i++; // skip over backslash
                                                // while ((sp_ptr < sp_end) and (sp_brace_level
                                                // > 0)) do begin
                                                while (i < n && braceLevel > 0) {
                                                      // if (str_pool[sp_ptr] = right_brace) then
                                                      if (c[i] == '}')
                                                            // decr(sp_brace_level)
                                                            braceLevel--;
                                                      // else if (str_pool[sp_ptr] = left_brace)
                                                      // then
                                                      else if (c[i] == '{')

                                                            // incr(sp_brace_level);
                                                            braceLevel++;
                                                      // incr(sp_ptr);
                                                      i++;
                                                      // end;
                                                }
                                                // incr(num_text_chars);
                                                result++;
                                                // end;
                                          }
                                    // end
                              }
                              // else if (str_pool[sp_ptr-1] = right_brace) then
                              // begin
                              else if (c[i - 1] == '}') {
                                    // if (sp_brace_level > 0) then
                                    if (braceLevel > 0)
                                          // decr(sp_brace_level);
                                          braceLevel--;
                                    // end
                              }
                              // else
                              else
                                    // incr(num_text_chars);
                                    result++;
                        }
                        stack.push(new Integer(result));
                  }
            });

            /**
             * Pops the top two literals (the integer literal len and a string
             * literal, in that order). It pushes the substring of the (at most) len
             * consecutive text characters starting from the beginning of the
             * string. This function is similar to substring$, but this one
             * considers a \special character", even if it's missing its matching
             * right brace, to be a single text character (rather than however many
             * ASCII characters it actually comprises), and this function doesn't
             * consider braces to be text characters; furthermore, this function
             * appends any needed matching right braces.
             */
            buildInFunctions.put("text.prefix$", new TextPrefixFunction(this));

            buildInFunctions.put("top$", new BstFunction() {
                  /**
                   * Pops and prints the top of the stack on the terminal and log
                   * file. It's useful for debugging.
                   */
                  public void execute(BstEntry context) {
                        System.out.println(stack.pop());
                  }
            });

            buildInFunctions.put("type$", new BstFunction() {
                  /**
                   * Pushes the current entry's type (book, article, etc.), but pushes
                   * the null string if the type is either unknown or undefined.
                   */
                  public void execute(BstEntry context) {
                        stack.push(context.entry.getType().getName());
                  }
            });

            buildInFunctions.put("warning$", new BstFunction() {
                  /**
                   * Pops the top (string) literal and prints it following a warning
                   * message. This also increments a count of the number of warning
                   * messages issued.
                   */
                  int warning = 1;

                  public void execute(BstEntry context) {
                        out.println("Warning (#" + (warning++) + "): " + stack.pop());
                  }
            });

            buildInFunctions.put("while$", new BstFunction() {
                  /**
                   * Pops the top two (function) literals, and keeps executing the
                   * second as long as the (integer) literal left on the stack by
                   * executing the first is greater than 0.
                   */
                  public void execute(BstEntry context) {
                        if (stack.size() < 2) {
                              throw new VMException("Not enough operands on stack for operation while$");
                        }
                        Object f2 = stack.pop();
                        Object f1 = stack.pop();

                        if (!(f1 instanceof Identifier || f1 instanceof Tree)
                              && (f2 instanceof Identifier || f2 instanceof Tree)) {
                              throw new VMException("Expecting two functions for while$.");
                        }

                        do {
                              VM.this.executeInContext(f1, context);

                              Object i = stack.pop();
                              if (!(i instanceof Integer)) {
                                    throw new VMException(
                                          "First parameter to while has to return an integer but was " + i);
                              }
                              if (((Integer) i).intValue() <= 0) {
                                    break;
                              }
                              VM.this.executeInContext(f2, context);
                        } while (true);
                  }
            });

            buildInFunctions.put("width$", new WidthFunction(this));

            buildInFunctions.put("write$", new BstFunction() {
                  /**
                   * Pops the top (string) literal and writes it on the output buffer
                   * (which will result in stuff being written onto the bbl file when
                   * the buffer fills up).
                   */
                  public void execute(BstEntry context) {
                        String s = (String) stack.pop();
                        System.out.println(s);
                        VM.this.bbl.append(s);
                  }
            });

      }

      protected boolean assign(BstEntry context, Object o1, Object o2) {

            if (!(o1 instanceof Identifier) || !(o2 instanceof String || o2 instanceof Integer))
                  throw new VMException("Invalid parameters");

            String name = ((Identifier) o1).getName();

            if (o2 instanceof String) {

                  if (context != null && context.strings.containsKey(name)) {
                        context.strings.put(name, (String) o2);
                        return true;
                  }

                  if (strings.containsKey(name)) {
                        strings.put(name, (String) o2);
                        return true;
                  }
                  return false;

            }
            if (o2 instanceof Integer) {
                  if (context != null && context.integers.containsKey(name)) {
                        context.integers.put(name, (Integer) o2);
                        return true;
                  }

                  if (integers.containsKey(name)) {
                        integers.put(name, (Integer) o2);
                        return true;
                  }
                  return false;
            }
            return false;
      }

      CommonTree tree;

      private StringBuffer bbl;

      String preamble;

      public String run(BibtexDatabase db) {
            preamble = db.getPreamble();
            return run(db.getEntries());
      }

      public String run(Collection<BibtexEntry> bibtex) {

            reset();

            { // Create entries
                  entries = new Vector<BstEntry>(bibtex.size());
                  ListIterator<BstEntry> i = entries.listIterator();
                  for (BibtexEntry entry : bibtex){
                        i.add(new BstEntry(entry));
                  }
            }

            // assert tree.getType() == Bst.COMMANDS;

            // Go
            for (int i = 0; i < tree.getChildCount(); i++) {
                  Tree child = tree.getChild(i);
                  switch (child.getType()) {
                  case BstParser.STRINGS:
                        strings(child);
                        break;
                  case BstParser.INTEGERS:
                        integers(child);
                        break;
                  case BstParser.FUNCTION:
                        function(child);
                        break;
                  case BstParser.EXECUTE:
                        execute(child);
                        break;
                  case BstParser.SORT:
                        sort(child);
                        break;
                  case BstParser.ITERATE:
                        iterate(child);
                        break;
                  case BstParser.REVERSE:
                        reverse(child);
                        break;
                  case BstParser.ENTRY:
                        entry(child);
                        break;
                  case BstParser.READ:
                        read();
                        break;
                  case BstParser.MACRO:
                        macro(child);
                        break;
                  }
            }

            return bbl.toString();
      }

      private void reset() {
            bbl = new StringBuffer();

            entries = null;

            strings = new HashMap<String, String>();

            integers = new HashMap<String, Integer>();
            integers.put("entry.max$", new Integer(Integer.MAX_VALUE));
            integers.put("global.max$", new Integer(Integer.MAX_VALUE));

            functions = new HashMap<String, BstFunction>();
            functions.putAll(buildInFunctions);

            stack = new Stack<Object>();
      }

      /**
       * Dredges up from the database file the field values for each entry in the
       * list. It has no arguments. If a database entry doesn't have a value for a
       * field (and probably no database entry will have a value for every field),
       * that field variable is marked as missing for the entry.
       * 
       * We use null for the missing entry designator.
       * 
       * @param child
       */
00980       private void read() {

            Iterator<BstEntry> i = entries.iterator();
            while (i.hasNext()) {
                  BstEntry e = i.next();

                  for (Map.Entry<String, String> mEntry : e.fields.entrySet()){
                        Object fieldValue = e.entry.getField(mEntry.getKey());

                        mEntry.setValue((fieldValue == null ? null : fieldValue.toString()));
                  }
            }

            i = entries.iterator();
            while (i.hasNext()) {
                  BstEntry e = i.next();
                  if (!e.fields.containsKey("crossref")) {
                        e.fields.put("crossref", null);
                  }
            }
      }

      /**
       * Defines a string macro. It has two arguments; the first is the macro's
       * name, which is treated like any other variable or function name, and the
       * second is its definition, which must be double-quote-delimited. You must
       * have one for each three-letter month abbreviation; in addition, you
       * should have one for common journal names. The user's database may
       * override any definition you define using this command. If you want to
       * define a string the user can't touch, use the FUNCTION command, which has
       * a compatible syntax.
       * 
       * @param child
       */
01014       private void macro(Tree child) {
            String name = child.getChild(0).getText();
            String replacement = child.getChild(1).getText();
            functions.put(name, new MacroFunction(replacement));
      }

      public class MacroFunction implements BstFunction {

            String replacement;

            public MacroFunction(String replacement) {
                  this.replacement = replacement;
            }

            public void execute(BstEntry context) {
                  VM.this.push(replacement);
            }
      }

      /*
       * Declares the fields and entry variables. It has three arguments, each a
       * (possibly empty) list of variable names. The three lists are of: fields,
       * integer entry variables, and string entry variables. There is an
       * additional field that BibTEX automatically declares, crossref, used for
       * cross ref- erencing. And there is an additional string entry variable
       * automatically declared, sort.key$, used by the SORT command. Each of
       * these variables has a value for each entry on the list.
       */
      private void entry(Tree child) {

            { // Fields first
                  Tree t = child.getChild(0);
                  // assert t.getType() == Bst.IDLIST;

                  for (int i = 0; i < t.getChildCount(); i++) {
                        String name = t.getChild(i).getText();

                        for (BstEntry entry : entries){
                              entry.fields.put(name, null);
                        }
                  }
            }
            { // Integers
                  Tree t = child.getChild(1);
                  // assert t.getType() == Bst.IDLIST;

                  for (int i = 0; i < t.getChildCount(); i++) {
                        String name = t.getChild(i).getText();
                        
                        for (BstEntry entry : entries){
                              entry.integers.put(name, new Integer(0));
                        }
                  }
            }
            { // Strings
                  Tree t = child.getChild(2);
                  // assert t.getType() == Bst.IDLIST;

                  for (int i = 0; i < t.getChildCount(); i++) {
                        String name = t.getChild(i).getText();
                        for (BstEntry entry : entries){
                              entry.strings.put(name, null);
                        }
                  }
                  for (BstEntry entry : entries){
                        entry.strings.put("sort.key$", null);
                  }
            }
      }

      private void reverse(Tree child) {

            BstFunction f = functions.get(child.getChild(0).getText());

            ListIterator<BstEntry> i = entries.listIterator(entries.size());
            while (i.hasPrevious()) {
                  f.execute(i.previous());
            }
      }

      private void iterate(Tree child) {
            BstFunction f = functions.get(child.getChild(0).getText());

            Iterator<BstEntry> i = entries.iterator();
            while (i.hasNext()) {
                  f.execute(i.next());
            }
      }

      /**
       * Sorts the entry list using the values of the string entry variable
       * sort.key$. It has no arguments.
       * 
       * @param child
       */
01109       private void sort(Tree child) {
            Collections.sort(entries, new Comparator<BstEntry>() {
                  public int compare(BstEntry o1, BstEntry o2) {
                        return (o1.strings.get("sort.key$")).compareTo(o2.strings
                              .get("sort.key$"));
                  }
            });
      }

      public void executeInContext(Object o, BstEntry context) {
            if (o instanceof Tree) {
                  Tree t = (Tree) o;
                  new StackFunction(t).execute(context);
            } else if (o instanceof Identifier) {
                  execute(((Identifier) o).getName(), context);
            }
      }

      public void execute(Tree child) {
            execute(child.getChild(0).getText(), null);
      }

      public class StackFunction implements BstFunction {

            Tree tree;

            public Tree getTree() {
                  return tree;
            }

            public StackFunction(Tree stack) {
                  // assert stack.getType() == Bst.STACK;
                  tree = stack;
            }

            public void execute(BstEntry context) {

                  for (int i = 0; i < tree.getChildCount(); i++) {

                        Tree c = tree.getChild(i);
                        try {

                              switch (c.getType()) {
                              case BstParser.STRING: {
                                    String s = c.getText();
                                    push(s.substring(1, s.length() - 1));
                              }
                                    break;
                              case BstParser.INTEGER:
                                    push(new Integer(Integer.parseInt(c.getText().substring(1))));
                                    break;
                              case BstParser.QUOTED:
                                    push(new Identifier(c.getText().substring(1)));
                                    break;
                              case BstParser.STACK:
                                    push(c);
                                    break;
                              default:
                                    VM.this.execute(c.getText(), context);
                              }
                        } catch (VMException e) {
                              if (file != null) {
                                    System.err.println("ERROR " + e.getMessage() + " (" + file.getPath() + ":"
                                          + c.getLine() + ")");
                              } else {
                                    System.err.println("ERROR " + e.getMessage() + " (" + c.getLine() + ")");
                              }
                              throw e;
                        }
                  }

            }
      }

      private void push(Tree t) {
            stack.push(t);
      }

      public void execute(String name, BstEntry context) {

            if (context != null) {

                  if (context.fields.containsKey(name)) {
                        stack.push(context.fields.get(name));
                        return;
                  }
                  if (context.strings.containsKey(name)) {
                        stack.push(context.strings.get(name));
                        return;
                  }
                  if (context.integers.containsKey(name)) {
                        stack.push(context.integers.get(name));
                        return;
                  }
            }
            if (strings.containsKey(name)) {
                  stack.push(strings.get(name));
                  return;
            }
            if (integers.containsKey(name)) {
                  stack.push(integers.get(name));
                  return;
            }

            if (functions.containsKey(name)) {
                  functions.get(name).execute(context);
                  return;
            }

            throw new VMException("No matching identifier found: " + name);
      }

      private void function(Tree child) {
            String name = child.getChild(0).getText();
            Tree stack = child.getChild(1);
            functions.put(name, new StackFunction(stack));

      }

      /**
       * Declares global integer variables. It has one argument, a list of
       * variable names. There are two such automatically-declared variables,
       * entry.max$ and global.max$, used for limiting the lengths of string vari-
       * ables. You may have any number of these commands, but a variable's
       * declaration must precede its use.
       * 
       * @param child
       */
01237       private void integers(Tree child) {
            Tree t = child.getChild(0);
            // assert t.getType() == Bst.IDLIST;

            for (int i = 0; i < t.getChildCount(); i++) {
                  String name = t.getChild(i).getText();
                  integers.put(name, new Integer(0));
            }
      }

      /**
       * Declares global string variables. It has one argument, a list of variable
       * names. You may have any number of these commands, but a variable's
       * declaration must precede its use.
       * 
       * @param child
       */
01254       private void strings(Tree child) {
            Tree t = child.getChild(0);
            // assert t.getType() == Bst.IDLIST;

            for (int i = 0; i < t.getChildCount(); i++) {
                  String name = t.getChild(i).getText();
                  strings.put(name, null);
            }
      }

      public class BstEntry {

            public BstEntry(BibtexEntry e) {
                  this.entry = e;
            }

            BibtexEntry entry;

            Map<String, String> strings = new HashMap<String, String>();

            Map<String, String> fields = new HashMap<String, String>();

            Map<String, Integer> integers = new HashMap<String, Integer>();

            public Map<String, String> getFields() {
                  return fields;
            }

            public BibtexEntry getBibtexEntry() {
                  return entry;
            }
      }

      Vector<BstEntry> entries;
      
      Map<String, String> strings = new HashMap<String, String>();
      
      Map<String, Integer> integers = new HashMap<String, Integer>();
      
      Map<String, BstFunction> functions = new HashMap<String, BstFunction>();
      
      Stack<Object> stack = new Stack<Object>();
      
      public void push(Integer integer) {
            stack.push(integer);
      }

      public void push(String string) {
            stack.push(string);
      }

      public void push(Identifier identifier) {
            stack.push(identifier);
      }

      
        public Map<String, String> getStrings() { return strings; }
        
        public Map<String, Integer> getIntegers() { return integers; }
        
        public Vector<BstEntry> getEntries() { return entries; }
        
       public Map<String, BstFunction> getFunctions() { return functions; }

      public Stack<Object> getStack() {
            return stack;
      }

      public void warn(String string) {
            System.out.println(string);
      }

}

Generated by  Doxygen 1.6.0   Back to index