diff --git a/tool-testsuite/test/org/antlr/v4/test/tool/TestCompositeGrammars.java b/tool-testsuite/test/org/antlr/v4/test/tool/TestCompositeGrammars.java index 75dc35d51..ecfc04f5d 100644 --- a/tool-testsuite/test/org/antlr/v4/test/tool/TestCompositeGrammars.java +++ b/tool-testsuite/test/org/antlr/v4/test/tool/TestCompositeGrammars.java @@ -61,6 +61,131 @@ public class TestCompositeGrammars extends BaseJavaToolTest { assertEquals(0, equeue.size()); } + @Test public void testImportIntoLexerGrammar() throws Exception { + BaseRuntimeTest.mkdir(tmpdir); + + String master = + "lexer grammar M;\n" + + "import S;\n" + + "A : 'a';\n" + + "B : 'b';\n"; + writeFile(tmpdir, "M.g4", master); + + String slave = + "lexer grammar S;\n" + + "C : 'c';\n"; + writeFile(tmpdir, "S.g4", slave); + + ErrorQueue equeue = BaseRuntimeTest.antlrOnString(tmpdir, "Java", "M.g4", false, "-lib", tmpdir); + assertEquals(0, equeue.errors.size()); + } + + @Test public void testImportModesIntoLexerGrammar() throws Exception { + BaseRuntimeTest.mkdir(tmpdir); + + String master = + "lexer grammar M;\n" + + "import S;\n" + + "A : 'a' -> pushMode(X);\n" + + "B : 'b';\n"; + writeFile(tmpdir, "M.g4", master); + + String slave = + "lexer grammar S;\n" + + "D : 'd';\n" + + "mode X;\n" + + "C : 'c' -> popMode;\n"; + writeFile(tmpdir, "S.g4", slave); + + ErrorQueue equeue = BaseRuntimeTest.antlrOnString(tmpdir, "Java", "M.g4", false, "-lib", tmpdir); + assertEquals(0, equeue.errors.size()); + } + + @Test public void testImportChannelsIntoLexerGrammar() throws Exception { + BaseRuntimeTest.mkdir(tmpdir); + + String master = + "lexer grammar M;\n" + + "import S;\n" + + "channels {CH_A, CH_B}\n" + + "A : 'a' -> channel(CH_A);\n" + + "B : 'b' -> channel(CH_B);\n"; + writeFile(tmpdir, "M.g4", master); + + String slave = + "lexer grammar S;\n" + + "C : 'c';\n"; + writeFile(tmpdir, "S.g4", slave); + + ErrorQueue equeue = BaseRuntimeTest.antlrOnString(tmpdir, "Java", "M.g4", false, "-lib", tmpdir); + assertEquals(0, equeue.errors.size()); + } + + @Test public void testImportMixedChannelsIntoLexerGrammar() throws Exception { + BaseRuntimeTest.mkdir(tmpdir); + + String master = + "lexer grammar M;\n" + + "import S;\n" + + "channels {CH_A, CH_B}\n" + + "A : 'a' -> channel(CH_A);\n" + + "B : 'b' -> channel(CH_B);\n"; + writeFile(tmpdir, "M.g4", master); + + String slave = + "lexer grammar S;\n" + + "channels {CH_C}\n" + + "C : 'c' -> channel(CH_C);\n"; + writeFile(tmpdir, "S.g4", slave); + + ErrorQueue equeue = BaseRuntimeTest.antlrOnString(tmpdir, "Java", "M.g4", false, "-lib", tmpdir); + assertEquals(0, equeue.errors.size()); + } + + @Test public void testMergeModesIntoLexerGrammar() throws Exception { + BaseRuntimeTest.mkdir(tmpdir); + + String master = + "lexer grammar M;\n" + + "import S;\n" + + "A : 'a' -> pushMode(X);\n" + + "mode X;\n" + + "B : 'b';\n"; + writeFile(tmpdir, "M.g4", master); + + String slave = + "lexer grammar S;\n" + + "D : 'd';\n" + + "mode X;\n" + + "C : 'c' -> popMode;\n"; + writeFile(tmpdir, "S.g4", slave); + + ErrorQueue equeue = BaseRuntimeTest.antlrOnString(tmpdir, "Java", "M.g4", false, "-lib", tmpdir); + assertEquals(0, equeue.errors.size()); + } + + @Test public void testEmptyModesInLexerGrammar() throws Exception { + BaseRuntimeTest.mkdir(tmpdir); + + String master = + "lexer grammar M;\n" + + "import S;\n" + + "A : 'a';\n" + + "C : 'e';\n" + + "B : 'b';\n"; + writeFile(tmpdir, "M.g4", master); + + String slave = + "lexer grammar S;\n" + + "D : 'd';\n" + + "mode X;\n" + + "C : 'c' -> popMode;\n"; + writeFile(tmpdir, "S.g4", slave); + + ErrorQueue equeue = BaseRuntimeTest.antlrOnString(tmpdir, "Java", "M.g4", false, "-lib", tmpdir); + assertEquals(0, equeue.errors.size()); + } + @Test public void testDelegatesSeeSameTokenType() throws Exception { String slaveS = "parser grammar S;\n"+ diff --git a/tool/src/org/antlr/v4/tool/GrammarTransformPipeline.java b/tool/src/org/antlr/v4/tool/GrammarTransformPipeline.java index e03652b6e..f6e4cdfbf 100644 --- a/tool/src/org/antlr/v4/tool/GrammarTransformPipeline.java +++ b/tool/src/org/antlr/v4/tool/GrammarTransformPipeline.java @@ -161,6 +161,7 @@ public class GrammarTransformPipeline { GrammarAST id = (GrammarAST) root.getChild(0); GrammarASTAdaptor adaptor = new GrammarASTAdaptor(id.token.getInputStream()); + GrammarAST channelsRoot = (GrammarAST)root.getFirstChildWithType(ANTLRParser.CHANNELS); GrammarAST tokensRoot = (GrammarAST)root.getFirstChildWithType(ANTLRParser.TOKENS_SPEC); List actionRoots = root.getNodesWithType(ANTLRParser.AT); @@ -172,7 +173,39 @@ public class GrammarTransformPipeline { List rootRules = RULES.getNodesWithType(ANTLRParser.RULE); for (GrammarAST r : rootRules) rootRuleNames.add(r.getChild(0).getText()); + // make list of modes we have in root grammar + List rootModes = root.getNodesWithType(ANTLRParser.MODE); + Set rootModeNames = new HashSet(); + for (GrammarAST m : rootModes) rootModeNames.add(m.getChild(0).getText()); + List addedModes = new ArrayList(); + for (Grammar imp : imports) { + // COPY CHANNELS + GrammarAST imp_channelRoot = (GrammarAST)imp.ast.getFirstChildWithType(ANTLRParser.CHANNELS); + if ( imp_channelRoot != null) { + rootGrammar.tool.log("grammar", "imported channels: "+imp_channelRoot.getChildren()); + if (channelsRoot==null) { + channelsRoot = imp_channelRoot.dupTree(); + channelsRoot.g = rootGrammar; + root.insertChild(1, channelsRoot); // ^(GRAMMAR ID TOKENS...) + } else { + for (int c = 0; c < imp_channelRoot.getChildCount(); ++c) { + String channel = imp_channelRoot.getChild(c).getText(); + boolean channelIsInRootGrammar = false; + for (int rc = 0; rc < channelsRoot.getChildCount(); ++rc) { + String rootChannel = channelsRoot.getChild(rc).getText(); + if (rootChannel.equals(channel)) { + channelIsInRootGrammar = true; + break; + } + } + if (!channelIsInRootGrammar) { + channelsRoot.addChild(imp_channelRoot.getChild(c).dupNode()); + } + } + } + } + // COPY TOKENS GrammarAST imp_tokensRoot = (GrammarAST)imp.ast.getFirstChildWithType(ANTLRParser.TOKENS_SPEC); if ( imp_tokensRoot!=null ) { @@ -242,7 +275,54 @@ public class GrammarTransformPipeline { } } + // COPY MODES + // The strategy is to copy all the mode sections rules across to any + // mode section in the new grammar with the same name or a new + // mode section if no matching mode is resolved. Rules which are + // already in the new grammar are ignored for copy. If the mode + // section being added ends up empty it is not added to the merged + // grammar. + List modes = imp.ast.getNodesWithType(ANTLRParser.MODE); + if (modes != null) { + for (GrammarAST m : modes) { + rootGrammar.tool.log("grammar", "imported mode: " + m.toStringTree()); + String name = m.getChild(0).getText(); + boolean rootAlreadyHasMode = rootModeNames.contains(name); + GrammarAST destinationAST = null; + if (rootAlreadyHasMode) { + for (GrammarAST m2 : rootModes) { + if (m2.getChild(0).getText().equals(name)) { + destinationAST = m2; + break; + } + } + } else { + destinationAST = m.dupNode(); + destinationAST.addChild(m.getChild(0).dupNode()); + } + + int addedRules = 0; + List modeRules = m.getAllChildrenWithType(ANTLRParser.RULE); + for (GrammarAST r : modeRules) { + rootGrammar.tool.log("grammar", "imported rule: "+r.toStringTree()); + String ruleName = r.getChild(0).getText(); + boolean rootAlreadyHasRule = rootRuleNames.contains(ruleName); + if (!rootAlreadyHasRule) { + destinationAST.addChild(r); + addedRules++; + rootRuleNames.add(ruleName); + } + } + if (!rootAlreadyHasMode && addedRules > 0) { + rootGrammar.ast.addChild(destinationAST); + rootModeNames.add(name); + rootModes.add(destinationAST); + } + } + } + // COPY RULES + // Rules copied in the mode copy phase are not copied again. List rules = imp.ast.getNodesWithType(ANTLRParser.RULE); if ( rules!=null ) { for (GrammarAST r : rules) {