blob: 250e351b7e6dd78e8be5128682232ed537f93832 [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.royale.compiler.internal.codegen.databinding;
import static org.apache.royale.abc.ABCConstants.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import org.apache.royale.abc.instructionlist.InstructionList;
import org.apache.royale.abc.semantics.Label;
import org.apache.royale.abc.semantics.MethodInfo;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.semantics.Namespace;
import org.apache.royale.abc.semantics.Nsset;
import org.apache.royale.abc.visitors.IABCVisitor;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.internal.abc.FunctionGeneratorHelper;
import org.apache.royale.compiler.internal.as.codegen.CodeGeneratorManager;
import org.apache.royale.compiler.internal.as.codegen.LexicalScope;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.tree.as.LiteralNode;
import org.apache.royale.compiler.mxml.IMXMLTypeConstants;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.as.IExpressionNode;
/**
* A bunch of static, low level helpers for generating instruction lists
*
* In this case, low-level means that these utilities don't know anything about
* our Databinding analysis strategy, what watchers are (other than some sdk class), directive processors etc..
*
* Because of the "low level" nature of these utilties, they should generally take "simple types"
* as arguments, with the exception is IASNodes. There is no need to be super strict about this, of course.
* There are already cases here where we pass in BindingInfo rather that jumpthrough hoops (and CPU cycles)
* to extract the simple types.
*/
public class BindingCodeGenUtils
{
private static final Name NAME_VOID = new Name(IASLanguageConstants.void_);
// params for destination function, get set up fully in static block below.
private static final Vector<Name> DEST_METHOD_PARAMS = new Vector<Name>();
static
{
// 1 arg, no type
DEST_METHOD_PARAMS.add(null);
}
/**
* emits the code for anonymous getter function, then adds to the instruction list for instantiating one onto the stack
* @param ret The instruction list being built.
* @param expressions will be codegened to do the bulk of the getter work
*
* * VM context:
* one entry: nothing
* exit: new function object for getter on TOS
*/
public static void generateGetter(IABCVisitor emitter, InstructionList ret, List<IExpressionNode> expressions, LexicalScope enclosing_scope)
{
// get a method info and a scope for the getter we will generate
MethodInfo mi = createGetterMethodInfo();
mi.setMethodName("bind_getter");
log(ret, "making bind_getter");
// generate the getter function
CodeGeneratorManager.getCodeGenerator().generateMXMLDataBindingGetterFunction(mi, expressions, enclosing_scope);
// the instruction stream that will be executed by the ctor
// needs to push the new function onto the stack
ret.addInstruction(OP_newfunction, mi);
}
/**
* emits the code for anonymous setter function, then adds to the instruction list for instantiating one onto the stack
* @param ret The instruction list being built.
* @param expression will be codegened to do the bulk of the setter work
*
* * VM context:
* one entry: nothing
* exit: new function object for getter on TOS
*/
public static void generateSetter(
InstructionList ret,
IExpressionNode expression,
LexicalScope enclosing_scope
)
{
// get a method info and a scope for the getter we will generate
MethodInfo mi = createSetterMethodInfo();
log(ret, "making bind_setter");
// After codegen of the binding expression, we just need to return it
// TODO: add O_coerce
InstructionList stuffToFollowExpression = new InstructionList();
stuffToFollowExpression.addInstruction(OP_returnvoid);
List<IExpressionNode> exprs = new ArrayList<IExpressionNode>();
exprs.add(expression);
// CG the function
CodeGeneratorManager.getCodeGenerator().generateMXMLDataBindingSetterFunction(mi, expression, enclosing_scope);
// the instruction stream that will be executed by the ctor
// needs to push the new function onto the stack
ret.addInstruction(OP_newfunction, mi);
}
/**
* Generate a MethodInfo appropriate for an anonymous getter
*/
private static MethodInfo createGetterMethodInfo()
{
MethodInfo mi = new MethodInfo();
// TODO: consider making us a more useful name for these anonymous functions
// For debugging with swfdump, you can uncomment the line below:
// mi.setMethodName("binding_getter");
// Set return type as '*'
mi.setReturnType( null);
return mi;
}
/**
* Generate a MethodInfo appropriate for an anonymous getter
*/
private static MethodInfo createSetterMethodInfo()
{
MethodInfo mi = new MethodInfo();
// TODO: consider making us a more useful name for these anonymous functions
// For debugging with swfdump, you can uncomment the line below:
// mi.setMethodName("binding_getter");
mi.setReturnType(NAME_VOID);
mi.setParamTypes(DEST_METHOD_PARAMS);
return mi;
}
/**
* generate code to instantiate a binding object. The code to generate the source and destination functions
* should be passed in in methodInstr - these instructions will be inserted in the appropriate place.
*
* exit: stack = binding object
*
* @param insns IL to add instructions to
* @param project Project we are compiling in
* @param destStr String representation of the destination expression
* @param srcString String representation of the source expression
* @param methodInstr IL with instructions to put the source function and destination function
* on the stack. These will be inserted where they are needed - these will usually
* be OP_newfunction, but could be anything that leaves the methods on the stack
*
*/
public static void makeBinding(InstructionList insns, RoyaleProject project, String destStr, String srcString, InstructionList methodInstr)
{
log(insns, "making binding dest=" + destStr);
/* instantiate Binding
* public function Binding(document:Object, srcFunc:Function,
destFunc:Function, destString:String,
srcString:String = null)
*/
insns.addInstruction(OP_findpropstrict, project.getBindingClassName());
// stack: mx.Binding
insns.addInstruction(OP_getlocal0);
// stack: this, mx.Binding
insns.addAll(methodInstr);
// stack: destination_func, getter_func, this, mx.Binding
pushString(insns, destStr != null ? destStr : "");
// stack: destStr, dest func, getter_func, this, mx.Binding, getters
// push the source string, which may be null
if (srcString != null)
insns.addInstruction(OP_pushstring, srcString);
else
insns.addInstruction(OP_pushnull);
// stack: sourceString, destStr, null, getter_func, this, mx.Binding, getters
insns.addInstruction(OP_constructprop, new Object[] { project.getBindingClassName(), 5 });
// stack: binding
}
/**
* thin wrapper around aet to prevent pushing null strings
*
* @param insns
* @param string
*/
private static void pushString(InstructionList insns, String string)
{
assert string != null;
insns.addInstruction(OP_pushstring, string);
}
/**
* Generates the ABC to instantiate a PropertyWatcher
* Can only generate one special case:
* single event (Constructor actually takes an array of events. we could easily add that
* no property getter function (we use null)
*
* Note that we are passing in references to BindingInfo. Although this somewhat breaks
* the rules for our low-level CG helpers, it's the only convenient way to pass in the list
* of indicies that we will need to prove to the property watcher ctor.
*
*
* * VM context:
* on entry: nothing on stack
* local1 = this._bindings
* exit: new property watcher on stack
*/
public static void makePropertyWatcher(
boolean makeStaticWatcher,
InstructionList insns,
String propertyName,
List<String> eventNames,
List<BindingInfo> bindingInfo,
MethodInfo propertyGetterFunction,
RoyaleProject project
)
{
log(insns, "makePropertyWatchercg");
// check input
assert propertyName != null;
Name watcherClassName = makeStaticWatcher ?
project.getStaticPropertyWatcherClassName()
: project.getPropertyWatcherClassName();
insns.addInstruction(OP_findpropstrict, watcherClassName);
// stack: mx.PropertyWatcher
/* ctor arguments:
PropertyWatcher(
propertyName:String,
events:Object,
listeners:Array,
propertyGetter:Function = null)
*/
pushString(insns, propertyName);
// stack: propName, mx.PropertyWatcher
// now make Object { eventName : true }, or null
makeEventNameArray(insns, eventNames);
// stack: {events}, propName, mx.PropertyWatcher
makeArrayOfBindingsForWatcher(insns, bindingInfo);
// stack: listeners[], {events}, propName, mx.PropertyWatcher
if (propertyGetterFunction == null)
{
insns.addInstruction(OP_pushnull); // null is valid
}
else
{
insns.addInstruction(OP_newfunction, propertyGetterFunction);
}
// stack: propertyGetter, listeners[], {events}, propName, mx.PropertyWatcher
insns.addInstruction(OP_constructprop, new Object[] { watcherClassName, 4 });
// stack: watcher
log(insns, "leave makePropertyWatchercg");
}
/**
* Generate instructions to instantiate an mx.binding.XMLWatcher
*
* @param insns The instruction list being built.
* @param propertyName The name of a property.
* @param bindingInfo The binding information for the property.
* @param project The compiler project.
*/
public static void makeXMLWatcher(
InstructionList insns,
String propertyName,
List<BindingInfo> bindingInfo,
RoyaleProject project)
{
/* ctor arguments:
XMLWatcher(
(propertyName:String,
listeners:Array)
*/
Name watcherClassName = project.getXMLWatcherClassName();
insns.addInstruction(OP_findpropstrict, watcherClassName);
pushString(insns, propertyName);
makeArrayOfBindingsForWatcher(insns, bindingInfo);
insns.addInstruction(OP_constructprop, new Object[] { watcherClassName, 2});
}
/**
* Given an event name, make Object { eventName: true }
* TODO: support multiple names
*
* returns with new object on stack
*/
private static boolean makeEventNameArray(InstructionList insns, List<String> eventNames)
{
boolean isStyle = false;
if (eventNames.isEmpty())
{
insns.addInstruction(OP_pushnull); // null is acceptable for the events object (old compiler does this)
}
else if (eventNames.size() == 1 && eventNames.get(0).equals("isStyle"))
{
isStyle = true;
insns.addInstruction(OP_pushnull); // null is acceptable for the events object (old compiler does this)
}
else
{
for (String eventName : eventNames)
{
insns.addInstruction(OP_pushstring, eventName);
insns.addInstruction(OP_pushtrue);
}
insns.addInstruction(OP_newobject, eventNames.size());
}
return isStyle;
}
/**
* Generates the ABC to instantiate a FunctionReturnWatcher
* * VM context:
* on entry: nothing on stack
* local1 = this._bindings
* exit: new property watcher on stack
*/
public static void makeFunctionWatcher(InstructionList insns,
RoyaleProject project,
IABCVisitor emitter,
String functionName,
List<String> eventNames,
List<BindingInfo> bindingInfo,
IExpressionNode[] params)
{
log(insns, "enter makeFunctionWatchercg");
Name watcherClassName = project.getFunctionReturnWatcherClassName();
insns.addInstruction(OP_findpropstrict, watcherClassName);
// stack: mx.binding.FunctionReturnWatcher
/*
FunctionReturnWatcher(
functionName:String,
document:Object,
parameterFunction:Function,
events:Object,
listeners:Array,
functionGetter:Function = null,
isStyle:Boolean = false)
*/
// arg1: functionName
pushString(insns, functionName);
// stack: functionName, watcher class
// arg2: document. which is "this"
insns.addInstruction(OP_getlocal0);
// stack: this, functionName, watcher class
// arg3: parameterFunction
makeParameterFunction(emitter, insns, params);
// arg4: events
boolean isStyle = makeEventNameArray(insns, eventNames);
// arg5: listeners
makeArrayOfBindingsForWatcher(insns, bindingInfo);
// arg 6, propertyGetter. For now, we don't have one, so don't pass (it's optional)
if (isStyle)
{
insns.addInstruction(OP_pushnull);
insns.addInstruction(OP_pushtrue);
}
// now construct the wacher
insns.addInstruction(OP_constructprop, new Object[] { watcherClassName, isStyle ? 7 : 5 });
log(insns, "leave makeFunctionWatchercg");
}
/**
* Generated the "parameter function" required by the FunctionReturnWatcher
* constructor.
* <p>
* This function is like this:
*
* <pre>
* function(): Array { return [param1, param2] };
* </pre>
*
* Since that is actually difficult to generate, we are instead generating a
* simple function that throws an exception that we know will be caught
* silently by the Flex SDK.
* <p>
* There may be some strange edge cases where this trick won't work, but it
* will cover the majority of cases BETTER than doing the "right" thing.
*
* @param emitter {@link IABCVisitor} to which generated code is added.
* @param ret {@link InstructionList} to which code that creates a function
* closure for the new parameter function should be added.
*/
// version 2: just throw
public static void makeParameterFunction(IABCVisitor emitter, InstructionList ret, IExpressionNode[] params)
{
//----------- step 1: build up a method info for the function
// we are going to make a new func
MethodInfo mi = new MethodInfo();
// TODO: consider making us a more useful name for these anonymous functions
// For debugging with swfdump, you can un-comment the line below:
//mi.setMethodName("parameterFunction");
mi.setReturnType( new Name(IASLanguageConstants.Array));
//---- generate code that throws the exception.
// This is based on the code that came out of the old compiler
InstructionList parameterFunctionBody = new InstructionList();
parameterFunctionBody.addInstruction(OP_getlocal0);
parameterFunctionBody.addInstruction(OP_pushscope);
if (params.length == 1 && params[0] instanceof LiteralNode)
{
parameterFunctionBody.addInstruction(OP_pushstring, ((LiteralNode)params[0]).getValue());
parameterFunctionBody.addInstruction(OP_newarray, 1);
parameterFunctionBody.addInstruction(OP_returnvalue);
}
else
{
parameterFunctionBody.addInstruction(OP_findpropstrict, IMXMLTypeConstants.NAME_ARGUMENTERROR);
// Make a new ArugmentError with correct ID
parameterFunctionBody.addInstruction(OP_pushstring, "unimp param func");
parameterFunctionBody.pushNumericConstant(1069);
parameterFunctionBody.addInstruction(OP_constructprop,
new Object[] { IMXMLTypeConstants.NAME_ARGUMENTERROR, 2 });
// stack : ArgumentError
parameterFunctionBody.addInstruction(OP_throw);
}
// now generate the function
FunctionGeneratorHelper.generateFunction(emitter, mi, parameterFunctionBody);
// the instruction stream that will be executed by the ctor
// needs to push the new function onto the stack
ret.addInstruction(OP_newfunction, mi);
}
/**
* Set this to true so that the runtime trace diagnostic gets
* re-routed to use SocketHelper.
*
* This is needed because most of our unit tests don't "see" regular traces,
* because they use the SocketHelper
*/
private static boolean useSocketForTrace = false; // Must check in false
/**
* debugging function to all "trace"
*
* @param insns The instruction list being built.
* @param string is a constant string to trace
*/
public static void trace(InstructionList insns, String string)
{
pushString(insns, string);
trace(insns);
insns.addInstruction(OP_pop);
}
/**
* debugging function to call "trace"
*
* will use top of stack as string parameter, doesn't change stack depth
* @param insns The instruction list being built.
*/
public static void trace(InstructionList insns)
{
insns.addInstruction(OP_dup);
if (!useSocketForTrace)
{
System.out.println("** Warning: diagnostic trace not using socket. Will be invisible when running most unit tests");
insns.addInstruction(OP_findpropstrict, new Object[] { new Name("trace") } );
insns.addInstruction(OP_swap);
insns.addInstruction(OP_callpropvoid, new Object[] { new Name("trace"), 1 } );
}
else
{
insns.addInstruction(OP_findpropstrict, new Object[] { new Name("SocketHelper") } );
insns.addInstruction(OP_getproperty, new Object[] { new Name("SocketHelper") } );
insns.addInstruction(OP_swap);
insns.addInstruction(OP_callpropvoid, new Object[] { new Name("trace"), 1 } );
}
}
/**
* VM Context:
* on entry: local1=_bindings
* local2=_watchers
* on exit: stack = array[ bindings ]
*/
private static void makeArrayOfBindingsForWatcher(InstructionList insns, List<BindingInfo> bindingInfos)
{
assert bindingInfos.size() > 0; // for now this is always true
// in future....?
for (BindingInfo bindingInfo : bindingInfos)
{
insns.addInstruction(OP_getlocal1); // stack: _bindings
insns.pushNumericConstant(bindingInfo.getIndex()); // stack: index, _bindings
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP); // stack: _bindings[index]
}
// stack: _bindings[k], _bindings[x], ...
insns.addInstruction(OP_newarray, bindingInfos.size()); // stack: array[ _bindings[0] ]
}
/**
* Makes an AET name that represents all the open namespaces, but no "name"
* use this when we need to lookup a property, but we aren't sure what namespace it's in.
* example: this[propertyName]
* @param project
* @param scope
* @return
*/
private static Name makeNameForPropertyLookup( ICompilerProject project,
ASScope scope)
{
Set<INamespaceDefinition> namespaceSet = scope.getNamespaceSet(project);
ArrayList<Namespace> ns_set = new ArrayList<Namespace>(namespaceSet.size());
for (INamespaceDefinition namespace : namespaceSet)
ns_set.add(((NamespaceDefinition)namespace).getAETNamespace());
int nameKind = CONSTANT_MultinameL;
String name = null; // We are not providing a name here
Name n = new Name(nameKind, new Nsset(ns_set), name);
return n;
}
/**
* generate this function: function(propertyName:String):* { return
* target[propertyName]; } where target == "this"
*
* @param emitter The {@link IABCVisitor} that generated code is added to.
* @param project The {@link ICompilerProject} in which code is being
* generated.
* @param scope The {@link ASScope} that contains the data binding
* expression for which a getter is being generated.
* @return the MethodInfo that we can use to refer to the generated function
*/
public static MethodInfo generatePropertyGetterFunction(
IABCVisitor emitter,
ICompilerProject project,
ASScope scope
)
{
MethodInfo mi = new MethodInfo();
// TODO: consider making us a more useful name for these anonymous functions
// For debugging with swfdump, you can un-comment the line below:
//mi.setMethodName("propertyGetterFunction");
// Method returns "*", and takes one string parameter
mi.setReturnType( null);
Vector<Name> x = new Vector<Name>();
x.add( new Name(IASLanguageConstants.String));
mi.setParamTypes( x);
Name magicName = makeNameForPropertyLookup(project, scope);
InstructionList propertyGetterBody = new InstructionList();
// we are not a member, function, so local 0 does not have "this"
// Make sure no-one accidently uses it
propertyGetterBody.addInstruction(OP_kill, 0);
// find "this" of class we are servicing by finding "someone"
// who has the property we are looking for. The property name is in param 1
propertyGetterBody.addInstruction(OP_getlocal1);
propertyGetterBody.addInstruction(OP_findproperty, magicName);
// stack: this
// get the name of the property we are supposed to find
propertyGetterBody.addInstruction(OP_getlocal1);
// stack: propertyName, this
propertyGetterBody.addInstruction(OP_getproperty, magicName);
// stack: this[propertyName]
propertyGetterBody.addInstruction(OP_returnvalue);
FunctionGeneratorHelper.generateFunction(emitter, mi, propertyGetterBody);
return mi;
}
/** enumerate all the bindings we have created for this class, and execute each
* of them so that initial values get assigned to binding targets.
*
* VM context:
* one entry: nothing
* while executing:
* local1 = index (temporary local var)
* local2 = this._bindings.length;
*
* exit: nothing
*/
public static InstructionList fireInitialBindings()
{
InstructionList insns = new InstructionList();
log(insns, "fireInitialBinding");
//-----------------------------------------------------
// index = 0;
insns.addInstruction(OP_pushbyte, 0);
insns.addInstruction(OP_setlocal1);
//--------------------------------------------------------
// size = _bindings.size
insns.addInstruction(OP_getlocal0); // stack: this
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_BINDINGS); // stack: _bindings
insns.addInstruction(OP_dup); // stack: _bindings, _bindings
insns.addInstruction(OP_getproperty, new Name("length")); // stack: _bindings.length, _bindings
insns.addInstruction(OP_setlocal2);
// now stack: _bindings
//---------------------- LOOP OVER BINDINGS ----------------
Label label = new Label();
insns.addInstruction(OP_label);
insns.labelCurrent(label);
insns.addInstruction(OP_dup); // stack: _bindings, _bindings
insns.addInstruction(OP_getlocal1); // stack: index, _bindings, _bindings
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP); // stack: _bindings[index], _bindings
// now call execute on TOS
insns.addInstruction(OP_callpropvoid, IMXMLTypeConstants.ARG_EXECUTE); // stack: _bindings
// increment index
insns.addInstruction(OP_inclocal_i, 1);
// if (index < length) goto label
insns.addInstruction(OP_getlocal1); // stack: index, ....
insns.addInstruction(OP_getlocal2); // stack: size, index, ...
insns.addInstruction(OP_iflt, label); // stack: _bindings
insns.addInstruction(OP_pop); // TODO: is this necessary?
return insns;
}
public static final boolean doLog = false; // check in false, please
public static void log(String s)
{
if (doLog)
{
System.out.println("MXMLBindingDirHelp: " + s);
}
}
// this version logs to console, but also to ABC output (as debugfile)
public static void log(InstructionList insns, String s)
{
assert s != null;
if (doLog)
{
System.out.println("MXMLBindingDirHelp: " + s);
insns.addInstruction(OP_debugfile, s);
}
}
/**
* This is a static utility class. It is a mistake to try and instantiate one.
*/
private BindingCodeGenUtils() {}
}