blob: 0cb6ba62cbbe1527012262e82520c7cc43ce5028 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.flex.compiler.internal.js.codegen.goog;
import java.io.FilterWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.flex.compiler.common.ASModifier;
import org.apache.flex.compiler.definitions.IClassDefinition;
import org.apache.flex.compiler.definitions.IPackageDefinition;
import org.apache.flex.compiler.definitions.ITypeDefinition;
import org.apache.flex.compiler.internal.js.codegen.JSEmitter;
import org.apache.flex.compiler.internal.js.codegen.JSSharedData;
import org.apache.flex.compiler.internal.tree.as.FunctionNode;
import org.apache.flex.compiler.js.codegen.goog.IJSGoogDocEmitter;
import org.apache.flex.compiler.js.codegen.goog.IJSGoogEmitter;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.compiler.projects.ICompilerProject;
import org.apache.flex.compiler.tree.as.IClassNode;
import org.apache.flex.compiler.tree.as.IDefinitionNode;
import org.apache.flex.compiler.tree.as.IExpressionNode;
import org.apache.flex.compiler.tree.as.IFunctionNode;
import org.apache.flex.compiler.tree.as.IPackageNode;
import org.apache.flex.compiler.tree.as.IParameterNode;
import org.apache.flex.compiler.tree.as.IScopedNode;
import org.apache.flex.compiler.tree.as.ITypeNode;
import org.apache.flex.compiler.tree.as.IVariableNode;
/**
* Concrete implementation of the 'goog' JavaScript production.
*
* @author Michael Schmalle
*/
public class JSGoogEmitter extends JSEmitter implements IJSGoogEmitter
{
IJSGoogDocEmitter getDoc()
{
return (IJSGoogDocEmitter) getDocEmitter();
}
//--------------------------------------------------------------------------
//
//--------------------------------------------------------------------------
public void emitJSDocPackgeHeader(IPackageNode node)
{
// getDocEmitter().emmitPackageHeader(node);
}
@Override
public void emitJSDocVariable(IVariableNode node)
{
getDoc().begin();
getDoc().emitType(node);
getDoc().end();
}
@Override
public void emitJSDocConstructor(IFunctionNode node,
ICompilerProject project)
{
IClassDefinition parent = (IClassDefinition) node.getDefinition()
.getParent();
IClassDefinition superClass = parent.resolveBaseClass(project);
getDoc().begin();
getDoc().emitConstructor(node);
if (superClass != null)
getDoc().emitExtends(superClass);
getDoc().end();
}
//--------------------------------------------------------------------------
//
//--------------------------------------------------------------------------
@Override
public void emitPackageHeader(IPackageNode node)
{
ITypeNode type = findTypeNode(node);
if (type == null)
return;
write("goog.provide('" + type.getQualifiedName() + "');");
write("\n");
write("\n");
}
@Override
public void emitPackageHeaderContents(IPackageNode node)
{
ITypeNode type = findTypeNode(node);
if (type == null)
return;
IPackageDefinition parent = (IPackageDefinition) node.getDefinition();
ArrayList<String> list = new ArrayList<String>();
parent.getContainedScope().getScopeNode().getAllImports(list);
for (String imp : list)
{
if (imp.indexOf("__AS3__") != -1)
continue;
write("goog.require('" + imp + "');");
write("\n");
}
write("\n");
}
@Override
public void emitPackageContents(IPackageNode node)
{
ITypeNode type = findTypeNode(node);
if (type == null)
return;
IClassNode cnode = (IClassNode) type;
IClassDefinition definition = cnode.getDefinition();
// constructor
emitConstructor((IFunctionNode) definition.getConstructor().getNode());
write(";\n");
write("\n");
IDefinitionNode[] members = cnode.getAllMemberNodes();
for (IDefinitionNode dnode : members)
{
if (dnode instanceof IVariableNode)
{
emitField((IVariableNode) dnode);
write(";\n");
write("\n");
}
}
for (IDefinitionNode dnode : members)
{
if (dnode instanceof IFunctionNode)
{
if (!((IFunctionNode) dnode).isConstructor())
{
emitMethod((IFunctionNode) dnode);
write(";\n");
write("\n");
}
}
}
}
@Override
public void emitPackageFooter(IPackageNode node)
{
}
@Override
public void emitConstructor(IFunctionNode node)
{
IClassDefinition definition = getClassDefinition(node);
FunctionNode fn = (FunctionNode) node;
fn.parseFunctionBody(new ArrayList<ICompilerProblem>());
emitJSDocConstructor(node, getWalker().getProject());
String qname = definition.getQualifiedName();
write(qname);
write(" ");
write("=");
write(" ");
write("function");
emitParamters(node.getParameterNodes());
emitMethodScope(node.getScopedNode());
}
@Override
public void emitField(IVariableNode node)
{
IClassDefinition definition = getClassDefinition(node);
emitJSDocVariable(node);
write(definition.getQualifiedName() + ".prototype." + node.getName());
IExpressionNode vnode = node.getAssignedValueNode();
if (vnode != null)
{
write(" = ");
getWalker().walk(vnode);
}
}
@Override
public void emitJSDoc(IFunctionNode node, ICompilerProject project,
boolean isConstructor, ITypeDefinition type)
{
// TODO (mschmalle) change method signature, remove type
// this is a temp override until I change the method signature
// unit testing dosn't have access to the type, we need to use the AST to get the definition
if (type == null)
{
ITypeNode tnode = (ITypeNode) node
.getAncestorOfType(ITypeNode.class);
type = (ITypeDefinition) tnode.getDefinition();
}
if (node instanceof IFunctionNode)
{
if (isConstructor)
{
emitJSDocConstructor(node, project);
}
else
{
Boolean hasDoc = false;
// @this
// TODO (erikdebruin) only emit @this when there actually is
// a 'this' reference in the method block
/*
if (false)
{
getDoc().begin();
hasDoc = true;
getDoc().emitThis(type);
}
//*/
// @param
IParameterNode[] parameters = node.getParameterNodes();
for (IParameterNode pnode : parameters)
{
if (!hasDoc)
{
getDoc().begin();
hasDoc = true;
}
getDoc().emitParam(pnode);
}
// @return
// TODO (erikdebruin) only emit @return when there actually is
// a return value defined
String returnType = node.getReturnType();
if (returnType != "")
{
if (!hasDoc)
{
getDoc().begin();
hasDoc = true;
}
getDoc().emitReturn(node);
}
// @override
Boolean override = node.hasModifier(ASModifier.OVERRIDE);
if (override)
{
if (!hasDoc)
{
getDoc().begin();
hasDoc = true;
}
getDoc().emitOverride(node);
}
if (hasDoc)
getDoc().end();
}
}
}
@Override
public void emitMethod(IFunctionNode node)
{
if (node.isConstructor())
return;
IClassDefinition definition = getClassDefinition(node);
emitJSDoc(node, getWalker().getProject(), false, definition);
FunctionNode fn = (FunctionNode) node;
fn.parseFunctionBody(new ArrayList<ICompilerProblem>());
String qname = getTypeDefinition(node).getQualifiedName();
if (qname != null && !qname.equals(""))
{
write(qname);
write(".");
if (!fn.hasModifier(ASModifier.STATIC))
write("prototype.");
}
emitMemberName(node);
write(" ");
write("=");
write(" ");
write("function");
emitParamters(node.getParameterNodes());
emitMethodScope(node.getScopedNode());
}
@Override
public void emitFunctionBlockHeader(IFunctionNode node)
{
emitRestParameterCodeBlock(node);
if (JSSharedData.OUTPUT_ALTERNATE)
{
emitDefaultParameterCodeBlock_Alternate(node);
}
else
{
emitDefaultParameterCodeBlock(node);
}
}
private void emitDefaultParameterCodeBlock(IFunctionNode node)
{
// TODO (mschmalle) test for ... rest
// if default parameters exist, produce the init code
IParameterNode[] pnodes = node.getParameterNodes();
Map<Integer, IParameterNode> defaults = getDefaults(pnodes);
if (pnodes.length == 0)
return;
final StringBuilder code = new StringBuilder();
if (defaults != null)
{
List<IParameterNode> parameters = new ArrayList<IParameterNode>(
defaults.values());
Collections.reverse(parameters);
int len = defaults.size();
int numDefaults = 0;
// make the header in reverse order
for (IParameterNode pnode : parameters)
{
if (pnode != null)
{
code.append(getIndent(numDefaults));
code.append("if (arguments.length < " + len + ") {\n");
numDefaults++;
}
len--;
}
Collections.reverse(parameters);
for (IParameterNode pnode : parameters)
{
if (pnode != null)
{
code.append(getIndent(numDefaults));
code.append(pnode.getName());
code.append(" = ");
code.append(pnode.getDefaultValue());
code.append(";\n");
code.append(getIndent(numDefaults - 1));
code.append("}");
if (numDefaults > 1)
code.append("\n");
numDefaults--;
}
}
IScopedNode sbn = node.getScopedNode();
boolean hasBody = sbn.getChildCount() > 0;
// adds the current block indent to the generated code
String indent = getIndent(getCurrentIndent() + (!hasBody ? 1 : 0));
String result = code.toString().replaceAll("\n", "\n" + indent);
// if the block dosn't have a body (children), need to add indent to head
if (!hasBody)
result = indent + result;
// have to add newline after the replace or we get an extra indent
result += "\n";
write(result);
}
}
private void emitDefaultParameterCodeBlock_Alternate(IFunctionNode node)
{
// (erikdebruin) implemented alternative approach to handling
// default parameter values in JS
IParameterNode[] pnodes = node.getParameterNodes();
Map<Integer, IParameterNode> defaults = getDefaults(pnodes);
if (!hasBody(node))
{
indentPush();
write("\t");
}
final StringBuilder code = new StringBuilder();
if (defaults != null)
{
List<IParameterNode> parameters = new ArrayList<IParameterNode>(
defaults.values());
int numDefaults = 0;
for (IParameterNode pnode : parameters)
{
if (pnode != null)
{
if (numDefaults > 0)
code.append(getIndent(getCurrentIndent()));
code.append(pnode.getName() + " = typeof "
+ pnode.getName() + " !== 'undefined' ? "
+ pnode.getName() + " : " + pnode.getDefaultValue()
+ ";\n");
numDefaults++;
}
}
if (!hasBody(node))
indentPop();
write(code.toString());
}
}
private void emitRestParameterCodeBlock(IFunctionNode node)
{
IParameterNode[] pnodes = node.getParameterNodes();
IParameterNode rest = getRest(pnodes);
if (rest != null)
{
final StringBuilder code = new StringBuilder();
code.append(rest.getName() + " = "
+ "Array.prototype.slice.call(arguments, "
+ (pnodes.length - 1)
+ ");\n");
write(code.toString());
}
}
@Override
public void emitParameter(IParameterNode node)
{
// only the name gets output in JS
getWalker().walk(node.getNameExpressionNode());
}
public JSGoogEmitter(FilterWriter out)
{
super(out);
}
private Map<Integer, IParameterNode> getDefaults(IParameterNode[] nodes)
{
Map<Integer, IParameterNode> result = new HashMap<Integer, IParameterNode>();
int i = 0;
boolean hasDefaults = false;
for (IParameterNode node : nodes)
{
if (node.hasDefaultValue())
{
hasDefaults = true;
result.put(i, node);
}
else
{
result.put(i, null);
}
i++;
}
if (!hasDefaults)
return null;
return result;
}
private IParameterNode getRest(IParameterNode[] nodes)
{
for (IParameterNode node : nodes)
{
if (node.isRest())
return node;
}
return null;
}
private static ITypeDefinition getTypeDefinition(IDefinitionNode node)
{
ITypeNode tnode = (ITypeNode) node.getAncestorOfType(ITypeNode.class);
return (ITypeDefinition) tnode.getDefinition();
}
private static IClassDefinition getClassDefinition(IDefinitionNode node)
{
IClassNode tnode = (IClassNode) node
.getAncestorOfType(IClassNode.class);
return tnode.getDefinition();
}
private static boolean hasBody(IFunctionNode node)
{
IScopedNode scope = node.getScopedNode();
return scope.getChildCount() > 0;
}
}