diff --git a/tool-testsuite/test/org/antlr/v4/test/tool/TestSymbolIssues.java b/tool-testsuite/test/org/antlr/v4/test/tool/TestSymbolIssues.java index f367459e2..f30d9c1c4 100644 --- a/tool-testsuite/test/org/antlr/v4/test/tool/TestSymbolIssues.java +++ b/tool-testsuite/test/org/antlr/v4/test/tool/TestSymbolIssues.java @@ -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); + } } diff --git a/tool/src/org/antlr/v4/automata/LexerATNFactory.java b/tool/src/org/antlr/v4/automata/LexerATNFactory.java index e1ce57436..03626e059 100644 --- a/tool/src/org/antlr/v4/automata/LexerATNFactory.java +++ b/tool/src/org/antlr/v4/automata/LexerATNFactory.java @@ -95,6 +95,8 @@ public class LexerATNFactory extends ParserATNFactory { COMMON_CONSTANTS.put("MIN_CHAR_VALUE", Lexer.MIN_CHAR_VALUE); } + private List ruleCommands = new ArrayList(); + /** * 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 modeNames = new ArrayList(((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; } diff --git a/tool/src/org/antlr/v4/tool/ErrorType.java b/tool/src/org/antlr/v4/tool/ErrorType.java index c5db57763..c9401e047 100644 --- a/tool/src/org/antlr/v4/tool/ErrorType.java +++ b/tool/src/org/antlr/v4/tool/ErrorType.java @@ -1024,7 +1024,7 @@ public enum ErrorType { * *

nameis not a recognized mode name

* - *
TOKEN: 'a' -> channel(MODE1); // error 176
+ *
TOKEN: 'a' -> mode(MODE1); // error 176
*/ CONSTANT_VALUE_IS_NOT_A_RECOGNIZED_MODE_NAME(176, " is not a recognized mode name", ErrorSeverity.ERROR), /** @@ -1032,9 +1032,25 @@ public enum ErrorType { * *

name is not a recognized channel name

* - *
TOKEN: 'a' -> mode(TOKEN1); // error 177
+ *
TOKEN: 'a' -> channel(TOKEN1); // error 177
*/ CONSTANT_VALUE_IS_NOT_A_RECOGNIZED_CHANNEL_NAME(177, " is not a recognized channel name", ErrorSeverity.ERROR), + /* + * Compiler Warning 178. + * + *

lexer rule has a duplicated commands

+ * + *

TOKEN: 'asdf' -> mode(MODE1), mode(MODE2);

+ * */ + DUPLICATED_COMMAND(178, "duplicated command ", ErrorSeverity.WARNING), + /* + * Compiler Waring 179. + * + *

incompatible commands command1 and command2

+ * + *

T00: 'a00' -> skip, more;

+ */ + INCOMPATIBLE_COMMANDS(179, "incompatible commands and ", ErrorSeverity.WARNING), /* * Backward incompatibility errors