Merge pull request #215 from sharwell/error-testing

Error testing
This commit is contained in:
Terence Parr 2013-04-04 11:14:09 -07:00
commit 31bf3ceef0
9 changed files with 93 additions and 87 deletions

View File

@ -423,10 +423,11 @@ public class Tool {
} }
} }
/** Important enough to avoid multiple defs that we do very early, /**
* right after AST construction. Turn redef'd rule's AST RULE node dead * Important enough to avoid multiple definitions that we do very early,
* field to true. Also check for undefined rules in parser/lexer to * right after AST construction. Also check for undefined rules in
* avoid exceptions later. Return true if we find an undefined rule. * parser/lexer to avoid exceptions later. Return true if we find multiple
* definitions of the same rule or a reference to an undefined rule.
*/ */
public boolean checkForRuleIssues(final Grammar g) { public boolean checkForRuleIssues(final Grammar g) {
// check for redefined rules // check for redefined rules
@ -436,6 +437,7 @@ public class Tool {
rules.addAll(mode.getAllChildrenWithType(ANTLRParser.RULE)); rules.addAll(mode.getAllChildrenWithType(ANTLRParser.RULE));
} }
boolean redefinition = false;
final Map<String, RuleAST> ruleToAST = new HashMap<String, RuleAST>(); final Map<String, RuleAST> ruleToAST = new HashMap<String, RuleAST>();
for (GrammarAST r : rules) { for (GrammarAST r : rules) {
RuleAST ruleAST = (RuleAST)r; RuleAST ruleAST = (RuleAST)r;
@ -449,7 +451,7 @@ public class Tool {
ID.getToken(), ID.getToken(),
ruleName, ruleName,
prevChild.getToken().getLine()); prevChild.getToken().getLine());
ruleAST.dead = true; redefinition = true;
continue; continue;
} }
ruleToAST.put(ruleName, ruleAST); ruleToAST.put(ruleName, ruleAST);
@ -478,10 +480,11 @@ public class Tool {
} }
} }
} }
UndefChecker chk = new UndefChecker(); UndefChecker chk = new UndefChecker();
chk.visitGrammar(g.ast); chk.visitGrammar(g.ast);
return chk.undefined; // no problem return redefinition || chk.undefined;
} }
public List<GrammarRootAST> sortGrammarByTokenVocab(List<String> fileNames) { public List<GrammarRootAST> sortGrammarByTokenVocab(List<String> fileNames) {
@ -527,7 +530,7 @@ public class Tool {
/** Manually get option node from tree; return null if no defined. */ /** Manually get option node from tree; return null if no defined. */
public static GrammarAST findOptionValueAST(GrammarRootAST root, String option) { public static GrammarAST findOptionValueAST(GrammarRootAST root, String option) {
GrammarAST options = (GrammarAST)root.getFirstChildWithType(ANTLRParser.OPTIONS); GrammarAST options = (GrammarAST)root.getFirstChildWithType(ANTLRParser.OPTIONS);
if ( options!=null ) { if ( options!=null && options.getChildCount() > 0 ) {
for (Object o : options.getChildren()) { for (Object o : options.getChildren()) {
GrammarAST c = (GrammarAST)o; GrammarAST c = (GrammarAST)o;
if ( c.getType() == ANTLRParser.ASSIGN && if ( c.getType() == ANTLRParser.ASSIGN &&

View File

@ -32,12 +32,18 @@ package org.antlr.v4.codegen.model;
import org.antlr.v4.codegen.OutputModelFactory; import org.antlr.v4.codegen.OutputModelFactory;
import org.antlr.v4.tool.Alternative; import org.antlr.v4.tool.Alternative;
/** The code associated with an outermost alternative overrule. /** The code associated with the outermost alternative of a rule.
* Sometimes we might want to treat them differently in the * Sometimes we might want to treat them differently in the
* code generation. * code generation.
*/ */
public class CodeBlockForOuterMostAlt extends CodeBlockForAlt { public class CodeBlockForOuterMostAlt extends CodeBlockForAlt {
/**
* The label for the alternative; or null if the alternative is not labeled.
*/
public String altLabel; public String altLabel;
/**
* The alternative.
*/
public Alternative alt; public Alternative alt;
public CodeBlockForOuterMostAlt(OutputModelFactory factory, Alternative alt) { public CodeBlockForOuterMostAlt(OutputModelFactory factory, Alternative alt) {

View File

@ -509,8 +509,7 @@ rule
@after { @after {
exitRule($start); exitRule($start);
} }
: {!((RuleAST)$start).dead}? : ^( RULE RULE_REF {currentRuleName=$RULE_REF.text; currentRuleAST=$RULE;}
^( RULE RULE_REF {currentRuleName=$RULE_REF.text; currentRuleAST=$RULE;}
DOC_COMMENT? (^(RULEMODIFIERS (m=ruleModifier{mods.add($m.start);})+))? DOC_COMMENT? (^(RULEMODIFIERS (m=ruleModifier{mods.add($m.start);})+))?
ARG_ACTION? ARG_ACTION?
ret=ruleReturns? ret=ruleReturns?
@ -527,20 +526,6 @@ rule
ruleBlock exceptionGroup ruleBlock exceptionGroup
{finishRule((RuleAST)$RULE, $RULE_REF, $ruleBlock.start); currentRuleName=null; currentRuleAST=null;} {finishRule((RuleAST)$RULE, $RULE_REF, $ruleBlock.start); currentRuleName=null; currentRuleAST=null;}
) )
| // ugly repeated alt w/o actions but needed to force ANTLR to use
// sem pred to avoid actions when rule dead
{((RuleAST)$start).dead}?
^( RULE RULE_REF
DOC_COMMENT? (^(RULEMODIFIERS (m=ruleModifier)+))?
ARG_ACTION?
ret=ruleReturns?
thr=throwsSpec?
loc=locals?
( opts=optionsSpec
| a=ruleAction
)*
ruleBlock exceptionGroup
)
; ;
exceptionGroup exceptionGroup

View File

@ -69,7 +69,7 @@ public enum ErrorType {
// Grammar errors // Grammar errors
SYNTAX_ERROR(50, "syntax error: <arg>", ErrorSeverity.ERROR), SYNTAX_ERROR(50, "syntax error: <arg>", ErrorSeverity.ERROR),
RULE_REDEFINITION(51, "rule '<arg>' redefinition (ignoring); previous at line <arg2>", ErrorSeverity.ERROR), RULE_REDEFINITION(51, "rule '<arg>' redefinition; previous at line <arg2>", ErrorSeverity.ERROR),
LEXER_RULES_NOT_ALLOWED(52, "lexer rule '<arg>' not allowed in parser", ErrorSeverity.ERROR), LEXER_RULES_NOT_ALLOWED(52, "lexer rule '<arg>' not allowed in parser", ErrorSeverity.ERROR),
PARSER_RULES_NOT_ALLOWED(53, "parser rule '<arg>' not allowed in lexer", ErrorSeverity.ERROR), PARSER_RULES_NOT_ALLOWED(53, "parser rule '<arg>' not allowed in lexer", ErrorSeverity.ERROR),
REPEATED_PREQUEL(54, "repeated grammar prequel spec (option, token, or import); please merge", ErrorSeverity.ERROR), REPEATED_PREQUEL(54, "repeated grammar prequel spec (option, token, or import); please merge", ErrorSeverity.ERROR),

View File

@ -36,9 +36,6 @@ import org.antlr.v4.parse.ANTLRParser;
import org.antlr.v4.tool.Grammar; import org.antlr.v4.tool.Grammar;
public class RuleAST extends GrammarASTWithOptions { public class RuleAST extends GrammarASTWithOptions {
/** Kill redef of rules */
public boolean dead;
public RuleAST(RuleAST node) { public RuleAST(RuleAST node) {
super(node); super(node);
} }

View File

@ -405,15 +405,11 @@ public abstract class BaseTest {
*/ */
} }
/** Return true if all is ok, no errors */ /** Return true if all is ok, no errors */
protected boolean antlr(String fileName, String grammarFileName, String grammarStr, boolean defaultListener, String... extraOptions) { protected ErrorQueue antlr(String fileName, String grammarFileName, String grammarStr, boolean defaultListener, String... extraOptions) {
boolean allIsWell = true;
System.out.println("dir "+tmpdir); System.out.println("dir "+tmpdir);
mkdir(tmpdir); mkdir(tmpdir);
writeFile(tmpdir, fileName, grammarStr); writeFile(tmpdir, fileName, grammarStr);
ErrorQueue equeue = new ErrorQueue();
final List<String> options = new ArrayList<String>(); final List<String> options = new ArrayList<String>();
Collections.addAll(options, extraOptions); Collections.addAll(options, extraOptions);
options.add("-o"); options.add("-o");
@ -421,23 +417,17 @@ public abstract class BaseTest {
options.add("-lib"); options.add("-lib");
options.add(tmpdir); options.add(tmpdir);
options.add(new File(tmpdir,grammarFileName).toString()); options.add(new File(tmpdir,grammarFileName).toString());
try {
final String[] optionsA = new String[options.size()];
options.toArray(optionsA);
Tool antlr = newTool(optionsA);
antlr.addListener(equeue);
if (defaultListener) {
antlr.addListener(new DefaultToolListener(antlr));
}
antlr.processGrammarsOnCommandLine();
}
catch (Exception e) {
allIsWell = false;
System.err.println("problems building grammar: "+e);
e.printStackTrace(System.err);
}
allIsWell = equeue.errors.isEmpty(); final String[] optionsA = new String[options.size()];
options.toArray(optionsA);
Tool antlr = newTool(optionsA);
ErrorQueue equeue = new ErrorQueue(antlr);
antlr.addListener(equeue);
if (defaultListener) {
antlr.addListener(new DefaultToolListener(antlr));
}
antlr.processGrammarsOnCommandLine();
if ( !defaultListener && !equeue.errors.isEmpty() ) { if ( !defaultListener && !equeue.errors.isEmpty() ) {
System.err.println("antlr reports errors from "+options); System.err.println("antlr reports errors from "+options);
for (int i = 0; i < equeue.errors.size(); i++) { for (int i = 0; i < equeue.errors.size(); i++) {
@ -456,7 +446,7 @@ public abstract class BaseTest {
} }
} }
return allIsWell; return equeue;
} }
protected String execLexer(String grammarFileName, protected String execLexer(String grammarFileName,
@ -526,9 +516,9 @@ public abstract class BaseTest {
boolean defaultListener, boolean defaultListener,
String... extraOptions) String... extraOptions)
{ {
boolean allIsWell = ErrorQueue equeue =
antlr(grammarFileName, grammarFileName, grammarStr, defaultListener, extraOptions); antlr(grammarFileName, grammarFileName, grammarStr, defaultListener, extraOptions);
if (!allIsWell) { if (!equeue.errors.isEmpty()) {
return false; return false;
} }
@ -546,7 +536,7 @@ public abstract class BaseTest {
files.add(grammarFileName.substring(0, grammarFileName.lastIndexOf('.'))+"BaseVisitor.java"); files.add(grammarFileName.substring(0, grammarFileName.lastIndexOf('.'))+"BaseVisitor.java");
} }
} }
allIsWell = compile(files.toArray(new String[files.size()])); boolean allIsWell = compile(files.toArray(new String[files.size()]));
return allIsWell; return allIsWell;
} }
@ -670,23 +660,13 @@ public abstract class BaseTest {
for (int i = 0; i < pairs.length; i+=2) { for (int i = 0; i < pairs.length; i+=2) {
String input = pairs[i]; String input = pairs[i];
String expect = pairs[i+1]; String expect = pairs[i+1];
ErrorQueue equeue = new ErrorQueue();
Grammar g = null; String[] lines = input.split("\n");
try { String fileName = getFilenameFromFirstLineOfGrammar(lines[0]);
String[] lines = input.split("\n"); ErrorQueue equeue = antlr(fileName, fileName, input, false);
String fileName = getFilenameFromFirstLineOfGrammar(lines[0]);
if (input.startsWith("lexer ")) { String actual = equeue.toString(true);
g = new LexerGrammar(fileName, input, equeue); actual = actual.replace(tmpdir + File.separator, "");
} else {
g = new Grammar(fileName, input, equeue);
}
}
catch (UnsupportedOperationException ex) {
}
catch (org.antlr.runtime.RecognitionException re) {
re.printStackTrace(System.err);
}
String actual = equeue.toString(g != null ? g.tool : new Tool());
System.err.println(actual); System.err.println(actual);
String msg = input; String msg = input;
msg = msg.replace("\n","\\n"); msg = msg.replace("\n","\\n");
@ -698,14 +678,14 @@ public abstract class BaseTest {
} }
public String getFilenameFromFirstLineOfGrammar(String line) { public String getFilenameFromFirstLineOfGrammar(String line) {
String fileName = "<string>"; String fileName = "A" + Tool.GRAMMAR_EXTENSION;
int grIndex = line.lastIndexOf("grammar"); int grIndex = line.lastIndexOf("grammar");
int semi = line.lastIndexOf(';'); int semi = line.lastIndexOf(';');
if ( grIndex>=0 && semi>=0 ) { if ( grIndex>=0 && semi>=0 ) {
int space = line.indexOf(' ', grIndex); int space = line.indexOf(' ', grIndex);
fileName = line.substring(space+1, semi)+Tool.GRAMMAR_EXTENSION; fileName = line.substring(space+1, semi)+Tool.GRAMMAR_EXTENSION;
} }
if ( fileName.length()==Tool.GRAMMAR_EXTENSION.length() ) fileName = "<string>"; if ( fileName.length()==Tool.GRAMMAR_EXTENSION.length() ) fileName = "A" + Tool.GRAMMAR_EXTENSION;
return fileName; return fileName;
} }
@ -776,7 +756,7 @@ public abstract class BaseTest {
st.add(actionName, action); st.add(actionName, action);
String grammar = st.render(); String grammar = st.render();
ErrorQueue equeue = new ErrorQueue(); ErrorQueue equeue = new ErrorQueue();
Grammar g = new Grammar(grammar); Grammar g = new Grammar(grammar, equeue);
if ( g.ast!=null && !g.ast.hasErrors ) { if ( g.ast!=null && !g.ast.hasErrors ) {
SemanticPipeline sem = new SemanticPipeline(g); SemanticPipeline sem = new SemanticPipeline(g);
sem.process(); sem.process();
@ -797,7 +777,7 @@ public abstract class BaseTest {
assertEquals(expected, snippet); assertEquals(expected, snippet);
} }
if ( equeue.size()>0 ) { if ( equeue.size()>0 ) {
System.err.println(equeue.toString(g.tool)); System.err.println(equeue.toString());
} }
} }

View File

@ -40,10 +40,19 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ErrorQueue implements ANTLRToolListener { public class ErrorQueue implements ANTLRToolListener {
public List<String> infos = new ArrayList<String>(); public final Tool tool;
public List<ANTLRMessage> errors = new ArrayList<ANTLRMessage>(); public final List<String> infos = new ArrayList<String>();
public List<ANTLRMessage> warnings = new ArrayList<ANTLRMessage>(); public final List<ANTLRMessage> errors = new ArrayList<ANTLRMessage>();
public List<ANTLRMessage> all = new ArrayList<ANTLRMessage>(); public final List<ANTLRMessage> warnings = new ArrayList<ANTLRMessage>();
public final List<ANTLRMessage> all = new ArrayList<ANTLRMessage>();
public ErrorQueue() {
this(null);
}
public ErrorQueue(Tool tool) {
this.tool = tool;
}
@Override @Override
public void info(String msg) { public void info(String msg) {
@ -64,7 +73,7 @@ public class ErrorQueue implements ANTLRToolListener {
public void error(ToolMessage msg) { public void error(ToolMessage msg) {
errors.add(msg); errors.add(msg);
all.add(msg); all.add(msg);
} }
public int size() { public int size() {
@ -72,15 +81,26 @@ public class ErrorQueue implements ANTLRToolListener {
} }
@Override @Override
public String toString() { return Utils.join(all.iterator(), "\n"); } public String toString() {
return toString(false);
}
public String toString(boolean rendered) {
if (!rendered) {
return Utils.join(all.iterator(), "\n");
}
if (tool == null) {
throw new IllegalStateException(String.format("No %s instance is available.", Tool.class.getName()));
}
public String toString(Tool tool) {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
for (ANTLRMessage m : all) { for (ANTLRMessage m : all) {
ST st = tool.errMgr.getMessageTemplate(m); ST st = tool.errMgr.getMessageTemplate(m);
buf.append(st.render()); buf.append(st.render());
buf.append("\n"); buf.append("\n");
} }
return buf.toString(); return buf.toString();
} }

View File

@ -641,9 +641,9 @@ public class TestCompositeGrammars extends BaseTest {
"s : a ;\n" + "s : a ;\n" +
"B : 'b' ;" + // defines B from inherited token space "B : 'b' ;" + // defines B from inherited token space
"WS : (' '|'\\n') -> skip ;\n" ; "WS : (' '|'\\n') -> skip ;\n" ;
boolean ok = antlr("M.g4", "M.g4", master, false); ErrorQueue equeue = antlr("M.g4", "M.g4", master, false);
boolean expecting = true; // should be ok int expecting = 0; // should be ok
assertEquals(expecting, ok); assertEquals(expecting, equeue.errors.size());
} }
@Test public void testImportedRuleWithAction() throws Exception { @Test public void testImportedRuleWithAction() throws Exception {

View File

@ -42,14 +42,14 @@ public class TestToolSyntaxErrors extends BaseTest {
"error(" + ErrorType.NO_RULES.code + "): A.g4::: grammar 'A' has no rules\n", "error(" + ErrorType.NO_RULES.code + "): A.g4::: grammar 'A' has no rules\n",
"A;", "A;",
"error(" + ErrorType.SYNTAX_ERROR.code + "): <string>:1:0: syntax error: 'A' came as a complete surprise to me\n", "error(" + ErrorType.SYNTAX_ERROR.code + "): A.g4:1:0: syntax error: 'A' came as a complete surprise to me\n",
"grammar ;", "grammar ;",
"error(" + ErrorType.SYNTAX_ERROR.code + "): <string>:1:8: syntax error: ';' came as a complete surprise to me while looking for an identifier\n", "error(" + ErrorType.SYNTAX_ERROR.code + "): A.g4:1:8: syntax error: ';' came as a complete surprise to me while looking for an identifier\n",
"grammar A\n" + "grammar A\n" +
"a : ID ;\n", "a : ID ;\n",
"error(" + ErrorType.SYNTAX_ERROR.code + "): <string>:2:0: syntax error: missing SEMI at 'a'\n", "error(" + ErrorType.SYNTAX_ERROR.code + "): A.g4:2:0: syntax error: missing SEMI at 'a'\n",
"grammar A;\n" + "grammar A;\n" +
"a : ID ;;\n"+ "a : ID ;;\n"+
@ -261,4 +261,19 @@ public class TestToolSyntaxErrors extends BaseTest {
}; };
super.testErrors(pair, true); super.testErrors(pair, true);
} }
@Test public void testRuleRedefinition() {
String[] pair = new String[] {
"grammar Oops;\n" +
"\n" +
"ret_ty : A ;\n" +
"ret_ty : B ;\n" +
"\n" +
"A : 'a' ;\n" +
"B : 'b' ;\n",
"error(" + ErrorType.RULE_REDEFINITION.code + "): Oops.g4:4:0: rule 'ret_ty' redefinition; previous at line 3\n"
};
super.testErrors(pair, true);
}
} }