Merge pull request #1449 from KvanTTT/duplicated_and_nonsensical_commands_1388

Duplicated and incompatible commands
This commit is contained in:
Terence Parr 2016-12-05 13:40:10 -08:00 committed by GitHub
commit abed9e733a
3 changed files with 145 additions and 15 deletions

View File

@ -247,4 +247,66 @@ public class TestSymbolIssues extends BaseJavaToolTest {
testErrors(test, false);
}
// https://github.com/antlr/antlr4/issues/1388
@Test public void testDuplicatedCommands() throws Exception {
String[] test = {
"lexer grammar Lexer;\n" +
"channels { CHANNEL1, CHANNEL2 }\n" +
"tokens { TEST1, TEST2 }\n" +
"TOKEN: 'a' -> mode(MODE1), mode(MODE2);\n" +
"TOKEN1: 'b' -> pushMode(MODE1), mode(MODE2);\n" +
"TOKEN2: 'c' -> pushMode(MODE1), pushMode(MODE2); // pushMode is not duplicate\n" +
"TOKEN3: 'd' -> popMode, popMode; // popMode is not duplicate\n" +
"mode MODE1;\n" +
"MODE1_TOKEN: 'e';\n" +
"mode MODE2;\n" +
"MODE2_TOKEN: 'f';\n" +
"MODE2_TOKEN1: 'g' -> type(TEST1), type(TEST2);\n" +
"MODE2_TOKEN2: 'h' -> channel(CHANNEL1), channel(CHANNEL2), channel(DEFAULT_TOKEN_CHANNEL);",
"warning(" + ErrorType.DUPLICATED_COMMAND.code + "): Lexer.g4:4:27: duplicated command mode\n" +
"warning(" + ErrorType.DUPLICATED_COMMAND.code + "): Lexer.g4:12:34: duplicated command type\n" +
"warning(" + ErrorType.DUPLICATED_COMMAND.code + "): Lexer.g4:13:40: duplicated command channel\n" +
"warning(" + ErrorType.DUPLICATED_COMMAND.code + "): Lexer.g4:13:59: duplicated command channel\n"
};
testErrors(test, false);
}
// https://github.com/antlr/antlr4/issues/1388
@Test public void testIncompatibleCommands() throws Exception {
String[] test = {
"lexer grammar L;\n" +
"channels { CHANNEL1 }\n" +
"tokens { TYPE1 }\n" +
"// Incompatible\n" +
"T00: 'a00' -> skip, more;\n" +
"T01: 'a01' -> skip, type(TYPE1);\n" +
"T02: 'a02' -> skip, channel(CHANNEL1);\n" +
"T03: 'a03' -> more, type(TYPE1);\n" +
"T04: 'a04' -> more, channel(CHANNEL1);\n" +
"T05: 'a05' -> more, skip;\n" +
"T06: 'a06' -> type(TYPE1), skip;\n" +
"T07: 'a07' -> type(TYPE1), more;\n" +
"T08: 'a08' -> channel(CHANNEL1), skip;\n" +
"T09: 'a09' -> channel(CHANNEL1), more;\n" +
"// Allowed\n" +
"T10: 'a10' -> type(TYPE1), channel(CHANNEL1);\n" +
"T11: 'a11' -> channel(CHANNEL1), type(TYPE1);",
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:5:20: incompatible commands skip and more\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:6:20: incompatible commands skip and type\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:7:20: incompatible commands skip and channel\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:8:20: incompatible commands more and type\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:9:20: incompatible commands more and channel\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:10:20: incompatible commands more and skip\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:11:27: incompatible commands type and skip\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:12:27: incompatible commands type and more\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:13:33: incompatible commands channel and skip\n" +
"warning(" + ErrorType.INCOMPATIBLE_COMMANDS.code + "): L.g4:14:33: incompatible commands channel and more\n"
};
testErrors(test, false);
}
}

View File

@ -95,6 +95,8 @@ public class LexerATNFactory extends ParserATNFactory {
COMMON_CONSTANTS.put("MIN_CHAR_VALUE", Lexer.MIN_CHAR_VALUE);
}
private List<String> ruleCommands = new ArrayList<String>();
/**
* Maps from an action index to a {@link LexerAction} object.
*/
@ -159,6 +161,12 @@ public class LexerATNFactory extends ParserATNFactory {
return atn;
}
@Override
public Handle rule(GrammarAST ruleAST, String name, Handle blk) {
ruleCommands.clear();
return super.rule(ruleAST, name, blk);
}
@Override
public Handle action(ActionAST action) {
int ruleIndex = currentRule.index;
@ -389,7 +397,7 @@ public class LexerATNFactory extends ParserATNFactory {
@Override
public Handle tokenRef(TerminalAST node) {
// Ref to EOF in lexer yields char transition on -1
if ( node.getText().equals("EOF") ) {
if (node.getText().equals("EOF") ) {
ATNState left = newState(node);
ATNState right = newState(node);
left.addTransition(new AtomTransition(right, IntStream.EOF));
@ -398,9 +406,10 @@ public class LexerATNFactory extends ParserATNFactory {
return _ruleRef(node);
}
protected LexerAction createLexerAction(GrammarAST ID, GrammarAST arg) {
private LexerAction createLexerAction(GrammarAST ID, GrammarAST arg) {
String command = ID.getText();
checkCommands(command, ID.getToken());
if ("skip".equals(command) && arg == null) {
return LexerSkipAction.INSTANCE;
}
@ -451,6 +460,49 @@ public class LexerATNFactory extends ParserATNFactory {
}
}
private void checkCommands(String command, Token commandToken) {
// Command combinations list: https://github.com/antlr/antlr4/issues/1388#issuecomment-263344701
if (!command.equals("pushMode") && !command.equals("popMode")) {
if (ruleCommands.contains(command)) {
g.tool.errMgr.grammarError(ErrorType.DUPLICATED_COMMAND, g.fileName, commandToken, command);
}
if (!ruleCommands.equals("mode")) {
String firstCommand = null;
if (command.equals("skip")) {
if (ruleCommands.contains("more")) {
firstCommand = "more";
} else if (ruleCommands.contains("type")) {
firstCommand = "type";
} else if (ruleCommands.contains("channel")) {
firstCommand = "channel";
}
} else if (command.equals("more")) {
if (ruleCommands.contains("skip")) {
firstCommand = "skip";
} else if (ruleCommands.contains("type")) {
firstCommand = "type";
} else if (ruleCommands.contains("channel")) {
firstCommand = "channel";
}
} else if (command.equals("type") || command.equals("channel")) {
if (ruleCommands.contains("more")) {
firstCommand = "more";
} else if (ruleCommands.contains("skip")) {
firstCommand = "skip";
}
}
if (firstCommand != null) {
g.tool.errMgr.grammarError(ErrorType.INCOMPATIBLE_COMMANDS, g.fileName, commandToken, firstCommand, command);
}
}
}
ruleCommands.add(command);
}
private Integer getModeConstantValue(String modeName, Token token) {
if (modeName == null) {
return null;
@ -459,16 +511,16 @@ public class LexerATNFactory extends ParserATNFactory {
if (modeName.equals("DEFAULT_MODE")) {
return Lexer.DEFAULT_MODE;
}
if (COMMON_CONSTANTS.containsKey(modeName)) {
g.tool.errMgr.grammarError(ErrorType.MODE_CONFLICTS_WITH_COMMON_CONSTANTS, g.fileName, token, token.getText());
return null;
}
List<String> modeNames = new ArrayList<String>(((LexerGrammar)g).modes.keySet());
int mode = modeNames.indexOf(modeName);
if (mode >= 0) {
return mode;
}
else if (COMMON_CONSTANTS.containsKey(modeName)) {
g.tool.errMgr.grammarError(ErrorType.MODE_CONFLICTS_WITH_COMMON_CONSTANTS, g.fileName, token, token.getText());
return null;
}
try {
return Integer.parseInt(modeName);
@ -486,15 +538,15 @@ public class LexerATNFactory extends ParserATNFactory {
if (tokenName.equals("EOF")) {
return Lexer.EOF;
}
if (COMMON_CONSTANTS.containsKey(tokenName)) {
g.tool.errMgr.grammarError(ErrorType.TOKEN_CONFLICTS_WITH_COMMON_CONSTANTS, g.fileName, token, token.getText());
return null;
}
int tokenType = g.getTokenType(tokenName);
if (tokenType != org.antlr.v4.runtime.Token.INVALID_TYPE) {
return tokenType;
}
else if (COMMON_CONSTANTS.containsKey(tokenName)) {
g.tool.errMgr.grammarError(ErrorType.TOKEN_CONFLICTS_WITH_COMMON_CONSTANTS, g.fileName, token, token.getText());
return null;
}
try {
return Integer.parseInt(tokenName);
@ -512,10 +564,10 @@ public class LexerATNFactory extends ParserATNFactory {
if (channelName.equals("HIDDEN")) {
return Lexer.HIDDEN;
}
else if (channelName.equals("DEFAULT_TOKEN_CHANNEL")) {
if (channelName.equals("DEFAULT_TOKEN_CHANNEL")) {
return Lexer.DEFAULT_TOKEN_CHANNEL;
}
else if (COMMON_CONSTANTS.containsKey(channelName)) {
if (COMMON_CONSTANTS.containsKey(channelName)) {
g.tool.errMgr.grammarError(ErrorType.CHANNEL_CONFLICTS_WITH_COMMON_CONSTANTS, g.fileName, token, token.getText());
return null;
}

View File

@ -1024,7 +1024,7 @@ public enum ErrorType {
*
* <p><em>name</em>is not a recognized mode name</p>
*
* <pre>TOKEN: 'a' -> channel(MODE1); // error 176</pre>
* <pre>TOKEN: 'a' -> mode(MODE1); // error 176</pre>
*/
CONSTANT_VALUE_IS_NOT_A_RECOGNIZED_MODE_NAME(176, "<arg> is not a recognized mode name", ErrorSeverity.ERROR),
/**
@ -1032,9 +1032,25 @@ public enum ErrorType {
*
* <p><em>name</em> is not a recognized channel name</p>
*
* <pre>TOKEN: 'a' -> mode(TOKEN1); // error 177</pre>
* <pre>TOKEN: 'a' -> channel(TOKEN1); // error 177</pre>
*/
CONSTANT_VALUE_IS_NOT_A_RECOGNIZED_CHANNEL_NAME(177, "<arg> is not a recognized channel name", ErrorSeverity.ERROR),
/*
* Compiler Warning 178.
*
* <p>lexer rule has a duplicated commands</p>
*
* <p>TOKEN: 'asdf' -> mode(MODE1), mode(MODE2);</p>
* */
DUPLICATED_COMMAND(178, "duplicated command <arg>", ErrorSeverity.WARNING),
/*
* Compiler Waring 179.
*
* <p>incompatible commands <em>command1</em> and <em>command2</em></p>
*
* <p>T00: 'a00' -> skip, more;</p>
*/
INCOMPATIBLE_COMMANDS(179, "incompatible commands <arg> and <arg2>", ErrorSeverity.WARNING),
/*
* Backward incompatibility errors