1 /* 2 [The "BSD licence"] 3 Copyright (c) 2007-2008 Leon Jen-Yuan Su 4 All rights reserved. 5 6 Redistribution and use in source and binary forms, with or without 7 modification, are permitted provided that the following conditions 8 are met: 9 1. Redistributions of source code must retain the above copyright 10 notice, this list of conditions and the following disclaimer. 11 2. Redistributions in binary form must reproduce the above copyright 12 notice, this list of conditions and the following disclaimer in the 13 documentation and/or other materials provided with the distribution. 14 3. The name of the author may not be used to endorse or promote products 15 derived from this software without specific prior written permission. 16 17 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 package org.antlr.gunit; 29 30 import org.antlr.stringtemplate.StringTemplate; 31 import org.antlr.stringtemplate.StringTemplateGroup; 32 import org.antlr.stringtemplate.StringTemplateGroupLoader; 33 import org.antlr.stringtemplate.CommonGroupLoader; 34 import org.antlr.stringtemplate.language.AngleBracketTemplateLexer; 35 36 import java.io.*; 37 import java.lang.reflect.Method; 38 import java.util.ArrayList; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.logging.ConsoleHandler; 43 import java.util.logging.Handler; 44 import java.util.logging.Level; 45 import java.util.logging.Logger; 46 47 public class JUnitCodeGen { 48 public GrammarInfo grammarInfo; 49 public Map<String, String> ruleWithReturn; 50 private final String testsuiteDir; 51 private String outputDirectoryPath = "."; 52 53 private final static Handler console = new ConsoleHandler(); 54 private static final Logger logger = Logger.getLogger(JUnitCodeGen.class.getName()); 55 static { 56 logger.addHandler(console); 57 } 58 JUnitCodeGen(GrammarInfo grammarInfo, String testsuiteDir)59 public JUnitCodeGen(GrammarInfo grammarInfo, String testsuiteDir) throws ClassNotFoundException { 60 this( grammarInfo, determineClassLoader(), testsuiteDir); 61 } 62 determineClassLoader()63 private static ClassLoader determineClassLoader() { 64 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 65 if ( classLoader == null ) { 66 classLoader = JUnitCodeGen.class.getClassLoader(); 67 } 68 return classLoader; 69 } 70 JUnitCodeGen(GrammarInfo grammarInfo, ClassLoader classLoader, String testsuiteDir)71 public JUnitCodeGen(GrammarInfo grammarInfo, ClassLoader classLoader, String testsuiteDir) throws ClassNotFoundException { 72 this.grammarInfo = grammarInfo; 73 this.testsuiteDir = testsuiteDir; 74 /** Map the name of rules having return value to its return type */ 75 ruleWithReturn = new HashMap<String, String>(); 76 Class<?> parserClass = locateParserClass( grammarInfo, classLoader ); 77 Method[] methods = parserClass.getDeclaredMethods(); 78 for(Method method : methods) { 79 if ( !method.getReturnType().getName().equals("void") ) { 80 ruleWithReturn.put(method.getName(), method.getReturnType().getName().replace('$', '.')); 81 } 82 } 83 } 84 locateParserClass(GrammarInfo grammarInfo, ClassLoader classLoader)85 private Class<?> locateParserClass(GrammarInfo grammarInfo, ClassLoader classLoader) throws ClassNotFoundException { 86 String parserClassName = grammarInfo.getGrammarName() + "Parser"; 87 if ( grammarInfo.getGrammarPackage() != null ) { 88 parserClassName = grammarInfo.getGrammarPackage()+ "." + parserClassName; 89 } 90 return classLoader.loadClass( parserClassName ); 91 } 92 getOutputDirectoryPath()93 public String getOutputDirectoryPath() { 94 return outputDirectoryPath; 95 } 96 setOutputDirectoryPath(String outputDirectoryPath)97 public void setOutputDirectoryPath(String outputDirectoryPath) { 98 this.outputDirectoryPath = outputDirectoryPath; 99 } 100 compile()101 public void compile() throws IOException{ 102 String junitFileName; 103 if ( grammarInfo.getTreeGrammarName()!=null ) { 104 junitFileName = "Test"+grammarInfo.getTreeGrammarName(); 105 } 106 else { 107 junitFileName = "Test"+grammarInfo.getGrammarName(); 108 } 109 String lexerName = grammarInfo.getGrammarName()+"Lexer"; 110 String parserName = grammarInfo.getGrammarName()+"Parser"; 111 112 StringTemplateGroupLoader loader = new CommonGroupLoader("org/antlr/gunit", null); 113 StringTemplateGroup.registerGroupLoader(loader); 114 StringTemplateGroup.registerDefaultLexer(AngleBracketTemplateLexer.class); 115 StringBuffer buf = compileToBuffer(junitFileName, lexerName, parserName); 116 writeTestFile(".", junitFileName+".java", buf.toString()); 117 } 118 compileToBuffer(String className, String lexerName, String parserName)119 public StringBuffer compileToBuffer(String className, String lexerName, String parserName) { 120 StringTemplateGroup group = StringTemplateGroup.loadGroup("junit"); 121 StringBuffer buf = new StringBuffer(); 122 buf.append(genClassHeader(group, className, lexerName, parserName)); 123 buf.append(genTestRuleMethods(group)); 124 buf.append("\n\n}"); 125 return buf; 126 } 127 genClassHeader(StringTemplateGroup group, String junitFileName, String lexerName, String parserName)128 protected String genClassHeader(StringTemplateGroup group, String junitFileName, String lexerName, String parserName) { 129 StringTemplate classHeaderST = group.getInstanceOf("classHeader"); 130 if ( grammarInfo.getTestPackage()!=null ) { // Set up class package if there is 131 classHeaderST.setAttribute("header", "package "+grammarInfo.getTestPackage()+";"); 132 } 133 classHeaderST.setAttribute("junitFileName", junitFileName); 134 135 String lexerPath = null; 136 String parserPath = null; 137 String treeParserPath = null; 138 String packagePath = null; 139 boolean isTreeGrammar = false; 140 boolean hasPackage = false; 141 /** Set up appropriate class path for parser/tree parser if using package */ 142 if ( grammarInfo.getGrammarPackage()!=null ) { 143 hasPackage = true; 144 packagePath = "./"+grammarInfo.getGrammarPackage().replace('.', '/'); 145 lexerPath = grammarInfo.getGrammarPackage()+"."+lexerName; 146 parserPath = grammarInfo.getGrammarPackage()+"."+parserName; 147 if ( grammarInfo.getTreeGrammarName()!=null ) { 148 treeParserPath = grammarInfo.getGrammarPackage()+"."+grammarInfo.getTreeGrammarName(); 149 isTreeGrammar = true; 150 } 151 } 152 else { 153 lexerPath = lexerName; 154 parserPath = parserName; 155 if ( grammarInfo.getTreeGrammarName()!=null ) { 156 treeParserPath = grammarInfo.getTreeGrammarName(); 157 isTreeGrammar = true; 158 } 159 } 160 // also set up custom tree adaptor if necessary 161 String treeAdaptorPath = null; 162 boolean hasTreeAdaptor = false; 163 if ( grammarInfo.getAdaptor()!=null ) { 164 hasTreeAdaptor = true; 165 treeAdaptorPath = grammarInfo.getAdaptor(); 166 } 167 classHeaderST.setAttribute("hasTreeAdaptor", hasTreeAdaptor); 168 classHeaderST.setAttribute("treeAdaptorPath", treeAdaptorPath); 169 classHeaderST.setAttribute("hasPackage", hasPackage); 170 classHeaderST.setAttribute("packagePath", packagePath); 171 classHeaderST.setAttribute("lexerPath", lexerPath); 172 classHeaderST.setAttribute("parserPath", parserPath); 173 classHeaderST.setAttribute("treeParserPath", treeParserPath); 174 classHeaderST.setAttribute("isTreeGrammar", isTreeGrammar); 175 return classHeaderST.toString(); 176 } 177 genTestRuleMethods(StringTemplateGroup group)178 protected String genTestRuleMethods(StringTemplateGroup group) { 179 StringBuffer buf = new StringBuffer(); 180 if ( grammarInfo.getTreeGrammarName()!=null ) { // Generate junit codes of for tree grammar rule 181 genTreeMethods(group, buf); 182 } 183 else { // Generate junit codes of for grammar rule 184 genParserMethods(group, buf); 185 } 186 return buf.toString(); 187 } 188 genParserMethods(StringTemplateGroup group, StringBuffer buf)189 private void genParserMethods(StringTemplateGroup group, StringBuffer buf) { 190 for ( gUnitTestSuite ts: grammarInfo.getRuleTestSuites() ) { 191 int i = 0; 192 for ( Map.Entry<gUnitTestInput, AbstractTest> entry : ts.testSuites.entrySet() ) { // each rule may contain multiple tests 193 gUnitTestInput input = entry.getKey(); 194 i++; 195 StringTemplate testRuleMethodST; 196 /** If rule has multiple return values or ast*/ 197 if ( entry.getValue().getType()== gUnitParser.ACTION && ruleWithReturn.containsKey(ts.getRuleName()) ) { 198 testRuleMethodST = group.getInstanceOf("testRuleMethod2"); 199 String outputString = entry.getValue().getText(); 200 testRuleMethodST.setAttribute("methodName", "test"+changeFirstCapital(ts.getRuleName())+i); 201 testRuleMethodST.setAttribute("testRuleName", '"'+ts.getRuleName()+'"'); 202 testRuleMethodST.setAttribute("test", input); 203 testRuleMethodST.setAttribute("returnType", ruleWithReturn.get(ts.getRuleName())); 204 testRuleMethodST.setAttribute("expecting", outputString); 205 } 206 else { 207 String testRuleName; 208 // need to determine whether it's a test for parser rule or lexer rule 209 if ( ts.isLexicalRule() ) testRuleName = ts.getLexicalRuleName(); 210 else testRuleName = ts.getRuleName(); 211 testRuleMethodST = group.getInstanceOf("testRuleMethod"); 212 String outputString = entry.getValue().getText(); 213 testRuleMethodST.setAttribute("isLexicalRule", ts.isLexicalRule()); 214 testRuleMethodST.setAttribute("methodName", "test"+changeFirstCapital(testRuleName)+i); 215 testRuleMethodST.setAttribute("testRuleName", '"'+testRuleName+'"'); 216 testRuleMethodST.setAttribute("test", input); 217 testRuleMethodST.setAttribute("tokenType", getTypeString(entry.getValue().getType())); 218 219 // normalize whitespace 220 outputString = normalizeTreeSpec(outputString); 221 222 if ( entry.getValue().getType()==gUnitParser.ACTION ) { // trim ';' at the end of ACTION if there is... 223 //testRuleMethodST.setAttribute("expecting", outputString.substring(0, outputString.length()-1)); 224 testRuleMethodST.setAttribute("expecting", outputString); 225 } 226 else if ( entry.getValue().getType()==gUnitParser.RETVAL ) { // Expected: RETVAL 227 testRuleMethodST.setAttribute("expecting", outputString); 228 } 229 else { // Attach "" to expected STRING or AST 230 // strip newlines for (...) tree stuff 231 outputString = outputString.replaceAll("\n", ""); 232 testRuleMethodST.setAttribute("expecting", '"'+escapeForJava(outputString)+'"'); 233 } 234 } 235 buf.append(testRuleMethodST.toString()); 236 } 237 } 238 } 239 genTreeMethods(StringTemplateGroup group, StringBuffer buf)240 private void genTreeMethods(StringTemplateGroup group, StringBuffer buf) { 241 for ( gUnitTestSuite ts: grammarInfo.getRuleTestSuites() ) { 242 int i = 0; 243 for ( Map.Entry<gUnitTestInput, AbstractTest> entry : ts.testSuites.entrySet() ) { // each rule may contain multiple tests 244 gUnitTestInput input = entry.getKey(); 245 i++; 246 StringTemplate testRuleMethodST; 247 /** If rule has multiple return values or ast*/ 248 if ( entry.getValue().getType()== gUnitParser.ACTION && ruleWithReturn.containsKey(ts.getTreeRuleName()) ) { 249 testRuleMethodST = group.getInstanceOf("testTreeRuleMethod2"); 250 String outputString = entry.getValue().getText(); 251 testRuleMethodST.setAttribute("methodName", "test"+changeFirstCapital(ts.getTreeRuleName())+"_walks_"+ 252 changeFirstCapital(ts.getRuleName())+i); 253 testRuleMethodST.setAttribute("testTreeRuleName", '"'+ts.getTreeRuleName()+'"'); 254 testRuleMethodST.setAttribute("testRuleName", '"'+ts.getRuleName()+'"'); 255 testRuleMethodST.setAttribute("test", input); 256 testRuleMethodST.setAttribute("returnType", ruleWithReturn.get(ts.getTreeRuleName())); 257 testRuleMethodST.setAttribute("expecting", outputString); 258 } 259 else { 260 testRuleMethodST = group.getInstanceOf("testTreeRuleMethod"); 261 String outputString = entry.getValue().getText(); 262 testRuleMethodST.setAttribute("methodName", "test"+changeFirstCapital(ts.getTreeRuleName())+"_walks_"+ 263 changeFirstCapital(ts.getRuleName())+i); 264 testRuleMethodST.setAttribute("testTreeRuleName", '"'+ts.getTreeRuleName()+'"'); 265 testRuleMethodST.setAttribute("testRuleName", '"'+ts.getRuleName()+'"'); 266 testRuleMethodST.setAttribute("test", input); 267 testRuleMethodST.setAttribute("tokenType", getTypeString(entry.getValue().getType())); 268 269 if ( entry.getValue().getType()==gUnitParser.ACTION ) { // trim ';' at the end of ACTION if there is... 270 //testRuleMethodST.setAttribute("expecting", outputString.substring(0, outputString.length()-1)); 271 testRuleMethodST.setAttribute("expecting", outputString); 272 } 273 else if ( entry.getValue().getType()==gUnitParser.RETVAL ) { // Expected: RETVAL 274 testRuleMethodST.setAttribute("expecting", outputString); 275 } 276 else { // Attach "" to expected STRING or AST 277 testRuleMethodST.setAttribute("expecting", '"'+escapeForJava(outputString)+'"'); 278 } 279 } 280 buf.append(testRuleMethodST.toString()); 281 } 282 } 283 } 284 285 // return a meaningful gUnit token type name instead of using the magic number getTypeString(int type)286 public String getTypeString(int type) { 287 String typeText; 288 switch (type) { 289 case gUnitParser.OK : 290 typeText = "org.antlr.gunit.gUnitParser.OK"; 291 break; 292 case gUnitParser.FAIL : 293 typeText = "org.antlr.gunit.gUnitParser.FAIL"; 294 break; 295 case gUnitParser.STRING : 296 typeText = "org.antlr.gunit.gUnitParser.STRING"; 297 break; 298 case gUnitParser.ML_STRING : 299 typeText = "org.antlr.gunit.gUnitParser.ML_STRING"; 300 break; 301 case gUnitParser.RETVAL : 302 typeText = "org.antlr.gunit.gUnitParser.RETVAL"; 303 break; 304 case gUnitParser.AST : 305 typeText = "org.antlr.gunit.gUnitParser.AST"; 306 break; 307 default : 308 typeText = "org.antlr.gunit.gUnitParser.EOF"; 309 break; 310 } 311 return typeText; 312 } 313 writeTestFile(String dir, String fileName, String content)314 protected void writeTestFile(String dir, String fileName, String content) { 315 try { 316 File f = new File(dir, fileName); 317 FileWriter w = new FileWriter(f); 318 BufferedWriter bw = new BufferedWriter(w); 319 bw.write(content); 320 bw.close(); 321 w.close(); 322 } 323 catch (IOException ioe) { 324 logger.log(Level.SEVERE, "can't write file", ioe); 325 } 326 } 327 escapeForJava(String inputString)328 public static String escapeForJava(String inputString) { 329 // Gotta escape literal backslash before putting in specials that use escape. 330 inputString = inputString.replace("\\", "\\\\"); 331 // Then double quotes need escaping (singles are OK of course). 332 inputString = inputString.replace("\"", "\\\""); 333 // note: replace newline to String ".\n", replace tab to String ".\t" 334 inputString = inputString.replace("\n", "\\n").replace("\t", "\\t").replace("\r", "\\r").replace("\b", "\\b").replace("\f", "\\f"); 335 336 return inputString; 337 } 338 changeFirstCapital(String ruleName)339 protected String changeFirstCapital(String ruleName) { 340 String firstChar = String.valueOf(ruleName.charAt(0)); 341 return firstChar.toUpperCase()+ruleName.substring(1); 342 } 343 normalizeTreeSpec(String t)344 public static String normalizeTreeSpec(String t) { 345 List<String> words = new ArrayList<String>(); 346 int i = 0; 347 StringBuilder word = new StringBuilder(); 348 while ( i<t.length() ) { 349 if ( t.charAt(i)=='(' || t.charAt(i)==')' ) { 350 if ( word.length()>0 ) { 351 words.add(word.toString()); 352 word.setLength(0); 353 } 354 words.add(String.valueOf(t.charAt(i))); 355 i++; 356 continue; 357 } 358 if ( Character.isWhitespace(t.charAt(i)) ) { 359 // upon WS, save word 360 if ( word.length()>0 ) { 361 words.add(word.toString()); 362 word.setLength(0); 363 } 364 i++; 365 continue; 366 } 367 368 // ... "x" or ...("x" 369 if ( t.charAt(i)=='"' && (i-1)>=0 && 370 (t.charAt(i-1)=='(' || Character.isWhitespace(t.charAt(i-1))) ) 371 { 372 i++; 373 while ( i<t.length() && t.charAt(i)!='"' ) { 374 if ( t.charAt(i)=='\\' && 375 (i+1)<t.length() && t.charAt(i+1)=='"' ) // handle \" 376 { 377 word.append('"'); 378 i+=2; 379 continue; 380 } 381 word.append(t.charAt(i)); 382 i++; 383 } 384 i++; // skip final " 385 words.add(word.toString()); 386 word.setLength(0); 387 continue; 388 } 389 word.append(t.charAt(i)); 390 i++; 391 } 392 if ( word.length()>0 ) { 393 words.add(word.toString()); 394 } 395 //System.out.println("words="+words); 396 StringBuilder buf = new StringBuilder(); 397 for (int j=0; j<words.size(); j++) { 398 if ( j>0 && !words.get(j).equals(")") && 399 !words.get(j-1).equals("(") ) { 400 buf.append(' '); 401 } 402 buf.append(words.get(j)); 403 } 404 return buf.toString(); 405 } 406 407 } 408