From 07a9ccb4f1affb15766e5c88410799d31dc059de Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Thu, 26 May 2022 01:26:47 +0200 Subject: [PATCH 01/39] migrate to antlr4 --- build.gradle | 14 +-- src/main/antlr3/org/jabref/bst/Bst.g | 96 ------------------- src/main/antlr4/org/jabref/bst/Bst.g4 | 87 +++++++++++++++++ src/main/java/module-info.java | 1 - .../jabref/logic/bst/BstPreviewLayout.java | 2 +- src/main/java/org/jabref/logic/bst/VM.java | 73 ++++++++------ .../java/org/jabref/logic/bst/TestVM.java | 8 +- 7 files changed, 143 insertions(+), 138 deletions(-) delete mode 100644 src/main/antlr3/org/jabref/bst/Bst.g create mode 100644 src/main/antlr4/org/jabref/bst/Bst.g4 diff --git a/build.gradle b/build.gradle index 5c364f6717d..a7d15588eb3 100644 --- a/build.gradle +++ b/build.gradle @@ -85,7 +85,6 @@ repositories { } configurations { - antlr3 antlr4 // TODO: Remove the following workaround for split error messages such as // error: module java.xml.bind reads package javax.annotation from both jsr305 and java.annotation @@ -136,9 +135,6 @@ dependencies { implementation 'io.github.java-diff-utils:java-diff-utils:4.11' implementation 'info.debatty:java-string-similarity:2.0.0' - antlr3 'org.antlr:antlr:3.5.3' - implementation 'org.antlr:antlr-runtime:3.5.2' - antlr4 'org.antlr:antlr4:4.9.3' implementation 'org.antlr:antlr4-runtime:4.9.3' @@ -271,14 +267,14 @@ task generateSource(dependsOn: ["generateBstGrammarSource", } tasks.register("generateBstGrammarSource", JavaExec) { - main = "org.antlr.Tool" - classpath = configurations.antlr3 + main = "org.antlr.v4.Tool" + classpath = configurations.antlr4 group = "JabRef" - description = 'Generates BstLexer.java and BstParser.java from the Bst.g grammar file using antlr3.' + description = 'Generates BstLexer.java and BstParser.java from the Bst.g grammar file using antlr4.' - inputs.dir('src/main/antlr3/org/jabref/bst/') + inputs.dir('src/main/antlr4/org/jabref/bst/') outputs.dir("src-gen/main/java/org/jabref/logic/bst/") - args = ["-o", "src-gen/main/java/org/jabref/logic/bst/" , "$projectDir/src/main/antlr3/org/jabref/bst/Bst.g" ] + args = ["-o", "src-gen/main/java/org/jabref/logic/bst/", "-listener", "-package", "org.jabref.logic.bst", "$projectDir/src/main/antlr4/org/jabref/bst/Bst.g4"] } tasks.register("generateSearchGrammarSource", JavaExec) { diff --git a/src/main/antlr3/org/jabref/bst/Bst.g b/src/main/antlr3/org/jabref/bst/Bst.g deleted file mode 100644 index 498621ad1a1..00000000000 --- a/src/main/antlr3/org/jabref/bst/Bst.g +++ /dev/null @@ -1,96 +0,0 @@ -grammar Bst; - -options { - output=AST; -} - -tokens { - IDLIST; - STACK; - ENTRY; - COMMANDS; -} - -// applies only to the parser: -@header {// Generated by ANTLR -package org.jabref.logic.bst;} - -// applies only to the lexer: -@lexer::header {// Generated by ANTLR -package org.jabref.logic.bst;} - -program : commands+ -> ^(COMMANDS commands+); - -commands - : STRINGS^ idList - | INTEGERS^ idList - | FUNCTION^ id stack - | MACRO^ id '{'! STRING '}'! - | READ^ - | EXECUTE^ '{'! function '}'! - | ITERATE^ '{'! function '}'! - | REVERSE^ '{'! function '}'! - | ENTRY^ idList0 idList0 idList0 - | SORT^; - -identifier - : IDENTIFIER; - -id - : '{'! identifier '}'!; - -idList - : '{' identifier+ '}' -> ^(IDLIST identifier+); - -idList0 - : '{' identifier* '}' -> ^(IDLIST identifier*); - -function - : '<' | '>' | '=' | '+' | '-' | ':=' | '*' | identifier; - -stack - : '{' stackitem+ '}' -> ^(STACK stackitem+); - -stackitem - : function - | STRING - | INTEGER - | QUOTED - | stack; - -STRINGS : 'STRINGS'; -INTEGERS : 'INTEGERS'; -FUNCTION : 'FUNCTION'; -EXECUTE : 'EXECUTE'; -SORT : 'SORT'; -ITERATE : 'ITERATE'; -REVERSE : 'REVERSE'; -ENTRY : 'ENTRY'; -READ : 'READ'; -MACRO : 'MACRO'; - -QUOTED - : '\'' IDENTIFIER; - -IDENTIFIER - : LETTER (LETTER|NUMERAL|'_')* ; - -fragment LETTER - : ('a'..'z'|'A'..'Z'|'.'|'$'); - -STRING - : '"' (~('"'))* '"'; - -INTEGER - : '#' ('+'|'-')? NUMERAL+ ; - -fragment NUMERAL - : ('0'..'9'); - -WS - : (' '|'\t'|'\n'|'\r')+ {_channel=99;} ; - -LINE_COMMENT - : '%' ~('\n'|'\r')* '\r'? '\n' {_channel=99;} - ; - diff --git a/src/main/antlr4/org/jabref/bst/Bst.g4 b/src/main/antlr4/org/jabref/bst/Bst.g4 new file mode 100644 index 00000000000..c6faaa0727a --- /dev/null +++ b/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -0,0 +1,87 @@ +grammar Bst; + +// Lexer + +STRINGS : 'STRINGS'; +INTEGERS : 'INTEGERS'; +FUNCTION : 'FUNCTION'; +EXECUTE : 'EXECUTE'; +SORT : 'SORT'; +ITERATE : 'ITERATE'; +REVERSE : 'REVERSE'; +ENTRY : 'ENTRY'; +READ : 'READ'; +MACRO : 'MACRO'; + +LBRACE : '{'; +RBRACE : '}'; +GT : '>'; +LT : '<'; +EQUAL : '='; +ASSIGN : ':='; +ADD : '+'; +SUB : '-'; +MUL : '*'; + +fragment LETTER : ('a'..'z'|'A'..'Z'|'.'|'$'); +fragment DIGIT : [0-9]; + +IDENTIFIER : LETTER (LETTER|DIGIT|'_')*; +INTEGER : '#' ('+'|'-')? DIGIT+; +QUOTED : '\'' IDENTIFIER; +STRING : '"' (~('"'))* '"'; + +WS: [ \r\n\t]+ -> skip; +LINE_COMMENT : '%' ~('\n'|'\r')* '\r'? '\n' -> skip; + +// Parser + +start + : commands+ EOF + ; + +commands + : STRINGS idList + | INTEGERS idList + | FUNCTION id stack + | MACRO id + | READ + | EXECUTE LBRACE function RBRACE + | ITERATE LBRACE function RBRACE + | REVERSE LBRACE function RBRACE + | ENTRY idList0 idList0 idList0 + | SORT + ; + + +identifier + : IDENTIFIER + ; + +id : LBRACE identifier RBRACE + ; + +idList + : LBRACE IDENTIFIER+ RBRACE + ; + +idList0 + : LBRACE IDENTIFIER* RBRACE + ; + +function + : LT | GT | EQUAL | ADD | SUB | ASSIGN | MUL + | identifier + ; + +stack + : LBRACE stackitem+ RBRACE + ; + +stackitem + : function + | STRING + | INTEGER + | QUOTED + | stack + ; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ca408a86348..bdb7fc7bedd 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -85,7 +85,6 @@ requires org.mariadb.jdbc; uses org.mariadb.jdbc.credential.CredentialPlugin; requires org.apache.commons.lang3; - requires antlr.runtime; requires org.antlr.antlr4.runtime; requires org.fxmisc.flowless; requires org.apache.tika.core; diff --git a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java index d0c040842fd..eb0c623f053 100644 --- a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java +++ b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java @@ -34,7 +34,7 @@ public BstPreviewLayout(Path path) { return; } try { - vm = new VM(path.toFile()); + vm = new VM(path); } catch (Exception e) { LOGGER.error("Could not read {}.", path.toAbsolutePath(), e); error = Localization.lang("Error opening file '%0'.", path.toString()); diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java index caaf9078f64..79fa8e63281 100644 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ b/src/main/java/org/jabref/logic/bst/VM.java @@ -1,7 +1,7 @@ package org.jabref.logic.bst; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -26,13 +26,17 @@ import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.StandardField; -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; +import org.antlr.v4.runtime.BailErrorStrategy; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.Tree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,7 +45,7 @@ *

* Documentation can be found in the original bibtex distribution: *

- * https://www.ctan.org/pkg/bibtex + * https://www.ctan.org/pkg/bibtex */ public class VM implements Warn { @@ -65,14 +69,26 @@ public class VM implements Warn { private final Map buildInFunctions; - private File file; + private Path path; - private final CommonTree tree; + private final ParseTree tree; private StringBuilder bbl; private String preamble = ""; + public static class ThrowingErrorListener extends BaseErrorListener { + + public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, + int line, int charPositionInLine, String msg, RecognitionException e) + throws ParseCancellationException { + throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg); + } + } + public static class Identifier { public final String name; @@ -104,20 +120,20 @@ public interface BstFunction { void execute(BstEntry context); } - public VM(File f) throws RecognitionException, IOException { - this(new ANTLRFileStream(f.getPath())); - this.file = f; + public VM(Path path) throws RecognitionException, IOException { + this(CharStreams.fromPath(path)); + this.path = path; } public VM(String s) throws RecognitionException { - this(new ANTLRStringStream(s)); + this(CharStreams.fromString(s)); } private VM(CharStream bst) throws RecognitionException { this(VM.charStream2CommonTree(bst)); } - private VM(CommonTree tree) { + private VM(ParseTree tree) { this.tree = tree; this.buildInFunctions = new HashMap<>(37); @@ -794,12 +810,15 @@ private void addPeriodFunction() { } } - private 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(); + private static ParseTree charStream2CommonTree(CharStream query) throws RecognitionException { + BstLexer lexer = new BstLexer(query); + lexer.removeErrorListeners(); // no infos on file system + lexer.addErrorListener(ThrowingErrorListener.INSTANCE); + BstParser parser = new BstParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); // no infos on file system + parser.addErrorListener(ThrowingErrorListener.INSTANCE); + parser.setErrorHandler(new BailErrorStrategy()); // ParseCancelationException on parse errors + return parser.start(); } private boolean assign(BstEntry context, Object o1, Object o2) { @@ -1106,10 +1125,10 @@ public void execute(BstEntry context) { break; } } catch (VMException e) { - if (file == null) { + if (path == null) { LOGGER.error("ERROR " + e.getMessage() + " (" + c.getLine() + ")"); } else { - LOGGER.error("ERROR " + e.getMessage() + " (" + file.getPath() + ":" + LOGGER.error("ERROR " + e.getMessage() + " (" + path + ":" + c.getLine() + ")"); } throw e; @@ -1156,7 +1175,7 @@ private void execute(String name, BstEntry context) { } private void function(Tree child) { - String name = child.getChild(0).getText(); + String name = ((Token) child.getChild(0).getPayload()).getText(); Tree localStack = child.getChild(1); functions.put(name, new StackFunction(localStack)); } @@ -1170,7 +1189,7 @@ private void integers(Tree child) { Tree t = child.getChild(0); for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); + String name = ((Token) t.getChild(i).getPayload()).getText(); integers.put(name, 0); } } @@ -1185,7 +1204,7 @@ private void strings(Tree child) { Tree t = child.getChild(0); for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); + String name = ((Token) t.getChild(i).getPayload()).getText(); strings.put(name, null); } } diff --git a/src/test/java/org/jabref/logic/bst/TestVM.java b/src/test/java/org/jabref/logic/bst/TestVM.java index bb9fc8ad193..a0bf9c0f76b 100644 --- a/src/test/java/org/jabref/logic/bst/TestVM.java +++ b/src/test/java/org/jabref/logic/bst/TestVM.java @@ -1,8 +1,8 @@ package org.jabref.logic.bst; -import java.io.File; import java.io.IOException; import java.io.StringReader; +import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -16,7 +16,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.util.DummyFileUpdateMonitor; -import org.antlr.runtime.RecognitionException; +import org.antlr.v4.runtime.RecognitionException; import org.junit.jupiter.api.Test; import org.mockito.Answers; @@ -29,7 +29,7 @@ public class TestVM { @Test public void testAbbrv() throws RecognitionException, IOException { - VM vm = new VM(new File("src/test/resources/org/jabref/logic/bst/abbrv.bst")); + VM vm = new VM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); List v = List.of(t1BibtexEntry()); String expected = "\\begin{thebibliography}{1}\\bibitem{canh05}K.~Crowston, H.~Annabi, J.~Howison, and C.~Masango.\\newblock Effective work practices for floss development: A model and propositions.\\newblock In {\\em Hawaii International Conference On System Sciences (HICSS)}, 2005.\\end{thebibliography}"; @@ -607,7 +607,7 @@ public void testVMSwap() throws RecognitionException { @Test public void testHypthenatedName() throws RecognitionException, IOException { - VM vm = new VM(new File("src/test/resources/org/jabref/logic/bst/abbrv.bst")); + VM vm = new VM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); List v = List.of(TestVM.bibtexString2BibtexEntry("@article{canh05, author = \"Jean-Paul Sartre\" }")); assertTrue(vm.run(v).contains("J.-P. Sartre")); } From 08c6a77972b52aa459568caf672b87ba34b3ec02 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Thu, 26 May 2022 01:32:16 +0200 Subject: [PATCH 02/39] apply ide suggestions --- src/main/java/org/jabref/logic/bst/VM.java | 118 +++++++----------- .../java/org/jabref/logic/bst/TestVM.java | 16 ++- 2 files changed, 52 insertions(+), 82 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java index 79fa8e63281..350029d0dfb 100644 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ b/src/main/java/org/jabref/logic/bst/VM.java @@ -307,12 +307,10 @@ private VM(ParseTree tree) { } Object o1 = stack.pop(); - if (!((o1 instanceof String) && (((String) o1).length() == 1))) { + if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { throw new VMException("Can only perform chr.to.int$ on string with length 1"); } - String s = (String) o1; - stack.push((int) s.charAt(0)); }); @@ -356,12 +354,10 @@ private VM(ParseTree tree) { return; } - if (!(o1 instanceof String)) { + if (!(o1 instanceof String s)) { throw new VMException("Operand does not match function empty$"); } - String s = (String) o1; - stack.push("".equals(s.trim()) ? VM.TRUE : VM.FALSE); }); @@ -404,12 +400,10 @@ private VM(ParseTree tree) { } Object o1 = stack.pop(); - if (!(o1 instanceof Integer)) { + if (!(o1 instanceof Integer i)) { throw new VMException("Can only perform operation int.to.chr$ on an Integer"); } - Integer i = (Integer) o1; - stack.push(String.valueOf((char) i.intValue())); }); @@ -475,10 +469,9 @@ private VM(ParseTree tree) { } Object o1 = stack.pop(); - if (!(o1 instanceof String)) { + if (!(o1 instanceof String s)) { throw new VMException("Need a string at the top of the stack for num.names$"); } - String s = (String) o1; stack.push(AuthorList.parse(s).getNumberOfAuthors()); }); @@ -496,9 +489,7 @@ private VM(ParseTree tree) { * * @PREAMBLE strings read from the database files. */ - buildInFunctions.put("preamble$", context -> { - stack.push(preamble); - }); + buildInFunctions.put("preamble$", context -> stack.push(preamble)); /* * Pops the top (string) literal, removes nonalphanumeric characters @@ -644,11 +635,10 @@ private void textLengthFunction() { } Object o1 = stack.pop(); - if (!(o1 instanceof String)) { + if (!(o1 instanceof String s)) { throw new VMException("Can only perform operation on a string text.length$"); } - String s = (String) o1; char[] c = s.toCharArray(); int result = 0; @@ -752,13 +742,10 @@ private void substringFunction() { Object o2 = stack.pop(); Object o3 = stack.pop(); - if (!((o1 instanceof Integer) && (o2 instanceof Integer) && (o3 instanceof String))) { + if (!((o1 instanceof Integer len) && (o2 instanceof Integer start) && (o3 instanceof String s))) { throw new VMException("Expecting two integers and a string for substring$"); } - Integer len = (Integer) o1; - Integer start = (Integer) o2; - int lenI = len; int startI = start; @@ -774,8 +761,6 @@ private void substringFunction() { startI = -Integer.MIN_VALUE / 2; } - String s = (String) o3; - if (startI < 0) { startI += s.length() + 1; startI = Math.max(1, (startI + 1) - lenI); @@ -789,11 +774,10 @@ private void addPeriodFunction() { } Object o1 = stack.pop(); - if (!(o1 instanceof String)) { + if (!(o1 instanceof String s)) { throw new VMException("Can only add a period to a string for add.period$"); } - String s = (String) o1; Matcher m = ADD_PERIOD_PATTERN.matcher(s); if (m.find()) { @@ -896,39 +880,28 @@ public String run(Collection bibEntries, BibDatabase bibDatabase) { 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(); - break; - case BstParser.ITERATE: - iterate(child); - break; - case BstParser.REVERSE: - reverse(child); - break; - case BstParser.ENTRY: - entry(child); - break; - case BstParser.READ: - read(bibDatabase); - break; - case BstParser.MACRO: - macro(child); - break; - default: - LOGGER.info("Unknown type: {}", child.getType()); - break; + case BstParser.STRINGS -> + strings(child); + case BstParser.INTEGERS -> + integers(child); + case BstParser.FUNCTION -> + function(child); + case BstParser.EXECUTE -> + execute(child); + case BstParser.SORT -> + sort(); + case BstParser.ITERATE -> + iterate(child); + case BstParser.REVERSE -> + reverse(child); + case BstParser.ENTRY -> + entry(child); + case BstParser.READ -> + read(bibDatabase); + case BstParser.MACRO -> + macro(child); + default -> + LOGGER.info("Unknown type: {}", child.getType()); } } @@ -960,7 +933,7 @@ private void read(BibDatabase bibDatabase) { // We nevertheless want to have the full month name. // Thus, we lookup the full month name here. return Month.parse(result) - .map(month -> month.getFullName()) + .map(Month::getFullName) .orElse(result); } return result; @@ -1076,8 +1049,7 @@ private void sort() { } private void executeInContext(Object o, BstEntry context) { - if (o instanceof Tree) { - Tree t = (Tree) o; + if (o instanceof Tree t) { new StackFunction(t).execute(context); } else if (o instanceof Identifier) { execute(((Identifier) o).getName(), context); @@ -1107,22 +1079,18 @@ public void execute(BstEntry context) { try { switch (c.getType()) { - case BstParser.STRING: + case BstParser.STRING -> { String s = c.getText(); push(s.substring(1, s.length() - 1)); - break; - case BstParser.INTEGER: - push(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); - break; + } + case BstParser.INTEGER -> + push(Integer.parseInt(c.getText().substring(1))); + case BstParser.QUOTED -> + push(new Identifier(c.getText().substring(1))); + case BstParser.STACK -> + push(c); + default -> + VM.this.execute(c.getText(), context); } } catch (VMException e) { if (path == null) { @@ -1197,8 +1165,6 @@ private void integers(Tree child) { /** * 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); diff --git a/src/test/java/org/jabref/logic/bst/TestVM.java b/src/test/java/org/jabref/logic/bst/TestVM.java index a0bf9c0f76b..97dc0c9bb25 100644 --- a/src/test/java/org/jabref/logic/bst/TestVM.java +++ b/src/test/java/org/jabref/logic/bst/TestVM.java @@ -620,12 +620,16 @@ private static BibEntry bibtexString2BibtexEntry(String s) throws IOException { } private static String t1BibtexString() { - return "@inproceedings{canh05,\n" - + " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.},\n" - + " title = {Effective work practices for floss development: A model and propositions},\n" - + " booktitle = {Hawaii International Conference On System Sciences (HICSS)},\n" - + " year = {2005},\n" + " owner = {oezbek},\n" + " timestamp = {2006.05.29},\n" - + " url = {http://james.howison.name/publications.html}}\n"; + return """ + @inproceedings{canh05, + author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}, + title = {Effective work practices for floss development: A model and propositions}, + booktitle = {Hawaii International Conference On System Sciences (HICSS)}, + year = {2005}, + owner = {oezbek}, + timestamp = {2006.05.29}, + url = {http://james.howison.name/publications.html}} + """; } private static BibEntry t1BibtexEntry() throws IOException { From 7a33e9f0366530e0bbed2a7b7e7b1f2f08fecba6 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Thu, 26 May 2022 22:30:04 +0200 Subject: [PATCH 03/39] Refactor --- build.gradle | 2 +- src/main/antlr4/org/jabref/bst/Bst.g4 | 44 +- .../java/org/jabref/logic/bst/BstEntry.java | 22 + .../jabref/logic/bst/ChangeCaseFunction.java | 65 - .../jabref/logic/bst/FormatNameFunction.java | 67 - .../org/jabref/logic/bst/PurifyFunction.java | 43 - .../jabref/logic/bst/TextPrefixFunction.java | 54 - src/main/java/org/jabref/logic/bst/VM.java | 1316 +++++++++-------- src/main/java/org/jabref/logic/bst/Warn.java | 7 - .../org/jabref/logic/bst/WidthFunction.java | 43 - .../bst/{ => util}/BibtexCaseChanger.java | 43 +- .../bst/{ => util}/BibtexNameFormatter.java | 77 +- .../logic/bst/{ => util}/BibtexPurify.java | 21 +- .../bst/{ => util}/BibtexTextPrefix.java | 17 +- .../logic/bst/{ => util}/BibtexWidth.java | 2 +- .../logic/layout/format/NameFormatter.java | 6 +- .../logic/bst/BibtexCaseChangersTest.java | 3 +- .../logic/bst/BibtexNameFormatterTest.java | 41 +- .../jabref/logic/bst/BibtexPurifyTest.java | 5 +- .../org/jabref/logic/bst/BibtexWidthTest.java | 2 + .../java/org/jabref/logic/bst/TestVM.java | 1 - .../logic/bst/TextPrefixFunctionTest.java | 5 +- 22 files changed, 859 insertions(+), 1027 deletions(-) create mode 100644 src/main/java/org/jabref/logic/bst/BstEntry.java delete mode 100644 src/main/java/org/jabref/logic/bst/ChangeCaseFunction.java delete mode 100644 src/main/java/org/jabref/logic/bst/FormatNameFunction.java delete mode 100644 src/main/java/org/jabref/logic/bst/PurifyFunction.java delete mode 100644 src/main/java/org/jabref/logic/bst/TextPrefixFunction.java delete mode 100644 src/main/java/org/jabref/logic/bst/Warn.java delete mode 100644 src/main/java/org/jabref/logic/bst/WidthFunction.java rename src/main/java/org/jabref/logic/bst/{ => util}/BibtexCaseChanger.java (91%) rename src/main/java/org/jabref/logic/bst/{ => util}/BibtexNameFormatter.java (81%) rename src/main/java/org/jabref/logic/bst/{ => util}/BibtexPurify.java (82%) rename src/main/java/org/jabref/logic/bst/{ => util}/BibtexTextPrefix.java (87%) rename src/main/java/org/jabref/logic/bst/{ => util}/BibtexWidth.java (99%) diff --git a/build.gradle b/build.gradle index a7d15588eb3..98cf03c87d6 100644 --- a/build.gradle +++ b/build.gradle @@ -274,7 +274,7 @@ tasks.register("generateBstGrammarSource", JavaExec) { inputs.dir('src/main/antlr4/org/jabref/bst/') outputs.dir("src-gen/main/java/org/jabref/logic/bst/") - args = ["-o", "src-gen/main/java/org/jabref/logic/bst/", "-listener", "-package", "org.jabref.logic.bst", "$projectDir/src/main/antlr4/org/jabref/bst/Bst.g4"] + args = ["-o", "src-gen/main/java/org/jabref/logic/bst/", "-visitor", "-no-listener", "-package", "org.jabref.logic.bst", "$projectDir/src/main/antlr4/org/jabref/bst/Bst.g4"] } tasks.register("generateSearchGrammarSource", JavaExec) { diff --git a/src/main/antlr4/org/jabref/bst/Bst.g4 b/src/main/antlr4/org/jabref/bst/Bst.g4 index c6faaa0727a..683c60a1e94 100644 --- a/src/main/antlr4/org/jabref/bst/Bst.g4 +++ b/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -21,7 +21,7 @@ EQUAL : '='; ASSIGN : ':='; ADD : '+'; SUB : '-'; -MUL : '*'; +CONCAT : '*'; fragment LETTER : ('a'..'z'|'A'..'Z'|'.'|'$'); fragment DIGIT : [0-9]; @@ -41,37 +41,35 @@ start ; commands - : STRINGS idList - | INTEGERS idList - | FUNCTION id stack - | MACRO id - | READ - | EXECUTE LBRACE function RBRACE - | ITERATE LBRACE function RBRACE - | REVERSE LBRACE function RBRACE - | ENTRY idList0 idList0 idList0 - | SORT + : list=(STRINGS|INTEGERS) id=idListObl #variablesCommand + | FUNCTION LBRACE id=identifier RBRACE exp=stack #functionCommand + | list=MACRO LBRACE id=identifier RBRACE LBRACE exp=STRING RBRACE #variablesCommand + | READ #readCommand + | EXECUTE LBRACE id=function RBRACE #executeCommand + | ITERATE LBRACE id=function RBRACE #iterateCommand + | REVERSE LBRACE id=function RBRACE #reverseCommand + | ENTRY first=idListOpt second=idListOpt third=idListOpt #entryCommand + | SORT #sortCommand ; - identifier - : IDENTIFIER - ; - -id : LBRACE identifier RBRACE - ; + : IDENTIFIER + ; -idList - : LBRACE IDENTIFIER+ RBRACE +idListObl + : LBRACE identifier+ RBRACE ; -idList0 - : LBRACE IDENTIFIER* RBRACE +idListOpt + : LBRACE identifier* RBRACE ; function - : LT | GT | EQUAL | ADD | SUB | ASSIGN | MUL - | identifier + : operator=(LT | GT | EQUAL) #comparisonFunction + | operator=(ADD | SUB) #arithmeticFunction + | ASSIGN #assignmentFunction + | CONCAT #concatFunction + | identifier #userFunction ; stack diff --git a/src/main/java/org/jabref/logic/bst/BstEntry.java b/src/main/java/org/jabref/logic/bst/BstEntry.java new file mode 100644 index 00000000000..0edf00515ab --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstEntry.java @@ -0,0 +1,22 @@ +package org.jabref.logic.bst; + +import java.util.HashMap; +import java.util.Map; + +import org.jabref.model.entry.BibEntry; + +public class BstEntry { + + public final BibEntry entry; + + public final Map localStrings = new HashMap<>(); + + // keys filled by org.jabref.logic.bst.VM.entry based on the contents of the bst file + public final Map fields = new HashMap<>(); + + public final Map localIntegers = new HashMap<>(); + + public BstEntry(BibEntry e) { + this.entry = e; + } +} diff --git a/src/main/java/org/jabref/logic/bst/ChangeCaseFunction.java b/src/main/java/org/jabref/logic/bst/ChangeCaseFunction.java deleted file mode 100644 index 65f4f64c619..00000000000 --- a/src/main/java/org/jabref/logic/bst/ChangeCaseFunction.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Locale; -import java.util.Stack; - -import org.jabref.logic.bst.BibtexCaseChanger.FORMAT_MODE; -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; - -/** - * From the Bibtex manual: - * - * Pops the top two (string) literals; it changes the case of the second - * according to the specifications of the first, as follows. (Note: The word - * `letters' in the next sentence refers only to those at brace-level 0, the - * top-most brace level; no other characters are changed, except perhaps for - * \special characters", described in Section 4.) If the first literal is the - * string `t', it converts to lower case all letters except the very first - * character in the string, which it leaves alone, and except the first - * character following any colon and then nonnull white space, which it also - * leaves alone; if it's the string `l', it converts all letters to lower case; - * and if it's the string `u', it converts all letters to upper case. It then - * pushes this resulting string. If either type is incorrect, it complains and - * pushes the null string; however, if both types are correct but the - * specification string (i.e., the first string) isn't one of the legal ones, it - * merely pushes the second back onto the stack, after complaining. (Another - * note: It ignores case differences in the specification string; for example, - * the strings t and T are equivalent for the purposes of this built-in - * function.) - * - * Christopher: I think this should be another grammar! This parser is horrible. - * - */ -public class ChangeCaseFunction implements BstFunction { - - private final VM vm; - - public ChangeCaseFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation change.case$"); - } - - Object o1 = stack.pop(); - if (!((o1 instanceof String) && (((String) o1).length() == 1))) { - throw new VMException("A format string of length 1 is needed for change.case$"); - } - - Object o2 = stack.pop(); - if (!(o2 instanceof String)) { - throw new VMException("A string is needed as second parameter for change.case$"); - } - - char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); - String s = (String) o2; - - stack.push(BibtexCaseChanger.changeCase(s, FORMAT_MODE.getFormatModeForBSTFormat(format))); - } -} diff --git a/src/main/java/org/jabref/logic/bst/FormatNameFunction.java b/src/main/java/org/jabref/logic/bst/FormatNameFunction.java deleted file mode 100644 index b2845c89be7..00000000000 --- a/src/main/java/org/jabref/logic/bst/FormatNameFunction.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Stack; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; -import org.jabref.model.entry.Author; -import org.jabref.model.entry.AuthorList; - -/** - * From Bibtex: - * - * "The |built_in| function {\.{format.name\$}} pops the - * top three literals (they are a string, an integer, and a string - * literal, in that order). The last string literal represents a - * name list (each name corresponding to a person), the integer - * literal specifies which name to pick from this list, and the - * first string literal specifies how to format this name, as - * described in the \BibTeX\ documentation. Finally, this function - * pushes the formatted name. If any of the types is incorrect, it - * complains and pushes the null string." - * - * All the pain is encapsulated in BibtexNameFormatter. :-) - * - */ -public class FormatNameFunction implements BstFunction { - - private final VM vm; - - public FormatNameFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation format.name$"); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - Object o3 = stack.pop(); - - if (!(o1 instanceof String) && !(o2 instanceof Integer) && !(o3 instanceof String)) { - // warning("A string is needed for change.case$"); - stack.push(""); - return; - } - - String format = (String) o1; - Integer name = (Integer) o2; - String names = (String) o3; - - if (names == null) { - stack.push(""); - } else { - AuthorList a = AuthorList.parse(names); - if (name > a.getNumberOfAuthors()) { - throw new VMException("Author Out of Bounds. Number " + name + " invalid for " + names); - } - Author author = a.getAuthor(name - 1); - - stack.push(BibtexNameFormatter.formatName(author, format, vm)); - } - } -} diff --git a/src/main/java/org/jabref/logic/bst/PurifyFunction.java b/src/main/java/org/jabref/logic/bst/PurifyFunction.java deleted file mode 100644 index b0df9df3735..00000000000 --- a/src/main/java/org/jabref/logic/bst/PurifyFunction.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Stack; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; - -/** - * - * The |built_in| function {\.{purify\$}} pops the top (string) literal, removes - * nonalphanumeric characters except for |white_space| and |sep_char| characters - * (these get converted to a |space|) and removes certain alphabetic characters - * contained in the control sequences associated with a special character, and - * pushes the resulting string. If the literal isn't a string, it complains and - * pushes the null string. - * - */ -public class PurifyFunction implements BstFunction { - - private final VM vm; - - public PurifyFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation purify$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - vm.warn("A string is needed for purify$"); - stack.push(""); - return; - } - - stack.push(BibtexPurify.purify((String) o1, vm)); - } -} diff --git a/src/main/java/org/jabref/logic/bst/TextPrefixFunction.java b/src/main/java/org/jabref/logic/bst/TextPrefixFunction.java deleted file mode 100644 index 415ee97498b..00000000000 --- a/src/main/java/org/jabref/logic/bst/TextPrefixFunction.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Stack; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; - -/** - * The |built_in| function {\.{text.prefix\$}} pops the top two literals - * (the integer literal |pop_lit1| and a string literal, in that order). - * It pushes the substring of the (at most) |pop_lit1| consecutive text - * characters starting from the beginning of the string. This function - * is similar to {\.{substring\$}}, but this one considers an accented - * character (or more precisely, a ``special character''$\!$, even if - * it's missing its matching |right_brace|) to be a single text character - * (rather than however many |ASCII_code| characters it actually - * comprises), and this function doesn't consider braces to be text - * characters; furthermore, this function appends any needed matching - * |right_brace|s. If any of the types is incorrect, it complains and - * pushes the null string. - */ -public class TextPrefixFunction implements BstFunction { - - private final VM vm; - - public TextPrefixFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation text.prefix$"); - } - - Object o1 = stack.pop(); - if (!(o1 instanceof Integer)) { - vm.warn("An integer is needed as first parameter to text.prefix$"); - stack.push(""); - return; - } - - Object o2 = stack.pop(); - if (!(o2 instanceof String)) { - vm.warn("A string is needed as second parameter to text.prefix$"); - stack.push(""); - return; - } - - stack.push(BibtexTextPrefix.textPrefix((Integer) o1, (String) o2, vm)); - } -} diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java index 350029d0dfb..3c9fa17d623 100644 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ b/src/main/java/org/jabref/logic/bst/VM.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.List; import java.util.ListIterator; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Stack; @@ -18,7 +19,13 @@ import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.bibtex.FieldWriterPreferences; import org.jabref.logic.bibtex.InvalidFieldValueException; +import org.jabref.logic.bst.util.BibtexCaseChanger; +import org.jabref.logic.bst.util.BibtexNameFormatter; +import org.jabref.logic.bst.util.BibtexPurify; +import org.jabref.logic.bst.util.BibtexTextPrefix; +import org.jabref.logic.bst.util.BibtexWidth; import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.Month; @@ -33,7 +40,6 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; -import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.Tree; @@ -47,7 +53,7 @@ *

* https://www.ctan.org/pkg/bibtex */ -public class VM implements Warn { +public class VM { public static final Integer FALSE = 0; @@ -67,7 +73,7 @@ public class VM implements Warn { private Stack stack = new Stack<>(); - private final Map buildInFunctions; + private final Map buildInFunctions = new HashMap<>(37); private Path path; @@ -77,6 +83,8 @@ public class VM implements Warn { private String preamble = ""; + private int bstWarning = 1; + public static class ThrowingErrorListener extends BaseErrorListener { public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); @@ -135,606 +143,613 @@ private VM(CharStream bst) throws RecognitionException { private VM(ParseTree tree) { this.tree = tree; + init(); + } - this.buildInFunctions = new HashMap<>(37); + private static ParseTree charStream2CommonTree(CharStream query) throws RecognitionException { + BstLexer lexer = new BstLexer(query); + lexer.removeErrorListeners(); // no infos on file system + lexer.addErrorListener(ThrowingErrorListener.INSTANCE); + BstParser parser = new BstParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); // no infos on file system + parser.addErrorListener(ThrowingErrorListener.INSTANCE); + parser.setErrorHandler(new BailErrorStrategy()); // ParseCancelationException on parse errors + return parser.start(); + } - /* - * Pops the top two (integer) literals, compares them, and pushes - * the integer 1 if the second is greater than the first, 0 - * otherwise. - */ - buildInFunctions.put(">", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation >"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); + public void init() { + buildInFunctions.put(">", this::bstIsGreaterThan); + buildInFunctions.put("<", this::bstIsLowerThan); + buildInFunctions.put("=", this::bstEquals); + buildInFunctions.put("+", this::bstAdd); + buildInFunctions.put("-", this::bstSubtract); + buildInFunctions.put("*", this::bstConcat); + buildInFunctions.put(":=", this::bstAssign); + buildInFunctions.put("add.period$", this::bstAddPeriod); + buildInFunctions.put("call.type$", this::bstCallType); + buildInFunctions.put("change.case$", this::bstChangeCase); + buildInFunctions.put("chr.to.int$", this::bstChrToInt); + buildInFunctions.put("cite$", this::bstCite); + buildInFunctions.put("duplicate$", this::bstDuplicate); + buildInFunctions.put("empty$", this::bstEmpty); + buildInFunctions.put("format.name$", this::bstFormatName); + buildInFunctions.put("if$", this::bstIf); + buildInFunctions.put("int.to.chr$", this::bstIntToChr); + buildInFunctions.put("int.to.str$", this::bstIntToStr); + buildInFunctions.put("missing$", this::bstMissing); + buildInFunctions.put("newline$", this::bstNewLine); + buildInFunctions.put("num.names$", this::bstNumNames); + buildInFunctions.put("pop$", this::bstPop); + buildInFunctions.put("preamble$", this::bstPreamble); + buildInFunctions.put("purify$", this::bstPurify); + buildInFunctions.put("quote$", this::bstQuote); + buildInFunctions.put("skip$", this::bstSkip); + buildInFunctions.put("stack$", this::bstStack); + buildInFunctions.put("substring$", this::bstSubstring); + buildInFunctions.put("swap$", this::bstSwap); + buildInFunctions.put("text.length$", this::bstTextLength); + buildInFunctions.put("text.prefix$", this::bstTextPrefix); + buildInFunctions.put("top$", this::bstTop); + buildInFunctions.put("type$", this::bstType); + buildInFunctions.put("warning$", this::bstWarning); + buildInFunctions.put("while$", this::bstWhile); + buildInFunctions.put("width$", this::bstWidth); + buildInFunctions.put("write$", this::bstWrite); + } - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new VMException("Can only compare two integers with >"); - } + /** + * Pops the top two (integer) literals, compares them, and pushes + * the integer 1 if the second is greater than the first, 0 + * otherwise. + */ + private void bstIsGreaterThan(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation >"); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); - stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? VM.TRUE : VM.FALSE); - }); + if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { + throw new VMException("Can only compare two integers with >"); + } - /* Analogous to >. */ - buildInFunctions.put("<", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation <"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); + stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? VM.TRUE : VM.FALSE); + } - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new VMException("Can only compare two integers with <"); - } + /** + * Pops the top two (integer) literals, compares them, and pushes + * the integer 1 if the second is lower than the first, 0 + * otherwise. + */ + private void bstIsLowerThan(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation <"); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); - stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? VM.TRUE : VM.FALSE); - }); + if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { + throw new VMException("Can only compare two integers with <"); + } - /* - * Pops the top two (both integer or both string) literals, compares - * them, and pushes the integer 1 if they're equal, 0 otherwise. - */ - buildInFunctions.put("=", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation ="); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); + stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? VM.TRUE : VM.FALSE); + } - if ((o1 == null) ^ (o2 == null)) { - stack.push(VM.FALSE); - return; - } + /** + * Pops the top two (both integer or both string) literals, compares + * them, and pushes the integer 1 if they're equal, 0 otherwise. + */ + private void bstEquals(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.TRUE); - return; - } + if ((o1 == null) ^ (o2 == null)) { + stack.push(VM.FALSE); + return; + } - stack.push(o1.equals(o2) ? VM.TRUE : VM.FALSE); - }); + if ((o1 == null) && (o2 == null)) { + stack.push(VM.TRUE); + return; + } - /* Pops the top two (integer) literals and pushes their sum. */ - buildInFunctions.put("+", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation +"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); + stack.push(o1.equals(o2) ? VM.TRUE : VM.FALSE); + } - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new VMException("Can only compare two integers with +"); - } + /** + * Pops the top two (integer) literals and pushes their sum. + */ + private void bstAdd(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation +"); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); - stack.push((Integer) o1 + (Integer) o2); - }); + if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { + throw new VMException("Can only compare two integers with +"); + } - /* - * Pops the top two (integer) literals and pushes their difference - * (the first subtracted from the second). - */ - buildInFunctions.put("-", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation -"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); + stack.push((Integer) o1 + (Integer) o2); + } - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new VMException("Can only subtract two integers with -"); - } + /** + * Pops the top two (integer) literals and pushes their difference + * (the first subtracted from the second). + */ + private void bstSubtract(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation -"); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); - stack.push((Integer) o1 - (Integer) o2); - }); - - /* - * Pops the top two (string) literals, concatenates them (in reverse - * order, that is, the order in which pushed), and pushes the - * resulting string. - */ - buildInFunctions.put("*", 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 -"); + } - if (o1 == null) { - o1 = ""; - } - if (o2 == null) { - o2 = ""; - } + stack.push((Integer) o1 - (Integer) o2); + } - if (!((o1 instanceof String) && (o2 instanceof String))) { - LOGGER.error("o1: {} ({})", o1, o1.getClass()); - LOGGER.error("o2: {} ({})", o2, o2.getClass()); - throw new VMException("Can only concatenate two String with *"); - } + /** + * Pops the top two (string) literals, concatenates them (in reverse + * order, that is, the order in which pushed), and pushes the + * resulting string. + */ + private void bstConcat(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation *"); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); - stack.push(o1.toString() + o2); - }); + if (o1 == null) { + o1 = ""; + } + if (o2 == null) { + o2 = ""; + } - /* - * Pops the top two literals and assigns to the first (which must be - * a global or entry variable) the value of the second. - */ - buildInFunctions.put(":=", context -> { - if (stack.size() < 2) { - throw new VMException("Invalid call to operation :="); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - assign(context, o1, o2); - }); - - /* - * Pops the top (string) literal, adds a `.' to it if the last non - * '}' character isn't a `.', `?', or `!', and pushes this resulting - * string. - */ - buildInFunctions.put("add.period$", context -> addPeriodFunction()); - - /* - * 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. - */ - buildInFunctions.put("call.type$", 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(), context); - }); - - buildInFunctions.put("change.case$", new ChangeCaseFunction(this)); - - /* - * Pops the top (string) literal, makes sure it's a single - * character, converts it to the corresponding ASCII integer, and - * pushes this integer. - */ - buildInFunctions.put("chr.to.int$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation chr.to.int$"); - } - Object o1 = stack.pop(); + if (!((o1 instanceof String) && (o2 instanceof String))) { + LOGGER.error("o1: {} ({})", o1, o1.getClass()); + LOGGER.error("o2: {} ({})", o2, o2.getClass()); + throw new VMException("Can only concatenate two String with *"); + } - if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { - throw new VMException("Can only perform chr.to.int$ on string with length 1"); - } + stack.push(o1.toString() + o2); + } + + /** + * Pops the top two literals and assigns to the first (which must be + * a global or entry variable) the value of the second. + */ + private void bstAssign(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Invalid call to operation :="); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + doBstAssign(context, o1, o2); + } - stack.push((int) s.charAt(0)); - }); + private boolean doBstAssign(BstEntry context, Object o1, Object o2) { + if (!(o1 instanceof VM.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { + throw new VMException("Invalid parameters"); + } - /* - * Pushes the string that was the \cite-command argument for this - * entry. - */ - buildInFunctions.put("cite$", context -> { - if (context == null) { - throw new VMException("Must have an entry to cite$"); - } - stack.push(context.entry.getCitationKey().orElse(null)); - }); - - /* - * Pops the top literal from the stack and pushes two copies of it. - */ - buildInFunctions.put("duplicate$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation duplicate$"); - } - Object o1 = stack.pop(); - - stack.push(o1); - stack.push(o1); - }); - - /* - * 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. - */ - buildInFunctions.put("empty$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation empty$"); - } - Object o1 = stack.pop(); + String name = ((VM.Identifier) o1).getName(); - if (o1 == null) { - stack.push(VM.TRUE); - return; + if (o2 instanceof String) { + if ((context != null) && context.localStrings.containsKey(name)) { + context.localStrings.put(name, (String) o2); + return true; } - if (!(o1 instanceof String s)) { - throw new VMException("Operand does not match function empty$"); + if (strings.containsKey(name)) { + strings.put(name, (String) o2); + return true; } + return false; + } - stack.push("".equals(s.trim()) ? VM.TRUE : VM.FALSE); - }); + if ((context != null) && context.localIntegers.containsKey(name)) { + context.localIntegers.put(name, (Integer) o2); + return true; + } - buildInFunctions.put("format.name$", new FormatNameFunction(this)); + if (integers.containsKey(name)) { + integers.put(name, (Integer) o2); + return true; + } + return false; + } - /* - * 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. - */ - buildInFunctions.put("if$", 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(); + /** + * Pops the top (string) literal, adds a `.' to it if the last non + * '}' character isn't a `.', `?', or `!', and pushes this resulting + * string. + */ + private void bstAddPeriod(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation add.period$"); + } + Object o1 = 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$."); - } + if (!(o1 instanceof String s)) { + throw new VMException("Can only add a period to a string for add.period$"); + } - if ((Integer) i > 0) { - VM.this.executeInContext(f2, context); - } else { - VM.this.executeInContext(f1, context); - } - }); - - /* - * 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. - */ - buildInFunctions.put("int.to.chr$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation int.to.chr$"); - } - Object o1 = stack.pop(); + Matcher m = ADD_PERIOD_PATTERN.matcher(s); - if (!(o1 instanceof Integer i)) { - throw new VMException("Can only perform operation int.to.chr$ on an Integer"); + if (m.find()) { + StringBuilder sb = new StringBuilder(); + 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); + } + } - stack.push(String.valueOf((char) i.intValue())); - }); + /** + * 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. + */ + private void bstCallType(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(), context); + } - /* - * Pops the top (integer) literal, converts it to its (unique) - * string equivalent, and pushes this string. - */ - buildInFunctions.put("int.to.str$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation int.to.str$"); - } - Object o1 = stack.pop(); + /** + * Pops the top two (string) literals; it changes the case of the second + * according to the specifications of the first, as follows. (Note: The word + * `letters' in the next sentence refers only to those at brace-level 0, the + * top-most brace level; no other characters are changed, except perhaps for + * \special characters", described in Section 4.) If the first literal is the + * string `t', it converts to lower case all letters except the very first + * character in the string, which it leaves alone, and except the first + * character following any colon and then nonnull white space, which it also + * leaves alone; if it's the string `l', it converts all letters to lower case; + * and if it's the string `u', it converts all letters to upper case. It then + * pushes this resulting string. If either type is incorrect, it complains and + * pushes the null string; however, if both types are correct but the + * specification string (i.e., the first string) isn't one of the legal ones, it + * merely pushes the second back onto the stack, after complaining. (Another + * note: It ignores case differences in the specification string; for example, + * the strings t and T are equivalent for the purposes of this built-in + * function.) + */ + private void bstChangeCase(BstEntry value) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation change.case$"); + } - if (!(o1 instanceof Integer)) { - throw new VMException("Can only transform an integer to an string using int.to.str$"); - } + Object o1 = stack.pop(); + if (!((o1 instanceof String) && (((String) o1).length() == 1))) { + throw new VMException("A format string of length 1 is needed for change.case$"); + } - stack.push(o1.toString()); - }); + Object o2 = stack.pop(); + if (!(o2 instanceof String s)) { + throw new VMException("A string is needed as second parameter for change.case$"); + } - /* - * Pops the top literal and pushes the integer 1 if it's a missing - * field, 0 otherwise. - */ - buildInFunctions.put("missing$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation missing$"); - } - Object o1 = stack.pop(); + char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); - if (o1 == null) { - stack.push(VM.TRUE); - return; - } + stack.push(BibtexCaseChanger.changeCase(s, BibtexCaseChanger.FORMAT_MODE.getFormatModeForBSTFormat(format))); + } - if (!(o1 instanceof String)) { - warn("Not a string or missing field in operation missing$"); - stack.push(VM.TRUE); - return; - } + /** + * Pops the top (string) literal, makes sure it's a single + * character, converts it to the corresponding ASCII integer, and + * pushes this integer. + */ + private void bstChrToInt(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation chr.to.int$"); + } + Object o1 = stack.pop(); - stack.push(VM.FALSE); - }); - - /* - * Writes onto the bbl file what is 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. - */ - buildInFunctions.put("newline$", context -> VM.this.bbl.append('\n')); - - /* - * 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. - */ - buildInFunctions.put("num.names$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation num.names$"); - } - Object o1 = stack.pop(); + if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { + throw new VMException("Can only perform chr.to.int$ on string with length 1"); + } - if (!(o1 instanceof String s)) { - throw new VMException("Need a string at the top of the stack for num.names$"); - } + stack.push((int) s.charAt(0)); + } - stack.push(AuthorList.parse(s).getNumberOfAuthors()); - }); - - /* - * Pops the top of the stack but doesn't print it; this gets rid of - * an unwanted stack literal. - */ - buildInFunctions.put("pop$", context -> stack.pop()); - - /* - * 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. - */ - buildInFunctions.put("preamble$", context -> stack.push(preamble)); - - /* - * 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)); - - /* - * Pushes the string consisting of the double-quote character. - */ - buildInFunctions.put("quote$", context -> stack.push("\"")); - - /* - * Is a no-op. - */ - buildInFunctions.put("skip$", context -> { - // Nothing to do! Yeah! - }); - - /* - * Pops and prints the whole stack; it's meant to be used for style - * designers while debugging. - */ - buildInFunctions.put("stack$", context -> { - while (!stack.empty()) { - LOGGER.debug("Stack entry {}", stack.pop()); - } - }); - - /* - * 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). - */ - buildInFunctions.put("substring$", context -> substringFunction()); - - /* - * Swaps the top two literals on the stack. 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. - */ - buildInFunctions.put("swap$", 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); - }); - - /* - * 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. - */ - buildInFunctions.put("text.length$", context -> textLengthFunction()); - - /* - * 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)); - - /* - * Pops and prints the top of the stack to the log file. It's useful for debugging. - */ - buildInFunctions.put("top$", context -> LOGGER.debug("Stack entry {}", stack.pop())); - - /* - * Pushes the current entry's type (book, article, etc.), but pushes - * the null string if the type is either unknown or undefined. - */ - buildInFunctions.put("type$", context -> { - if (context == null) { - throw new VMException("type$ need a context."); - } + /** + * Pushes the string that was the \cite-command argument for this + * entry. + */ + private void bstCite(BstEntry context) { + if (context == null) { + throw new VMException("Must have an entry to cite$"); + } + stack.push(context.entry.getCitationKey().orElse(null)); + } - stack.push(context.entry.getType().getName()); - }); + /** + * Pops the top literal from the stack and pushes two copies of it. + */ + private void bstDuplicate(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation duplicate$"); + } + Object o1 = stack.pop(); + + stack.push(o1); + stack.push(o1); + } + + /** + * 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. + */ + private void bstEmpty(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation empty$"); + } + Object o1 = stack.pop(); + + if (o1 == null) { + stack.push(VM.TRUE); + return; + } - /* - * Pops the top (string) literal and prints it following a warning - * message. This also increments a count of the number of warning - * messages issued. - */ - buildInFunctions.put("warning$", new BstFunction() { - int warning = 1; + if (!(o1 instanceof String s)) { + throw new VMException("Operand does not match function empty$"); + } + + stack.push("".equals(s.trim()) ? VM.TRUE : VM.FALSE); + } + + /** + * The |built_in| function {\.{format.name\$}} pops the + * top three literals (they are a string, an integer, and a string + * literal, in that order). The last string literal represents a + * name list (each name corresponding to a person), the integer + * literal specifies which name to pick from this list, and the + * first string literal specifies how to format this name, as + * described in the \BibTeX\ documentation. Finally, this function + * pushes the formatted name. If any of the types is incorrect, it + * complains and pushes the null string. + */ + private void bstFormatName(BstEntry context) { + if (stack.size() < 3) { + throw new VMException("Not enough operands on stack for operation format.name$"); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + Object o3 = stack.pop(); + + if (!(o1 instanceof String) && !(o2 instanceof Integer) && !(o3 instanceof String)) { + // warning("A string is needed for change.case$"); + stack.push(""); + return; + } - @Override - public void execute(BstEntry context) { - LOGGER.warn("Warning (#" + (warning++) + "): " + stack.pop()); + String format = (String) o1; + Integer name = (Integer) o2; + String names = (String) o3; + + if (names == null) { + stack.push(""); + } else { + AuthorList a = AuthorList.parse(names); + if (name > a.getNumberOfAuthors()) { + throw new VMException("Author Out of Bounds. Number " + name + " invalid for " + names); } - }); + Author author = a.getAuthor(name - 1); - /* - * 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. - */ - buildInFunctions.put("while$", this::whileFunction); + stack.push(BibtexNameFormatter.formatName(author, format)); + } + } - buildInFunctions.put("width$", new WidthFunction(this)); + /** + * 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. + */ + private void bstIf(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 VM.Identifier) || (f1 instanceof Tree)) + && ((f2 instanceof VM.Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { + throw new VMException("Expecting two functions and an integer for if$."); + } - /* - * 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). - */ - buildInFunctions.put("write$", context -> { - String s = (String) stack.pop(); - VM.this.bbl.append(s); - }); + if ((Integer) i > 0) { + VM.this.executeInContext(f2, context); + } else { + VM.this.executeInContext(f1, context); + } } - private void textLengthFunction() { + /** + * 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. + */ + private void bstIntToChr(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation text.length$"); + throw new VMException("Not enough operands on stack for operation int.to.chr$"); } Object o1 = stack.pop(); - if (!(o1 instanceof String s)) { - throw new VMException("Can only perform operation on a string text.length$"); + if (!(o1 instanceof Integer i)) { + throw new VMException("Can only perform operation int.to.chr$ on an Integer"); } - char[] c = s.toCharArray(); - int result = 0; + stack.push(String.valueOf((char) i.intValue())); + } - // Comments from bibtex.web: + /** + * Pops the top (integer) literal, converts it to its (unique) + * string equivalent, and pushes this string. + */ + private void bstIntToStr(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation int.to.str$"); + } + Object o1 = stack.pop(); - // sp_ptr := str_start[pop_lit1]; - int i = 0; + if (!(o1 instanceof Integer)) { + throw new VMException("Can only transform an integer to an string using int.to.str$"); + } - // sp_end := str_start[pop_lit1+1]; - int n = s.length(); + stack.push(o1.toString()); + } - // sp_brace_level := 0; - int braceLevel = 0; + /** + * Pops the top literal and pushes the integer 1 if it's a missing + * field, 0 otherwise. + */ + private void bstMissing(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation missing$"); + } + Object o1 = stack.pop(); - // 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 (c[i] == '{') { - // incr(sp_brace_level); - braceLevel++; - } - // incr(sp_ptr); - i++; - // end; - } - // incr(num_text_chars); - result++; - // end; - } - // end - } + if (o1 == null) { + stack.push(VM.TRUE); + return; + } - // 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++; - } + if (!(o1 instanceof String)) { + LOGGER.warn("Not a string or missing field in operation missing$"); + stack.push(VM.TRUE); + return; } - stack.push(result); + + stack.push(VM.FALSE); } - private void whileFunction(BstEntry context) { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation while$"); + /** + * Writes onto the bbl file what is 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. + */ + private void bstNewLine(BstEntry context) { + VM.this.bbl.append('\n'); + } + + /** + * 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. + */ + private void bstNumNames(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation num.names$"); } - Object f2 = stack.pop(); - Object f1 = stack.pop(); + Object o1 = stack.pop(); - if (!((f1 instanceof Identifier) || (f1 instanceof Tree)) - && ((f2 instanceof Identifier) || (f2 instanceof Tree))) { - throw new VMException("Expecting two functions for while$."); + if (!(o1 instanceof String s)) { + throw new VMException("Need a string at the top of the stack for num.names$"); } - do { - VM.this.executeInContext(f1, context); + stack.push(AuthorList.parse(s).getNumberOfAuthors()); + } - 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 <= 0) { - break; - } - VM.this.executeInContext(f2, context); - } while (true); + /** + * Pops the top of the stack but doesn't print it; this gets rid of + * an unwanted stack literal. + */ + private void bstPop(BstEntry context) { + stack.pop(); + } + + /** + * 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 were none) + * '@PREAMBLE' strings are read from the database files. + */ + private void bstPreamble(BstEntry context) { + stack.push(preamble); + } + + /** + * 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. + */ + private void bstPurify(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation purify$"); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String)) { + LOGGER.warn("A string is needed for purify$"); + stack.push(""); + return; + } + + stack.push(BibtexPurify.purify((String) o1)); + } + + /** + * Pushes the string consisting of the double-quote character. + */ + private void bstQuote(BstEntry context) { + stack.push("\""); + } + + /** + * Does nothing. + */ + private void bstSkip(BstEntry context) { + // no-op + } + + /** + * Pops and prints the whole stack; it's meant to be used for style + * designers while debugging. + */ + private void bstStack(BstEntry context) { + while (!stack.empty()) { + LOGGER.debug("Stack entry {}", stack.pop()); + } } - private void substringFunction() { + /** + * 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). + */ + private void bstSubstring(BstEntry context) { if (stack.size() < 3) { throw new VMException("Not enough operands on stack for operation substring$"); } @@ -768,73 +783,205 @@ private void substringFunction() { stack.push(s.substring(startI - 1, Math.min((startI - 1) + lenI, s.length()))); } - private void addPeriodFunction() { + /** + * Swaps the top two literals on the stack. 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. + */ + private void bstSwap(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); + } + + /** + * 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. + */ + private void bstTextLength(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation add.period$"); + throw new VMException("Not enough operands on stack for operation text.length$"); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new VMException("Can only add a period to a string for add.period$"); + throw new VMException("Can only perform operation on a string text.length$"); } - Matcher m = ADD_PERIOD_PATTERN.matcher(s); + char[] c = s.toCharArray(); + int result = 0; + int i = 0; + int n = s.length(); + int braceLevel = 0; - if (m.find()) { - StringBuilder sb = new StringBuilder(); - m.appendReplacement(sb, m.group(1)); - sb.append('.'); - String group2 = m.group(2); - if (group2 != null) { - sb.append(m.group(2)); + while (i < n) { + i++; + if (c[i - 1] == '{') { + braceLevel++; + if ((braceLevel == 1) && (i < n)) { + if (c[i] == '\\') { + i++; // skip over backslash + while ((i < n) && (braceLevel > 0)) { + if (c[i] == '}') { + braceLevel--; + } else if (c[i] == '{') { + braceLevel++; + } + i++; + } + result++; + } + } + } else if (c[i - 1] == '}') { + if (braceLevel > 0) { + braceLevel--; + } + } else { + result++; } - stack.push(sb.toString()); - } else { - stack.push(s); } + stack.push(result); } - private static ParseTree charStream2CommonTree(CharStream query) throws RecognitionException { - BstLexer lexer = new BstLexer(query); - lexer.removeErrorListeners(); // no infos on file system - lexer.addErrorListener(ThrowingErrorListener.INSTANCE); - BstParser parser = new BstParser(new CommonTokenStream(lexer)); - parser.removeErrorListeners(); // no infos on file system - parser.addErrorListener(ThrowingErrorListener.INSTANCE); - parser.setErrorHandler(new BailErrorStrategy()); // ParseCancelationException on parse errors - return parser.start(); + /** + * 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. + */ + private void bstTextPrefix(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation text.prefix$"); + } + + Object o1 = stack.pop(); + if (!(o1 instanceof Integer)) { + LOGGER.warn("An integer is needed as first parameter to text.prefix$"); + stack.push(""); + return; + } + + Object o2 = stack.pop(); + if (!(o2 instanceof String)) { + LOGGER.warn("A string is needed as second parameter to text.prefix$"); + stack.push(""); + return; + } + + stack.push(BibtexTextPrefix.textPrefix((Integer) o1, (String) o2)); } - private boolean assign(BstEntry context, Object o1, Object o2) { - if (!(o1 instanceof Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { - throw new VMException("Invalid parameters"); + /** + * Pops and prints the top of the stack to the log file. It's useful for debugging. + */ + private void bstTop(BstEntry context) { + LOGGER.debug("Stack entry {}", stack.pop()); + } + + /** + * Pushes the current entry's type (book, article, etc.), but pushes + * the null string if the type is either unknown or undefined. + */ + private void bstType(BstEntry context) { + if (context == null) { + throw new VMException("type$ need a context."); } - String name = ((Identifier) o1).getName(); + stack.push(context.entry.getType().getName()); + } - if (o2 instanceof String) { - if ((context != null) && context.localStrings.containsKey(name)) { - context.localStrings.put(name, (String) o2); - return true; - } + /** + * Pops the top (string) literal and prints it following a warning + * message. This also increments a count of the number of warning + * messages issued. + */ + private void bstWarning(BstEntry context) { + LOGGER.warn("Warning (#" + (bstWarning++) + "): " + stack.pop()); + } - if (strings.containsKey(name)) { - strings.put(name, (String) o2); - return true; - } - return false; + /** + * 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. + */ + private void bstWhile(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 ((context != null) && context.localIntegers.containsKey(name)) { - context.localIntegers.put(name, (Integer) o2); - return true; + if (!((f1 instanceof VM.Identifier) || (f1 instanceof Tree)) + && ((f2 instanceof VM.Identifier) || (f2 instanceof Tree))) { + throw new VMException("Expecting two functions for while$."); } - if (integers.containsKey(name)) { - integers.put(name, (Integer) o2); - return true; + do { + 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 <= 0) { + break; + } + executeInContext(f2, context); + } while (true); + } + + /** + * The |built_in| function {\.{width\$}} pops the top (string) literal and + * pushes the integer that represents its width in units specified by the + * |char_width| array. This function takes the literal literally; that is, it + * assumes each character in the string is to be printed as is, regardless of + * whether the character has a special meaning to \TeX, except that special + * characters (even without their |right_brace|s) are handled specially. If the + * literal isn't a string, it complains and pushes~0. + */ + private void bstWidth(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation width$"); } - return false; + Object o1 = stack.pop(); + + if (!(o1 instanceof String)) { + LOGGER.warn("A string is needed for width$"); + stack.push(0); + return; + } + + stack.push(BibtexWidth.width((String) o1)); + } + + /** + * 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). + */ + private void bstWrite(BstEntry context) { + String s = (String) stack.pop(); + bbl.append(s); } public String run(BibDatabase db) { @@ -1143,7 +1290,7 @@ private void execute(String name, BstEntry context) { } private void function(Tree child) { - String name = ((Token) child.getChild(0).getPayload()).getText(); + String name = child.getChild(0).getText(); Tree localStack = child.getChild(1); functions.put(name, new StackFunction(localStack)); } @@ -1157,7 +1304,7 @@ private void integers(Tree child) { Tree t = child.getChild(0); for (int i = 0; i < t.getChildCount(); i++) { - String name = ((Token) t.getChild(i).getPayload()).getText(); + String name = t.getChild(i).getText(); integers.put(name, 0); } } @@ -1170,27 +1317,11 @@ private void strings(Tree child) { Tree t = child.getChild(0); for (int i = 0; i < t.getChildCount(); i++) { - String name = ((Token) t.getChild(i).getPayload()).getText(); + String name = t.getChild(i).getText(); strings.put(name, null); } } - public static class BstEntry { - - public final BibEntry entry; - - public final Map localStrings = new HashMap<>(); - - // keys filled by org.jabref.logic.bst.VM.entry based on the contents of the bst file - public final Map fields = new HashMap<>(); - - public final Map localIntegers = new HashMap<>(); - - public BstEntry(BibEntry e) { - this.entry = e; - } - } - private void push(Integer integer) { stack.push(integer); } @@ -1222,9 +1353,4 @@ public Map getFunctions() { public Stack getStack() { return stack; } - - @Override - public void warn(String string) { - LOGGER.warn(string); - } } diff --git a/src/main/java/org/jabref/logic/bst/Warn.java b/src/main/java/org/jabref/logic/bst/Warn.java deleted file mode 100644 index 7a524ad9834..00000000000 --- a/src/main/java/org/jabref/logic/bst/Warn.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.jabref.logic.bst; - -@FunctionalInterface -public interface Warn { - - void warn(String s); -} diff --git a/src/main/java/org/jabref/logic/bst/WidthFunction.java b/src/main/java/org/jabref/logic/bst/WidthFunction.java deleted file mode 100644 index 06784267bf0..00000000000 --- a/src/main/java/org/jabref/logic/bst/WidthFunction.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Stack; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; - -/** - * The |built_in| function {\.{width\$}} pops the top (string) literal and - * pushes the integer that represents its width in units specified by the - * |char_width| array. This function takes the literal literally; that is, it - * assumes each character in the string is to be printed as is, regardless of - * whether the character has a special meaning to \TeX, except that special - * characters (even without their |right_brace|s) are handled specially. If the - * literal isn't a string, it complains and pushes~0. - * - */ -public class WidthFunction implements BstFunction { - - private final VM vm; - - public WidthFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation width$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - vm.warn("A string is needed for change.case$"); - stack.push(0); - return; - } - - stack.push(BibtexWidth.width((String) o1)); - } -} diff --git a/src/main/java/org/jabref/logic/bst/BibtexCaseChanger.java b/src/main/java/org/jabref/logic/bst/util/BibtexCaseChanger.java similarity index 91% rename from src/main/java/org/jabref/logic/bst/BibtexCaseChanger.java rename to src/main/java/org/jabref/logic/bst/util/BibtexCaseChanger.java index e05ce998e80..f94b9055eea 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexCaseChanger.java +++ b/src/main/java/org/jabref/logic/bst/util/BibtexCaseChanger.java @@ -1,4 +1,4 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.Locale; import java.util.Optional; @@ -136,10 +136,7 @@ private String doChangeCase(String s, FORMAT_MODE format) { * is other stuff, too, between braces, but it doesn't try to do anything * special with |colon|s. * - * @param c * @param start the current position. It points to the opening brace - * @param format - * @return */ private int convertSpecialChar(StringBuilder sb, char[] c, int start, FORMAT_MODE format) { int i = start; @@ -174,11 +171,6 @@ private int convertSpecialChar(StringBuilder sb, char[] c, int start, FORMAT_MOD * up) and append the result to the stringBuffer, return the updated * position. * - * @param c - * @param start - * @param s - * @param sb - * @param format * @return the new position */ private int convertAccented(char[] c, int start, String s, StringBuilder sb, FORMAT_MODE format) { @@ -217,18 +209,16 @@ private int convertAccented(char[] c, int start, String s, StringBuilder sb, FOR private int convertNonControl(char[] c, int start, StringBuilder sb, FORMAT_MODE format) { int pos = start; switch (format) { - case TITLE_LOWERS: - case ALL_LOWERS: + case TITLE_LOWERS, ALL_LOWERS -> { sb.append(Character.toLowerCase(c[pos])); pos++; - break; - case ALL_UPPERS: + } + case ALL_UPPERS -> { sb.append(Character.toUpperCase(c[pos])); pos++; - break; - default: - LOGGER.info("convertNonControl - Unknown format: " + format); - break; + } + default -> + LOGGER.info("convertNonControl - Unknown format: " + format); } return pos; } @@ -236,7 +226,7 @@ private int convertNonControl(char[] c, int start, StringBuilder sb, FORMAT_MODE private int convertCharIfBraceLevelIsZero(char[] c, int start, StringBuilder sb, FORMAT_MODE format) { int i = start; switch (format) { - case TITLE_LOWERS: + case TITLE_LOWERS -> { if ((i == 0) || (prevColon && Character.isWhitespace(c[i - 1]))) { sb.append(c[i]); } else { @@ -247,16 +237,13 @@ private int convertCharIfBraceLevelIsZero(char[] c, int start, StringBuilder sb, } else if (!Character.isWhitespace(c[i])) { prevColon = false; } - break; - case ALL_LOWERS: - sb.append(Character.toLowerCase(c[i])); - break; - case ALL_UPPERS: - sb.append(Character.toUpperCase(c[i])); - break; - default: - LOGGER.info("convertCharIfBraceLevelIsZero - Unknown format: " + format); - break; + } + case ALL_LOWERS -> + sb.append(Character.toLowerCase(c[i])); + case ALL_UPPERS -> + sb.append(Character.toUpperCase(c[i])); + default -> + LOGGER.info("convertCharIfBraceLevelIsZero - Unknown format: " + format); } i++; return i; diff --git a/src/main/java/org/jabref/logic/bst/BibtexNameFormatter.java b/src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java similarity index 81% rename from src/main/java/org/jabref/logic/bst/BibtexNameFormatter.java rename to src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java index aecf571bdf4..fef87ab60e5 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexNameFormatter.java +++ b/src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java @@ -1,13 +1,17 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.Arrays; import java.util.Locale; import java.util.Optional; import java.util.stream.Collectors; +import org.jabref.logic.bst.VMException; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * From Bibtex: * @@ -25,6 +29,7 @@ * */ public class BibtexNameFormatter { + private static final Logger LOGGER = LoggerFactory.getLogger(BibtexNameFormatter.class); private BibtexNameFormatter() { } @@ -35,23 +40,18 @@ private BibtexNameFormatter() { * @param authorsNameList The string from an author field * @param whichName index of the list, starting with 1 * @param formatString TODO - * @param warn collects the warnings, may-be-null - * @return */ - public static String formatName(String authorsNameList, int whichName, String formatString, Warn warn) { + public static String formatName(String authorsNameList, int whichName, String formatString) { AuthorList al = AuthorList.parse(authorsNameList); if ((whichName < 1) && (whichName > al.getNumberOfAuthors())) { - warn.warn("AuthorList " + authorsNameList + " does not contain an author with number " + whichName); + LOGGER.warn("AuthorList {} does not contain an author with number {}", authorsNameList, whichName); return ""; } - return BibtexNameFormatter.formatName(al.getAuthor(whichName - 1), formatString, warn); + return BibtexNameFormatter.formatName(al.getAuthor(whichName - 1), formatString); } - /** - * @param warn collects the warnings, may-be-null - */ - public static String formatName(Author author, String format, Warn warn) { + public static String formatName(Author author, String format) { StringBuilder sb = new StringBuilder(); char[] c = format.toCharArray(); @@ -81,11 +81,7 @@ public static String formatName(Author author, String format, Warn warn) { } if ((braceLevel == 1) && Character.isLetter(c[i])) { if ("fvlj".indexOf(c[i]) == -1) { - if (warn != null) { - warn.warn( - "Format string in format.name$ may only contain fvlj on brace level 1 in group " - + group + ": " + format); - } + LOGGER.warn("Format string in format.name$ may only contain fvlj on brace level 1 in group {}: {}", group, format); } else { level1Chars.append(c[i]); } @@ -99,31 +95,26 @@ public static String formatName(Author author, String format, Warn warn) { continue; } - if ((control.length() > 2) && (warn != null)) { - warn.warn("Format string in format.name$ may only be one or two character long on brace level 1 in group " + group + ": " + format); + if ((control.length() > 2)) { + LOGGER.warn("Format string in format.name$ may only be one or two character long on brace level 1 in group {}: {}", group, format); } char type = control.charAt(0); - Optional tokenS; - switch (type) { - case 'f': - tokenS = author.getFirst(); - break; - case 'v': - tokenS = author.getVon(); - break; - case 'l': - tokenS = author.getLast(); - break; - case 'j': - tokenS = author.getJr(); - break; - default: - throw new VMException("Internal error"); - } - - if (!tokenS.isPresent()) { + Optional tokenS = switch (type) { + case 'f' -> + author.getFirst(); + case 'v' -> + author.getVon(); + case 'l' -> + author.getLast(); + case 'j' -> + author.getJr(); + default -> + throw new VMException("Internal error"); + }; + + if (tokenS.isEmpty()) { i++; continue; } @@ -135,9 +126,7 @@ public static String formatName(Author author, String format, Warn warn) { if (control.charAt(1) == control.charAt(0)) { abbreviateThatIsSingleLetter = false; } else { - if (warn != null) { - warn.warn("Format string in format.name$ may only contain one type of vlfj on brace level 1 in group " + group + ": " + format); - } + LOGGER.warn("Format string in format.name$ may only contain one type of vlfj on brace level 1 in group {}: {}", group, format); } } @@ -171,7 +160,7 @@ public static String formatName(Author author, String format, Warn warn) { if (abbreviateThatIsSingleLetter) { String[] dashes = token.split("-"); - token = Arrays.asList(dashes).stream().map(BibtexNameFormatter::getFirstCharOfString) + token = Arrays.stream(dashes).map(BibtexNameFormatter::getFirstCharOfString) .collect(Collectors.joining(".-")); } @@ -221,16 +210,14 @@ public static String formatName(Author author, String format, Warn warn) { } } } else if (c[i] == '}') { - if (warn != null) { - warn.warn("Unmatched brace in format string: " + format); - } + LOGGER.warn("Unmatched brace in format string: {}", format); } else { sb.append(c[i]); // verbatim } i++; } - if ((braceLevel != 0) && (warn != null)) { - warn.warn("Unbalanced brace in format string for nameFormat: " + format); + if ((braceLevel != 0)) { + LOGGER.warn("Unbalanced brace in format string for nameFormat: {}", format); } return sb.toString(); diff --git a/src/main/java/org/jabref/logic/bst/BibtexPurify.java b/src/main/java/org/jabref/logic/bst/util/BibtexPurify.java similarity index 82% rename from src/main/java/org/jabref/logic/bst/BibtexPurify.java rename to src/main/java/org/jabref/logic/bst/util/BibtexPurify.java index 08cb14db207..951c3838240 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexPurify.java +++ b/src/main/java/org/jabref/logic/bst/util/BibtexPurify.java @@ -1,4 +1,7 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @@ -11,16 +14,12 @@ * */ public class BibtexPurify { + private static final Logger LOGGER = LoggerFactory.getLogger(BibtexPurify.class); private BibtexPurify() { } - /** - * @param toPurify - * @param warn may-be-null - * @return - */ - public static String purify(String toPurify, Warn warn) { + public static String purify(String toPurify) { StringBuilder sb = new StringBuilder(); char[] cs = toPurify.toCharArray(); @@ -63,15 +62,13 @@ public static String purify(String toPurify, Warn warn) { if (braceLevel > 0) { braceLevel--; } else { - if (warn != null) { - warn.warn("Unbalanced brace in string for purify$: " + toPurify); - } + LOGGER.warn("Unbalanced brace in string for purify$: {}", toPurify); } } i++; } - if ((braceLevel != 0) && (warn != null)) { - warn.warn("Unbalanced brace in string for purify$: " + toPurify); + if ((braceLevel != 0)) { + LOGGER.warn("Unbalanced brace in string for purify$: {}", toPurify); } return sb.toString(); diff --git a/src/main/java/org/jabref/logic/bst/BibtexTextPrefix.java b/src/main/java/org/jabref/logic/bst/util/BibtexTextPrefix.java similarity index 87% rename from src/main/java/org/jabref/logic/bst/BibtexTextPrefix.java rename to src/main/java/org/jabref/logic/bst/util/BibtexTextPrefix.java index 721838e29fe..e3bc961d9ea 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexTextPrefix.java +++ b/src/main/java/org/jabref/logic/bst/util/BibtexTextPrefix.java @@ -1,4 +1,7 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The |built_in| function {\.{text.prefix\$}} pops the top two literals (the @@ -15,14 +18,12 @@ * */ public class BibtexTextPrefix { + private static final Logger LOGGER = LoggerFactory.getLogger(BibtexTextPrefix.class); private BibtexTextPrefix() { } - /** - * @param warn may-be-null - */ - public static String textPrefix(int inNumOfChars, String toPrefix, Warn warn) { + public static String textPrefix(int inNumOfChars, String toPrefix) { int numOfChars = inNumOfChars; StringBuilder sb = new StringBuilder(); @@ -53,15 +54,13 @@ public static String textPrefix(int inNumOfChars, String toPrefix, Warn warn) { if (braceLevel > 0) { braceLevel--; } else { - if (warn != null) { - warn.warn("Unbalanced brace in string for purify$: " + toPrefix); - } + LOGGER.warn("Unbalanced brace in string for purify$: {}", toPrefix); } } else { numOfChars--; } } - sb.append(toPrefix.substring(0, i)); + sb.append(toPrefix, 0, i); while (braceLevel > 0) { sb.append('}'); braceLevel--; diff --git a/src/main/java/org/jabref/logic/bst/BibtexWidth.java b/src/main/java/org/jabref/logic/bst/util/BibtexWidth.java similarity index 99% rename from src/main/java/org/jabref/logic/bst/BibtexWidth.java rename to src/main/java/org/jabref/logic/bst/util/BibtexWidth.java index 61e0b099f20..b4c8f77b349 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexWidth.java +++ b/src/main/java/org/jabref/logic/bst/util/BibtexWidth.java @@ -1,4 +1,4 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/jabref/logic/layout/format/NameFormatter.java b/src/main/java/org/jabref/logic/layout/format/NameFormatter.java index 919ff4df2d5..18b27d35ebe 100644 --- a/src/main/java/org/jabref/logic/layout/format/NameFormatter.java +++ b/src/main/java/org/jabref/logic/layout/format/NameFormatter.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Objects; -import org.jabref.logic.bst.BibtexNameFormatter; +import org.jabref.logic.bst.util.BibtexNameFormatter; import org.jabref.logic.layout.LayoutFormatter; import org.jabref.model.entry.AuthorList; @@ -86,7 +86,7 @@ private static String format(String toFormat, AuthorList al, String[] formats) { for (int i = 1; i <= al.getNumberOfAuthors(); i++) { for (int j = 1; j < formats.length; j += 2) { if ("*".equals(formats[j])) { - sb.append(BibtexNameFormatter.formatName(toFormat, i, formats[j + 1], null)); + sb.append(BibtexNameFormatter.formatName(toFormat, i, formats[j + 1])); break; } else { String[] range = formats[j].split("\\.\\."); @@ -112,7 +112,7 @@ private static String format(String toFormat, AuthorList al, String[] formats) { } if ((s <= i) && (i <= e)) { - sb.append(BibtexNameFormatter.formatName(toFormat, i, formats[j + 1], null)); + sb.append(BibtexNameFormatter.formatName(toFormat, i, formats[j + 1])); break; } } diff --git a/src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java b/src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java index 5349be3ef3f..8deac500a21 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java +++ b/src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java @@ -2,7 +2,8 @@ import java.util.stream.Stream; -import org.jabref.logic.bst.BibtexCaseChanger.FORMAT_MODE; +import org.jabref.logic.bst.util.BibtexCaseChanger; +import org.jabref.logic.bst.util.BibtexCaseChanger.FORMAT_MODE; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java b/src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java index a8000752edb..78eb6c27604 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java +++ b/src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java @@ -1,8 +1,8 @@ package org.jabref.logic.bst; +import org.jabref.logic.bst.util.BibtexNameFormatter; import org.jabref.model.entry.AuthorList; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -11,38 +11,33 @@ public class BibtexNameFormatterTest { @Test public void testUmlautsFullNames() { - AuthorList al = AuthorList - .parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); + AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("de~laVall{\\'e}e~PoussinCharles Louis Xavier~Joseph", BibtexNameFormatter.formatName(al - .getAuthor(0), "{vv}{ll}{jj}{ff}", Assertions::fail)); + assertEquals("de~laVall{\\'e}e~PoussinCharles Louis Xavier~Joseph", + BibtexNameFormatter.formatName(list.getAuthor(0), "{vv}{ll}{jj}{ff}")); } @Test public void testUmlautsAbbreviations() { - AuthorList al = AuthorList - .parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); + AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J.", BibtexNameFormatter.formatName(al - .getAuthor(0), "{vv~}{ll}{, jj}{, f.}", Assertions::fail)); + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J.", + BibtexNameFormatter.formatName(list.getAuthor(0), "{vv~}{ll}{, jj}{, f.}")); } @Test public void testUmlautsAbbreviationsWithQuestionMark() { - AuthorList al = AuthorList - .parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); + AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", BibtexNameFormatter.formatName(al - .getAuthor(0), "{vv~}{ll}{, jj}{, f}?", Assertions::fail)); + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", + BibtexNameFormatter.formatName(list.getAuthor(0), "{vv~}{ll}{, jj}{, f}?")); } @Test public void testFormatName() { - AuthorList al = AuthorList - .parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); + AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("dlVP", BibtexNameFormatter.formatName(al.getAuthor(0), "{v{}}{l{}}", - Assertions::fail)); + assertEquals("dlVP", BibtexNameFormatter.formatName(list.getAuthor(0), "{v{}}{l{}}")); assertNameFormatA("Meyer, J?", "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); assertNameFormatB("J.~Meyer", "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); @@ -60,8 +55,7 @@ public void testFormatName() { } private void assertNameFormat(String string, String string2, int which, String format) { - assertEquals(string, BibtexNameFormatter.formatName(string2, which, format, - Assertions::fail)); + assertEquals(string, BibtexNameFormatter.formatName(string2, which, format)); } private void assertNameFormatC(String string, String string2) { @@ -79,24 +73,21 @@ private void assertNameFormatA(String string, String string2) { @Test public void matchingBraceConsumedForCompleteWords() { StringBuilder sb = new StringBuilder(); - assertEquals(6, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HELLO} {WORLD}" - .toCharArray(), 0)); + assertEquals(6, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HELLO} {WORLD}".toCharArray(), 0)); assertEquals("{HELLO}", sb.toString()); } @Test public void matchingBraceConsumedForBracesInWords() { StringBuilder sb = new StringBuilder(); - assertEquals(18, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}" - .toCharArray(), 12)); + assertEquals(18, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}".toCharArray(), 12)); assertEquals("{WORLD}", sb.toString()); } @Test public void testConsumeToMatchingBrace() { StringBuilder sb = new StringBuilder(); - assertEquals(10, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}" - .toCharArray(), 0)); + assertEquals(10, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}".toCharArray(), 0)); assertEquals("{HE{L{}L}O}", sb.toString()); } diff --git a/src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java b/src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java index 684e4366225..4018ef78f17 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java +++ b/src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java @@ -2,19 +2,20 @@ import java.util.stream.Stream; +import org.jabref.logic.bst.util.BibtexPurify; + import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; public class BibtexPurifyTest { @ParameterizedTest @MethodSource("provideTestStrings") public void testPurify(String expected, String toBePurified) { - assertEquals(expected, BibtexPurify.purify(toBePurified, s -> fail("Should not Warn (" + s + ")! purify should be " + expected + " for " + toBePurified))); + assertEquals(expected, BibtexPurify.purify(toBePurified)); } private static Stream provideTestStrings() { diff --git a/src/test/java/org/jabref/logic/bst/BibtexWidthTest.java b/src/test/java/org/jabref/logic/bst/BibtexWidthTest.java index 79cb00b3eb8..1d5ee597782 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexWidthTest.java +++ b/src/test/java/org/jabref/logic/bst/BibtexWidthTest.java @@ -2,6 +2,8 @@ import java.util.stream.Stream; +import org.jabref.logic.bst.util.BibtexWidth; + import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/org/jabref/logic/bst/TestVM.java b/src/test/java/org/jabref/logic/bst/TestVM.java index 97dc0c9bb25..7a662ed4abc 100644 --- a/src/test/java/org/jabref/logic/bst/TestVM.java +++ b/src/test/java/org/jabref/logic/bst/TestVM.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Optional; -import org.jabref.logic.bst.VM.BstEntry; import org.jabref.logic.bst.VM.StackFunction; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParserResult; diff --git a/src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java b/src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java index d91eadd8920..0ec30dd64c8 100644 --- a/src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java +++ b/src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java @@ -1,9 +1,10 @@ package org.jabref.logic.bst; +import org.jabref.logic.bst.util.BibtexTextPrefix; + import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; public class TextPrefixFunctionTest { @@ -21,6 +22,6 @@ public void testPrefix() { } private static void assertPrefix(final String string, final String string2) { - assertEquals(string, BibtexTextPrefix.textPrefix(5, string2, s -> fail("Should not Warn! text.prefix$ should be " + string + " for (5) " + string2))); + assertEquals(string, BibtexTextPrefix.textPrefix(5, string2)); } } From 73270617835a069547d52902bd3129ae1d722f92 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Thu, 26 May 2022 23:02:59 +0200 Subject: [PATCH 04/39] Introduce BstVM, wip --- src/main/antlr4/org/jabref/bst/Bst.g4 | 13 +- .../org/jabref/logic/bst/BstFunctions.java | 884 ++++++++++++++++++ src/main/java/org/jabref/logic/bst/BstVM.java | 130 +++ src/main/java/org/jabref/logic/bst/VM.java | 54 +- 4 files changed, 1048 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/jabref/logic/bst/BstFunctions.java create mode 100644 src/main/java/org/jabref/logic/bst/BstVM.java diff --git a/src/main/antlr4/org/jabref/bst/Bst.g4 b/src/main/antlr4/org/jabref/bst/Bst.g4 index 683c60a1e94..a3ecd697a30 100644 --- a/src/main/antlr4/org/jabref/bst/Bst.g4 +++ b/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -41,14 +41,15 @@ start ; commands - : list=(STRINGS|INTEGERS) id=idListObl #variablesCommand + : STRINGS ids=idListObl #stringsCommand + | INTEGERS ids=idListObl #integersCommand | FUNCTION LBRACE id=identifier RBRACE exp=stack #functionCommand - | list=MACRO LBRACE id=identifier RBRACE LBRACE exp=STRING RBRACE #variablesCommand + | MACRO LBRACE id=identifier RBRACE LBRACE repl=STRING RBRACE #macroCommand | READ #readCommand - | EXECUTE LBRACE id=function RBRACE #executeCommand - | ITERATE LBRACE id=function RBRACE #iterateCommand - | REVERSE LBRACE id=function RBRACE #reverseCommand - | ENTRY first=idListOpt second=idListOpt third=idListOpt #entryCommand + | EXECUTE LBRACE exp=function RBRACE #executeCommand + | ITERATE LBRACE exp=function RBRACE #iterateCommand + | REVERSE LBRACE exp=function RBRACE #reverseCommand + | ENTRY idListOpt idListOpt idListOpt #entryCommand | SORT #sortCommand ; diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java new file mode 100644 index 00000000000..e691946f2b2 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -0,0 +1,884 @@ +package org.jabref.logic.bst; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jabref.logic.bst.util.BibtexCaseChanger; +import org.jabref.logic.bst.util.BibtexNameFormatter; +import org.jabref.logic.bst.util.BibtexPurify; +import org.jabref.logic.bst.util.BibtexTextPrefix; +import org.jabref.logic.bst.util.BibtexWidth; +import org.jabref.model.entry.Author; +import org.jabref.model.entry.AuthorList; + +import org.antlr.v4.runtime.tree.Tree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BstFunctions { + private static final Logger LOGGER = LoggerFactory.getLogger(BstFunctions.class); + private static final Pattern ADD_PERIOD_PATTERN = Pattern.compile("([^\\.\\?\\!\\}\\s])(\\}|\\s)*$"); + + private final Map strings; + private final Map integers; + private final Map functions; + private final Map buildInFunctions = new HashMap<>(37); + + private final Stack stack; + + private final StringBuilder bbl; + private int bstWarning = 0; + + private String preamble = ""; + + @FunctionalInterface + public interface BstFunction { + void execute(BstEntry context); + } + + public BstFunctions(Map strings, + Map integers, + Map functions, + Stack stack, + StringBuilder bbl) { + this.strings = strings; + this.integers = integers; + this.functions = functions; + this.stack = stack; + this.bbl = bbl; + + initBuildInFunctions(); + } + + private void initBuildInFunctions() { + buildInFunctions.put(">", this::bstIsGreaterThan); + buildInFunctions.put("<", this::bstIsLowerThan); + buildInFunctions.put("=", this::bstEquals); + buildInFunctions.put("+", this::bstAdd); + buildInFunctions.put("-", this::bstSubtract); + buildInFunctions.put("*", this::bstConcat); + buildInFunctions.put(":=", this::bstAssign); + buildInFunctions.put("add.period$", this::bstAddPeriod); + buildInFunctions.put("call.type$", this::bstCallType); + buildInFunctions.put("change.case$", this::bstChangeCase); + buildInFunctions.put("chr.to.int$", this::bstChrToInt); + buildInFunctions.put("cite$", this::bstCite); + buildInFunctions.put("duplicate$", this::bstDuplicate); + buildInFunctions.put("empty$", this::bstEmpty); + buildInFunctions.put("format.name$", this::bstFormatName); + buildInFunctions.put("if$", this::bstIf); + buildInFunctions.put("int.to.chr$", this::bstIntToChr); + buildInFunctions.put("int.to.str$", this::bstIntToStr); + buildInFunctions.put("missing$", this::bstMissing); + buildInFunctions.put("newline$", this::bstNewLine); + buildInFunctions.put("num.names$", this::bstNumNames); + buildInFunctions.put("pop$", this::bstPop); + buildInFunctions.put("preamble$", this::bstPreamble); + buildInFunctions.put("purify$", this::bstPurify); + buildInFunctions.put("quote$", this::bstQuote); + buildInFunctions.put("skip$", this::bstSkip); + buildInFunctions.put("stack$", this::bstStack); + buildInFunctions.put("substring$", this::bstSubstring); + buildInFunctions.put("swap$", this::bstSwap); + buildInFunctions.put("text.length$", this::bstTextLength); + buildInFunctions.put("text.prefix$", this::bstTextPrefix); + buildInFunctions.put("top$", this::bstTop); + buildInFunctions.put("type$", this::bstType); + buildInFunctions.put("warning$", this::bstWarning); + buildInFunctions.put("while$", this::bstWhile); + buildInFunctions.put("width$", this::bstWidth); + buildInFunctions.put("write$", this::bstWrite); + } + + /** + * Pops the top two (integer) literals, compares them, and pushes + * the integer 1 if the second is greater than the first, 0 + * otherwise. + */ + private void bstIsGreaterThan(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(((Integer) o1).compareTo((Integer) o2) > 0 ? BstVM.TRUE : BstVM.FALSE); + } + + /** + * Pops the top two (integer) literals, compares them, and pushes + * the integer 1 if the second is lower than the first, 0 + * otherwise. + */ + private void bstIsLowerThan(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(((Integer) o1).compareTo((Integer) o2) < 0 ? BstVM.TRUE : BstVM.FALSE); + } + + /** + * Pops the top two (both integer or both string) literals, compares + * them, and pushes the integer 1 if they're equal, 0 otherwise. + */ + private void bstEquals(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 == null) && (o2 == null)) { + stack.push(VM.TRUE); + return; + } + + stack.push(o1.equals(o2) ? BstVM.TRUE : BstVM.FALSE); + } + + /** + * Pops the top two (integer) literals and pushes their sum. + */ + private void bstAdd(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((Integer) o1 + (Integer) o2); + } + + /** + * Pops the top two (integer) literals and pushes their difference + * (the first subtracted from the second). + */ + private void bstSubtract(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((Integer) o1 - (Integer) o2); + } + + /** + * Pops the top two (string) literals, concatenates them (in reverse + * order, that is, the order in which pushed), and pushes the + * resulting string. + */ + private void bstConcat(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 == null) { + o1 = ""; + } + if (o2 == null) { + o2 = ""; + } + + if (!((o1 instanceof String) && (o2 instanceof String))) { + LOGGER.error("o1: {} ({})", o1, o1.getClass()); + LOGGER.error("o2: {} ({})", o2, o2.getClass()); + throw new VMException("Can only concatenate two String with *"); + } + + stack.push(o1.toString() + o2); + } + + /** + * Pops the top two literals and assigns to the first (which must be + * a global or entry variable) the value of the second. + */ + private void bstAssign(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Invalid call to operation :="); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + doBstAssign(context, o1, o2); + } + + private boolean doBstAssign(BstEntry context, Object o1, Object o2) { + if (!(o1 instanceof BstVM.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { + throw new VMException("Invalid parameters"); + } + + String name = ((VM.Identifier) o1).getName(); + + if (o2 instanceof String) { + if ((context != null) && context.localStrings.containsKey(name)) { + context.localStrings.put(name, (String) o2); + return true; + } + + if (strings.containsKey(name)) { + strings.put(name, (String) o2); + return true; + } + return false; + } + + if ((context != null) && context.localIntegers.containsKey(name)) { + context.localIntegers.put(name, (Integer) o2); + return true; + } + + if (integers.containsKey(name)) { + integers.put(name, (Integer) o2); + return true; + } + return false; + } + + /** + * Pops the top (string) literal, adds a `.' to it if the last non + * '}' character isn't a `.', `?', or `!', and pushes this resulting + * string. + */ + private void bstAddPeriod(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation add.period$"); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String s)) { + throw new VMException("Can only add a period to a string for add.period$"); + } + + Matcher m = ADD_PERIOD_PATTERN.matcher(s); + + if (m.find()) { + StringBuilder sb = new StringBuilder(); + 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); + } + } + + /** + * 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. + */ + private void bstCallType(BstEntry context) { + if (context == null) { + throw new VMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); + } + // BstVM.this.execute(context.entry.getType().getName(), context); // FIXME + } + + /** + * Pops the top two (string) literals; it changes the case of the second + * according to the specifications of the first, as follows. (Note: The word + * `letters' in the next sentence refers only to those at brace-level 0, the + * top-most brace level; no other characters are changed, except perhaps for + * \special characters", described in Section 4.) If the first literal is the + * string `t', it converts to lower case all letters except the very first + * character in the string, which it leaves alone, and except the first + * character following any colon and then nonnull white space, which it also + * leaves alone; if it's the string `l', it converts all letters to lower case; + * and if it's the string `u', it converts all letters to upper case. It then + * pushes this resulting string. If either type is incorrect, it complains and + * pushes the null string; however, if both types are correct but the + * specification string (i.e., the first string) isn't one of the legal ones, it + * merely pushes the second back onto the stack, after complaining. (Another + * note: It ignores case differences in the specification string; for example, + * the strings t and T are equivalent for the purposes of this built-in + * function.) + */ + private void bstChangeCase(BstEntry value) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation change.case$"); + } + + Object o1 = stack.pop(); + if (!((o1 instanceof String) && (((String) o1).length() == 1))) { + throw new VMException("A format string of length 1 is needed for change.case$"); + } + + Object o2 = stack.pop(); + if (!(o2 instanceof String)) { + throw new VMException("A string is needed as second parameter for change.case$"); + } + + char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); + String s = (String) o2; + + stack.push(BibtexCaseChanger.changeCase(s, BibtexCaseChanger.FORMAT_MODE.getFormatModeForBSTFormat(format))); + } + + /** + * Pops the top (string) literal, makes sure it's a single + * character, converts it to the corresponding ASCII integer, and + * pushes this integer. + */ + private void bstChrToInt(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation chr.to.int$"); + } + Object o1 = stack.pop(); + + if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { + throw new VMException("Can only perform chr.to.int$ on string with length 1"); + } + + stack.push((int) s.charAt(0)); + } + + /** + * Pushes the string that was the \cite-command argument for this + * entry. + */ + private void bstCite(BstEntry context) { + if (context == null) { + throw new VMException("Must have an entry to cite$"); + } + stack.push(context.entry.getCitationKey().orElse(null)); + } + + /** + * Pops the top literal from the stack and pushes two copies of it. + */ + private void bstDuplicate(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation duplicate$"); + } + Object o1 = stack.pop(); + + stack.push(o1); + stack.push(o1); + } + + /** + * 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. + */ + private void bstEmpty(BstEntry context) { + if (stack.isEmpty()) { + 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 s)) { + throw new VMException("Operand does not match function empty$"); + } + + stack.push("".equals(s.trim()) ? BstVM.TRUE : BstVM.FALSE); + } + + /** + * The |built_in| function {\.{format.name\$}} pops the + * top three literals (they are a string, an integer, and a string + * literal, in that order). The last string literal represents a + * name list (each name corresponding to a person), the integer + * literal specifies which name to pick from this list, and the + * first string literal specifies how to format this name, as + * described in the \BibTeX\ documentation. Finally, this function + * pushes the formatted name. If any of the types is incorrect, it + * complains and pushes the null string. + */ + private void bstFormatName(BstEntry context) { + if (stack.size() < 3) { + throw new VMException("Not enough operands on stack for operation format.name$"); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + Object o3 = stack.pop(); + + if (!(o1 instanceof String) && !(o2 instanceof Integer) && !(o3 instanceof String)) { + // warning("A string is needed for change.case$"); + stack.push(""); + return; + } + + String format = (String) o1; + Integer name = (Integer) o2; + String names = (String) o3; + + if (names == null) { + stack.push(""); + } else { + AuthorList a = AuthorList.parse(names); + if (name > a.getNumberOfAuthors()) { + throw new VMException("Author Out of Bounds. Number " + name + " invalid for " + names); + } + Author author = a.getAuthor(name - 1); + + stack.push(BibtexNameFormatter.formatName(author, format)); + } + } + + /** + * 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. + */ + private void bstIf(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 BstVM.Identifier) || (f1 instanceof Tree)) + && ((f2 instanceof BstVM.Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { + throw new VMException("Expecting two functions and an integer for if$."); + } + + if ((Integer) i > 0) { + // BstVM.this.executeInContext(f2, context); // FIXME + } else { + // BstVM.this.executeInContext(f1, context); // FIXME + } + } + + /** + * 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. + */ + private void bstIntToChr(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation int.to.chr$"); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof Integer i)) { + throw new VMException("Can only perform operation int.to.chr$ on an Integer"); + } + + stack.push(String.valueOf((char) i.intValue())); + } + + /** + * Pops the top (integer) literal, converts it to its (unique) + * string equivalent, and pushes this string. + */ + private void bstIntToStr(BstEntry context) { + if (stack.isEmpty()) { + 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(o1.toString()); + } + + /** + * Pops the top literal and pushes the integer 1 if it's a missing + * field, 0 otherwise. + */ + private void bstMissing(BstEntry context) { + if (stack.isEmpty()) { + 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)) { + LOGGER.warn("Not a string or missing field in operation missing$"); + stack.push(VM.TRUE); + return; + } + + stack.push(VM.FALSE); + } + + /** + * Writes onto the bbl file what is 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. + */ + private void bstNewLine(BstEntry context) { + this.bbl.append('\n'); + } + + /** + * 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. + */ + private void bstNumNames(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation num.names$"); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String s)) { + throw new VMException("Need a string at the top of the stack for num.names$"); + } + + stack.push(AuthorList.parse(s).getNumberOfAuthors()); + } + + /** + * Pops the top of the stack but doesn't print it; this gets rid of + * an unwanted stack literal. + */ + private void bstPop(BstEntry context) { + stack.pop(); + } + + /** + * 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 were none) + * '@PREAMBLE' strings are read from the database files. + */ + private void bstPreamble(BstEntry context) { + stack.push(preamble); + } + + /** + * 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. + */ + private void bstPurify(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation purify$"); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String)) { + LOGGER.warn("A string is needed for purify$"); + stack.push(""); + return; + } + + stack.push(BibtexPurify.purify((String) o1)); + } + + /** + * Pushes the string consisting of the double-quote character. + */ + private void bstQuote(BstEntry context) { + stack.push("\""); + } + + /** + * Does nothing. + */ + private void bstSkip(BstEntry context) { + // no-op + } + + /** + * Pops and prints the whole stack; it's meant to be used for style + * designers while debugging. + */ + private void bstStack(BstEntry context) { + while (!stack.empty()) { + LOGGER.debug("Stack entry {}", stack.pop()); + } + } + + /** + * 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). + */ + private void bstSubstring(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 len) && (o2 instanceof Integer start) && (o3 instanceof String s))) { + throw new VMException("Expecting two integers and a string for substring$"); + } + + int lenI = len; + int startI = start; + + 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; + } + + 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()))); + } + + /** + * Swaps the top two literals on the stack. 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. + */ + private void bstSwap(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); + } + + /** + * 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. + */ + private void bstTextLength(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation text.length$"); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String s)) { + throw new VMException("Can only perform operation on a string text.length$"); + } + + char[] c = s.toCharArray(); + int result = 0; + int i = 0; + int n = s.length(); + int braceLevel = 0; + + while (i < n) { + i++; + if (c[i - 1] == '{') { + braceLevel++; + if ((braceLevel == 1) && (i < n)) { + if (c[i] == '\\') { + i++; // skip over backslash + while ((i < n) && (braceLevel > 0)) { + if (c[i] == '}') { + braceLevel--; + } else if (c[i] == '{') { + braceLevel++; + } + i++; + } + result++; + } + } + } else if (c[i - 1] == '}') { + if (braceLevel > 0) { + braceLevel--; + } + } else { + result++; + } + } + stack.push(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. + */ + private void bstTextPrefix(BstEntry context) { + if (stack.size() < 2) { + throw new VMException("Not enough operands on stack for operation text.prefix$"); + } + + Object o1 = stack.pop(); + if (!(o1 instanceof Integer)) { + LOGGER.warn("An integer is needed as first parameter to text.prefix$"); + stack.push(""); + return; + } + + Object o2 = stack.pop(); + if (!(o2 instanceof String)) { + LOGGER.warn("A string is needed as second parameter to text.prefix$"); + stack.push(""); + return; + } + + stack.push(BibtexTextPrefix.textPrefix((Integer) o1, (String) o2)); + } + + /** + * Pops and prints the top of the stack to the log file. It's useful for debugging. + */ + private void bstTop(BstEntry context) { + LOGGER.debug("Stack entry {}", stack.pop()); + } + + /** + * Pushes the current entry's type (book, article, etc.), but pushes + * the null string if the type is either unknown or undefined. + */ + private void bstType(BstEntry context) { + if (context == null) { + throw new VMException("type$ need a context."); + } + + stack.push(context.entry.getType().getName()); + } + + /** + * Pops the top (string) literal and prints it following a warning + * message. This also increments a count of the number of warning + * messages issued. + */ + private void bstWarning(BstEntry context) { + LOGGER.warn("Warning (#{}): {}", bstWarning++, stack.pop()); + } + + /** + * 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. + */ + private void bstWhile(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 BstVM.Identifier) || (f1 instanceof Tree)) + && ((f2 instanceof BstVM.Identifier) || (f2 instanceof Tree))) { + throw new VMException("Expecting two functions for while$."); + } + + do { + // executeInContext(f1, context); // FIXME + + 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 <= 0) { + break; + } + // executeInContext(f2, context); // FIXME + } while (true); + } + + /** + * The |built_in| function {\.{width\$}} pops the top (string) literal and + * pushes the integer that represents its width in units specified by the + * |char_width| array. This function takes the literal literally; that is, it + * assumes each character in the string is to be printed as is, regardless of + * whether the character has a special meaning to \TeX, except that special + * characters (even without their |right_brace|s) are handled specially. If the + * literal isn't a string, it complains and pushes~0. + */ + private void bstWidth(BstEntry context) { + if (stack.isEmpty()) { + throw new VMException("Not enough operands on stack for operation width$"); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String)) { + LOGGER.warn("A string is needed for width$"); + stack.push(0); + return; + } + + stack.push(BibtexWidth.width((String) o1)); + } + + /** + * 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). + */ + private void bstWrite(BstEntry context) { + String s = (String) stack.pop(); + bbl.append(s); + } +} diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java new file mode 100644 index 00000000000..6245a1ff3ee --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -0,0 +1,130 @@ +package org.jabref.logic.bst; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +import org.antlr.v4.runtime.BailErrorStrategy; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.runtime.tree.ParseTree; + +public class BstVM { + + protected static final int FALSE = 0; + protected static final int TRUE = 1; + + private final ParseTree tree; + + private final Map strings = new HashMap<>(); + private final Map integers = new HashMap<>(); + private final Map functions = new HashMap<>(); + private final Stack stack = new Stack<>(); + + private final BstFunctions bstFunctions; + private final StringBuilder bbl; + + private Path path; + + public BstVM(Path path) throws RecognitionException, IOException { + this(CharStreams.fromPath(path)); + this.path = path; + } + + public BstVM(String s) throws RecognitionException { + this(CharStreams.fromString(s)); + } + + private BstVM(CharStream bst) throws RecognitionException { + this(charStream2CommonTree(bst)); + } + + private BstVM(ParseTree tree) { + this.tree = tree; + bbl = new StringBuilder(); + bstFunctions = new BstFunctions(strings, integers, functions, stack, bbl); + } + + private static ParseTree charStream2CommonTree(CharStream query) { + BstLexer lexer = new BstLexer(query); + lexer.removeErrorListeners(); + lexer.addErrorListener(VM.ThrowingErrorListener.INSTANCE); + BstParser parser = new BstParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(ThrowingErrorListener.INSTANCE); + parser.setErrorHandler(new BailErrorStrategy()); + return parser.start(); + } + + private static class ThrowingErrorListener extends BaseErrorListener { + public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, + int line, int charPositionInLine, String msg, RecognitionException e) + throws ParseCancellationException { + throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg); + } + } + + protected static class Identifier { + + public final String name; + + public Identifier(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + private class BstVMVisitor extends BstBaseVisitor { + @Override + public Integer visitStringsCommand(BstParser.StringsCommandContext ctx) { + if (ctx.ids.getChildCount() > 20) { + throw new ParseCancellationException("Strings limit reached"); + } + + for (int i = 0; i < ctx.ids.getChildCount(); i++) { + strings.put(ctx.ids.getChild(i).getText(), null); + } + return BstVM.TRUE; + } + + @Override + public Integer visitIntegersCommand(BstParser.IntegersCommandContext ctx) { + for (int i = 0; i < ctx.ids.getChildCount(); i++) { + integers.put(ctx.ids.getChild(i).getText(), 0); + } + return BstVM.TRUE; + } + + @Override + public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { + functions.put(ctx.id.getText(), new MacroFunction(ctx.repl.getText())); + return BstVM.TRUE; + } + + private class MacroFunction implements BstFunctions.BstFunction { + private final String replacement; + + MacroFunction(String replacement) { + this.replacement = replacement; + } + + @Override + public void execute(BstEntry context) { + stack.push(replacement); + } + } + } +} diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java index 3c9fa17d623..6522bb441dc 100644 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ b/src/main/java/org/jabref/logic/bst/VM.java @@ -143,7 +143,7 @@ private VM(CharStream bst) throws RecognitionException { private VM(ParseTree tree) { this.tree = tree; - init(); + initBuildInFunctions(); } private static ParseTree charStream2CommonTree(CharStream query) throws RecognitionException { @@ -157,7 +157,7 @@ private static ParseTree charStream2CommonTree(CharStream query) throws Recognit return parser.start(); } - public void init() { + private void initBuildInFunctions() { buildInFunctions.put(">", this::bstIsGreaterThan); buildInFunctions.put("<", this::bstIsLowerThan); buildInFunctions.put("=", this::bstEquals); @@ -202,7 +202,7 @@ public void init() { * the integer 1 if the second is greater than the first, 0 * otherwise. */ - private void bstIsGreaterThan(BstEntry context) { + private void bstIsGreaterThan(Object context) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation >"); } @@ -1026,29 +1026,29 @@ public String run(Collection bibEntries, BibDatabase bibDatabase) { // Go for (int i = 0; i < tree.getChildCount(); i++) { Tree child = tree.getChild(i); - switch (child.getType()) { - case BstParser.STRINGS -> - strings(child); - case BstParser.INTEGERS -> - integers(child); - case BstParser.FUNCTION -> - function(child); - case BstParser.EXECUTE -> - execute(child); - case BstParser.SORT -> - sort(); - case BstParser.ITERATE -> - iterate(child); - case BstParser.REVERSE -> - reverse(child); - case BstParser.ENTRY -> - entry(child); - case BstParser.READ -> - read(bibDatabase); - case BstParser.MACRO -> - macro(child); + switch (child) { + case BstParser.StringsCommandContext ctx -> + strings(ctx); + case BstParser.IntegersCommandContext ctx -> + integers(ctx); + case BstParser.FunctionCommandContext ctx -> + function(ctx); + case BstParser.ExecuteCommandContext ctx -> + execute(ctx); + case BstParser.SortCommandContext ctx -> + sort(ctx); + case BstParser.IterateCommandContext ctx -> + iterate(ctx); + case BstParser.ReverseCommandContext ctx -> + reverse(ctx); + case BstParser.EntryCommandContext ctx -> + entry(ctx); + case BstParser.ReadCommandContext ctx -> + read(bibDatabase, ctx); + case BstParser.MacroCommandContext ctx -> + macro(ctx); default -> - LOGGER.info("Unknown type: {}", child.getType()); + LOGGER.info("Unknown type: {}", ((BstParser.CommandsContext) child).getText()); } } @@ -1062,7 +1062,7 @@ public String run(Collection bibEntries, BibDatabase bibDatabase) { *

* We use null for the missing entry designator. */ - private void read(BibDatabase bibDatabase) { + private void read(BibDatabase bibDatabase, Tree child) { FieldWriter fieldWriter = new FieldWriter(new FieldWriterPreferences(true, List.of(StandardField.MONTH), new FieldContentFormatterPreferences())); for (BstEntry e : entries) { for (Map.Entry mEntry : e.fields.entrySet()) { @@ -1191,7 +1191,7 @@ private void iterate(Tree child) { /** * Sorts the entry list using the values of the string entry variable sort.key$. It has no arguments. */ - private void sort() { + private void sort(Tree child) { entries.sort(Comparator.comparing(o -> (o.localStrings.get("sort.key$")))); } From bb9be35a4551426de62de0aba2f1b7077f985fbf Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Fri, 27 May 2022 13:53:10 +0200 Subject: [PATCH 05/39] read, entry, sort wip --- .../org/jabref/logic/bst/BstFunctions.java | 88 +++++------ src/main/java/org/jabref/logic/bst/BstVM.java | 140 +++++++++++++++++- 2 files changed, 182 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index e691946f2b2..179ed2f7c50 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -26,7 +26,7 @@ public class BstFunctions { private final Map strings; private final Map integers; private final Map functions; - private final Map buildInFunctions = new HashMap<>(37); + private final Map builtInFunctions = new HashMap<>(37); private final Stack stack; @@ -51,47 +51,51 @@ public BstFunctions(Map strings, this.stack = stack; this.bbl = bbl; - initBuildInFunctions(); - } - - private void initBuildInFunctions() { - buildInFunctions.put(">", this::bstIsGreaterThan); - buildInFunctions.put("<", this::bstIsLowerThan); - buildInFunctions.put("=", this::bstEquals); - buildInFunctions.put("+", this::bstAdd); - buildInFunctions.put("-", this::bstSubtract); - buildInFunctions.put("*", this::bstConcat); - buildInFunctions.put(":=", this::bstAssign); - buildInFunctions.put("add.period$", this::bstAddPeriod); - buildInFunctions.put("call.type$", this::bstCallType); - buildInFunctions.put("change.case$", this::bstChangeCase); - buildInFunctions.put("chr.to.int$", this::bstChrToInt); - buildInFunctions.put("cite$", this::bstCite); - buildInFunctions.put("duplicate$", this::bstDuplicate); - buildInFunctions.put("empty$", this::bstEmpty); - buildInFunctions.put("format.name$", this::bstFormatName); - buildInFunctions.put("if$", this::bstIf); - buildInFunctions.put("int.to.chr$", this::bstIntToChr); - buildInFunctions.put("int.to.str$", this::bstIntToStr); - buildInFunctions.put("missing$", this::bstMissing); - buildInFunctions.put("newline$", this::bstNewLine); - buildInFunctions.put("num.names$", this::bstNumNames); - buildInFunctions.put("pop$", this::bstPop); - buildInFunctions.put("preamble$", this::bstPreamble); - buildInFunctions.put("purify$", this::bstPurify); - buildInFunctions.put("quote$", this::bstQuote); - buildInFunctions.put("skip$", this::bstSkip); - buildInFunctions.put("stack$", this::bstStack); - buildInFunctions.put("substring$", this::bstSubstring); - buildInFunctions.put("swap$", this::bstSwap); - buildInFunctions.put("text.length$", this::bstTextLength); - buildInFunctions.put("text.prefix$", this::bstTextPrefix); - buildInFunctions.put("top$", this::bstTop); - buildInFunctions.put("type$", this::bstType); - buildInFunctions.put("warning$", this::bstWarning); - buildInFunctions.put("while$", this::bstWhile); - buildInFunctions.put("width$", this::bstWidth); - buildInFunctions.put("write$", this::bstWrite); + inititalize(); + } + + private void inititalize() { + builtInFunctions.put(">", this::bstIsGreaterThan); + builtInFunctions.put("<", this::bstIsLowerThan); + builtInFunctions.put("=", this::bstEquals); + builtInFunctions.put("+", this::bstAdd); + builtInFunctions.put("-", this::bstSubtract); + builtInFunctions.put("*", this::bstConcat); + builtInFunctions.put(":=", this::bstAssign); + builtInFunctions.put("add.period$", this::bstAddPeriod); + builtInFunctions.put("call.type$", this::bstCallType); + builtInFunctions.put("change.case$", this::bstChangeCase); + builtInFunctions.put("chr.to.int$", this::bstChrToInt); + builtInFunctions.put("cite$", this::bstCite); + builtInFunctions.put("duplicate$", this::bstDuplicate); + builtInFunctions.put("empty$", this::bstEmpty); + builtInFunctions.put("format.name$", this::bstFormatName); + builtInFunctions.put("if$", this::bstIf); + builtInFunctions.put("int.to.chr$", this::bstIntToChr); + builtInFunctions.put("int.to.str$", this::bstIntToStr); + builtInFunctions.put("missing$", this::bstMissing); + builtInFunctions.put("newline$", this::bstNewLine); + builtInFunctions.put("num.names$", this::bstNumNames); + builtInFunctions.put("pop$", this::bstPop); + builtInFunctions.put("preamble$", this::bstPreamble); + builtInFunctions.put("purify$", this::bstPurify); + builtInFunctions.put("quote$", this::bstQuote); + builtInFunctions.put("skip$", this::bstSkip); + builtInFunctions.put("stack$", this::bstStack); + builtInFunctions.put("substring$", this::bstSubstring); + builtInFunctions.put("swap$", this::bstSwap); + builtInFunctions.put("text.length$", this::bstTextLength); + builtInFunctions.put("text.prefix$", this::bstTextPrefix); + builtInFunctions.put("top$", this::bstTop); + builtInFunctions.put("type$", this::bstType); + builtInFunctions.put("warning$", this::bstWarning); + builtInFunctions.put("while$", this::bstWhile); + builtInFunctions.put("width$", this::bstWidth); + builtInFunctions.put("write$", this::bstWrite); + } + + public Map getBuiltInFunction() { + return builtInFunctions; } /** diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java index 6245a1ff3ee..847caaa41fb 100644 --- a/src/main/java/org/jabref/logic/bst/BstVM.java +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -2,10 +2,26 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Stack; +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.bibtex.InvalidFieldValueException; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.Month; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.StandardField; + import org.antlr.v4.runtime.BailErrorStrategy; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStream; @@ -27,11 +43,11 @@ public class BstVM { private final Map integers = new HashMap<>(); private final Map functions = new HashMap<>(); private final Stack stack = new Stack<>(); + private List entries = new ArrayList<>(); - private final BstFunctions bstFunctions; - private final StringBuilder bbl; - + private StringBuilder bbl; private Path path; + private BibDatabase bibDatabase; public BstVM(Path path) throws RecognitionException, IOException { this(CharStreams.fromPath(path)); @@ -49,7 +65,6 @@ private BstVM(CharStream bst) throws RecognitionException { private BstVM(ParseTree tree) { this.tree = tree; bbl = new StringBuilder(); - bstFunctions = new BstFunctions(strings, integers, functions, stack, bbl); } private static ParseTree charStream2CommonTree(CharStream query) { @@ -63,6 +78,42 @@ private static ParseTree charStream2CommonTree(CharStream query) { return parser.start(); } + /** + * Transforms the given list of BibEntries to a rendered list of references using the underlying bst file + * + * @param bibEntries list of entries to convert + * @param bibDatabase (may be null) the bibDatabase used for resolving strings / crossref + * @return list of references in plain text form + */ + public String render(Collection bibEntries, BibDatabase bibDatabase) { + Objects.requireNonNull(bibEntries); + this.bibDatabase = bibDatabase; + + reset(); + + entries = new ArrayList<>(bibEntries.size()); + for (BibEntry entry : bibEntries) { + entries.add(new BstEntry(entry)); + } + + return bbl.toString(); + } + + private void reset() { + bbl = new StringBuilder(); + + strings.clear(); + + integers.clear(); + integers.put("entry.max$", Integer.MAX_VALUE); + integers.put("global.max$", Integer.MAX_VALUE); + + functions.clear(); + functions.putAll(new BstFunctions(strings, integers, functions, stack, bbl).getBuiltInFunction()); + + stack.clear(); + } + private static class ThrowingErrorListener extends BaseErrorListener { public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); @@ -114,6 +165,87 @@ public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { return BstVM.TRUE; } + @Override + public Integer visitReadCommand(BstParser.ReadCommandContext ctx) { + FieldWriter fieldWriter = new FieldWriter(new FieldWriterPreferences(true, List.of(StandardField.MONTH), new FieldContentFormatterPreferences())); + for (BstEntry e : entries) { + for (Map.Entry mEntry : e.fields.entrySet()) { + Field field = FieldFactory.parseField(mEntry.getKey()); + String fieldValue = e.entry.getResolvedFieldOrAlias(field, bibDatabase) + .map(content -> { + try { + String result = fieldWriter.write(field, content); + if (result.startsWith("{")) { + // Strip enclosing {} from the output + return result.substring(1, result.length() - 1); + } + if (field == StandardField.MONTH) { + // We don't have the internal BibTeX strings at hand. + // Thus, we look up the full month name in the generic table. + return Month.parse(result) + .map(Month::getFullName) + .orElse(result); + } + return result; + } catch ( + InvalidFieldValueException invalidFieldValueException) { + // in case there is something wrong with the content, just return the content itself + return content; + } + }) + .orElse(null); + mEntry.setValue(fieldValue); + } + } + + for (BstEntry e : entries) { + if (!e.fields.containsKey(StandardField.CROSSREF.getName())) { + e.fields.put(StandardField.CROSSREF.getName(), null); + } + } + + return BstVM.TRUE; + } + + @Override + public Integer visitEntryCommand(BstParser.EntryCommandContext ctx) { + // Entry contains 3 optionally filled identifier lists: + // Fields, Integers and Strings + + ParseTree entryFields = ctx.getChild(1); + for (int i = 0; i < entryFields.getChildCount(); i++) { + for (BstEntry entry : entries) { + entry.fields.put(entryFields.getChild(i).getText(), null); + } + } + + ParseTree entryIntegers = ctx.getChild(2); + for (int i = 0; i < entryIntegers.getChildCount(); i++) { + for (BstEntry entry : entries) { + entry.localIntegers.put(entryIntegers.getChild(i).getText(), 0); + } + } + + ParseTree entryStrings = ctx.getChild(3); + for (int i = 0; i < entryStrings.getChildCount(); i++) { + for (BstEntry entry : entries) { + entry.fields.put(entryStrings.getChild(i).getText(), null); + } + } + + for (BstEntry entry : entries) { + entry.localStrings.put("sort.key$", null); + } + + return BstVM.TRUE; + } + + @Override + public Integer visitSortCommand(BstParser.SortCommandContext ctx) { + entries.sort(Comparator.comparing(o -> (o.localStrings.get("sort.key$")))); + return BstVM.TRUE; + } + private class MacroFunction implements BstFunctions.BstFunction { private final String replacement; From ad63426d11c4cbcf1fb9c94dad7b7ee293f05ce9 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Wed, 8 Jun 2022 22:44:33 +0200 Subject: [PATCH 06/39] functions visitor --- src/main/antlr4/org/jabref/bst/Bst.g4 | 29 +-- .../org/jabref/logic/bst/BstFunctions.java | 220 ++++++++++-------- src/main/java/org/jabref/logic/bst/BstVM.java | 206 +++------------- .../org/jabref/logic/bst/BstVMContext.java | 22 ++ .../org/jabref/logic/bst/BstVMVisitor.java | 159 +++++++++++++ 5 files changed, 345 insertions(+), 291 deletions(-) create mode 100644 src/main/java/org/jabref/logic/bst/BstVMContext.java create mode 100644 src/main/java/org/jabref/logic/bst/BstVMVisitor.java diff --git a/src/main/antlr4/org/jabref/bst/Bst.g4 b/src/main/antlr4/org/jabref/bst/Bst.g4 index a3ecd697a30..35ac6a5c624 100644 --- a/src/main/antlr4/org/jabref/bst/Bst.g4 +++ b/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -13,8 +13,6 @@ ENTRY : 'ENTRY'; READ : 'READ'; MACRO : 'MACRO'; -LBRACE : '{'; -RBRACE : '}'; GT : '>'; LT : '<'; EQUAL : '='; @@ -22,6 +20,10 @@ ASSIGN : ':='; ADD : '+'; SUB : '-'; CONCAT : '*'; +IF : 'if$'; +WHILE : 'while$'; +LBRACE : '{'; +RBRACE : '}'; fragment LETTER : ('a'..'z'|'A'..'Z'|'.'|'$'); fragment DIGIT : [0-9]; @@ -36,19 +38,19 @@ LINE_COMMENT : '%' ~('\n'|'\r')* '\r'? '\n' -> skip; // Parser -start +bstFile : commands+ EOF ; commands : STRINGS ids=idListObl #stringsCommand | INTEGERS ids=idListObl #integersCommand - | FUNCTION LBRACE id=identifier RBRACE exp=stack #functionCommand + | FUNCTION LBRACE id=identifier RBRACE function=stack #functionCommand | MACRO LBRACE id=identifier RBRACE LBRACE repl=STRING RBRACE #macroCommand | READ #readCommand - | EXECUTE LBRACE exp=function RBRACE #executeCommand - | ITERATE LBRACE exp=function RBRACE #iterateCommand - | REVERSE LBRACE exp=function RBRACE #reverseCommand + | EXECUTE LBRACE bstFunction RBRACE #executeCommand + | ITERATE LBRACE bstFunction RBRACE #iterateCommand + | REVERSE LBRACE bstFunction RBRACE #reverseCommand | ENTRY idListOpt idListOpt idListOpt #entryCommand | SORT #sortCommand ; @@ -57,20 +59,19 @@ identifier : IDENTIFIER ; +// Obligatory identifier list idListObl : LBRACE identifier+ RBRACE ; +// Optional identifier list idListOpt : LBRACE identifier* RBRACE ; -function - : operator=(LT | GT | EQUAL) #comparisonFunction - | operator=(ADD | SUB) #arithmeticFunction - | ASSIGN #assignmentFunction - | CONCAT #concatFunction - | identifier #userFunction +bstFunction + : LT | GT | EQUAL | ADD | SUB | ASSIGN | CONCAT + | identifier ; stack @@ -78,7 +79,7 @@ stack ; stackitem - : function + : bstFunction | STRING | INTEGER | QUOTED diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 179ed2f7c50..0f74f35c628 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -15,6 +15,8 @@ import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.Tree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,44 +28,48 @@ public class BstFunctions { private final Map strings; private final Map integers; private final Map functions; - private final Map builtInFunctions = new HashMap<>(37); + private final String preamble; private final Stack stack; - private final StringBuilder bbl; - private int bstWarning = 0; - private String preamble = ""; + private int bstWarning = 0; @FunctionalInterface public interface BstFunction { - void execute(BstEntry context); + + void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext); + + default void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntry) { + this.execute(visitor, functionContext); + } } - public BstFunctions(Map strings, - Map integers, - Map functions, + public BstFunctions(BstVMContext bstVMContext, Stack stack, StringBuilder bbl) { - this.strings = strings; - this.integers = integers; - this.functions = functions; + this.strings = bstVMContext.strings(); + this.integers = bstVMContext.integers(); + this.functions = bstVMContext.functions(); + this.bstWarning = bstVMContext.warnings(); + this.preamble = bstVMContext.bibDatabase().getPreamble().orElse(""); + this.stack = stack; this.bbl = bbl; - - inititalize(); } - private void inititalize() { + protected Map getBuiltInFunctions() { + Map builtInFunctions = new HashMap<>(); + builtInFunctions.put(">", this::bstIsGreaterThan); builtInFunctions.put("<", this::bstIsLowerThan); builtInFunctions.put("=", this::bstEquals); builtInFunctions.put("+", this::bstAdd); builtInFunctions.put("-", this::bstSubtract); builtInFunctions.put("*", this::bstConcat); - builtInFunctions.put(":=", this::bstAssign); + builtInFunctions.put(":=", new BstAssignFunction()); builtInFunctions.put("add.period$", this::bstAddPeriod); - builtInFunctions.put("call.type$", this::bstCallType); + builtInFunctions.put("call.type$", new BstCallTypeFunction()); builtInFunctions.put("change.case$", this::bstChangeCase); builtInFunctions.put("chr.to.int$", this::bstChrToInt); builtInFunctions.put("cite$", this::bstCite); @@ -92,9 +98,7 @@ private void inititalize() { builtInFunctions.put("while$", this::bstWhile); builtInFunctions.put("width$", this::bstWidth); builtInFunctions.put("write$", this::bstWrite); - } - public Map getBuiltInFunction() { return builtInFunctions; } @@ -103,7 +107,7 @@ public Map getBuiltInFunction() { * the integer 1 if the second is greater than the first, 0 * otherwise. */ - private void bstIsGreaterThan(BstEntry context) { + private void bstIsGreaterThan(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation >"); } @@ -122,7 +126,7 @@ private void bstIsGreaterThan(BstEntry context) { * the integer 1 if the second is lower than the first, 0 * otherwise. */ - private void bstIsLowerThan(BstEntry context) { + private void bstIsLowerThan(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation <"); } @@ -140,7 +144,7 @@ private void bstIsLowerThan(BstEntry context) { * Pops the top two (both integer or both string) literals, compares * them, and pushes the integer 1 if they're equal, 0 otherwise. */ - private void bstEquals(BstEntry context) { + private void bstEquals(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation ="); } @@ -148,12 +152,12 @@ private void bstEquals(BstEntry context) { Object o2 = stack.pop(); if ((o1 == null) ^ (o2 == null)) { - stack.push(VM.FALSE); + stack.push(BstVM.FALSE); return; } if ((o1 == null) && (o2 == null)) { - stack.push(VM.TRUE); + stack.push(BstVM.TRUE); return; } @@ -163,7 +167,7 @@ private void bstEquals(BstEntry context) { /** * Pops the top two (integer) literals and pushes their sum. */ - private void bstAdd(BstEntry context) { + private void bstAdd(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation +"); } @@ -181,7 +185,7 @@ private void bstAdd(BstEntry context) { * Pops the top two (integer) literals and pushes their difference * (the first subtracted from the second). */ - private void bstSubtract(BstEntry context) { + private void bstSubtract(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation -"); } @@ -200,7 +204,7 @@ private void bstSubtract(BstEntry context) { * order, that is, the order in which pushed), and pushes the * resulting string. */ - private void bstConcat(BstEntry context) { + private void bstConcat(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation *"); } @@ -227,45 +231,54 @@ private void bstConcat(BstEntry context) { * Pops the top two literals and assigns to the first (which must be * a global or entry variable) the value of the second. */ - private void bstAssign(BstEntry context) { - if (stack.size() < 2) { - throw new VMException("Invalid call to operation :="); + public class BstAssignFunction implements BstFunction { + + @Override + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + this.execute(visitor, functionContext, null); } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - doBstAssign(context, o1, o2); - } - private boolean doBstAssign(BstEntry context, Object o1, Object o2) { - if (!(o1 instanceof BstVM.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { - throw new VMException("Invalid parameters"); + @Override + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntry) { + if (stack.size() < 2) { + throw new VMException("Invalid call to operation :="); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + doAssign(bstEntry, o1, o2); } - String name = ((VM.Identifier) o1).getName(); + private boolean doAssign(BstEntry context, Object o1, Object o2) { + if (!(o1 instanceof BstVMVisitor.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { + throw new VMException("Invalid parameters"); + } - if (o2 instanceof String) { - if ((context != null) && context.localStrings.containsKey(name)) { - context.localStrings.put(name, (String) o2); + String name = ((BstVMVisitor.Identifier) o1).name(); + + if (o2 instanceof String) { + if ((context != null) && context.localStrings.containsKey(name)) { + context.localStrings.put(name, (String) o2); + return true; + } + + if (strings.containsKey(name)) { + strings.put(name, (String) o2); + return true; + } + return false; + } + + if ((context != null) && context.localIntegers.containsKey(name)) { + context.localIntegers.put(name, (Integer) o2); return true; } - if (strings.containsKey(name)) { - strings.put(name, (String) o2); + if (integers.containsKey(name)) { + integers.put(name, (Integer) o2); return true; } return false; } - - if ((context != null) && context.localIntegers.containsKey(name)) { - context.localIntegers.put(name, (Integer) o2); - return true; - } - - if (integers.containsKey(name)) { - integers.put(name, (Integer) o2); - return true; - } - return false; } /** @@ -273,7 +286,7 @@ private boolean doBstAssign(BstEntry context, Object o1, Object o2) { * '}' character isn't a `.', `?', or `!', and pushes this resulting * string. */ - private void bstAddPeriod(BstEntry context) { + private void bstAddPeriod(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation add.period$"); } @@ -309,11 +322,19 @@ private void bstAddPeriod(BstEntry context) { * one function for each standard entry type as well as a * default.type function. */ - private void bstCallType(BstEntry context) { - if (context == null) { - throw new VMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); + public class BstCallTypeFunction implements BstFunction { + @Override + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + throw new ParseCancellationException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); + } + + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntry) { + if (bstEntry == null) { + this.execute(visitor, functionContext); // Throw error + } + + // functions.get(functionContext.getText()).execute(functionContext, bstEntry); } - // BstVM.this.execute(context.entry.getType().getName(), context); // FIXME } /** @@ -335,7 +356,7 @@ private void bstCallType(BstEntry context) { * the strings t and T are equivalent for the purposes of this built-in * function.) */ - private void bstChangeCase(BstEntry value) { + private void bstChangeCase(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation change.case$"); } @@ -361,7 +382,7 @@ private void bstChangeCase(BstEntry value) { * character, converts it to the corresponding ASCII integer, and * pushes this integer. */ - private void bstChrToInt(BstEntry context) { + private void bstChrToInt(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation chr.to.int$"); } @@ -378,7 +399,7 @@ private void bstChrToInt(BstEntry context) { * Pushes the string that was the \cite-command argument for this * entry. */ - private void bstCite(BstEntry context) { + private void bstCite(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (context == null) { throw new VMException("Must have an entry to cite$"); } @@ -388,7 +409,7 @@ private void bstCite(BstEntry context) { /** * Pops the top literal from the stack and pushes two copies of it. */ - private void bstDuplicate(BstEntry context) { + private void bstDuplicate(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation duplicate$"); } @@ -403,14 +424,14 @@ private void bstDuplicate(BstEntry context) { * field or a string having no non-white-space characters, 0 * otherwise. */ - private void bstEmpty(BstEntry context) { + private void bstEmpty(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation empty$"); } Object o1 = stack.pop(); if (o1 == null) { - stack.push(VM.TRUE); + stack.push(BstVM.TRUE); return; } @@ -432,7 +453,7 @@ private void bstEmpty(BstEntry context) { * pushes the formatted name. If any of the types is incorrect, it * complains and pushes the null string. */ - private void bstFormatName(BstEntry context) { + private void bstFormatName(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 3) { throw new VMException("Not enough operands on stack for operation format.name$"); } @@ -469,23 +490,24 @@ private void bstFormatName(BstEntry context) { * than 0, it executes the second literal, else it executes the * first. */ - private void bstIf(BstEntry context) { + private void bstIf(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { 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 BstVM.Identifier) || (f1 instanceof Tree)) - && ((f2 instanceof BstVM.Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { + if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof Tree)) + && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { throw new VMException("Expecting two functions and an integer for if$."); } - if ((Integer) i > 0) { - // BstVM.this.executeInContext(f2, context); // FIXME + if (((Integer) i) > 0) { + visitor.visit((ParseTree) f2); } else { - // BstVM.this.executeInContext(f1, context); // FIXME + visitor.visit((ParseTree) f1); } } @@ -494,7 +516,7 @@ private void bstIf(BstEntry context) { * value of a single character, converts it to the corresponding * single-character string, and pushes this string. */ - private void bstIntToChr(BstEntry context) { + private void bstIntToChr(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation int.to.chr$"); } @@ -511,7 +533,7 @@ private void bstIntToChr(BstEntry context) { * Pops the top (integer) literal, converts it to its (unique) * string equivalent, and pushes this string. */ - private void bstIntToStr(BstEntry context) { + private void bstIntToStr(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation int.to.str$"); } @@ -528,24 +550,24 @@ private void bstIntToStr(BstEntry context) { * Pops the top literal and pushes the integer 1 if it's a missing * field, 0 otherwise. */ - private void bstMissing(BstEntry context) { + private void bstMissing(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation missing$"); } Object o1 = stack.pop(); if (o1 == null) { - stack.push(VM.TRUE); + stack.push(BstVM.TRUE); return; } if (!(o1 instanceof String)) { LOGGER.warn("Not a string or missing field in operation missing$"); - stack.push(VM.TRUE); + stack.push(BstVM.TRUE); return; } - stack.push(VM.FALSE); + stack.push(BstVM.FALSE); } /** @@ -555,7 +577,7 @@ private void bstMissing(BstEntry context) { * function only when you want a blank line or an explicit line * break. */ - private void bstNewLine(BstEntry context) { + private void bstNewLine(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { this.bbl.append('\n'); } @@ -565,7 +587,7 @@ private void bstNewLine(BstEntry context) { * substring "and" (ignoring case differences) surrounded by * non-null white-space at the top brace level. */ - private void bstNumNames(BstEntry context) { + private void bstNumNames(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation num.names$"); } @@ -582,7 +604,7 @@ private void bstNumNames(BstEntry context) { * Pops the top of the stack but doesn't print it; this gets rid of * an unwanted stack literal. */ - private void bstPop(BstEntry context) { + private void bstPop(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { stack.pop(); } @@ -592,7 +614,7 @@ private void bstPop(BstEntry context) { * database files. (or the empty string if there were none) * '@PREAMBLE' strings are read from the database files. */ - private void bstPreamble(BstEntry context) { + private void bstPreamble(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { stack.push(preamble); } @@ -603,7 +625,7 @@ private void bstPreamble(BstEntry context) { * contained in the control sequences associated with a \special * character", and pushes the resulting string. */ - private void bstPurify(BstEntry context) { + private void bstPurify(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation purify$"); } @@ -621,14 +643,14 @@ private void bstPurify(BstEntry context) { /** * Pushes the string consisting of the double-quote character. */ - private void bstQuote(BstEntry context) { + private void bstQuote(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { stack.push("\""); } /** * Does nothing. */ - private void bstSkip(BstEntry context) { + private void bstSkip(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { // no-op } @@ -636,7 +658,7 @@ private void bstSkip(BstEntry context) { * Pops and prints the whole stack; it's meant to be used for style * designers while debugging. */ - private void bstStack(BstEntry context) { + private void bstStack(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { while (!stack.empty()) { LOGGER.debug("Stack entry {}", stack.pop()); } @@ -651,7 +673,7 @@ private void bstStack(BstEntry context) { * (including) from the end if start is negative (where the first * character from the end is the last character). */ - private void bstSubstring(BstEntry context) { + private void bstSubstring(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 3) { throw new VMException("Not enough operands on stack for operation substring$"); } @@ -693,7 +715,7 @@ private void bstSubstring(BstEntry context) { * text character, even if it's missing its matching right brace, * and where braces don't count as text characters. */ - private void bstSwap(BstEntry context) { + private void bstSwap(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation swap$"); } @@ -715,7 +737,7 @@ private void bstSwap(BstEntry context) { * BibTEX considers everything contained inside the braces as a * single letter. */ - private void bstTextLength(BstEntry context) { + private void bstTextLength(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation text.length$"); } @@ -771,7 +793,7 @@ private void bstTextLength(BstEntry context) { * consider braces to be text characters; furthermore, this function * appends any needed matching right braces. */ - private void bstTextPrefix(BstEntry context) { + private void bstTextPrefix(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.size() < 2) { throw new VMException("Not enough operands on stack for operation text.prefix$"); } @@ -796,7 +818,7 @@ private void bstTextPrefix(BstEntry context) { /** * Pops and prints the top of the stack to the log file. It's useful for debugging. */ - private void bstTop(BstEntry context) { + private void bstTop(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { LOGGER.debug("Stack entry {}", stack.pop()); } @@ -804,7 +826,7 @@ private void bstTop(BstEntry context) { * Pushes the current entry's type (book, article, etc.), but pushes * the null string if the type is either unknown or undefined. */ - private void bstType(BstEntry context) { + private void bstType(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (context == null) { throw new VMException("type$ need a context."); } @@ -817,7 +839,7 @@ private void bstType(BstEntry context) { * message. This also increments a count of the number of warning * messages issued. */ - private void bstWarning(BstEntry context) { + private void bstWarning(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { LOGGER.warn("Warning (#{}): {}", bstWarning++, stack.pop()); } @@ -826,20 +848,20 @@ private void bstWarning(BstEntry context) { * second as long as the (integer) literal left on the stack by * executing the first is greater than 0. */ - private void bstWhile(BstEntry context) { + private void bstWhile(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { 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 BstVM.Identifier) || (f1 instanceof Tree)) - && ((f2 instanceof BstVM.Identifier) || (f2 instanceof Tree))) { + if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) + && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree))) { throw new VMException("Expecting two functions for while$."); } do { - // executeInContext(f1, context); // FIXME + visitor.visit((ParseTree) f1); Object i = stack.pop(); if (!(i instanceof Integer)) { @@ -848,7 +870,7 @@ private void bstWhile(BstEntry context) { if ((Integer) i <= 0) { break; } - // executeInContext(f2, context); // FIXME + visitor.visit((ParseTree) f2); } while (true); } @@ -861,7 +883,7 @@ private void bstWhile(BstEntry context) { * characters (even without their |right_brace|s) are handled specially. If the * literal isn't a string, it complains and pushes~0. */ - private void bstWidth(BstEntry context) { + private void bstWidth(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { if (stack.isEmpty()) { throw new VMException("Not enough operands on stack for operation width$"); } @@ -881,7 +903,7 @@ private void bstWidth(BstEntry context) { * (which will result in stuff being written onto the bbl file when * the buffer fills up). */ - private void bstWrite(BstEntry context) { + private void bstWrite(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { String s = (String) stack.pop(); bbl.append(s); } diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java index 847caaa41fb..3cb9272af8b 100644 --- a/src/main/java/org/jabref/logic/bst/BstVM.java +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -4,23 +4,12 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Stack; -import org.jabref.logic.bibtex.FieldContentFormatterPreferences; -import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.logic.bibtex.FieldWriterPreferences; -import org.jabref.logic.bibtex.InvalidFieldValueException; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.Month; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.FieldFactory; -import org.jabref.model.entry.field.StandardField; import org.antlr.v4.runtime.BailErrorStrategy; import org.antlr.v4.runtime.BaseErrorListener; @@ -32,26 +21,32 @@ import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTree; +/* ToDo: + * [x] clean up bst functions and fix javadoc + * [x] migrate grammar + * [x] migrate runner/visitor + * [x] migrate functions + * [ ] context / entry needs to be set as a private final for the visitor can iteratate forward or backward on + * corresponding command. + * [ ] migrate execute / forward / backward / stack / quote / etc. + * [ ] remove old VM + * [ ] fix tests + * [ ] create new tests + * [ ] debug + * [ ] more debug + * [ ] clean up bstFunctions + * [ ] still more debug + */ + public class BstVM { - protected static final int FALSE = 0; - protected static final int TRUE = 1; + protected static final Integer FALSE = 0; + protected static final Integer TRUE = 1; private final ParseTree tree; - private final Map strings = new HashMap<>(); - private final Map integers = new HashMap<>(); - private final Map functions = new HashMap<>(); - private final Stack stack = new Stack<>(); - private List entries = new ArrayList<>(); - - private StringBuilder bbl; - private Path path; - private BibDatabase bibDatabase; - public BstVM(Path path) throws RecognitionException, IOException { this(CharStreams.fromPath(path)); - this.path = path; } public BstVM(String s) throws RecognitionException { @@ -64,7 +59,6 @@ private BstVM(CharStream bst) throws RecognitionException { private BstVM(ParseTree tree) { this.tree = tree; - bbl = new StringBuilder(); } private static ParseTree charStream2CommonTree(CharStream query) { @@ -75,7 +69,7 @@ private static ParseTree charStream2CommonTree(CharStream query) { parser.removeErrorListeners(); parser.addErrorListener(ThrowingErrorListener.INSTANCE); parser.setErrorHandler(new BailErrorStrategy()); - return parser.start(); + return parser.bstFile(); } /** @@ -87,31 +81,22 @@ private static ParseTree charStream2CommonTree(CharStream query) { */ public String render(Collection bibEntries, BibDatabase bibDatabase) { Objects.requireNonNull(bibEntries); - this.bibDatabase = bibDatabase; - reset(); - - entries = new ArrayList<>(bibEntries.size()); + List entries = new ArrayList<>(bibEntries.size()); for (BibEntry entry : bibEntries) { entries.add(new BstEntry(entry)); } - return bbl.toString(); - } - - private void reset() { - bbl = new StringBuilder(); - - strings.clear(); + StringBuilder bbl = new StringBuilder(); + Stack stack = new Stack<>(); - integers.clear(); - integers.put("entry.max$", Integer.MAX_VALUE); - integers.put("global.max$", Integer.MAX_VALUE); + BstVMContext bstVMContext = new BstVMContext(entries, bibDatabase); + bstVMContext.functions().putAll(new BstFunctions(bstVMContext, stack, bbl).getBuiltInFunctions()); - functions.clear(); - functions.putAll(new BstFunctions(strings, integers, functions, stack, bbl).getBuiltInFunction()); + BstVMVisitor bstVMVisitor = new BstVMVisitor(bstVMContext, bbl); + bstVMVisitor.visit(tree); - stack.clear(); + return bbl.toString(); } private static class ThrowingErrorListener extends BaseErrorListener { @@ -124,139 +109,4 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg); } } - - protected static class Identifier { - - public final String name; - - public Identifier(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - private class BstVMVisitor extends BstBaseVisitor { - @Override - public Integer visitStringsCommand(BstParser.StringsCommandContext ctx) { - if (ctx.ids.getChildCount() > 20) { - throw new ParseCancellationException("Strings limit reached"); - } - - for (int i = 0; i < ctx.ids.getChildCount(); i++) { - strings.put(ctx.ids.getChild(i).getText(), null); - } - return BstVM.TRUE; - } - - @Override - public Integer visitIntegersCommand(BstParser.IntegersCommandContext ctx) { - for (int i = 0; i < ctx.ids.getChildCount(); i++) { - integers.put(ctx.ids.getChild(i).getText(), 0); - } - return BstVM.TRUE; - } - - @Override - public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { - functions.put(ctx.id.getText(), new MacroFunction(ctx.repl.getText())); - return BstVM.TRUE; - } - - @Override - public Integer visitReadCommand(BstParser.ReadCommandContext ctx) { - FieldWriter fieldWriter = new FieldWriter(new FieldWriterPreferences(true, List.of(StandardField.MONTH), new FieldContentFormatterPreferences())); - for (BstEntry e : entries) { - for (Map.Entry mEntry : e.fields.entrySet()) { - Field field = FieldFactory.parseField(mEntry.getKey()); - String fieldValue = e.entry.getResolvedFieldOrAlias(field, bibDatabase) - .map(content -> { - try { - String result = fieldWriter.write(field, content); - if (result.startsWith("{")) { - // Strip enclosing {} from the output - return result.substring(1, result.length() - 1); - } - if (field == StandardField.MONTH) { - // We don't have the internal BibTeX strings at hand. - // Thus, we look up the full month name in the generic table. - return Month.parse(result) - .map(Month::getFullName) - .orElse(result); - } - return result; - } catch ( - InvalidFieldValueException invalidFieldValueException) { - // in case there is something wrong with the content, just return the content itself - return content; - } - }) - .orElse(null); - mEntry.setValue(fieldValue); - } - } - - for (BstEntry e : entries) { - if (!e.fields.containsKey(StandardField.CROSSREF.getName())) { - e.fields.put(StandardField.CROSSREF.getName(), null); - } - } - - return BstVM.TRUE; - } - - @Override - public Integer visitEntryCommand(BstParser.EntryCommandContext ctx) { - // Entry contains 3 optionally filled identifier lists: - // Fields, Integers and Strings - - ParseTree entryFields = ctx.getChild(1); - for (int i = 0; i < entryFields.getChildCount(); i++) { - for (BstEntry entry : entries) { - entry.fields.put(entryFields.getChild(i).getText(), null); - } - } - - ParseTree entryIntegers = ctx.getChild(2); - for (int i = 0; i < entryIntegers.getChildCount(); i++) { - for (BstEntry entry : entries) { - entry.localIntegers.put(entryIntegers.getChild(i).getText(), 0); - } - } - - ParseTree entryStrings = ctx.getChild(3); - for (int i = 0; i < entryStrings.getChildCount(); i++) { - for (BstEntry entry : entries) { - entry.fields.put(entryStrings.getChild(i).getText(), null); - } - } - - for (BstEntry entry : entries) { - entry.localStrings.put("sort.key$", null); - } - - return BstVM.TRUE; - } - - @Override - public Integer visitSortCommand(BstParser.SortCommandContext ctx) { - entries.sort(Comparator.comparing(o -> (o.localStrings.get("sort.key$")))); - return BstVM.TRUE; - } - - private class MacroFunction implements BstFunctions.BstFunction { - private final String replacement; - - MacroFunction(String replacement) { - this.replacement = replacement; - } - - @Override - public void execute(BstEntry context) { - stack.push(replacement); - } - } - } } diff --git a/src/main/java/org/jabref/logic/bst/BstVMContext.java b/src/main/java/org/jabref/logic/bst/BstVMContext.java new file mode 100644 index 00000000000..0d40e94789d --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstVMContext.java @@ -0,0 +1,22 @@ +package org.jabref.logic.bst; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jabref.model.database.BibDatabase; + +public record BstVMContext(List entries, + Map strings, + Map integers, + Map functions, + BibDatabase bibDatabase, + Integer warnings) { + + public BstVMContext(List entries, BibDatabase bibDatabase) { + this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), bibDatabase, 0); + + this.integers.put("entry.max$", Integer.MAX_VALUE); + this.integers.put("global.max$", Integer.MAX_VALUE); + } +} diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java new file mode 100644 index 00000000000..42e55b2cb1c --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -0,0 +1,159 @@ +package org.jabref.logic.bst; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.bibtex.InvalidFieldValueException; +import org.jabref.model.entry.Month; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.StandardField; + +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.runtime.tree.ParseTree; + +class BstVMVisitor extends BstBaseVisitor { + private final BstVMContext bstVMContext; + private final Stack stack = new Stack<>(); + + private final StringBuilder bbl; + + public record Identifier(String name) { + } + + public BstVMVisitor(BstVMContext bstVMContext, StringBuilder bbl) { + this.bstVMContext = bstVMContext; + this.bbl = bbl; + } + + @Override + public Integer visitStringsCommand(BstParser.StringsCommandContext ctx) { + if (ctx.ids.getChildCount() > 20) { + throw new ParseCancellationException("Strings limit reached"); + } + + for (int i = 0; i < ctx.ids.getChildCount(); i++) { + bstVMContext.strings().put(ctx.ids.getChild(i).getText(), null); + } + return BstVM.TRUE; + } + + @Override + public Integer visitIntegersCommand(BstParser.IntegersCommandContext ctx) { + for (int i = 0; i < ctx.ids.getChildCount(); i++) { + bstVMContext.integers().put(ctx.ids.getChild(i).getText(), 0); + } + return BstVM.TRUE; + } + + @Override + public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { + bstVMContext.functions().put(ctx.id.getText(), new MacroFunction(ctx.repl.getText())); + return BstVM.TRUE; + } + + @Override + public Integer visitReadCommand(BstParser.ReadCommandContext ctx) { + FieldWriter fieldWriter = new FieldWriter(new FieldWriterPreferences(true, List.of(StandardField.MONTH), new FieldContentFormatterPreferences())); + for (BstEntry e : bstVMContext.entries()) { + for (Map.Entry mEntry : e.fields.entrySet()) { + Field field = FieldFactory.parseField(mEntry.getKey()); + String fieldValue = e.entry.getResolvedFieldOrAlias(field, bstVMContext.bibDatabase()) + .map(content -> { + try { + String result = fieldWriter.write(field, content); + if (result.startsWith("{")) { + // Strip enclosing {} from the output + return result.substring(1, result.length() - 1); + } + if (field == StandardField.MONTH) { + // We don't have the internal BibTeX strings at hand. + // Thus, we look up the full month name in the generic table. + return Month.parse(result) + .map(Month::getFullName) + .orElse(result); + } + return result; + } catch ( + InvalidFieldValueException invalidFieldValueException) { + // in case there is something wrong with the content, just return the content itself + return content; + } + }) + .orElse(null); + mEntry.setValue(fieldValue); + } + } + + for (BstEntry e : bstVMContext.entries()) { + if (!e.fields.containsKey(StandardField.CROSSREF.getName())) { + e.fields.put(StandardField.CROSSREF.getName(), null); + } + } + + return BstVM.TRUE; + } + + @Override + public Integer visitEntryCommand(BstParser.EntryCommandContext ctx) { + // ENTRY command contains 3 optionally filled identifier lists: + // Fields, Integers and Strings + + ParseTree entryFields = ctx.getChild(1); + for (int i = 0; i < entryFields.getChildCount(); i++) { + for (BstEntry entry : bstVMContext.entries()) { + entry.fields.put(entryFields.getChild(i).getText(), null); + } + } + + ParseTree entryIntegers = ctx.getChild(2); + for (int i = 0; i < entryIntegers.getChildCount(); i++) { + for (BstEntry entry : bstVMContext.entries()) { + entry.localIntegers.put(entryIntegers.getChild(i).getText(), 0); + } + } + + ParseTree entryStrings = ctx.getChild(3); + for (int i = 0; i < entryStrings.getChildCount(); i++) { + for (BstEntry entry : bstVMContext.entries()) { + entry.localStrings.put(entryStrings.getChild(i).getText(), null); + } + } + + for (BstEntry entry : bstVMContext.entries()) { + entry.localStrings.put("sort.key$", null); + } + + return BstVM.TRUE; + } + + @Override + public Integer visitSortCommand(BstParser.SortCommandContext ctx) { + bstVMContext.entries().sort(Comparator.comparing(o -> (o.localStrings.get("sort.key$")))); + return BstVM.TRUE; + } + + @Override + public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { + bstVMContext.functions().get(ctx.getChild(0).getText()).execute(this, ctx); + return BstVM.TRUE; + } + + private class MacroFunction implements BstFunctions.BstFunction { + private final String replacement; + + MacroFunction(String replacement) { + this.replacement = replacement; + } + + @Override + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + stack.push(replacement); + } + } +} From 56b45c80639dcfa65452807da8add901426df57f Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Fri, 10 Jun 2022 18:22:15 +0200 Subject: [PATCH 07/39] bstEntryContext --- .../org/jabref/logic/bst/BstFunctions.java | 62 ++++++++++++------- src/main/java/org/jabref/logic/bst/BstVM.java | 6 +- .../org/jabref/logic/bst/BstVMContext.java | 5 +- .../org/jabref/logic/bst/BstVMVisitor.java | 6 +- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 0f74f35c628..318fa5c8b7b 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -15,7 +15,6 @@ import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; -import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.Tree; import org.slf4j.Logger; @@ -40,7 +39,7 @@ public interface BstFunction { void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext); - default void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntry) { + default void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntryContext) { this.execute(visitor, functionContext); } } @@ -51,7 +50,6 @@ public BstFunctions(BstVMContext bstVMContext, this.strings = bstVMContext.strings(); this.integers = bstVMContext.integers(); this.functions = bstVMContext.functions(); - this.bstWarning = bstVMContext.warnings(); this.preamble = bstVMContext.bibDatabase().getPreamble().orElse(""); this.stack = stack; @@ -72,7 +70,7 @@ protected Map getBuiltInFunctions() { builtInFunctions.put("call.type$", new BstCallTypeFunction()); builtInFunctions.put("change.case$", this::bstChangeCase); builtInFunctions.put("chr.to.int$", this::bstChrToInt); - builtInFunctions.put("cite$", this::bstCite); + builtInFunctions.put("cite$", new BstCiteFunction()); builtInFunctions.put("duplicate$", this::bstDuplicate); builtInFunctions.put("empty$", this::bstEmpty); builtInFunctions.put("format.name$", this::bstFormatName); @@ -93,7 +91,7 @@ protected Map getBuiltInFunctions() { builtInFunctions.put("text.length$", this::bstTextLength); builtInFunctions.put("text.prefix$", this::bstTextPrefix); builtInFunctions.put("top$", this::bstTop); - builtInFunctions.put("type$", this::bstType); + builtInFunctions.put("type$", new BstTypeFunction()); builtInFunctions.put("warning$", this::bstWarning); builtInFunctions.put("while$", this::bstWhile); builtInFunctions.put("width$", this::bstWidth); @@ -255,6 +253,7 @@ private boolean doAssign(BstEntry context, Object o1, Object o2) { String name = ((BstVMVisitor.Identifier) o1).name(); + // ToDo: use switch statement pattern matching instead (Java 19) if (o2 instanceof String) { if ((context != null) && context.localStrings.containsKey(name)) { context.localStrings.put(name, (String) o2); @@ -266,17 +265,18 @@ private boolean doAssign(BstEntry context, Object o1, Object o2) { return true; } return false; - } + } else { + if ((context != null) && context.localIntegers.containsKey(name)) { + context.localIntegers.put(name, (Integer) o2); + return true; + } - if ((context != null) && context.localIntegers.containsKey(name)) { - context.localIntegers.put(name, (Integer) o2); - return true; + if (integers.containsKey(name)) { + integers.put(name, (Integer) o2); + return true; + } } - if (integers.containsKey(name)) { - integers.put(name, (Integer) o2); - return true; - } return false; } } @@ -325,15 +325,16 @@ private void bstAddPeriod(BstVMVisitor visitor, BstParser.BstFunctionContext fun public class BstCallTypeFunction implements BstFunction { @Override public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { - throw new ParseCancellationException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); + throw new VMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); } + @Override public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntry) { if (bstEntry == null) { this.execute(visitor, functionContext); // Throw error } - // functions.get(functionContext.getText()).execute(functionContext, bstEntry); + functions.get(functionContext.getText()).execute(visitor, functionContext, bstEntry); } } @@ -399,11 +400,21 @@ private void bstChrToInt(BstVMVisitor visitor, BstParser.BstFunctionContext func * Pushes the string that was the \cite-command argument for this * entry. */ - private void bstCite(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { - if (context == null) { + public class BstCiteFunction implements BstFunction { + @Override + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { throw new VMException("Must have an entry to cite$"); } - stack.push(context.entry.getCitationKey().orElse(null)); + + @Override + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntryContext) { + if (bstEntryContext == null) { + execute(visitor, functionContext); + return; + } + + stack.push(bstEntryContext.entry.getCitationKey().orElse(null)); + } } /** @@ -826,12 +837,21 @@ private void bstTop(BstVMVisitor visitor, BstParser.BstFunctionContext functionC * Pushes the current entry's type (book, article, etc.), but pushes * the null string if the type is either unknown or undefined. */ - private void bstType(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { - if (context == null) { + public class BstTypeFunction implements BstFunction { + @Override + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { throw new VMException("type$ need a context."); } - stack.push(context.entry.getType().getName()); + @Override + public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntryContext) { + if (bstEntryContext == null) { + this.execute(visitor, functionContext); + return; + } + + stack.push(bstEntryContext.entry.getType().getName()); + } } /** diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java index 3cb9272af8b..cbf1bb52459 100644 --- a/src/main/java/org/jabref/logic/bst/BstVM.java +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -26,16 +26,12 @@ * [x] migrate grammar * [x] migrate runner/visitor * [x] migrate functions - * [ ] context / entry needs to be set as a private final for the visitor can iteratate forward or backward on - * corresponding command. + * [x] context / entry needs to be set as a field for the visitor to iterate forwards or backwards through all the entries * [ ] migrate execute / forward / backward / stack / quote / etc. * [ ] remove old VM * [ ] fix tests * [ ] create new tests - * [ ] debug - * [ ] more debug * [ ] clean up bstFunctions - * [ ] still more debug */ public class BstVM { diff --git a/src/main/java/org/jabref/logic/bst/BstVMContext.java b/src/main/java/org/jabref/logic/bst/BstVMContext.java index 0d40e94789d..869096de107 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMContext.java +++ b/src/main/java/org/jabref/logic/bst/BstVMContext.java @@ -10,11 +10,10 @@ public record BstVMContext(List entries, Map strings, Map integers, Map functions, - BibDatabase bibDatabase, - Integer warnings) { + BibDatabase bibDatabase) { public BstVMContext(List entries, BibDatabase bibDatabase) { - this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), bibDatabase, 0); + this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), bibDatabase); this.integers.put("entry.max$", Integer.MAX_VALUE); this.integers.put("global.max$", Integer.MAX_VALUE); diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 42e55b2cb1c..bdebbc70fa1 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -19,10 +19,12 @@ class BstVMVisitor extends BstBaseVisitor { private final BstVMContext bstVMContext; - private final Stack stack = new Stack<>(); + private final Stack stack = new Stack<>(); private final StringBuilder bbl; + private BstEntry currentBstEntry; + public record Identifier(String name) { } @@ -140,7 +142,7 @@ public Integer visitSortCommand(BstParser.SortCommandContext ctx) { @Override public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { - bstVMContext.functions().get(ctx.getChild(0).getText()).execute(this, ctx); + bstVMContext.functions().get(ctx.getChild(0).getText()).execute(this, ctx, currentBstEntry); return BstVM.TRUE; } From c30fe48caa23eeba98516398d45c19d64d29483c Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Fri, 10 Jun 2022 20:55:11 +0200 Subject: [PATCH 08/39] function, identifier, stackitem, exception --- .../org/jabref/logic/bst/BstFunctions.java | 195 +++++++++--------- .../org/jabref/logic/bst/BstVMException.java | 8 + .../org/jabref/logic/bst/BstVMVisitor.java | 118 ++++++++--- src/main/java/org/jabref/logic/bst/VM.java | 101 ++++----- .../org/jabref/logic/bst/VMException.java | 8 - .../logic/bst/util/BibtexNameFormatter.java | 4 +- .../java/org/jabref/logic/bst/TestVM.java | 2 +- 7 files changed, 253 insertions(+), 183 deletions(-) create mode 100644 src/main/java/org/jabref/logic/bst/BstVMException.java delete mode 100644 src/main/java/org/jabref/logic/bst/VMException.java diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 318fa5c8b7b..a0dfe3cb41a 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -15,6 +15,7 @@ import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; +import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.Tree; import org.slf4j.Logger; @@ -37,10 +38,10 @@ public class BstFunctions { @FunctionalInterface public interface BstFunction { - void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext); + void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext); - default void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntryContext) { - this.execute(visitor, functionContext); + default void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntryContext) { + this.execute(visitor, parserRuleContext); } } @@ -105,15 +106,15 @@ protected Map getBuiltInFunctions() { * the integer 1 if the second is greater than the first, 0 * otherwise. */ - private void bstIsGreaterThan(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstIsGreaterThan(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation >"); + throw new BstVMException("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 >"); + throw new BstVMException("Can only compare two integers with >"); } stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? BstVM.TRUE : BstVM.FALSE); @@ -124,15 +125,15 @@ private void bstIsGreaterThan(BstVMVisitor visitor, BstParser.BstFunctionContext * the integer 1 if the second is lower than the first, 0 * otherwise. */ - private void bstIsLowerThan(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstIsLowerThan(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation <"); + throw new BstVMException("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 <"); + throw new BstVMException("Can only compare two integers with <"); } stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? BstVM.TRUE : BstVM.FALSE); @@ -142,9 +143,9 @@ private void bstIsLowerThan(BstVMVisitor visitor, BstParser.BstFunctionContext f * Pops the top two (both integer or both string) literals, compares * them, and pushes the integer 1 if they're equal, 0 otherwise. */ - private void bstEquals(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstEquals(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation ="); + throw new BstVMException("Not enough operands on stack for operation ="); } Object o1 = stack.pop(); Object o2 = stack.pop(); @@ -165,15 +166,15 @@ private void bstEquals(BstVMVisitor visitor, BstParser.BstFunctionContext functi /** * Pops the top two (integer) literals and pushes their sum. */ - private void bstAdd(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstAdd(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation +"); + throw new BstVMException("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 +"); + throw new BstVMException("Can only compare two integers with +"); } stack.push((Integer) o1 + (Integer) o2); @@ -183,15 +184,15 @@ private void bstAdd(BstVMVisitor visitor, BstParser.BstFunctionContext functionC * Pops the top two (integer) literals and pushes their difference * (the first subtracted from the second). */ - private void bstSubtract(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstSubtract(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation -"); + throw new BstVMException("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 -"); + throw new BstVMException("Can only subtract two integers with -"); } stack.push((Integer) o1 - (Integer) o2); @@ -202,9 +203,9 @@ private void bstSubtract(BstVMVisitor visitor, BstParser.BstFunctionContext func * order, that is, the order in which pushed), and pushes the * resulting string. */ - private void bstConcat(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstConcat(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation *"); + throw new BstVMException("Not enough operands on stack for operation *"); } Object o2 = stack.pop(); Object o1 = stack.pop(); @@ -219,7 +220,7 @@ private void bstConcat(BstVMVisitor visitor, BstParser.BstFunctionContext functi if (!((o1 instanceof String) && (o2 instanceof String))) { LOGGER.error("o1: {} ({})", o1, o1.getClass()); LOGGER.error("o2: {} ({})", o2, o2.getClass()); - throw new VMException("Can only concatenate two String with *"); + throw new BstVMException("Can only concatenate two String with *"); } stack.push(o1.toString() + o2); @@ -232,14 +233,14 @@ private void bstConcat(BstVMVisitor visitor, BstParser.BstFunctionContext functi public class BstAssignFunction implements BstFunction { @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { - this.execute(visitor, functionContext, null); + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + this.execute(visitor, parserRuleContext, null); } @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntry) { + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntry) { if (stack.size() < 2) { - throw new VMException("Invalid call to operation :="); + throw new BstVMException("Invalid call to operation :="); } Object o1 = stack.pop(); Object o2 = stack.pop(); @@ -248,7 +249,7 @@ public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionC private boolean doAssign(BstEntry context, Object o1, Object o2) { if (!(o1 instanceof BstVMVisitor.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { - throw new VMException("Invalid parameters"); + throw new BstVMException("Invalid parameters"); } String name = ((BstVMVisitor.Identifier) o1).name(); @@ -286,14 +287,14 @@ private boolean doAssign(BstEntry context, Object o1, Object o2) { * '}' character isn't a `.', `?', or `!', and pushes this resulting * string. */ - private void bstAddPeriod(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstAddPeriod(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation add.period$"); + throw new BstVMException("Not enough operands on stack for operation add.period$"); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new VMException("Can only add a period to a string for add.period$"); + throw new BstVMException("Can only add a period to a string for add.period$"); } Matcher m = ADD_PERIOD_PATTERN.matcher(s); @@ -324,17 +325,17 @@ private void bstAddPeriod(BstVMVisitor visitor, BstParser.BstFunctionContext fun */ public class BstCallTypeFunction implements BstFunction { @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { - throw new VMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + throw new BstVMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); } @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntry) { + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntry) { if (bstEntry == null) { - this.execute(visitor, functionContext); // Throw error + this.execute(visitor, parserRuleContext); // Throw error } - functions.get(functionContext.getText()).execute(visitor, functionContext, bstEntry); + functions.get(parserRuleContext.getText()).execute(visitor, parserRuleContext, bstEntry); } } @@ -357,19 +358,19 @@ public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionC * the strings t and T are equivalent for the purposes of this built-in * function.) */ - private void bstChangeCase(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstChangeCase(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation change.case$"); + throw new BstVMException("Not enough operands on stack for operation change.case$"); } Object o1 = stack.pop(); if (!((o1 instanceof String) && (((String) o1).length() == 1))) { - throw new VMException("A format string of length 1 is needed for change.case$"); + throw new BstVMException("A format string of length 1 is needed for change.case$"); } Object o2 = stack.pop(); if (!(o2 instanceof String)) { - throw new VMException("A string is needed as second parameter for change.case$"); + throw new BstVMException("A string is needed as second parameter for change.case$"); } char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); @@ -383,14 +384,14 @@ private void bstChangeCase(BstVMVisitor visitor, BstParser.BstFunctionContext fu * character, converts it to the corresponding ASCII integer, and * pushes this integer. */ - private void bstChrToInt(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstChrToInt(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation chr.to.int$"); + throw new BstVMException("Not enough operands on stack for operation chr.to.int$"); } Object o1 = stack.pop(); if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { - throw new VMException("Can only perform chr.to.int$ on string with length 1"); + throw new BstVMException("Can only perform chr.to.int$ on string with length 1"); } stack.push((int) s.charAt(0)); @@ -402,14 +403,14 @@ private void bstChrToInt(BstVMVisitor visitor, BstParser.BstFunctionContext func */ public class BstCiteFunction implements BstFunction { @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { - throw new VMException("Must have an entry to cite$"); + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + throw new BstVMException("Must have an entry to cite$"); } @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntryContext) { + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntryContext) { if (bstEntryContext == null) { - execute(visitor, functionContext); + execute(visitor, parserRuleContext); return; } @@ -420,9 +421,9 @@ public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionC /** * Pops the top literal from the stack and pushes two copies of it. */ - private void bstDuplicate(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstDuplicate(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation duplicate$"); + throw new BstVMException("Not enough operands on stack for operation duplicate$"); } Object o1 = stack.pop(); @@ -435,9 +436,9 @@ private void bstDuplicate(BstVMVisitor visitor, BstParser.BstFunctionContext fun * field or a string having no non-white-space characters, 0 * otherwise. */ - private void bstEmpty(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstEmpty(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation empty$"); + throw new BstVMException("Not enough operands on stack for operation empty$"); } Object o1 = stack.pop(); @@ -447,7 +448,7 @@ private void bstEmpty(BstVMVisitor visitor, BstParser.BstFunctionContext functio } if (!(o1 instanceof String s)) { - throw new VMException("Operand does not match function empty$"); + throw new BstVMException("Operand does not match function empty$"); } stack.push("".equals(s.trim()) ? BstVM.TRUE : BstVM.FALSE); @@ -464,9 +465,9 @@ private void bstEmpty(BstVMVisitor visitor, BstParser.BstFunctionContext functio * pushes the formatted name. If any of the types is incorrect, it * complains and pushes the null string. */ - private void bstFormatName(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstFormatName(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation format.name$"); + throw new BstVMException("Not enough operands on stack for operation format.name$"); } Object o1 = stack.pop(); Object o2 = stack.pop(); @@ -487,7 +488,7 @@ private void bstFormatName(BstVMVisitor visitor, BstParser.BstFunctionContext fu } else { AuthorList a = AuthorList.parse(names); if (name > a.getNumberOfAuthors()) { - throw new VMException("Author Out of Bounds. Number " + name + " invalid for " + names); + throw new BstVMException("Author Out of Bounds. Number " + name + " invalid for " + names); } Author author = a.getAuthor(name - 1); @@ -501,9 +502,9 @@ private void bstFormatName(BstVMVisitor visitor, BstParser.BstFunctionContext fu * than 0, it executes the second literal, else it executes the * first. */ - private void bstIf(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstIf(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation ="); + throw new BstVMException("Not enough operands on stack for operation ="); } Object f1 = stack.pop(); @@ -512,7 +513,7 @@ private void bstIf(BstVMVisitor visitor, BstParser.BstFunctionContext functionCo if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof Tree)) && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { - throw new VMException("Expecting two functions and an integer for if$."); + throw new BstVMException("Expecting two functions and an integer for if$."); } if (((Integer) i) > 0) { @@ -527,14 +528,14 @@ private void bstIf(BstVMVisitor visitor, BstParser.BstFunctionContext functionCo * value of a single character, converts it to the corresponding * single-character string, and pushes this string. */ - private void bstIntToChr(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstIntToChr(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation int.to.chr$"); + throw new BstVMException("Not enough operands on stack for operation int.to.chr$"); } Object o1 = stack.pop(); if (!(o1 instanceof Integer i)) { - throw new VMException("Can only perform operation int.to.chr$ on an Integer"); + throw new BstVMException("Can only perform operation int.to.chr$ on an Integer"); } stack.push(String.valueOf((char) i.intValue())); @@ -544,14 +545,14 @@ private void bstIntToChr(BstVMVisitor visitor, BstParser.BstFunctionContext func * Pops the top (integer) literal, converts it to its (unique) * string equivalent, and pushes this string. */ - private void bstIntToStr(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstIntToStr(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation int.to.str$"); + throw new BstVMException("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$"); + throw new BstVMException("Can only transform an integer to an string using int.to.str$"); } stack.push(o1.toString()); @@ -561,9 +562,9 @@ private void bstIntToStr(BstVMVisitor visitor, BstParser.BstFunctionContext func * Pops the top literal and pushes the integer 1 if it's a missing * field, 0 otherwise. */ - private void bstMissing(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstMissing(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation missing$"); + throw new BstVMException("Not enough operands on stack for operation missing$"); } Object o1 = stack.pop(); @@ -588,7 +589,7 @@ private void bstMissing(BstVMVisitor visitor, BstParser.BstFunctionContext funct * function only when you want a blank line or an explicit line * break. */ - private void bstNewLine(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstNewLine(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { this.bbl.append('\n'); } @@ -598,14 +599,14 @@ private void bstNewLine(BstVMVisitor visitor, BstParser.BstFunctionContext funct * substring "and" (ignoring case differences) surrounded by * non-null white-space at the top brace level. */ - private void bstNumNames(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstNumNames(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation num.names$"); + throw new BstVMException("Not enough operands on stack for operation num.names$"); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new VMException("Need a string at the top of the stack for num.names$"); + throw new BstVMException("Need a string at the top of the stack for num.names$"); } stack.push(AuthorList.parse(s).getNumberOfAuthors()); @@ -615,7 +616,7 @@ private void bstNumNames(BstVMVisitor visitor, BstParser.BstFunctionContext func * Pops the top of the stack but doesn't print it; this gets rid of * an unwanted stack literal. */ - private void bstPop(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstPop(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { stack.pop(); } @@ -625,7 +626,7 @@ private void bstPop(BstVMVisitor visitor, BstParser.BstFunctionContext functionC * database files. (or the empty string if there were none) * '@PREAMBLE' strings are read from the database files. */ - private void bstPreamble(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstPreamble(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { stack.push(preamble); } @@ -636,9 +637,9 @@ private void bstPreamble(BstVMVisitor visitor, BstParser.BstFunctionContext func * contained in the control sequences associated with a \special * character", and pushes the resulting string. */ - private void bstPurify(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstPurify(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation purify$"); + throw new BstVMException("Not enough operands on stack for operation purify$"); } Object o1 = stack.pop(); @@ -654,14 +655,14 @@ private void bstPurify(BstVMVisitor visitor, BstParser.BstFunctionContext functi /** * Pushes the string consisting of the double-quote character. */ - private void bstQuote(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstQuote(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { stack.push("\""); } /** * Does nothing. */ - private void bstSkip(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstSkip(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { // no-op } @@ -669,7 +670,7 @@ private void bstSkip(BstVMVisitor visitor, BstParser.BstFunctionContext function * Pops and prints the whole stack; it's meant to be used for style * designers while debugging. */ - private void bstStack(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstStack(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { while (!stack.empty()) { LOGGER.debug("Stack entry {}", stack.pop()); } @@ -684,16 +685,16 @@ private void bstStack(BstVMVisitor visitor, BstParser.BstFunctionContext functio * (including) from the end if start is negative (where the first * character from the end is the last character). */ - private void bstSubstring(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstSubstring(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation substring$"); + throw new BstVMException("Not enough operands on stack for operation substring$"); } Object o1 = stack.pop(); Object o2 = stack.pop(); Object o3 = stack.pop(); if (!((o1 instanceof Integer len) && (o2 instanceof Integer start) && (o3 instanceof String s))) { - throw new VMException("Expecting two integers and a string for substring$"); + throw new BstVMException("Expecting two integers and a string for substring$"); } int lenI = len; @@ -726,9 +727,9 @@ private void bstSubstring(BstVMVisitor visitor, BstParser.BstFunctionContext fun * text character, even if it's missing its matching right brace, * and where braces don't count as text characters. */ - private void bstSwap(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstSwap(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation swap$"); + throw new BstVMException("Not enough operands on stack for operation swap$"); } Object f1 = stack.pop(); Object f2 = stack.pop(); @@ -748,14 +749,14 @@ private void bstSwap(BstVMVisitor visitor, BstParser.BstFunctionContext function * BibTEX considers everything contained inside the braces as a * single letter. */ - private void bstTextLength(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstTextLength(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation text.length$"); + throw new BstVMException("Not enough operands on stack for operation text.length$"); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new VMException("Can only perform operation on a string text.length$"); + throw new BstVMException("Can only perform operation on a string text.length$"); } char[] c = s.toCharArray(); @@ -804,9 +805,9 @@ private void bstTextLength(BstVMVisitor visitor, BstParser.BstFunctionContext fu * consider braces to be text characters; furthermore, this function * appends any needed matching right braces. */ - private void bstTextPrefix(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstTextPrefix(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation text.prefix$"); + throw new BstVMException("Not enough operands on stack for operation text.prefix$"); } Object o1 = stack.pop(); @@ -829,7 +830,7 @@ private void bstTextPrefix(BstVMVisitor visitor, BstParser.BstFunctionContext fu /** * Pops and prints the top of the stack to the log file. It's useful for debugging. */ - private void bstTop(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstTop(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { LOGGER.debug("Stack entry {}", stack.pop()); } @@ -839,14 +840,14 @@ private void bstTop(BstVMVisitor visitor, BstParser.BstFunctionContext functionC */ public class BstTypeFunction implements BstFunction { @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { - throw new VMException("type$ need a context."); + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + throw new BstVMException("type$ need a context."); } @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext, BstEntry bstEntryContext) { + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntryContext) { if (bstEntryContext == null) { - this.execute(visitor, functionContext); + this.execute(visitor, parserRuleContext); return; } @@ -859,7 +860,7 @@ public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionC * message. This also increments a count of the number of warning * messages issued. */ - private void bstWarning(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstWarning(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { LOGGER.warn("Warning (#{}): {}", bstWarning++, stack.pop()); } @@ -868,16 +869,16 @@ private void bstWarning(BstVMVisitor visitor, BstParser.BstFunctionContext funct * second as long as the (integer) literal left on the stack by * executing the first is greater than 0. */ - private void bstWhile(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstWhile(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation while$"); + throw new BstVMException("Not enough operands on stack for operation while$"); } Object f2 = stack.pop(); Object f1 = stack.pop(); if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree))) { - throw new VMException("Expecting two functions for while$."); + throw new BstVMException("Expecting two functions for while$."); } do { @@ -885,7 +886,7 @@ private void bstWhile(BstVMVisitor visitor, BstParser.BstFunctionContext functio Object i = stack.pop(); if (!(i instanceof Integer)) { - throw new VMException("First parameter to while has to return an integer but was " + i); + throw new BstVMException("First parameter to while has to return an integer but was " + i); } if ((Integer) i <= 0) { break; @@ -903,9 +904,9 @@ private void bstWhile(BstVMVisitor visitor, BstParser.BstFunctionContext functio * characters (even without their |right_brace|s) are handled specially. If the * literal isn't a string, it complains and pushes~0. */ - private void bstWidth(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstWidth(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation width$"); + throw new BstVMException("Not enough operands on stack for operation width$"); } Object o1 = stack.pop(); @@ -923,7 +924,7 @@ private void bstWidth(BstVMVisitor visitor, BstParser.BstFunctionContext functio * (which will result in stuff being written onto the bbl file when * the buffer fills up). */ - private void bstWrite(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { + private void bstWrite(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { String s = (String) stack.pop(); bbl.append(s); } diff --git a/src/main/java/org/jabref/logic/bst/BstVMException.java b/src/main/java/org/jabref/logic/bst/BstVMException.java new file mode 100644 index 00000000000..52de2d9e9f9 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstVMException.java @@ -0,0 +1,8 @@ +package org.jabref.logic.bst; + +public class BstVMException extends RuntimeException { + + public BstVMException(String string) { + super(string); + } +} diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index bdebbc70fa1..96bbd560b66 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -14,8 +14,9 @@ import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.StandardField; -import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; class BstVMVisitor extends BstBaseVisitor { private final BstVMContext bstVMContext; @@ -23,7 +24,7 @@ class BstVMVisitor extends BstBaseVisitor { private final Stack stack = new Stack<>(); private final StringBuilder bbl; - private BstEntry currentBstEntry; + private BstEntry selectedBstEntry; public record Identifier(String name) { } @@ -35,27 +36,33 @@ public BstVMVisitor(BstVMContext bstVMContext, StringBuilder bbl) { @Override public Integer visitStringsCommand(BstParser.StringsCommandContext ctx) { - if (ctx.ids.getChildCount() > 20) { - throw new ParseCancellationException("Strings limit reached"); + if (ctx.ids.identifier().size() > 20) { + throw new BstVMException("Strings limit reached"); } - for (int i = 0; i < ctx.ids.getChildCount(); i++) { - bstVMContext.strings().put(ctx.ids.getChild(i).getText(), null); + for (BstParser.IdentifierContext identifierContext : ctx.ids.identifier()) { + bstVMContext.integers().put(identifierContext.getText(), null); } return BstVM.TRUE; } @Override public Integer visitIntegersCommand(BstParser.IntegersCommandContext ctx) { - for (int i = 0; i < ctx.ids.getChildCount(); i++) { - bstVMContext.integers().put(ctx.ids.getChild(i).getText(), 0); + for (BstParser.IdentifierContext identifierContext : ctx.ids.identifier()) { + bstVMContext.integers().put(identifierContext.getText(), 0); } return BstVM.TRUE; } @Override public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { - bstVMContext.functions().put(ctx.id.getText(), new MacroFunction(ctx.repl.getText())); + bstVMContext.functions().put(ctx.id.getText(), new BstVMFunction<>(ctx.repl.getText())); + return BstVM.TRUE; + } + + @Override + public Integer visitFunctionCommand(BstParser.FunctionCommandContext ctx) { + bstVMContext.functions().put(ctx.id.getText(), new BstVMFunction<>(ctx.function)); return BstVM.TRUE; } @@ -101,29 +108,50 @@ public Integer visitReadCommand(BstParser.ReadCommandContext ctx) { return BstVM.TRUE; } + @Override + public Integer visitStackitem(BstParser.StackitemContext ctx) { + for (ParseTree childNode : ctx.children) { + if (childNode instanceof TerminalNode token) { + switch (token.getSymbol().getType()) { + case BstParser.STRING -> { + String s = token.getText(); + stack.push(s.substring(1, s.length() - 1)); + } + case BstParser.INTEGER -> + stack.push(Integer.parseInt(token.getText().substring(1))); + case BstParser.QUOTED -> + stack.push(new Identifier(token.getText().substring(1))); + } + } else { + visit(childNode); + } + } + return BstVM.TRUE; + } + @Override public Integer visitEntryCommand(BstParser.EntryCommandContext ctx) { // ENTRY command contains 3 optionally filled identifier lists: // Fields, Integers and Strings - ParseTree entryFields = ctx.getChild(1); - for (int i = 0; i < entryFields.getChildCount(); i++) { + BstParser.IdListOptContext entryFields = ctx.idListOpt(0); + for (BstParser.IdentifierContext identifierContext : entryFields.identifier()) { for (BstEntry entry : bstVMContext.entries()) { - entry.fields.put(entryFields.getChild(i).getText(), null); + entry.fields.put(identifierContext.getText(), null); } } - ParseTree entryIntegers = ctx.getChild(2); - for (int i = 0; i < entryIntegers.getChildCount(); i++) { + BstParser.IdListOptContext entryIntegers = ctx.idListOpt(1); + for (BstParser.IdentifierContext identifierContext : entryIntegers.identifier()) { for (BstEntry entry : bstVMContext.entries()) { - entry.localIntegers.put(entryIntegers.getChild(i).getText(), 0); + entry.localIntegers.put(identifierContext.getText(), 0); } } - ParseTree entryStrings = ctx.getChild(3); - for (int i = 0; i < entryStrings.getChildCount(); i++) { + BstParser.IdListOptContext entryStrings = ctx.idListOpt(2); + for (BstParser.IdentifierContext identifierContext : entryStrings.identifier()) { for (BstEntry entry : bstVMContext.entries()) { - entry.localStrings.put(entryStrings.getChild(i).getText(), null); + entry.localStrings.put(identifierContext.getText(), null); } } @@ -142,20 +170,60 @@ public Integer visitSortCommand(BstParser.SortCommandContext ctx) { @Override public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { - bstVMContext.functions().get(ctx.getChild(0).getText()).execute(this, ctx, currentBstEntry); + bstVMContext.functions().get(ctx.getChild(0).getText()).execute(this, ctx, selectedBstEntry); return BstVM.TRUE; } - private class MacroFunction implements BstFunctions.BstFunction { - private final String replacement; + @Override + public Integer visitIdentifier(BstParser.IdentifierContext ctx) { + String name = ctx.IDENTIFIER().getText(); + + if (selectedBstEntry != null) { + if (selectedBstEntry.fields.containsKey(name)) { + stack.push(selectedBstEntry.fields.get(name)); + return BstVM.TRUE; + } + if (selectedBstEntry.localStrings.containsKey(name)) { + stack.push(selectedBstEntry.localStrings.get(name)); + return BstVM.TRUE; + } + if (selectedBstEntry.localIntegers.containsKey(name)) { + stack.push(selectedBstEntry.localIntegers.get(name)); + return BstVM.TRUE; + } + } + if (bstVMContext.strings().containsKey(name)) { + stack.push(bstVMContext.strings().get(name)); + return BstVM.TRUE; + } + if (bstVMContext.integers().containsKey(name)) { + stack.push(bstVMContext.integers().get(name)); + return BstVM.TRUE; + } + + if (bstVMContext.functions().containsKey(name)) { + // OK to have a null context + bstVMContext.functions().get(name).execute(this, ctx, selectedBstEntry); + return BstVM.TRUE; + } + + throw new BstVMException("No matching identifier found: " + name); + } + + private class BstVMFunction implements BstFunctions.BstFunction { + private final T expression; - MacroFunction(String replacement) { - this.replacement = replacement; + BstVMFunction(T expression) { + this.expression = expression; } @Override - public void execute(BstVMVisitor visitor, BstParser.BstFunctionContext functionContext) { - stack.push(replacement); + public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + if (parserRuleContext instanceof BstParser.IdentifierContext) { + stack.push(expression); + } else { + visitor.visit(parserRuleContext); + } } } } diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java index 6522bb441dc..137f8288c7c 100644 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ b/src/main/java/org/jabref/logic/bst/VM.java @@ -204,13 +204,13 @@ private void initBuildInFunctions() { */ private void bstIsGreaterThan(Object context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation >"); + throw new BstVMException("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 >"); + throw new BstVMException("Can only compare two integers with >"); } stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? VM.TRUE : VM.FALSE); @@ -223,13 +223,13 @@ private void bstIsGreaterThan(Object context) { */ private void bstIsLowerThan(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation <"); + throw new BstVMException("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 <"); + throw new BstVMException("Can only compare two integers with <"); } stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? VM.TRUE : VM.FALSE); @@ -241,7 +241,7 @@ private void bstIsLowerThan(BstEntry context) { */ private void bstEquals(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation ="); + throw new BstVMException("Not enough operands on stack for operation ="); } Object o1 = stack.pop(); Object o2 = stack.pop(); @@ -264,13 +264,13 @@ private void bstEquals(BstEntry context) { */ private void bstAdd(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation +"); + throw new BstVMException("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 +"); + throw new BstVMException("Can only compare two integers with +"); } stack.push((Integer) o1 + (Integer) o2); @@ -282,13 +282,13 @@ private void bstAdd(BstEntry context) { */ private void bstSubtract(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation -"); + throw new BstVMException("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 -"); + throw new BstVMException("Can only subtract two integers with -"); } stack.push((Integer) o1 - (Integer) o2); @@ -301,7 +301,7 @@ private void bstSubtract(BstEntry context) { */ private void bstConcat(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation *"); + throw new BstVMException("Not enough operands on stack for operation *"); } Object o2 = stack.pop(); Object o1 = stack.pop(); @@ -316,7 +316,7 @@ private void bstConcat(BstEntry context) { if (!((o1 instanceof String) && (o2 instanceof String))) { LOGGER.error("o1: {} ({})", o1, o1.getClass()); LOGGER.error("o2: {} ({})", o2, o2.getClass()); - throw new VMException("Can only concatenate two String with *"); + throw new BstVMException("Can only concatenate two String with *"); } stack.push(o1.toString() + o2); @@ -328,7 +328,7 @@ private void bstConcat(BstEntry context) { */ private void bstAssign(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Invalid call to operation :="); + throw new BstVMException("Invalid call to operation :="); } Object o1 = stack.pop(); Object o2 = stack.pop(); @@ -337,7 +337,7 @@ private void bstAssign(BstEntry context) { private boolean doBstAssign(BstEntry context, Object o1, Object o2) { if (!(o1 instanceof VM.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { - throw new VMException("Invalid parameters"); + throw new BstVMException("Invalid parameters"); } String name = ((VM.Identifier) o1).getName(); @@ -374,12 +374,12 @@ private boolean doBstAssign(BstEntry context, Object o1, Object o2) { */ private void bstAddPeriod(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation add.period$"); + throw new BstVMException("Not enough operands on stack for operation add.period$"); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new VMException("Can only add a period to a string for add.period$"); + throw new BstVMException("Can only add a period to a string for add.period$"); } Matcher m = ADD_PERIOD_PATTERN.matcher(s); @@ -410,7 +410,7 @@ private void bstAddPeriod(BstEntry context) { */ private void bstCallType(BstEntry context) { if (context == null) { - throw new VMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); + throw new BstVMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); } VM.this.execute(context.entry.getType().getName(), context); } @@ -436,17 +436,17 @@ private void bstCallType(BstEntry context) { */ private void bstChangeCase(BstEntry value) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation change.case$"); + throw new BstVMException("Not enough operands on stack for operation change.case$"); } Object o1 = stack.pop(); if (!((o1 instanceof String) && (((String) o1).length() == 1))) { - throw new VMException("A format string of length 1 is needed for change.case$"); + throw new BstVMException("A format string of length 1 is needed for change.case$"); } Object o2 = stack.pop(); if (!(o2 instanceof String s)) { - throw new VMException("A string is needed as second parameter for change.case$"); + throw new BstVMException("A string is needed as second parameter for change.case$"); } char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); @@ -461,12 +461,12 @@ private void bstChangeCase(BstEntry value) { */ private void bstChrToInt(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation chr.to.int$"); + throw new BstVMException("Not enough operands on stack for operation chr.to.int$"); } Object o1 = stack.pop(); if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { - throw new VMException("Can only perform chr.to.int$ on string with length 1"); + throw new BstVMException("Can only perform chr.to.int$ on string with length 1"); } stack.push((int) s.charAt(0)); @@ -478,7 +478,7 @@ private void bstChrToInt(BstEntry context) { */ private void bstCite(BstEntry context) { if (context == null) { - throw new VMException("Must have an entry to cite$"); + throw new BstVMException("Must have an entry to cite$"); } stack.push(context.entry.getCitationKey().orElse(null)); } @@ -488,7 +488,7 @@ private void bstCite(BstEntry context) { */ private void bstDuplicate(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation duplicate$"); + throw new BstVMException("Not enough operands on stack for operation duplicate$"); } Object o1 = stack.pop(); @@ -503,7 +503,7 @@ private void bstDuplicate(BstEntry context) { */ private void bstEmpty(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation empty$"); + throw new BstVMException("Not enough operands on stack for operation empty$"); } Object o1 = stack.pop(); @@ -513,7 +513,7 @@ private void bstEmpty(BstEntry context) { } if (!(o1 instanceof String s)) { - throw new VMException("Operand does not match function empty$"); + throw new BstVMException("Operand does not match function empty$"); } stack.push("".equals(s.trim()) ? VM.TRUE : VM.FALSE); @@ -532,7 +532,7 @@ private void bstEmpty(BstEntry context) { */ private void bstFormatName(BstEntry context) { if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation format.name$"); + throw new BstVMException("Not enough operands on stack for operation format.name$"); } Object o1 = stack.pop(); Object o2 = stack.pop(); @@ -553,7 +553,7 @@ private void bstFormatName(BstEntry context) { } else { AuthorList a = AuthorList.parse(names); if (name > a.getNumberOfAuthors()) { - throw new VMException("Author Out of Bounds. Number " + name + " invalid for " + names); + throw new BstVMException("Author Out of Bounds. Number " + name + " invalid for " + names); } Author author = a.getAuthor(name - 1); @@ -569,7 +569,7 @@ private void bstFormatName(BstEntry context) { */ private void bstIf(BstEntry context) { if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation ="); + throw new BstVMException("Not enough operands on stack for operation ="); } Object f1 = stack.pop(); Object f2 = stack.pop(); @@ -577,7 +577,7 @@ private void bstIf(BstEntry context) { if (!((f1 instanceof VM.Identifier) || (f1 instanceof Tree)) && ((f2 instanceof VM.Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { - throw new VMException("Expecting two functions and an integer for if$."); + throw new BstVMException("Expecting two functions and an integer for if$."); } if ((Integer) i > 0) { @@ -594,12 +594,12 @@ private void bstIf(BstEntry context) { */ private void bstIntToChr(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation int.to.chr$"); + throw new BstVMException("Not enough operands on stack for operation int.to.chr$"); } Object o1 = stack.pop(); if (!(o1 instanceof Integer i)) { - throw new VMException("Can only perform operation int.to.chr$ on an Integer"); + throw new BstVMException("Can only perform operation int.to.chr$ on an Integer"); } stack.push(String.valueOf((char) i.intValue())); @@ -611,12 +611,12 @@ private void bstIntToChr(BstEntry context) { */ private void bstIntToStr(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation int.to.str$"); + throw new BstVMException("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$"); + throw new BstVMException("Can only transform an integer to an string using int.to.str$"); } stack.push(o1.toString()); @@ -628,7 +628,7 @@ private void bstIntToStr(BstEntry context) { */ private void bstMissing(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation missing$"); + throw new BstVMException("Not enough operands on stack for operation missing$"); } Object o1 = stack.pop(); @@ -665,12 +665,12 @@ private void bstNewLine(BstEntry context) { */ private void bstNumNames(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation num.names$"); + throw new BstVMException("Not enough operands on stack for operation num.names$"); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new VMException("Need a string at the top of the stack for num.names$"); + throw new BstVMException("Need a string at the top of the stack for num.names$"); } stack.push(AuthorList.parse(s).getNumberOfAuthors()); @@ -703,7 +703,7 @@ private void bstPreamble(BstEntry context) { */ private void bstPurify(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation purify$"); + throw new BstVMException("Not enough operands on stack for operation purify$"); } Object o1 = stack.pop(); @@ -751,14 +751,14 @@ private void bstStack(BstEntry context) { */ private void bstSubstring(BstEntry context) { if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation substring$"); + throw new BstVMException("Not enough operands on stack for operation substring$"); } Object o1 = stack.pop(); Object o2 = stack.pop(); Object o3 = stack.pop(); if (!((o1 instanceof Integer len) && (o2 instanceof Integer start) && (o3 instanceof String s))) { - throw new VMException("Expecting two integers and a string for substring$"); + throw new BstVMException("Expecting two integers and a string for substring$"); } int lenI = len; @@ -793,7 +793,7 @@ private void bstSubstring(BstEntry context) { */ private void bstSwap(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation swap$"); + throw new BstVMException("Not enough operands on stack for operation swap$"); } Object f1 = stack.pop(); Object f2 = stack.pop(); @@ -815,12 +815,12 @@ private void bstSwap(BstEntry context) { */ private void bstTextLength(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation text.length$"); + throw new BstVMException("Not enough operands on stack for operation text.length$"); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new VMException("Can only perform operation on a string text.length$"); + throw new BstVMException("Can only perform operation on a string text.length$"); } char[] c = s.toCharArray(); @@ -871,7 +871,7 @@ private void bstTextLength(BstEntry context) { */ private void bstTextPrefix(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation text.prefix$"); + throw new BstVMException("Not enough operands on stack for operation text.prefix$"); } Object o1 = stack.pop(); @@ -904,7 +904,7 @@ private void bstTop(BstEntry context) { */ private void bstType(BstEntry context) { if (context == null) { - throw new VMException("type$ need a context."); + throw new BstVMException("type$ need a context."); } stack.push(context.entry.getType().getName()); @@ -926,14 +926,14 @@ private void bstWarning(BstEntry context) { */ private void bstWhile(BstEntry context) { if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation while$"); + throw new BstVMException("Not enough operands on stack for operation while$"); } Object f2 = stack.pop(); Object f1 = stack.pop(); if (!((f1 instanceof VM.Identifier) || (f1 instanceof Tree)) && ((f2 instanceof VM.Identifier) || (f2 instanceof Tree))) { - throw new VMException("Expecting two functions for while$."); + throw new BstVMException("Expecting two functions for while$."); } do { @@ -941,7 +941,7 @@ private void bstWhile(BstEntry context) { Object i = stack.pop(); if (!(i instanceof Integer)) { - throw new VMException("First parameter to while has to return an integer but was " + i); + throw new BstVMException("First parameter to while has to return an integer but was " + i); } if ((Integer) i <= 0) { break; @@ -961,7 +961,7 @@ private void bstWhile(BstEntry context) { */ private void bstWidth(BstEntry context) { if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation width$"); + throw new BstVMException("Not enough operands on stack for operation width$"); } Object o1 = stack.pop(); @@ -1239,7 +1239,8 @@ public void execute(BstEntry context) { default -> VM.this.execute(c.getText(), context); } - } catch (VMException e) { + } catch ( + BstVMException e) { if (path == null) { LOGGER.error("ERROR " + e.getMessage() + " (" + c.getLine() + ")"); } else { @@ -1286,7 +1287,7 @@ private void execute(String name, BstEntry context) { return; } - throw new VMException("No matching identifier found: " + name); + throw new BstVMException("No matching identifier found: " + name); } private void function(Tree child) { diff --git a/src/main/java/org/jabref/logic/bst/VMException.java b/src/main/java/org/jabref/logic/bst/VMException.java deleted file mode 100644 index f46c5cd5277..00000000000 --- a/src/main/java/org/jabref/logic/bst/VMException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.jabref.logic.bst; - -public class VMException extends RuntimeException { - - public VMException(String string) { - super(string); - } -} diff --git a/src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java b/src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java index fef87ab60e5..3e5954b7287 100644 --- a/src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java +++ b/src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java @@ -5,7 +5,7 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.jabref.logic.bst.VMException; +import org.jabref.logic.bst.BstVMException; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; @@ -111,7 +111,7 @@ public static String formatName(Author author, String format) { case 'j' -> author.getJr(); default -> - throw new VMException("Internal error"); + throw new BstVMException("Internal error"); }; if (tokenS.isEmpty()) { diff --git a/src/test/java/org/jabref/logic/bst/TestVM.java b/src/test/java/org/jabref/logic/bst/TestVM.java index 7a662ed4abc..244dbed568f 100644 --- a/src/test/java/org/jabref/logic/bst/TestVM.java +++ b/src/test/java/org/jabref/logic/bst/TestVM.java @@ -162,7 +162,7 @@ public void testVMArithmetic() throws RecognitionException { @Test public void testVMArithmetic2() throws RecognitionException { VM vm = new VM("FUNCTION {test} { " + "#1 \"HELLO\" + #5 #2 - }" + "EXECUTE {test}"); - assertThrows(VMException.class, () -> vm.run(Collections.emptyList())); + assertThrows(BstVMException.class, () -> vm.run(Collections.emptyList())); } @Test From 9d076e27a11cbb819283d53134c9aa5951c86132 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Fri, 10 Jun 2022 21:07:15 +0200 Subject: [PATCH 09/39] reorder --- .../org/jabref/logic/bst/BstVMVisitor.java | 64 +++++++++---------- src/main/java/org/jabref/logic/bst/VM.java | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 96bbd560b66..13ec3158da2 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -24,7 +24,7 @@ class BstVMVisitor extends BstBaseVisitor { private final Stack stack = new Stack<>(); private final StringBuilder bbl; - private BstEntry selectedBstEntry; + private BstEntry selectedBstEntry = null; public record Identifier(String name) { } @@ -55,14 +55,14 @@ public Integer visitIntegersCommand(BstParser.IntegersCommandContext ctx) { } @Override - public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { - bstVMContext.functions().put(ctx.id.getText(), new BstVMFunction<>(ctx.repl.getText())); + public Integer visitFunctionCommand(BstParser.FunctionCommandContext ctx) { + bstVMContext.functions().put(ctx.id.getText(), new BstVMFunction<>(ctx.function)); return BstVM.TRUE; } @Override - public Integer visitFunctionCommand(BstParser.FunctionCommandContext ctx) { - bstVMContext.functions().put(ctx.id.getText(), new BstVMFunction<>(ctx.function)); + public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { + bstVMContext.functions().put(ctx.id.getText(), new BstVMFunction<>(ctx.repl.getText())); return BstVM.TRUE; } @@ -108,27 +108,6 @@ public Integer visitReadCommand(BstParser.ReadCommandContext ctx) { return BstVM.TRUE; } - @Override - public Integer visitStackitem(BstParser.StackitemContext ctx) { - for (ParseTree childNode : ctx.children) { - if (childNode instanceof TerminalNode token) { - switch (token.getSymbol().getType()) { - case BstParser.STRING -> { - String s = token.getText(); - stack.push(s.substring(1, s.length() - 1)); - } - case BstParser.INTEGER -> - stack.push(Integer.parseInt(token.getText().substring(1))); - case BstParser.QUOTED -> - stack.push(new Identifier(token.getText().substring(1))); - } - } else { - visit(childNode); - } - } - return BstVM.TRUE; - } - @Override public Integer visitEntryCommand(BstParser.EntryCommandContext ctx) { // ENTRY command contains 3 optionally filled identifier lists: @@ -168,12 +147,6 @@ public Integer visitSortCommand(BstParser.SortCommandContext ctx) { return BstVM.TRUE; } - @Override - public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { - bstVMContext.functions().get(ctx.getChild(0).getText()).execute(this, ctx, selectedBstEntry); - return BstVM.TRUE; - } - @Override public Integer visitIdentifier(BstParser.IdentifierContext ctx) { String name = ctx.IDENTIFIER().getText(); @@ -210,6 +183,33 @@ public Integer visitIdentifier(BstParser.IdentifierContext ctx) { throw new BstVMException("No matching identifier found: " + name); } + @Override + public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { + bstVMContext.functions().get(ctx.getChild(0).getText()).execute(this, ctx, selectedBstEntry); + return BstVM.TRUE; + } + + @Override + public Integer visitStackitem(BstParser.StackitemContext ctx) { + for (ParseTree childNode : ctx.children) { + if (childNode instanceof TerminalNode token) { + switch (token.getSymbol().getType()) { + case BstParser.STRING -> { + String s = token.getText(); + stack.push(s.substring(1, s.length() - 1)); + } + case BstParser.INTEGER -> + stack.push(Integer.parseInt(token.getText().substring(1))); + case BstParser.QUOTED -> + stack.push(new Identifier(token.getText().substring(1))); + } + } else { + visit(childNode); + } + } + return BstVM.TRUE; + } + private class BstVMFunction implements BstFunctions.BstFunction { private final T expression; diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java index 137f8288c7c..920730940b2 100644 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ b/src/main/java/org/jabref/logic/bst/VM.java @@ -154,7 +154,7 @@ private static ParseTree charStream2CommonTree(CharStream query) throws Recognit parser.removeErrorListeners(); // no infos on file system parser.addErrorListener(ThrowingErrorListener.INSTANCE); parser.setErrorHandler(new BailErrorStrategy()); // ParseCancelationException on parse errors - return parser.start(); + return parser.bstFile(); } private void initBuildInFunctions() { From 3e80d205813aea7bfeabd3ed97fb72d75238ec98 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Fri, 10 Jun 2022 21:32:37 +0200 Subject: [PATCH 10/39] execute, iterate, reverse --- src/main/java/org/jabref/logic/bst/BstVM.java | 2 +- .../org/jabref/logic/bst/BstVMVisitor.java | 35 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java index cbf1bb52459..afea571804d 100644 --- a/src/main/java/org/jabref/logic/bst/BstVM.java +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -27,7 +27,7 @@ * [x] migrate runner/visitor * [x] migrate functions * [x] context / entry needs to be set as a field for the visitor to iterate forwards or backwards through all the entries - * [ ] migrate execute / forward / backward / stack / quote / etc. + * [x] migrate execute / forward / backward / stack / quote / etc. * [ ] remove old VM * [ ] fix tests * [ ] create new tests diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 13ec3158da2..c240cb1d960 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -2,6 +2,7 @@ import java.util.Comparator; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Stack; @@ -20,10 +21,9 @@ class BstVMVisitor extends BstBaseVisitor { private final BstVMContext bstVMContext; - - private final Stack stack = new Stack<>(); private final StringBuilder bbl; + private final Stack stack = new Stack<>(); private BstEntry selectedBstEntry = null; public record Identifier(String name) { @@ -108,6 +108,35 @@ public Integer visitReadCommand(BstParser.ReadCommandContext ctx) { return BstVM.TRUE; } + @Override + public Integer visitExecuteCommand(BstParser.ExecuteCommandContext ctx) { + this.selectedBstEntry = null; + visit(ctx.bstFunction()); + + return BstVM.TRUE; + } + + @Override + public Integer visitIterateCommand(BstParser.IterateCommandContext ctx) { + for (BstEntry entry : bstVMContext.entries()) { + this.selectedBstEntry = entry; + visit(ctx.bstFunction()); + } + + return BstVM.TRUE; + } + + @Override + public Integer visitReverseCommand(BstParser.ReverseCommandContext ctx) { + ListIterator i = bstVMContext.entries().listIterator(bstVMContext.entries().size()); + while (i.hasPrevious()) { + this.selectedBstEntry = i.previous(); + visit(ctx.bstFunction()); + } + + return BstVM.TRUE; + } + @Override public Integer visitEntryCommand(BstParser.EntryCommandContext ctx) { // ENTRY command contains 3 optionally filled identifier lists: @@ -220,7 +249,7 @@ private class BstVMFunction implements BstFunctions.BstFunction { @Override public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (parserRuleContext instanceof BstParser.IdentifierContext) { - stack.push(expression); + stack.push(expression); // Macro } else { visitor.visit(parserRuleContext); } From 377ad26a52e0e5a2fd84a208e28ed286972d7691 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Fri, 10 Jun 2022 23:16:52 +0200 Subject: [PATCH 11/39] removed old vm, prepared test architecture --- .../org/jabref/logic/bst/BstFunctions.java | 3 +- .../jabref/logic/bst/BstPreviewLayout.java | 6 +- src/main/java/org/jabref/logic/bst/BstVM.java | 20 +- .../org/jabref/logic/bst/BstVMContext.java | 4 +- .../org/jabref/logic/bst/BstVMVisitor.java | 20 +- src/main/java/org/jabref/logic/bst/VM.java | 1357 ----------------- .../logic/bst/{TestVM.java => TestBstVM.java} | 289 ++-- 7 files changed, 189 insertions(+), 1510 deletions(-) delete mode 100644 src/main/java/org/jabref/logic/bst/VM.java rename src/test/java/org/jabref/logic/bst/{TestVM.java => TestBstVM.java} (69%) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index a0dfe3cb41a..c6a929df4d2 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -46,14 +46,13 @@ default void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, } public BstFunctions(BstVMContext bstVMContext, - Stack stack, StringBuilder bbl) { this.strings = bstVMContext.strings(); this.integers = bstVMContext.integers(); this.functions = bstVMContext.functions(); this.preamble = bstVMContext.bibDatabase().getPreamble().orElse(""); + this.stack = bstVMContext.stack(); - this.stack = stack; this.bbl = bbl; } diff --git a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java index eb0c623f053..a002d388e60 100644 --- a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java +++ b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java @@ -23,7 +23,7 @@ public class BstPreviewLayout implements PreviewLayout { private final String name; - private VM vm; + private BstVM bstVM; private String error; public BstPreviewLayout(Path path) { @@ -34,7 +34,7 @@ public BstPreviewLayout(Path path) { return; } try { - vm = new VM(path); + bstVM = new BstVM(path); } catch (Exception e) { LOGGER.error("Could not read {}.", path.toAbsolutePath(), e); error = Localization.lang("Error opening file '%0'.", path.toString()); @@ -49,7 +49,7 @@ public String generatePreview(BibEntry originalEntry, BibDatabaseContext databas // ensure that the entry is of BibTeX format (and do not modify the original entry) BibEntry entry = (BibEntry) originalEntry.clone(); new ConvertToBibtexCleanup().cleanup(entry); - String result = vm.run(List.of(entry)); + String result = bstVM.render(List.of(entry)); // Remove all comments result = result.replaceAll("%.*", ""); // Remove all LaTeX comments diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java index afea571804d..4c11de5f1f9 100644 --- a/src/main/java/org/jabref/logic/bst/BstVM.java +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -6,7 +6,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.Stack; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; @@ -28,7 +27,7 @@ * [x] migrate functions * [x] context / entry needs to be set as a field for the visitor to iterate forwards or backwards through all the entries * [x] migrate execute / forward / backward / stack / quote / etc. - * [ ] remove old VM + * [x] remove old VM * [ ] fix tests * [ ] create new tests * [ ] clean up bstFunctions @@ -39,7 +38,9 @@ public class BstVM { protected static final Integer FALSE = 0; protected static final Integer TRUE = 1; - private final ParseTree tree; + protected final ParseTree tree; + + protected BstVMContext latestContext; // for testing public BstVM(Path path) throws RecognitionException, IOException { this(CharStreams.fromPath(path)); @@ -49,7 +50,7 @@ public BstVM(String s) throws RecognitionException { this(CharStreams.fromString(s)); } - private BstVM(CharStream bst) throws RecognitionException { + protected BstVM(CharStream bst) throws RecognitionException { this(charStream2CommonTree(bst)); } @@ -60,7 +61,7 @@ private BstVM(ParseTree tree) { private static ParseTree charStream2CommonTree(CharStream query) { BstLexer lexer = new BstLexer(query); lexer.removeErrorListeners(); - lexer.addErrorListener(VM.ThrowingErrorListener.INSTANCE); + lexer.addErrorListener(ThrowingErrorListener.INSTANCE); BstParser parser = new BstParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); parser.addErrorListener(ThrowingErrorListener.INSTANCE); @@ -84,17 +85,22 @@ public String render(Collection bibEntries, BibDatabase bibDatabase) { } StringBuilder bbl = new StringBuilder(); - Stack stack = new Stack<>(); BstVMContext bstVMContext = new BstVMContext(entries, bibDatabase); - bstVMContext.functions().putAll(new BstFunctions(bstVMContext, stack, bbl).getBuiltInFunctions()); + bstVMContext.functions().putAll(new BstFunctions(bstVMContext, bbl).getBuiltInFunctions()); BstVMVisitor bstVMVisitor = new BstVMVisitor(bstVMContext, bbl); bstVMVisitor.visit(tree); + latestContext = bstVMContext; + return bbl.toString(); } + public String render(Collection bibEntries) { + return render(bibEntries, null); + } + private static class ThrowingErrorListener extends BaseErrorListener { public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); diff --git a/src/main/java/org/jabref/logic/bst/BstVMContext.java b/src/main/java/org/jabref/logic/bst/BstVMContext.java index 869096de107..0f0db228641 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMContext.java +++ b/src/main/java/org/jabref/logic/bst/BstVMContext.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Stack; import org.jabref.model.database.BibDatabase; @@ -10,10 +11,11 @@ public record BstVMContext(List entries, Map strings, Map integers, Map functions, + Stack stack, BibDatabase bibDatabase) { public BstVMContext(List entries, BibDatabase bibDatabase) { - this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), bibDatabase); + this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), new Stack<>(), bibDatabase); this.integers.put("entry.max$", Integer.MAX_VALUE); this.integers.put("global.max$", Integer.MAX_VALUE); diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index c240cb1d960..ec88d62cf65 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; -import java.util.Stack; import org.jabref.logic.bibtex.FieldContentFormatterPreferences; import org.jabref.logic.bibtex.FieldWriter; @@ -23,7 +22,6 @@ class BstVMVisitor extends BstBaseVisitor { private final BstVMContext bstVMContext; private final StringBuilder bbl; - private final Stack stack = new Stack<>(); private BstEntry selectedBstEntry = null; public record Identifier(String name) { @@ -182,24 +180,24 @@ public Integer visitIdentifier(BstParser.IdentifierContext ctx) { if (selectedBstEntry != null) { if (selectedBstEntry.fields.containsKey(name)) { - stack.push(selectedBstEntry.fields.get(name)); + bstVMContext.stack().push(selectedBstEntry.fields.get(name)); return BstVM.TRUE; } if (selectedBstEntry.localStrings.containsKey(name)) { - stack.push(selectedBstEntry.localStrings.get(name)); + bstVMContext.stack().push(selectedBstEntry.localStrings.get(name)); return BstVM.TRUE; } if (selectedBstEntry.localIntegers.containsKey(name)) { - stack.push(selectedBstEntry.localIntegers.get(name)); + bstVMContext.stack().push(selectedBstEntry.localIntegers.get(name)); return BstVM.TRUE; } } if (bstVMContext.strings().containsKey(name)) { - stack.push(bstVMContext.strings().get(name)); + bstVMContext.stack().push(bstVMContext.strings().get(name)); return BstVM.TRUE; } if (bstVMContext.integers().containsKey(name)) { - stack.push(bstVMContext.integers().get(name)); + bstVMContext.stack().push(bstVMContext.integers().get(name)); return BstVM.TRUE; } @@ -225,12 +223,12 @@ public Integer visitStackitem(BstParser.StackitemContext ctx) { switch (token.getSymbol().getType()) { case BstParser.STRING -> { String s = token.getText(); - stack.push(s.substring(1, s.length() - 1)); + bstVMContext.stack().push(s.substring(1, s.length() - 1)); } case BstParser.INTEGER -> - stack.push(Integer.parseInt(token.getText().substring(1))); + bstVMContext.stack().push(Integer.parseInt(token.getText().substring(1))); case BstParser.QUOTED -> - stack.push(new Identifier(token.getText().substring(1))); + bstVMContext.stack().push(new Identifier(token.getText().substring(1))); } } else { visit(childNode); @@ -249,7 +247,7 @@ private class BstVMFunction implements BstFunctions.BstFunction { @Override public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (parserRuleContext instanceof BstParser.IdentifierContext) { - stack.push(expression); // Macro + bstVMContext.stack().push(expression); // Macro } else { visitor.visit(parserRuleContext); } diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java deleted file mode 100644 index 920730940b2..00000000000 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ /dev/null @@ -1,1357 +0,0 @@ -package org.jabref.logic.bst; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.jabref.logic.bibtex.FieldContentFormatterPreferences; -import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.logic.bibtex.FieldWriterPreferences; -import org.jabref.logic.bibtex.InvalidFieldValueException; -import org.jabref.logic.bst.util.BibtexCaseChanger; -import org.jabref.logic.bst.util.BibtexNameFormatter; -import org.jabref.logic.bst.util.BibtexPurify; -import org.jabref.logic.bst.util.BibtexTextPrefix; -import org.jabref.logic.bst.util.BibtexWidth; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.entry.Author; -import org.jabref.model.entry.AuthorList; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.Month; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.FieldFactory; -import org.jabref.model.entry.field.StandardField; - -import org.antlr.v4.runtime.BailErrorStrategy; -import org.antlr.v4.runtime.BaseErrorListener; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.RecognitionException; -import org.antlr.v4.runtime.Recognizer; -import org.antlr.v4.runtime.misc.ParseCancellationException; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.Tree; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A BibTeX Virtual machine that can execute .bst files. - *

- * Documentation can be found in the original bibtex distribution: - *

- * https://www.ctan.org/pkg/bibtex - */ -public class VM { - - public static final Integer FALSE = 0; - - public static final Integer TRUE = 1; - - private static final Pattern ADD_PERIOD_PATTERN = Pattern.compile("([^\\.\\?\\!\\}\\s])(\\}|\\s)*$"); - - private static final Logger LOGGER = LoggerFactory.getLogger(VM.class); - - private List entries; - - private Map strings = new HashMap<>(); - - private Map integers = new HashMap<>(); - - private Map functions = new HashMap<>(); - - private Stack stack = new Stack<>(); - - private final Map buildInFunctions = new HashMap<>(37); - - private Path path; - - private final ParseTree tree; - - private StringBuilder bbl; - - private String preamble = ""; - - private int bstWarning = 1; - - public static class ThrowingErrorListener extends BaseErrorListener { - - public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); - - @Override - public void syntaxError(Recognizer recognizer, Object offendingSymbol, - int line, int charPositionInLine, String msg, RecognitionException e) - throws ParseCancellationException { - throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg); - } - } - - public static class Identifier { - - public final String name; - - public Identifier(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class Variable { - - public final String name; - - public Variable(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - @FunctionalInterface - public interface BstFunction { - void execute(BstEntry context); - } - - public VM(Path path) throws RecognitionException, IOException { - this(CharStreams.fromPath(path)); - this.path = path; - } - - public VM(String s) throws RecognitionException { - this(CharStreams.fromString(s)); - } - - private VM(CharStream bst) throws RecognitionException { - this(VM.charStream2CommonTree(bst)); - } - - private VM(ParseTree tree) { - this.tree = tree; - initBuildInFunctions(); - } - - private static ParseTree charStream2CommonTree(CharStream query) throws RecognitionException { - BstLexer lexer = new BstLexer(query); - lexer.removeErrorListeners(); // no infos on file system - lexer.addErrorListener(ThrowingErrorListener.INSTANCE); - BstParser parser = new BstParser(new CommonTokenStream(lexer)); - parser.removeErrorListeners(); // no infos on file system - parser.addErrorListener(ThrowingErrorListener.INSTANCE); - parser.setErrorHandler(new BailErrorStrategy()); // ParseCancelationException on parse errors - return parser.bstFile(); - } - - private void initBuildInFunctions() { - buildInFunctions.put(">", this::bstIsGreaterThan); - buildInFunctions.put("<", this::bstIsLowerThan); - buildInFunctions.put("=", this::bstEquals); - buildInFunctions.put("+", this::bstAdd); - buildInFunctions.put("-", this::bstSubtract); - buildInFunctions.put("*", this::bstConcat); - buildInFunctions.put(":=", this::bstAssign); - buildInFunctions.put("add.period$", this::bstAddPeriod); - buildInFunctions.put("call.type$", this::bstCallType); - buildInFunctions.put("change.case$", this::bstChangeCase); - buildInFunctions.put("chr.to.int$", this::bstChrToInt); - buildInFunctions.put("cite$", this::bstCite); - buildInFunctions.put("duplicate$", this::bstDuplicate); - buildInFunctions.put("empty$", this::bstEmpty); - buildInFunctions.put("format.name$", this::bstFormatName); - buildInFunctions.put("if$", this::bstIf); - buildInFunctions.put("int.to.chr$", this::bstIntToChr); - buildInFunctions.put("int.to.str$", this::bstIntToStr); - buildInFunctions.put("missing$", this::bstMissing); - buildInFunctions.put("newline$", this::bstNewLine); - buildInFunctions.put("num.names$", this::bstNumNames); - buildInFunctions.put("pop$", this::bstPop); - buildInFunctions.put("preamble$", this::bstPreamble); - buildInFunctions.put("purify$", this::bstPurify); - buildInFunctions.put("quote$", this::bstQuote); - buildInFunctions.put("skip$", this::bstSkip); - buildInFunctions.put("stack$", this::bstStack); - buildInFunctions.put("substring$", this::bstSubstring); - buildInFunctions.put("swap$", this::bstSwap); - buildInFunctions.put("text.length$", this::bstTextLength); - buildInFunctions.put("text.prefix$", this::bstTextPrefix); - buildInFunctions.put("top$", this::bstTop); - buildInFunctions.put("type$", this::bstType); - buildInFunctions.put("warning$", this::bstWarning); - buildInFunctions.put("while$", this::bstWhile); - buildInFunctions.put("width$", this::bstWidth); - buildInFunctions.put("write$", this::bstWrite); - } - - /** - * Pops the top two (integer) literals, compares them, and pushes - * the integer 1 if the second is greater than the first, 0 - * otherwise. - */ - private void bstIsGreaterThan(Object context) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation >"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new BstVMException("Can only compare two integers with >"); - } - - stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? VM.TRUE : VM.FALSE); - } - - /** - * Pops the top two (integer) literals, compares them, and pushes - * the integer 1 if the second is lower than the first, 0 - * otherwise. - */ - private void bstIsLowerThan(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation <"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new BstVMException("Can only compare two integers with <"); - } - - stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? VM.TRUE : VM.FALSE); - } - - /** - * Pops the top two (both integer or both string) literals, compares - * them, and pushes the integer 1 if they're equal, 0 otherwise. - */ - private void bstEquals(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("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 == null) && (o2 == null)) { - stack.push(VM.TRUE); - return; - } - - stack.push(o1.equals(o2) ? VM.TRUE : VM.FALSE); - } - - /** - * Pops the top two (integer) literals and pushes their sum. - */ - private void bstAdd(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation +"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new BstVMException("Can only compare two integers with +"); - } - - stack.push((Integer) o1 + (Integer) o2); - } - - /** - * Pops the top two (integer) literals and pushes their difference - * (the first subtracted from the second). - */ - private void bstSubtract(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation -"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new BstVMException("Can only subtract two integers with -"); - } - - stack.push((Integer) o1 - (Integer) o2); - } - - /** - * Pops the top two (string) literals, concatenates them (in reverse - * order, that is, the order in which pushed), and pushes the - * resulting string. - */ - private void bstConcat(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation *"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (o1 == null) { - o1 = ""; - } - if (o2 == null) { - o2 = ""; - } - - if (!((o1 instanceof String) && (o2 instanceof String))) { - LOGGER.error("o1: {} ({})", o1, o1.getClass()); - LOGGER.error("o2: {} ({})", o2, o2.getClass()); - throw new BstVMException("Can only concatenate two String with *"); - } - - stack.push(o1.toString() + o2); - } - - /** - * Pops the top two literals and assigns to the first (which must be - * a global or entry variable) the value of the second. - */ - private void bstAssign(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("Invalid call to operation :="); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - doBstAssign(context, o1, o2); - } - - private boolean doBstAssign(BstEntry context, Object o1, Object o2) { - if (!(o1 instanceof VM.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { - throw new BstVMException("Invalid parameters"); - } - - String name = ((VM.Identifier) o1).getName(); - - if (o2 instanceof String) { - if ((context != null) && context.localStrings.containsKey(name)) { - context.localStrings.put(name, (String) o2); - return true; - } - - if (strings.containsKey(name)) { - strings.put(name, (String) o2); - return true; - } - return false; - } - - if ((context != null) && context.localIntegers.containsKey(name)) { - context.localIntegers.put(name, (Integer) o2); - return true; - } - - if (integers.containsKey(name)) { - integers.put(name, (Integer) o2); - return true; - } - return false; - } - - /** - * Pops the top (string) literal, adds a `.' to it if the last non - * '}' character isn't a `.', `?', or `!', and pushes this resulting - * string. - */ - private void bstAddPeriod(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation add.period$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String s)) { - throw new BstVMException("Can only add a period to a string for add.period$"); - } - - Matcher m = ADD_PERIOD_PATTERN.matcher(s); - - if (m.find()) { - StringBuilder sb = new StringBuilder(); - 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); - } - } - - /** - * 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. - */ - private void bstCallType(BstEntry context) { - if (context == null) { - throw new BstVMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); - } - VM.this.execute(context.entry.getType().getName(), context); - } - - /** - * Pops the top two (string) literals; it changes the case of the second - * according to the specifications of the first, as follows. (Note: The word - * `letters' in the next sentence refers only to those at brace-level 0, the - * top-most brace level; no other characters are changed, except perhaps for - * \special characters", described in Section 4.) If the first literal is the - * string `t', it converts to lower case all letters except the very first - * character in the string, which it leaves alone, and except the first - * character following any colon and then nonnull white space, which it also - * leaves alone; if it's the string `l', it converts all letters to lower case; - * and if it's the string `u', it converts all letters to upper case. It then - * pushes this resulting string. If either type is incorrect, it complains and - * pushes the null string; however, if both types are correct but the - * specification string (i.e., the first string) isn't one of the legal ones, it - * merely pushes the second back onto the stack, after complaining. (Another - * note: It ignores case differences in the specification string; for example, - * the strings t and T are equivalent for the purposes of this built-in - * function.) - */ - private void bstChangeCase(BstEntry value) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation change.case$"); - } - - Object o1 = stack.pop(); - if (!((o1 instanceof String) && (((String) o1).length() == 1))) { - throw new BstVMException("A format string of length 1 is needed for change.case$"); - } - - Object o2 = stack.pop(); - if (!(o2 instanceof String s)) { - throw new BstVMException("A string is needed as second parameter for change.case$"); - } - - char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); - - stack.push(BibtexCaseChanger.changeCase(s, BibtexCaseChanger.FORMAT_MODE.getFormatModeForBSTFormat(format))); - } - - /** - * Pops the top (string) literal, makes sure it's a single - * character, converts it to the corresponding ASCII integer, and - * pushes this integer. - */ - private void bstChrToInt(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation chr.to.int$"); - } - Object o1 = stack.pop(); - - if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { - throw new BstVMException("Can only perform chr.to.int$ on string with length 1"); - } - - stack.push((int) s.charAt(0)); - } - - /** - * Pushes the string that was the \cite-command argument for this - * entry. - */ - private void bstCite(BstEntry context) { - if (context == null) { - throw new BstVMException("Must have an entry to cite$"); - } - stack.push(context.entry.getCitationKey().orElse(null)); - } - - /** - * Pops the top literal from the stack and pushes two copies of it. - */ - private void bstDuplicate(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation duplicate$"); - } - Object o1 = stack.pop(); - - stack.push(o1); - stack.push(o1); - } - - /** - * 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. - */ - private void bstEmpty(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation empty$"); - } - Object o1 = stack.pop(); - - if (o1 == null) { - stack.push(VM.TRUE); - return; - } - - if (!(o1 instanceof String s)) { - throw new BstVMException("Operand does not match function empty$"); - } - - stack.push("".equals(s.trim()) ? VM.TRUE : VM.FALSE); - } - - /** - * The |built_in| function {\.{format.name\$}} pops the - * top three literals (they are a string, an integer, and a string - * literal, in that order). The last string literal represents a - * name list (each name corresponding to a person), the integer - * literal specifies which name to pick from this list, and the - * first string literal specifies how to format this name, as - * described in the \BibTeX\ documentation. Finally, this function - * pushes the formatted name. If any of the types is incorrect, it - * complains and pushes the null string. - */ - private void bstFormatName(BstEntry context) { - if (stack.size() < 3) { - throw new BstVMException("Not enough operands on stack for operation format.name$"); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - Object o3 = stack.pop(); - - if (!(o1 instanceof String) && !(o2 instanceof Integer) && !(o3 instanceof String)) { - // warning("A string is needed for change.case$"); - stack.push(""); - return; - } - - String format = (String) o1; - Integer name = (Integer) o2; - String names = (String) o3; - - if (names == null) { - stack.push(""); - } else { - AuthorList a = AuthorList.parse(names); - if (name > a.getNumberOfAuthors()) { - throw new BstVMException("Author Out of Bounds. Number " + name + " invalid for " + names); - } - Author author = a.getAuthor(name - 1); - - stack.push(BibtexNameFormatter.formatName(author, format)); - } - } - - /** - * 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. - */ - private void bstIf(BstEntry context) { - if (stack.size() < 3) { - throw new BstVMException("Not enough operands on stack for operation ="); - } - Object f1 = stack.pop(); - Object f2 = stack.pop(); - Object i = stack.pop(); - - if (!((f1 instanceof VM.Identifier) || (f1 instanceof Tree)) - && ((f2 instanceof VM.Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { - throw new BstVMException("Expecting two functions and an integer for if$."); - } - - if ((Integer) i > 0) { - VM.this.executeInContext(f2, context); - } else { - VM.this.executeInContext(f1, context); - } - } - - /** - * 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. - */ - private void bstIntToChr(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation int.to.chr$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof Integer i)) { - throw new BstVMException("Can only perform operation int.to.chr$ on an Integer"); - } - - stack.push(String.valueOf((char) i.intValue())); - } - - /** - * Pops the top (integer) literal, converts it to its (unique) - * string equivalent, and pushes this string. - */ - private void bstIntToStr(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation int.to.str$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof Integer)) { - throw new BstVMException("Can only transform an integer to an string using int.to.str$"); - } - - stack.push(o1.toString()); - } - - /** - * Pops the top literal and pushes the integer 1 if it's a missing - * field, 0 otherwise. - */ - private void bstMissing(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation missing$"); - } - Object o1 = stack.pop(); - - if (o1 == null) { - stack.push(VM.TRUE); - return; - } - - if (!(o1 instanceof String)) { - LOGGER.warn("Not a string or missing field in operation missing$"); - stack.push(VM.TRUE); - return; - } - - stack.push(VM.FALSE); - } - - /** - * Writes onto the bbl file what is 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. - */ - private void bstNewLine(BstEntry context) { - VM.this.bbl.append('\n'); - } - - /** - * 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. - */ - private void bstNumNames(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation num.names$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String s)) { - throw new BstVMException("Need a string at the top of the stack for num.names$"); - } - - stack.push(AuthorList.parse(s).getNumberOfAuthors()); - } - - /** - * Pops the top of the stack but doesn't print it; this gets rid of - * an unwanted stack literal. - */ - private void bstPop(BstEntry context) { - stack.pop(); - } - - /** - * 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 were none) - * '@PREAMBLE' strings are read from the database files. - */ - private void bstPreamble(BstEntry context) { - stack.push(preamble); - } - - /** - * 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. - */ - private void bstPurify(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation purify$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - LOGGER.warn("A string is needed for purify$"); - stack.push(""); - return; - } - - stack.push(BibtexPurify.purify((String) o1)); - } - - /** - * Pushes the string consisting of the double-quote character. - */ - private void bstQuote(BstEntry context) { - stack.push("\""); - } - - /** - * Does nothing. - */ - private void bstSkip(BstEntry context) { - // no-op - } - - /** - * Pops and prints the whole stack; it's meant to be used for style - * designers while debugging. - */ - private void bstStack(BstEntry context) { - while (!stack.empty()) { - LOGGER.debug("Stack entry {}", stack.pop()); - } - } - - /** - * 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). - */ - private void bstSubstring(BstEntry context) { - if (stack.size() < 3) { - throw new BstVMException("Not enough operands on stack for operation substring$"); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - Object o3 = stack.pop(); - - if (!((o1 instanceof Integer len) && (o2 instanceof Integer start) && (o3 instanceof String s))) { - throw new BstVMException("Expecting two integers and a string for substring$"); - } - - int lenI = len; - int startI = start; - - 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; - } - - 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()))); - } - - /** - * Swaps the top two literals on the stack. 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. - */ - private void bstSwap(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation swap$"); - } - Object f1 = stack.pop(); - Object f2 = stack.pop(); - - stack.push(f1); - stack.push(f2); - } - - /** - * 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. - */ - private void bstTextLength(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation text.length$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String s)) { - throw new BstVMException("Can only perform operation on a string text.length$"); - } - - char[] c = s.toCharArray(); - int result = 0; - int i = 0; - int n = s.length(); - int braceLevel = 0; - - while (i < n) { - i++; - if (c[i - 1] == '{') { - braceLevel++; - if ((braceLevel == 1) && (i < n)) { - if (c[i] == '\\') { - i++; // skip over backslash - while ((i < n) && (braceLevel > 0)) { - if (c[i] == '}') { - braceLevel--; - } else if (c[i] == '{') { - braceLevel++; - } - i++; - } - result++; - } - } - } else if (c[i - 1] == '}') { - if (braceLevel > 0) { - braceLevel--; - } - } else { - result++; - } - } - stack.push(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. - */ - private void bstTextPrefix(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation text.prefix$"); - } - - Object o1 = stack.pop(); - if (!(o1 instanceof Integer)) { - LOGGER.warn("An integer is needed as first parameter to text.prefix$"); - stack.push(""); - return; - } - - Object o2 = stack.pop(); - if (!(o2 instanceof String)) { - LOGGER.warn("A string is needed as second parameter to text.prefix$"); - stack.push(""); - return; - } - - stack.push(BibtexTextPrefix.textPrefix((Integer) o1, (String) o2)); - } - - /** - * Pops and prints the top of the stack to the log file. It's useful for debugging. - */ - private void bstTop(BstEntry context) { - LOGGER.debug("Stack entry {}", stack.pop()); - } - - /** - * Pushes the current entry's type (book, article, etc.), but pushes - * the null string if the type is either unknown or undefined. - */ - private void bstType(BstEntry context) { - if (context == null) { - throw new BstVMException("type$ need a context."); - } - - stack.push(context.entry.getType().getName()); - } - - /** - * Pops the top (string) literal and prints it following a warning - * message. This also increments a count of the number of warning - * messages issued. - */ - private void bstWarning(BstEntry context) { - LOGGER.warn("Warning (#" + (bstWarning++) + "): " + stack.pop()); - } - - /** - * 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. - */ - private void bstWhile(BstEntry context) { - if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation while$"); - } - Object f2 = stack.pop(); - Object f1 = stack.pop(); - - if (!((f1 instanceof VM.Identifier) || (f1 instanceof Tree)) - && ((f2 instanceof VM.Identifier) || (f2 instanceof Tree))) { - throw new BstVMException("Expecting two functions for while$."); - } - - do { - executeInContext(f1, context); - - Object i = stack.pop(); - if (!(i instanceof Integer)) { - throw new BstVMException("First parameter to while has to return an integer but was " + i); - } - if ((Integer) i <= 0) { - break; - } - executeInContext(f2, context); - } while (true); - } - - /** - * The |built_in| function {\.{width\$}} pops the top (string) literal and - * pushes the integer that represents its width in units specified by the - * |char_width| array. This function takes the literal literally; that is, it - * assumes each character in the string is to be printed as is, regardless of - * whether the character has a special meaning to \TeX, except that special - * characters (even without their |right_brace|s) are handled specially. If the - * literal isn't a string, it complains and pushes~0. - */ - private void bstWidth(BstEntry context) { - if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation width$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - LOGGER.warn("A string is needed for width$"); - stack.push(0); - return; - } - - stack.push(BibtexWidth.width((String) o1)); - } - - /** - * 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). - */ - private void bstWrite(BstEntry context) { - String s = (String) stack.pop(); - bbl.append(s); - } - - public String run(BibDatabase db) { - preamble = db.getPreamble().orElse(""); - return run(db.getEntries()); - } - - public String run(Collection bibtex) { - return this.run(bibtex, null); - } - - /** - * Transforms the given list of BibEntries to a rendered list of references using the underlying bst file - * - * @param bibEntries list of entries to convert - * @param bibDatabase (may be null) the bibDatabase used for resolving strings / crossref - * @return list of references in plain text form - */ - public String run(Collection bibEntries, BibDatabase bibDatabase) { - Objects.requireNonNull(bibEntries); - - // Reset - bbl = new StringBuilder(); - - strings = new HashMap<>(); - - integers = new HashMap<>(); - integers.put("entry.max$", Integer.MAX_VALUE); - integers.put("global.max$", Integer.MAX_VALUE); - - functions = new HashMap<>(); - functions.putAll(buildInFunctions); - - stack = new Stack<>(); - - // Create entries - entries = new ArrayList<>(bibEntries.size()); - for (BibEntry entry : bibEntries) { - entries.add(new BstEntry(entry)); - } - - // Go - for (int i = 0; i < tree.getChildCount(); i++) { - Tree child = tree.getChild(i); - switch (child) { - case BstParser.StringsCommandContext ctx -> - strings(ctx); - case BstParser.IntegersCommandContext ctx -> - integers(ctx); - case BstParser.FunctionCommandContext ctx -> - function(ctx); - case BstParser.ExecuteCommandContext ctx -> - execute(ctx); - case BstParser.SortCommandContext ctx -> - sort(ctx); - case BstParser.IterateCommandContext ctx -> - iterate(ctx); - case BstParser.ReverseCommandContext ctx -> - reverse(ctx); - case BstParser.EntryCommandContext ctx -> - entry(ctx); - case BstParser.ReadCommandContext ctx -> - read(bibDatabase, ctx); - case BstParser.MacroCommandContext ctx -> - macro(ctx); - default -> - LOGGER.info("Unknown type: {}", ((BstParser.CommandsContext) child).getText()); - } - } - - return bbl.toString(); - } - - /** - * 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. - */ - private void read(BibDatabase bibDatabase, Tree child) { - FieldWriter fieldWriter = new FieldWriter(new FieldWriterPreferences(true, List.of(StandardField.MONTH), new FieldContentFormatterPreferences())); - for (BstEntry e : entries) { - for (Map.Entry mEntry : e.fields.entrySet()) { - Field field = FieldFactory.parseField(mEntry.getKey()); - String fieldValue = e.entry.getResolvedFieldOrAlias(field, bibDatabase) - .map(content -> { - try { - String result = fieldWriter.write(field, content); - if (result.startsWith("{")) { - // Strip enclosing {} from the output - return result.substring(1, result.length() - 1); - } - if (field == StandardField.MONTH) { - // We don't have the internal BibTeX strings at hand. - // We nevertheless want to have the full month name. - // Thus, we lookup the full month name here. - return Month.parse(result) - .map(Month::getFullName) - .orElse(result); - } - return result; - } catch (InvalidFieldValueException invalidFieldValueException) { - // in case there is something wrong with the content, just return the content itself - return content; - } - }) - .orElse(null); - mEntry.setValue(fieldValue); - } - } - - for (BstEntry e : entries) { - if (!e.fields.containsKey(StandardField.CROSSREF.getName())) { - e.fields.put(StandardField.CROSSREF.getName(), 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. - */ - 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 { - - private final String replacement; - - public MacroFunction(String replacement) { - this.replacement = replacement; - } - - @Override - 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 referencing. 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); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - - for (BstEntry entry : entries) { - entry.fields.put(name, null); - } - } - - // Integers - t = child.getChild(1); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - - for (BstEntry entry : entries) { - entry.localIntegers.put(name, 0); - } - } - // Strings - t = child.getChild(2); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - for (BstEntry entry : entries) { - entry.localStrings.put(name, null); - } - } - for (BstEntry entry : entries) { - entry.localStrings.put("sort.key$", null); - } - } - - private void reverse(Tree child) { - BstFunction f = functions.get(child.getChild(0).getText()); - - ListIterator 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()); - - for (BstEntry entry : entries) { - f.execute(entry); - } - } - - /** - * Sorts the entry list using the values of the string entry variable sort.key$. It has no arguments. - */ - private void sort(Tree child) { - entries.sort(Comparator.comparing(o -> (o.localStrings.get("sort.key$")))); - } - - private void executeInContext(Object o, BstEntry context) { - if (o instanceof Tree t) { - new StackFunction(t).execute(context); - } else if (o instanceof Identifier) { - execute(((Identifier) o).getName(), context); - } - } - - private void execute(Tree child) { - execute(child.getChild(0).getText(), null); - } - - public class StackFunction implements BstFunction { - - private final Tree localTree; - - public StackFunction(Tree stack) { - localTree = stack; - } - - public Tree getTree() { - return localTree; - } - - @Override - public void execute(BstEntry context) { - for (int i = 0; i < localTree.getChildCount(); i++) { - Tree c = localTree.getChild(i); - try { - - switch (c.getType()) { - case BstParser.STRING -> { - String s = c.getText(); - push(s.substring(1, s.length() - 1)); - } - case BstParser.INTEGER -> - push(Integer.parseInt(c.getText().substring(1))); - case BstParser.QUOTED -> - push(new Identifier(c.getText().substring(1))); - case BstParser.STACK -> - push(c); - default -> - VM.this.execute(c.getText(), context); - } - } catch ( - BstVMException e) { - if (path == null) { - LOGGER.error("ERROR " + e.getMessage() + " (" + c.getLine() + ")"); - } else { - LOGGER.error("ERROR " + e.getMessage() + " (" + path + ":" - + c.getLine() + ")"); - } - throw e; - } - } - } - } - - private void push(Tree t) { - stack.push(t); - } - - private void execute(String name, BstEntry context) { - if (context != null) { - if (context.fields.containsKey(name)) { - stack.push(context.fields.get(name)); - return; - } - if (context.localStrings.containsKey(name)) { - stack.push(context.localStrings.get(name)); - return; - } - if (context.localIntegers.containsKey(name)) { - stack.push(context.localIntegers.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)) { - // OK to have a null context - functions.get(name).execute(context); - return; - } - - throw new BstVMException("No matching identifier found: " + name); - } - - private void function(Tree child) { - String name = child.getChild(0).getText(); - Tree localStack = child.getChild(1); - functions.put(name, new StackFunction(localStack)); - } - - /** - * 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. - */ - private void integers(Tree child) { - Tree t = child.getChild(0); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - integers.put(name, 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. - */ - private void strings(Tree child) { - Tree t = child.getChild(0); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - strings.put(name, null); - } - } - - private void push(Integer integer) { - stack.push(integer); - } - - private void push(String string) { - stack.push(string); - } - - private void push(Identifier identifier) { - stack.push(identifier); - } - - public Map getStrings() { - return strings; - } - - public Map getIntegers() { - return integers; - } - - public List getEntries() { - return entries; - } - - public Map getFunctions() { - return functions; - } - - public Stack getStack() { - return stack; - } -} diff --git a/src/test/java/org/jabref/logic/bst/TestVM.java b/src/test/java/org/jabref/logic/bst/TestBstVM.java similarity index 69% rename from src/test/java/org/jabref/logic/bst/TestVM.java rename to src/test/java/org/jabref/logic/bst/TestBstVM.java index 244dbed568f..c9f9114dd4c 100644 --- a/src/test/java/org/jabref/logic/bst/TestVM.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVM.java @@ -6,40 +6,43 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Stack; -import org.jabref.logic.bst.VM.StackFunction; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.model.entry.BibEntry; import org.jabref.model.util.DummyFileUpdateMonitor; +import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.RecognitionException; import org.junit.jupiter.api.Test; import org.mockito.Answers; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; -public class TestVM { +public class TestBstVM { @Test public void testAbbrv() throws RecognitionException, IOException { - VM vm = new VM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); + TestVM vm = new TestVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); List v = List.of(t1BibtexEntry()); String expected = "\\begin{thebibliography}{1}\\bibitem{canh05}K.~Crowston, H.~Annabi, J.~Howison, and C.~Masango.\\newblock Effective work practices for floss development: A model and propositions.\\newblock In {\\em Hawaii International Conference On System Sciences (HICSS)}, 2005.\\end{thebibliography}"; - assertEquals(expected.replaceAll("\\s", ""), vm.run(v).replaceAll("\\s", "")); + assertEquals(expected.replaceAll("\\s", ""), vm.render(v).replaceAll("\\s", "")); } @Test - public void testVMSimple() throws RecognitionException, IOException { + public void testSimple() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { " + " address " + " author " + " title " + " type " + TestVM vm = new TestVM("ENTRY { " + " address " + " author " + " title " + " type " + "} {} { label }" + "INTEGERS { output.state before.all" + " mid.sentence after.sentence after.block }" + "FUNCTION {init.state.consts}{ #0 'before.all := " @@ -48,7 +51,7 @@ public void testVMSimple() throws RecognitionException, IOException { List v = List.of(t1BibtexEntry()); - vm.run(v); + vm.render(v); assertEquals(2, vm.getStrings().size()); assertEquals(7, vm.getIntegers().size()); @@ -59,13 +62,13 @@ public void testVMSimple() throws RecognitionException, IOException { @Test public void testLabel() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { title } {} { label } " + TestVM vm = new TestVM("ENTRY { title } {} { label } " + "FUNCTION { test } { label #0 = title 'label := #5 label #6 pop$ } " + "READ " + "ITERATE { test }"); List v = List.of(t1BibtexEntry()); - vm.run(v); + vm.render(v); assertEquals("Effective work practices for floss development: A model and propositions", vm .getStack() @@ -74,85 +77,82 @@ public void testLabel() throws RecognitionException, IOException { @Test public void testQuote() throws RecognitionException { - VM vm = new VM("FUNCTION {a}{ quote$ quote$ * } EXECUTE {a}"); + TestVM vm = new TestVM("FUNCTION {a}{ quote$ quote$ * } EXECUTE {a}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals("\"\"", vm.getStack().pop()); } @Test - public void testVMFunction1() throws RecognitionException { - VM vm = new VM("FUNCTION {init.state.consts}{ #0 'before.all := } "); + public void testFunction1() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {init.state.consts}{ #0 'before.all := } "); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals(38, vm.getFunctions().size()); - assertTrue(vm.getFunctions().get("init.state.consts") instanceof StackFunction); - - StackFunction fun = (StackFunction) vm.getFunctions().get("init.state.consts"); - assertEquals(3, fun.getTree().getChildCount()); + assertNotNull(vm.getFunctions().get("init.state.consts")); } @Test - public void testVMExecuteSimple() throws RecognitionException { - VM vm = new VM("INTEGERS { variable.a } " + "FUNCTION {init.state.consts}{ #5 'variable.a := } " + public void testExecuteSimple() throws RecognitionException { + TestVM vm = new TestVM("INTEGERS { variable.a } " + "FUNCTION {init.state.consts}{ #5 'variable.a := } " + "EXECUTE {init.state.consts}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals(Integer.valueOf(5), vm.getIntegers().get("variable.a")); } @Test - public void testVMExecuteSimple2() throws RecognitionException { - VM vm = new VM("FUNCTION {a}{ #5 #5 = " + "#1 #2 = " + "#3 #4 < " + "#4 #3 < " + public void testExecuteSimple2() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {a}{ #5 #5 = " + "#1 #2 = " + "#3 #4 < " + "#4 #3 < " + "#4 #4 < " + "#3 #4 > " + "#4 #3 > " + "#4 #4 > " + "\"H\" \"H\" = " + "\"H\" \"Ha\" = } " + "EXECUTE {a}"); - vm.run(Collections.emptyList()); - - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); + vm.render(Collections.emptyList()); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } @Test - public void testVMIfSkipPop() throws RecognitionException { - VM vm = new VM("FUNCTION {not} { { #0 } { #1 } if$ }" + public void testIfSkipPop() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {not} { { #0 } { #1 } if$ }" + "FUNCTION {and} { 'skip$ { pop$ #0 } if$ }" + "FUNCTION {or} { { pop$ #1 } 'skip$ if$ }" + "FUNCTION {test} { " + "#1 #1 and #0 #1 and #1 #0 and #0 #0 and " + "#0 not #1 not " + "#1 #1 or #0 #1 or #1 #0 or #0 #0 or }" + "EXECUTE {test}"); - vm.run(Collections.emptyList()); - - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); + vm.render(Collections.emptyList()); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } @Test - public void testVMArithmetic() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { " + "#1 #1 + #5 #2 - }" + "EXECUTE {test}"); + public void testArithmetic() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {test} { " + "#1 #1 + #5 #2 - }" + "EXECUTE {test}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals(3, vm.getStack().pop()); assertEquals(2, vm.getStack().pop()); @@ -160,16 +160,16 @@ public void testVMArithmetic() throws RecognitionException { } @Test - public void testVMArithmetic2() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { " + "#1 \"HELLO\" + #5 #2 - }" + "EXECUTE {test}"); - assertThrows(BstVMException.class, () -> vm.run(Collections.emptyList())); + public void testArithmetic2() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {test} { " + "#1 \"HELLO\" + #5 #2 - }" + "EXECUTE {test}"); + assertThrows(BstVMException.class, () -> vm.render(Collections.emptyList())); } @Test public void testNumNames() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { \"Johnny Foo and Mary Bar\" num.names$ }" + "EXECUTE {test}"); + TestVM vm = new TestVM("FUNCTION {test} { \"Johnny Foo and Mary Bar\" num.names$ }" + "EXECUTE {test}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals(2, vm.getStack().pop()); assertEquals(0, vm.getStack().size()); @@ -177,24 +177,24 @@ public void testNumNames() throws RecognitionException { @Test public void testNumNames2() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { \"Johnny Foo { and } Mary Bar\" num.names$ }" + TestVM vm = new TestVM("FUNCTION {test} { \"Johnny Foo { and } Mary Bar\" num.names$ }" + "EXECUTE {test}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals(1, vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } @Test - public void testVMStringOps1() throws RecognitionException { - VM vm = new VM( + public void testStringOps1() throws RecognitionException { + TestVM vm = new TestVM( "FUNCTION {test} { \"H\" \"allo\" * \"Johnny\" add.period$ \"Johnny.\" add.period$" + "\"Johnny!\" add.period$ \"Johnny?\" add.period$ \"Johnny} }}}\" add.period$" + "\"Johnny!}\" add.period$ \"Johnny?}\" add.period$ \"Johnny.}\" add.period$ }" + "EXECUTE {test}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals("Johnny.}", vm.getStack().pop()); assertEquals("Johnny?}", vm.getStack().pop()); @@ -210,7 +210,7 @@ public void testVMStringOps1() throws RecognitionException { @Test public void testSubstring() throws RecognitionException { - VM vm = new VM("FUNCTION {test} " + "{ \"123456789\" #2 #1 substring$ " + // 2 + TestVM vm = new TestVM("FUNCTION {test} " + "{ \"123456789\" #2 #1 substring$ " + // 2 " \"123456789\" #4 global.max$ substring$ " + // 456789 " \"123456789\" #1 #9 substring$ " + // 123456789 " \"123456789\" #1 #10 substring$ " + // 123456789 @@ -223,7 +223,7 @@ public void testSubstring() throws RecognitionException { "} EXECUTE {test} "); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals("78", vm.getStack().pop()); assertEquals("789", vm.getStack().pop()); @@ -240,29 +240,29 @@ public void testSubstring() throws RecognitionException { @Test public void testEmpty() throws RecognitionException, IOException { - VM vm = new VM("ENTRY {title}{}{} READ STRINGS { s } FUNCTION {test} " + "{ s empty$ " + // FALSE + TestVM vm = new TestVM("ENTRY {title}{}{} READ STRINGS { s } FUNCTION {test} " + "{ s empty$ " + // FALSE "\"\" empty$ " + // FALSE "\" \" empty$ " + // FALSE " title empty$ " + // FALSE " \" HALLO \" empty$ } ITERATE {test} "); - List v = List.of(TestVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}")); - vm.run(v); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); + List v = List.of(TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}")); + vm.render(v); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } @Test public void testDuplicateEmptyPopSwapIf() throws RecognitionException { - VM vm = new VM("FUNCTION {emphasize} " + "{ duplicate$ empty$ " + " { pop$ \"\" } " + TestVM vm = new TestVM("FUNCTION {emphasize} " + "{ duplicate$ empty$ " + " { pop$ \"\" } " + " { \"{\\em \" swap$ * \"}\" * } " + " if$ " + "} " + "FUNCTION {test} {" + " \"\" emphasize " + " \"Hello\" emphasize " + "}" + "EXECUTE {test} "); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals("{\\em Hello}", vm.getStack().pop()); assertEquals("", vm.getStack().pop()); @@ -271,7 +271,7 @@ public void testDuplicateEmptyPopSwapIf() throws RecognitionException { @Test public void testChangeCase() throws RecognitionException { - VM vm = new VM( + TestVM vm = new TestVM( "STRINGS { title } " + "READ " + "FUNCTION {format.title}" @@ -287,7 +287,7 @@ public void testChangeCase() throws RecognitionException { + " \"{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase\" \"u\" change.case$ format.title " + "}" + "EXECUTE {test} "); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals( "{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase", @@ -300,7 +300,7 @@ public void testChangeCase() throws RecognitionException { @Test public void testTextLength() throws RecognitionException { - VM vm = new VM("FUNCTION {test} {" + " \"hello world\" text.length$ " + TestVM vm = new TestVM("FUNCTION {test} {" + " \"hello world\" text.length$ " + " \"Hello {W}orld\" text.length$ " + " \"\" text.length$ " + " \"{A}{D}/{Cycle}\" text.length$ " + " \"{\\This is one character}\" text.length$ " @@ -308,7 +308,7 @@ public void testTextLength() throws RecognitionException { + " \"{\\And this too\" text.length$ " + " \"These are {\\11}\" text.length$ " + "} " + "EXECUTE {test} "); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals(11, vm.getStack().pop()); assertEquals(1, vm.getStack().pop()); @@ -322,10 +322,10 @@ public void testTextLength() throws RecognitionException { } @Test - public void testVMIntToStr() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { #3 int.to.str$ #9999 int.to.str$}" + "EXECUTE {test}"); + public void testIntToStr() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {test} { #3 int.to.str$ #9999 int.to.str$}" + "EXECUTE {test}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals("9999", vm.getStack().pop()); assertEquals("3", vm.getStack().pop()); @@ -333,20 +333,20 @@ public void testVMIntToStr() throws RecognitionException { } @Test - public void testVMChrToInt() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { \"H\" chr.to.int$ }" + "EXECUTE {test}"); + public void testChrToInt() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {test} { \"H\" chr.to.int$ }" + "EXECUTE {test}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals(72, vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } @Test - public void testVMChrToIntIntToChr() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { \"H\" chr.to.int$ int.to.chr$ }" + "EXECUTE {test}"); + public void testChrToIntIntToChr() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {test} { \"H\" chr.to.int$ int.to.chr$ }" + "EXECUTE {test}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals("H", vm.getStack().pop()); assertEquals(0, vm.getStack().size()); @@ -354,15 +354,15 @@ public void testVMChrToIntIntToChr() throws RecognitionException { @Test public void testSort() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { title } { } { label }" + TestVM vm = new TestVM("ENTRY { title } { } { label }" + "FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } SORT"); List v = List.of( - TestVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), - TestVM.bibtexString2BibtexEntry("@article{b, author=\"BBB\"}"), - TestVM.bibtexString2BibtexEntry("@article{d, author=\"DDD\"}"), - TestVM.bibtexString2BibtexEntry("@article{c, author=\"CCC\"}")); - vm.run(v); + TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), + TestBstVM.bibtexString2BibtexEntry("@article{b, author=\"BBB\"}"), + TestBstVM.bibtexString2BibtexEntry("@article{d, author=\"DDD\"}"), + TestBstVM.bibtexString2BibtexEntry("@article{c, author=\"CCC\"}")); + vm.render(v); List v2 = vm.getEntries(); assertEquals(Optional.of("a"), v2.get(0).entry.getCitationKey()); @@ -373,9 +373,9 @@ public void testSort() throws RecognitionException, IOException { @Test public void testBuildIn() throws RecognitionException { - VM vm = new VM("EXECUTE {global.max$}"); + TestVM vm = new TestVM("EXECUTE {global.max$}"); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); assertEquals(Integer.MAX_VALUE, vm.getStack().pop()); assertTrue(vm.getStack().empty()); @@ -383,19 +383,19 @@ public void testBuildIn() throws RecognitionException { @Test public void testVariables() throws RecognitionException { - VM vm = new VM(" STRINGS { t } " + TestVM vm = new TestVM(" STRINGS { t } " + " FUNCTION {not} { { #0 } { #1 } if$ } " + " FUNCTION {n.dashify} { \"HELLO-WORLD\" 't := t empty$ not } " + " EXECUTE {n.dashify} "); - vm.run(Collections.emptyList()); + vm.render(Collections.emptyList()); - assertEquals(VM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); } @Test public void testWhile() throws RecognitionException { - VM vm = new VM( + TestVM vm = new TestVM( "STRINGS { t } " + "FUNCTION {not} { " + " { #0 } { #1 } if$ } " @@ -427,7 +427,7 @@ public void testWhile() throws RecognitionException { + " EXECUTE {n.dashify} "); List v = Collections.emptyList(); - vm.run(v); + vm.render(v); assertEquals(1, vm.getStack().size()); assertEquals("HELLO--WORLD", vm.getStack().pop()); @@ -435,15 +435,15 @@ public void testWhile() throws RecognitionException { @Test public void testType() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { title } { } { label }" + TestVM vm = new TestVM("ENTRY { title } { } { label }" + "FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } SORT FUNCTION {test} { type$ } ITERATE { test }"); List v = List.of( - TestVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), - TestVM.bibtexString2BibtexEntry("@book{b, author=\"BBB\"}"), - TestVM.bibtexString2BibtexEntry("@misc{c, author=\"CCC\"}"), - TestVM.bibtexString2BibtexEntry("@inproceedings{d, author=\"DDD\"}")); - vm.run(v); + TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), + TestBstVM.bibtexString2BibtexEntry("@book{b, author=\"BBB\"}"), + TestBstVM.bibtexString2BibtexEntry("@misc{c, author=\"CCC\"}"), + TestBstVM.bibtexString2BibtexEntry("@inproceedings{d, author=\"DDD\"}")); + vm.render(v); assertEquals(4, vm.getStack().size()); assertEquals("inproceedings", vm.getStack().pop()); @@ -454,7 +454,7 @@ public void testType() throws RecognitionException, IOException { @Test public void testMissing() throws RecognitionException, IOException { - VM vm = new VM( + TestVM vm = new TestVM( "ENTRY { title } { } { label } " + "FUNCTION {presort} { cite$ 'sort.key$ := } " + "ITERATE {presort} " + @@ -464,8 +464,8 @@ public void testMissing() throws RecognitionException, IOException { List v = List.of( t1BibtexEntry(), - TestVM.bibtexString2BibtexEntry("@article{test, author=\"No title\"}")); - vm.run(v); + TestBstVM.bibtexString2BibtexEntry("@article{test, author=\"No title\"}")); + vm.render(v); assertEquals(4, vm.getStack().size()); @@ -477,27 +477,27 @@ public void testMissing() throws RecognitionException, IOException { @Test public void testFormatName() throws RecognitionException { - VM vm = new VM( + TestVM vm = new TestVM( "FUNCTION {format}{ \"Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin\" #1 \"{vv~}{ll}{, jj}{, f}?\" format.name$ }" + "EXECUTE {format}"); List v = Collections.emptyList(); - vm.run(v); + vm.render(v); assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } @Test public void testFormatName2() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { author } { } { label } " + "FUNCTION {presort} { cite$ 'sort.key$ := } " + TestVM vm = new TestVM("ENTRY { author } { } { label } " + "FUNCTION {presort} { cite$ 'sort.key$ := } " + "ITERATE { presort } " + "READ " + "SORT " + "FUNCTION {format}{ author #2 \"{vv~}{ll}{, jj}{, f}?\" format.name$ }" + "ITERATE {format}"); List v = List.of( t1BibtexEntry(), - TestVM.bibtexString2BibtexEntry( + TestBstVM.bibtexString2BibtexEntry( "@book{test, author=\"Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin\"}")); - vm.run(v); + vm.render(v); assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); assertEquals("Annabi, H?", vm.getStack().pop()); assertEquals(0, vm.getStack().size()); @@ -505,16 +505,16 @@ public void testFormatName2() throws RecognitionException, IOException { @Test public void testCallType() throws RecognitionException, IOException { - VM vm = new VM( + TestVM vm = new TestVM( "ENTRY { title } { } { label } FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } READ SORT " + "FUNCTION {inproceedings}{ \"InProceedings called on \" title * } " + "FUNCTION {book}{ \"Book called on \" title * } " + " ITERATE { call.type$ }"); List v = List.of( t1BibtexEntry(), - TestVM.bibtexString2BibtexEntry("@book{test, title=\"Test\"}")); + TestBstVM.bibtexString2BibtexEntry("@book{test, title=\"Test\"}")); - vm.run(v); + vm.render(v); assertEquals(2, vm.getStack().size()); @@ -527,14 +527,14 @@ public void testCallType() throws RecognitionException, IOException { @Test public void testIterate() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { " + " address " + " author " + " title " + " type " + TestVM vm = new TestVM("ENTRY { " + " address " + " author " + " title " + " type " + "} {} { label } " + "FUNCTION {test}{ cite$ } " + "READ " + "ITERATE { test }"); List v = List.of( t1BibtexEntry(), - TestVM.bibtexString2BibtexEntry("@article{test, title=\"BLA\"}")); + TestBstVM.bibtexString2BibtexEntry("@article{test, title=\"BLA\"}")); - vm.run(v); + vm.render(v); assertEquals(2, vm.getStack().size()); @@ -551,7 +551,7 @@ public void testIterate() throws RecognitionException, IOException { @Test public void testWidth() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { " + " address " + " author " + " title " + " type " + TestVM vm = new TestVM("ENTRY { " + " address " + " author " + " title " + " type " + "} {} { label } " + "STRINGS { longest.label } " + "INTEGERS { number.label longest.label.width } " + @@ -586,18 +586,18 @@ public void testWidth() throws RecognitionException, IOException { List v = List.of(t1BibtexEntry()); - vm.run(v); + vm.render(v); assertTrue(vm.getIntegers().containsKey("longest.label.width")); assertEquals("\\begin{thebibliography}{1}", vm.getStack().pop()); } @Test - public void testVMSwap() throws RecognitionException { - VM vm = new VM("FUNCTION {a}{ #3 \"Hallo\" swap$ } EXECUTE { a }"); + public void testSwap() throws RecognitionException { + TestVM vm = new TestVM("FUNCTION {a}{ #3 \"Hallo\" swap$ } EXECUTE { a }"); List v = Collections.emptyList(); - vm.run(v); + vm.render(v); assertEquals(2, vm.getStack().size()); assertEquals(3, vm.getStack().pop()); @@ -606,9 +606,9 @@ public void testVMSwap() throws RecognitionException { @Test public void testHypthenatedName() throws RecognitionException, IOException { - VM vm = new VM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); - List v = List.of(TestVM.bibtexString2BibtexEntry("@article{canh05, author = \"Jean-Paul Sartre\" }")); - assertTrue(vm.run(v).contains("J.-P. Sartre")); + TestVM vm = new TestVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); + List v = List.of(TestBstVM.bibtexString2BibtexEntry("@article{canh05, author = \"Jean-Paul Sartre\" }")); + assertTrue(vm.render(v).contains("J.-P. Sartre")); } private static BibEntry bibtexString2BibtexEntry(String s) throws IOException { @@ -632,6 +632,37 @@ private static String t1BibtexString() { } private static BibEntry t1BibtexEntry() throws IOException { - return TestVM.bibtexString2BibtexEntry(t1BibtexString()); + return TestBstVM.bibtexString2BibtexEntry(t1BibtexString()); + } + + private static class TestVM extends BstVM { + + private TestVM(Path path) throws RecognitionException, IOException { + super(CharStreams.fromPath(path)); + } + + private TestVM(String s) throws RecognitionException { + super(s); + } + + protected Map getStrings() { + return latestContext.strings(); + } + + protected Map getIntegers() { + return latestContext.integers(); + } + + protected List getEntries() { + return latestContext.entries(); + } + + protected Map getFunctions() { + return latestContext.functions(); + } + + protected Stack getStack() { + return latestContext.stack(); + } } } From 3eb676c426e6965a2d4f1c038c2ead2616b70f74 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sat, 11 Jun 2022 12:51:26 +0200 Subject: [PATCH 12/39] fixed parser --- src/main/antlr4/org/jabref/bst/Bst.g4 | 2 -- .../java/org/jabref/logic/bst/BstFunctions.java | 4 +++- src/test/java/org/jabref/logic/bst/TestBstVM.java | 14 ++++++-------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/antlr4/org/jabref/bst/Bst.g4 b/src/main/antlr4/org/jabref/bst/Bst.g4 index 35ac6a5c624..4e93fb79200 100644 --- a/src/main/antlr4/org/jabref/bst/Bst.g4 +++ b/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -20,8 +20,6 @@ ASSIGN : ':='; ADD : '+'; SUB : '-'; CONCAT : '*'; -IF : 'if$'; -WHILE : 'while$'; LBRACE : '{'; RBRACE : '}'; diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index c6a929df4d2..3357c6094bd 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,6 +13,7 @@ import org.jabref.logic.bst.util.BibtexPurify; import org.jabref.logic.bst.util.BibtexTextPrefix; import org.jabref.logic.bst.util.BibtexWidth; +import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; @@ -50,7 +52,7 @@ public BstFunctions(BstVMContext bstVMContext, this.strings = bstVMContext.strings(); this.integers = bstVMContext.integers(); this.functions = bstVMContext.functions(); - this.preamble = bstVMContext.bibDatabase().getPreamble().orElse(""); + this.preamble = Optional.ofNullable(bstVMContext.bibDatabase()).flatMap(BibDatabase::getPreamble).orElse(""); this.stack = bstVMContext.stack(); this.bbl = bbl; diff --git a/src/test/java/org/jabref/logic/bst/TestBstVM.java b/src/test/java/org/jabref/logic/bst/TestBstVM.java index c9f9114dd4c..0b2ae007447 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVM.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVM.java @@ -42,12 +42,11 @@ public void testAbbrv() throws RecognitionException, IOException { @Test public void testSimple() throws RecognitionException, IOException { - TestVM vm = new TestVM("ENTRY { " + " address " + " author " + " title " + " type " - + "} {} { label }" + "INTEGERS { output.state before.all" - + " mid.sentence after.sentence after.block }" - + "FUNCTION {init.state.consts}{ #0 'before.all := " - + " #1 'mid.sentence := #2 'after.sentence := #3 'after.block := } " - + "STRINGS { s t } " + "READ"); + TestVM vm = new TestVM("ENTRY { address author title type } {} { label } " + + "INTEGERS { output.state before.all mid.sentence after.sentence after.block } " + + "FUNCTION {init.state.consts}{ #0 'before.all := #1 'mid.sentence := #2 'after.sentence := #3 'after.block := } " + + "STRINGS { s t } " + + "READ"); List v = List.of(t1BibtexEntry()); @@ -271,8 +270,7 @@ public void testDuplicateEmptyPopSwapIf() throws RecognitionException { @Test public void testChangeCase() throws RecognitionException { - TestVM vm = new TestVM( - "STRINGS { title } " + TestVM vm = new TestVM("STRINGS { title } " + "READ " + "FUNCTION {format.title}" + " { duplicate$ empty$ " From 220679c30d98999974d8359ff497b7a365efde7c Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sun, 12 Jun 2022 14:47:06 +0200 Subject: [PATCH 13/39] fixed stringscommand --- src/main/antlr4/org/jabref/bst/Bst.g4 | 6 +- .../org/jabref/logic/bst/BstVMVisitor.java | 2 +- .../java/org/jabref/logic/bst/TestBstVM.java | 6 +- .../jabref/logic/bst/TestBstVMVisitor.java | 82 +++++++++++++++++++ 4 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java diff --git a/src/main/antlr4/org/jabref/bst/Bst.g4 b/src/main/antlr4/org/jabref/bst/Bst.g4 index 4e93fb79200..372200484bc 100644 --- a/src/main/antlr4/org/jabref/bst/Bst.g4 +++ b/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -24,10 +24,10 @@ LBRACE : '{'; RBRACE : '}'; fragment LETTER : ('a'..'z'|'A'..'Z'|'.'|'$'); -fragment DIGIT : [0-9]; +fragment NUMERAL : ('0'..'9'); -IDENTIFIER : LETTER (LETTER|DIGIT|'_')*; -INTEGER : '#' ('+'|'-')? DIGIT+; +IDENTIFIER : LETTER (LETTER|NUMERAL|'_')*; +INTEGER : '#' ('+'|'-')? NUMERAL+; QUOTED : '\'' IDENTIFIER; STRING : '"' (~('"'))* '"'; diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index ec88d62cf65..04478fd0f51 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -39,7 +39,7 @@ public Integer visitStringsCommand(BstParser.StringsCommandContext ctx) { } for (BstParser.IdentifierContext identifierContext : ctx.ids.identifier()) { - bstVMContext.integers().put(identifierContext.getText(), null); + bstVMContext.strings().put(identifierContext.getText(), null); } return BstVM.TRUE; } diff --git a/src/test/java/org/jabref/logic/bst/TestBstVM.java b/src/test/java/org/jabref/logic/bst/TestBstVM.java index 0b2ae007447..970d08f9c6f 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVM.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVM.java @@ -629,17 +629,17 @@ private static String t1BibtexString() { """; } - private static BibEntry t1BibtexEntry() throws IOException { + public static BibEntry t1BibtexEntry() throws IOException { return TestBstVM.bibtexString2BibtexEntry(t1BibtexString()); } - private static class TestVM extends BstVM { + public static class TestVM extends BstVM { private TestVM(Path path) throws RecognitionException, IOException { super(CharStreams.fromPath(path)); } - private TestVM(String s) throws RecognitionException { + protected TestVM(String s) throws RecognitionException { super(s); } diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java new file mode 100644 index 00000000000..f68545de893 --- /dev/null +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -0,0 +1,82 @@ +package org.jabref.logic.bst; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.jabref.model.entry.BibEntry; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TestBstVMVisitor { + private List testEntry; + + @BeforeEach + void beforeAll() throws IOException { + // this.testEntry = List.of(TestBstVM.t1BibtexEntry()); + } + + @Test + public void testVisitStringsCommand() { + TestBstVM.TestVM vm = new TestBstVM.TestVM("STRINGS { test.string1 test.string2 test.string3 }"); + vm.render(Collections.emptyList()); + + Map strList = vm.latestContext.strings(); + + assertTrue(strList.containsKey("test.string1")); + assertNull(strList.get("test.string1")); + + assertTrue(strList.containsKey("test.string2")); + assertNull(strList.get("test.string2")); + + assertTrue(strList.containsKey("test.string3")); + assertNull(strList.get("test.string3")); + } + + @Test + public void testVisitIntegersCommand() { + TestBstVM.TestVM vm = new TestBstVM.TestVM("INTEGERS { variable.a variable.b variable.c }"); + vm.render(Collections.emptyList()); + + Map integersList = vm.latestContext.integers(); + + assertTrue(integersList.containsKey("variable.a")); + assertEquals(0, integersList.get("variable.a")); + + assertTrue(integersList.containsKey("variable.b")); + assertEquals(0, integersList.get("variable.b")); + + assertTrue(integersList.containsKey("variable.c")); + assertEquals(0, integersList.get("variable.c")); + } + + @Test + void testVisitFunctionCommand() { + TestBstVM.TestVM vm = new TestBstVM.TestVM("FUNCTION {init.state.consts} { #0 'before.all := } "); + vm.render(Collections.emptyList()); + + Map functions = vm.latestContext.functions(); + + assertTrue(functions.containsKey("init.state.consts")); + assertNotNull(functions.get("init.state.consts")); + } + + @Test + void testVisitMacroCommand() { + TestBstVM.TestVM vm = new TestBstVM.TestVM("MACRO {jan} { \"January\" } EXECUTE {jan}"); + vm.render(Collections.emptyList()); + + Map functions = vm.latestContext.functions(); + assertTrue(functions.containsKey("jan")); + assertNotNull(functions.get("jan")); + + + } +} From 018c3d61b2074bc0459173261128bf04000a3ba3 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sun, 12 Jun 2022 14:54:49 +0200 Subject: [PATCH 14/39] fixed macrocommand --- src/main/java/org/jabref/logic/bst/BstVMVisitor.java | 4 ++-- src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 04478fd0f51..c5f88a513d9 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -246,8 +246,8 @@ private class BstVMFunction implements BstFunctions.BstFunction { @Override public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { - if (parserRuleContext instanceof BstParser.IdentifierContext) { - bstVMContext.stack().push(expression); // Macro + if (expression instanceof String str) { + bstVMContext.stack().push(str.substring(1, str.length() - 1)); // Macro } else { visitor.visit(parserRuleContext); } diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index f68545de893..a2413051ada 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -77,6 +77,6 @@ void testVisitMacroCommand() { assertTrue(functions.containsKey("jan")); assertNotNull(functions.get("jan")); - + assertEquals("January", vm.latestContext.stack().pop()); } } From 472dcfb426bbec9daab795c2f4f2ef1d5868b448 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sun, 12 Jun 2022 15:17:44 +0200 Subject: [PATCH 15/39] fixed functioncommand --- .../org/jabref/logic/bst/BstVMVisitor.java | 6 ++--- .../jabref/logic/bst/TestBstVMVisitor.java | 22 ++++++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index c5f88a513d9..d8b3e938ae1 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -245,11 +245,11 @@ private class BstVMFunction implements BstFunctions.BstFunction { } @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + public void execute(BstVMVisitor visitor, ParserRuleContext bstFunctionContext) { if (expression instanceof String str) { bstVMContext.stack().push(str.substring(1, str.length() - 1)); // Macro - } else { - visitor.visit(parserRuleContext); + } else if (expression instanceof ParserRuleContext ctx) { + visitor.visit(ctx); } } } diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index a2413051ada..298f77bc329 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -59,13 +59,29 @@ public void testVisitIntegersCommand() { @Test void testVisitFunctionCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM("FUNCTION {init.state.consts} { #0 'before.all := } "); + TestBstVM.TestVM vm = new TestBstVM.TestVM("FUNCTION {test.func} { #1 'test.var := } EXECUTE { test.func }"); vm.render(Collections.emptyList()); Map functions = vm.latestContext.functions(); - assertTrue(functions.containsKey("init.state.consts")); - assertNotNull(functions.get("init.state.consts")); + assertTrue(functions.containsKey("test.func")); + assertNotNull(functions.get("test.func")); + } + + // ToDo: Belongs in testBstFunction + @Test + void testAssignFunction() { + TestBstVM.TestVM vm = new TestBstVM.TestVM("INTEGERS { test.var }" + + "FUNCTION {test.func} { #1 'test.var := }" + + "EXECUTE { test.func }"); + vm.render(Collections.emptyList()); + + Map functions = vm.latestContext.functions(); + + assertTrue(functions.containsKey("test.func")); + assertNotNull(functions.get("test.func")); + + assertEquals(1, vm.latestContext.integers().get("test.var")); } @Test From a9f31aa1865ce53eecf52d09031d02562cc0fd57 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sun, 12 Jun 2022 16:07:21 +0200 Subject: [PATCH 16/39] added exception handling for functions --- src/main/java/org/jabref/logic/bst/BstVMVisitor.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index d8b3e938ae1..b493291d786 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -212,7 +212,12 @@ public Integer visitIdentifier(BstParser.IdentifierContext ctx) { @Override public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { - bstVMContext.functions().get(ctx.getChild(0).getText()).execute(this, ctx, selectedBstEntry); + String name = ctx.getChild(0).getText(); + if (!bstVMContext.functions().containsKey(name)) { + throw new BstVMException("Function does not exist: " + name); + } + + bstVMContext.functions().get(name).execute(this, ctx, selectedBstEntry); return BstVM.TRUE; } From f748f8dda345eec2a19866b47bf0a8b9f9f6b1a7 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 13 Jun 2022 09:04:38 +0200 Subject: [PATCH 17/39] testVisitEntryCommand --- .../jabref/logic/bst/TestBstVMVisitor.java | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index 298f77bc329..97bed01c13b 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -7,7 +7,6 @@ import org.jabref.model.entry.BibEntry; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,12 +15,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class TestBstVMVisitor { - private List testEntry; - - @BeforeEach - void beforeAll() throws IOException { - // this.testEntry = List.of(TestBstVM.t1BibtexEntry()); - } @Test public void testVisitStringsCommand() { @@ -94,5 +87,29 @@ void testVisitMacroCommand() { assertNotNull(functions.get("jan")); assertEquals("January", vm.latestContext.stack().pop()); + assertTrue(vm.latestContext.stack().isEmpty()); + } + + @Test + void testVisitEntryCommand() throws IOException { + TestBstVM.TestVM vm = new TestBstVM.TestVM("ENTRY {address author title type}{variable}{label}"); + List testEntries = List.of(TestBstVM.t1BibtexEntry()); + + vm.render(testEntries); + + assertTrue(vm.latestContext.entries().get(0).fields.containsKey("address")); + assertTrue(vm.latestContext.entries().get(0).fields.containsKey("author")); + assertTrue(vm.latestContext.entries().get(0).fields.containsKey("title")); + assertTrue(vm.latestContext.entries().get(0).fields.containsKey("type")); + + assertTrue(vm.latestContext.entries().get(0).localIntegers.containsKey("variable")); + + assertTrue(vm.latestContext.entries().get(0).localStrings.containsKey("label")); + assertTrue(vm.latestContext.entries().get(0).localStrings.containsKey("sort.key$")); + } + + @Test + void testVisitReadCommand() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""); // ENTRY {type author title booktitle year owner timestamp url}{}{} } } From 1ffb6ab5671e1af9561818afa911e849054c45d8 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 13 Jun 2022 09:23:02 +0200 Subject: [PATCH 18/39] testVisitReadCommand --- .../jabref/logic/bst/TestBstVMVisitor.java | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index 97bed01c13b..d4f74035c20 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -96,20 +96,41 @@ void testVisitEntryCommand() throws IOException { List testEntries = List.of(TestBstVM.t1BibtexEntry()); vm.render(testEntries); + BstEntry bstEntry = vm.latestContext.entries().get(0); - assertTrue(vm.latestContext.entries().get(0).fields.containsKey("address")); - assertTrue(vm.latestContext.entries().get(0).fields.containsKey("author")); - assertTrue(vm.latestContext.entries().get(0).fields.containsKey("title")); - assertTrue(vm.latestContext.entries().get(0).fields.containsKey("type")); + assertTrue(bstEntry.fields.containsKey("address")); + assertTrue(bstEntry.fields.containsKey("author")); + assertTrue(bstEntry.fields.containsKey("title")); + assertTrue(bstEntry.fields.containsKey("type")); - assertTrue(vm.latestContext.entries().get(0).localIntegers.containsKey("variable")); + assertTrue(bstEntry.localIntegers.containsKey("variable")); - assertTrue(vm.latestContext.entries().get(0).localStrings.containsKey("label")); - assertTrue(vm.latestContext.entries().get(0).localStrings.containsKey("sort.key$")); + assertTrue(bstEntry.localStrings.containsKey("label")); + assertTrue(bstEntry.localStrings.containsKey("sort.key$")); } @Test - void testVisitReadCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""); // ENTRY {type author title booktitle year owner timestamp url}{}{} + void testVisitReadCommand() throws IOException { + TestBstVM.TestVM vm = new TestBstVM.TestVM("ENTRY {author title booktitle year owner timestamp url}{}{} READ"); + List testEntries = List.of(TestBstVM.t1BibtexEntry()); + + vm.render(testEntries); + + Map fields = vm.latestContext.entries().get(0).fields; + + assertTrue(fields.containsKey("author")); + assertEquals("Crowston, K. and Annabi, H. and Howison, J. and Masango, C.", fields.get("author")); + assertTrue(fields.containsKey("title")); + assertEquals("Effective work practices for floss development: A model and propositions", fields.get("title")); + assertTrue(fields.containsKey("booktitle")); + assertEquals("Hawaii International Conference On System Sciences (HICSS)", fields.get("booktitle")); + assertTrue(fields.containsKey("year")); + assertEquals("2005", fields.get("year")); + assertTrue(fields.containsKey("owner")); + assertEquals("oezbek", fields.get("owner")); + assertTrue(fields.containsKey("timestamp")); + assertEquals("2006.05.29", fields.get("timestamp")); + assertTrue(fields.containsKey("url")); + assertEquals("http://james.howison.name/publications.html", fields.get("url")); } } From 51cc2768d312ba30e6e2e87f2c95e6606f4e64b9 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 13 Jun 2022 09:25:33 +0200 Subject: [PATCH 19/39] reorder and cleanup --- .../java/org/jabref/logic/bst/BstEntry.java | 1 - .../jabref/logic/bst/TestBstVMVisitor.java | 49 +++++++++---------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstEntry.java b/src/main/java/org/jabref/logic/bst/BstEntry.java index 0edf00515ab..89571de8046 100644 --- a/src/main/java/org/jabref/logic/bst/BstEntry.java +++ b/src/main/java/org/jabref/logic/bst/BstEntry.java @@ -11,7 +11,6 @@ public class BstEntry { public final Map localStrings = new HashMap<>(); - // keys filled by org.jabref.logic.bst.VM.entry based on the contents of the bst file public final Map fields = new HashMap<>(); public final Map localIntegers = new HashMap<>(); diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index d4f74035c20..4ac0362be08 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -19,16 +19,14 @@ class TestBstVMVisitor { @Test public void testVisitStringsCommand() { TestBstVM.TestVM vm = new TestBstVM.TestVM("STRINGS { test.string1 test.string2 test.string3 }"); - vm.render(Collections.emptyList()); - Map strList = vm.latestContext.strings(); + vm.render(Collections.emptyList()); + Map strList = vm.getStrings(); assertTrue(strList.containsKey("test.string1")); assertNull(strList.get("test.string1")); - assertTrue(strList.containsKey("test.string2")); assertNull(strList.get("test.string2")); - assertTrue(strList.containsKey("test.string3")); assertNull(strList.get("test.string3")); } @@ -36,16 +34,14 @@ public void testVisitStringsCommand() { @Test public void testVisitIntegersCommand() { TestBstVM.TestVM vm = new TestBstVM.TestVM("INTEGERS { variable.a variable.b variable.c }"); - vm.render(Collections.emptyList()); - Map integersList = vm.latestContext.integers(); + vm.render(Collections.emptyList()); + Map integersList = vm.getIntegers(); assertTrue(integersList.containsKey("variable.a")); assertEquals(0, integersList.get("variable.a")); - assertTrue(integersList.containsKey("variable.b")); assertEquals(0, integersList.get("variable.b")); - assertTrue(integersList.containsKey("variable.c")); assertEquals(0, integersList.get("variable.c")); } @@ -53,10 +49,10 @@ public void testVisitIntegersCommand() { @Test void testVisitFunctionCommand() { TestBstVM.TestVM vm = new TestBstVM.TestVM("FUNCTION {test.func} { #1 'test.var := } EXECUTE { test.func }"); - vm.render(Collections.emptyList()); - Map functions = vm.latestContext.functions(); + vm.render(Collections.emptyList()); + Map functions = vm.getFunctions(); assertTrue(functions.containsKey("test.func")); assertNotNull(functions.get("test.func")); } @@ -67,25 +63,24 @@ void testAssignFunction() { TestBstVM.TestVM vm = new TestBstVM.TestVM("INTEGERS { test.var }" + "FUNCTION {test.func} { #1 'test.var := }" + "EXECUTE { test.func }"); - vm.render(Collections.emptyList()); - Map functions = vm.latestContext.functions(); + vm.render(Collections.emptyList()); + Map functions = vm.getFunctions(); assertTrue(functions.containsKey("test.func")); assertNotNull(functions.get("test.func")); - assertEquals(1, vm.latestContext.integers().get("test.var")); } @Test void testVisitMacroCommand() { TestBstVM.TestVM vm = new TestBstVM.TestVM("MACRO {jan} { \"January\" } EXECUTE {jan}"); + vm.render(Collections.emptyList()); - Map functions = vm.latestContext.functions(); + Map functions = vm.getFunctions(); assertTrue(functions.containsKey("jan")); assertNotNull(functions.get("jan")); - assertEquals("January", vm.latestContext.stack().pop()); assertTrue(vm.latestContext.stack().isEmpty()); } @@ -96,15 +91,13 @@ void testVisitEntryCommand() throws IOException { List testEntries = List.of(TestBstVM.t1BibtexEntry()); vm.render(testEntries); - BstEntry bstEntry = vm.latestContext.entries().get(0); + BstEntry bstEntry = vm.getEntries().get(0); assertTrue(bstEntry.fields.containsKey("address")); assertTrue(bstEntry.fields.containsKey("author")); assertTrue(bstEntry.fields.containsKey("title")); assertTrue(bstEntry.fields.containsKey("type")); - assertTrue(bstEntry.localIntegers.containsKey("variable")); - assertTrue(bstEntry.localStrings.containsKey("label")); assertTrue(bstEntry.localStrings.containsKey("sort.key$")); } @@ -116,21 +109,23 @@ void testVisitReadCommand() throws IOException { vm.render(testEntries); - Map fields = vm.latestContext.entries().get(0).fields; - - assertTrue(fields.containsKey("author")); + Map fields = vm.getEntries().get(0).fields; assertEquals("Crowston, K. and Annabi, H. and Howison, J. and Masango, C.", fields.get("author")); - assertTrue(fields.containsKey("title")); assertEquals("Effective work practices for floss development: A model and propositions", fields.get("title")); - assertTrue(fields.containsKey("booktitle")); assertEquals("Hawaii International Conference On System Sciences (HICSS)", fields.get("booktitle")); - assertTrue(fields.containsKey("year")); assertEquals("2005", fields.get("year")); - assertTrue(fields.containsKey("owner")); assertEquals("oezbek", fields.get("owner")); - assertTrue(fields.containsKey("timestamp")); assertEquals("2006.05.29", fields.get("timestamp")); - assertTrue(fields.containsKey("url")); assertEquals("http://james.howison.name/publications.html", fields.get("url")); } + + // execute iterate reverse + + // sort + + // identifier + + // bstFunction + + // stackitem } From a6a3a70b5cb3ff822f5155a065ac8d8f343f3911 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 13 Jun 2022 19:09:08 +0200 Subject: [PATCH 20/39] testdata cleanup --- .../java/org/jabref/logic/bst/TestBstVM.java | 496 +++++++++++------- .../jabref/logic/bst/TestBstVMVisitor.java | 10 +- 2 files changed, 322 insertions(+), 184 deletions(-) diff --git a/src/test/java/org/jabref/logic/bst/TestBstVM.java b/src/test/java/org/jabref/logic/bst/TestBstVM.java index 970d08f9c6f..51c934b8cc9 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVM.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVM.java @@ -41,13 +41,18 @@ public void testAbbrv() throws RecognitionException, IOException { @Test public void testSimple() throws RecognitionException, IOException { - - TestVM vm = new TestVM("ENTRY { address author title type } {} { label } " - + "INTEGERS { output.state before.all mid.sentence after.sentence after.block } " - + "FUNCTION {init.state.consts}{ #0 'before.all := #1 'mid.sentence := #2 'after.sentence := #3 'after.block := } " - + "STRINGS { s t } " - + "READ"); - + TestVM vm = new TestVM(""" + ENTRY { address author title type } { } { label } + INTEGERS { output.state before.all mid.sentence after.sentence after.block } + FUNCTION { init.state.consts }{ + #0 'before.all := + #1 'mid.sentence := + #2 'after.sentence := + #3 'after.block := + } + STRINGS { s t } + READ + """); List v = List.of(t1BibtexEntry()); vm.render(v); @@ -61,10 +66,12 @@ public void testSimple() throws RecognitionException, IOException { @Test public void testLabel() throws RecognitionException, IOException { - TestVM vm = new TestVM("ENTRY { title } {} { label } " - + "FUNCTION { test } { label #0 = title 'label := #5 label #6 pop$ } " + "READ " - + "ITERATE { test }"); - + TestVM vm = new TestVM(""" + ENTRY { title } {} { label } + FUNCTION { test } { label #0 = title 'label := #5 label #6 pop$ } + READ + ITERATE { test } + """); List v = List.of(t1BibtexEntry()); vm.render(v); @@ -76,7 +83,7 @@ public void testLabel() throws RecognitionException, IOException { @Test public void testQuote() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {a}{ quote$ quote$ * } EXECUTE {a}"); + TestVM vm = new TestVM("FUNCTION { a }{ quote$ quote$ * } EXECUTE { a }"); vm.render(Collections.emptyList()); assertEquals("\"\"", vm.getStack().pop()); @@ -84,19 +91,21 @@ public void testQuote() throws RecognitionException { @Test public void testFunction1() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {init.state.consts}{ #0 'before.all := } "); + TestVM vm = new TestVM("FUNCTION { init.state.consts }{ #0 'before.all := } "); vm.render(Collections.emptyList()); assertEquals(38, vm.getFunctions().size()); - assertNotNull(vm.getFunctions().get("init.state.consts")); } @Test public void testExecuteSimple() throws RecognitionException { - TestVM vm = new TestVM("INTEGERS { variable.a } " + "FUNCTION {init.state.consts}{ #5 'variable.a := } " - + "EXECUTE {init.state.consts}"); + TestVM vm = new TestVM(""" + INTEGERS { variable.a } + FUNCTION { init.state.consts }{ #5 'variable.a := } + EXECUTE { init.state.consts } + """); vm.render(Collections.emptyList()); @@ -105,9 +114,21 @@ public void testExecuteSimple() throws RecognitionException { @Test public void testExecuteSimple2() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {a}{ #5 #5 = " + "#1 #2 = " + "#3 #4 < " + "#4 #3 < " - + "#4 #4 < " + "#3 #4 > " + "#4 #3 > " + "#4 #4 > " + "\"H\" \"H\" = " - + "\"H\" \"Ha\" = } " + "EXECUTE {a}"); + TestVM vm = new TestVM(""" + FUNCTION {a}{ + #5 #5 = + #1 #2 = + #3 #4 < + #4 #3 < + #4 #4 < + #3 #4 > + #4 #3 > + #4 #4 > + "H" "H" = + "H" "Ha" = + } + EXECUTE { a } + """); vm.render(Collections.emptyList()); @@ -126,11 +147,24 @@ public void testExecuteSimple2() throws RecognitionException { @Test public void testIfSkipPop() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {not} { { #0 } { #1 } if$ }" - + "FUNCTION {and} { 'skip$ { pop$ #0 } if$ }" - + "FUNCTION {or} { { pop$ #1 } 'skip$ if$ }" + "FUNCTION {test} { " - + "#1 #1 and #0 #1 and #1 #0 and #0 #0 and " + "#0 not #1 not " - + "#1 #1 or #0 #1 or #1 #0 or #0 #0 or }" + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { not } { { #0 } { #1 } if$ } + FUNCTION { and } { 'skip$ { pop$ #0 } if$ }" + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #1 #1 and + #0 #1 and + #1 #0 and + #0 #0 and + #0 not + #1 not + #1 #1 or + #0 #1 or + #1 #0 or + #0 #0 or + } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -149,7 +183,13 @@ public void testIfSkipPop() throws RecognitionException { @Test public void testArithmetic() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} { " + "#1 #1 + #5 #2 - }" + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { test } { + #1 #1 + + #5 #2 - + } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -160,13 +200,23 @@ public void testArithmetic() throws RecognitionException { @Test public void testArithmetic2() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} { " + "#1 \"HELLO\" + #5 #2 - }" + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { test } { + #1 "HELLO" + % Throws exception + #5 #2 - + } + EXECUTE { test } + """); + assertThrows(BstVMException.class, () -> vm.render(Collections.emptyList())); } @Test public void testNumNames() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} { \"Johnny Foo and Mary Bar\" num.names$ }" + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { test } { "Johnny Foo and Mary Bar" num.names$ } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -176,8 +226,10 @@ public void testNumNames() throws RecognitionException { @Test public void testNumNames2() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} { \"Johnny Foo { and } Mary Bar\" num.names$ }" - + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { test } { "Johnny Foo { and } Mary Bar" num.names$ } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -187,11 +239,14 @@ public void testNumNames2() throws RecognitionException { @Test public void testStringOps1() throws RecognitionException { - TestVM vm = new TestVM( - "FUNCTION {test} { \"H\" \"allo\" * \"Johnny\" add.period$ \"Johnny.\" add.period$" - + "\"Johnny!\" add.period$ \"Johnny?\" add.period$ \"Johnny} }}}\" add.period$" - + "\"Johnny!}\" add.period$ \"Johnny?}\" add.period$ \"Johnny.}\" add.period$ }" - + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { test } { + "H" "allo" * "Johnny" add.period$ "Johnny." add.period$ + "Johnny!" add.period$ "Johnny?" add.period$ "Johnny} }}}" add.period$ + "Johnny!}" add.period$ "Johnny?}" add.period$ "Johnny.}" add.period$ + } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -209,18 +264,19 @@ public void testStringOps1() throws RecognitionException { @Test public void testSubstring() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} " + "{ \"123456789\" #2 #1 substring$ " + // 2 - " \"123456789\" #4 global.max$ substring$ " + // 456789 - " \"123456789\" #1 #9 substring$ " + // 123456789 - " \"123456789\" #1 #10 substring$ " + // 123456789 - " \"123456789\" #1 #99 substring$ " + // 123456789 - - " \"123456789\" #-7 #3 substring$ " + // 123 - " \"123456789\" #-1 #1 substring$ " + // 9 - " \"123456789\" #-1 #3 substring$ " + // 789 - " \"123456789\" #-2 #2 substring$ " + // 78 - - "} EXECUTE {test} "); + TestVM vm = new TestVM(""" + FUNCTION { test } { + "123456789" #2 #1 substring$ % 2 + "123456789" #4 global.max$ substring$ % 456789 + "123456789" #1 #9 substring$ % 123456789 + "123456789" #1 #10 substring$ % 123456789 + "123456789" #1 #99 substring$ % 123456789 + "123456789" #-7 #3 substring$ % 123 + "123456789" #-1 #1 substring$ % 9 + "123456789" #-1 #3 substring$ % 789 + "123456789" #-2 #2 substring$ % 78 + } EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -228,7 +284,6 @@ public void testSubstring() throws RecognitionException { assertEquals("789", vm.getStack().pop()); assertEquals("9", vm.getStack().pop()); assertEquals("123", vm.getStack().pop()); - assertEquals("123456789", vm.getStack().pop()); assertEquals("123456789", vm.getStack().pop()); assertEquals("123456789", vm.getStack().pop()); @@ -239,14 +294,23 @@ public void testSubstring() throws RecognitionException { @Test public void testEmpty() throws RecognitionException, IOException { - TestVM vm = new TestVM("ENTRY {title}{}{} READ STRINGS { s } FUNCTION {test} " + "{ s empty$ " + // FALSE - "\"\" empty$ " + // FALSE - "\" \" empty$ " + // FALSE - " title empty$ " + // FALSE - " \" HALLO \" empty$ } ITERATE {test} "); - + TestVM vm = new TestVM(""" + ENTRY { title } { } { } + READ + STRINGS { s } + FUNCTION { test } { + s empty$ % TRUE + "" empty$ % TRUE + " " empty$ % TRUE + title empty$ % TRUE + " HALLO " empty$ % FALSE + } + ITERATE { test } + """); List v = List.of(TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}")); + vm.render(v); + assertEquals(BstVM.FALSE, vm.getStack().pop()); assertEquals(BstVM.TRUE, vm.getStack().pop()); assertEquals(BstVM.TRUE, vm.getStack().pop()); @@ -257,9 +321,19 @@ public void testEmpty() throws RecognitionException, IOException { @Test public void testDuplicateEmptyPopSwapIf() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {emphasize} " + "{ duplicate$ empty$ " + " { pop$ \"\" } " - + " { \"{\\em \" swap$ * \"}\" * } " + " if$ " + "} " + "FUNCTION {test} {" - + " \"\" emphasize " + " \"Hello\" emphasize " + "}" + "EXECUTE {test} "); + TestVM vm = new TestVM(""" + FUNCTION { emphasize } { + duplicate$ empty$ + { pop$ "" } + { "{\\em " swap$ * "}" * } + if$ + } + FUNCTION { test } { + "" emphasize + "Hello" emphasize + } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -270,25 +344,27 @@ public void testDuplicateEmptyPopSwapIf() throws RecognitionException { @Test public void testChangeCase() throws RecognitionException { - TestVM vm = new TestVM("STRINGS { title } " - + "READ " - + "FUNCTION {format.title}" - + " { duplicate$ empty$ " - + " { pop$ \"\" } " - + " { \"t\" change.case$ } " - + " if$ " - + "} " - + "FUNCTION {test} {" - + " \"hello world\" \"u\" change.case$ format.title " - + " \"Hello World\" format.title " - + " \"\" format.title " - + " \"{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase\" \"u\" change.case$ format.title " - + "}" + "EXECUTE {test} "); + TestVM vm = new TestVM(""" + STRINGS { title } + READ + FUNCTION { format.title } { + duplicate$ empty$ + { pop$ "" } + { "t" change.case$ } + if$ + } + FUNCTION { test } { + "hello world" "u" change.case$ format.title + "Hello World" format.title + "" format.title + "{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase" "u" change.case$ format.title + } + EXECUTE { test } + """); vm.render(Collections.emptyList()); - assertEquals( - "{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase", + assertEquals("{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase", vm.getStack().pop()); assertEquals("", vm.getStack().pop()); assertEquals("Hello world", vm.getStack().pop()); @@ -298,13 +374,19 @@ public void testChangeCase() throws RecognitionException { @Test public void testTextLength() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} {" + " \"hello world\" text.length$ " - + " \"Hello {W}orld\" text.length$ " + " \"\" text.length$ " - + " \"{A}{D}/{Cycle}\" text.length$ " - + " \"{\\This is one character}\" text.length$ " - + " \"{\\This {is} {one} {c{h}}aracter as well}\" text.length$ " - + " \"{\\And this too\" text.length$ " + " \"These are {\\11}\" text.length$ " + "} " - + "EXECUTE {test} "); + TestVM vm = new TestVM(""" + FUNCTION { test } { + "hello world" text.length$ + "Hello {W}orld" text.length$ + "" text.length$ + "{A}{D}/{Cycle}" text.length$ + "{\\This is one character}" text.length$ + "{\\This {is} {one} {c{h}}aracter as well}" text.length$ + "{\\And this too" text.length$ + "These are {\\11}" text.length$ + } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -321,7 +403,10 @@ public void testTextLength() throws RecognitionException { @Test public void testIntToStr() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} { #3 int.to.str$ #9999 int.to.str$}" + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { test } { #3 int.to.str$ #9999 int.to.str$ } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -332,7 +417,10 @@ public void testIntToStr() throws RecognitionException { @Test public void testChrToInt() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} { \"H\" chr.to.int$ }" + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { test } { "H" chr.to.int$ } + EXECUTE { test } + """); vm.render(Collections.emptyList()); @@ -342,7 +430,10 @@ public void testChrToInt() throws RecognitionException { @Test public void testChrToIntIntToChr() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {test} { \"H\" chr.to.int$ int.to.chr$ }" + "EXECUTE {test}"); + TestVM vm = new TestVM(""" + FUNCTION { test } { "H" chr.to.int$ int.to.chr$ } + EXECUTE {test} + """); vm.render(Collections.emptyList()); @@ -352,14 +443,18 @@ public void testChrToIntIntToChr() throws RecognitionException { @Test public void testSort() throws RecognitionException, IOException { - TestVM vm = new TestVM("ENTRY { title } { } { label }" - + "FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } SORT"); - + TestVM vm = new TestVM(""" + ENTRY { title } { } { label } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + SORT + """); List v = List.of( TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), TestBstVM.bibtexString2BibtexEntry("@article{b, author=\"BBB\"}"), TestBstVM.bibtexString2BibtexEntry("@article{d, author=\"DDD\"}"), TestBstVM.bibtexString2BibtexEntry("@article{c, author=\"CCC\"}")); + vm.render(v); List v2 = vm.getEntries(); @@ -371,7 +466,7 @@ public void testSort() throws RecognitionException, IOException { @Test public void testBuildIn() throws RecognitionException { - TestVM vm = new TestVM("EXECUTE {global.max$}"); + TestVM vm = new TestVM("EXECUTE { global.max$ }"); vm.render(Collections.emptyList()); @@ -381,10 +476,17 @@ public void testBuildIn() throws RecognitionException { @Test public void testVariables() throws RecognitionException { - TestVM vm = new TestVM(" STRINGS { t } " - + " FUNCTION {not} { { #0 } { #1 } if$ } " - + " FUNCTION {n.dashify} { \"HELLO-WORLD\" 't := t empty$ not } " - + " EXECUTE {n.dashify} "); + TestVM vm = new TestVM(""" + STRINGS { t } + FUNCTION { not } { + { #0 } { #1 } if$ + } + FUNCTION { n.dashify } { + "HELLO-WORLD" 't := + t empty$ not + } + EXECUTE { n.dashify } + """); vm.render(Collections.emptyList()); @@ -393,38 +495,43 @@ public void testVariables() throws RecognitionException { @Test public void testWhile() throws RecognitionException { - TestVM vm = new TestVM( - "STRINGS { t } " - + "FUNCTION {not} { " - + " { #0 } { #1 } if$ } " - + "FUNCTION {n.dashify} " - + "{ \"HELLO-WORLD\" " - + " 't := " - + " \"\" " - + " { t empty$ not } " - + " { t #1 #1 substring$ \"-\" = " - + " { t #1 #2 substring$ \"--\" = not " - + " { \"--\" * " - + " t #2 global.max$ substring$ 't := " - + " } " - + " { { t #1 #1 substring$ \"-\" = } " - + " { \"-\" * " - + " t #2 global.max$ substring$ 't := " - + " } " - + " while$ " - + " } " - + " if$ " - + " } " - + " { t #1 #1 substring$ * " - + " t #2 global.max$ substring$ 't := " - + " } " - + " if$ " - + " } " - + " while$ " - + " } " - + " EXECUTE {n.dashify} "); - + TestVM vm = new TestVM(""" + STRINGS { t } + FUNCTION {not} { { #0 } { #1 } if$ } + FUNCTION {n.dashify} { + "HELLO-WORLD" 't := + "" + { t empty$ not } % while + { + t #1 #1 substring$ "-" = % if + { + t #1 #2 substring$ "--" = not % if + { + "--" * + t #2 global.max$ substring$ 't := + } + { + { t #1 #1 substring$ "-" = } % while + { + "-" * + t #2 global.max$ substring$ 't := + } + while$ + } + if$ + } + { + t #1 #1 substring$ * + t #2 global.max$ substring$ 't := + } + if$ + } + while$ + } + EXECUTE {n.dashify} + """); List v = Collections.emptyList(); + vm.render(v); assertEquals(1, vm.getStack().size()); @@ -433,14 +540,20 @@ public void testWhile() throws RecognitionException { @Test public void testType() throws RecognitionException, IOException { - TestVM vm = new TestVM("ENTRY { title } { } { label }" - + "FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } SORT FUNCTION {test} { type$ } ITERATE { test }"); - + TestVM vm = new TestVM(""" + ENTRY { title } { } { label } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + SORT + FUNCTION { test } { type$ } + ITERATE { test } + """); List v = List.of( TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), TestBstVM.bibtexString2BibtexEntry("@book{b, author=\"BBB\"}"), TestBstVM.bibtexString2BibtexEntry("@misc{c, author=\"CCC\"}"), TestBstVM.bibtexString2BibtexEntry("@inproceedings{d, author=\"DDD\"}")); + vm.render(v); assertEquals(4, vm.getStack().size()); @@ -452,21 +565,22 @@ public void testType() throws RecognitionException, IOException { @Test public void testMissing() throws RecognitionException, IOException { - TestVM vm = new TestVM( - "ENTRY { title } { } { label } " + - "FUNCTION {presort} { cite$ 'sort.key$ := } " + - "ITERATE {presort} " + - "READ SORT " + - "FUNCTION {test}{ title missing$ cite$ } " + - "ITERATE { test }"); - + TestVM vm = new TestVM(""" + ENTRY { title } { } { label } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { test } { title missing$ cite$ } + ITERATE { test } + """); List v = List.of( t1BibtexEntry(), TestBstVM.bibtexString2BibtexEntry("@article{test, author=\"No title\"}")); + vm.render(v); assertEquals(4, vm.getStack().size()); - assertEquals("test", vm.getStack().pop()); assertEquals(1, vm.getStack().pop()); assertEquals("canh05", vm.getStack().pop()); @@ -475,21 +589,29 @@ public void testMissing() throws RecognitionException, IOException { @Test public void testFormatName() throws RecognitionException { - TestVM vm = new TestVM( - "FUNCTION {format}{ \"Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin\" #1 \"{vv~}{ll}{, jj}{, f}?\" format.name$ }" - + "EXECUTE {format}"); - + TestVM vm = new TestVM(""" + FUNCTION { format }{ "Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin" #1 "{vv~}{ll}{, jj}{, f}?" format.name$ } + EXECUTE { format } + """); List v = Collections.emptyList(); + vm.render(v); + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } @Test public void testFormatName2() throws RecognitionException, IOException { - TestVM vm = new TestVM("ENTRY { author } { } { label } " + "FUNCTION {presort} { cite$ 'sort.key$ := } " - + "ITERATE { presort } " + "READ " + "SORT " - + "FUNCTION {format}{ author #2 \"{vv~}{ll}{, jj}{, f}?\" format.name$ }" + "ITERATE {format}"); + TestVM vm = new TestVM(""" + ENTRY { author } { } { label } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { format }{ author #2 "{vv~}{ll}{, jj}{, f}?" format.name$ } + ITERATE { format } + """); List v = List.of( t1BibtexEntry(), @@ -503,11 +625,16 @@ public void testFormatName2() throws RecognitionException, IOException { @Test public void testCallType() throws RecognitionException, IOException { - TestVM vm = new TestVM( - "ENTRY { title } { } { label } FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } READ SORT " - + "FUNCTION {inproceedings}{ \"InProceedings called on \" title * } " - + "FUNCTION {book}{ \"Book called on \" title * } " + " ITERATE { call.type$ }"); - + TestVM vm = new TestVM(""" + ENTRY { title } { } { label } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { inproceedings }{ "InProceedings called on " title * } + FUNCTION { book }{ "Book called on " title * } + ITERATE { call.type$ } + """); List v = List.of( t1BibtexEntry(), TestBstVM.bibtexString2BibtexEntry("@book{test, title=\"Test\"}")); @@ -515,7 +642,6 @@ public void testCallType() throws RecognitionException, IOException { vm.render(v); assertEquals(2, vm.getStack().size()); - assertEquals("Book called on Test", vm.getStack().pop()); assertEquals( "InProceedings called on Effective work practices for floss development: A model and propositions", @@ -525,9 +651,12 @@ public void testCallType() throws RecognitionException, IOException { @Test public void testIterate() throws RecognitionException, IOException { - TestVM vm = new TestVM("ENTRY { " + " address " + " author " + " title " + " type " - + "} {} { label } " + "FUNCTION {test}{ cite$ } " + "READ " + "ITERATE { test }"); - + TestVM vm = new TestVM(""" + ENTRY { address author title type } {} { label } + FUNCTION { test }{ cite$ } + READ + ITERATE { test } + """); List v = List.of( t1BibtexEntry(), TestBstVM.bibtexString2BibtexEntry("@article{test, title=\"BLA\"}")); @@ -535,7 +664,6 @@ public void testIterate() throws RecognitionException, IOException { vm.render(v); assertEquals(2, vm.getStack().size()); - String s1 = (String) vm.getStack().pop(); String s2 = (String) vm.getStack().pop(); @@ -549,38 +677,37 @@ public void testIterate() throws RecognitionException, IOException { @Test public void testWidth() throws RecognitionException, IOException { - TestVM vm = new TestVM("ENTRY { " + " address " + " author " + " title " + " type " - + "} {} { label } " + - "STRINGS { longest.label } " + - "INTEGERS { number.label longest.label.width } " + - "FUNCTION {initialize.longest.label} " + - "{ \"\" 'longest.label := " + - " #1 'number.label := " + - " #0 'longest.label.width := " + - "} " + - " " + - " FUNCTION {longest.label.pass} " + - " { number.label int.to.str$ 'label := " + - " number.label #1 + 'number.label := " + - " label width$ longest.label.width > " + - " { label 'longest.label := " + - " label width$ 'longest.label.width := " + - " } " + - " 'skip$ " + - " if$ " + - " } " + - " " + - " EXECUTE {initialize.longest.label} " + - " " + - " ITERATE {longest.label.pass} " + - "FUNCTION {begin.bib} " + - "{ preamble$ empty$" + - " 'skip$" + - " { preamble$ write$ newline$ }" + - " if$" + - " \"\\begin{thebibliography}{\" longest.label * \"}\" *" + - "}" + - "EXECUTE {begin.bib}"); + TestVM vm = new TestVM(""" + ENTRY { address author title type } {} { label } + STRINGS { longest.label } + INTEGERS { number.label longest.label.width } + FUNCTION { initialize.longest.label } { + "" 'longest.label := + #1 'number.label := + #0 'longest.label.width := + } + FUNCTION {longest.label.pass} { + number.label int.to.str$ 'label := + number.label #1 + 'number.label := + label width$ longest.label.width > + { + label 'longest.label := + label width$ 'longest.label.width := + } + 'skip$ + if$ + } + EXECUTE { initialize.longest.label } + ITERATE { longest.label.pass } + FUNCTION { begin.bib } { + preamble$ empty$ + 'skip$ + { preamble$ write$ newline$ } + if$ + "\\begin{thebibliography}{" longest.label * "}" * + } + EXECUTE {begin.bib} + """); List v = List.of(t1BibtexEntry()); @@ -592,7 +719,10 @@ public void testWidth() throws RecognitionException, IOException { @Test public void testSwap() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION {a}{ #3 \"Hallo\" swap$ } EXECUTE { a }"); + TestVM vm = new TestVM(""" + FUNCTION { a } { #3 "Hallo" swap$ } + EXECUTE { a } + """); List v = Collections.emptyList(); vm.render(v); diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index 4ac0362be08..0927bf9d76e 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -123,7 +123,15 @@ void testVisitReadCommand() throws IOException { // sort - // identifier + @Test + void testVisitIdentifier() throws IOException { + TestBstVM.TestVM vm = new TestBstVM.TestVM( + "ENTRY {author title booktitle year owner timestamp url}{}{} READ" + + "FUNCTION { test } { }"); + List testEntries = List.of(TestBstVM.t1BibtexEntry()); + + vm.render(testEntries); + } // bstFunction From b95957aaaf3e218e1cf45e13d28a470604ff35d8 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 20 Jun 2022 20:49:26 +0200 Subject: [PATCH 21/39] fix function call --- .../org/jabref/logic/bst/BstFunctions.java | 18 +++++------ .../org/jabref/logic/bst/BstVMVisitor.java | 13 +++----- .../java/org/jabref/logic/bst/TestBstVM.java | 6 ++-- .../jabref/logic/bst/TestBstVMVisitor.java | 32 +++++++++++++++---- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 3357c6094bd..996402a6ef6 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -19,7 +19,6 @@ import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.Tree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -257,24 +256,25 @@ private boolean doAssign(BstEntry context, Object o1, Object o2) { // ToDo: use switch statement pattern matching instead (Java 19) if (o2 instanceof String) { + String value = (String) o2; if ((context != null) && context.localStrings.containsKey(name)) { - context.localStrings.put(name, (String) o2); + context.localStrings.put(name, value); return true; } if (strings.containsKey(name)) { - strings.put(name, (String) o2); + strings.put(name, value); return true; } - return false; } else { + Integer value = (Integer) o2; if ((context != null) && context.localIntegers.containsKey(name)) { - context.localIntegers.put(name, (Integer) o2); + context.localIntegers.put(name, value); return true; } if (integers.containsKey(name)) { - integers.put(name, (Integer) o2); + integers.put(name, value); return true; } } @@ -505,15 +505,15 @@ private void bstFormatName(BstVMVisitor visitor, ParserRuleContext parserRuleCon */ private void bstIf(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (stack.size() < 3) { - throw new BstVMException("Not enough operands on stack for operation ="); + throw new BstVMException("Not enough operands on stack for if$"); } Object f1 = stack.pop(); Object f2 = stack.pop(); Object i = stack.pop(); - if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof Tree)) - && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { + if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) + && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree)) && (i instanceof Integer)) { throw new BstVMException("Expecting two functions and an integer for if$."); } diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index b493291d786..882b4a3d63e 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -201,23 +201,18 @@ public Integer visitIdentifier(BstParser.IdentifierContext ctx) { return BstVM.TRUE; } - if (bstVMContext.functions().containsKey(name)) { - // OK to have a null context - bstVMContext.functions().get(name).execute(this, ctx, selectedBstEntry); - return BstVM.TRUE; - } - throw new BstVMException("No matching identifier found: " + name); } @Override public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { String name = ctx.getChild(0).getText(); - if (!bstVMContext.functions().containsKey(name)) { - throw new BstVMException("Function does not exist: " + name); + if (bstVMContext.functions().containsKey(name)) { + bstVMContext.functions().get(name).execute(this, ctx, selectedBstEntry); + } else { + visit(ctx.getChild(0)); } - bstVMContext.functions().get(name).execute(this, ctx, selectedBstEntry); return BstVM.TRUE; } diff --git a/src/test/java/org/jabref/logic/bst/TestBstVM.java b/src/test/java/org/jabref/logic/bst/TestBstVM.java index 51c934b8cc9..8b7450ecfc0 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVM.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVM.java @@ -115,7 +115,7 @@ public void testExecuteSimple() throws RecognitionException { @Test public void testExecuteSimple2() throws RecognitionException { TestVM vm = new TestVM(""" - FUNCTION {a}{ + FUNCTION { a } { #5 #5 = #1 #2 = #3 #4 < @@ -149,7 +149,7 @@ public void testExecuteSimple2() throws RecognitionException { public void testIfSkipPop() throws RecognitionException { TestVM vm = new TestVM(""" FUNCTION { not } { { #0 } { #1 } if$ } - FUNCTION { and } { 'skip$ { pop$ #0 } if$ }" + FUNCTION { and } { 'skip$ { pop$ #0 } if$ } FUNCTION { or } { { pop$ #1 } 'skip$ if$ } FUNCTION { test } { #1 #1 and @@ -678,7 +678,7 @@ public void testIterate() throws RecognitionException, IOException { @Test public void testWidth() throws RecognitionException, IOException { TestVM vm = new TestVM(""" - ENTRY { address author title type } {} { label } + ENTRY { address author title type } { } { label } STRINGS { longest.label } INTEGERS { number.label longest.label.width } FUNCTION { initialize.longest.label } { diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index 0927bf9d76e..af0d4e19130 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -60,9 +60,11 @@ void testVisitFunctionCommand() { // ToDo: Belongs in testBstFunction @Test void testAssignFunction() { - TestBstVM.TestVM vm = new TestBstVM.TestVM("INTEGERS { test.var }" + - "FUNCTION {test.func} { #1 'test.var := }" + - "EXECUTE { test.func }"); + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + INTEGERS { test.var } + FUNCTION { test.func } { #1 'test.var := } + EXECUTE { test.func } + """); vm.render(Collections.emptyList()); @@ -125,12 +127,30 @@ void testVisitReadCommand() throws IOException { @Test void testVisitIdentifier() throws IOException { - TestBstVM.TestVM vm = new TestBstVM.TestVM( - "ENTRY {author title booktitle year owner timestamp url}{}{} READ" - + "FUNCTION { test } { }"); + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + ENTRY { } { local.variable } { local.label } + READ + STRINGS { label } + INTEGERS { variable } + FUNCTION { test } { + #1 'local.variable := + #2 'variable := + "TEST" 'local.label := + "TEST-GLOBAL" 'label := + local.label local.variable + label variable + } + ITERATE { test } + """); List testEntries = List.of(TestBstVM.t1BibtexEntry()); vm.render(testEntries); + + assertEquals(2, vm.getStack().pop()); + assertEquals("TEST-GLOBAL", vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals("TEST", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); } // bstFunction From ceba234f4561901df277396245dfbe861506380b Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sat, 25 Jun 2022 21:17:13 +0200 Subject: [PATCH 22/39] fixed call.type --- .../org/jabref/logic/bst/BstFunctions.java | 10 +++++----- .../org/jabref/logic/bst/BstVMVisitor.java | 11 ++++------- .../jabref/logic/bst/TestBstVMVisitor.java | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 996402a6ef6..5bed189fce6 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -334,9 +334,9 @@ public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntry) { if (bstEntry == null) { this.execute(visitor, parserRuleContext); // Throw error + } else { + functions.get(bstEntry.entry.getType().getName()).execute(visitor, parserRuleContext, bstEntry); } - - functions.get(parserRuleContext.getText()).execute(visitor, parserRuleContext, bstEntry); } } @@ -508,8 +508,8 @@ private void bstIf(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { throw new BstVMException("Not enough operands on stack for if$"); } - Object f1 = stack.pop(); Object f2 = stack.pop(); + Object f1 = stack.pop(); Object i = stack.pop(); if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) @@ -518,9 +518,9 @@ private void bstIf(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { } if (((Integer) i) > 0) { - visitor.visit((ParseTree) f2); + new BstVMVisitor.BstVMFunction<>(f1).execute(visitor, parserRuleContext); } else { - visitor.visit((ParseTree) f1); + new BstVMVisitor.BstVMFunction<>(f2).execute(visitor, parserRuleContext); } } diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 882b4a3d63e..63d0c4f85c8 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -237,17 +237,14 @@ public Integer visitStackitem(BstParser.StackitemContext ctx) { return BstVM.TRUE; } - private class BstVMFunction implements BstFunctions.BstFunction { - private final T expression; - - BstVMFunction(T expression) { - this.expression = expression; - } + public record BstVMFunction(T expression) implements BstFunctions.BstFunction { @Override public void execute(BstVMVisitor visitor, ParserRuleContext bstFunctionContext) { if (expression instanceof String str) { - bstVMContext.stack().push(str.substring(1, str.length() - 1)); // Macro + visitor.bstVMContext.stack().push(str.substring(1, str.length() - 1)); + } else if (expression instanceof Integer integer) { + visitor.bstVMContext.stack().push(integer); } else if (expression instanceof ParserRuleContext ctx) { visitor.visit(ctx); } diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index af0d4e19130..3f76143a019 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -153,6 +153,25 @@ void testVisitIdentifier() throws IOException { assertEquals(0, vm.getStack().size()); } + @Test + void testIf() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { path1 } { #1 } + FUNCTION { path0 } { #0 } + FUNCTION { test } { + #1 path1 path0 if$ + #0 path1 path0 if$ + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(0, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + // bstFunction // stackitem From ce508ccc76e41723077ea36be658356d78fa15f8 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Sat, 25 Jun 2022 22:15:24 +0200 Subject: [PATCH 23/39] wip if --- src/main/java/org/jabref/logic/bst/BstFunctions.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 5bed189fce6..63800955ec3 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -508,19 +508,20 @@ private void bstIf(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { throw new BstVMException("Not enough operands on stack for if$"); } - Object f2 = stack.pop(); Object f1 = stack.pop(); + Object f2 = stack.pop(); Object i = stack.pop(); if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) - && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree)) && (i instanceof Integer)) { + && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree)) + && (i instanceof Integer)) { throw new BstVMException("Expecting two functions and an integer for if$."); } if (((Integer) i) > 0) { - new BstVMVisitor.BstVMFunction<>(f1).execute(visitor, parserRuleContext); - } else { new BstVMVisitor.BstVMFunction<>(f2).execute(visitor, parserRuleContext); + } else { + new BstVMVisitor.BstVMFunction<>(f1).execute(visitor, parserRuleContext); } } From 424e168d70930a5f12dbf8e685857e3b603c93d5 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 27 Jun 2022 23:11:46 +0200 Subject: [PATCH 24/39] wip if --- src/main/java/org/jabref/logic/bst/BstVMVisitor.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 63d0c4f85c8..3cfdec2e28d 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -201,6 +201,10 @@ public Integer visitIdentifier(BstParser.IdentifierContext ctx) { return BstVM.TRUE; } + if (bstVMContext.functions().containsKey(name)) { + bstVMContext.functions().get(name).execute(this, ctx); + } + throw new BstVMException("No matching identifier found: " + name); } @@ -242,11 +246,13 @@ public record BstVMFunction(T expression) implements BstFunctions.BstFunction @Override public void execute(BstVMVisitor visitor, ParserRuleContext bstFunctionContext) { if (expression instanceof String str) { - visitor.bstVMContext.stack().push(str.substring(1, str.length() - 1)); + visitor.bstVMContext.stack().push(str.substring(1, str.length() - 1)); // Macro + } else if (expression instanceof ParserRuleContext ctx) { + visitor.visit(ctx); // Function } else if (expression instanceof Integer integer) { visitor.bstVMContext.stack().push(integer); - } else if (expression instanceof ParserRuleContext ctx) { - visitor.visit(ctx); + } else if (expression instanceof Identifier identifier) { + // Fixme: should push parsed identifier } } } From b91006db1481187cb2440e357156e984aa5b85ab Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 27 Jun 2022 23:28:17 +0200 Subject: [PATCH 25/39] addedTests --- .../jabref/logic/bst/TestBstVMVisitor.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index 3f76143a019..0207d1d16c2 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -172,6 +172,70 @@ void testIf() { assertEquals(0, vm.getStack().size()); } + @Test + void testOrTrueTrue() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #1 #1 or + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testOrFalseTrue() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #0 #1 or + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testOrTrueFalse() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #1 #0 or + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testOrFalseFalse() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #0 #0 or + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + // bstFunction // stackitem From 9726f3be1b4c827410c7231ebf853f21fe175441 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Tue, 28 Jun 2022 19:41:35 +0200 Subject: [PATCH 26/39] finally fixed if --- .../org/jabref/logic/bst/BstVMVisitor.java | 10 ++++-- .../jabref/logic/bst/TestBstVMVisitor.java | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 3cfdec2e28d..c672ce438f5 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -234,6 +234,8 @@ public Integer visitStackitem(BstParser.StackitemContext ctx) { case BstParser.QUOTED -> bstVMContext.stack().push(new Identifier(token.getText().substring(1))); } + } else if (childNode instanceof BstParser.StackContext) { + bstVMContext.stack().push(childNode); } else { visit(childNode); } @@ -247,12 +249,14 @@ public record BstVMFunction(T expression) implements BstFunctions.BstFunction public void execute(BstVMVisitor visitor, ParserRuleContext bstFunctionContext) { if (expression instanceof String str) { visitor.bstVMContext.stack().push(str.substring(1, str.length() - 1)); // Macro - } else if (expression instanceof ParserRuleContext ctx) { - visitor.visit(ctx); // Function } else if (expression instanceof Integer integer) { visitor.bstVMContext.stack().push(integer); } else if (expression instanceof Identifier identifier) { - // Fixme: should push parsed identifier + if (visitor.bstVMContext.functions().containsKey(identifier.name)) { + visitor.bstVMContext.functions().get(identifier.name).execute(visitor, bstFunctionContext); + } + } else if (expression instanceof ParserRuleContext ctx) { + visitor.visit(ctx); // Function } } } diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index 0207d1d16c2..95f9e3a6d12 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -236,6 +236,38 @@ void testOrFalseFalse() { assertEquals(0, vm.getStack().size()); } + @Test + void testOrFiveSix() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #5 #6 or + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testOrFalseFive() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #0 #5 or + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + // bstFunction // stackitem From 637048e1e41d2500b1b4c565956915e6eddc68d8 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Tue, 12 Jul 2022 18:02:32 +0200 Subject: [PATCH 27/39] wip tests --- src/main/java/org/jabref/logic/bst/BstVM.java | 12 +- .../org/jabref/logic/bst/BstVMContext.java | 3 - .../jabref/logic/bst/TestBstFunctions.java | 643 ++++++++++++++++ .../java/org/jabref/logic/bst/TestBstVM.java | 711 ++---------------- .../jabref/logic/bst/TestBstVMVisitor.java | 207 ++--- 5 files changed, 769 insertions(+), 807 deletions(-) create mode 100644 src/test/java/org/jabref/logic/bst/TestBstFunctions.java diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java index 4c11de5f1f9..cea818bd442 100644 --- a/src/main/java/org/jabref/logic/bst/BstVM.java +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -70,7 +70,7 @@ private static ParseTree charStream2CommonTree(CharStream query) { } /** - * Transforms the given list of BibEntries to a rendered list of references using the underlying bst file + * Transforms the given list of BibEntries to a rendered list of references using the parsed bst program * * @param bibEntries list of entries to convert * @param bibDatabase (may be null) the bibDatabase used for resolving strings / crossref @@ -84,17 +84,19 @@ public String render(Collection bibEntries, BibDatabase bibDatabase) { entries.add(new BstEntry(entry)); } - StringBuilder bbl = new StringBuilder(); + StringBuilder resultBuffer = new StringBuilder(); BstVMContext bstVMContext = new BstVMContext(entries, bibDatabase); - bstVMContext.functions().putAll(new BstFunctions(bstVMContext, bbl).getBuiltInFunctions()); + bstVMContext.functions().putAll(new BstFunctions(bstVMContext, resultBuffer).getBuiltInFunctions()); + bstVMContext.integers().put("entry.max$", Integer.MAX_VALUE); + bstVMContext.integers().put("global.max$", Integer.MAX_VALUE); - BstVMVisitor bstVMVisitor = new BstVMVisitor(bstVMContext, bbl); + BstVMVisitor bstVMVisitor = new BstVMVisitor(bstVMContext, resultBuffer); bstVMVisitor.visit(tree); latestContext = bstVMContext; - return bbl.toString(); + return resultBuffer.toString(); } public String render(Collection bibEntries) { diff --git a/src/main/java/org/jabref/logic/bst/BstVMContext.java b/src/main/java/org/jabref/logic/bst/BstVMContext.java index 0f0db228641..8f9253a7ca2 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMContext.java +++ b/src/main/java/org/jabref/logic/bst/BstVMContext.java @@ -16,8 +16,5 @@ public record BstVMContext(List entries, public BstVMContext(List entries, BibDatabase bibDatabase) { this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), new Stack<>(), bibDatabase); - - this.integers.put("entry.max$", Integer.MAX_VALUE); - this.integers.put("global.max$", Integer.MAX_VALUE); } } diff --git a/src/test/java/org/jabref/logic/bst/TestBstFunctions.java b/src/test/java/org/jabref/logic/bst/TestBstFunctions.java new file mode 100644 index 00000000000..439bd78b828 --- /dev/null +++ b/src/test/java/org/jabref/logic/bst/TestBstFunctions.java @@ -0,0 +1,643 @@ +package org.jabref.logic.bst; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.antlr.v4.runtime.RecognitionException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TestBstFunctions { + @Test + public void testCompareFunctions() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test.compare } { + #5 #5 = % TRUE + #1 #2 = % FALSE + #3 #4 < % TRUE + #4 #3 < % FALSE + #4 #4 < % FALSE + #3 #4 > % FALSE + #4 #3 > % TRUE + #4 #4 > % FALSE + "H" "H" = % TRUE + "H" "Ha" = % TRUE + } + EXECUTE { test.compare } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testArithmeticFunctions() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { + #1 #1 + % 2 + #5 #2 - % 3 + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(3, vm.getStack().pop()); + assertEquals(2, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testArithmeticFunctionTypeMismatch() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { + #1 "HELLO" + % Should throw exception + } + EXECUTE { test } + """); + + assertThrows(BstVMException.class, () -> vm.render(Collections.emptyList())); + } + + @Test + public void testStringOperations() throws RecognitionException { + // Test for concat (*) and add.period + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { + "H" "ello" * % Hello + "Johnny" add.period$ % Johnny. + "Johnny." add.period$ % Johnny. + "Johnny!" add.period$ % Johnny! + "Johnny?" add.period$ % Johnny? + "Johnny} }}}" add.period$ % Johnny.} + "Johnny!}" add.period$ % Johnny!} + "Johnny?}" add.period$ % Johnny?} + "Johnny.}" add.period$ % Johnny.} + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("Johnny.}", vm.getStack().pop()); + assertEquals("Johnny?}", vm.getStack().pop()); + assertEquals("Johnny!}", vm.getStack().pop()); + assertEquals("Johnny.}", vm.getStack().pop()); + assertEquals("Johnny?", vm.getStack().pop()); + assertEquals("Johnny!", vm.getStack().pop()); + assertEquals("Johnny.", vm.getStack().pop()); + assertEquals("Johnny.", vm.getStack().pop()); + assertEquals("Hello", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testMissing() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + ENTRY { title } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { test } { title missing$ cite$ } + ITERATE { test } + """); + List testEntries = List.of( + TestBstVM.defaultTestEntry(), + new BibEntry(StandardEntryType.Article) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "No title")); + + vm.render(testEntries); + + assertEquals("test", vm.getStack().pop()); // cite + assertEquals(BstVM.TRUE, vm.getStack().pop()); // missing title + assertEquals("canh05", vm.getStack().pop()); // cite + assertEquals(BstVM.FALSE, vm.getStack().pop()); // missing title + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testNumNames() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { + "Johnny Foo { and } Mary Bar" num.names$ + "Johnny Foo and Mary Bar" num.names$ + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(2, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testSubstring() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { + "123456789" #2 #1 substring$ % 2 + "123456789" #4 global.max$ substring$ % 456789 + "123456789" #1 #9 substring$ % 123456789 + "123456789" #1 #10 substring$ % 123456789 + "123456789" #1 #99 substring$ % 123456789 + "123456789" #-7 #3 substring$ % 123 + "123456789" #-1 #1 substring$ % 9 + "123456789" #-1 #3 substring$ % 789 + "123456789" #-2 #2 substring$ % 78 + } EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("78", vm.getStack().pop()); + assertEquals("789", vm.getStack().pop()); + assertEquals("9", vm.getStack().pop()); + assertEquals("123", vm.getStack().pop()); + assertEquals("123456789", vm.getStack().pop()); + assertEquals("123456789", vm.getStack().pop()); + assertEquals("123456789", vm.getStack().pop()); + assertEquals("456789", vm.getStack().pop()); + assertEquals("2", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testEmpty() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + ENTRY { title } { } { } + READ + STRINGS { s } + FUNCTION { test } { + s empty$ % TRUE + "" empty$ % TRUE + " " empty$ % TRUE + title empty$ % TRUE + " HALLO " empty$ % FALSE + } + ITERATE { test } + """); + List testEntry = List.of(new BibEntry(StandardEntryType.Article)); + + vm.render(testEntry); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testFormatNameStatic() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { format }{ "Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin" #1 "{vv~}{ll}{, jj}{, f}?" format.name$ } + EXECUTE { format } + """); + List v = Collections.emptyList(); + + vm.render(v); + + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testFormatNameInEntries() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + ENTRY { author } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { format }{ author #2 "{vv~}{ll}{, jj}{, f}?" format.name$ } + ITERATE { format } + """); + List testEntries = List.of( + TestBstVM.defaultTestEntry(), + new BibEntry(StandardEntryType.Book) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin")); + + vm.render(testEntries); + + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); + assertEquals("Annabi, H?", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testChangeCase() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + STRINGS { title } + READ + FUNCTION { format.title } { + duplicate$ empty$ + { pop$ "" } + { "t" change.case$ } + if$ + } + FUNCTION { test } { + "hello world" "u" change.case$ format.title + "Hello World" format.title + "" format.title + "{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase" "u" change.case$ format.title + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase", + vm.getStack().pop()); + assertEquals("", vm.getStack().pop()); + assertEquals("Hello world", vm.getStack().pop()); + assertEquals("Hello world", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testTextLength() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { + "hello world" text.length$ % 11 + "Hello {W}orld" text.length$ % 11 + "" text.length$ % 0 + "{A}{D}/{Cycle}" text.length$ % 8 + "{\\This is one character}" text.length$ % 1 + "{\\This {is} {one} {c{h}}aracter as well}" text.length$ % 1 + "{\\And this too" text.length$ % 1 + "These are {\\11}" text.length$ % 11 + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(11, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(8, vm.getStack().pop()); + assertEquals(0, vm.getStack().pop()); + assertEquals(11, vm.getStack().pop()); + assertEquals(11, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testIntToStr() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { #3 int.to.str$ #9999 int.to.str$ } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("9999", vm.getStack().pop()); + assertEquals("3", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testChrToInt() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { "H" chr.to.int$ } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(72, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testChrToIntIntToChr() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { test } { "H" chr.to.int$ int.to.chr$ } + EXECUTE {test} + """); + + vm.render(Collections.emptyList()); + + assertEquals("H", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testType() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + ENTRY { } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + SORT + FUNCTION { test } { type$ } + ITERATE { test } + """); + List testEntries = List.of( + new BibEntry(StandardEntryType.Article).withCitationKey("a"), + new BibEntry(StandardEntryType.Book).withCitationKey("b"), + new BibEntry(StandardEntryType.Misc).withCitationKey("c"), + new BibEntry(StandardEntryType.InProceedings).withCitationKey("d")); + + vm.render(testEntries); + + assertEquals("inproceedings", vm.getStack().pop()); + assertEquals("misc", vm.getStack().pop()); + assertEquals("book", vm.getStack().pop()); + assertEquals("article", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testCallType() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + ENTRY { title } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { inproceedings }{ "InProceedings called on " title * } + FUNCTION { book }{ "Book called on " title * } + ITERATE { call.type$ } + """); + List testEntries = List.of( + TestBstVM.defaultTestEntry(), + new BibEntry(StandardEntryType.Book) + .withCitationKey("test") + .withField(StandardField.TITLE, "Test")); + + vm.render(testEntries); + + assertEquals("Book called on Test", vm.getStack().pop()); + assertEquals( + "InProceedings called on Effective work practices for floss development: A model and propositions", + vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testSwap() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { a } { #3 "Hallo" swap$ } + EXECUTE { a } + """); + + List v = Collections.emptyList(); + vm.render(v); + + assertEquals(3, vm.getStack().pop()); + assertEquals("Hallo", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testAssignFunction() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + INTEGERS { test.var } + FUNCTION { test.func } { #1 'test.var := } + EXECUTE { test.func } + """); + + vm.render(Collections.emptyList()); + + Map functions = vm.getFunctions(); + assertTrue(functions.containsKey("test.func")); + assertNotNull(functions.get("test.func")); + assertEquals(1, vm.latestContext.integers().get("test.var")); + } + + @Test + void testSimpleIf() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { path1 } { #1 } + FUNCTION { path0 } { #0 } + FUNCTION { test } { + #1 path1 path0 if$ + #0 path1 path0 if$ + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(0, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testSimpleWhile() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + INTEGERS { i } + FUNCTION { test } { + #3 'i := + { i } + { + i + i #1 - + 'i := + } + while$ + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(1, vm.getStack().pop()); + assertEquals(2, vm.getStack().pop()); + assertEquals(3, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testNestedControlFunctions() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + STRINGS { t } + FUNCTION { not } { { #0 } { #1 } if$ } + FUNCTION { n.dashify } { + "HELLO-WORLD" 't := + "" + { t empty$ not } % while + { + t #1 #1 substring$ "-" = % if + { + t #1 #2 substring$ "--" = not % if + { + "--" * + t #2 global.max$ substring$ 't := + } + { + { t #1 #1 substring$ "-" = } % while + { + "-" * + t #2 global.max$ substring$ 't := + } + while$ + } + if$ + } + { + t #1 #1 substring$ * + t #2 global.max$ substring$ 't := + } + if$ + } + while$ + } + EXECUTE { n.dashify } + """); + List v = Collections.emptyList(); + + vm.render(v); + + assertEquals(1, vm.getStack().size()); + assertEquals("HELLO--WORLD", vm.getStack().pop()); + } + + @Test + public void testLogic() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { not } { { #0 } { #1 } if$ } + FUNCTION { and } { 'skip$ { pop$ #0 } if$ } + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #1 #1 and + #0 #1 and + #1 #0 and + #0 #0 and + #0 not + #1 not + #1 #1 or + #0 #1 or + #1 #0 or + #0 #0 or + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + + + @Test + public void testWidth() throws RecognitionException { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + ENTRY { address author title type } { } { label } + STRINGS { longest.label } + INTEGERS { number.label longest.label.width } + FUNCTION { initialize.longest.label } { + "" 'longest.label := + #1 'number.label := + #0 'longest.label.width := + } + FUNCTION {longest.label.pass} { + number.label int.to.str$ 'label := + number.label #1 + 'number.label := + label width$ longest.label.width > + { + label 'longest.label := + label width$ 'longest.label.width := + } + 'skip$ + if$ + } + EXECUTE { initialize.longest.label } + ITERATE { longest.label.pass } + FUNCTION { begin.bib } { + preamble$ empty$ + 'skip$ + { preamble$ write$ newline$ } + if$ + "\\begin{thebibliography}{" longest.label * "}" * + } + EXECUTE {begin.bib} + """); + + List testEntries = List.of(TestBstVM.defaultTestEntry()); + + vm.render(testEntries); + + assertTrue(vm.getIntegers().containsKey("longest.label.width")); + assertEquals("\\begin{thebibliography}{1}", vm.getStack().pop()); + } + + /* + ToDo: + - cite + - duplicate + - newline + - preamble + - purify + - quote + - stack + - text.prefix + - top + - warning + - write + - skip + - pop + */ + + @Test + public void testDuplicateEmptyPopSwapIf() throws RecognitionException { + // ToDo: Make better tests for this + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + FUNCTION { emphasize } { + duplicate$ empty$ + { pop$ "" } + { "{\\em " swap$ * "}" * } + if$ + } + FUNCTION { test } { + "" emphasize + "Hello" emphasize + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("{\\em Hello}", vm.getStack().pop()); + assertEquals("", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } +} diff --git a/src/test/java/org/jabref/logic/bst/TestBstVM.java b/src/test/java/org/jabref/logic/bst/TestBstVM.java index 8b7450ecfc0..dea343d8093 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVM.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVM.java @@ -1,46 +1,63 @@ package org.jabref.logic.bst; import java.io.IOException; -import java.io.StringReader; import java.nio.file.Path; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Stack; -import org.jabref.logic.importer.ImportFormatPreferences; -import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.importer.fileformat.BibtexParser; import org.jabref.model.entry.BibEntry; -import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.RecognitionException; import org.junit.jupiter.api.Test; -import org.mockito.Answers; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; public class TestBstVM { + public static BibEntry defaultTestEntry() { + /* + @inproceedings{canh05, + author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}, + title = {Effective work practices for floss development: A model and propositions}, + booktitle = {Hawaii International Conference On System Sciences (HICSS)}, + year = {2005}, + owner = {oezbek}, + timestamp = {2006.05.29}, + url = {http://james.howison.name/publications.html}} + */ + + return new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("canh05") + .withField(StandardField.AUTHOR, "Crowston, K. and Annabi, H. and Howison, J. and Masango, C.") + .withField(StandardField.TITLE, "Effective work practices for floss development: A model and propositions") + .withField(StandardField.BOOKTITLE, "Hawaii International Conference On System Sciences (HICSS)") + .withField(StandardField.YEAR, "2005") + .withField(StandardField.OWNER, "oezbek") + .withField(StandardField.TIMESTAMP, "2006.05.29") + .withField(StandardField.URL, "http://james.howison.name/publications.html"); + } + @Test public void testAbbrv() throws RecognitionException, IOException { TestVM vm = new TestVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); - List v = List.of(t1BibtexEntry()); + List testEntries = List.of(defaultTestEntry()); String expected = "\\begin{thebibliography}{1}\\bibitem{canh05}K.~Crowston, H.~Annabi, J.~Howison, and C.~Masango.\\newblock Effective work practices for floss development: A model and propositions.\\newblock In {\\em Hawaii International Conference On System Sciences (HICSS)}, 2005.\\end{thebibliography}"; + String result = vm.render(testEntries); - assertEquals(expected.replaceAll("\\s", ""), vm.render(v).replaceAll("\\s", "")); + assertEquals( + expected.replaceAll("\\s", ""), + result.replaceAll("\\s", "")); } @Test - public void testSimple() throws RecognitionException, IOException { + public void testSimple() throws RecognitionException { TestVM vm = new TestVM(""" ENTRY { address author title type } { } { label } INTEGERS { output.state before.all mid.sentence after.sentence after.block } @@ -53,9 +70,9 @@ public void testSimple() throws RecognitionException, IOException { STRINGS { s t } READ """); - List v = List.of(t1BibtexEntry()); + List testEntries = List.of(defaultTestEntry()); - vm.render(v); + vm.render(testEntries); assertEquals(2, vm.getStrings().size()); assertEquals(7, vm.getIntegers().size()); @@ -65,20 +82,23 @@ public void testSimple() throws RecognitionException, IOException { } @Test - public void testLabel() throws RecognitionException, IOException { + public void testLabel() throws RecognitionException { TestVM vm = new TestVM(""" ENTRY { title } {} { label } - FUNCTION { test } { label #0 = title 'label := #5 label #6 pop$ } + FUNCTION { test } { + label #0 = + title 'label := + #5 label #6 pop$ } READ ITERATE { test } """); - List v = List.of(t1BibtexEntry()); + List testEntries = List.of(defaultTestEntry()); - vm.render(v); + vm.render(testEntries); - assertEquals("Effective work practices for floss development: A model and propositions", vm - .getStack() - .pop()); + assertEquals( + "Effective work practices for floss development: A model and propositions", + vm.getStack().pop()); } @Test @@ -89,381 +109,6 @@ public void testQuote() throws RecognitionException { assertEquals("\"\"", vm.getStack().pop()); } - @Test - public void testFunction1() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION { init.state.consts }{ #0 'before.all := } "); - - vm.render(Collections.emptyList()); - - assertEquals(38, vm.getFunctions().size()); - assertNotNull(vm.getFunctions().get("init.state.consts")); - } - - @Test - public void testExecuteSimple() throws RecognitionException { - TestVM vm = new TestVM(""" - INTEGERS { variable.a } - FUNCTION { init.state.consts }{ #5 'variable.a := } - EXECUTE { init.state.consts } - """); - - vm.render(Collections.emptyList()); - - assertEquals(Integer.valueOf(5), vm.getIntegers().get("variable.a")); - } - - @Test - public void testExecuteSimple2() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { a } { - #5 #5 = - #1 #2 = - #3 #4 < - #4 #3 < - #4 #4 < - #3 #4 > - #4 #3 > - #4 #4 > - "H" "H" = - "H" "Ha" = - } - EXECUTE { a } - """); - - vm.render(Collections.emptyList()); - - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testIfSkipPop() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { not } { { #0 } { #1 } if$ } - FUNCTION { and } { 'skip$ { pop$ #0 } if$ } - FUNCTION { or } { { pop$ #1 } 'skip$ if$ } - FUNCTION { test } { - #1 #1 and - #0 #1 and - #1 #0 and - #0 #0 and - #0 not - #1 not - #1 #1 or - #0 #1 or - #1 #0 or - #0 #0 or - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testArithmetic() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { - #1 #1 + - #5 #2 - - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(3, vm.getStack().pop()); - assertEquals(2, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testArithmetic2() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { - #1 "HELLO" + % Throws exception - #5 #2 - - } - EXECUTE { test } - """); - - assertThrows(BstVMException.class, () -> vm.render(Collections.emptyList())); - } - - @Test - public void testNumNames() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { "Johnny Foo and Mary Bar" num.names$ } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(2, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testNumNames2() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { "Johnny Foo { and } Mary Bar" num.names$ } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(1, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testStringOps1() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { - "H" "allo" * "Johnny" add.period$ "Johnny." add.period$ - "Johnny!" add.period$ "Johnny?" add.period$ "Johnny} }}}" add.period$ - "Johnny!}" add.period$ "Johnny?}" add.period$ "Johnny.}" add.period$ - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals("Johnny.}", vm.getStack().pop()); - assertEquals("Johnny?}", vm.getStack().pop()); - assertEquals("Johnny!}", vm.getStack().pop()); - assertEquals("Johnny.}", vm.getStack().pop()); - assertEquals("Johnny?", vm.getStack().pop()); - assertEquals("Johnny!", vm.getStack().pop()); - assertEquals("Johnny.", vm.getStack().pop()); - assertEquals("Johnny.", vm.getStack().pop()); - assertEquals("Hallo", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testSubstring() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { - "123456789" #2 #1 substring$ % 2 - "123456789" #4 global.max$ substring$ % 456789 - "123456789" #1 #9 substring$ % 123456789 - "123456789" #1 #10 substring$ % 123456789 - "123456789" #1 #99 substring$ % 123456789 - "123456789" #-7 #3 substring$ % 123 - "123456789" #-1 #1 substring$ % 9 - "123456789" #-1 #3 substring$ % 789 - "123456789" #-2 #2 substring$ % 78 - } EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals("78", vm.getStack().pop()); - assertEquals("789", vm.getStack().pop()); - assertEquals("9", vm.getStack().pop()); - assertEquals("123", vm.getStack().pop()); - assertEquals("123456789", vm.getStack().pop()); - assertEquals("123456789", vm.getStack().pop()); - assertEquals("123456789", vm.getStack().pop()); - assertEquals("456789", vm.getStack().pop()); - assertEquals("2", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testEmpty() throws RecognitionException, IOException { - TestVM vm = new TestVM(""" - ENTRY { title } { } { } - READ - STRINGS { s } - FUNCTION { test } { - s empty$ % TRUE - "" empty$ % TRUE - " " empty$ % TRUE - title empty$ % TRUE - " HALLO " empty$ % FALSE - } - ITERATE { test } - """); - List v = List.of(TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}")); - - vm.render(v); - - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testDuplicateEmptyPopSwapIf() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { emphasize } { - duplicate$ empty$ - { pop$ "" } - { "{\\em " swap$ * "}" * } - if$ - } - FUNCTION { test } { - "" emphasize - "Hello" emphasize - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals("{\\em Hello}", vm.getStack().pop()); - assertEquals("", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testChangeCase() throws RecognitionException { - TestVM vm = new TestVM(""" - STRINGS { title } - READ - FUNCTION { format.title } { - duplicate$ empty$ - { pop$ "" } - { "t" change.case$ } - if$ - } - FUNCTION { test } { - "hello world" "u" change.case$ format.title - "Hello World" format.title - "" format.title - "{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase" "u" change.case$ format.title - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals("{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase", - vm.getStack().pop()); - assertEquals("", vm.getStack().pop()); - assertEquals("Hello world", vm.getStack().pop()); - assertEquals("Hello world", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testTextLength() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { - "hello world" text.length$ - "Hello {W}orld" text.length$ - "" text.length$ - "{A}{D}/{Cycle}" text.length$ - "{\\This is one character}" text.length$ - "{\\This {is} {one} {c{h}}aracter as well}" text.length$ - "{\\And this too" text.length$ - "These are {\\11}" text.length$ - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(11, vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals(8, vm.getStack().pop()); - assertEquals(0, vm.getStack().pop()); - assertEquals(11, vm.getStack().pop()); - assertEquals(11, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testIntToStr() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { #3 int.to.str$ #9999 int.to.str$ } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals("9999", vm.getStack().pop()); - assertEquals("3", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testChrToInt() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { "H" chr.to.int$ } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(72, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testChrToIntIntToChr() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { test } { "H" chr.to.int$ int.to.chr$ } - EXECUTE {test} - """); - - vm.render(Collections.emptyList()); - - assertEquals("H", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testSort() throws RecognitionException, IOException { - TestVM vm = new TestVM(""" - ENTRY { title } { } { label } - FUNCTION { presort } { cite$ 'sort.key$ := } - ITERATE { presort } - SORT - """); - List v = List.of( - TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), - TestBstVM.bibtexString2BibtexEntry("@article{b, author=\"BBB\"}"), - TestBstVM.bibtexString2BibtexEntry("@article{d, author=\"DDD\"}"), - TestBstVM.bibtexString2BibtexEntry("@article{c, author=\"CCC\"}")); - - vm.render(v); - - List v2 = vm.getEntries(); - assertEquals(Optional.of("a"), v2.get(0).entry.getCitationKey()); - assertEquals(Optional.of("b"), v2.get(1).entry.getCitationKey()); - assertEquals(Optional.of("c"), v2.get(2).entry.getCitationKey()); - assertEquals(Optional.of("d"), v2.get(3).entry.getCitationKey()); - } - @Test public void testBuildIn() throws RecognitionException { TestVM vm = new TestVM("EXECUTE { global.max$ }"); @@ -493,274 +138,18 @@ public void testVariables() throws RecognitionException { assertEquals(BstVM.TRUE, vm.getStack().pop()); } - @Test - public void testWhile() throws RecognitionException { - TestVM vm = new TestVM(""" - STRINGS { t } - FUNCTION {not} { { #0 } { #1 } if$ } - FUNCTION {n.dashify} { - "HELLO-WORLD" 't := - "" - { t empty$ not } % while - { - t #1 #1 substring$ "-" = % if - { - t #1 #2 substring$ "--" = not % if - { - "--" * - t #2 global.max$ substring$ 't := - } - { - { t #1 #1 substring$ "-" = } % while - { - "-" * - t #2 global.max$ substring$ 't := - } - while$ - } - if$ - } - { - t #1 #1 substring$ * - t #2 global.max$ substring$ 't := - } - if$ - } - while$ - } - EXECUTE {n.dashify} - """); - List v = Collections.emptyList(); - - vm.render(v); - - assertEquals(1, vm.getStack().size()); - assertEquals("HELLO--WORLD", vm.getStack().pop()); - } - - @Test - public void testType() throws RecognitionException, IOException { - TestVM vm = new TestVM(""" - ENTRY { title } { } { label } - FUNCTION { presort } { cite$ 'sort.key$ := } - ITERATE { presort } - SORT - FUNCTION { test } { type$ } - ITERATE { test } - """); - List v = List.of( - TestBstVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), - TestBstVM.bibtexString2BibtexEntry("@book{b, author=\"BBB\"}"), - TestBstVM.bibtexString2BibtexEntry("@misc{c, author=\"CCC\"}"), - TestBstVM.bibtexString2BibtexEntry("@inproceedings{d, author=\"DDD\"}")); - - vm.render(v); - - assertEquals(4, vm.getStack().size()); - assertEquals("inproceedings", vm.getStack().pop()); - assertEquals("misc", vm.getStack().pop()); - assertEquals("book", vm.getStack().pop()); - assertEquals("article", vm.getStack().pop()); - } - - @Test - public void testMissing() throws RecognitionException, IOException { - TestVM vm = new TestVM(""" - ENTRY { title } { } { label } - FUNCTION { presort } { cite$ 'sort.key$ := } - ITERATE { presort } - READ - SORT - FUNCTION { test } { title missing$ cite$ } - ITERATE { test } - """); - List v = List.of( - t1BibtexEntry(), - TestBstVM.bibtexString2BibtexEntry("@article{test, author=\"No title\"}")); - - vm.render(v); - - assertEquals(4, vm.getStack().size()); - assertEquals("test", vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals("canh05", vm.getStack().pop()); - assertEquals(0, vm.getStack().pop()); - } - - @Test - public void testFormatName() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { format }{ "Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin" #1 "{vv~}{ll}{, jj}{, f}?" format.name$ } - EXECUTE { format } - """); - List v = Collections.emptyList(); - - vm.render(v); - - assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testFormatName2() throws RecognitionException, IOException { - TestVM vm = new TestVM(""" - ENTRY { author } { } { label } - FUNCTION { presort } { cite$ 'sort.key$ := } - ITERATE { presort } - READ - SORT - FUNCTION { format }{ author #2 "{vv~}{ll}{, jj}{, f}?" format.name$ } - ITERATE { format } - """); - - List v = List.of( - t1BibtexEntry(), - TestBstVM.bibtexString2BibtexEntry( - "@book{test, author=\"Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin\"}")); - vm.render(v); - assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); - assertEquals("Annabi, H?", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testCallType() throws RecognitionException, IOException { - TestVM vm = new TestVM(""" - ENTRY { title } { } { label } - FUNCTION { presort } { cite$ 'sort.key$ := } - ITERATE { presort } - READ - SORT - FUNCTION { inproceedings }{ "InProceedings called on " title * } - FUNCTION { book }{ "Book called on " title * } - ITERATE { call.type$ } - """); - List v = List.of( - t1BibtexEntry(), - TestBstVM.bibtexString2BibtexEntry("@book{test, title=\"Test\"}")); - - vm.render(v); - - assertEquals(2, vm.getStack().size()); - assertEquals("Book called on Test", vm.getStack().pop()); - assertEquals( - "InProceedings called on Effective work practices for floss development: A model and propositions", - vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testIterate() throws RecognitionException, IOException { - TestVM vm = new TestVM(""" - ENTRY { address author title type } {} { label } - FUNCTION { test }{ cite$ } - READ - ITERATE { test } - """); - List v = List.of( - t1BibtexEntry(), - TestBstVM.bibtexString2BibtexEntry("@article{test, title=\"BLA\"}")); - - vm.render(v); - - assertEquals(2, vm.getStack().size()); - String s1 = (String) vm.getStack().pop(); - String s2 = (String) vm.getStack().pop(); - - if ("canh05".equals(s1)) { - assertEquals("test", s2); - } else { - assertEquals("canh05", s2); - assertEquals("test", s1); - } - } - - @Test - public void testWidth() throws RecognitionException, IOException { - TestVM vm = new TestVM(""" - ENTRY { address author title type } { } { label } - STRINGS { longest.label } - INTEGERS { number.label longest.label.width } - FUNCTION { initialize.longest.label } { - "" 'longest.label := - #1 'number.label := - #0 'longest.label.width := - } - FUNCTION {longest.label.pass} { - number.label int.to.str$ 'label := - number.label #1 + 'number.label := - label width$ longest.label.width > - { - label 'longest.label := - label width$ 'longest.label.width := - } - 'skip$ - if$ - } - EXECUTE { initialize.longest.label } - ITERATE { longest.label.pass } - FUNCTION { begin.bib } { - preamble$ empty$ - 'skip$ - { preamble$ write$ newline$ } - if$ - "\\begin{thebibliography}{" longest.label * "}" * - } - EXECUTE {begin.bib} - """); - - List v = List.of(t1BibtexEntry()); - - vm.render(v); - - assertTrue(vm.getIntegers().containsKey("longest.label.width")); - assertEquals("\\begin{thebibliography}{1}", vm.getStack().pop()); - } - - @Test - public void testSwap() throws RecognitionException { - TestVM vm = new TestVM(""" - FUNCTION { a } { #3 "Hallo" swap$ } - EXECUTE { a } - """); - - List v = Collections.emptyList(); - vm.render(v); - - assertEquals(2, vm.getStack().size()); - assertEquals(3, vm.getStack().pop()); - assertEquals("Hallo", vm.getStack().pop()); - } - @Test public void testHypthenatedName() throws RecognitionException, IOException { TestVM vm = new TestVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); - List v = List.of(TestBstVM.bibtexString2BibtexEntry("@article{canh05, author = \"Jean-Paul Sartre\" }")); - assertTrue(vm.render(v).contains("J.-P. Sartre")); - } - - private static BibEntry bibtexString2BibtexEntry(String s) throws IOException { - ParserResult result = new BibtexParser(mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS), new DummyFileUpdateMonitor()).parse(new StringReader(s)); - Collection c = result.getDatabase().getEntries(); - assertEquals(1, c.size()); - return c.iterator().next(); - } + List testEntries = List.of( + new BibEntry(StandardEntryType.Article) + .withCitationKey("canh05") + .withField(StandardField.AUTHOR, "Jean-Paul Sartre") + ); - private static String t1BibtexString() { - return """ - @inproceedings{canh05, - author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}, - title = {Effective work practices for floss development: A model and propositions}, - booktitle = {Hawaii International Conference On System Sciences (HICSS)}, - year = {2005}, - owner = {oezbek}, - timestamp = {2006.05.29}, - url = {http://james.howison.name/publications.html}} - """; - } + String result = vm.render(testEntries); - public static BibEntry t1BibtexEntry() throws IOException { - return TestBstVM.bibtexString2BibtexEntry(t1BibtexString()); + assertTrue(result.contains("J.-P. Sartre")); } public static class TestVM extends BstVM { diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java index 95f9e3a6d12..63e46e70def 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java @@ -1,12 +1,14 @@ package org.jabref.logic.bst; -import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.types.StandardEntryType; +import org.antlr.v4.runtime.RecognitionException; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -48,20 +50,7 @@ public void testVisitIntegersCommand() { @Test void testVisitFunctionCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM("FUNCTION {test.func} { #1 'test.var := } EXECUTE { test.func }"); - - vm.render(Collections.emptyList()); - - Map functions = vm.getFunctions(); - assertTrue(functions.containsKey("test.func")); - assertNotNull(functions.get("test.func")); - } - - // ToDo: Belongs in testBstFunction - @Test - void testAssignFunction() { TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - INTEGERS { test.var } FUNCTION { test.func } { #1 'test.var := } EXECUTE { test.func } """); @@ -71,12 +60,14 @@ void testAssignFunction() { Map functions = vm.getFunctions(); assertTrue(functions.containsKey("test.func")); assertNotNull(functions.get("test.func")); - assertEquals(1, vm.latestContext.integers().get("test.var")); } @Test void testVisitMacroCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM("MACRO {jan} { \"January\" } EXECUTE {jan}"); + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + MACRO { jan } { "January" } + EXECUTE { jan } + """); vm.render(Collections.emptyList()); @@ -88,9 +79,9 @@ void testVisitMacroCommand() { } @Test - void testVisitEntryCommand() throws IOException { - TestBstVM.TestVM vm = new TestBstVM.TestVM("ENTRY {address author title type}{variable}{label}"); - List testEntries = List.of(TestBstVM.t1BibtexEntry()); + void testVisitEntryCommand() { + TestBstVM.TestVM vm = new TestBstVM.TestVM("ENTRY { address author title type } { variable } { label }"); + List testEntries = List.of(TestBstVM.defaultTestEntry()); vm.render(testEntries); @@ -105,9 +96,12 @@ void testVisitEntryCommand() throws IOException { } @Test - void testVisitReadCommand() throws IOException { - TestBstVM.TestVM vm = new TestBstVM.TestVM("ENTRY {author title booktitle year owner timestamp url}{}{} READ"); - List testEntries = List.of(TestBstVM.t1BibtexEntry()); + void testVisitReadCommand() { + TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + ENTRY { author title booktitle year owner timestamp url } { } { } + READ + """); + List testEntries = List.of(TestBstVM.defaultTestEntry()); vm.render(testEntries); @@ -121,154 +115,91 @@ void testVisitReadCommand() throws IOException { assertEquals("http://james.howison.name/publications.html", fields.get("url")); } - // execute iterate reverse - - // sort - - @Test - void testVisitIdentifier() throws IOException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - ENTRY { } { local.variable } { local.label } - READ - STRINGS { label } - INTEGERS { variable } - FUNCTION { test } { - #1 'local.variable := - #2 'variable := - "TEST" 'local.label := - "TEST-GLOBAL" 'label := - local.label local.variable - label variable - } - ITERATE { test } - """); - List testEntries = List.of(TestBstVM.t1BibtexEntry()); - - vm.render(testEntries); - - assertEquals(2, vm.getStack().pop()); - assertEquals("TEST-GLOBAL", vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals("TEST", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - void testIf() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - FUNCTION { path1 } { #1 } - FUNCTION { path0 } { #0 } - FUNCTION { test } { - #1 path1 path0 if$ - #0 path1 path0 if$ - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(0, vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - @Test - void testOrTrueTrue() { + public void testVisitExecuteCommand() throws RecognitionException { TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - FUNCTION { or } { { pop$ #1 } 'skip$ if$ } - FUNCTION { test } { - #1 #1 or - } - EXECUTE { test } + INTEGERS { variable.a } + FUNCTION { init.state.consts } { #5 'variable.a := } + EXECUTE { init.state.consts } """); vm.render(Collections.emptyList()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); + assertEquals(5, vm.getIntegers().get("variable.a")); } - @Test - void testOrFalseTrue() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - FUNCTION { or } { { pop$ #1 } 'skip$ if$ } - FUNCTION { test } { - #0 #1 or - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } + // ToDo: testVisitReverseCommand @Test - void testOrTrueFalse() { + public void testVisitIterateCommand() throws RecognitionException { TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - FUNCTION { or } { { pop$ #1 } 'skip$ if$ } - FUNCTION { test } { - #1 #0 or - } - EXECUTE { test } - """); - - vm.render(Collections.emptyList()); - - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - void testOrFalseFalse() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - FUNCTION { or } { { pop$ #1 } 'skip$ if$ } - FUNCTION { test } { - #0 #0 or - } - EXECUTE { test } + ENTRY { } { } { } + FUNCTION { test } { cite$ } + READ + ITERATE { test } """); + List testEntries = List.of( + TestBstVM.defaultTestEntry(), + new BibEntry(StandardEntryType.Article) + .withCitationKey("test")); - vm.render(Collections.emptyList()); + vm.render(testEntries); - assertEquals(BstVM.FALSE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); + assertEquals(2, vm.getStack().size()); + assertEquals("test", vm.getStack().pop()); + assertEquals("canh05", vm.getStack().pop()); } @Test - void testOrFiveSix() { + public void testVisitSortCommand() throws RecognitionException { TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - FUNCTION { or } { { pop$ #1 } 'skip$ if$ } - FUNCTION { test } { - #5 #6 or - } - EXECUTE { test } + ENTRY { } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + SORT """); + List testEntries = List.of( + new BibEntry(StandardEntryType.Article).withCitationKey("c"), + new BibEntry(StandardEntryType.Article).withCitationKey("b"), + new BibEntry(StandardEntryType.Article).withCitationKey("d"), + new BibEntry(StandardEntryType.Article).withCitationKey("a")); - vm.render(Collections.emptyList()); + vm.render(testEntries); - assertEquals(BstVM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); + List sortedEntries = vm.getEntries(); + assertEquals(Optional.of("a"), sortedEntries.get(0).entry.getCitationKey()); + assertEquals(Optional.of("b"), sortedEntries.get(1).entry.getCitationKey()); + assertEquals(Optional.of("c"), sortedEntries.get(2).entry.getCitationKey()); + assertEquals(Optional.of("d"), sortedEntries.get(3).entry.getCitationKey()); } @Test - void testOrFalseFive() { + void testVisitIdentifier() { TestBstVM.TestVM vm = new TestBstVM.TestVM(""" - FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + ENTRY { } { local.variable } { local.label } + READ + STRINGS { label } + INTEGERS { variable } FUNCTION { test } { - #0 #5 or + #1 'local.variable := + #2 'variable := + "TEST" 'local.label := + "TEST-GLOBAL" 'label := + local.label local.variable + label variable } - EXECUTE { test } + ITERATE { test } """); + List testEntries = List.of(TestBstVM.defaultTestEntry()); - vm.render(Collections.emptyList()); + vm.render(testEntries); - assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(2, vm.getStack().pop()); + assertEquals("TEST-GLOBAL", vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals("TEST", vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } - // bstFunction - // stackitem } From 7dc202bbcef2d688bbf0b08349a87f2318acf5ea Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Tue, 12 Jul 2022 18:09:48 +0200 Subject: [PATCH 28/39] stylistic issues --- build.gradle | 2 +- src/main/antlr4/org/jabref/bst/Bst.g4 | 26 +++++++++---------- .../org/jabref/logic/bst/BstVMContext.java | 1 - .../org/jabref/logic/bst/BstVMException.java | 1 - 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 82143398ace..e2797b5ab05 100644 --- a/build.gradle +++ b/build.gradle @@ -277,7 +277,7 @@ tasks.register("generateBstGrammarSource", JavaExec) { inputs.dir('src/main/antlr4/org/jabref/bst/') outputs.dir("src-gen/main/java/org/jabref/logic/bst/") - args = ["-o", "src-gen/main/java/org/jabref/logic/bst/", "-visitor", "-no-listener", "-package", "org.jabref.logic.bst", "$projectDir/src/main/antlr4/org/jabref/bst/Bst.g4"] + args = ["-o", "src-gen/main/java/org/jabref/logic/bst/", "-visitor", "-no-listener", "-package", "org.jabref.logic.bst", "$projectDir/src/main/antlr4/org/jabref/bst/Bst.g4"] } tasks.register("generateSearchGrammarSource", JavaExec) { diff --git a/src/main/antlr4/org/jabref/bst/Bst.g4 b/src/main/antlr4/org/jabref/bst/Bst.g4 index 372200484bc..92b96ce18df 100644 --- a/src/main/antlr4/org/jabref/bst/Bst.g4 +++ b/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -54,8 +54,8 @@ commands ; identifier - : IDENTIFIER - ; + : IDENTIFIER + ; // Obligatory identifier list idListObl @@ -68,18 +68,18 @@ idListOpt ; bstFunction - : LT | GT | EQUAL | ADD | SUB | ASSIGN | CONCAT - | identifier - ; + : LT | GT | EQUAL | ADD | SUB | ASSIGN | CONCAT + | identifier + ; stack - : LBRACE stackitem+ RBRACE - ; + : LBRACE stackitem+ RBRACE + ; stackitem - : bstFunction - | STRING - | INTEGER - | QUOTED - | stack - ; + : bstFunction + | STRING + | INTEGER + | QUOTED + | stack + ; diff --git a/src/main/java/org/jabref/logic/bst/BstVMContext.java b/src/main/java/org/jabref/logic/bst/BstVMContext.java index 8f9253a7ca2..13d6563ffac 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMContext.java +++ b/src/main/java/org/jabref/logic/bst/BstVMContext.java @@ -13,7 +13,6 @@ public record BstVMContext(List entries, Map functions, Stack stack, BibDatabase bibDatabase) { - public BstVMContext(List entries, BibDatabase bibDatabase) { this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), new Stack<>(), bibDatabase); } diff --git a/src/main/java/org/jabref/logic/bst/BstVMException.java b/src/main/java/org/jabref/logic/bst/BstVMException.java index 52de2d9e9f9..2851166d094 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMException.java +++ b/src/main/java/org/jabref/logic/bst/BstVMException.java @@ -1,7 +1,6 @@ package org.jabref.logic.bst; public class BstVMException extends RuntimeException { - public BstVMException(String string) { super(string); } From 4569dc793a8bff79666a1cfb35bf92bff9f5e8d2 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Fri, 15 Jul 2022 18:36:22 +0200 Subject: [PATCH 29/39] Reworded --- .../org/jabref/logic/bst/BstFunctions.java | 20 +- .../jabref/logic/bst/util/BibtexWidth.java | 241 ------------------ ...exCaseChanger.java => BstCaseChanger.java} | 10 +- ...meFormatter.java => BstNameFormatter.java} | 18 +- .../{BibtexPurify.java => BstPurifier.java} | 8 +- ...exTextPrefix.java => BstTextPrefixer.java} | 6 +- .../logic/bst/util/BstWidthCalculator.java | 241 ++++++++++++++++++ .../logic/layout/format/NameFormatter.java | 6 +- ...stFunctions.java => BstFunctionsTest.java} | 82 +++--- .../bst/{TestBstVM.java => BstVMTest.java} | 2 +- ...stVMVisitor.java => BstVMVisitorTest.java} | 30 +-- .../BstCaseChangersTest.java} | 15 +- .../BstNameFormatterTest.java} | 45 ++-- .../BstPurifierTest.java} | 8 +- .../BstTextPrefixerTest.java} | 8 +- .../BstWidthCalculatorTest.java} | 10 +- 16 files changed, 377 insertions(+), 373 deletions(-) delete mode 100644 src/main/java/org/jabref/logic/bst/util/BibtexWidth.java rename src/main/java/org/jabref/logic/bst/util/{BibtexCaseChanger.java => BstCaseChanger.java} (97%) rename src/main/java/org/jabref/logic/bst/util/{BibtexNameFormatter.java => BstNameFormatter.java} (94%) rename src/main/java/org/jabref/logic/bst/util/{BibtexPurify.java => BstPurifier.java} (93%) rename src/main/java/org/jabref/logic/bst/util/{BibtexTextPrefix.java => BstTextPrefixer.java} (96%) create mode 100644 src/main/java/org/jabref/logic/bst/util/BstWidthCalculator.java rename src/test/java/org/jabref/logic/bst/{TestBstFunctions.java => BstFunctionsTest.java} (90%) rename src/test/java/org/jabref/logic/bst/{TestBstVM.java => BstVMTest.java} (99%) rename src/test/java/org/jabref/logic/bst/{TestBstVMVisitor.java => BstVMVisitorTest.java} (89%) rename src/test/java/org/jabref/logic/bst/{BibtexCaseChangersTest.java => util/BstCaseChangersTest.java} (93%) rename src/test/java/org/jabref/logic/bst/{BibtexNameFormatterTest.java => util/BstNameFormatterTest.java} (65%) rename src/test/java/org/jabref/logic/bst/{BibtexPurifyTest.java => util/BstPurifierTest.java} (86%) rename src/test/java/org/jabref/logic/bst/{TextPrefixFunctionTest.java => util/BstTextPrefixerTest.java} (79%) rename src/test/java/org/jabref/logic/bst/{BibtexWidthTest.java => util/BstWidthCalculatorTest.java} (90%) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 63800955ec3..2f15c308fbe 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -8,11 +8,11 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.jabref.logic.bst.util.BibtexCaseChanger; -import org.jabref.logic.bst.util.BibtexNameFormatter; -import org.jabref.logic.bst.util.BibtexPurify; -import org.jabref.logic.bst.util.BibtexTextPrefix; -import org.jabref.logic.bst.util.BibtexWidth; +import org.jabref.logic.bst.util.BstCaseChanger; +import org.jabref.logic.bst.util.BstNameFormatter; +import org.jabref.logic.bst.util.BstPurifier; +import org.jabref.logic.bst.util.BstTextPrefixer; +import org.jabref.logic.bst.util.BstWidthCalculator; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; @@ -377,7 +377,7 @@ private void bstChangeCase(BstVMVisitor visitor, ParserRuleContext parserRuleCon char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); String s = (String) o2; - stack.push(BibtexCaseChanger.changeCase(s, BibtexCaseChanger.FORMAT_MODE.getFormatModeForBSTFormat(format))); + stack.push(BstCaseChanger.changeCase(s, BstCaseChanger.FORMAT_MODE.getFormatModeForBSTFormat(format))); } /** @@ -493,7 +493,7 @@ private void bstFormatName(BstVMVisitor visitor, ParserRuleContext parserRuleCon } Author author = a.getAuthor(name - 1); - stack.push(BibtexNameFormatter.formatName(author, format)); + stack.push(BstNameFormatter.formatName(author, format)); } } @@ -651,7 +651,7 @@ private void bstPurify(BstVMVisitor visitor, ParserRuleContext parserRuleContext return; } - stack.push(BibtexPurify.purify((String) o1)); + stack.push(BstPurifier.purify((String) o1)); } /** @@ -826,7 +826,7 @@ private void bstTextPrefix(BstVMVisitor visitor, ParserRuleContext parserRuleCon return; } - stack.push(BibtexTextPrefix.textPrefix((Integer) o1, (String) o2)); + stack.push(BstTextPrefixer.textPrefix((Integer) o1, (String) o2)); } /** @@ -918,7 +918,7 @@ private void bstWidth(BstVMVisitor visitor, ParserRuleContext parserRuleContext) return; } - stack.push(BibtexWidth.width((String) o1)); + stack.push(BstWidthCalculator.width((String) o1)); } /** diff --git a/src/main/java/org/jabref/logic/bst/util/BibtexWidth.java b/src/main/java/org/jabref/logic/bst/util/BibtexWidth.java deleted file mode 100644 index b4c8f77b349..00000000000 --- a/src/main/java/org/jabref/logic/bst/util/BibtexWidth.java +++ /dev/null @@ -1,241 +0,0 @@ -package org.jabref.logic.bst.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * The |built_in| function {\.{purify\$}} pops the top (string) literal, removes - * nonalphanumeric characters except for |white_space| and |sep_char| characters - * (these get converted to a |space|) and removes certain alphabetic characters - * contained in the control sequences associated with a special character, and - * pushes the resulting string. If the literal isn't a string, it complains and - * pushes the null string. - * - */ -public class BibtexWidth { - - private static final Logger LOGGER = LoggerFactory.getLogger(BibtexWidth.class); - - /* - * Quoted from Bibtex: - * - * Now we initialize the system-dependent |char_width| array, for which - * |space| is the only |white_space| character given a nonzero printing - * width. The widths here are taken from Stanford's June~'87 $cmr10$~font - * and represent hundredths of a point (rounded), but since they're used - * only for relative comparisons, the units have no meaning. - */ - - private static int[] widths; - - static { - if (BibtexWidth.widths == null) { - BibtexWidth.widths = new int[128]; - - for (int i = 0; i < 128; i++) { - BibtexWidth.widths[i] = 0; - } - BibtexWidth.widths[32] = 278; - BibtexWidth.widths[33] = 278; - BibtexWidth.widths[34] = 500; - BibtexWidth.widths[35] = 833; - BibtexWidth.widths[36] = 500; - BibtexWidth.widths[37] = 833; - BibtexWidth.widths[38] = 778; - BibtexWidth.widths[39] = 278; - BibtexWidth.widths[40] = 389; - BibtexWidth.widths[41] = 389; - BibtexWidth.widths[42] = 500; - BibtexWidth.widths[43] = 778; - BibtexWidth.widths[44] = 278; - BibtexWidth.widths[45] = 333; - BibtexWidth.widths[46] = 278; - BibtexWidth.widths[47] = 500; - BibtexWidth.widths[48] = 500; - BibtexWidth.widths[49] = 500; - BibtexWidth.widths[50] = 500; - BibtexWidth.widths[51] = 500; - BibtexWidth.widths[52] = 500; - BibtexWidth.widths[53] = 500; - BibtexWidth.widths[54] = 500; - BibtexWidth.widths[55] = 500; - BibtexWidth.widths[56] = 500; - BibtexWidth.widths[57] = 500; - BibtexWidth.widths[58] = 278; - BibtexWidth.widths[59] = 278; - BibtexWidth.widths[60] = 278; - BibtexWidth.widths[61] = 778; - BibtexWidth.widths[62] = 472; - BibtexWidth.widths[63] = 472; - BibtexWidth.widths[64] = 778; - BibtexWidth.widths[65] = 750; - BibtexWidth.widths[66] = 708; - BibtexWidth.widths[67] = 722; - BibtexWidth.widths[68] = 764; - BibtexWidth.widths[69] = 681; - BibtexWidth.widths[70] = 653; - BibtexWidth.widths[71] = 785; - BibtexWidth.widths[72] = 750; - BibtexWidth.widths[73] = 361; - BibtexWidth.widths[74] = 514; - BibtexWidth.widths[75] = 778; - BibtexWidth.widths[76] = 625; - BibtexWidth.widths[77] = 917; - BibtexWidth.widths[78] = 750; - BibtexWidth.widths[79] = 778; - BibtexWidth.widths[80] = 681; - BibtexWidth.widths[81] = 778; - BibtexWidth.widths[82] = 736; - BibtexWidth.widths[83] = 556; - BibtexWidth.widths[84] = 722; - BibtexWidth.widths[85] = 750; - BibtexWidth.widths[86] = 750; - BibtexWidth.widths[87] = 1028; - BibtexWidth.widths[88] = 750; - BibtexWidth.widths[89] = 750; - BibtexWidth.widths[90] = 611; - BibtexWidth.widths[91] = 278; - BibtexWidth.widths[92] = 500; - BibtexWidth.widths[93] = 278; - BibtexWidth.widths[94] = 500; - BibtexWidth.widths[95] = 278; - BibtexWidth.widths[96] = 278; - BibtexWidth.widths[97] = 500; - BibtexWidth.widths[98] = 556; - BibtexWidth.widths[99] = 444; - BibtexWidth.widths[100] = 556; - BibtexWidth.widths[101] = 444; - BibtexWidth.widths[102] = 306; - BibtexWidth.widths[103] = 500; - BibtexWidth.widths[104] = 556; - BibtexWidth.widths[105] = 278; - BibtexWidth.widths[106] = 306; - BibtexWidth.widths[107] = 528; - BibtexWidth.widths[108] = 278; - BibtexWidth.widths[109] = 833; - BibtexWidth.widths[110] = 556; - BibtexWidth.widths[111] = 500; - BibtexWidth.widths[112] = 556; - BibtexWidth.widths[113] = 528; - BibtexWidth.widths[114] = 392; - BibtexWidth.widths[115] = 394; - BibtexWidth.widths[116] = 389; - BibtexWidth.widths[117] = 556; - BibtexWidth.widths[118] = 528; - BibtexWidth.widths[119] = 722; - BibtexWidth.widths[120] = 528; - BibtexWidth.widths[121] = 528; - BibtexWidth.widths[122] = 444; - BibtexWidth.widths[123] = 500; - BibtexWidth.widths[124] = 1000; - BibtexWidth.widths[125] = 500; - BibtexWidth.widths[126] = 500; - } - } - - private BibtexWidth() { - } - - private static int getSpecialCharWidth(char[] c, int pos) { - if ((pos + 1) < c.length) { - if ((c[pos] == 'o') && (c[pos + 1] == 'e')) { - return 778; - } - if ((c[pos] == 'O') && (c[pos + 1] == 'E')) { - return 1014; - } - if ((c[pos] == 'a') && (c[pos + 1] == 'e')) { - return 722; - } - if ((c[pos] == 'A') && (c[pos + 1] == 'E')) { - return 903; - } - if ((c[pos] == 's') && (c[pos + 1] == 's')) { - return 500; - } - } - return BibtexWidth.getCharWidth(c[pos]); - } - - public static int getCharWidth(char c) { - if ((c >= 0) && (c < 128)) { - return BibtexWidth.widths[c]; - } else { - return 0; - } - } - - public static int width(String toMeasure) { - /* - * From Bibtex: We use the natural width for all but special characters, - * and we complain if the string isn't brace-balanced. - */ - - int i = 0; - int n = toMeasure.length(); - int braceLevel = 0; - char[] c = toMeasure.toCharArray(); - int result = 0; - - /* - * From Bibtex: - * - * We use the natural widths of all characters except that some - * characters have no width: braces, control sequences (except for the - * usual 13 accented and foreign characters, whose widths are given in - * the next module), and |white_space| following control sequences (even - * a null control sequence). - * - */ - while (i < n) { - if (c[i] == '{') { - braceLevel++; - if ((braceLevel == 1) && ((i + 1) < n) && (c[i + 1] == '\\')) { - i++; // skip brace - while ((i < n) && (braceLevel > 0)) { - i++; // skip backslash - - int afterBackslash = i; - while ((i < n) && Character.isLetter(c[i])) { - i++; - } - if ((i < n) && (i == afterBackslash)) { - i++; // Skip non-alpha control seq - } else { - if (BibtexCaseChanger.findSpecialChar(c, afterBackslash).isPresent()) { - result += BibtexWidth.getSpecialCharWidth(c, afterBackslash); - } - } - while ((i < n) && Character.isWhitespace(c[i])) { - i++; - } - while ((i < n) && (braceLevel > 0) && (c[i] != '\\')) { - if (c[i] == '}') { - braceLevel--; - } else if (c[i] == '{') { - braceLevel++; - } else { - result += BibtexWidth.getCharWidth(c[i]); - } - i++; - } - } - continue; - } - } else if (c[i] == '}') { - if (braceLevel > 0) { - braceLevel--; - } else { - LOGGER.warn("Too many closing braces in string: " + toMeasure); - } - } - result += BibtexWidth.getCharWidth(c[i]); - i++; - } - if (braceLevel > 0) { - LOGGER.warn("No enough closing braces in string: " + toMeasure); - } - return result; - } -} diff --git a/src/main/java/org/jabref/logic/bst/util/BibtexCaseChanger.java b/src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java similarity index 97% rename from src/main/java/org/jabref/logic/bst/util/BibtexCaseChanger.java rename to src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java index f94b9055eea..67fdeb4acc7 100644 --- a/src/main/java/org/jabref/logic/bst/util/BibtexCaseChanger.java +++ b/src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java @@ -6,9 +6,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public final class BibtexCaseChanger { +public final class BstCaseChanger { - private static final Logger LOGGER = LoggerFactory.getLogger(BibtexCaseChanger.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BstCaseChanger.class); // stores whether the char before the current char was a colon private boolean prevColon = true; @@ -63,7 +63,7 @@ public static FORMAT_MODE getFormatModeForBSTFormat(final char bstFormat) { } } - private BibtexCaseChanger() { + private BstCaseChanger() { } /** @@ -73,7 +73,7 @@ private BibtexCaseChanger() { * @param format the format */ public static String changeCase(String s, FORMAT_MODE format) { - return (new BibtexCaseChanger()).doChangeCase(s, format); + return (new BstCaseChanger()).doChangeCase(s, format); } private String doChangeCase(String s, FORMAT_MODE format) { @@ -149,7 +149,7 @@ private int convertSpecialChar(StringBuilder sb, char[] c, int start, FORMAT_MOD i++; // skip over the |backslash| - Optional s = BibtexCaseChanger.findSpecialChar(c, i); + Optional s = BstCaseChanger.findSpecialChar(c, i); if (s.isPresent()) { i = convertAccented(c, i, s.get(), sb, format); } diff --git a/src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java b/src/main/java/org/jabref/logic/bst/util/BstNameFormatter.java similarity index 94% rename from src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java rename to src/main/java/org/jabref/logic/bst/util/BstNameFormatter.java index 3e5954b7287..c4986dc12dd 100644 --- a/src/main/java/org/jabref/logic/bst/util/BibtexNameFormatter.java +++ b/src/main/java/org/jabref/logic/bst/util/BstNameFormatter.java @@ -28,10 +28,10 @@ * Sounds easy - is a nightmare... X-( * */ -public class BibtexNameFormatter { - private static final Logger LOGGER = LoggerFactory.getLogger(BibtexNameFormatter.class); +public class BstNameFormatter { + private static final Logger LOGGER = LoggerFactory.getLogger(BstNameFormatter.class); - private BibtexNameFormatter() { + private BstNameFormatter() { } /** @@ -48,7 +48,7 @@ public static String formatName(String authorsNameList, int whichName, String fo LOGGER.warn("AuthorList {} does not contain an author with number {}", authorsNameList, whichName); return ""; } - return BibtexNameFormatter.formatName(al.getAuthor(whichName - 1), formatString); + return BstNameFormatter.formatName(al.getAuthor(whichName - 1), formatString); } public static String formatName(Author author, String format) { @@ -151,7 +151,7 @@ public static String formatName(Author author, String format) { } if (((j + 1) < d.length) && (d[j + 1] == '{')) { StringBuilder interTokenSb = new StringBuilder(); - j = BibtexNameFormatter.consumeToMatchingBrace(interTokenSb, d, j + 1); + j = BstNameFormatter.consumeToMatchingBrace(interTokenSb, d, j + 1); interToken = interTokenSb.substring(1, interTokenSb.length() - 1); } @@ -160,7 +160,7 @@ public static String formatName(Author author, String format) { if (abbreviateThatIsSingleLetter) { String[] dashes = token.split("-"); - token = Arrays.stream(dashes).map(BibtexNameFormatter::getFirstCharOfString) + token = Arrays.stream(dashes).map(BstNameFormatter::getFirstCharOfString) .collect(Collectors.joining(".-")); } @@ -176,7 +176,7 @@ public static String formatName(Author author, String format) { // No clue what this means (What the hell are tokens anyway??? // if (lex_class[name_sep_char[cur_token]] = sep_char) then // append_ex_buf_char_and_check (name_sep_char[cur_token]) - if ((k == (tokens.length - 2)) || (BibtexNameFormatter.numberOfChars(sb.substring(groupStart, sb.length()), 3) < 3)) { + if ((k == (tokens.length - 2)) || (BstNameFormatter.numberOfChars(sb.substring(groupStart, sb.length()), 3) < 3)) { sb.append('~'); } else { sb.append(' '); @@ -201,7 +201,7 @@ public static String formatName(Author author, String format) { if (sb.length() > 0) { boolean noDisTie = false; if ((sb.charAt(sb.length() - 1) == '~') && - ((BibtexNameFormatter.numberOfChars(sb.substring(groupStart, sb.length()), 4) >= 4) || + ((BstNameFormatter.numberOfChars(sb.substring(groupStart, sb.length()), 4) >= 4) || ((sb.length() > 1) && (noDisTie = sb.charAt(sb.length() - 2) == '~')))) { sb.deleteCharAt(sb.length() - 1); if (!noDisTie) { @@ -255,7 +255,7 @@ public static String getFirstCharOfString(String s) { } if ((c[i] == '{') && ((i + 1) < c.length) && (c[i + 1] == '\\')) { StringBuilder sb = new StringBuilder(); - BibtexNameFormatter.consumeToMatchingBrace(sb, c, i); + BstNameFormatter.consumeToMatchingBrace(sb, c, i); return sb.toString(); } } diff --git a/src/main/java/org/jabref/logic/bst/util/BibtexPurify.java b/src/main/java/org/jabref/logic/bst/util/BstPurifier.java similarity index 93% rename from src/main/java/org/jabref/logic/bst/util/BibtexPurify.java rename to src/main/java/org/jabref/logic/bst/util/BstPurifier.java index 951c3838240..90818a0749f 100644 --- a/src/main/java/org/jabref/logic/bst/util/BibtexPurify.java +++ b/src/main/java/org/jabref/logic/bst/util/BstPurifier.java @@ -13,10 +13,10 @@ * pushes the null string. * */ -public class BibtexPurify { - private static final Logger LOGGER = LoggerFactory.getLogger(BibtexPurify.class); +public class BstPurifier { + private static final Logger LOGGER = LoggerFactory.getLogger(BstPurifier.class); - private BibtexPurify() { + private BstPurifier() { } public static String purify(String toPurify) { @@ -40,7 +40,7 @@ public static String purify(String toPurify) { i++; // skip brace while ((i < n) && (braceLevel > 0)) { i++; // skip backslash - BibtexCaseChanger.findSpecialChar(cs, i).ifPresent(sb::append); + BstCaseChanger.findSpecialChar(cs, i).ifPresent(sb::append); while ((i < n) && Character.isLetter(cs[i])) { i++; diff --git a/src/main/java/org/jabref/logic/bst/util/BibtexTextPrefix.java b/src/main/java/org/jabref/logic/bst/util/BstTextPrefixer.java similarity index 96% rename from src/main/java/org/jabref/logic/bst/util/BibtexTextPrefix.java rename to src/main/java/org/jabref/logic/bst/util/BstTextPrefixer.java index e3bc961d9ea..31cbc71de9b 100644 --- a/src/main/java/org/jabref/logic/bst/util/BibtexTextPrefix.java +++ b/src/main/java/org/jabref/logic/bst/util/BstTextPrefixer.java @@ -17,10 +17,10 @@ * complains and pushes the null string. * */ -public class BibtexTextPrefix { - private static final Logger LOGGER = LoggerFactory.getLogger(BibtexTextPrefix.class); +public class BstTextPrefixer { + private static final Logger LOGGER = LoggerFactory.getLogger(BstTextPrefixer.class); - private BibtexTextPrefix() { + private BstTextPrefixer() { } public static String textPrefix(int inNumOfChars, String toPrefix) { diff --git a/src/main/java/org/jabref/logic/bst/util/BstWidthCalculator.java b/src/main/java/org/jabref/logic/bst/util/BstWidthCalculator.java new file mode 100644 index 00000000000..c286fe1497b --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/util/BstWidthCalculator.java @@ -0,0 +1,241 @@ +package org.jabref.logic.bst.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * The |built_in| function {\.{purify\$}} pops the top (string) literal, removes + * nonalphanumeric characters except for |white_space| and |sep_char| characters + * (these get converted to a |space|) and removes certain alphabetic characters + * contained in the control sequences associated with a special character, and + * pushes the resulting string. If the literal isn't a string, it complains and + * pushes the null string. + * + */ +public class BstWidthCalculator { + + private static final Logger LOGGER = LoggerFactory.getLogger(BstWidthCalculator.class); + + /* + * Quoted from Bibtex: + * + * Now we initialize the system-dependent |char_width| array, for which + * |space| is the only |white_space| character given a nonzero printing + * width. The widths here are taken from Stanford's June~'87 $cmr10$~font + * and represent hundredths of a point (rounded), but since they're used + * only for relative comparisons, the units have no meaning. + */ + + private static int[] widths; + + static { + if (BstWidthCalculator.widths == null) { + BstWidthCalculator.widths = new int[128]; + + for (int i = 0; i < 128; i++) { + BstWidthCalculator.widths[i] = 0; + } + BstWidthCalculator.widths[32] = 278; + BstWidthCalculator.widths[33] = 278; + BstWidthCalculator.widths[34] = 500; + BstWidthCalculator.widths[35] = 833; + BstWidthCalculator.widths[36] = 500; + BstWidthCalculator.widths[37] = 833; + BstWidthCalculator.widths[38] = 778; + BstWidthCalculator.widths[39] = 278; + BstWidthCalculator.widths[40] = 389; + BstWidthCalculator.widths[41] = 389; + BstWidthCalculator.widths[42] = 500; + BstWidthCalculator.widths[43] = 778; + BstWidthCalculator.widths[44] = 278; + BstWidthCalculator.widths[45] = 333; + BstWidthCalculator.widths[46] = 278; + BstWidthCalculator.widths[47] = 500; + BstWidthCalculator.widths[48] = 500; + BstWidthCalculator.widths[49] = 500; + BstWidthCalculator.widths[50] = 500; + BstWidthCalculator.widths[51] = 500; + BstWidthCalculator.widths[52] = 500; + BstWidthCalculator.widths[53] = 500; + BstWidthCalculator.widths[54] = 500; + BstWidthCalculator.widths[55] = 500; + BstWidthCalculator.widths[56] = 500; + BstWidthCalculator.widths[57] = 500; + BstWidthCalculator.widths[58] = 278; + BstWidthCalculator.widths[59] = 278; + BstWidthCalculator.widths[60] = 278; + BstWidthCalculator.widths[61] = 778; + BstWidthCalculator.widths[62] = 472; + BstWidthCalculator.widths[63] = 472; + BstWidthCalculator.widths[64] = 778; + BstWidthCalculator.widths[65] = 750; + BstWidthCalculator.widths[66] = 708; + BstWidthCalculator.widths[67] = 722; + BstWidthCalculator.widths[68] = 764; + BstWidthCalculator.widths[69] = 681; + BstWidthCalculator.widths[70] = 653; + BstWidthCalculator.widths[71] = 785; + BstWidthCalculator.widths[72] = 750; + BstWidthCalculator.widths[73] = 361; + BstWidthCalculator.widths[74] = 514; + BstWidthCalculator.widths[75] = 778; + BstWidthCalculator.widths[76] = 625; + BstWidthCalculator.widths[77] = 917; + BstWidthCalculator.widths[78] = 750; + BstWidthCalculator.widths[79] = 778; + BstWidthCalculator.widths[80] = 681; + BstWidthCalculator.widths[81] = 778; + BstWidthCalculator.widths[82] = 736; + BstWidthCalculator.widths[83] = 556; + BstWidthCalculator.widths[84] = 722; + BstWidthCalculator.widths[85] = 750; + BstWidthCalculator.widths[86] = 750; + BstWidthCalculator.widths[87] = 1028; + BstWidthCalculator.widths[88] = 750; + BstWidthCalculator.widths[89] = 750; + BstWidthCalculator.widths[90] = 611; + BstWidthCalculator.widths[91] = 278; + BstWidthCalculator.widths[92] = 500; + BstWidthCalculator.widths[93] = 278; + BstWidthCalculator.widths[94] = 500; + BstWidthCalculator.widths[95] = 278; + BstWidthCalculator.widths[96] = 278; + BstWidthCalculator.widths[97] = 500; + BstWidthCalculator.widths[98] = 556; + BstWidthCalculator.widths[99] = 444; + BstWidthCalculator.widths[100] = 556; + BstWidthCalculator.widths[101] = 444; + BstWidthCalculator.widths[102] = 306; + BstWidthCalculator.widths[103] = 500; + BstWidthCalculator.widths[104] = 556; + BstWidthCalculator.widths[105] = 278; + BstWidthCalculator.widths[106] = 306; + BstWidthCalculator.widths[107] = 528; + BstWidthCalculator.widths[108] = 278; + BstWidthCalculator.widths[109] = 833; + BstWidthCalculator.widths[110] = 556; + BstWidthCalculator.widths[111] = 500; + BstWidthCalculator.widths[112] = 556; + BstWidthCalculator.widths[113] = 528; + BstWidthCalculator.widths[114] = 392; + BstWidthCalculator.widths[115] = 394; + BstWidthCalculator.widths[116] = 389; + BstWidthCalculator.widths[117] = 556; + BstWidthCalculator.widths[118] = 528; + BstWidthCalculator.widths[119] = 722; + BstWidthCalculator.widths[120] = 528; + BstWidthCalculator.widths[121] = 528; + BstWidthCalculator.widths[122] = 444; + BstWidthCalculator.widths[123] = 500; + BstWidthCalculator.widths[124] = 1000; + BstWidthCalculator.widths[125] = 500; + BstWidthCalculator.widths[126] = 500; + } + } + + private BstWidthCalculator() { + } + + private static int getSpecialCharWidth(char[] c, int pos) { + if ((pos + 1) < c.length) { + if ((c[pos] == 'o') && (c[pos + 1] == 'e')) { + return 778; + } + if ((c[pos] == 'O') && (c[pos + 1] == 'E')) { + return 1014; + } + if ((c[pos] == 'a') && (c[pos + 1] == 'e')) { + return 722; + } + if ((c[pos] == 'A') && (c[pos + 1] == 'E')) { + return 903; + } + if ((c[pos] == 's') && (c[pos + 1] == 's')) { + return 500; + } + } + return BstWidthCalculator.getCharWidth(c[pos]); + } + + public static int getCharWidth(char c) { + if ((c >= 0) && (c < 128)) { + return BstWidthCalculator.widths[c]; + } else { + return 0; + } + } + + public static int width(String toMeasure) { + /* + * From Bibtex: We use the natural width for all but special characters, + * and we complain if the string isn't brace-balanced. + */ + + int i = 0; + int n = toMeasure.length(); + int braceLevel = 0; + char[] c = toMeasure.toCharArray(); + int result = 0; + + /* + * From Bibtex: + * + * We use the natural widths of all characters except that some + * characters have no width: braces, control sequences (except for the + * usual 13 accented and foreign characters, whose widths are given in + * the next module), and |white_space| following control sequences (even + * a null control sequence). + * + */ + while (i < n) { + if (c[i] == '{') { + braceLevel++; + if ((braceLevel == 1) && ((i + 1) < n) && (c[i + 1] == '\\')) { + i++; // skip brace + while ((i < n) && (braceLevel > 0)) { + i++; // skip backslash + + int afterBackslash = i; + while ((i < n) && Character.isLetter(c[i])) { + i++; + } + if ((i < n) && (i == afterBackslash)) { + i++; // Skip non-alpha control seq + } else { + if (BstCaseChanger.findSpecialChar(c, afterBackslash).isPresent()) { + result += BstWidthCalculator.getSpecialCharWidth(c, afterBackslash); + } + } + while ((i < n) && Character.isWhitespace(c[i])) { + i++; + } + while ((i < n) && (braceLevel > 0) && (c[i] != '\\')) { + if (c[i] == '}') { + braceLevel--; + } else if (c[i] == '{') { + braceLevel++; + } else { + result += BstWidthCalculator.getCharWidth(c[i]); + } + i++; + } + } + continue; + } + } else if (c[i] == '}') { + if (braceLevel > 0) { + braceLevel--; + } else { + LOGGER.warn("Too many closing braces in string: " + toMeasure); + } + } + result += BstWidthCalculator.getCharWidth(c[i]); + i++; + } + if (braceLevel > 0) { + LOGGER.warn("No enough closing braces in string: " + toMeasure); + } + return result; + } +} diff --git a/src/main/java/org/jabref/logic/layout/format/NameFormatter.java b/src/main/java/org/jabref/logic/layout/format/NameFormatter.java index 18b27d35ebe..e8812235435 100644 --- a/src/main/java/org/jabref/logic/layout/format/NameFormatter.java +++ b/src/main/java/org/jabref/logic/layout/format/NameFormatter.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Objects; -import org.jabref.logic.bst.util.BibtexNameFormatter; +import org.jabref.logic.bst.util.BstNameFormatter; import org.jabref.logic.layout.LayoutFormatter; import org.jabref.model.entry.AuthorList; @@ -86,7 +86,7 @@ private static String format(String toFormat, AuthorList al, String[] formats) { for (int i = 1; i <= al.getNumberOfAuthors(); i++) { for (int j = 1; j < formats.length; j += 2) { if ("*".equals(formats[j])) { - sb.append(BibtexNameFormatter.formatName(toFormat, i, formats[j + 1])); + sb.append(BstNameFormatter.formatName(toFormat, i, formats[j + 1])); break; } else { String[] range = formats[j].split("\\.\\."); @@ -112,7 +112,7 @@ private static String format(String toFormat, AuthorList al, String[] formats) { } if ((s <= i) && (i <= e)) { - sb.append(BibtexNameFormatter.formatName(toFormat, i, formats[j + 1])); + sb.append(BstNameFormatter.formatName(toFormat, i, formats[j + 1])); break; } } diff --git a/src/test/java/org/jabref/logic/bst/TestBstFunctions.java b/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java similarity index 90% rename from src/test/java/org/jabref/logic/bst/TestBstFunctions.java rename to src/test/java/org/jabref/logic/bst/BstFunctionsTest.java index 439bd78b828..c0184f3ec2b 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstFunctions.java +++ b/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java @@ -4,6 +4,11 @@ import java.util.List; import java.util.Map; +import org.jabref.logic.bst.util.BstCaseChangersTest; +import org.jabref.logic.bst.util.BstNameFormatterTest; +import org.jabref.logic.bst.util.BstPurifierTest; +import org.jabref.logic.bst.util.BstTextPrefixerTest; +import org.jabref.logic.bst.util.BstWidthCalculatorTest; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; @@ -16,10 +21,18 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -class TestBstFunctions { +/** + * For additional tests see + * - purify: {@link BstPurifierTest} + * - width: {@link BstWidthCalculatorTest} + * - format.name: {@link BstNameFormatterTest} + * - change.case: {@link BstCaseChangersTest} + * - prefix: {@link BstTextPrefixerTest} + */ +class BstFunctionsTest { @Test public void testCompareFunctions() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test.compare } { #5 #5 = % TRUE #1 #2 = % FALSE @@ -30,7 +43,7 @@ public void testCompareFunctions() throws RecognitionException { #4 #3 > % TRUE #4 #4 > % FALSE "H" "H" = % TRUE - "H" "Ha" = % TRUE + "H" "Ha" = % FALSE } EXECUTE { test.compare } """); @@ -52,7 +65,7 @@ public void testCompareFunctions() throws RecognitionException { @Test public void testArithmeticFunctions() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { #1 #1 + % 2 #5 #2 - % 3 @@ -69,7 +82,7 @@ public void testArithmeticFunctions() throws RecognitionException { @Test public void testArithmeticFunctionTypeMismatch() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { #1 "HELLO" + % Should throw exception } @@ -82,7 +95,7 @@ public void testArithmeticFunctionTypeMismatch() throws RecognitionException { @Test public void testStringOperations() throws RecognitionException { // Test for concat (*) and add.period - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { "H" "ello" * % Hello "Johnny" add.period$ % Johnny. @@ -113,7 +126,7 @@ public void testStringOperations() throws RecognitionException { @Test public void testMissing() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { title } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -123,7 +136,7 @@ public void testMissing() throws RecognitionException { ITERATE { test } """); List testEntries = List.of( - TestBstVM.defaultTestEntry(), + BstVMTest.defaultTestEntry(), new BibEntry(StandardEntryType.Article) .withCitationKey("test") .withField(StandardField.AUTHOR, "No title")); @@ -139,7 +152,7 @@ public void testMissing() throws RecognitionException { @Test public void testNumNames() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { "Johnny Foo { and } Mary Bar" num.names$ "Johnny Foo and Mary Bar" num.names$ @@ -156,7 +169,7 @@ public void testNumNames() throws RecognitionException { @Test public void testSubstring() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { "123456789" #2 #1 substring$ % 2 "123456789" #4 global.max$ substring$ % 456789 @@ -186,7 +199,7 @@ public void testSubstring() throws RecognitionException { @Test public void testEmpty() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { title } { } { } READ STRINGS { s } @@ -213,7 +226,7 @@ public void testEmpty() throws RecognitionException { @Test public void testFormatNameStatic() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { format }{ "Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin" #1 "{vv~}{ll}{, jj}{, f}?" format.name$ } EXECUTE { format } """); @@ -227,7 +240,7 @@ public void testFormatNameStatic() throws RecognitionException { @Test public void testFormatNameInEntries() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { author } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -237,7 +250,7 @@ public void testFormatNameInEntries() throws RecognitionException { ITERATE { format } """); List testEntries = List.of( - TestBstVM.defaultTestEntry(), + BstVMTest.defaultTestEntry(), new BibEntry(StandardEntryType.Book) .withCitationKey("test") .withField(StandardField.AUTHOR, "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin")); @@ -251,7 +264,7 @@ public void testFormatNameInEntries() throws RecognitionException { @Test public void testChangeCase() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" STRINGS { title } READ FUNCTION { format.title } { @@ -281,7 +294,7 @@ public void testChangeCase() throws RecognitionException { @Test public void testTextLength() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { "hello world" text.length$ % 11 "Hello {W}orld" text.length$ % 11 @@ -310,7 +323,7 @@ public void testTextLength() throws RecognitionException { @Test public void testIntToStr() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { #3 int.to.str$ #9999 int.to.str$ } EXECUTE { test } """); @@ -324,7 +337,7 @@ public void testIntToStr() throws RecognitionException { @Test public void testChrToInt() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { "H" chr.to.int$ } EXECUTE { test } """); @@ -337,7 +350,7 @@ public void testChrToInt() throws RecognitionException { @Test public void testChrToIntIntToChr() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test } { "H" chr.to.int$ int.to.chr$ } EXECUTE {test} """); @@ -350,7 +363,7 @@ public void testChrToIntIntToChr() throws RecognitionException { @Test public void testType() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -375,7 +388,7 @@ public void testType() throws RecognitionException { @Test public void testCallType() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { title } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -386,7 +399,7 @@ public void testCallType() throws RecognitionException { ITERATE { call.type$ } """); List testEntries = List.of( - TestBstVM.defaultTestEntry(), + BstVMTest.defaultTestEntry(), new BibEntry(StandardEntryType.Book) .withCitationKey("test") .withField(StandardField.TITLE, "Test")); @@ -402,7 +415,7 @@ public void testCallType() throws RecognitionException { @Test public void testSwap() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { a } { #3 "Hallo" swap$ } EXECUTE { a } """); @@ -417,7 +430,7 @@ public void testSwap() throws RecognitionException { @Test void testAssignFunction() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" INTEGERS { test.var } FUNCTION { test.func } { #1 'test.var := } EXECUTE { test.func } @@ -433,7 +446,7 @@ void testAssignFunction() { @Test void testSimpleIf() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { path1 } { #1 } FUNCTION { path0 } { #0 } FUNCTION { test } { @@ -452,7 +465,7 @@ void testSimpleIf() { @Test void testSimpleWhile() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" INTEGERS { i } FUNCTION { test } { #3 'i := @@ -477,7 +490,7 @@ void testSimpleWhile() { @Test public void testNestedControlFunctions() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" STRINGS { t } FUNCTION { not } { { #0 } { #1 } if$ } FUNCTION { n.dashify } { @@ -522,7 +535,7 @@ public void testNestedControlFunctions() throws RecognitionException { @Test public void testLogic() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { not } { { #0 } { #1 } if$ } FUNCTION { and } { 'skip$ { pop$ #0 } if$ } FUNCTION { or } { { pop$ #1 } 'skip$ if$ } @@ -556,11 +569,13 @@ public void testLogic() throws RecognitionException { assertEquals(0, vm.getStack().size()); } - + /** + * See also {@link BstWidthCalculatorTest} + */ @Test public void testWidth() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { address author title type } { } { label } STRINGS { longest.label } INTEGERS { number.label longest.label.width } @@ -592,7 +607,7 @@ public void testWidth() throws RecognitionException { EXECUTE {begin.bib} """); - List testEntries = List.of(TestBstVM.defaultTestEntry()); + List testEntries = List.of(BstVMTest.defaultTestEntry()); vm.render(testEntries); @@ -602,14 +617,11 @@ public void testWidth() throws RecognitionException { /* ToDo: - - cite - duplicate - newline - preamble - - purify - quote - stack - - text.prefix - top - warning - write @@ -620,7 +632,7 @@ public void testWidth() throws RecognitionException { @Test public void testDuplicateEmptyPopSwapIf() throws RecognitionException { // ToDo: Make better tests for this - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { emphasize } { duplicate$ empty$ { pop$ "" } diff --git a/src/test/java/org/jabref/logic/bst/TestBstVM.java b/src/test/java/org/jabref/logic/bst/BstVMTest.java similarity index 99% rename from src/test/java/org/jabref/logic/bst/TestBstVM.java rename to src/test/java/org/jabref/logic/bst/BstVMTest.java index dea343d8093..657c4ca053d 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVM.java +++ b/src/test/java/org/jabref/logic/bst/BstVMTest.java @@ -18,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class TestBstVM { +public class BstVMTest { public static BibEntry defaultTestEntry() { /* diff --git a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java similarity index 89% rename from src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java rename to src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java index 63e46e70def..917ea27f6d0 100644 --- a/src/test/java/org/jabref/logic/bst/TestBstVMVisitor.java +++ b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java @@ -16,11 +16,11 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -class TestBstVMVisitor { +class BstVMVisitorTest { @Test public void testVisitStringsCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM("STRINGS { test.string1 test.string2 test.string3 }"); + BstVMTest.TestVM vm = new BstVMTest.TestVM("STRINGS { test.string1 test.string2 test.string3 }"); vm.render(Collections.emptyList()); @@ -35,7 +35,7 @@ public void testVisitStringsCommand() { @Test public void testVisitIntegersCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM("INTEGERS { variable.a variable.b variable.c }"); + BstVMTest.TestVM vm = new BstVMTest.TestVM("INTEGERS { variable.a variable.b variable.c }"); vm.render(Collections.emptyList()); @@ -50,7 +50,7 @@ public void testVisitIntegersCommand() { @Test void testVisitFunctionCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" FUNCTION { test.func } { #1 'test.var := } EXECUTE { test.func } """); @@ -64,7 +64,7 @@ void testVisitFunctionCommand() { @Test void testVisitMacroCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" MACRO { jan } { "January" } EXECUTE { jan } """); @@ -80,8 +80,8 @@ void testVisitMacroCommand() { @Test void testVisitEntryCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM("ENTRY { address author title type } { variable } { label }"); - List testEntries = List.of(TestBstVM.defaultTestEntry()); + BstVMTest.TestVM vm = new BstVMTest.TestVM("ENTRY { address author title type } { variable } { label }"); + List testEntries = List.of(BstVMTest.defaultTestEntry()); vm.render(testEntries); @@ -97,11 +97,11 @@ void testVisitEntryCommand() { @Test void testVisitReadCommand() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { author title booktitle year owner timestamp url } { } { } READ """); - List testEntries = List.of(TestBstVM.defaultTestEntry()); + List testEntries = List.of(BstVMTest.defaultTestEntry()); vm.render(testEntries); @@ -117,7 +117,7 @@ void testVisitReadCommand() { @Test public void testVisitExecuteCommand() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" INTEGERS { variable.a } FUNCTION { init.state.consts } { #5 'variable.a := } EXECUTE { init.state.consts } @@ -132,14 +132,14 @@ public void testVisitExecuteCommand() throws RecognitionException { @Test public void testVisitIterateCommand() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { } { } { } FUNCTION { test } { cite$ } READ ITERATE { test } """); List testEntries = List.of( - TestBstVM.defaultTestEntry(), + BstVMTest.defaultTestEntry(), new BibEntry(StandardEntryType.Article) .withCitationKey("test")); @@ -152,7 +152,7 @@ public void testVisitIterateCommand() throws RecognitionException { @Test public void testVisitSortCommand() throws RecognitionException { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -175,7 +175,7 @@ public void testVisitSortCommand() throws RecognitionException { @Test void testVisitIdentifier() { - TestBstVM.TestVM vm = new TestBstVM.TestVM(""" + BstVMTest.TestVM vm = new BstVMTest.TestVM(""" ENTRY { } { local.variable } { local.label } READ STRINGS { label } @@ -190,7 +190,7 @@ void testVisitIdentifier() { } ITERATE { test } """); - List testEntries = List.of(TestBstVM.defaultTestEntry()); + List testEntries = List.of(BstVMTest.defaultTestEntry()); vm.render(testEntries); diff --git a/src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java b/src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java similarity index 93% rename from src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java rename to src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java index 8deac500a21..10207b45b10 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java @@ -1,9 +1,8 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.stream.Stream; -import org.jabref.logic.bst.util.BibtexCaseChanger; -import org.jabref.logic.bst.util.BibtexCaseChanger.FORMAT_MODE; +import org.jabref.logic.bst.util.BstCaseChanger.FORMAT_MODE; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -13,12 +12,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class BibtexCaseChangersTest { +public class BstCaseChangersTest { @ParameterizedTest @MethodSource("provideStringsForTitleLowers") public void testChangeCaseTitleLowers(String expected, String toBeFormatted) { - assertEquals(expected, BibtexCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.TITLE_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.TITLE_LOWERS)); } private static Stream provideStringsForTitleLowers() { @@ -60,7 +59,7 @@ private static Stream provideStringsForTitleLowers() { @ParameterizedTest @MethodSource("provideStringsForAllLowers") public void testChangeCaseAllLowers(String expected, String toBeFormatted) { - assertEquals(expected, BibtexCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_LOWERS)); } private static Stream provideStringsForAllLowers() { @@ -91,7 +90,7 @@ private static Stream provideStringsForAllLowers() { @ParameterizedTest @MethodSource("provideStringsForAllUppers") public void testChangeCaseAllUppers(String expected, String toBeFormatted) { - assertEquals(expected, BibtexCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_UPPERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_UPPERS)); } private static Stream provideStringsForAllUppers() { @@ -122,7 +121,7 @@ private static Stream provideStringsForAllUppers() { @ParameterizedTest @MethodSource("provideTitleCaseAllLowers") public void testTitleCaseAllLowers(String expected, String toBeFormatted) { - assertEquals(expected, BibtexCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_LOWERS)); } private static Stream provideTitleCaseAllLowers() { diff --git a/src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java b/src/test/java/org/jabref/logic/bst/util/BstNameFormatterTest.java similarity index 65% rename from src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java rename to src/test/java/org/jabref/logic/bst/util/BstNameFormatterTest.java index 78eb6c27604..4912af099bf 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstNameFormatterTest.java @@ -1,20 +1,19 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; -import org.jabref.logic.bst.util.BibtexNameFormatter; import org.jabref.model.entry.AuthorList; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -public class BibtexNameFormatterTest { +public class BstNameFormatterTest { @Test public void testUmlautsFullNames() { AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); assertEquals("de~laVall{\\'e}e~PoussinCharles Louis Xavier~Joseph", - BibtexNameFormatter.formatName(list.getAuthor(0), "{vv}{ll}{jj}{ff}")); + BstNameFormatter.formatName(list.getAuthor(0), "{vv}{ll}{jj}{ff}")); } @Test @@ -22,7 +21,7 @@ public void testUmlautsAbbreviations() { AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J.", - BibtexNameFormatter.formatName(list.getAuthor(0), "{vv~}{ll}{, jj}{, f.}")); + BstNameFormatter.formatName(list.getAuthor(0), "{vv~}{ll}{, jj}{, f.}")); } @Test @@ -30,14 +29,14 @@ public void testUmlautsAbbreviationsWithQuestionMark() { AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", - BibtexNameFormatter.formatName(list.getAuthor(0), "{vv~}{ll}{, jj}{, f}?")); + BstNameFormatter.formatName(list.getAuthor(0), "{vv~}{ll}{, jj}{, f}?")); } @Test public void testFormatName() { AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("dlVP", BibtexNameFormatter.formatName(list.getAuthor(0), "{v{}}{l{}}")); + assertEquals("dlVP", BstNameFormatter.formatName(list.getAuthor(0), "{v{}}{l{}}")); assertNameFormatA("Meyer, J?", "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); assertNameFormatB("J.~Meyer", "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); @@ -55,7 +54,7 @@ public void testFormatName() { } private void assertNameFormat(String string, String string2, int which, String format) { - assertEquals(string, BibtexNameFormatter.formatName(string2, which, format)); + assertEquals(string, BstNameFormatter.formatName(string2, which, format)); } private void assertNameFormatC(String string, String string2) { @@ -73,41 +72,41 @@ private void assertNameFormatA(String string, String string2) { @Test public void matchingBraceConsumedForCompleteWords() { StringBuilder sb = new StringBuilder(); - assertEquals(6, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HELLO} {WORLD}".toCharArray(), 0)); + assertEquals(6, BstNameFormatter.consumeToMatchingBrace(sb, "{HELLO} {WORLD}".toCharArray(), 0)); assertEquals("{HELLO}", sb.toString()); } @Test public void matchingBraceConsumedForBracesInWords() { StringBuilder sb = new StringBuilder(); - assertEquals(18, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}".toCharArray(), 12)); + assertEquals(18, BstNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}".toCharArray(), 12)); assertEquals("{WORLD}", sb.toString()); } @Test public void testConsumeToMatchingBrace() { StringBuilder sb = new StringBuilder(); - assertEquals(10, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}".toCharArray(), 0)); + assertEquals(10, BstNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}".toCharArray(), 0)); assertEquals("{HE{L{}L}O}", sb.toString()); } @Test public void testGetFirstCharOfString() { - assertEquals("C", BibtexNameFormatter.getFirstCharOfString("Charles")); - assertEquals("V", BibtexNameFormatter.getFirstCharOfString("Vall{\\'e}e")); - assertEquals("{\\'e}", BibtexNameFormatter.getFirstCharOfString("{\\'e}")); - assertEquals("{\\'e", BibtexNameFormatter.getFirstCharOfString("{\\'e")); - assertEquals("E", BibtexNameFormatter.getFirstCharOfString("{E")); + assertEquals("C", BstNameFormatter.getFirstCharOfString("Charles")); + assertEquals("V", BstNameFormatter.getFirstCharOfString("Vall{\\'e}e")); + assertEquals("{\\'e}", BstNameFormatter.getFirstCharOfString("{\\'e}")); + assertEquals("{\\'e", BstNameFormatter.getFirstCharOfString("{\\'e")); + assertEquals("E", BstNameFormatter.getFirstCharOfString("{E")); } @Test public void testNumberOfChars() { - assertEquals(6, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", -1)); - assertEquals(2, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", 2)); - assertEquals(1, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", 1)); - assertEquals(6, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", 6)); - assertEquals(6, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", 7)); - assertEquals(8, BibtexNameFormatter.numberOfChars("Vall{e}e", -1)); - assertEquals(6, BibtexNameFormatter.numberOfChars("Vall{\\'e this will be skipped}e", -1)); + assertEquals(6, BstNameFormatter.numberOfChars("Vall{\\'e}e", -1)); + assertEquals(2, BstNameFormatter.numberOfChars("Vall{\\'e}e", 2)); + assertEquals(1, BstNameFormatter.numberOfChars("Vall{\\'e}e", 1)); + assertEquals(6, BstNameFormatter.numberOfChars("Vall{\\'e}e", 6)); + assertEquals(6, BstNameFormatter.numberOfChars("Vall{\\'e}e", 7)); + assertEquals(8, BstNameFormatter.numberOfChars("Vall{e}e", -1)); + assertEquals(6, BstNameFormatter.numberOfChars("Vall{\\'e this will be skipped}e", -1)); } } diff --git a/src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java b/src/test/java/org/jabref/logic/bst/util/BstPurifierTest.java similarity index 86% rename from src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java rename to src/test/java/org/jabref/logic/bst/util/BstPurifierTest.java index 4018ef78f17..295b735fd37 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstPurifierTest.java @@ -1,21 +1,19 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.stream.Stream; -import org.jabref.logic.bst.util.BibtexPurify; - import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; -public class BibtexPurifyTest { +public class BstPurifierTest { @ParameterizedTest @MethodSource("provideTestStrings") public void testPurify(String expected, String toBePurified) { - assertEquals(expected, BibtexPurify.purify(toBePurified)); + assertEquals(expected, BstPurifier.purify(toBePurified)); } private static Stream provideTestStrings() { diff --git a/src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java b/src/test/java/org/jabref/logic/bst/util/BstTextPrefixerTest.java similarity index 79% rename from src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java rename to src/test/java/org/jabref/logic/bst/util/BstTextPrefixerTest.java index 0ec30dd64c8..cc857101822 100644 --- a/src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstTextPrefixerTest.java @@ -1,12 +1,10 @@ -package org.jabref.logic.bst; - -import org.jabref.logic.bst.util.BibtexTextPrefix; +package org.jabref.logic.bst.util; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -public class TextPrefixFunctionTest { +public class BstTextPrefixerTest { @Test public void testPrefix() { @@ -22,6 +20,6 @@ public void testPrefix() { } private static void assertPrefix(final String string, final String string2) { - assertEquals(string, BibtexTextPrefix.textPrefix(5, string2)); + assertEquals(string, BstTextPrefixer.textPrefix(5, string2)); } } diff --git a/src/test/java/org/jabref/logic/bst/BibtexWidthTest.java b/src/test/java/org/jabref/logic/bst/util/BstWidthCalculatorTest.java similarity index 90% rename from src/test/java/org/jabref/logic/bst/BibtexWidthTest.java rename to src/test/java/org/jabref/logic/bst/util/BstWidthCalculatorTest.java index 1d5ee597782..30f2f6770e5 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexWidthTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstWidthCalculatorTest.java @@ -1,9 +1,7 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.stream.Stream; -import org.jabref.logic.bst.util.BibtexWidth; - import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -37,12 +35,12 @@ * \bibcite{canh05}{CMM{$^{+}$}05} * */ -public class BibtexWidthTest { +public class BstWidthCalculatorTest { @ParameterizedTest @MethodSource("provideTestWidth") public void testWidth(int i, String str) { - assertEquals(i, BibtexWidth.width(str)); + assertEquals(i, BstWidthCalculator.width(str)); } private static Stream provideTestWidth() { @@ -62,7 +60,7 @@ private static Stream provideTestWidth() { @ParameterizedTest @MethodSource("provideTestGetCharWidth") public void testGetCharWidth(int i, Character c) { - assertEquals(i, BibtexWidth.getCharWidth(c)); + assertEquals(i, BstWidthCalculator.getCharWidth(c)); } private static Stream provideTestGetCharWidth() { From 951f20dcdea6d921de9f5dd82d99dea3047a4bd3 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 18 Jul 2022 17:48:08 +0200 Subject: [PATCH 30/39] Improved error logging --- .../org/jabref/logic/bst/BstFunctions.java | 207 +++++++++--------- src/main/java/org/jabref/logic/bst/BstVM.java | 6 +- .../org/jabref/logic/bst/BstVMContext.java | 9 +- .../org/jabref/logic/bst/BstVMVisitor.java | 36 +-- .../java/org/jabref/logic/bst/BstVMTest.java | 2 +- 5 files changed, 138 insertions(+), 122 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 2f15c308fbe..3df0f6847be 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -39,10 +39,10 @@ public class BstFunctions { @FunctionalInterface public interface BstFunction { - void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext); + void execute(BstVMVisitor visitor, ParserRuleContext ctx); - default void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntryContext) { - this.execute(visitor, parserRuleContext); + default void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntryContext) { + this.execute(visitor, ctx); } } @@ -106,9 +106,9 @@ protected Map getBuiltInFunctions() { * the integer 1 if the second is greater than the first, 0 * otherwise. */ - private void bstIsGreaterThan(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstIsGreaterThan(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation >"); + throw new BstVMException("Not enough operands on stack for operation > (line %d)".formatted(ctx.start.getLine())); } Object o2 = stack.pop(); Object o1 = stack.pop(); @@ -125,7 +125,7 @@ private void bstIsGreaterThan(BstVMVisitor visitor, ParserRuleContext parserRule * the integer 1 if the second is lower than the first, 0 * otherwise. */ - private void bstIsLowerThan(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstIsLowerThan(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { throw new BstVMException("Not enough operands on stack for operation <"); } @@ -133,7 +133,7 @@ private void bstIsLowerThan(BstVMVisitor visitor, ParserRuleContext parserRuleCo Object o1 = stack.pop(); if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new BstVMException("Can only compare two integers with <"); + throw new BstVMException("Can only compare two integers with < (line %d)".formatted(ctx.start.getLine())); } stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? BstVM.TRUE : BstVM.FALSE); @@ -143,9 +143,9 @@ private void bstIsLowerThan(BstVMVisitor visitor, ParserRuleContext parserRuleCo * Pops the top two (both integer or both string) literals, compares * them, and pushes the integer 1 if they're equal, 0 otherwise. */ - private void bstEquals(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstEquals(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation ="); + throw new BstVMException("Not enough operands on stack for operation = (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); Object o2 = stack.pop(); @@ -166,15 +166,15 @@ private void bstEquals(BstVMVisitor visitor, ParserRuleContext parserRuleContext /** * Pops the top two (integer) literals and pushes their sum. */ - private void bstAdd(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstAdd(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation +"); + throw new BstVMException("Not enough operands on stack for operation + (line %d)".formatted(ctx.start.getLine())); } Object o2 = stack.pop(); Object o1 = stack.pop(); if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new BstVMException("Can only compare two integers with +"); + throw new BstVMException("Can only compare two integers with + (line %d)".formatted(ctx.start.getLine())); } stack.push((Integer) o1 + (Integer) o2); @@ -184,15 +184,15 @@ private void bstAdd(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { * Pops the top two (integer) literals and pushes their difference * (the first subtracted from the second). */ - private void bstSubtract(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstSubtract(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation -"); + throw new BstVMException("Not enough operands on stack for operation - (line %d)".formatted(ctx.start.getLine())); } Object o2 = stack.pop(); Object o1 = stack.pop(); if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new BstVMException("Can only subtract two integers with -"); + throw new BstVMException("Can only subtract two integers with - (line %d)".formatted(ctx.start.getLine())); } stack.push((Integer) o1 - (Integer) o2); @@ -203,9 +203,9 @@ private void bstSubtract(BstVMVisitor visitor, ParserRuleContext parserRuleConte * order, that is, the order in which pushed), and pushes the * resulting string. */ - private void bstConcat(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstConcat(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation *"); + throw new BstVMException("Not enough operands on stack for operation * (line %d)".formatted(ctx.start.getLine())); } Object o2 = stack.pop(); Object o1 = stack.pop(); @@ -220,7 +220,7 @@ private void bstConcat(BstVMVisitor visitor, ParserRuleContext parserRuleContext if (!((o1 instanceof String) && (o2 instanceof String))) { LOGGER.error("o1: {} ({})", o1, o1.getClass()); LOGGER.error("o2: {} ({})", o2, o2.getClass()); - throw new BstVMException("Can only concatenate two String with *"); + throw new BstVMException("Can only concatenate two String with * (line %d)".formatted(ctx.start.getLine())); } stack.push(o1.toString() + o2); @@ -233,23 +233,23 @@ private void bstConcat(BstVMVisitor visitor, ParserRuleContext parserRuleContext public class BstAssignFunction implements BstFunction { @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { - this.execute(visitor, parserRuleContext, null); + public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { + this.execute(visitor, ctx, null); } @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntry) { + public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntry) { if (stack.size() < 2) { - throw new BstVMException("Invalid call to operation :="); + throw new BstVMException("Invalid call to operation := (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); Object o2 = stack.pop(); - doAssign(bstEntry, o1, o2); + doAssign(bstEntry, o1, o2, ctx); } - private boolean doAssign(BstEntry context, Object o1, Object o2) { + private boolean doAssign(BstEntry context, Object o1, Object o2, ParserRuleContext ctx) { if (!(o1 instanceof BstVMVisitor.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { - throw new BstVMException("Invalid parameters"); + throw new BstVMException("Invalid parameters (line %d)".formatted(ctx.start.getLine())); } String name = ((BstVMVisitor.Identifier) o1).name(); @@ -288,14 +288,14 @@ private boolean doAssign(BstEntry context, Object o1, Object o2) { * '}' character isn't a `.', `?', or `!', and pushes this resulting * string. */ - private void bstAddPeriod(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstAddPeriod(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation add.period$"); + throw new BstVMException("Not enough operands on stack for operation add.period$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new BstVMException("Can only add a period to a string for add.period$"); + throw new BstVMException("Can only add a period to a string for add.period$ (line %d)".formatted(ctx.start.getLine())); } Matcher m = ADD_PERIOD_PATTERN.matcher(s); @@ -326,16 +326,16 @@ private void bstAddPeriod(BstVMVisitor visitor, ParserRuleContext parserRuleCont */ public class BstCallTypeFunction implements BstFunction { @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { - throw new BstVMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); + public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { + throw new BstVMException("Call.type$ can only be called from within a context (ITERATE or REVERSE). (line %d)".formatted(ctx.start.getLine())); } @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntry) { + public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntry) { if (bstEntry == null) { - this.execute(visitor, parserRuleContext); // Throw error + this.execute(visitor, ctx); // Throw error } else { - functions.get(bstEntry.entry.getType().getName()).execute(visitor, parserRuleContext, bstEntry); + functions.get(bstEntry.entry.getType().getName()).execute(visitor, ctx, bstEntry); } } } @@ -359,19 +359,19 @@ public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, B * the strings t and T are equivalent for the purposes of this built-in * function.) */ - private void bstChangeCase(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstChangeCase(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation change.case$"); + throw new BstVMException("Not enough operands on stack for operation change.case$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); if (!((o1 instanceof String) && (((String) o1).length() == 1))) { - throw new BstVMException("A format string of length 1 is needed for change.case$"); + throw new BstVMException("A format string of length 1 is needed for change.case$ (line %d)".formatted(ctx.start.getLine())); } Object o2 = stack.pop(); if (!(o2 instanceof String)) { - throw new BstVMException("A string is needed as second parameter for change.case$"); + throw new BstVMException("A string is needed as second parameter for change.case$ (line %d)".formatted(ctx.start.getLine())); } char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); @@ -385,14 +385,14 @@ private void bstChangeCase(BstVMVisitor visitor, ParserRuleContext parserRuleCon * character, converts it to the corresponding ASCII integer, and * pushes this integer. */ - private void bstChrToInt(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstChrToInt(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation chr.to.int$"); + throw new BstVMException("Not enough operands on stack for operation chr.to.int$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { - throw new BstVMException("Can only perform chr.to.int$ on string with length 1"); + throw new BstVMException("Can only perform chr.to.int$ on string with length 1 (line %d)".formatted(ctx.start.getLine())); } stack.push((int) s.charAt(0)); @@ -404,14 +404,14 @@ private void bstChrToInt(BstVMVisitor visitor, ParserRuleContext parserRuleConte */ public class BstCiteFunction implements BstFunction { @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { - throw new BstVMException("Must have an entry to cite$"); + public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { + throw new BstVMException("Must have an entry to cite$ (line %d)".formatted(ctx.start.getLine())); } @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntryContext) { + public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntryContext) { if (bstEntryContext == null) { - execute(visitor, parserRuleContext); + execute(visitor, ctx); return; } @@ -422,9 +422,9 @@ public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, B /** * Pops the top literal from the stack and pushes two copies of it. */ - private void bstDuplicate(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstDuplicate(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation duplicate$"); + throw new BstVMException("Not enough operands on stack for operation duplicate$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); @@ -437,9 +437,9 @@ private void bstDuplicate(BstVMVisitor visitor, ParserRuleContext parserRuleCont * field or a string having no non-white-space characters, 0 * otherwise. */ - private void bstEmpty(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstEmpty(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation empty$"); + throw new BstVMException("Not enough operands on stack for operation empty$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); @@ -449,7 +449,7 @@ private void bstEmpty(BstVMVisitor visitor, ParserRuleContext parserRuleContext) } if (!(o1 instanceof String s)) { - throw new BstVMException("Operand does not match function empty$"); + throw new BstVMException("Operand does not match function empty$ (line %d)".formatted(ctx.start.getLine())); } stack.push("".equals(s.trim()) ? BstVM.TRUE : BstVM.FALSE); @@ -466,9 +466,9 @@ private void bstEmpty(BstVMVisitor visitor, ParserRuleContext parserRuleContext) * pushes the formatted name. If any of the types is incorrect, it * complains and pushes the null string. */ - private void bstFormatName(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstFormatName(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 3) { - throw new BstVMException("Not enough operands on stack for operation format.name$"); + throw new BstVMException("Not enough operands on stack for operation format.name$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); Object o2 = stack.pop(); @@ -489,7 +489,7 @@ private void bstFormatName(BstVMVisitor visitor, ParserRuleContext parserRuleCon } else { AuthorList a = AuthorList.parse(names); if (name > a.getNumberOfAuthors()) { - throw new BstVMException("Author Out of Bounds. Number " + name + " invalid for " + names); + throw new BstVMException("Author Out of Bounds. Number %d invalid for %s (line %d)".formatted(name, names, ctx.start.getLine())); } Author author = a.getAuthor(name - 1); @@ -503,9 +503,9 @@ private void bstFormatName(BstVMVisitor visitor, ParserRuleContext parserRuleCon * than 0, it executes the second literal, else it executes the * first. */ - private void bstIf(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstIf(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 3) { - throw new BstVMException("Not enough operands on stack for if$"); + throw new BstVMException("Not enough operands on stack for if$ (line %d)".formatted(ctx.start.getLine())); } Object f1 = stack.pop(); @@ -515,13 +515,13 @@ private void bstIf(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree)) && (i instanceof Integer)) { - throw new BstVMException("Expecting two functions and an integer for if$."); + throw new BstVMException("Expecting two functions and an integer for if$ (line %d)".formatted(ctx.start.getLine())); } if (((Integer) i) > 0) { - new BstVMVisitor.BstVMFunction<>(f2).execute(visitor, parserRuleContext); + new BstVMVisitor.BstVMFunction<>(f2).execute(visitor, ctx); } else { - new BstVMVisitor.BstVMFunction<>(f1).execute(visitor, parserRuleContext); + new BstVMVisitor.BstVMFunction<>(f1).execute(visitor, ctx); } } @@ -530,14 +530,14 @@ private void bstIf(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { * value of a single character, converts it to the corresponding * single-character string, and pushes this string. */ - private void bstIntToChr(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstIntToChr(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation int.to.chr$"); + throw new BstVMException("Not enough operands on stack for operation int.to.chr$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); if (!(o1 instanceof Integer i)) { - throw new BstVMException("Can only perform operation int.to.chr$ on an Integer"); + throw new BstVMException("Can only perform operation int.to.chr$ on an Integer (line %d)".formatted(ctx.start.getLine())); } stack.push(String.valueOf((char) i.intValue())); @@ -547,14 +547,14 @@ private void bstIntToChr(BstVMVisitor visitor, ParserRuleContext parserRuleConte * Pops the top (integer) literal, converts it to its (unique) * string equivalent, and pushes this string. */ - private void bstIntToStr(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstIntToStr(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation int.to.str$"); + throw new BstVMException("Not enough operands on stack for operation int.to.str$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); if (!(o1 instanceof Integer)) { - throw new BstVMException("Can only transform an integer to an string using int.to.str$"); + throw new BstVMException("Can only transform an integer to an string using int.to.str$ (line %d)".formatted(ctx.start.getLine())); } stack.push(o1.toString()); @@ -564,9 +564,9 @@ private void bstIntToStr(BstVMVisitor visitor, ParserRuleContext parserRuleConte * Pops the top literal and pushes the integer 1 if it's a missing * field, 0 otherwise. */ - private void bstMissing(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstMissing(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation missing$"); + throw new BstVMException("Not enough operands on stack for operation missing$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); @@ -576,7 +576,7 @@ private void bstMissing(BstVMVisitor visitor, ParserRuleContext parserRuleContex } if (!(o1 instanceof String)) { - LOGGER.warn("Not a string or missing field in operation missing$"); + LOGGER.warn("Not a string or missing field in operation missing$ (line %d)".formatted(ctx.start.getLine())); stack.push(BstVM.TRUE); return; } @@ -591,7 +591,7 @@ private void bstMissing(BstVMVisitor visitor, ParserRuleContext parserRuleContex * function only when you want a blank line or an explicit line * break. */ - private void bstNewLine(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstNewLine(BstVMVisitor visitor, ParserRuleContext ctx) { this.bbl.append('\n'); } @@ -601,14 +601,14 @@ private void bstNewLine(BstVMVisitor visitor, ParserRuleContext parserRuleContex * substring "and" (ignoring case differences) surrounded by * non-null white-space at the top brace level. */ - private void bstNumNames(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstNumNames(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation num.names$"); + throw new BstVMException("Not enough operands on stack for operation num.names$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new BstVMException("Need a string at the top of the stack for num.names$"); + throw new BstVMException("Need a string at the top of the stack for num.names$ (line %d)".formatted(ctx.start.getLine())); } stack.push(AuthorList.parse(s).getNumberOfAuthors()); @@ -618,7 +618,7 @@ private void bstNumNames(BstVMVisitor visitor, ParserRuleContext parserRuleConte * Pops the top of the stack but doesn't print it; this gets rid of * an unwanted stack literal. */ - private void bstPop(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstPop(BstVMVisitor visitor, ParserRuleContext ctx) { stack.pop(); } @@ -628,7 +628,7 @@ private void bstPop(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { * database files. (or the empty string if there were none) * '@PREAMBLE' strings are read from the database files. */ - private void bstPreamble(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstPreamble(BstVMVisitor visitor, ParserRuleContext ctx) { stack.push(preamble); } @@ -639,9 +639,9 @@ private void bstPreamble(BstVMVisitor visitor, ParserRuleContext parserRuleConte * contained in the control sequences associated with a \special * character", and pushes the resulting string. */ - private void bstPurify(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstPurify(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation purify$"); + throw new BstVMException("Not enough operands on stack for operation purify$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); @@ -657,14 +657,14 @@ private void bstPurify(BstVMVisitor visitor, ParserRuleContext parserRuleContext /** * Pushes the string consisting of the double-quote character. */ - private void bstQuote(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstQuote(BstVMVisitor visitor, ParserRuleContext ctx) { stack.push("\""); } /** * Does nothing. */ - private void bstSkip(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstSkip(BstVMVisitor visitor, ParserRuleContext ctx) { // no-op } @@ -672,7 +672,7 @@ private void bstSkip(BstVMVisitor visitor, ParserRuleContext parserRuleContext) * Pops and prints the whole stack; it's meant to be used for style * designers while debugging. */ - private void bstStack(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstStack(BstVMVisitor visitor, ParserRuleContext ctx) { while (!stack.empty()) { LOGGER.debug("Stack entry {}", stack.pop()); } @@ -687,16 +687,16 @@ private void bstStack(BstVMVisitor visitor, ParserRuleContext parserRuleContext) * (including) from the end if start is negative (where the first * character from the end is the last character). */ - private void bstSubstring(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstSubstring(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 3) { - throw new BstVMException("Not enough operands on stack for operation substring$"); + throw new BstVMException("Not enough operands on stack for operation substring$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); Object o2 = stack.pop(); Object o3 = stack.pop(); if (!((o1 instanceof Integer len) && (o2 instanceof Integer start) && (o3 instanceof String s))) { - throw new BstVMException("Expecting two integers and a string for substring$"); + throw new BstVMException("Expecting two integers and a string for substring$ (line %d)".formatted(ctx.start.getLine())); } int lenI = len; @@ -729,9 +729,9 @@ private void bstSubstring(BstVMVisitor visitor, ParserRuleContext parserRuleCont * text character, even if it's missing its matching right brace, * and where braces don't count as text characters. */ - private void bstSwap(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstSwap(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation swap$"); + throw new BstVMException("Not enough operands on stack for operation swap$ (line %d)".formatted(ctx.start.getLine())); } Object f1 = stack.pop(); Object f2 = stack.pop(); @@ -751,14 +751,14 @@ private void bstSwap(BstVMVisitor visitor, ParserRuleContext parserRuleContext) * BibTEX considers everything contained inside the braces as a * single letter. */ - private void bstTextLength(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstTextLength(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation text.length$"); + throw new BstVMException("Not enough operands on stack for operation text.length$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); if (!(o1 instanceof String s)) { - throw new BstVMException("Can only perform operation on a string text.length$"); + throw new BstVMException("Can only perform operation on a string text.length$ (line %d)".formatted(ctx.start.getLine())); } char[] c = s.toCharArray(); @@ -807,21 +807,21 @@ private void bstTextLength(BstVMVisitor visitor, ParserRuleContext parserRuleCon * consider braces to be text characters; furthermore, this function * appends any needed matching right braces. */ - private void bstTextPrefix(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstTextPrefix(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation text.prefix$"); + throw new BstVMException("Not enough operands on stack for operation text.prefix$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); if (!(o1 instanceof Integer)) { - LOGGER.warn("An integer is needed as first parameter to text.prefix$"); + LOGGER.warn("An integer is needed as first parameter to text.prefix$ (line {})", ctx.start.getLine()); stack.push(""); return; } Object o2 = stack.pop(); if (!(o2 instanceof String)) { - LOGGER.warn("A string is needed as second parameter to text.prefix$"); + LOGGER.warn("A string is needed as second parameter to text.prefix$ (line {})", ctx.start.getLine()); stack.push(""); return; } @@ -832,8 +832,8 @@ private void bstTextPrefix(BstVMVisitor visitor, ParserRuleContext parserRuleCon /** * Pops and prints the top of the stack to the log file. It's useful for debugging. */ - private void bstTop(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { - LOGGER.debug("Stack entry {}", stack.pop()); + private void bstTop(BstVMVisitor visitor, ParserRuleContext ctx) { + LOGGER.debug("Stack entry {} (line {})", stack.pop(), ctx.start.getLine()); } /** @@ -842,14 +842,14 @@ private void bstTop(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { */ public class BstTypeFunction implements BstFunction { @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { - throw new BstVMException("type$ need a context."); + public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { + throw new BstVMException("type$ need a context (line %d)".formatted(ctx.start.getLine())); } @Override - public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, BstEntry bstEntryContext) { + public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntryContext) { if (bstEntryContext == null) { - this.execute(visitor, parserRuleContext); + this.execute(visitor, ctx); return; } @@ -862,7 +862,7 @@ public void execute(BstVMVisitor visitor, ParserRuleContext parserRuleContext, B * message. This also increments a count of the number of warning * messages issued. */ - private void bstWarning(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstWarning(BstVMVisitor visitor, ParserRuleContext ctx) { LOGGER.warn("Warning (#{}): {}", bstWarning++, stack.pop()); } @@ -871,16 +871,16 @@ private void bstWarning(BstVMVisitor visitor, ParserRuleContext parserRuleContex * second as long as the (integer) literal left on the stack by * executing the first is greater than 0. */ - private void bstWhile(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstWhile(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.size() < 2) { - throw new BstVMException("Not enough operands on stack for operation while$"); + throw new BstVMException("Not enough operands on stack for operation while$ (line %d)".formatted(ctx.start.getLine())); } Object f2 = stack.pop(); Object f1 = stack.pop(); if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree))) { - throw new BstVMException("Expecting two functions for while$."); + throw new BstVMException("Expecting two functions for while$ (line %d)".formatted(ctx.start.getLine())); } do { @@ -888,7 +888,8 @@ private void bstWhile(BstVMVisitor visitor, ParserRuleContext parserRuleContext) Object i = stack.pop(); if (!(i instanceof Integer)) { - throw new BstVMException("First parameter to while has to return an integer but was " + i); + throw new BstVMException("First parameter to while has to return an integer but was %s (line %d)" + .formatted(i.toString(), ctx.start.getLine())); } if ((Integer) i <= 0) { break; @@ -906,9 +907,9 @@ private void bstWhile(BstVMVisitor visitor, ParserRuleContext parserRuleContext) * characters (even without their |right_brace|s) are handled specially. If the * literal isn't a string, it complains and pushes~0. */ - private void bstWidth(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstWidth(BstVMVisitor visitor, ParserRuleContext ctx) { if (stack.isEmpty()) { - throw new BstVMException("Not enough operands on stack for operation width$"); + throw new BstVMException("Not enough operands on stack for operation width$ (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); @@ -926,7 +927,7 @@ private void bstWidth(BstVMVisitor visitor, ParserRuleContext parserRuleContext) * (which will result in stuff being written onto the bbl file when * the buffer fills up). */ - private void bstWrite(BstVMVisitor visitor, ParserRuleContext parserRuleContext) { + private void bstWrite(BstVMVisitor visitor, ParserRuleContext ctx) { String s = (String) stack.pop(); bbl.append(s); } diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java index cea818bd442..0bc2f2c83e9 100644 --- a/src/main/java/org/jabref/logic/bst/BstVM.java +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -39,11 +39,13 @@ public class BstVM { protected static final Integer TRUE = 1; protected final ParseTree tree; - protected BstVMContext latestContext; // for testing + private Path path = null; + public BstVM(Path path) throws RecognitionException, IOException { this(CharStreams.fromPath(path)); + this.path = path; } public BstVM(String s) throws RecognitionException { @@ -86,7 +88,7 @@ public String render(Collection bibEntries, BibDatabase bibDatabase) { StringBuilder resultBuffer = new StringBuilder(); - BstVMContext bstVMContext = new BstVMContext(entries, bibDatabase); + BstVMContext bstVMContext = new BstVMContext(entries, bibDatabase, path); bstVMContext.functions().putAll(new BstFunctions(bstVMContext, resultBuffer).getBuiltInFunctions()); bstVMContext.integers().put("entry.max$", Integer.MAX_VALUE); bstVMContext.integers().put("global.max$", Integer.MAX_VALUE); diff --git a/src/main/java/org/jabref/logic/bst/BstVMContext.java b/src/main/java/org/jabref/logic/bst/BstVMContext.java index 13d6563ffac..8a98f605dd8 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMContext.java +++ b/src/main/java/org/jabref/logic/bst/BstVMContext.java @@ -1,8 +1,10 @@ package org.jabref.logic.bst; +import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Stack; import org.jabref.model.database.BibDatabase; @@ -12,8 +14,9 @@ public record BstVMContext(List entries, Map integers, Map functions, Stack stack, - BibDatabase bibDatabase) { - public BstVMContext(List entries, BibDatabase bibDatabase) { - this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), new Stack<>(), bibDatabase); + BibDatabase bibDatabase, + Optional path) { + public BstVMContext(List entries, BibDatabase bibDatabase, Path path) { + this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), new Stack<>(), bibDatabase, Optional.ofNullable(path)); } } diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index c672ce438f5..494fa7a53a4 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -17,8 +17,12 @@ import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class BstVMVisitor extends BstBaseVisitor { + private static final Logger LOGGER = LoggerFactory.getLogger(BstVMVisitor.class); + private final BstVMContext bstVMContext; private final StringBuilder bbl; @@ -223,21 +227,27 @@ public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { @Override public Integer visitStackitem(BstParser.StackitemContext ctx) { for (ParseTree childNode : ctx.children) { - if (childNode instanceof TerminalNode token) { - switch (token.getSymbol().getType()) { - case BstParser.STRING -> { - String s = token.getText(); - bstVMContext.stack().push(s.substring(1, s.length() - 1)); + try { + if (childNode instanceof TerminalNode token) { + switch (token.getSymbol().getType()) { + case BstParser.STRING -> { + String s = token.getText(); + bstVMContext.stack().push(s.substring(1, s.length() - 1)); + } + case BstParser.INTEGER -> + bstVMContext.stack().push(Integer.parseInt(token.getText().substring(1))); + case BstParser.QUOTED -> + bstVMContext.stack().push(new Identifier(token.getText().substring(1))); } - case BstParser.INTEGER -> - bstVMContext.stack().push(Integer.parseInt(token.getText().substring(1))); - case BstParser.QUOTED -> - bstVMContext.stack().push(new Identifier(token.getText().substring(1))); + } else if (childNode instanceof BstParser.StackContext) { + bstVMContext.stack().push(childNode); + } else { + new BstVMVisitor.BstVMFunction<>(childNode).execute(this, ctx); } - } else if (childNode instanceof BstParser.StackContext) { - bstVMContext.stack().push(childNode); - } else { - visit(childNode); + } catch (BstVMException e) { + bstVMContext.path().ifPresentOrElse( + (path) -> LOGGER.error("{} ({})", e.getMessage(), path), + () -> LOGGER.error(e.getMessage())); } } return BstVM.TRUE; diff --git a/src/test/java/org/jabref/logic/bst/BstVMTest.java b/src/test/java/org/jabref/logic/bst/BstVMTest.java index 657c4ca053d..8f256e726ca 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMTest.java @@ -155,7 +155,7 @@ public void testHypthenatedName() throws RecognitionException, IOException { public static class TestVM extends BstVM { private TestVM(Path path) throws RecognitionException, IOException { - super(CharStreams.fromPath(path)); + super(path); } protected TestVM(String s) throws RecognitionException { From ce070ac9d28eef73e3217c4d2e570f9f0ea9d8ae Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 18 Jul 2022 20:50:44 +0200 Subject: [PATCH 31/39] Fix error msg --- src/main/java/org/jabref/logic/bst/BstVMVisitor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 494fa7a53a4..320c0ccdf9b 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -207,6 +207,7 @@ public Integer visitIdentifier(BstParser.IdentifierContext ctx) { if (bstVMContext.functions().containsKey(name)) { bstVMContext.functions().get(name).execute(this, ctx); + return BstVM.TRUE; } throw new BstVMException("No matching identifier found: " + name); From 824ae7f42750434196aeee890d92cc4894db6934 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 18 Jul 2022 20:50:54 +0200 Subject: [PATCH 32/39] wip --- .../org/jabref/logic/bst/BstFunctions.java | 6 +- .../org/jabref/logic/bst/BstVMVisitor.java | 15 +-- .../java/org/jabref/logic/bst/BstVMTest.java | 97 ++++++++++++++++--- 3 files changed, 97 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 3df0f6847be..99a86ac447b 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -519,9 +519,11 @@ private void bstIf(BstVMVisitor visitor, ParserRuleContext ctx) { } if (((Integer) i) > 0) { - new BstVMVisitor.BstVMFunction<>(f2).execute(visitor, ctx); + visitor.visit((ParseTree) f2); + // new BstVMVisitor.BstVMFunction<>(f2).execute(visitor, ctx); } else { - new BstVMVisitor.BstVMFunction<>(f1).execute(visitor, ctx); + // new BstVMVisitor.BstVMFunction<>(f1).execute(visitor, ctx); + visitor.visit((ParseTree) f1); } } diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 320c0ccdf9b..c6b3def16b9 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -196,6 +196,7 @@ public Integer visitIdentifier(BstParser.IdentifierContext ctx) { return BstVM.TRUE; } } + if (bstVMContext.strings().containsKey(name)) { bstVMContext.stack().push(bstVMContext.strings().get(name)); return BstVM.TRUE; @@ -204,7 +205,6 @@ public Integer visitIdentifier(BstParser.IdentifierContext ctx) { bstVMContext.stack().push(bstVMContext.integers().get(name)); return BstVM.TRUE; } - if (bstVMContext.functions().containsKey(name)) { bstVMContext.functions().get(name).execute(this, ctx); return BstVM.TRUE; @@ -260,15 +260,16 @@ public record BstVMFunction(T expression) implements BstFunctions.BstFunction public void execute(BstVMVisitor visitor, ParserRuleContext bstFunctionContext) { if (expression instanceof String str) { visitor.bstVMContext.stack().push(str.substring(1, str.length() - 1)); // Macro - } else if (expression instanceof Integer integer) { - visitor.bstVMContext.stack().push(integer); - } else if (expression instanceof Identifier identifier) { - if (visitor.bstVMContext.functions().containsKey(identifier.name)) { - visitor.bstVMContext.functions().get(identifier.name).execute(visitor, bstFunctionContext); - } } else if (expression instanceof ParserRuleContext ctx) { visitor.visit(ctx); // Function } +// else if (expression instanceof Integer integer) { +// visitor.bstVMContext.stack().push(integer); +// } else if (expression instanceof Identifier identifier) { +// if (visitor.bstVMContext.functions().containsKey(identifier.name)) { +// visitor.bstVMContext.functions().get(identifier.name).execute(visitor, bstFunctionContext); +// } +// } } } } diff --git a/src/test/java/org/jabref/logic/bst/BstVMTest.java b/src/test/java/org/jabref/logic/bst/BstVMTest.java index 8f256e726ca..db5f2b71f68 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMTest.java @@ -11,7 +11,6 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; -import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.RecognitionException; import org.junit.jupiter.api.Test; @@ -59,17 +58,17 @@ public void testAbbrv() throws RecognitionException, IOException { @Test public void testSimple() throws RecognitionException { TestVM vm = new TestVM(""" - ENTRY { address author title type } { } { label } - INTEGERS { output.state before.all mid.sentence after.sentence after.block } - FUNCTION { init.state.consts }{ - #0 'before.all := - #1 'mid.sentence := - #2 'after.sentence := - #3 'after.block := - } - STRINGS { s t } - READ - """); + ENTRY { address author title type } { } { label } + INTEGERS { output.state before.all mid.sentence after.sentence after.block } + FUNCTION { init.state.consts }{ + #0 'before.all := + #1 'mid.sentence := + #2 'after.sentence := + #3 'after.block := + } + STRINGS { s t } + READ + """); List testEntries = List.of(defaultTestEntry()); vm.render(testEntries); @@ -152,6 +151,80 @@ public void testHypthenatedName() throws RecognitionException, IOException { assertTrue(result.contains("J.-P. Sartre")); } + @Test + void testChopWord() { + TestVM vm = new TestVM(""" + STRINGS { s } + INTEGERS { len } + + FUNCTION { chop.word } + { + 's := + 'len := + s #1 len substring$ = + { s len #1 + global.max$ substring$ } + 's + if$ + } + + FUNCTION { test } { + "A " #2 + "A Colorful Morning" + chop.word + } + + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertTrue(vm.latestContext.stack().get(0).equals("Colorful Morning")); + } + + @Test + void testSortFormatTitle() { + TestVM vm = new TestVM(""" + STRINGS { s t } + INTEGERS { len } + FUNCTION { sortify } { + purify$ + "l" change.case$ + } + + FUNCTION { chop.word } + { + 's := + 'len := + s #1 len substring$ = + { s len #1 + global.max$ substring$ } + 's + if$ + } + + FUNCTION {sort.format.title} + { 't := + % "A " #2 + % "An " #3 + "The " #4 t chop.word + % chop.word + % chop.word + sortify + #1 global.max$ substring$ + } + + FUNCTION { test } { + "A Colorful Morning" + sort.format.title + } + + EXECUTE {test} + """); + + vm.render(Collections.emptyList()); + + assertTrue(vm.latestContext.stack().get(0).equals("Colorful Morning")); + } + public static class TestVM extends BstVM { private TestVM(Path path) throws RecognitionException, IOException { From ff4ca80b72fb4f5025b2b4ea0e764a0b52ba2fb1 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 1 Aug 2022 10:18:21 +0200 Subject: [PATCH 33/39] Fixed tests --- .../org/jabref/logic/bst/BstFunctions.java | 14 ++++-- .../org/jabref/logic/bst/BstVMVisitor.java | 45 +++++++------------ .../java/org/jabref/logic/bst/BstVMTest.java | 24 ++++++---- 3 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 99a86ac447b..2dc66d8b52d 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -519,11 +519,17 @@ private void bstIf(BstVMVisitor visitor, ParserRuleContext ctx) { } if (((Integer) i) > 0) { - visitor.visit((ParseTree) f2); - // new BstVMVisitor.BstVMFunction<>(f2).execute(visitor, ctx); + callIdentifierOrTree(f2, visitor, ctx); } else { - // new BstVMVisitor.BstVMFunction<>(f1).execute(visitor, ctx); - visitor.visit((ParseTree) f1); + callIdentifierOrTree(f1, visitor, ctx); + } + } + + private void callIdentifierOrTree(Object f, BstVMVisitor visitor, ParserRuleContext ctx) { + if (f instanceof ParseTree tree) { + visitor.visit(tree); + } else if (f instanceof BstVMVisitor.Identifier identifier) { + visitor.resolveIdentifier(identifier.name(), ctx); } } diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index c6b3def16b9..1629a4bb329 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -58,13 +58,16 @@ public Integer visitIntegersCommand(BstParser.IntegersCommandContext ctx) { @Override public Integer visitFunctionCommand(BstParser.FunctionCommandContext ctx) { - bstVMContext.functions().put(ctx.id.getText(), new BstVMFunction<>(ctx.function)); + bstVMContext.functions().put(ctx.id.getText(), + (visitor, functionContext) -> visitor.visit(ctx.function)); return BstVM.TRUE; } @Override public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { - bstVMContext.functions().put(ctx.id.getText(), new BstVMFunction<>(ctx.repl.getText())); + String replacement = ctx.repl.getText().substring(1, ctx.repl.getText().length() - 1); + bstVMContext.functions().put(ctx.id.getText(), + (visitor, functionContext) -> bstVMContext.stack().push(replacement)); return BstVM.TRUE; } @@ -180,34 +183,37 @@ public Integer visitSortCommand(BstParser.SortCommandContext ctx) { @Override public Integer visitIdentifier(BstParser.IdentifierContext ctx) { - String name = ctx.IDENTIFIER().getText(); + resolveIdentifier(ctx.IDENTIFIER().getText(), ctx); + return BstVM.TRUE; + } + protected void resolveIdentifier(String name, ParserRuleContext ctx) { if (selectedBstEntry != null) { if (selectedBstEntry.fields.containsKey(name)) { bstVMContext.stack().push(selectedBstEntry.fields.get(name)); - return BstVM.TRUE; + return; } if (selectedBstEntry.localStrings.containsKey(name)) { bstVMContext.stack().push(selectedBstEntry.localStrings.get(name)); - return BstVM.TRUE; + return; } if (selectedBstEntry.localIntegers.containsKey(name)) { bstVMContext.stack().push(selectedBstEntry.localIntegers.get(name)); - return BstVM.TRUE; + return; } } if (bstVMContext.strings().containsKey(name)) { bstVMContext.stack().push(bstVMContext.strings().get(name)); - return BstVM.TRUE; + return; } if (bstVMContext.integers().containsKey(name)) { bstVMContext.stack().push(bstVMContext.integers().get(name)); - return BstVM.TRUE; + return; } if (bstVMContext.functions().containsKey(name)) { bstVMContext.functions().get(name).execute(this, ctx); - return BstVM.TRUE; + return; } throw new BstVMException("No matching identifier found: " + name); @@ -243,7 +249,7 @@ public Integer visitStackitem(BstParser.StackitemContext ctx) { } else if (childNode instanceof BstParser.StackContext) { bstVMContext.stack().push(childNode); } else { - new BstVMVisitor.BstVMFunction<>(childNode).execute(this, ctx); + this.visit(childNode); } } catch (BstVMException e) { bstVMContext.path().ifPresentOrElse( @@ -253,23 +259,4 @@ public Integer visitStackitem(BstParser.StackitemContext ctx) { } return BstVM.TRUE; } - - public record BstVMFunction(T expression) implements BstFunctions.BstFunction { - - @Override - public void execute(BstVMVisitor visitor, ParserRuleContext bstFunctionContext) { - if (expression instanceof String str) { - visitor.bstVMContext.stack().push(str.substring(1, str.length() - 1)); // Macro - } else if (expression instanceof ParserRuleContext ctx) { - visitor.visit(ctx); // Function - } -// else if (expression instanceof Integer integer) { -// visitor.bstVMContext.stack().push(integer); -// } else if (expression instanceof Identifier identifier) { -// if (visitor.bstVMContext.functions().containsKey(identifier.name)) { -// visitor.bstVMContext.functions().get(identifier.name).execute(visitor, bstFunctionContext); -// } -// } - } - } } diff --git a/src/test/java/org/jabref/logic/bst/BstVMTest.java b/src/test/java/org/jabref/logic/bst/BstVMTest.java index db5f2b71f68..85b340fb4ba 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMTest.java @@ -152,7 +152,7 @@ public void testHypthenatedName() throws RecognitionException, IOException { } @Test - void testChopWord() { + void testAbbrevStyleChopWord() { TestVM vm = new TestVM(""" STRINGS { s } INTEGERS { len } @@ -171,6 +171,10 @@ void testChopWord() { "A " #2 "A Colorful Morning" chop.word + + "An " #3 + "A Colorful Morning" + chop.word } EXECUTE { test } @@ -178,11 +182,13 @@ void testChopWord() { vm.render(Collections.emptyList()); - assertTrue(vm.latestContext.stack().get(0).equals("Colorful Morning")); + assertEquals("A Colorful Morning", vm.latestContext.stack().pop()); + assertEquals("Colorful Morning", vm.latestContext.stack().pop()); + assertEquals(0, vm.latestContext.stack().size()); } @Test - void testSortFormatTitle() { + void testAbbrevStyleSortFormatTitle() { TestVM vm = new TestVM(""" STRINGS { s t } INTEGERS { len } @@ -201,13 +207,13 @@ void testSortFormatTitle() { if$ } - FUNCTION {sort.format.title} + FUNCTION { sort.format.title } { 't := - % "A " #2 - % "An " #3 + "A " #2 + "An " #3 "The " #4 t chop.word - % chop.word - % chop.word + chop.word + chop.word sortify #1 global.max$ substring$ } @@ -222,7 +228,7 @@ void testSortFormatTitle() { vm.render(Collections.emptyList()); - assertTrue(vm.latestContext.stack().get(0).equals("Colorful Morning")); + assertEquals("colorful morning", vm.latestContext.stack().pop()); } public static class TestVM extends BstVM { From 501349f432eda761bf223c59d2bbfcc50ee1a71a Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 1 Aug 2022 11:14:51 +0200 Subject: [PATCH 34/39] Fixed tests --- src/main/java/org/jabref/logic/bst/BstFunctions.java | 2 ++ src/main/java/org/jabref/logic/bst/BstVMVisitor.java | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index 2dc66d8b52d..af083de719e 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -530,6 +530,8 @@ private void callIdentifierOrTree(Object f, BstVMVisitor visitor, ParserRuleCont visitor.visit(tree); } else if (f instanceof BstVMVisitor.Identifier identifier) { visitor.resolveIdentifier(identifier.name(), ctx); + } else { + stack.push(f); } } diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java index 1629a4bb329..a815480e506 100644 --- a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -255,6 +255,7 @@ public Integer visitStackitem(BstParser.StackitemContext ctx) { bstVMContext.path().ifPresentOrElse( (path) -> LOGGER.error("{} ({})", e.getMessage(), path), () -> LOGGER.error(e.getMessage())); + throw e; } } return BstVM.TRUE; From 30d49c73819e70fac8b6a1397c2180f9c40bd2d4 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 1 Aug 2022 11:59:37 +0200 Subject: [PATCH 35/39] Removed TestVM --- src/main/java/org/jabref/logic/bst/BstVM.java | 22 ++--- .../jabref/logic/bst/BstFunctionsTest.java | 68 +++++++-------- .../java/org/jabref/logic/bst/BstVMTest.java | 82 +++++-------------- .../jabref/logic/bst/BstVMVisitorTest.java | 36 ++++---- 4 files changed, 81 insertions(+), 127 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java index 0bc2f2c83e9..a2428d683d9 100644 --- a/src/main/java/org/jabref/logic/bst/BstVM.java +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -6,6 +6,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Stack; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; @@ -20,19 +21,6 @@ import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTree; -/* ToDo: - * [x] clean up bst functions and fix javadoc - * [x] migrate grammar - * [x] migrate runner/visitor - * [x] migrate functions - * [x] context / entry needs to be set as a field for the visitor to iterate forwards or backwards through all the entries - * [x] migrate execute / forward / backward / stack / quote / etc. - * [x] remove old VM - * [ ] fix tests - * [ ] create new tests - * [ ] clean up bstFunctions - */ - public class BstVM { protected static final Integer FALSE = 0; @@ -105,6 +93,14 @@ public String render(Collection bibEntries) { return render(bibEntries, null); } + protected Stack getStack() { + if (latestContext != null) { + return latestContext.stack(); + } else { + throw new BstVMException("BstVM must have rendered at least once to provide the latest stack"); + } + } + private static class ThrowingErrorListener extends BaseErrorListener { public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); diff --git a/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java b/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java index c0184f3ec2b..cf242fd78a5 100644 --- a/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java +++ b/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java @@ -22,17 +22,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue; /** - * For additional tests see - * - purify: {@link BstPurifierTest} - * - width: {@link BstWidthCalculatorTest} - * - format.name: {@link BstNameFormatterTest} - * - change.case: {@link BstCaseChangersTest} - * - prefix: {@link BstTextPrefixerTest} + * For additional tests see for + *
    + *
  • purify: {@link BstPurifierTest}
  • + *
  • width: {@link BstWidthCalculatorTest}
  • + *
  • format.name: {@link BstNameFormatterTest}
  • + *
  • change.case: {@link BstCaseChangersTest}
  • + *
  • prefix: {@link BstTextPrefixerTest}
  • + *
*/ class BstFunctionsTest { @Test public void testCompareFunctions() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test.compare } { #5 #5 = % TRUE #1 #2 = % FALSE @@ -65,7 +67,7 @@ public void testCompareFunctions() throws RecognitionException { @Test public void testArithmeticFunctions() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { #1 #1 + % 2 #5 #2 - % 3 @@ -82,7 +84,7 @@ public void testArithmeticFunctions() throws RecognitionException { @Test public void testArithmeticFunctionTypeMismatch() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { #1 "HELLO" + % Should throw exception } @@ -95,7 +97,7 @@ public void testArithmeticFunctionTypeMismatch() throws RecognitionException { @Test public void testStringOperations() throws RecognitionException { // Test for concat (*) and add.period - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { "H" "ello" * % Hello "Johnny" add.period$ % Johnny. @@ -126,7 +128,7 @@ public void testStringOperations() throws RecognitionException { @Test public void testMissing() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { title } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -152,7 +154,7 @@ public void testMissing() throws RecognitionException { @Test public void testNumNames() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { "Johnny Foo { and } Mary Bar" num.names$ "Johnny Foo and Mary Bar" num.names$ @@ -169,7 +171,7 @@ public void testNumNames() throws RecognitionException { @Test public void testSubstring() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { "123456789" #2 #1 substring$ % 2 "123456789" #4 global.max$ substring$ % 456789 @@ -199,7 +201,7 @@ public void testSubstring() throws RecognitionException { @Test public void testEmpty() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { title } { } { } READ STRINGS { s } @@ -226,7 +228,7 @@ public void testEmpty() throws RecognitionException { @Test public void testFormatNameStatic() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { format }{ "Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin" #1 "{vv~}{ll}{, jj}{, f}?" format.name$ } EXECUTE { format } """); @@ -240,7 +242,7 @@ public void testFormatNameStatic() throws RecognitionException { @Test public void testFormatNameInEntries() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { author } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -264,7 +266,7 @@ public void testFormatNameInEntries() throws RecognitionException { @Test public void testChangeCase() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" STRINGS { title } READ FUNCTION { format.title } { @@ -294,7 +296,7 @@ public void testChangeCase() throws RecognitionException { @Test public void testTextLength() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { "hello world" text.length$ % 11 "Hello {W}orld" text.length$ % 11 @@ -323,7 +325,7 @@ public void testTextLength() throws RecognitionException { @Test public void testIntToStr() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { #3 int.to.str$ #9999 int.to.str$ } EXECUTE { test } """); @@ -337,7 +339,7 @@ public void testIntToStr() throws RecognitionException { @Test public void testChrToInt() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { "H" chr.to.int$ } EXECUTE { test } """); @@ -350,7 +352,7 @@ public void testChrToInt() throws RecognitionException { @Test public void testChrToIntIntToChr() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test } { "H" chr.to.int$ int.to.chr$ } EXECUTE {test} """); @@ -363,7 +365,7 @@ public void testChrToIntIntToChr() throws RecognitionException { @Test public void testType() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -388,7 +390,7 @@ public void testType() throws RecognitionException { @Test public void testCallType() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { title } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -415,7 +417,7 @@ public void testCallType() throws RecognitionException { @Test public void testSwap() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { a } { #3 "Hallo" swap$ } EXECUTE { a } """); @@ -430,7 +432,7 @@ public void testSwap() throws RecognitionException { @Test void testAssignFunction() { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" INTEGERS { test.var } FUNCTION { test.func } { #1 'test.var := } EXECUTE { test.func } @@ -438,7 +440,7 @@ void testAssignFunction() { vm.render(Collections.emptyList()); - Map functions = vm.getFunctions(); + Map functions = vm.latestContext.functions(); assertTrue(functions.containsKey("test.func")); assertNotNull(functions.get("test.func")); assertEquals(1, vm.latestContext.integers().get("test.var")); @@ -446,7 +448,7 @@ void testAssignFunction() { @Test void testSimpleIf() { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { path1 } { #1 } FUNCTION { path0 } { #0 } FUNCTION { test } { @@ -465,7 +467,7 @@ void testSimpleIf() { @Test void testSimpleWhile() { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" INTEGERS { i } FUNCTION { test } { #3 'i := @@ -490,7 +492,7 @@ void testSimpleWhile() { @Test public void testNestedControlFunctions() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" STRINGS { t } FUNCTION { not } { { #0 } { #1 } if$ } FUNCTION { n.dashify } { @@ -535,7 +537,7 @@ public void testNestedControlFunctions() throws RecognitionException { @Test public void testLogic() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { not } { { #0 } { #1 } if$ } FUNCTION { and } { 'skip$ { pop$ #0 } if$ } FUNCTION { or } { { pop$ #1 } 'skip$ if$ } @@ -575,7 +577,7 @@ public void testLogic() throws RecognitionException { @Test public void testWidth() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { address author title type } { } { label } STRINGS { longest.label } INTEGERS { number.label longest.label.width } @@ -611,7 +613,7 @@ public void testWidth() throws RecognitionException { vm.render(testEntries); - assertTrue(vm.getIntegers().containsKey("longest.label.width")); + assertTrue(vm.latestContext.integers().containsKey("longest.label.width")); assertEquals("\\begin{thebibliography}{1}", vm.getStack().pop()); } @@ -632,7 +634,7 @@ public void testWidth() throws RecognitionException { @Test public void testDuplicateEmptyPopSwapIf() throws RecognitionException { // ToDo: Make better tests for this - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { emphasize } { duplicate$ empty$ { pop$ "" } diff --git a/src/test/java/org/jabref/logic/bst/BstVMTest.java b/src/test/java/org/jabref/logic/bst/BstVMTest.java index 85b340fb4ba..4a66a8d5836 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMTest.java @@ -4,8 +4,6 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Stack; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; @@ -20,17 +18,6 @@ public class BstVMTest { public static BibEntry defaultTestEntry() { - /* - @inproceedings{canh05, - author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}, - title = {Effective work practices for floss development: A model and propositions}, - booktitle = {Hawaii International Conference On System Sciences (HICSS)}, - year = {2005}, - owner = {oezbek}, - timestamp = {2006.05.29}, - url = {http://james.howison.name/publications.html}} - */ - return new BibEntry(StandardEntryType.InProceedings) .withCitationKey("canh05") .withField(StandardField.AUTHOR, "Crowston, K. and Annabi, H. and Howison, J. and Masango, C.") @@ -44,7 +31,7 @@ public static BibEntry defaultTestEntry() { @Test public void testAbbrv() throws RecognitionException, IOException { - TestVM vm = new TestVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); + BstVM vm = new BstVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); List testEntries = List.of(defaultTestEntry()); String expected = "\\begin{thebibliography}{1}\\bibitem{canh05}K.~Crowston, H.~Annabi, J.~Howison, and C.~Masango.\\newblock Effective work practices for floss development: A model and propositions.\\newblock In {\\em Hawaii International Conference On System Sciences (HICSS)}, 2005.\\end{thebibliography}"; @@ -57,7 +44,7 @@ public void testAbbrv() throws RecognitionException, IOException { @Test public void testSimple() throws RecognitionException { - TestVM vm = new TestVM(""" + BstVM vm = new BstVM(""" ENTRY { address author title type } { } { label } INTEGERS { output.state before.all mid.sentence after.sentence after.block } FUNCTION { init.state.consts }{ @@ -73,16 +60,16 @@ public void testSimple() throws RecognitionException { vm.render(testEntries); - assertEquals(2, vm.getStrings().size()); - assertEquals(7, vm.getIntegers().size()); - assertEquals(1, vm.getEntries().size()); - assertEquals(5, vm.getEntries().get(0).fields.size()); - assertEquals(38, vm.getFunctions().size()); + assertEquals(2, vm.latestContext.strings().size()); + assertEquals(7, vm.latestContext.integers().size()); + assertEquals(1, vm.latestContext.entries().size()); + assertEquals(5, vm.latestContext.entries().get(0).fields.size()); + assertEquals(38, vm.latestContext.functions().size()); } @Test public void testLabel() throws RecognitionException { - TestVM vm = new TestVM(""" + BstVM vm = new BstVM(""" ENTRY { title } {} { label } FUNCTION { test } { label #0 = @@ -97,30 +84,30 @@ public void testLabel() throws RecognitionException { assertEquals( "Effective work practices for floss development: A model and propositions", - vm.getStack().pop()); + vm.latestContext.stack().pop()); } @Test public void testQuote() throws RecognitionException { - TestVM vm = new TestVM("FUNCTION { a }{ quote$ quote$ * } EXECUTE { a }"); + BstVM vm = new BstVM("FUNCTION { a }{ quote$ quote$ * } EXECUTE { a }"); vm.render(Collections.emptyList()); - assertEquals("\"\"", vm.getStack().pop()); + assertEquals("\"\"", vm.latestContext.stack().pop()); } @Test public void testBuildIn() throws RecognitionException { - TestVM vm = new TestVM("EXECUTE { global.max$ }"); + BstVM vm = new BstVM("EXECUTE { global.max$ }"); vm.render(Collections.emptyList()); - assertEquals(Integer.MAX_VALUE, vm.getStack().pop()); - assertTrue(vm.getStack().empty()); + assertEquals(Integer.MAX_VALUE, vm.latestContext.stack().pop()); + assertTrue(vm.latestContext.stack().empty()); } @Test public void testVariables() throws RecognitionException { - TestVM vm = new TestVM(""" + BstVM vm = new BstVM(""" STRINGS { t } FUNCTION { not } { { #0 } { #1 } if$ @@ -134,12 +121,12 @@ public void testVariables() throws RecognitionException { vm.render(Collections.emptyList()); - assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.latestContext.stack().pop()); } @Test public void testHypthenatedName() throws RecognitionException, IOException { - TestVM vm = new TestVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); + BstVM vm = new BstVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); List testEntries = List.of( new BibEntry(StandardEntryType.Article) .withCitationKey("canh05") @@ -153,7 +140,7 @@ public void testHypthenatedName() throws RecognitionException, IOException { @Test void testAbbrevStyleChopWord() { - TestVM vm = new TestVM(""" + BstVM vm = new BstVM(""" STRINGS { s } INTEGERS { len } @@ -189,7 +176,7 @@ void testAbbrevStyleChopWord() { @Test void testAbbrevStyleSortFormatTitle() { - TestVM vm = new TestVM(""" + BstVM vm = new BstVM(""" STRINGS { s t } INTEGERS { len } FUNCTION { sortify } { @@ -230,35 +217,4 @@ void testAbbrevStyleSortFormatTitle() { assertEquals("colorful morning", vm.latestContext.stack().pop()); } - - public static class TestVM extends BstVM { - - private TestVM(Path path) throws RecognitionException, IOException { - super(path); - } - - protected TestVM(String s) throws RecognitionException { - super(s); - } - - protected Map getStrings() { - return latestContext.strings(); - } - - protected Map getIntegers() { - return latestContext.integers(); - } - - protected List getEntries() { - return latestContext.entries(); - } - - protected Map getFunctions() { - return latestContext.functions(); - } - - protected Stack getStack() { - return latestContext.stack(); - } - } } diff --git a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java index 917ea27f6d0..810b7f4d3f6 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java @@ -20,11 +20,11 @@ class BstVMVisitorTest { @Test public void testVisitStringsCommand() { - BstVMTest.TestVM vm = new BstVMTest.TestVM("STRINGS { test.string1 test.string2 test.string3 }"); + BstVM vm = new BstVM("STRINGS { test.string1 test.string2 test.string3 }"); vm.render(Collections.emptyList()); - Map strList = vm.getStrings(); + Map strList = vm.latestContext.strings(); assertTrue(strList.containsKey("test.string1")); assertNull(strList.get("test.string1")); assertTrue(strList.containsKey("test.string2")); @@ -35,11 +35,11 @@ public void testVisitStringsCommand() { @Test public void testVisitIntegersCommand() { - BstVMTest.TestVM vm = new BstVMTest.TestVM("INTEGERS { variable.a variable.b variable.c }"); + BstVM vm = new BstVM("INTEGERS { variable.a variable.b variable.c }"); vm.render(Collections.emptyList()); - Map integersList = vm.getIntegers(); + Map integersList = vm.latestContext.integers(); assertTrue(integersList.containsKey("variable.a")); assertEquals(0, integersList.get("variable.a")); assertTrue(integersList.containsKey("variable.b")); @@ -50,28 +50,28 @@ public void testVisitIntegersCommand() { @Test void testVisitFunctionCommand() { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" FUNCTION { test.func } { #1 'test.var := } EXECUTE { test.func } """); vm.render(Collections.emptyList()); - Map functions = vm.getFunctions(); + Map functions = vm.latestContext.functions(); assertTrue(functions.containsKey("test.func")); assertNotNull(functions.get("test.func")); } @Test void testVisitMacroCommand() { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" MACRO { jan } { "January" } EXECUTE { jan } """); vm.render(Collections.emptyList()); - Map functions = vm.getFunctions(); + Map functions = vm.latestContext.functions(); assertTrue(functions.containsKey("jan")); assertNotNull(functions.get("jan")); assertEquals("January", vm.latestContext.stack().pop()); @@ -80,12 +80,12 @@ void testVisitMacroCommand() { @Test void testVisitEntryCommand() { - BstVMTest.TestVM vm = new BstVMTest.TestVM("ENTRY { address author title type } { variable } { label }"); + BstVM vm = new BstVM("ENTRY { address author title type } { variable } { label }"); List testEntries = List.of(BstVMTest.defaultTestEntry()); vm.render(testEntries); - BstEntry bstEntry = vm.getEntries().get(0); + BstEntry bstEntry = vm.latestContext.entries().get(0); assertTrue(bstEntry.fields.containsKey("address")); assertTrue(bstEntry.fields.containsKey("author")); assertTrue(bstEntry.fields.containsKey("title")); @@ -97,7 +97,7 @@ void testVisitEntryCommand() { @Test void testVisitReadCommand() { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { author title booktitle year owner timestamp url } { } { } READ """); @@ -105,7 +105,7 @@ void testVisitReadCommand() { vm.render(testEntries); - Map fields = vm.getEntries().get(0).fields; + Map fields = vm.latestContext.entries().get(0).fields; assertEquals("Crowston, K. and Annabi, H. and Howison, J. and Masango, C.", fields.get("author")); assertEquals("Effective work practices for floss development: A model and propositions", fields.get("title")); assertEquals("Hawaii International Conference On System Sciences (HICSS)", fields.get("booktitle")); @@ -117,7 +117,7 @@ void testVisitReadCommand() { @Test public void testVisitExecuteCommand() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" INTEGERS { variable.a } FUNCTION { init.state.consts } { #5 'variable.a := } EXECUTE { init.state.consts } @@ -125,14 +125,14 @@ public void testVisitExecuteCommand() throws RecognitionException { vm.render(Collections.emptyList()); - assertEquals(5, vm.getIntegers().get("variable.a")); + assertEquals(5, vm.latestContext.integers().get("variable.a")); } // ToDo: testVisitReverseCommand @Test public void testVisitIterateCommand() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { } { } { } FUNCTION { test } { cite$ } READ @@ -152,7 +152,7 @@ public void testVisitIterateCommand() throws RecognitionException { @Test public void testVisitSortCommand() throws RecognitionException { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { } { } { } FUNCTION { presort } { cite$ 'sort.key$ := } ITERATE { presort } @@ -166,7 +166,7 @@ public void testVisitSortCommand() throws RecognitionException { vm.render(testEntries); - List sortedEntries = vm.getEntries(); + List sortedEntries = vm.latestContext.entries(); assertEquals(Optional.of("a"), sortedEntries.get(0).entry.getCitationKey()); assertEquals(Optional.of("b"), sortedEntries.get(1).entry.getCitationKey()); assertEquals(Optional.of("c"), sortedEntries.get(2).entry.getCitationKey()); @@ -175,7 +175,7 @@ public void testVisitSortCommand() throws RecognitionException { @Test void testVisitIdentifier() { - BstVMTest.TestVM vm = new BstVMTest.TestVM(""" + BstVM vm = new BstVM(""" ENTRY { } { local.variable } { local.label } READ STRINGS { label } From d210e6ac91da803288e29756b75b20c80482c91c Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 1 Aug 2022 12:08:26 +0200 Subject: [PATCH 36/39] Added reverse test --- .../jabref/logic/bst/BstVMVisitorTest.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java index 810b7f4d3f6..9cd516a9121 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java @@ -128,8 +128,6 @@ public void testVisitExecuteCommand() throws RecognitionException { assertEquals(5, vm.latestContext.integers().get("variable.a")); } - // ToDo: testVisitReverseCommand - @Test public void testVisitIterateCommand() throws RecognitionException { BstVM vm = new BstVM(""" @@ -150,6 +148,26 @@ public void testVisitIterateCommand() throws RecognitionException { assertEquals("canh05", vm.getStack().pop()); } + @Test + public void testVisitReverseCommand() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { } { } { } + FUNCTION { test } { cite$ } + READ + REVERSE { test } + """); + List testEntries = List.of( + BstVMTest.defaultTestEntry(), + new BibEntry(StandardEntryType.Article) + .withCitationKey("test")); + + vm.render(testEntries); + + assertEquals(2, vm.getStack().size()); + assertEquals("canh05", vm.getStack().pop()); + assertEquals("test", vm.getStack().pop()); + } + @Test public void testVisitSortCommand() throws RecognitionException { BstVM vm = new BstVM(""" From 39fe3f21fd8078c4cfd52226ad2ce18ab765e094 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 1 Aug 2022 13:15:13 +0200 Subject: [PATCH 37/39] Added visitStackitem test --- .../jabref/logic/bst/BstVMVisitorTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java index 9cd516a9121..366ba0a9d55 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java @@ -9,6 +9,7 @@ import org.jabref.model.entry.types.StandardEntryType; import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.tree.ParseTree; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -219,5 +220,30 @@ void testVisitIdentifier() { assertEquals(0, vm.getStack().size()); } + @Test + void testVisitStackitem() { + BstVM vm = new BstVM(""" + STRINGS { t } + FUNCTION { test2 } { #3 } + FUNCTION { test } { + "HELLO" + #1 + 't + { #2 } + test2 + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(3, vm.getStack().pop()); + assertTrue(vm.getStack().pop() instanceof ParseTree); + assertEquals(new BstVMVisitor.Identifier("t"), vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals("HELLO", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + // stackitem } From 881a4d9d937d74c77661acffa64eb1f91774c126 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 1 Aug 2022 14:38:49 +0200 Subject: [PATCH 38/39] Added bbl tests --- .../jabref/logic/bst/BstFunctionsTest.java | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java b/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java index cf242fd78a5..1b63e26cbb3 100644 --- a/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java +++ b/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java @@ -9,6 +9,7 @@ import org.jabref.logic.bst.util.BstPurifierTest; import org.jabref.logic.bst.util.BstTextPrefixerTest; import org.jabref.logic.bst.util.BstWidthCalculatorTest; +import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; @@ -617,23 +618,8 @@ public void testWidth() throws RecognitionException { assertEquals("\\begin{thebibliography}{1}", vm.getStack().pop()); } - /* - ToDo: - - duplicate - - newline - - preamble - - quote - - stack - - top - - warning - - write - - skip - - pop - */ - @Test public void testDuplicateEmptyPopSwapIf() throws RecognitionException { - // ToDo: Make better tests for this BstVM vm = new BstVM(""" FUNCTION { emphasize } { duplicate$ empty$ @@ -654,4 +640,27 @@ public void testDuplicateEmptyPopSwapIf() throws RecognitionException { assertEquals("", vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } + + @Test + public void testPreambleWriteNewlineQuote() { + BstVM vm = new BstVM(""" + FUNCTION { test } { + preamble$ + write$ + newline$ + "hello" + write$ + quote$ "quoted" * quote$ * + write$ + } + EXECUTE { test } + """); + + BibDatabase testDatabase = new BibDatabase(); + testDatabase.setPreamble("A Preamble"); + + String result = vm.render(Collections.emptyList(), testDatabase); + + assertEquals("A Preamble\nhello\"quoted\"", result); + } } From 8bca7178db80eb2ad967cf63dc5c95d88604a765 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 1 Aug 2022 15:03:57 +0200 Subject: [PATCH 39/39] clean up --- .../org/jabref/logic/bst/BstFunctions.java | 47 +++++++------------ .../jabref/logic/bst/util/BstCaseChanger.java | 26 +++++----- .../logic/bst/util/BstCaseChangersTest.java | 10 ++-- 3 files changed, 37 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java index af083de719e..6689f83b2a5 100644 --- a/src/main/java/org/jabref/logic/bst/BstFunctions.java +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -1,7 +1,6 @@ package org.jabref.logic.bst; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Stack; @@ -24,7 +23,7 @@ public class BstFunctions { private static final Logger LOGGER = LoggerFactory.getLogger(BstFunctions.class); - private static final Pattern ADD_PERIOD_PATTERN = Pattern.compile("([^\\.\\?\\!\\}\\s])(\\}|\\s)*$"); + private static final Pattern ADD_PERIOD_PATTERN = Pattern.compile("([^.?!}\\s])(}|\\s)*$"); private final Map strings; private final Map integers; @@ -240,46 +239,37 @@ public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { @Override public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntry) { if (stack.size() < 2) { - throw new BstVMException("Invalid call to operation := (line %d)".formatted(ctx.start.getLine())); + throw new BstVMException("Not enough operands on stack for operation := (line %d)".formatted(ctx.start.getLine())); } Object o1 = stack.pop(); Object o2 = stack.pop(); - doAssign(bstEntry, o1, o2, ctx); - } - private boolean doAssign(BstEntry context, Object o1, Object o2, ParserRuleContext ctx) { - if (!(o1 instanceof BstVMVisitor.Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { + if (!(o1 instanceof BstVMVisitor.Identifier identifier)) { throw new BstVMException("Invalid parameters (line %d)".formatted(ctx.start.getLine())); } + String name = identifier.name(); - String name = ((BstVMVisitor.Identifier) o1).name(); - - // ToDo: use switch statement pattern matching instead (Java 19) - if (o2 instanceof String) { - String value = (String) o2; - if ((context != null) && context.localStrings.containsKey(name)) { - context.localStrings.put(name, value); - return true; + if (o2 instanceof String value) { + if ((bstEntry != null) && bstEntry.localStrings.containsKey(name)) { + bstEntry.localStrings.put(name, value); + return; } if (strings.containsKey(name)) { strings.put(name, value); - return true; } - } else { - Integer value = (Integer) o2; - if ((context != null) && context.localIntegers.containsKey(name)) { - context.localIntegers.put(name, value); - return true; + } else if (o2 instanceof Integer value) { + if ((bstEntry != null) && bstEntry.localIntegers.containsKey(name)) { + bstEntry.localIntegers.put(name, value); + return; } if (integers.containsKey(name)) { integers.put(name, value); - return true; } + } else { + throw new BstVMException("Invalid parameters (line %d)".formatted(ctx.start.getLine())); } - - return false; } } @@ -365,19 +355,16 @@ private void bstChangeCase(BstVMVisitor visitor, ParserRuleContext ctx) { } Object o1 = stack.pop(); - if (!((o1 instanceof String) && (((String) o1).length() == 1))) { + if (!((o1 instanceof String format) && (format.length() == 1))) { throw new BstVMException("A format string of length 1 is needed for change.case$ (line %d)".formatted(ctx.start.getLine())); } Object o2 = stack.pop(); - if (!(o2 instanceof String)) { + if (!(o2 instanceof String toChange)) { throw new BstVMException("A string is needed as second parameter for change.case$ (line %d)".formatted(ctx.start.getLine())); } - char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); - String s = (String) o2; - - stack.push(BstCaseChanger.changeCase(s, BstCaseChanger.FORMAT_MODE.getFormatModeForBSTFormat(format))); + stack.push(BstCaseChanger.changeCase(toChange, BstCaseChanger.FormatMode.of(format))); } /** diff --git a/src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java b/src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java index 67fdeb4acc7..dd3e7c7b05c 100644 --- a/src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java +++ b/src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java @@ -16,7 +16,7 @@ public final class BstCaseChanger { // global variable to store the current brace level private int braceLevel; - public enum FORMAT_MODE { + public enum FormatMode { // First character and character after a ":" as upper case - everything else in lower case. Obey {}. TITLE_LOWERS('t'), @@ -40,7 +40,7 @@ public enum FORMAT_MODE { private final char asChar; - FORMAT_MODE(char asChar) { + FormatMode(char asChar) { this.asChar = asChar; } @@ -53,14 +53,18 @@ public char asChar() { * * @throws IllegalArgumentException if char is not 't', 'l', 'u' */ - public static FORMAT_MODE getFormatModeForBSTFormat(final char bstFormat) { - for (FORMAT_MODE mode : FORMAT_MODE.values()) { + public static FormatMode of(final char bstFormat) { + for (FormatMode mode : FormatMode.values()) { if (mode.asChar == bstFormat) { return mode; } } throw new IllegalArgumentException(); } + + public static FormatMode of(final String bstFormat) { + return of(bstFormat.toLowerCase(Locale.ROOT).charAt(0)); + } } private BstCaseChanger() { @@ -72,11 +76,11 @@ private BstCaseChanger() { * @param s the string to handle * @param format the format */ - public static String changeCase(String s, FORMAT_MODE format) { + public static String changeCase(String s, FormatMode format) { return (new BstCaseChanger()).doChangeCase(s, format); } - private String doChangeCase(String s, FORMAT_MODE format) { + private String doChangeCase(String s, FormatMode format) { char[] c = s.toCharArray(); StringBuilder sb = new StringBuilder(); @@ -93,7 +97,7 @@ private String doChangeCase(String s, FORMAT_MODE format) { i++; continue; } - if ((format == FORMAT_MODE.TITLE_LOWERS) && ((i == 0) || (prevColon && Character.isWhitespace(c[i - 1])))) { + if ((format == FormatMode.TITLE_LOWERS) && ((i == 0) || (prevColon && Character.isWhitespace(c[i - 1])))) { sb.append('{'); i++; prevColon = false; @@ -138,7 +142,7 @@ private String doChangeCase(String s, FORMAT_MODE format) { * * @param start the current position. It points to the opening brace */ - private int convertSpecialChar(StringBuilder sb, char[] c, int start, FORMAT_MODE format) { + private int convertSpecialChar(StringBuilder sb, char[] c, int start, FormatMode format) { int i = start; sb.append(c[i]); @@ -173,7 +177,7 @@ private int convertSpecialChar(StringBuilder sb, char[] c, int start, FORMAT_MOD * * @return the new position */ - private int convertAccented(char[] c, int start, String s, StringBuilder sb, FORMAT_MODE format) { + private int convertAccented(char[] c, int start, String s, StringBuilder sb, FormatMode format) { int pos = start; pos += s.length(); @@ -206,7 +210,7 @@ private int convertAccented(char[] c, int start, String s, StringBuilder sb, FOR return pos; } - private int convertNonControl(char[] c, int start, StringBuilder sb, FORMAT_MODE format) { + private int convertNonControl(char[] c, int start, StringBuilder sb, FormatMode format) { int pos = start; switch (format) { case TITLE_LOWERS, ALL_LOWERS -> { @@ -223,7 +227,7 @@ private int convertNonControl(char[] c, int start, StringBuilder sb, FORMAT_MODE return pos; } - private int convertCharIfBraceLevelIsZero(char[] c, int start, StringBuilder sb, FORMAT_MODE format) { + private int convertCharIfBraceLevelIsZero(char[] c, int start, StringBuilder sb, FormatMode format) { int i = start; switch (format) { case TITLE_LOWERS -> { diff --git a/src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java b/src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java index 10207b45b10..3bf615093e4 100644 --- a/src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java @@ -2,7 +2,7 @@ import java.util.stream.Stream; -import org.jabref.logic.bst.util.BstCaseChanger.FORMAT_MODE; +import org.jabref.logic.bst.util.BstCaseChanger.FormatMode; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -17,7 +17,7 @@ public class BstCaseChangersTest { @ParameterizedTest @MethodSource("provideStringsForTitleLowers") public void testChangeCaseTitleLowers(String expected, String toBeFormatted) { - assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.TITLE_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FormatMode.TITLE_LOWERS)); } private static Stream provideStringsForTitleLowers() { @@ -59,7 +59,7 @@ private static Stream provideStringsForTitleLowers() { @ParameterizedTest @MethodSource("provideStringsForAllLowers") public void testChangeCaseAllLowers(String expected, String toBeFormatted) { - assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FormatMode.ALL_LOWERS)); } private static Stream provideStringsForAllLowers() { @@ -90,7 +90,7 @@ private static Stream provideStringsForAllLowers() { @ParameterizedTest @MethodSource("provideStringsForAllUppers") public void testChangeCaseAllUppers(String expected, String toBeFormatted) { - assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_UPPERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FormatMode.ALL_UPPERS)); } private static Stream provideStringsForAllUppers() { @@ -121,7 +121,7 @@ private static Stream provideStringsForAllUppers() { @ParameterizedTest @MethodSource("provideTitleCaseAllLowers") public void testTitleCaseAllLowers(String expected, String toBeFormatted) { - assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FormatMode.ALL_LOWERS)); } private static Stream provideTitleCaseAllLowers() {