package polyglot.main;
import java.io.File;
import java.io.PrintStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.Vector;
import java.util.StringTokenizer;
import polyglot.frontend.ExtensionInfo;
/**
* This object encapsulates various polyglot options.
*/
public class Options {
/**
* An annoying hack to allow objects to get their hands on the Options
* object. This should be fixed. XXX###@@@
*/
public static Options global;
/**
* 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 Collection source_path; // List[String]
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 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 = 120;
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 dump_ast = new HashSet();
/** Pretty-print the AST after the following passes? */
public Set print_ast = new HashSet();
/** Disable the following passes? */
public Set disable_passes = new HashSet();
/** keep output files */
public boolean keep_output_files = true;
/**
* 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();
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 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 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() + "\n");
}
sb.append("Polyglot compiler toolkit version " +
new polyglot.ext.jl.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] + System.getProperty("path.separator") +
default_classpath;
output_classpath = args[i] + System.getProperty("path.separator") +
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 (f != null && !source_path.contains(f))
source_path.add(f);
}
i++;
}
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++;
Report.addTopic("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) {}
}
Report.addTopic(topic, level);
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, "-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 width of the .java output files");
usageForFlag(out, "-dump <pass>", "dump the ast after pass <pass>");
usageForFlag(out, "-print <pass>",
"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, "-c", "compile only to .java");
usageForFlag(out, "-post <compiler>",
"run javac-like compiler after translation");
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 iter = Report.topics.iterator(); iter.hasNext(); ) {
allowedTopics.append(iter.next().toString());
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;
/**
* 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(classpath);
return fullcp.toString();
}
public String constructPostCompilerClasspath() {
return output_directory + File.pathSeparator
+ "." + File.pathSeparator
+ output_classpath;
}
}