package LBJ2; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java_cup.runtime.Symbol; import LBJ2.IR.AST; import LBJ2.frontend.parser; import LBJ2.frontend.sym; import LBJ2.frontend.SymbolNames; import LBJ2.frontend.TokenValue; import LBJ2.frontend.Yylex; /** * LBJ2's command line interface. Passing a source file to this class will * invoke LBJ2's frontend and optimization passes, resulting in the execution * of the source file's code including creation of java files that implement * the source file's semantics. <p> * * <font size=+2><b>LBJ</b></font> stands for * <font size=+1><b>L</b></font>earning <font size=+1><b>B</b></font>ased * <font size=+1><b>J</b></font>ava. LBJ2 is a language for building systems * that learn. <p> * * <dl> * <dt>Usage:</dt> * <dd> * <code>java LBJ2.Main [options] <source file></code> * <dl> * <dt>where [options] is one or more of the following:</dt> * <dd> * <table> * <tr> * <td valign=top><code>-c</code></td> * <td> * Compile only: This option tells LBJ2 to translate the given * source to Java, but not to compile the generated Java * sources or do any training. * </td> * </tr> * <tr> * <td valign=top><code>-d <directory></code></td> * <td> * Any class files generated during compilation will be written * in the specified directory, just like <code>javac</code>'s * <code>-d</code> command line parameter. * </td> * </tr> * <tr> * <td valign=top><code>-j <a></code></td> * <td> * Sends the contents of <code><a></code> to * <code>javac</code> as command line arguments while * compiling. Don't forget to put quotes around * <code><a></code> if there is more than one such * argument or if the argument has a parameter. * </td> * </tr> * <!-- Doesn't work well because of interned features. * <tr> * <td valign=top><code>-p</code></td> * <td> * Train in parallel: When this option is enabled, learners * that don't depend on each other are trained concurrently. * </td> * </tr> * --> * <tr> * <td valign=top nowrap><code>-s</code></td> * <td> Print the names of all declarations and quit. </td> * </tr> * <tr> * <td valign=top nowrap><code>-t <n></code></td> * <td> * Enables default progress output during training. A message * is printed every <code><n></code> examples while * training any classifier whose <code>learn</code> expression * doesn't contain a <code>progressOutput</code> clause. * </td> * </tr> * <tr> * <td valign=top><code>-v</code></td> * <td> Prints the version number and exits. </td> * </tr> * <tr> * <td valign=top><code>-w</code></td> * <td> * Disables the output of warning messages. Currenlty, there * are only two types of warnings. A warning is reported if a * constraint declaration does not contain any constraint * statements, and a warning is reported if a learner's type is * less specific than the declared type of the classifier it's * being used in. * </td> * </tr> * <tr> * <td valign=top><code>-x</code></td> * <td> * Clean: Delete all files that would otherwise be generated. * No code is generated and no training takes place. * </td> * </tr> * <tr> * <td valign=top nowrap> * <code>-generatedsourcepath <directory></code><br> * <code>-gsp <directory></code> * </td> * <td> * LBJ will potentially generate many Java source files. Use * this option to have LBJ write them to the specified * directory instead of the current directory. * <code><directory></code> must already exist. Note * that LBJ will also compile these files which can result in * even more class files than there were sources. Those class * files will also be written in <code><directory></code> * unless the <code>-d</code> command line parameter is * utilized as well. * </td> * </tr> * <tr> * <td valign=top><code>-sourcepath <path></code></td> * <td> * If the LBJ source depends on classes whose source files * cannot be found on the user's classpath, specify the * directories where they can be found using this parameter. * It works just like <code>javac</code>'s * <code>-sourcepath</code> command line parameter. * </td> * </tr> * <tr> * <td valign=top><code>--parserDebug</code></td> * <td>Debug: Debug output for parse phase only.</td> * </tr> * <tr> * <td valign=top><code>--lexerOutput</code></td> * <td>Lexer output: Print lexical token stream and quit.</td> * </tr> * <tr> * <td valign=top><code>--parserOutput</code></td> * <td>Parser output: Print the parsed AST and quit.</td> * </tr> * <tr> * <td valign=top><code>--semanticOutput</code></td> * <td> * Semantic analysis output: Print semantic analysis * information and quit. * </td> * </tr> * </table> * </dd> * </dl> * </dd> * </dl> * * @author Nick Rizzolo **/ public class Main { /** * This flag is set to <code>true</code> if token printing is enabled on * the command line. Tokens are the output from the scanner. **/ private static boolean printTokens = false; /** * This flag is set to <code>true</code> if AST printing is enabled on the * command line. The AST is the output from the parser. **/ private static boolean printAST = false; /** * This flag is set to <code>true</code> if semantic analysis output is * enabled on the command line. **/ private static boolean printSemantic = false; /** * This flag is set to <code>true</code> if revision analysis output is * enabled on the command line. **/ private static boolean printRevisions = false; /** * This flag is set to <code>true</code> if the output of parser debugging * information is enabled on the command line. **/ private static boolean parserDebug = false; /** * Set to the granularity at which progress messages should be printed * during training. **/ private static int trainingOutput = 0; /** * This flag is set to <code>true</code> if cleaning has been enabled on * the command line. **/ public static boolean clean = false; /** * This flag is set to <code>true</code> if the user has requested that the * source only be compiled to Java. **/ private static boolean compileOnly = false; /** This flag is set if concurrent training has been enabled. */ public static boolean concurrentTraining = false; /** This flag is set if warnings have been disabled on the command line. */ public static boolean warningsDisabled = false; /** This flag is set if symbol printing is enabled on the command line. */ public static boolean printSymbols = false; /** The relative path to the LBJ source file. */ public static String sourceDirectory; /** The name of the LBJ2 source file as specified on the command line. */ public static String sourceFilename; /** The source file's name without the <code>.lbj</code> extension. */ public static String sourceFileBase; /** * Holds command line arguments to be sent to <code>javac</code> when * compiling. **/ public static String javacArguments = ""; /** * A list of names of files generated by the compiler, created as they are * generated. **/ public static HashSet fileNames; /** * The directory in which Javac will place class files (with subdirectories * mimicing the package name included). **/ public static String classDirectory; /** * The directory in which class files should be written, not including the * subdirectory structure that mimics the package. **/ public static String classPackageDirectory; /** The directory in which to search for source files. */ public static String classPath = System.getProperty("java.class.path"); /** The directory in which to search for source files. */ public static String sourcePath = System.getProperty("java.class.path"); /** * The directory in which to write generated Java source files (with * subdirectories mimicing the package name included). **/ public static String generatedSourceDirectory; /** The passes that will be executed. */ private static LinkedList passes; /** * The main compiler driver. This method parses command line options and * then calls all of LBJ2's components. * * @param args The user's command line arguments are found here. * @exception Exception An exception is thrown when any error occurs. **/ public static void main(String[] args) throws Exception { AST ast = null; try { ast = frontend(ProcessCommandLine(args)); } catch (Exception e) { if ("version".equals(e.getMessage())) System.exit(0); if (e.getMessage() == null || !e.getMessage().equals("Incorrect arguments")) throw e; System.exit(1); } if (ast == null) return; // Happens if --lexerOutput, --parserOutput, or --semanticOutput is // enabled. fileNames = new HashSet(); passes = new LinkedList(); runSemanticAnalysis(ast); if (clean) passes.add(new Clean(ast)); else { passes.add(new ClassifierCSE(ast)); passes.add(new RevisionAnalysis(ast)); passes.add(new TranslateToJava(ast)); if (!compileOnly) passes.add(new Train(ast, trainingOutput)); } for (Iterator I = passes.iterator(); I.hasNext() && !Pass.fatalError; ) { ((Pass) I.next()).run(); Pass.printErrorsAndWarnings(); } if (Pass.fatalError) System.exit(1); } /** * Runs the semantic analysis pass on the specified AST, then prints errors * and warnings if they exist, and finally sets the * {@link #generatedSourceDirectory} and {@link #classDirectory} variables. * * @param ast The AST. **/ public static void runSemanticAnalysis(AST ast) { new SemanticAnalysis(ast).run(); Pass.printErrorsAndWarnings(); if (generatedSourceDirectory != null) { if (AST.globalSymbolTable.getPackage().length() != 0) generatedSourceDirectory += File.separator + AST.globalSymbolTable.getPackage() .replace('.', File.separatorChar); } else if (sourceDirectory != null) generatedSourceDirectory = sourceDirectory; if (classPackageDirectory != null && AST.globalSymbolTable.getPackage().length() != 0) classDirectory = classPackageDirectory + File.separator + AST.globalSymbolTable.getPackage().replace('.', File.separatorChar); else classDirectory = classPackageDirectory; } /** * Sets all the internal flags that correspond to the specified command * line parameters, and checks the command line for errors. * * @param args The user's command line arguments are found here. * @exception Exception An exception is thrown if an error is found. * @return A stream for the input source file. **/ private static FileInputStream ProcessCommandLine(String[] args) throws Exception { if (args.length < 1) { PrintUsage(); throw new Exception("Incorrect arguments"); } boolean printVersion = false; int index; for (index = 0; index < args.length - 1; ++index) { if (args[index].equals("-t")) { try { trainingOutput = Integer.parseInt(args[++index]); if (trainingOutput < 0) throw new Exception(); } catch (Exception e) { PrintUsage(); throw new Exception("The -t argument must be followed by a " + "non-negative integer."); } } else if (args[index].equals("-c")) compileOnly = true; else if (args[index].equals("-d")) classPackageDirectory = args[++index]; else if (args[index].equals("-j")) javacArguments += " " + args[++index]; else if (args[index].equals("-p")) concurrentTraining = true; else if (args[index].equals("-s")) printSymbols = true; else if (args[index].equals("-v")) printVersion = true; else if (args[index].equals("-w")) warningsDisabled = true; else if (args[index].equals("-x")) clean = true; else if (args[index].equals("-generatedsourcepath") || args[index].equals("-gsp")) generatedSourceDirectory = args[++index]; else if (args[index].equals("-sourcepath")) sourcePath = args[++index]; else if (args[index].equals("--parserDebug")) parserDebug = true; else if (args[index].equals("--lexerOutput")) printTokens = true; else if (args[index].equals("--parserOutput")) printAST = true; else if (args[index].equals("--revisionOutput")) printRevisions = true; else if (args[index].equals("--semanticOutput")) printSemantic = true; else { PrintUsage(); throw new Exception("Unrecognized parameter: " + args[index]); } } if (printVersion || args.length == 1 && args[0].equals("-v")) { System.out.println("Learning Based Java (LBJ) " + Configuration.packageVersion); System.out.println( "Copyright (C) 2011, Nicholas D. Rizzolo and Dan Roth."); System.out.println("Cognitive Computations Group"); System.out.println("University of Illinois at Urbana-Champaign"); System.out.println(Configuration.webSite); throw new Exception("version"); } if (javacArguments.indexOf("-d ") != -1 || javacArguments.indexOf("-sourcepath ") != -1 || javacArguments.indexOf("-classpath ") != -1 || javacArguments.indexOf("-cp ") != -1) throw new Exception( "None of the options '-d', '-sourcepath', or '-classpath' should " + "be specified inside LBJ's '-j' option. Instead, specify '-d' " + "and '-sourcepath' directly as options to LBJ, and specify " + "-classpath to the JVM when executing LBJ."); if (clean && (compileOnly || printTokens || printAST || printSemantic || trainingOutput != 0)) { System.err.println( "The -x flag supercedes all other flags except --parserDebug and " + "the path related flags."); compileOnly = printTokens = printAST = printSemantic = false; trainingOutput = 0; } if (index >= args.length) { PrintUsage(); throw new Exception("Error: No input filename specified."); } String file = args[index]; if (!(file.length() > 4 && file.endsWith(".lbj"))) { PrintUsage(); throw new Exception("Source file name must end with \".lbj\"."); } int lastSlash = file.lastIndexOf(File.separatorChar); if (lastSlash != -1) { sourceDirectory = file.substring(0, lastSlash); sourceFilename = file.substring(lastSlash + 1); } else sourceFilename = file; sourceFileBase = sourceFilename.substring(0, sourceFilename.length() - 4); FileInputStream instream; try { instream = new FileInputStream(file); } catch (FileNotFoundException e) { System.err.println("Error: Unable to open input file " + file + ": " + e.getMessage()); throw e; } return instream; } /** * This method scans and then parses the input. * * @param in A stream for the source input file. * @exception Exception Thrown when any error occurs. * @return The AST that results from parsing. **/ private static AST frontend(FileInputStream in) throws Exception { Yylex scanner = new Yylex(in); scanner.sourceFilename = sourceFilename; if (printTokens) { dumpTokenStream(scanner); return null; } AST ast = null; parser LBJ2parser = new parser(scanner); if (parserDebug) ast = (AST) LBJ2parser.debug_parse().value; else ast = (AST) LBJ2parser.parse().value; if (ast == null) throw new InternalError("Parser returned null abstract syntax tree."); AST result = ast; if (printAST) { new PrintAST(ast).run(); result = null; } if (printSemantic) { runSemanticAnalysis(ast); System.out.println("\nGlobal symbol table:"); System.out.println("--------------------"); ast.symbolTable.print(); System.out.println("\nDependor graph:"); System.out.println("--------------------"); SemanticAnalysis.printDependorGraph(); System.out.println("\nInvoked graph:"); System.out.println("--------------------"); SemanticAnalysis.printInvokedGraph(); System.out.println(); result = null; } if (printSymbols) { new DeclarationNames(ast).run(); result = null; } if (printRevisions) { if (!printSemantic) runSemanticAnalysis(ast); new RevisionAnalysis(ast).run(); System.out.println("\nRevision statuses:"); System.out.println("--------------------"); RevisionAnalysis.printRevisionStatus(); result = null; } return result; } /** * Dump the token stream produced by the given scanner to standard output. * Returns when the end-of-file token is returned from the scanner, or if an * exception is thrown by the scanner's next_token() method. * * <p> Tokens are output as follows: the name of the token (as provided by * the array symNames.nameTable[]), followed by a tab, followed by the * token's semantic value (see TokenValue.toString()), followed by a tab, * followed by the line and the byte offset in the file where the token * began (the last two separated by a colon). * * <p> Error tokens are printed specially; an error message is printed with * only the line number listed. * * @param scanner A reference to the JLex generated scanner object. */ private static void dumpTokenStream(Yylex scanner) { Symbol t; TokenValue tValue; while (true) { try { t = scanner.next_token(); } catch (IOException e) { System.err.println(e); return; } tValue = (TokenValue)t.value; switch (t.sym) { case sym.EOF: return; case sym.error: System.out.println("Scanner returned error token at " + tValue.line); break; default: System.out.println(SymbolNames.nameTable[t.sym] + "\t" + tValue + "\t" + (tValue.line + 1) + ":" + tValue.byteOffset); } } } /** * Print a usage message. This method is called when the user's command * line cannot be interpreted. **/ public static void PrintUsage() { System.err.print( "Usage: java LBJ2.Main [options] <filename.lbj>\n" + " where [options] is one or more of the following:\n" + " -c Compile to Java only\n" + " -d <dir> Write generated class files to <dir>\n" + " -j <a> Send the specified arguments to javac\n" //+ " -p Train in parallel\n" + " -s Print the names of all declarations and quit\n" + " -t <n> Enables default progress output during training\n" + " -v Print the version number and quit\n" + " -w Disables the output of warning messages\n" + " -x Delete all files that would have been generated\n\n" + " -generatedsourcepath <dir>\n" + " -gsp <dir>\n" + " Write generated Java source files to <dir>\n" + " -sourcepath <path>\n" + " Search for Java source files in <path>\n\n" + " --parserDebug Debug output for parse phase only\n" + " --lexerOutput Print lexical token stream and quit\n" + " --parserOutput Print the parsed AST and quit\n" + " --revisionOutput Print revision analysis information and quit\n" + " --semanticOutput Print semantic analysis information and quit\n"); } }