/*
 [The "BSD licence"]
 Copyright (c) 2005-2007 Terence Parr
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
 3. The name of the author may not be used to endorse or promote products
    derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.antlr.test;

import org.antlr.Tool;
import org.antlr.tool.*;

import org.junit.Test;
import static org.junit.Assert.*;

public class TestCompositeGrammars extends BaseTest {
	protected boolean debug = false;

	@Test public void testWildcardStillWorks() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String grammar =
			"parser grammar S;\n" +
			"a : B . C ;\n"; // not qualified ID
		Grammar g = new Grammar(grammar);
		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());
	}

	@Test public void testDelegatorInvokesDelegateRule() throws Exception {
		String slave =
			"parser grammar S;\n" +
			"a : B {System.out.println(\"S.a\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"grammar M;\n" +
			"import S;\n" +
			"s : a ;\n" +
			"B : 'b' ;" + // defines B from inherited token space
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "s", "b", debug);
		assertEquals("S.a\n", found);
	}

	@Test public void testDelegatorInvokesDelegateRuleWithArgs() throws Exception {
		// must generate something like:
		// public int a(int x) throws RecognitionException { return gS.a(x); }
		// in M.
		String slave =
			"parser grammar S;\n" +
			"a[int x] returns [int y] : B {System.out.print(\"S.a\"); $y=1000;} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"grammar M;\n" +
			"import S;\n" +
			"s : label=a[3] {System.out.println($label.y);} ;\n" +
			"B : 'b' ;" + // defines B from inherited token space
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "s", "b", debug);
		assertEquals("S.a1000\n", found);
	}

	@Test public void testDelegatorInvokesDelegateRuleWithReturnStruct() throws Exception {
		// must generate something like:
		// public int a(int x) throws RecognitionException { return gS.a(x); }
		// in M.
		String slave =
			"parser grammar S;\n" +
			"a : B {System.out.print(\"S.a\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"grammar M;\n" +
			"import S;\n" +
			"s : a {System.out.println($a.text);} ;\n" +
			"B : 'b' ;" + // defines B from inherited token space
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "s", "b", debug);
		assertEquals("S.ab\n", found);
	}

	@Test public void testDelegatorAccessesDelegateMembers() throws Exception {
		String slave =
			"parser grammar S;\n" +
			"@members {\n" +
			"  public void foo() {System.out.println(\"foo\");}\n" +
			"}\n" +
			"a : B ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"grammar M;\n" +		// uses no rules from the import
			"import S;\n" +
			"s : 'b' {gS.foo();} ;\n" + // gS is import pointer
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "s", "b", debug);
		assertEquals("foo\n", found);
	}

	@Test public void testDelegatorInvokesFirstVersionOfDelegateRule() throws Exception {
		String slave =
			"parser grammar S;\n" +
			"a : b {System.out.println(\"S.a\");} ;\n" +
			"b : B ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String slave2 =
			"parser grammar T;\n" +
			"a : B {System.out.println(\"T.a\");} ;\n"; // hidden by S.a
		writeFile(tmpdir, "T.g", slave2);
		String master =
			"grammar M;\n" +
			"import S,T;\n" +
			"s : a ;\n" +
			"B : 'b' ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "s", "b", debug);
		assertEquals("S.a\n", found);
	}

	@Test public void testDelegatesSeeSameTokenType() throws Exception {
		String slave =
			"parser grammar S;\n" + // A, B, C token type order
			"tokens { A; B; C; }\n" +
			"x : A {System.out.println(\"S.x\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String slave2 =
			"parser grammar T;\n" +
			"tokens { C; B; A; }\n" + // reverse order
			"y : A {System.out.println(\"T.y\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "T.g", slave2);
		// The lexer will create rules to match letters a, b, c.
		// The associated token types A, B, C must have the same value
		// and all import'd parsers.  Since ANTLR regenerates all imports
		// for use with the delegator M, it can generate the same token type
		// mapping in each parser:
		// public static final int C=6;
		// public static final int EOF=-1;
		// public static final int B=5;
		// public static final int WS=7;
		// public static final int A=4;

		String master =
			"grammar M;\n" +
			"import S,T;\n" +
			"s : x y ;\n" + // matches AA, which should be "aa"
			"B : 'b' ;\n" + // another order: B, A, C
			"A : 'a' ;\n" +
			"C : 'c' ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "s", "aa", debug);
		assertEquals("S.x\n" +
					 "T.y\n", found);
	}

	@Test public void testDelegatesSeeSameTokenType2() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar S;\n" + // A, B, C token type order
			"tokens { A; B; C; }\n" +
			"x : A {System.out.println(\"S.x\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String slave2 =
			"parser grammar T;\n" +
			"tokens { C; B; A; }\n" + // reverse order
			"y : A {System.out.println(\"T.y\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "T.g", slave2);

		String master =
			"grammar M;\n" +
			"import S,T;\n" +
			"s : x y ;\n" + // matches AA, which should be "aa"
			"B : 'b' ;\n" + // another order: B, A, C
			"A : 'a' ;\n" +
			"C : 'c' ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();

		String expectedTokenIDToTypeMap = "[A=4, B=5, C=6, WS=7]";
		String expectedStringLiteralToTypeMap = "{}";
		String expectedTypeToTokenList = "[A, B, C, WS]";

		assertEquals(expectedTokenIDToTypeMap,
					 realElements(g.composite.tokenIDToTypeMap).toString());
		assertEquals(expectedStringLiteralToTypeMap, g.composite.stringLiteralToTypeMap.toString());
		assertEquals(expectedTypeToTokenList,
					 realElements(g.composite.typeToTokenList).toString());

		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());
	}

	@Test public void testCombinedImportsCombined() throws Exception {
		// for now, we don't allow combined to import combined
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"grammar S;\n" + // A, B, C token type order
			"tokens { A; B; C; }\n" +
			"x : 'x' INT {System.out.println(\"S.x\");} ;\n" +
			"INT : '0'..'9'+ ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);

		String master =
			"grammar M;\n" +
			"import S;\n" +
			"s : x INT ;\n";
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();

		assertEquals("unexpected errors: "+equeue, 1, equeue.errors.size());
		String expectedError = "error(161): "+tmpdir.toString().replaceFirst("\\-[0-9]+","")+"/M.g:2:8: combined grammar M cannot import combined grammar S";
		assertEquals("unexpected errors: "+equeue, expectedError, equeue.errors.get(0).toString().replaceFirst("\\-[0-9]+",""));
	}

	@Test public void testSameStringTwoNames() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar S;\n" +
			"tokens { A='a'; }\n" +
			"x : A {System.out.println(\"S.x\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String slave2 =
			"parser grammar T;\n" +
			"tokens { X='a'; }\n" +
			"y : X {System.out.println(\"T.y\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "T.g", slave2);

		String master =
			"grammar M;\n" +
			"import S,T;\n" +
			"s : x y ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();

		String expectedTokenIDToTypeMap = "[A=4, WS=6, X=5]";
		String expectedStringLiteralToTypeMap = "{'a'=4}";
		String expectedTypeToTokenList = "[A, X, WS]";

		assertEquals(expectedTokenIDToTypeMap,
					 realElements(g.composite.tokenIDToTypeMap).toString());
		assertEquals(expectedStringLiteralToTypeMap, g.composite.stringLiteralToTypeMap.toString());
		assertEquals(expectedTypeToTokenList,
					 realElements(g.composite.typeToTokenList).toString());

		Object expectedArg = "X='a'";
		Object expectedArg2 = "A";
		int expectedMsgID = ErrorManager.MSG_TOKEN_ALIAS_CONFLICT;
		GrammarSemanticsMessage expectedMessage =
			new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg, expectedArg2);
		checkGrammarSemanticsError(equeue, expectedMessage);

		assertEquals("unexpected errors: "+equeue, 1, equeue.errors.size());

		String expectedError =
			"error(158): T.g:2:10: cannot alias X='a'; string already assigned to A";
		assertEquals(expectedError, equeue.errors.get(0).toString());
	}

	@Test public void testSameNameTwoStrings() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar S;\n" +
			"tokens { A='a'; }\n" +
			"x : A {System.out.println(\"S.x\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String slave2 =
			"parser grammar T;\n" +
			"tokens { A='x'; }\n" +
			"y : A {System.out.println(\"T.y\");} ;\n";
		
		writeFile(tmpdir, "T.g", slave2);

		String master =
			"grammar M;\n" +
			"import S,T;\n" +
			"s : x y ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();

		String expectedTokenIDToTypeMap = "[A=4, T__6=6, WS=5]";
		String expectedStringLiteralToTypeMap = "{'a'=4, 'x'=6}";
		String expectedTypeToTokenList = "[A, WS, T__6]";

		assertEquals(expectedTokenIDToTypeMap,
					 realElements(g.composite.tokenIDToTypeMap).toString());
		assertEquals(expectedStringLiteralToTypeMap, sortMapToString(g.composite.stringLiteralToTypeMap));
		assertEquals(expectedTypeToTokenList,
					 realElements(g.composite.typeToTokenList).toString());

		Object expectedArg = "A='x'";
		Object expectedArg2 = "'a'";
		int expectedMsgID = ErrorManager.MSG_TOKEN_ALIAS_REASSIGNMENT;
		GrammarSemanticsMessage expectedMessage =
			new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg, expectedArg2);
		checkGrammarSemanticsError(equeue, expectedMessage);

		assertEquals("unexpected errors: "+equeue, 1, equeue.errors.size());

		String expectedError =
			"error(159): T.g:2:10: cannot alias A='x'; token name already assigned to 'a'";
		assertEquals(expectedError, equeue.errors.get(0).toString());
	}

	@Test public void testImportedTokenVocabIgnoredWithWarning() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar S;\n" +
			"options {tokenVocab=whatever;}\n" +
			"tokens { A='a'; }\n" +
			"x : A {System.out.println(\"S.x\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);

		String master =
			"grammar M;\n" +
			"import S;\n" +
			"s : x ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();

		Object expectedArg = "S";
		int expectedMsgID = ErrorManager.MSG_TOKEN_VOCAB_IN_DELEGATE;
		GrammarSemanticsMessage expectedMessage =
			new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg);
		checkGrammarSemanticsWarning(equeue, expectedMessage);

		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());
		assertEquals("unexpected errors: "+equeue, 1, equeue.warnings.size());

		String expectedError =
			"warning(160): S.g:2:10: tokenVocab option ignored in imported grammar S";
		assertEquals(expectedError, equeue.warnings.get(0).toString());
	}

	@Test public void testImportedTokenVocabWorksInRoot() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar S;\n" +
			"tokens { A='a'; }\n" +
			"x : A {System.out.println(\"S.x\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);

		String tokens =
			"A=99\n";
		writeFile(tmpdir, "Test.tokens", tokens);

		String master =
			"grammar M;\n" +
			"options {tokenVocab=Test;}\n" +
			"import S;\n" +
			"s : x ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();

		String expectedTokenIDToTypeMap = "[A=99, WS=101]";
		String expectedStringLiteralToTypeMap = "{'a'=100}";
		String expectedTypeToTokenList = "[A, 'a', WS]";

		assertEquals(expectedTokenIDToTypeMap,
					 realElements(g.composite.tokenIDToTypeMap).toString());
		assertEquals(expectedStringLiteralToTypeMap, g.composite.stringLiteralToTypeMap.toString());
		assertEquals(expectedTypeToTokenList,
					 realElements(g.composite.typeToTokenList).toString());

		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());
	}

	@Test public void testSyntaxErrorsInImportsNotThrownOut() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar S;\n" +
			"options {toke\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);

		String master =
			"grammar M;\n" +
			"import S;\n" +
			"s : x ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();

		// whole bunch of errors from bad S.g file
		assertEquals("unexpected errors: "+equeue, 5, equeue.errors.size());
	}

	@Test public void testSyntaxErrorsInImportsNotThrownOut2() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar S;\n" +
			": A {System.out.println(\"S.x\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);

		String master =
			"grammar M;\n" +
			"import S;\n" +
			"s : x ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();

		// whole bunch of errors from bad S.g file
		assertEquals("unexpected errors: "+equeue, 3, equeue.errors.size());
	}

	@Test public void testDelegatorRuleOverridesDelegate() throws Exception {
		String slave =
			"parser grammar S;\n" +
			"a : b {System.out.println(\"S.a\");} ;\n" +
			"b : B ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"grammar M;\n" +
			"import S;\n" +
			"b : 'b'|'c' ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "a", "c", debug);
		assertEquals("S.a\n", found);
	}

	@Test public void testDelegatorRuleOverridesLookaheadInDelegate() throws Exception {
		String slave =
			"parser grammar JavaDecl;\n" +
			"type : 'int' ;\n" +
			"decl : type ID ';'\n" +
			"     | type ID init ';' {System.out.println(\"JavaDecl: \"+$decl.text);}\n" +
			"     ;\n" +
			"init : '=' INT ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "JavaDecl.g", slave);
		String master =
			"grammar Java;\n" +
			"import JavaDecl;\n" +
			"prog : decl ;\n" +
			"type : 'int' | 'float' ;\n" +
			"\n" +
			"ID  : 'a'..'z'+ ;\n" +
			"INT : '0'..'9'+ ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		// for float to work in decl, type must be overridden
		String found = execParser("Java.g", master, "JavaParser", "JavaLexer",
								  "prog", "float x = 3;", debug);
		assertEquals("JavaDecl: floatx=3;\n", found);
	}

    @Test public void testDelegatorRuleOverridesDelegates() throws Exception {
        String slave =
            "parser grammar S;\n" +
            "a : b {System.out.println(\"S.a\");} ;\n" +
            "b : B ;\n" ;
        mkdir(tmpdir);
        writeFile(tmpdir, "S.g", slave);

        String slave2 =
            "parser grammar T;\n" +
            "tokens { A='x'; }\n" +
            "b : B {System.out.println(\"T.b\");} ;\n";
        writeFile(tmpdir, "T.g", slave2);

        String master =
            "grammar M;\n" +
            "import S, T;\n" +
            "b : 'b'|'c' {System.out.println(\"M.b\");}|B|A ;\n" +
            "WS : (' '|'\\n') {skip();} ;\n" ;
        String found = execParser("M.g", master, "MParser", "MLexer",
                                  "a", "c", debug);
        assertEquals("M.b\n" +
                     "S.a\n", found);
    }

	// LEXER INHERITANCE

	@Test public void testLexerDelegatorInvokesDelegateRule() throws Exception {
		String slave =
			"lexer grammar S;\n" +
			"A : 'a' {System.out.println(\"S.A\");} ;\n" +
			"C : 'c' ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"lexer grammar M;\n" +
			"import S;\n" +
			"B : 'b' ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execLexer("M.g", master, "M", "abc", debug);
		assertEquals("S.A\nabc\n", found);
	}

	@Test public void testLexerDelegatorRuleOverridesDelegate() throws Exception {
		String slave =
			"lexer grammar S;\n" +
			"A : 'a' {System.out.println(\"S.A\");} ;\n" +
			"B : 'b' {System.out.println(\"S.B\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"lexer grammar M;\n" +
			"import S;\n" +
			"A : 'a' B {System.out.println(\"M.A\");} ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execLexer("M.g", master, "M", "ab", debug);
		assertEquals("S.B\n" +
					 "M.A\n" +
					 "ab\n", found);
	}

	@Test public void testLexerDelegatorRuleOverridesDelegateLeavingNoRules() throws Exception {
		// M.Tokens has nothing to predict tokens from S.  Should
		// not include S.Tokens alt in this case?
		String slave =
			"lexer grammar S;\n" +
			"A : 'a' {System.out.println(\"S.A\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"lexer grammar M;\n" +
			"import S;\n" +
			"A : 'a' {System.out.println(\"M.A\");} ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		writeFile(tmpdir, "/M.g", master);

		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		composite.assignTokenTypes();
		composite.defineGrammarSymbols();
		composite.createNFAs();
		g.createLookaheadDFAs(false);

		// predict only alts from M not S
		String expectingDFA =
			".s0-'a'->.s1\n" +
			".s0-{'\\n', ' '}->:s3=>2\n" +
			".s1-<EOT>->:s2=>1\n";
		org.antlr.analysis.DFA dfa = g.getLookaheadDFA(1);
		FASerializer serializer = new FASerializer(g);
		String result = serializer.serialize(dfa.startState);
		assertEquals(expectingDFA, result);

		// must not be a "unreachable alt: Tokens" error
		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());
	}

	@Test public void testInvalidImportMechanism() throws Exception {
		// M.Tokens has nothing to predict tokens from S.  Should
		// not include S.Tokens alt in this case?
		String slave =
			"lexer grammar S;\n" +
			"A : 'a' {System.out.println(\"S.A\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"tree grammar M;\n" +
			"import S;\n" +
			"a : A ;";
		writeFile(tmpdir, "/M.g", master);

		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();

		assertEquals("unexpected errors: "+equeue, 1, equeue.errors.size());
		assertEquals("unexpected errors: "+equeue, 0, equeue.warnings.size());

		String expectedError =
			"error(161): "+tmpdir.toString().replaceFirst("\\-[0-9]+","")+"/M.g:2:8: tree grammar M cannot import lexer grammar S";
		assertEquals(expectedError, equeue.errors.get(0).toString().replaceFirst("\\-[0-9]+",""));
	}

	@Test public void testSyntacticPredicateRulesAreNotInherited() throws Exception {
		// if this compiles, it means that synpred1_S is defined in S.java
		// but not MParser.java.  MParser has its own synpred1_M which must
		// be separate to compile.
		String slave =
			"parser grammar S;\n" +
			"a : 'a' {System.out.println(\"S.a1\");}\n" +
			"  | 'a' {System.out.println(\"S.a2\");}\n" +
			"  ;\n" +
			"b : 'x' | 'y' {;} ;\n"; // preds generated but not need in DFA here
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"grammar M;\n" +
			"options {backtrack=true;}\n" +
			"import S;\n" +
			"start : a b ;\n" +
			"nonsense : 'q' | 'q' {;} ;" + // forces def of preds here in M
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "start", "ax", debug);
		assertEquals("S.a1\n", found);
	}

	@Test public void testKeywordVSIDGivesNoWarning() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"lexer grammar S;\n" +
			"A : 'abc' {System.out.println(\"S.A\");} ;\n" +
			"ID : 'a'..'z'+ ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"grammar M;\n" +
			"import S;\n" +
			"a : A {System.out.println(\"M.a\");} ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		String found = execParser("M.g", master, "MParser", "MLexer",
								  "a", "abc", debug);

		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());
		assertEquals("unexpected warnings: "+equeue, 0, equeue.warnings.size());

		assertEquals("S.A\nM.a\n", found);
	}

	@Test public void testWarningForUndefinedToken() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"lexer grammar S;\n" +
			"A : 'abc' {System.out.println(\"S.A\");} ;\n";
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);
		String master =
			"grammar M;\n" +
			"import S;\n" +
			"a : ABC A {System.out.println(\"M.a\");} ;\n" +
			"WS : (' '|'\\n') {skip();} ;\n" ;
		// A is defined in S but M should still see it and not give warning.
		// only problem is ABC.

		rawGenerateAndBuildRecognizer("M.g", master, "MParser", "MLexer", debug);

		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());
		assertEquals("unexpected warnings: "+equeue, 1, equeue.warnings.size());

		String expectedError =
			"warning(105): "+tmpdir.toString().replaceFirst("\\-[0-9]+","")+"/M.g:3:5: no lexer rule corresponding to token: ABC";
		assertEquals(expectedError, equeue.warnings.get(0).toString().replaceFirst("\\-[0-9]+",""));
	}

	/** Make sure that M can import S that imports T. */
	@Test public void test3LevelImport() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar T;\n" +
			"a : T ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "T.g", slave);
		String slave2 =
			"parser grammar S;\n" + // A, B, C token type order
			"import T;\n" +
			"a : S ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave2);

		String master =
			"grammar M;\n" +
			"import S;\n" +
			"a : M ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();
		g.composite.defineGrammarSymbols();

		String expectedTokenIDToTypeMap = "[M=6, S=5, T=4]";
		String expectedStringLiteralToTypeMap = "{}";
		String expectedTypeToTokenList = "[T, S, M]";

		assertEquals(expectedTokenIDToTypeMap,
					 realElements(g.composite.tokenIDToTypeMap).toString());
		assertEquals(expectedStringLiteralToTypeMap, g.composite.stringLiteralToTypeMap.toString());
		assertEquals(expectedTypeToTokenList,
					 realElements(g.composite.typeToTokenList).toString());

		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());

		boolean ok =
			rawGenerateAndBuildRecognizer("M.g", master, "MParser", null, false);
		boolean expecting = true; // should be ok
		assertEquals(expecting, ok);
	}

	@Test public void testBigTreeOfImports() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar T;\n" +
			"x : T ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "T.g", slave);
		slave =
			"parser grammar S;\n" +
			"import T;\n" +
			"y : S ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave);

		slave =
			"parser grammar C;\n" +
			"i : C ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "C.g", slave);
		slave =
			"parser grammar B;\n" +
			"j : B ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "B.g", slave);
		slave =
			"parser grammar A;\n" +
			"import B,C;\n" +
			"k : A ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "A.g", slave);

		String master =
			"grammar M;\n" +
			"import S,A;\n" +
			"a : M ;\n" ;
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();
		g.composite.defineGrammarSymbols();

		String expectedTokenIDToTypeMap = "[A=8, B=6, C=7, M=9, S=5, T=4]";
		String expectedStringLiteralToTypeMap = "{}";
		String expectedTypeToTokenList = "[T, S, B, C, A, M]";

		assertEquals(expectedTokenIDToTypeMap,
					 realElements(g.composite.tokenIDToTypeMap).toString());
		assertEquals(expectedStringLiteralToTypeMap, g.composite.stringLiteralToTypeMap.toString());
		assertEquals(expectedTypeToTokenList,
					 realElements(g.composite.typeToTokenList).toString());

		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());

		boolean ok =
			rawGenerateAndBuildRecognizer("M.g", master, "MParser", null, false);
		boolean expecting = true; // should be ok
		assertEquals(expecting, ok);
	}

	@Test public void testRulesVisibleThroughMultilevelImport() throws Exception {
		ErrorQueue equeue = new ErrorQueue();
		ErrorManager.setErrorListener(equeue);
		String slave =
			"parser grammar T;\n" +
			"x : T ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "T.g", slave);
		String slave2 =
			"parser grammar S;\n" + // A, B, C token type order
			"import T;\n" +
			"a : S ;\n" ;
		mkdir(tmpdir);
		writeFile(tmpdir, "S.g", slave2);

		String master =
			"grammar M;\n" +
			"import S;\n" +
			"a : M x ;\n" ; // x MUST BE VISIBLE TO M
		writeFile(tmpdir, "M.g", master);
		Tool antlr = newTool(new String[] {"-lib", tmpdir});
		CompositeGrammar composite = new CompositeGrammar();
		Grammar g = new Grammar(antlr,tmpdir+"/M.g",composite);
		composite.setDelegationRoot(g);
		g.parseAndBuildAST();
		g.composite.assignTokenTypes();
		g.composite.defineGrammarSymbols();

		String expectedTokenIDToTypeMap = "[M=6, S=5, T=4]";
		String expectedStringLiteralToTypeMap = "{}";
		String expectedTypeToTokenList = "[T, S, M]";

		assertEquals(expectedTokenIDToTypeMap,
					 realElements(g.composite.tokenIDToTypeMap).toString());
		assertEquals(expectedStringLiteralToTypeMap, g.composite.stringLiteralToTypeMap.toString());
		assertEquals(expectedTypeToTokenList,
					 realElements(g.composite.typeToTokenList).toString());

		assertEquals("unexpected errors: "+equeue, 0, equeue.errors.size());
	}

}