/*
* 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.visit;
import java.io.File;
import java.io.IOException;
import java.util.*;
import polyglot.ast.*;
import polyglot.frontend.Job;
import polyglot.frontend.TargetFactory;
import polyglot.types.*;
import polyglot.types.Package;
import polyglot.util.*;
/**
* A Translator generates output code from the processed AST.
* Output is sent to one or more java file in the directory
* <code>Options.output_directory</code>. Each SourceFile in the AST
* is output to exactly one java file. The name of that file is
* determined as follows:
* <ul>
* <li> If the SourceFile has a declaration of a public top-level class "C",
* file name is "C.java". It is an error for there to be more than one
* top-level public declaration.
* <li> If the SourceFile has no public declarations, the file name
* is the input file name (e.g., "X.jl") with the suffix replaced with ".java"
* (thus, "X.java").
* </ul>
*
* To use:
* <pre>
* new Translator(job, ts, nf, tf).translate(ast);
* </pre>
* The <code>ast</code> must be either a SourceFile or a SourceCollection.
*/
public class Translator extends PrettyPrinter implements Cloneable
{
protected Job job;
protected NodeFactory nf;
protected TargetFactory tf;
protected TypeSystem ts;
/** The current typing context, or null if type information is unavailable in this subtree of the AST. */
protected Context context;
/**
* Create a Translator. The output of the visitor is a collection of files
* whose names are added to the collection <code>outputFiles</code>.
*/
public Translator(Job job, TypeSystem ts, NodeFactory nf, TargetFactory tf) {
super();
this.job = job;
this.nf = nf;
this.tf = tf;
this.ts = ts;
this.context = ts.emptyContext();
}
/**
* Return the job associated with this Translator.
*/
public Job job() {
return job;
}
/** Copy the translator. */
protected Translator shallowCopy() {
try {
return (Translator) super.clone();
}
catch (CloneNotSupportedException e) {
throw new InternalCompilerError("Java clone() weirdness.");
}
}
/** Get the extension's type system. */
public TypeSystem typeSystem() {
return ts;
}
/** Get the extension's node factory. */
public NodeFactory nodeFactory() {
return nf;
}
/** Get the current typing context, or null. */
public Context context() {
return this.context;
}
/** Create a new <code>Translator</code> identical to <code>this</code> but
* with new context <code>c</code> */
public Translator context(Context c) {
if (c == this.context) {
return this;
}
Translator tr = shallowCopy();
tr.context = c;
return tr;
}
/** Print an ast node using the given code writer. This method should not
* be called directly to translate a source file AST; use
* <code>translate(Node)</code> instead. This method should only be called
* by nodes to print their children.
*/
public void print(Node parent, Node child, CodeWriter w) {
Translator tr = this;
if (context != null) {
if (parent == null) {
Context c = child.del().enterScope(context);
tr = this.context(c);
}
else {
Context c = parent.del().enterChildScope(child, context);
tr = this.context(c);
}
}
child.del().translate(w, tr);
if (context != null) {
child.addDecls(context);
}
}
/** Translate the entire AST. */
public boolean translate(Node ast) {
if (ast instanceof SourceFile) {
SourceFile sfn = (SourceFile) ast;
return translateSource(sfn);
}
else if (ast instanceof SourceCollection) {
SourceCollection sc = (SourceCollection) ast;
boolean okay = true;
for (SourceFile sfn : sc.sources()) {
okay &= translateSource(sfn);
}
return okay;
}
else {
throw new InternalCompilerError("AST root must be a SourceFile; " +
"found a " + ast.getClass().getName());
}
}
/** Translate a single SourceFile node */
protected boolean translateSource(SourceFile sfn) {
TypeSystem ts = typeSystem();
NodeFactory nf = nodeFactory();
TargetFactory tf = this.tf;
int outputWidth = job.compiler().outputWidth();
// Find the public declarations in the file. We'll use these to
// derive the names of the target files. There will be one
// target file per public declaration. If there are no public
// declarations, we'll use the source file name to derive the
// target file name.
List<TopLevelDecl> exports = exports(sfn);
CodeWriter w = null;
try {
File of;
QName pkg = null;
if (sfn.package_() != null) {
Package p = sfn.package_().package_().get();
pkg = p.fullName();
}
TopLevelDecl first = null;
if (exports.size() == 0) {
// Use the source name to derive a default output file name.
of = tf.outputFile(pkg, sfn.source());
}
else {
first = (TopLevelDecl) exports.get(0);
of = tf.outputFile(pkg, first.name().id(), sfn.source());
}
String opfPath = of.getPath();
if (!opfPath.endsWith("$")) job.compiler().addOutputFile(sfn, of.getPath());
w = tf.outputCodeWriter(of, outputWidth);
writeHeader(sfn, w);
for (Iterator<TopLevelDecl> i = sfn.decls().iterator(); i.hasNext(); ) {
TopLevelDecl decl = i.next();
if (decl.flags().flags().isPublic() && decl != first) {
// We hit a new exported declaration, open a new file.
// But, first close the old file.
w.flush();
w.close();
of = tf.outputFile(pkg, decl.name().id(), sfn.source());
job.compiler().addOutputFile(sfn, of.getPath());
w = tf.outputCodeWriter(of, outputWidth);
writeHeader(sfn, w);
}
translateTopLevelDecl(w, sfn, decl);
if (i.hasNext()) {
w.newline(0);
}
}
w.flush();
return true;
}
catch (IOException e) {
job.compiler().errorQueue().enqueue(ErrorInfo.IO_ERROR,
"I/O error while translating: " + e.getMessage());
return false;
}
finally {
if (w != null) {
try {
w.close();
}
catch (IOException e) {
job.compiler().errorQueue().enqueue(ErrorInfo.IO_ERROR,
"I/O error while closing output file: " + e.getMessage());
}
}
}
}
/**
* Translate a top-level declaration <code>decl</code> of source file <code>source</code>.
* @param w
* @param source
* @param decl
*/
protected void translateTopLevelDecl(CodeWriter w, SourceFile source, TopLevelDecl decl) {
Translator tr;
Context c = source.del().enterScope(context);
tr = this.context(c);
decl.del().translate(w, tr);
}
/** Write the package and import declarations for a source file. */
protected void writeHeader(SourceFile sfn, CodeWriter w) {
if (sfn.package_() != null) {
w.write("package ");
sfn.package_().del().translate(w, this);
w.write(";");
w.newline(0);
w.newline(0);
}
boolean newline = false;
for (Import imp : sfn.imports()) {
imp.del().translate(w, this);
newline = true;
}
if (newline) {
w.newline(0);
}
}
/** Get the list of public top-level classes declared in the source file. */
protected List<TopLevelDecl> exports(SourceFile sfn) {
List<TopLevelDecl> exports = new ArrayList<TopLevelDecl>();
for (Iterator<TopLevelDecl> i = sfn.decls().iterator(); i.hasNext(); ) {
TopLevelDecl decl = i.next();
if (decl.flags().flags().isPublic()) {
exports.add(decl);
}
}
return exports;
}
public String toString() {
return "Translator";
}
}