/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* This file was originally derived from the Polyglot extensible compiler framework.
*
* (C) Copyright 2000-2007 Polyglot project group, Cornell University
* (C) Copyright IBM Corporation 2007-2012.
*/
package polyglot.main;
import java.io.File;
import java.io.PrintStream;
import java.util.*;
import polyglot.frontend.ExtensionInfo;
import polyglot.util.CollectionUtil;
import x10.util.CollectionFactory;
/**
* This object encapsulates various polyglot options.
*/
public class Options {
/**
* Back pointer to the extension that owns this options
*/
protected ExtensionInfo extension = null;
/*
* Fields for storing values for options.
*/
public int error_count = 100;
public List<File> source_path; // List<File>
public File output_directory;
public String default_classpath;
public String default_output_classpath;
public String classpath;
public String output_classpath;
public String bootclasspath = null;
public boolean assertions = false;
public boolean compile_command_line_only = false;
public String[] source_ext = null; // e.g., java, jl, pj
public String output_ext = "java"; // java, by default
public boolean output_stdout = false; // whether to output to stdout
public String post_compiler;
// compiler to run on java output file
public int output_width = 80;
public boolean fully_qualified_names = false;
/** Inject type information in serialized form into output file? */
public boolean serialize_type_info = true;
/** Dump the AST after the following passes? */
public Set<String> dump_ast = CollectionFactory.newHashSet();
/** -define */
public Set<String> macros = CollectionFactory.newHashSet();
/** Pretty-print the AST after the following passes? */
public Set<String> print_ast = CollectionFactory.newHashSet();
/** Disable the following passes? */
public Set<String> disable_passes = CollectionFactory.newHashSet();
/** keep output files */
public boolean keep_output_files = true;
/** Generate position information for compiler-generated code. */
public boolean precise_compiler_generated_positions = false;
/** Use SimpleCodeWriter instead of OptimalCodeWriter */
public boolean use_simple_code_writer = false;
public Reporter reporter = new Reporter();
/**
* Constructor
*/
public Options(ExtensionInfo extension) {
this.extension = extension;
setDefaultValues();
}
/**
* Set default values for options
*/
public void setDefaultValues() {
String default_bootpath = System.getProperty("sun.boot.class.path");
if (default_bootpath == null) {
default_bootpath = System.getProperty("java.home") +
File.separator + "jre" +
File.separator + "lib" +
File.separator + "rt.jar";
}
default_classpath = System.getProperty("java.class.path") +
File.pathSeparator + default_bootpath;
classpath = default_classpath;
default_output_classpath = System.getProperty("java.class.path");
output_classpath = default_output_classpath;
String java_home = System.getProperty("java.home");
String current_dir = System.getProperty("user.dir");
source_path = new LinkedList<File>();
source_path.add(new File(current_dir));
output_directory = new File(current_dir);
// First try: $JAVA_HOME/../bin/javac
// This should work with JDK 1.2 and 1.3
//
// If not found, try: $JAVA_HOME/bin/javac
// This should work for JDK 1.1.
//
// If neither found, assume "javac" is in the path.
//
post_compiler = java_home + File.separator + ".." + File.separator +
"bin" + File.separator + "javac";
if (! new File(post_compiler).exists()) {
post_compiler = java_home + File.separator +
"bin" + File.separator + "javac";
if (! new File(post_compiler).exists()) {
post_compiler = "javac";
}
}
}
/**
* Parse the command line
*
* @throws UsageError if the usage is incorrect.
*/
public void parseCommandLine(String args[], Set<String> source) throws UsageError {
if(args.length < 1) {
throw new UsageError("No command line arguments given");
}
for(int i = 0; i < args.length; ) {
try {
int ni = parseCommand(args, i, source);
if (ni == i) {
throw new UsageError("illegal option -- " + args[i]);
}
i = ni;
}
catch (ArrayIndexOutOfBoundsException e) {
throw new UsageError("missing argument");
}
}
if (source.size() < 1) {
throw new UsageError("must specify at least one source file");
}
}
/**
* Parse a command
* @return the next index to process. i.e., if calling this method
* processes two commands, then the return value should be index+2
*/
protected int parseCommand(String args[], int index, Set<String> source)
throws UsageError, Main.TerminationException {
int i = index;
if (args[i].equals("-h") ||
args[i].equals("-help") ||
args[i].equals("--help")) {
throw new UsageError("", 0);
}
else if (args[i].equals("-version")) {
StringBuffer sb = new StringBuffer();
if (extension != null) {
sb.append(extension.compilerName() +
" version " + extension.version());
}
throw new Main.TerminationException(sb.toString(), 0);
}
else if (args[i].equals("-d"))
{
i++;
output_directory = new File(args[i]);
i++;
}
else if (args[i].equals("-classpath") ||
args[i].equals("-cp")) {
i++;
classpath = args[i] + File.pathSeparator + default_classpath;
output_classpath = args[i] + File.pathSeparator + default_output_classpath;
i++;
}
else if (args[i].equals("-bootclasspath")) {
i++;
bootclasspath = args[i];
i++;
}
else if (args[i].equals("-sourcepath"))
{
i++;
StringTokenizer st = new StringTokenizer(args[i], File.pathSeparator);
while(st.hasMoreTokens())
{
File f = new File(st.nextToken());
if (!source_path.contains(f))
source_path.add(f);
}
i++;
}
else if (args[i].equals("-commandlineonly"))
{
i++;
compile_command_line_only = true;
}
else if (args[i].equals("-preferclassfiles"))
{
i++;
ignore_mod_times = true;
}
else if (args[i].equals("-assert"))
{
i++;
assertions = true;
}
else if (args[i].equals("-fqcn"))
{
i++;
fully_qualified_names = true;
}
else if (args[i].equals("-c"))
{
post_compiler = null;
i++;
}
else if (args[i].equals("-errors"))
{
i++;
try {
error_count = Integer.parseInt(args[i]);
} catch (NumberFormatException e) {}
i++;
}
else if (args[i].equals("-w"))
{
i++;
try {
output_width = Integer.parseInt(args[i]);
} catch (NumberFormatException e) {}
i++;
}
else if (args[i].equals("-post"))
{
i++;
post_compiler = args[i];
i++;
}
else if (args[i].equals("-stdout"))
{
i++;
output_stdout = true;
}
else if (args[i].equals("-sx"))
{
i++;
if (source_ext == null) {
source_ext = new String[] { args[i] };
}
else {
String[] s = new String[source_ext.length+1];
System.arraycopy(source_ext, 0, s, 0, source_ext.length);
s[s.length-1] = args[i];
source_ext = s;
}
i++;
}
else if (args[i].equals("-ox"))
{
i++;
output_ext = args[i];
i++;
}
else if (args[i].equals("-noserial"))
{
i++;
serialize_type_info = false;
}
else if (args[i].equals("-dump"))
{
i++;
String pass_name = args[i];
dump_ast.add(pass_name);
i++;
}
else if (args[i].equals("-print"))
{
i++;
String pass_name = args[i];
print_ast.add(pass_name);
i++;
}
else if (args[i].equals("-disable"))
{
i++;
String pass_name = args[i];
disable_passes.add(pass_name);
i++;
}
else if (args[i].equals("-nooutput"))
{
i++;
keep_output_files = false;
output_width = 1000; // we do not keep the output files, so
// set the output_width to a large number
// to reduce the time spent pretty-printing
}
else if (args[i].equals("-v") || args[i].equals("-verbose"))
{
i++;
reporter.addTopic(Reporter.verbose, 1);
}
else if (args[i].equals("-report")) {
i++;
String report_option = args[i];
StringTokenizer st = new StringTokenizer(args[i], "=");
String topic = ""; int level = 0;
if (st.hasMoreTokens()) topic = st.nextToken();
if (st.hasMoreTokens()) {
try {
level = Integer.parseInt(st.nextToken());
}
catch (NumberFormatException e) {}
}
reporter.addTopic(topic, level);
i++;
}
else if (args[i].equals("-debugpositions")) {
precise_compiler_generated_positions = true;
i++;
}
else if (args[i].equals("-simpleoutput")) {
use_simple_code_writer = true;
i++;
}
else if (args[i].equals("-define"))
{
i++;
String macro = args[i];
macros.add(macro);
i++;
}
else if (!args[i].startsWith("-")) {
source.add(args[i]);
File f = new File(args[i]).getParentFile();
if (f != null && !source_path.contains(f))
source_path.add(f);
i++;
}
return i;
}
/**
* Print usage information
*/
public void usage(PrintStream out) {
out.println("usage: " + extension.compilerName() + " [options] " +
"<source-file>." + extension.fileExtensions()[0] + " ...");
out.println("where [options] includes:");
usageForFlag(out, "@<file>", "read options from <file>");
usageForFlag(out, "-d <directory>", "output directory");
usageForFlag(out, "-assert", "recognize the assert keyword");
usageForFlag(out, "-sourcepath <path>", "source path");
usageForFlag(out, "-bootclasspath <path>",
"path for bootstrap class files");
usageForFlag(out, "-ext <extension>", "use language extension");
usageForFlag(out, "-extclass <ext-class>", "use language extension");
usageForFlag(out, "-commandlineonly", "only compile files named on the command-line (may also require -c)");
usageForFlag(out, "-preferclassfiles", "prefer class files to source files even if the source is newer");
usageForFlag(out, "-fqcn", "use fully-qualified class names");
usageForFlag(out, "-sx <ext>", "set source extension");
usageForFlag(out, "-ox <ext>", "set output extension");
usageForFlag(out, "-errors <num>", "set the maximum number of errors");
usageForFlag(out, "-w <num>",
"set the maximum column width of the output files");
usageForFlag(out, "-dump <pass>|dumpall", "dump the ast after pass <pass>");
usageForFlag(out, "-print <pass>|printall",
"pretty-print the ast after pass <pass>");
usageForFlag(out, "-disable <pass>", "disable pass <pass>");
// usageForFlag(out, "-scramble [seed]", "scramble the ast (for testing)");
usageForFlag(out, "-noserial", "disable class serialization");
usageForFlag(out, "-nooutput", "delete output files after compilation");
usageForFlag(out, "-debugpositions", "generate position information for compiler-generated code");
usageForFlag(out, "-simpleoutput", "use SimpleCodeWriter");
usageForFlag(out, "-define <macro>", "define <macro>");
usageForFlag(out, "-v -verbose", "print verbose debugging information");
usageForFlag(out, "-report <topic>=<level>",
"print verbose debugging information about " +
"topic at specified verbosity");
StringBuffer allowedTopics = new StringBuffer("Allowed topics: ");
for (Iterator<String> iter = reporter.topics.iterator(); iter.hasNext(); ) {
allowedTopics.append(iter.next());
if (iter.hasNext()) {
allowedTopics.append(", ");
}
}
usageSubsection(out, allowedTopics.toString());
usageForFlag(out, "-version", "print version info");
usageForFlag(out, "-h", "print this message");
}
/**
* The maximum width of a line when printing usage information. Used
* by <code>usageForFlag</code> and <code>usageSubsection</code>.
*/
protected int USAGE_SCREEN_WIDTH = 76;
/**
* The number of spaces from the left that the descriptions for flags will
* be displayed. Used
* by <code>usageForFlag</code>.
*/
protected int USAGE_FLAG_WIDTH = 27;
/**
* The number of spaces to indent a subsection of usage information.
* Used by <code>usageSubsection</code>.
*/
protected int USAGE_SUBSECTION_INDENT = 8;
/** Ignore source and class file modification times when compiling; always prefer the class file. */
public boolean ignore_mod_times;
/**
* Output a flag and a description of its usage in a nice format. This
* makes it easier for extensions to output their usage in a consistent
* format.
*
* @param out output PrintStream
* @param flag
* @param description description of the flag.
*/
protected void usageForFlag(PrintStream out, String flag, String description) {
out.print(" ");
out.print(flag);
// cur is where the cursor is on the screen.
int cur = flag.length() + 2;
// print space to get up to indentation level
if (cur < USAGE_FLAG_WIDTH) {
printSpaces(out, USAGE_FLAG_WIDTH - cur);
}
else {
// the flag is long. Get a new line before printing the
// description.
out.println();
printSpaces(out, USAGE_FLAG_WIDTH);
}
cur = USAGE_FLAG_WIDTH;
// break up the description.
StringTokenizer st = new StringTokenizer(description);
while (st.hasMoreTokens()) {
String s = st.nextToken();
if (cur + s.length() > USAGE_SCREEN_WIDTH) {
out.println();
printSpaces(out, USAGE_FLAG_WIDTH);
cur = USAGE_FLAG_WIDTH;
}
out.print(s);
cur += s.length();
if (st.hasMoreTokens()) {
if (cur + 1 > USAGE_SCREEN_WIDTH) {
out.println();
printSpaces(out, USAGE_FLAG_WIDTH);
cur = USAGE_FLAG_WIDTH;
}
else {
out.print(" ");
cur++;
}
}
}
out.println();
}
/**
* Output a section of text for usage information. This text will be
* displayed indented a certain amount from the left, controlled by
* the field <code>USAGE_SUBSECTION_INDENT</code>
*
* @param out the output PrintStream
* @param text the text to output.
*/
protected void usageSubsection(PrintStream out, String text) {
// print space to get up to indentation level
printSpaces(out, USAGE_SUBSECTION_INDENT);
// cur is where the cursor is on the screen.
int cur = USAGE_SUBSECTION_INDENT;
// break up the description.
StringTokenizer st = new StringTokenizer(text);
while (st.hasMoreTokens()) {
String s = st.nextToken();
if (cur + s.length() > USAGE_SCREEN_WIDTH) {
out.println();
printSpaces(out, USAGE_SUBSECTION_INDENT);
cur = USAGE_SUBSECTION_INDENT;
}
out.print(s);
cur += s.length();
if (st.hasMoreTokens()) {
if (cur + 1 > USAGE_SCREEN_WIDTH) {
out.println();
printSpaces(out, USAGE_SUBSECTION_INDENT);
cur = USAGE_SUBSECTION_INDENT;
}
else {
out.print(' ');
cur++;
}
}
}
out.println();
}
/**
* Utility method to print a number of spaces to a PrintStream.
* @param out output PrintStream
* @param n number of spaces to print.
*/
protected static void printSpaces(PrintStream out, int n) {
while (n-- > 0) {
out.print(' ');
}
}
public String constructFullClasspath() {
StringBuffer fullcp = new StringBuffer();
if (bootclasspath != null) {
fullcp.append(bootclasspath);
fullcp.append(File.pathSeparator);
}
fullcp.append(classpath);
return fullcp.toString();
}
public String constructPostCompilerClasspath() {
return output_directory + File.pathSeparator
+ "." + File.pathSeparator
+ output_classpath;
}
/**
* Returns a string representation for the compiler options, usable from the command line.
*/
public String toString(){
String result = "";
if (classpath != null && !classpath.equals("")){
result += "-classpath " + classpath;
}
if (output_directory != null){
result += " -d " + output_directory.getAbsolutePath();
}
if (assertions) {
result += " -assert";
}
if (source_path != null && !source_path.isEmpty()){
result += " -sourcepath ";
for (int i = 0; i < source_path.size(); i++){
result += ((i > 0)? ":" : "") + source_path.get(i).getAbsolutePath();
}
}
if (bootclasspath != null && !bootclasspath.equals("")) {
result += " -bootclasspath " + bootclasspath;
}
if (compile_command_line_only){
result += " -commandlineonly";
}
if (fully_qualified_names){
result += " -fqcn";
}
if (source_ext != null){
for (int i = 0; i < source_ext.length; i++){
result += " -sx "+ source_ext[i];
}
}
if (output_ext != null && !output_ext.equals("")){
result += " -ox " + output_ext;
}
result += " -errors " + error_count;
result += " -w " + output_width;
for (String s: dump_ast){
result += " -dump " + s;
}
for (String s: print_ast){
result += " -print " + s;
}
for (String s: disable_passes){
result += " -disable " + s;
}
if (!serialize_type_info){
result += " -noserial";
}
if (!keep_output_files){
result += " -nooutput";
}
if (post_compiler != null && !post_compiler.equals("")){
result += " -post " + post_compiler;
}
if (precise_compiler_generated_positions){
result += " -debugpositions";
}
if (use_simple_code_writer){
result += " -simpleoutput";
}
for (String macro: macros){
result += " -define " + macro;
}
return result;
}
}