/*
[The "BSD licence"]
Copyright (c) 2005-2006 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.ErrorManager;
import org.antlr.tool.GrammarSemanticsMessage;
import org.antlr.tool.Message;
import org.antlr.tool.Grammar;
import org.antlr.Tool;
import org.antlr.codegen.CodeGenerator;
public class TestRewriteAST extends BaseTest {
protected boolean debug = false;
public void testDelete() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID INT -> ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc 34", debug);
assertEquals("", found);
}
public void testSingleToken() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID -> ID;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("abc\n", found);
}
public void testSingleTokenToNewNode() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID -> ID[\"x\"];\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("x\n", found);
}
public void testSingleTokenToNewNodeRoot() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID -> ^(ID[\"x\"] INT);\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("(x INT)\n", found);
}
public void testSingleTokenToNewNode2() throws Exception {
// currently this Fails. Allow creation of new nodes w/o args.
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID -> ID[ ];\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("abc\n", found);
}
public void testSingleCharLiteral() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : 'c' -> 'c';\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "c", debug);
assertEquals("c\n", found);
}
public void testSingleStringLiteral() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : 'ick' -> 'ick';\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "ick", debug);
assertEquals("ick\n", found);
}
public void testSingleRule() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : b -> b;\n" +
"b : ID ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("abc\n", found);
}
public void testReorderTokens() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID INT -> INT ID;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc 34", debug);
assertEquals("34 abc\n", found);
}
public void testReorderTokenAndRule() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : b INT -> INT b;\n" +
"b : ID ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc 34", debug);
assertEquals("34 abc\n", found);
}
public void testTokenTree() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID INT -> ^(INT ID);\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc 34", debug);
assertEquals("(34 abc)\n", found);
}
public void testTokenTreeAfterOtherStuff() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : 'void' ID INT -> 'void' ^(INT ID);\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "void abc 34", debug);
assertEquals("void (34 abc)\n", found);
}
public void testNestedTokenTreeWithOuterLoop() throws Exception {
// verify that ID and INT both iterate over outer index variable
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {DUH;}\n" +
"a : ID INT ID INT -> ^( DUH ID ^( DUH INT) )+ ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a 1 b 2", debug);
assertEquals("(DUH a (DUH 1)) (DUH b (DUH 2))\n", found);
}
public void testOptionalSingleToken() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID -> ID? ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("abc\n", found);
}
public void testClosureSingleToken() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID ID -> ID* ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testPositiveClosureSingleToken() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID ID -> ID+ ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testOptionalSingleRule() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : b -> b?;\n" +
"b : ID ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("abc\n", found);
}
public void testClosureSingleRule() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : b b -> b*;\n" +
"b : ID ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testClosureOfLabel() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : x+=b x+=b -> $x*;\n" +
"b : ID ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testOptionalLabelNoListLabel() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : (x=ID)? -> $x?;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a", debug);
assertEquals("a\n", found);
}
public void testPositiveClosureSingleRule() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : b b -> b+;\n" +
"b : ID ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testSinglePredicateT() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID -> {true}? ID -> ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("abc\n", found);
}
public void testSinglePredicateF() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID -> {false}? ID -> ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "abc", debug);
assertEquals("", found);
}
public void testMultiplePredicate() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID INT -> {false}? ID\n" +
" -> {true}? INT\n" +
" -> \n" +
" ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a 2", debug);
assertEquals("2\n", found);
}
public void testMultiplePredicateTrees() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID INT -> {false}? ^(ID INT)\n" +
" -> {true}? ^(INT ID)\n" +
" -> ID\n" +
" ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a 2", debug);
assertEquals("(2 a)\n", found);
}
public void testSimpleTree() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : op INT -> ^(op INT);\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "-34", debug);
assertEquals("(- 34)\n", found);
}
public void testSimpleTree2() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : op INT -> ^(INT op);\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "+ 34", debug);
assertEquals("(34 +)\n", found);
}
public void testNestedTrees() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : 'var' (ID ':' type ';')+ -> ^('var' ^(':' ID type)+) ;\n" +
"type : 'int' | 'float' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "var a:int; b:float;", debug);
assertEquals("(var (: a int) (: b float))\n", found);
}
public void testImaginaryTokenCopy() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {VAR;}\n" +
"a : ID (',' ID)*-> ^(VAR ID)+ ;\n" +
"type : 'int' | 'float' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a,b,c", debug);
assertEquals("(VAR a) (VAR b) (VAR c)\n", found);
}
public void testTokenUnreferencedOnLeftButDefined() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {VAR;}\n" +
"a : b -> ID ;\n" +
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a", debug);
assertEquals("ID\n", found);
}
public void testImaginaryTokenCopySetText() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {VAR;}\n" +
"a : ID (',' ID)*-> ^(VAR[\"var\"] ID)+ ;\n" +
"type : 'int' | 'float' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a,b,c", debug);
assertEquals("(var a) (var b) (var c)\n", found);
}
public void testImaginaryTokenNoCopyFromToken() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : lc='{' ID+ '}' -> ^(BLOCK[$lc] ID+) ;\n" +
"type : 'int' | 'float' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "{a b c}", debug);
assertEquals("({ a b c)\n", found);
}
public void testImaginaryTokenNoCopyFromTokenSetText() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : lc='{' ID+ '}' -> ^(BLOCK[$lc,\"block\"] ID+) ;\n" +
"type : 'int' | 'float' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "{a b c}", debug);
assertEquals("(block a b c)\n", found);
}
public void testMixedRewriteAndAutoAST() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : b b^ ;\n" + // 2nd b matches only an INT; can make it root
"b : ID INT -> INT ID\n" +
" | INT\n" +
" ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a 1 2", debug);
assertEquals("(2 1 a)\n", found);
}
public void testSubruleWithRewrite() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : b b ;\n" +
"b : (ID INT -> INT ID | INT INT -> INT+ )\n" +
" ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a 1 2 3", debug);
assertEquals("1 a 2 3\n", found);
}
public void testSubruleWithRewrite2() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {TYPE;}\n" +
"a : b b ;\n" +
"b : 'int'\n" +
" ( ID -> ^(TYPE 'int' ID)\n" +
" | ID '=' INT -> ^(TYPE 'int' ID INT)\n" +
" )\n" +
" ';'\n" +
" ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "int a; int b=3;", debug);
assertEquals("(TYPE int a) (TYPE int b 3)\n", found);
}
public void testNestedRewriteShutsOffAutoAST() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : b b ;\n" +
"b : ID ( ID (last=ID -> $last)+ ) ';'\n" + // get last ID
" | INT\n" + // should still get auto AST construction
" ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b c d; 42", debug);
assertEquals("d 42\n", found);
}
public void testRewriteActions() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : atom -> ^({adaptor.create(INT,\"9\")} atom) ;\n" +
"atom : INT ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "3", debug);
assertEquals("(9 3)\n", found);
}
public void testRewriteActions2() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : atom -> {adaptor.create(INT,\"9\")} atom ;\n" +
"atom : INT ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "3", debug);
assertEquals("9 3\n", found);
}
public void testRefToOldValue() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : (atom -> atom) (op='+' r=atom -> ^($op $a $r) )* ;\n" +
"atom : INT ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "3+4+5", debug);
assertEquals("(+ (+ 3 4) 5)\n", found);
}
public void testCopySemanticsForRules() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : atom -> ^(atom atom) ;\n" + // NOT CYCLE! (dup atom)
"atom : INT ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "3", debug);
assertEquals("(3 3)\n", found);
}
public void testCopySemanticsForRules2() throws Exception {
// copy type as a root for each invocation of (...)+ in rewrite
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : type ID (',' ID)* ';' -> ^(type ID)+ ;\n" +
"type : 'int' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "int a,b,c;", debug);
assertEquals("(int a) (int b) (int c)\n", found);
}
public void testCopySemanticsForRules3() throws Exception {
// copy type *and* modifier even though it's optional
// for each invocation of (...)+ in rewrite
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : modifier? type ID (',' ID)* ';' -> ^(type modifier? ID)+ ;\n" +
"type : 'int' ;\n" +
"modifier : 'public' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "public int a,b,c;", debug);
assertEquals("(int public a) (int public b) (int public c)\n", found);
}
public void testCopySemanticsForRules3Double() throws Exception {
// copy type *and* modifier even though it's optional
// for each invocation of (...)+ in rewrite
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : modifier? type ID (',' ID)* ';' -> ^(type modifier? ID)+ ^(type modifier? ID)+ ;\n" +
"type : 'int' ;\n" +
"modifier : 'public' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "public int a,b,c;", debug);
assertEquals("(int public a) (int public b) (int public c) (int public a) (int public b) (int public c)\n", found);
}
public void testCopySemanticsForRules4() throws Exception {
// copy type *and* modifier even though it's optional
// for each invocation of (...)+ in rewrite
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {MOD;}\n" +
"a : modifier? type ID (',' ID)* ';' -> ^(type ^(MOD modifier)? ID)+ ;\n" +
"type : 'int' ;\n" +
"modifier : 'public' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "public int a,b,c;", debug);
assertEquals("(int (MOD public) a) (int (MOD public) b) (int (MOD public) c)\n", found);
}
public void testCopySemanticsLists() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {MOD;}\n" +
"a : ID (',' ID)* ';' -> ID+ ID+ ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a,b,c;", debug);
assertEquals("a b c a b c\n", found);
}
public void testCopyRuleLabel() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x=b -> $x $x;\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a", debug);
assertEquals("a a\n", found);
}
public void testCopyRuleLabel2() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x=b -> ^($x $x);\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a", debug);
assertEquals("(a a)\n", found);
}
public void testQueueingOfTokens() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : 'int' ID (',' ID)* ';' -> ^('int' ID+) ;\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "int a,b,c;", debug);
assertEquals("(int a b c)\n", found);
}
public void testCopyOfTokens() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : 'int' ID ';' -> 'int' ID 'int' ID ;\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "int a;", debug);
assertEquals("int a int a\n", found);
}
public void testTokenCopyInLoop() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : 'int' ID (',' ID)* ';' -> ^('int' ID)+ ;\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "int a,b,c;", debug);
assertEquals("(int a) (int b) (int c)\n", found);
}
public void testTokenCopyInLoopAgainstTwoOthers() throws Exception {
// must smear 'int' copies across as root of multiple trees
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : 'int' ID ':' INT (',' ID ':' INT)* ';' -> ^('int' ID INT)+ ;\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "int a:1,b:2,c:3;", debug);
assertEquals("(int a 1) (int b 2) (int c 3)\n", found);
}
public void testListRefdOneAtATime() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID+ -> ID ID ID ;\n" + // works if 3 input IDs
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b c", debug);
assertEquals("a b c\n", found);
}
public void testSplitListWithLabels() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {VAR;}\n"+
"a : first=ID others+=ID* -> $first VAR $others+ ;\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b c", debug);
assertEquals("a VAR b c\n", found);
}
public void testComplicatedMelange() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : A A b=B B b=B c+=C C c+=C D {$D.text;} -> A+ B+ C+ D ;\n" +
"type : 'int' | 'float' ;\n" +
"A : 'a' ;\n" +
"B : 'b' ;\n" +
"C : 'c' ;\n" +
"D : 'd' ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a a b b b c c c d", debug);
assertEquals("a a b b b c c c d\n", found);
}
public void testRuleLabel() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x=b -> $x;\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a", debug);
assertEquals("a\n", found);
}
public void testRuleListLabel() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x+=b x+=b -> $x+;\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testRuleListLabel2() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x+=b x+=b -> $x $x*;\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testOptional() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x=b (y=b)? -> $x $y?;\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a", debug);
assertEquals("a\n", found);
}
public void testOptional2() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x=ID (y=b)? -> $x $y?;\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testOptional3() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x=ID (y=b)? -> ($x $y)?;\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testOptional4() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x+=ID (y=b)? -> ($x $y)?;\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
assertEquals("a b\n", found);
}
public void testOptional5() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : ID -> ID? ;\n"+ // match an ID to optional ID
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a", debug);
assertEquals("a\n", found);
}
public void testArbitraryExprType() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : x+=b x+=b -> {new CommonTree()};\n"+
"b : ID ;\n"+
"ID : 'a'..'z'+ ;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
//assertEquals("[not sure what this should be!]\n", found);
//ATTENTION: I changed this one's behavior from the above. Is it right?
assertEquals("nil\n", found);
}
public void testSet() throws Exception {
String grammar =
"grammar T;\n" +
"options { output = AST; } \n" +
"a: (INT|ID)+ -> INT+ ID+ ;\n" +
"INT: '0'..'9'+;\n" +
"ID : 'a'..'z'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"a", "2 a 34 de", debug);
assertEquals("2 34 a de\n", found);
}
public void testRewriteAction() throws Exception {
String grammar =
"grammar T; \n" +
"options { output = AST; }\n" +
"tokens { FLOAT; }\n" +
"r\n" +
" : INT -> {new CommonTree(new CommonToken(FLOAT,$INT.text+\".0\"))} \n" +
" ; \n" +
"INT : '0'..'9'+; \n" +
"WS: (' ' | '\\n' | '\\t')+ {$channel = HIDDEN;}; \n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"r", "25", debug);
assertEquals("25.0\n", found);
}
public void testOptionalSubruleWithoutRealElements() throws Exception {
// copy type *and* modifier even though it's optional
// for each invocation of (...)+ in rewrite
String grammar =
"grammar T;\n" +
"options {output=AST;} \n" +
"tokens {PARMS;} \n" +
"\n" +
"modulo \n" +
" : 'modulo' ID ('(' parms+ ')')? -> ^('modulo' ID ^(PARMS parms+)?) \n" +
" ; \n" +
"parms : '#'|ID; \n" +
"ID : ('a'..'z' | 'A'..'Z')+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
String found = execParser("T.g", grammar, "TParser", "TLexer",
"modulo", "modulo abc (x y #)", debug);
assertEquals("(modulo abc (PARMS x y #))\n", found);
}
// C A R D I N A L I T Y I S S U E S
public void testCardinality() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"tokens {BLOCK;}\n" +
"a : ID ID INT INT INT -> (ID INT)+;\n"+
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+; \n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b 3 4 5", debug);
String expecting =
"org.antlr.runtime.tree.RewriteCardinalityException: token ID";
String found = getFirstLineOfException();
assertEquals(expecting, found);
}
public void testCardinality2() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID+ -> ID ID ID ;\n" + // only 2 input IDs
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
execParser("T.g", grammar, "TParser", "TLexer",
"a", "a b", debug);
String expecting =
"org.antlr.runtime.tree.RewriteCardinalityException: token ID";
String found = getFirstLineOfException();
assertEquals(expecting, found);
}
public void testCardinality3() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID? INT -> ID INT ;\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
execParser("T.g", grammar, "TParser", "TLexer",
"a", "3", debug);
String expecting =
"org.antlr.runtime.tree.RewriteEmptyStreamException: token ID";
String found = getFirstLineOfException();
assertEquals(expecting, found);
}
public void testLoopCardinality() throws Exception {
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : ID? INT -> ID+ INT ;\n" +
"op : '+'|'-' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
execParser("T.g", grammar, "TParser", "TLexer",
"a", "3", debug);
String expecting =
"org.antlr.runtime.tree.RewriteEarlyExitException";
String found = getFirstLineOfException();
assertEquals(expecting, found);
}
// E R R O R S
public void testUnknownRule() throws Exception {
ErrorQueue equeue = new ErrorQueue();
ErrorManager.setErrorListener(equeue);
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : INT -> ugh ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
Grammar g = new Grammar(grammar);
Tool antlr = newTool();
antlr.setOutputDirectory(null); // write to /dev/null
CodeGenerator generator = new CodeGenerator(antlr, g, "Java");
g.setCodeGenerator(generator);
generator.genRecognizer();
int expectedMsgID = ErrorManager.MSG_UNDEFINED_RULE_REF;
Object expectedArg = "ugh";
Object expectedArg2 = null;
GrammarSemanticsMessage expectedMessage =
new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg, expectedArg2);
checkError(equeue, expectedMessage);
}
public void testKnownRuleButNotInLHS() throws Exception {
ErrorQueue equeue = new ErrorQueue();
ErrorManager.setErrorListener(equeue);
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : INT -> b ;\n" +
"b : 'b' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
Grammar g = new Grammar(grammar);
Tool antlr = newTool();
antlr.setOutputDirectory(null); // write to /dev/null
CodeGenerator generator = new CodeGenerator(antlr, g, "Java");
g.setCodeGenerator(generator);
generator.genRecognizer();
int expectedMsgID = ErrorManager.MSG_REWRITE_ELEMENT_NOT_PRESENT_ON_LHS;
Object expectedArg = "b";
Object expectedArg2 = null;
GrammarSemanticsMessage expectedMessage =
new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg, expectedArg2);
checkError(equeue, expectedMessage);
}
public void testUnknownToken() throws Exception {
ErrorQueue equeue = new ErrorQueue();
ErrorManager.setErrorListener(equeue);
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : INT -> ICK ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
Grammar g = new Grammar(grammar);
Tool antlr = newTool();
antlr.setOutputDirectory(null); // write to /dev/null
CodeGenerator generator = new CodeGenerator(antlr, g, "Java");
g.setCodeGenerator(generator);
generator.genRecognizer();
int expectedMsgID = ErrorManager.MSG_UNDEFINED_TOKEN_REF_IN_REWRITE;
Object expectedArg = "ICK";
Object expectedArg2 = null;
GrammarSemanticsMessage expectedMessage =
new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg, expectedArg2);
checkError(equeue, expectedMessage);
}
public void testUnknownLabel() throws Exception {
ErrorQueue equeue = new ErrorQueue();
ErrorManager.setErrorListener(equeue);
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : INT -> $foo ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
Grammar g = new Grammar(grammar);
Tool antlr = newTool();
antlr.setOutputDirectory(null); // write to /dev/null
CodeGenerator generator = new CodeGenerator(antlr, g, "Java");
g.setCodeGenerator(generator);
generator.genRecognizer();
int expectedMsgID = ErrorManager.MSG_UNDEFINED_LABEL_REF_IN_REWRITE;
Object expectedArg = "foo";
Object expectedArg2 = null;
GrammarSemanticsMessage expectedMessage =
new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg, expectedArg2);
checkError(equeue, expectedMessage);
}
public void testUnknownCharLiteralToken() throws Exception {
ErrorQueue equeue = new ErrorQueue();
ErrorManager.setErrorListener(equeue);
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : INT -> 'a' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
Grammar g = new Grammar(grammar);
Tool antlr = newTool();
antlr.setOutputDirectory(null); // write to /dev/null
CodeGenerator generator = new CodeGenerator(antlr, g, "Java");
g.setCodeGenerator(generator);
generator.genRecognizer();
int expectedMsgID = ErrorManager.MSG_UNDEFINED_TOKEN_REF_IN_REWRITE;
Object expectedArg = "'a'";
Object expectedArg2 = null;
GrammarSemanticsMessage expectedMessage =
new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg, expectedArg2);
checkError(equeue, expectedMessage);
}
public void testUnknownStringLiteralToken() throws Exception {
ErrorQueue equeue = new ErrorQueue();
ErrorManager.setErrorListener(equeue);
String grammar =
"grammar T;\n" +
"options {output=AST;}\n" +
"a : INT -> 'foo' ;\n" +
"ID : 'a'..'z'+ ;\n" +
"INT : '0'..'9'+;\n" +
"WS : (' '|'\\n') {$channel=HIDDEN;} ;\n";
Grammar g = new Grammar(grammar);
Tool antlr = newTool();
antlr.setOutputDirectory(null); // write to /dev/null
CodeGenerator generator = new CodeGenerator(antlr, g, "Java");
g.setCodeGenerator(generator);
generator.genRecognizer();
int expectedMsgID = ErrorManager.MSG_UNDEFINED_TOKEN_REF_IN_REWRITE;
Object expectedArg = "'foo'";
Object expectedArg2 = null;
GrammarSemanticsMessage expectedMessage =
new GrammarSemanticsMessage(expectedMsgID, g, null, expectedArg, expectedArg2);
checkError(equeue, expectedMessage);
}
// S U P P O R T
protected void checkError(ErrorQueue equeue,
GrammarSemanticsMessage expectedMessage)
throws Exception
{
//System.out.println("errors="+equeue);
Message foundMsg = null;
for (int i = 0; i < equeue.errors.size(); i++) {
Message m = (Message)equeue.errors.get(i);
if (m.msgID==expectedMessage.msgID ) {
foundMsg = m;
}
}
assertTrue("no error; "+expectedMessage.msgID+" expected", equeue.errors.size()>0);
assertTrue("too many errors; "+equeue.errors, equeue.errors.size()<=1);
assertNotNull("couldn't find expected error: "+expectedMessage.msgID, foundMsg);
assertTrue("error is not a GrammarSemanticsMessage",
foundMsg instanceof GrammarSemanticsMessage);
assertEquals(expectedMessage.arg, foundMsg.arg);
assertEquals(expectedMessage.arg2, foundMsg.arg2);
ErrorManager.resetErrorState(); // wack errors for next test
}
}