gUnit.g revision 324c4644fee44b9898524c09511bd33c3f12e2df
1/* 2[The "BSD licence"] 3Copyright (c) 2007-2008 Leon Jen-Yuan Su 4All rights reserved. 5 6Redistribution and use in source and binary forms, with or without 7modification, are permitted provided that the following conditions 8are met: 9 10 1. Redistributions of source code must retain the above copyright 11 notice, this list of conditions and the following disclaimer. 12 2. Redistributions in binary form must reproduce the above copyright 13 notice, this list of conditions and the following disclaimer in the 14 documentation and/or other materials provided with the distribution. 15 3. The name of the author may not be used to endorse or promote products 16 derived from this software without specific prior written permission. 17 18THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28*/ 29grammar gUnit; 30options {language=Java;} 31tokens { 32 OK = 'OK'; 33 FAIL = 'FAIL'; 34 DOC_COMMENT; 35} 36@header {package org.antlr.gunit;} 37@lexer::header { 38package org.antlr.gunit; 39import java.io.BufferedReader; 40import java.io.IOException; 41import java.io.StringReader; 42} 43@members { 44public GrammarInfo grammarInfo; 45public gUnitParser(TokenStream input, GrammarInfo grammarInfo) { 46 super(input); 47 this.grammarInfo = grammarInfo; 48} 49} 50 51gUnitDef: 'gunit' g1=id ('walks' g2=id)? ';' 52 { 53 if ( $g2.text!=null ) { 54 grammarInfo.setGrammarName($g2.text); 55 grammarInfo.setTreeGrammarName($g1.text); 56 } 57 else { 58 grammarInfo.setGrammarName($g1.text); 59 } 60 } 61 optionsSpec? header? testsuite* 62 ; 63 64optionsSpec 65 : OPTIONS (option ';')+ '}' 66 ; 67 68// Note: currently, this is the only valid option for setting customized tree adaptor 69option : id '=' treeAdaptor 70 { 71 if ( $id.text.equals("TreeAdaptor") ) { 72 grammarInfo.setAdaptor($treeAdaptor.text); 73 } 74 // TODO: need a better error logging strategy 75 else System.err.println("Invalid option detected: "+$text); 76 } 77 ; 78 79treeAdaptor 80 : id EXT* 81 ; 82 83header : '@header' ACTION 84 { 85 int pos1, pos2; 86 if ( (pos1=$ACTION.text.indexOf("package"))!=-1 && (pos2=$ACTION.text.indexOf(';'))!=-1 ) { 87 grammarInfo.setGrammarPackage($ACTION.text.substring(pos1+8, pos2).trim()); // substring the package path 88 } 89 else { 90 System.err.println("error(line "+$ACTION.getLine()+"): invalid header"); 91 } 92 } 93 ; 94 95testsuite // gUnit test suite based on individual rule 96scope { 97boolean isLexicalRule; 98} 99@init { 100gUnitTestSuite ts = null; 101$testsuite::isLexicalRule = false; 102} 103 : ( r1=RULE_REF ('walks' r2=RULE_REF)? 104 { 105 if ( $r2==null ) ts = new gUnitTestSuite($r1.text); 106 else ts = new gUnitTestSuite($r1.text, $r2.text); 107 } 108 | t=TOKEN_REF 109 { 110 ts = new gUnitTestSuite(); 111 ts.setLexicalRuleName($t.text); 112 $testsuite::isLexicalRule = true; 113 } 114 ) 115 ':' 116 testcase[ts]+ {grammarInfo.addRuleTestSuite(ts);} 117 ; 118 119// TODO : currently gUnit just ignores illegal test for lexer rule, but should also emit a reminding message 120testcase[gUnitTestSuite ts] // individual test within a (rule)testsuite 121 : input expect {$ts.addTestCase($input.in, $expect.out);} 122 ; 123 124input returns [gUnitTestInput in] 125@init { 126String testInput = null; 127boolean inputIsFile = false; 128int line = -1; 129} 130@after { 131in = new gUnitTestInput(testInput, inputIsFile, line); 132} 133 : STRING 134 { 135 testInput = $STRING.text.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t") 136 .replace("\\b", "\b").replace("\\f", "\f").replace("\\\"", "\"").replace("\\'", "\'").replace("\\\\", "\\"); 137 line = $STRING.line; 138 } 139 | ML_STRING 140 { 141 testInput = $ML_STRING.text; 142 line = $ML_STRING.line; 143 } 144 | file 145 { 146 testInput = $file.text; 147 inputIsFile = true; 148 line = $file.line; 149 } 150 ; 151 152expect returns [AbstractTest out] 153 : OK {$out = new BooleanTest(true);} 154 | FAIL {$out = new BooleanTest(false);} 155 | 'returns' RETVAL {if ( !$testsuite::isLexicalRule ) $out = new ReturnTest($RETVAL);} 156 | '->' output {if ( !$testsuite::isLexicalRule ) $out = new OutputTest($output.token);} 157 ; 158 159output returns [Token token] 160 : STRING 161 { 162 $STRING.setText($STRING.text.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t") 163 .replace("\\b", "\b").replace("\\f", "\f").replace("\\\"", "\"").replace("\\'", "\'").replace("\\\\", "\\")); 164 $token = $STRING; 165 } 166 | ML_STRING {$token = $ML_STRING;} 167 | AST {$token = $AST;} 168 | ACTION {$token = $ACTION;} 169 ; 170 171file returns [int line] 172 : id EXT? {$line = $id.line;} 173 ; 174 175id returns [int line] 176 : TOKEN_REF {$line = $TOKEN_REF.line;} 177 | RULE_REF {$line = $RULE_REF.line;} 178 ; 179 180// L E X I C A L R U L E S 181 182SL_COMMENT 183 : '//' ~('\r'|'\n')* '\r'? '\n' {$channel=HIDDEN;} 184 ; 185 186ML_COMMENT 187 : '/*' {$channel=HIDDEN;} .* '*/' 188 ; 189 190STRING : '"' ( ESC | ~('\\'|'"') )* '"' {setText(getText().substring(1, getText().length()-1));} 191 ; 192 193ML_STRING 194 : {// we need to determine the number of spaces or tabs (indentation) for multi-line input 195 StringBuffer buf = new StringBuffer(); 196 int i = -1; 197 int c = input.LA(-1); 198 while ( c==' ' || c=='\t' ) { 199 buf.append((char)c); 200 c = input.LA(--i); 201 } 202 String indentation = buf.reverse().toString(); 203 } 204 '<<' .* '>>' 205 {// also determine the appropriate newline separator and get info of the first and last 2 characters (exclude '<<' and '>>') 206 String newline = System.getProperty("line.separator"); 207 String front, end; 208 int oldFrontIndex = 2; 209 int oldEndIndex = getText().length()-2; 210 int newFrontIndex, newEndIndex; 211 if ( newline.length()==1 ) { 212 front = getText().substring(2, 3); 213 end = getText().substring(getText().length()-3, getText().length()-2); 214 newFrontIndex = 3; 215 newEndIndex = getText().length()-3; 216 } 217 else {// must be 2, e.g. Windows System which uses \r\n as a line separator 218 front = getText().substring(2, 4); 219 end = getText().substring(getText().length()-4, getText().length()-2); 220 newFrontIndex = 4; 221 newEndIndex = getText().length()-4; 222 } 223 // strip unwanted characters, e.g. '<<' (including a newline after it) or '>>' (including a newline before it) 224 String temp = null; 225 if ( front.equals(newline) && end.equals(newline) ) { 226 // need to handle the special case: <<\n>> or <<\r\n>> 227 if ( newline.length()==1 && getText().length()==5 ) temp = ""; 228 else if ( newline.length()==2 && getText().length()==6 ) temp = ""; 229 else temp = getText().substring(newFrontIndex, newEndIndex); 230 } 231 else if ( front.equals(newline) ) { 232 temp = getText().substring(newFrontIndex, oldEndIndex); 233 } 234 else if ( end.equals(newline) ) { 235 temp = getText().substring(oldFrontIndex, newEndIndex); 236 } 237 else { 238 temp = getText().substring(oldFrontIndex, oldEndIndex); 239 } 240 // finally we need to prpcess the indentation line by line 241 BufferedReader bufReader = new BufferedReader(new StringReader(temp)); 242 buf = new StringBuffer(); 243 String line = null; 244 int count = 0; 245 try { 246 while((line = bufReader.readLine()) != null) { 247 if ( line.startsWith(indentation) ) line = line.substring(indentation.length()); 248 if ( count>0 ) buf.append(newline); 249 buf.append(line); 250 count++; 251 } 252 setText(buf.toString()); 253 } 254 catch (IOException ioe) { 255 setText(temp); 256 } 257 } 258 ; 259 260TOKEN_REF 261 : 'A'..'Z' ('a'..'z'|'A'..'Z'|'_'|'0'..'9')* 262 ; 263 264RULE_REF 265 : 'a'..'z' ('a'..'z'|'A'..'Z'|'_'|'0'..'9')* 266 ; 267 268EXT : '.'('a'..'z'|'A'..'Z'|'0'..'9')+; 269 270RETVAL : NESTED_RETVAL {setText(getText().substring(1, getText().length()-1));} 271 ; 272 273fragment 274NESTED_RETVAL : 275 '[' 276 ( options {greedy=false;} 277 : NESTED_RETVAL 278 | . 279 )* 280 ']' 281 ; 282 283AST : NESTED_AST (' '? NESTED_AST)*; 284 285fragment 286NESTED_AST : 287 '(' 288 ( NESTED_AST 289 | STRING_LITERAL 290 | ~('('|')'|'"') 291 )* 292 ')' 293 ; 294 295OPTIONS : 'options' WS* '{' 296 ; 297 298ACTION 299 : NESTED_ACTION {setText(getText().substring(1, getText().length()-1));} 300 ; 301 302fragment 303NESTED_ACTION : 304 '{' 305 ( options {greedy=false; k=3;} 306 : NESTED_ACTION 307 | STRING_LITERAL 308 | CHAR_LITERAL 309 | . 310 )* 311 '}' 312 ; 313 314fragment 315CHAR_LITERAL 316 : '\'' ( ESC | ~('\''|'\\') ) '\'' 317 ; 318 319fragment 320STRING_LITERAL 321 : '"' ( ESC | ~('\\'|'"') )* '"' 322 ; 323 324fragment 325ESC : '\\' 326 ( 'n' 327 | 'r' 328 | 't' 329 | 'b' 330 | 'f' 331 | '"' 332 | '\'' 333 | '\\' 334 | '>' 335 | 'u' XDIGIT XDIGIT XDIGIT XDIGIT 336 | . // unknown, leave as it is 337 ) 338 ; 339 340fragment 341XDIGIT : 342 '0' .. '9' 343 | 'a' .. 'f' 344 | 'A' .. 'F' 345 ; 346 347WS : ( ' ' 348 | '\t' 349 | '\r'? '\n' 350 )+ 351 {$channel=HIDDEN;} 352 ; 353