package org.jmlspecs.openjmltest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.LinkedList;
import java.util.List;
import org.jmlspecs.openjml.IJmlVisitor;
import org.jmlspecs.openjml.JmlTreeScanner;
import com.sun.tools.javac.comp.JmlAttr;
import com.sun.tools.javac.comp.JmlEnter;
import com.sun.tools.javac.parser.JmlFactory;
import com.sun.tools.javac.parser.JmlParser;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.parser.ScannerFactory;
import com.sun.tools.javac.parser.Tokens.TokenKind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.Log;
/** This class is the base class for test suites that just are exercising the parser,
* without doing any further typechecking. FOr this purpose the parser can be
* called standalone, and the parse tree inspected.
* @author David Cok
*
*/
abstract public class ParseBase extends JmlTestCase {
protected static String z = java.io.File.pathSeparator;
protected static String testspecpath = "$A"+z+"$B";
protected ParserFactory fac;
protected ScannerFactory sfac;
protected JmlParser parser;
/** Set this to true in tests which start out the scanner in jml mode
* (avoiding the need to begin the test string with a JML comment annotation)
*/
protected boolean jml;
@Override
public void setUp() throws Exception {
super.setUp();
main.addOptions("compilePolicy","check"); // Don't do code generation
main.addOptions("-specspath", testspecpath);
//main.register(context);
JmlAttr.instance(context); // Needed to avoid circular dependencies in tool constructors that only occur in testing
JmlEnter.instance(context); // Needed to avoid circular dependencies in tool constructors that only occur in testing
sfac = ScannerFactory.instance(context);
fac = ParserFactory.instance(context);
print = false;
jml = false;
}
@Override
public void tearDown() throws Exception {
super.tearDown();
fac = null;
sfac = null;
}
/** Compiles the given string as the content of a compilation unit,
* comparing the parse tree found to the expected node types and character
* positions found in the second argument.
* @param s the content of a compilation unit
* @param list the expected parse tree formed by parsing the first argument
* as a compilation unit, each node is represented by a node type (instance
* of Class) and character position (an int).
*/
public void checkCompilationUnit(String s, Object ... list) {
List<JCTree> out = parseCompilationUnit(s);
checkParseTree(out,list);
}
// TODO - put in a few harness tests
// TODO - test error messages
public void checkCompilationUnitFailure(String failureMessage, String s, Object ... list) {
boolean failed = false;
try {
checkCompilationUnit(s,list);
} catch (AssertionError a) {
failed = true;
assertEquals("Failure report wrong",failureMessage,a.getMessage());
}
if (!failed) fail("Test Harness failed to report an error");
}
/** Parses the content of a compilation unit, producing a list of nodes of
* the parse tree
* @param s the string to parse
* @return the list of nodes in the resulting parse tree
*/
public List<JCTree> parseCompilationUnit(String s) {
Log.instance(context).useSource(new TestJavaFileObject(s));
parser = ((JmlFactory)fac).newParser(s,false,true,true,jml);
JCTree e = parser.parseCompilationUnit();
return ParseTreeScanner.walk(e);
}
/** Compares a list of nodes to the expected values given in the
* second argument; the second argument is expected to consist of the
* class of a node (e.g. JCIdent.class) and the preferred character
* position of that node, for each element of the actual list. The two
* lists are compared (both for node type and position) and JUnit failures
* are raised for the first difference found.
* @param actual a list of nodes as produced by ParseTreeScanner.walk
* @param expected a list of expected data - class types and character positions
* for each node in turn
*/
public void checkParseTree(List<JCTree> actual, Object[] expected) {
try {
int i = 0;
int k = 0;
if (print) {
for (JCTree t: actual) {
System.out.println(t.getClass() + " " + t.getStartPosition() + " " + t.getPreferredPosition() + " " + parser.getEndPos(t));
}
}
if (print) printDiagnostics();
Object p1, p2, p3;
for (JCTree t: actual) {
if (i>=expected.length) break;
assertEquals("Class not matched at token " + k, expected[i++], t.getClass());
p1 = expected[i++];
p2 = (i < expected.length && expected[i] instanceof Integer) ? expected[i++] : null;
p3 = (i < expected.length && expected[i] instanceof Integer) ? expected[i++] : null;
if (p3 != null) {
assertEquals("Start position for token " + k, p1, t.getStartPosition());
assertEquals("Preferred position for token " + k, p2, t.getPreferredPosition());
assertEquals("End position for token " + k, p3, parser.getEndPos(t));
} else if (p2 != null) {
assertEquals("Start position for token " + k, p1, t.getStartPosition());
assertEquals("End position for token " + k, p2, parser.getEndPos(t));
} else {
assertEquals("Preferred position for token " + k, p1, t.getPreferredPosition());
}
++k;
}
if ( i != expected.length) fail("Incorrect number of nodes listed");
if (parser.getScanner().token().kind != TokenKind.EOF) fail("Not at end of input");
} catch (AssertionError e) {
if (!print) printTree(actual);
if (!print) printDiagnostics();
throw e;
}
}
/** Prints out the nodes of the tree */
public void printTree(List<JCTree> list) {
System.out.println("NODES FOR " + name.getMethodName());
for (JCTree t: list) {
System.out.println(t.getClass() + " " + t.getStartPosition() + " " + t.getPreferredPosition() + " " + parser.getEndPos(t));
}
}
/** A tree visitor class that walks the tree (depth-first),
* creating a list of the nodes it encounters.
*/
static public class ParseTreeScanner extends JmlTreeScanner implements IJmlVisitor {
/** The list of nodes */
private List<JCTree> list = new LinkedList<JCTree>();;
/** Constructs the visitor, but otherwise does nothing. */
public ParseTreeScanner() {
}
/** A convenience method to walk the given tree and return the list of
* its nodes.
* @param tree the tree to be walked
* @return the list of nodes in depth-first traversal order
*/
static public List<JCTree> walk(JCTree tree) {
ParseTreeScanner t = new ParseTreeScanner();
t.scan(tree);
return t.result();
}
/** Returns a reference to the list accumulated so far.
* @return the accumulator list
*/
public List<JCTree> result() { return list; }
/** Adds a node to the internal accumulator and then calls the
* super class method.
*/
@Override
public void scan(JCTree t) {
if (t == null) return;
list.add(t);
super.scan(t);
}
}
/** Just to avoid Junit framework complaints about no tests */
public void test() {}
}