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 (Mi, 14 Nov 2007) $) * */ 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)); } 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 */ 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 */ 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 */ 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 */ 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 */ 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); } }