blob: 7f28031309be4ef1ca3fbd9bb04e135d0de6b5d0 [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.as.codegen;
import org.apache.royale.abc.ABCConstants;
import org.apache.royale.abc.instructionlist.InstructionList;
import org.apache.royale.abc.semantics.MethodInfo;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.visitors.IABCVisitor;
import org.apache.royale.abc.visitors.ITraitVisitor;
import org.apache.royale.abc.visitors.ITraitsVisitor;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.common.ASModifier;
import org.apache.royale.compiler.common.ModifiersSet;
import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
import org.apache.royale.compiler.internal.definitions.FunctionDefinition;
import org.apache.royale.compiler.internal.definitions.TypeDefinitionBase;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.semantics.SemanticUtils;
import org.apache.royale.compiler.internal.tree.as.ClassNode;
import org.apache.royale.compiler.internal.tree.as.FunctionNode;
import org.apache.royale.compiler.internal.tree.as.InterfaceNode;
import org.apache.royale.compiler.internal.tree.as.NamespaceIdentifierNode;
import org.apache.royale.compiler.internal.tree.as.PackageNode;
import org.apache.royale.compiler.internal.tree.as.VariableNode;
import org.apache.royale.compiler.internal.tree.mxml.MXMLDocumentNode;
import org.apache.royale.compiler.problems.AbstractOutsideClassProblem;
import org.apache.royale.compiler.problems.DynamicNotOnClassProblem;
import org.apache.royale.compiler.problems.EmbedOnlyOnClassesAndVarsProblem;
import org.apache.royale.compiler.problems.FinalOutsideClassProblem;
import org.apache.royale.compiler.problems.GlobalBindablePropertyProblem;
import org.apache.royale.compiler.problems.InterfaceModifierProblem;
import org.apache.royale.compiler.problems.NativeNotOnFunctionProblem;
import org.apache.royale.compiler.problems.NativeVariableProblem;
import org.apache.royale.compiler.problems.OverrideOutsideClassProblem;
import org.apache.royale.compiler.problems.StaticOutsideClassProblem;
import org.apache.royale.compiler.problems.SyntaxProblem;
import org.apache.royale.compiler.problems.VirtualOutsideClassProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.mxml.IMXMLDocumentNode;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
/**
* A GlobalDirectiveProcessor translates directives at
* global scope into ABC.
*/
class GlobalDirectiveProcessor extends DirectiveProcessor
{
/** Instructions to implement directives as they're encountered. */
InstructionList directiveInsns = new InstructionList();
/** The top of the lexical scope chain. */
protected LexicalScope currentScope;
/** The AET emitter generating code for this script. */
protected IABCVisitor emitter;
/**
* Set when processing a package, which inplies some different
* strategies for declaring traits.
*/
protected boolean processingPackage = false;
private final List<GenerateFunctionInParallelResult> parallelCodeGenList;
/**
* {@link ExecutorService} used to generation function
* bodies in background threads.
*/
private final ExecutorService executorService;
/**
* Flag to enabling or disabling use of background threads
* to generate function bodies.
*/
private final boolean useParallelCodeGen;
/**
* @param current_scope the scope to use. It may be created a priori by the
* caller, so it's not created by nesting an enclosing scope.
* @param emitter the ABC emitter.
*/
GlobalDirectiveProcessor(LexicalScope current_scope, IABCVisitor emitter)
{
this(null, false, current_scope, emitter);
}
/**
* @param executorService {@link ExecutorService} used to schedule
* generation of function bodies on background threads. This may be null if
* useParallelCodeGen is false.
* @param useParallelCodeGen Flag to enabling or disabling use of background
* threads to generate function bodies.
* @param current_scope the scope to use. It may be created a priori by the
* caller, so it's not created by nesting an enclosing scope.
* @param emitter the ABC emitter.
*/
GlobalDirectiveProcessor(ExecutorService executorService, boolean useParallelCodeGen, LexicalScope current_scope, IABCVisitor emitter)
{
super(current_scope.getProblems());
assert (!useParallelCodeGen) || (executorService != null) : "Parallel code generation requires access to an ExecutorService";
this.currentScope = current_scope;
this.emitter = emitter;
this.parallelCodeGenList = new LinkedList<GenerateFunctionInParallelResult>();
this.executorService = executorService;
this.useParallelCodeGen = useParallelCodeGen;
}
/**
* Start generation of the specified function in a background thread if
* parallel code generation is enabled, otherwise generate code for the
* specified function in the calling thread
*
* @param f Function to generate code for.
* @return {@link MethodInfo} for the specified function.
*/
private MethodInfo startFunctionGeneration(FunctionNode f)
{
if (this.useParallelCodeGen)
{
assert this.executorService != null : "Parallel codegen requires an ExecutorService!";
final GenerateFunctionInParallelResult parallelCodeGen =
currentScope.getGenerator().generateFunctionInParallel(this.executorService, f, this.currentScope);
this.parallelCodeGenList.add(parallelCodeGen);
return parallelCodeGen.getMethodInfo();
}
else
{
f.parseFunctionBody(currentScope.getProblems());
return currentScope.getGenerator().generateFunction(f, this.currentScope, null, null);
}
}
/**
* Declare a function.
*/
@Override
void declareFunction(FunctionNode f)
{
verifyFunctionModifiers(f);
final MethodInfo mi = startFunctionGeneration(f);
if ( mi != null )
{
FunctionDefinition funcDef = f.getDefinition();
Name funcName = funcDef.getMName(this.currentScope.getProject());
SemanticUtils.checkScopedToDefaultNamespaceProblem(this.currentScope, f, funcDef, null);
boolean conflictsWithOtherDefinition = false;
if ( funcName == null )
{
// getMName() emitted a diagnostic,
// repair and continue.
funcName = new Name("<invalid>");
}
else
{
conflictsWithOtherDefinition = currentScope.getMethodBodySemanticChecker().checkFunctionForConflictingDefinitions(f, funcDef);
}
ITraitVisitor tv = null;
int traitKind = this.processingPackage?
DirectiveProcessor.functionTraitKind(f, ABCConstants.TRAIT_Method):
DirectiveProcessor.functionTraitKind(f, ABCConstants.TRAIT_Var);
if (! this.currentScope.traitsVisitor.getTraits().containsTrait(traitKind, funcName) )
{
this.currentScope.declareVariableName(funcName);
if ( ! this.processingPackage )
{
if ( f.isGetter() || f.isSetter() )
{
tv = this.currentScope.traitsVisitor.visitMethodTrait(
traitKind,
funcName,
ITraitsVisitor.RUNTIME_DISP_ID,
mi);
assert tv != null : "visitMethodTrait should never return null!";
}
else
{
tv = this.currentScope.traitsVisitor.visitSlotTrait(
traitKind,
funcName,
ITraitsVisitor.RUNTIME_SLOT,
LexicalScope.anyType,
LexicalScope.noInitializer);
assert tv != null : "visitSlotTrait should never return null!";
this.currentScope.getInitInstructions().addInstruction(ABCConstants.OP_getglobalscope);
this.currentScope.getInitInstructions().addInstruction(ABCConstants.OP_newfunction, mi);
this.currentScope.getInitInstructions().addInstruction(ABCConstants.OP_setproperty, funcName);
}
}
else
{
tv = this.currentScope.traitsVisitor.visitMethodTrait(traitKind, funcName, 0, mi);
assert tv != null : "visitMethodTrait should never return null!";
}
if ( tv != null )
{
this.currentScope.processMetadata(tv, funcDef.getAllMetaTags());
tv.visitEnd();
}
}
else if (!conflictsWithOtherDefinition)
{
// Duplicate that is not a "conflict" - must be a global, where dupes are "allowed" as
// per ECMA
// In strict mode (only) we issue a warning for this. Which is the behavior of the old compiler
// (as well as being the "right" thing to do)
// But - our simple criteria for "conflicts with other definitions" will give a false positive for
// Getter/setter pairs, so only emit the warning if this is not the case.
//
// Updater: the warning is detected elsewhere, so all we are doing here is
// generating code to create the new function as per ECMAS
ICompilerProject project = currentScope.getProject();
List<IDefinition> defs = SemanticUtils.findPotentialFunctionConflicts(project, funcDef);
if (!SemanticUtils.isGetterSetterPair(defs, project))
{
// This is a new funciton, so generate code for it
// Add initialization logic to the init instructions.
if ( ! ( this.processingPackage || f.isGetter() || f.isSetter() ) )
{
this.currentScope.getInitInstructions().addInstruction(ABCConstants.OP_getglobalscope);
this.currentScope.getInitInstructions().addInstruction(ABCConstants.OP_newfunction, mi);
this.currentScope.getInitInstructions().addInstruction(ABCConstants.OP_setproperty, funcName);
}
}
}
}
}
/**
* validate the modifiers used on a function decl.
*/
protected void verifyFunctionModifiers(FunctionNode f)
{
ModifiersSet modifiersSet = f.getModifiers();
if (modifiersSet == null)
return;
ASModifier[] modifiers = modifiersSet.getAllModifiers();
IExpressionNode site = f.getNameExpressionNode();
for (ASModifier modifier : modifiers)
{
verifyModifier(site, modifier);
}
currentScope.getMethodBodySemanticChecker().checkForDuplicateModifiers(f);
}
/**
* validate the modifiers used on a var decl
*/
protected void verifyVariableModifiers(VariableNode v)
{
ModifiersSet modifiersSet = v.getModifiers();
if (modifiersSet == null)
return;
ASModifier[] modifiers = modifiersSet.getAllModifiers();
IExpressionNode site = v.getNameExpressionNode();
for (ASModifier modifier : modifiers)
{
// native on a variable generates a different error
if (modifier == ASModifier.NATIVE)
{
currentScope.addProblem(new NativeVariableProblem(site));
}
else if( modifier == ASModifier.DYNAMIC )
{
currentScope.addProblem(new DynamicNotOnClassProblem(site));
}
else
{
verifyModifier(site, modifier);
}
}
currentScope.getMethodBodySemanticChecker().checkForDuplicateModifiers(v);
}
/**
* validate the modifiers used on a class decl
*/
protected void verifyClassModifiers(ClassNode c)
{
IExpressionNode site = c.getNameExpressionNode();
ModifiersSet modifiersSet = c.getModifiers();
if (modifiersSet != null)
{
ASModifier[] modifiers = modifiersSet.getAllModifiers();
for (ASModifier modifier : modifiers)
{
// final, dynamic, and abstract allowed on a class
if( modifier == ASModifier.FINAL || modifier == ASModifier.DYNAMIC || modifier == ASModifier.ABSTRACT)
{
continue;
}
// native generates different error for class/interface
else if (modifier == ASModifier.NATIVE)
{
currentScope.addProblem(new NativeNotOnFunctionProblem(site) );
}
else
{
verifyModifier(site, modifier);
}
}
currentScope.getMethodBodySemanticChecker().checkForDuplicateModifiers(c);
}
IDefinition classDef = c.getDefinition();
if (classDef.isAbstract())
{
if (currentScope.getProject().getAllowAbstractClasses())
{
if(!SemanticUtils.canBeAbstract(c, currentScope.getProject()))
{
currentScope.addProblem(new AbstractOutsideClassProblem(site) );
}
}
else
{
currentScope.addProblem(new SyntaxProblem(site, IASKeywordConstants.ABSTRACT));
}
}
}
/**
* validate the skinning data used on a class decl
*/
protected void verifySkinning(ClassDefinition classDefinition)
{
// call these skinPart/skinState methods to collect any problems
// with the metadata.
classDefinition.getSkinParts(currentScope.getProblems());
classDefinition.getSkinStates(currentScope.getProblems());
classDefinition.verifyHostComponent((CompilerProject)currentScope.getProject(), currentScope.getProblems());
}
/**
* Validate the modifiers used on an interface decl
*/
protected void verifyInterfaceModifiers(InterfaceNode i)
{
ModifiersSet modifiersSet = i.getModifiers();
if (modifiersSet == null)
return;
ASModifier[] modifiers = modifiersSet.getAllModifiers();
IExpressionNode site = i.getNameExpressionNode();
for (ASModifier modifier : modifiers)
{
// final generates a different error for an interface
if( modifier == ASModifier.FINAL || modifier == ASModifier.DYNAMIC)
{
currentScope.addProblem(new InterfaceModifierProblem(site, modifier.toString()));
}
// native generates different error for class/interface
else if (modifier == ASModifier.NATIVE)
{
currentScope.addProblem(new NativeNotOnFunctionProblem(site) );
// ASC also emits this for good measure.
currentScope.addProblem(new InterfaceModifierProblem(site, modifier.toString()));
}
else
{
verifyModifier(site, modifier);
}
}
currentScope.getMethodBodySemanticChecker().checkForDuplicateModifiers(i);
}
/**
* verify a modifier used in global scope - issues errors that are common for all different decls
* at this scope.
* @param site location to use if a problem is reported
* @param modifier the modifier to check
*/
protected void verifyModifier(IASNode site, ASModifier modifier)
{
if( modifier == ASModifier.STATIC )
currentScope.addProblem(new StaticOutsideClassProblem(site));
else if( modifier == ASModifier.FINAL )
currentScope.addProblem(new FinalOutsideClassProblem(site));
else if( modifier == ASModifier.OVERRIDE )
currentScope.addProblem(new OverrideOutsideClassProblem(site));
else if( modifier == ASModifier.VIRTUAL )
currentScope.addProblem(new VirtualOutsideClassProblem(site));
else if( modifier == ASModifier.ABSTRACT )
{
if(currentScope.getProject().getAllowAbstractClasses())
{
currentScope.addProblem(new AbstractOutsideClassProblem(site));
}
else
{
currentScope.addProblem(new SyntaxProblem(site, IASKeywordConstants.ABSTRACT));
}
}
}
/**
* Declare a class.
*/
@Override
void declareClass(ClassNode c)
{
verifyClassModifiers(c);
verifySkinning(c.getDefinition());
currentScope.getMethodBodySemanticChecker().checkNamespaceOfDefinition(c, c.getDefinition(), currentScope.getProject());
if (c.getDefinition().getConstructor().isImplicit()) {
//check that the implicit super call is to a default constructor
//otherwise this class needs to have an explicit constructor with an explicit super call
//checks for problem: Error: No default constructor found in base class {base class}
currentScope.getMethodBodySemanticChecker().checkDefaultSuperCall(c.getDefinition().getConstructor().getNode());
}
ClassDirectiveProcessor cp = new ClassDirectiveProcessor(c, this.currentScope, this.emitter);
cp.traverse(c.getScopedNode());
cp.finishClassDefinition();
}
/**
* Declare an interface.
*/
@Override
void declareInterface(InterfaceNode interface_ast)
{
verifyInterfaceModifiers(interface_ast);
currentScope.getMethodBodySemanticChecker().checkNamespaceOfDefinition(interface_ast, interface_ast.getDefinition(), currentScope.getProject());
InterfaceDirectiveProcessor ip = new InterfaceDirectiveProcessor(interface_ast, this.currentScope, this.emitter);
ip.traverse(interface_ast.getScopedNode());
ip.finishInterfaceDefinition();
}
/**
* "Declare" a package.
*/
@Override
void declarePackage(PackageNode p)
{
try
{
this.processingPackage = true;
traverse(p.getScopedNode());
}
finally
{
this.processingPackage = false;
}
}
/**
* Declare a variable.
*/
@Override
void declareVariable(VariableNode var)
{
verifyVariableModifiers(var);
if (var.getMetaTags() != null && var.getMetaTags().hasTagByName(IMetaAttributeConstants.ATTRIBUTE_EMBED))
{
currentScope.addProblem(new EmbedOnlyOnClassesAndVarsProblem(var));
}
DefinitionBase varDef = var.getDefinition();
SemanticUtils.checkScopedToDefaultNamespaceProblem(this.currentScope, var, varDef, null);
if ( var.hasModifier(ASModifier.STATIC))
{
ICompilerProject project = this.currentScope.getProject();
Name var_name = varDef.getMName(project);
TypeDefinitionBase typeDef = (TypeDefinitionBase)varDef.resolveType(project);
Name var_type = typeDef != null ? typeDef.getMName(project) : null;
// It's not necessary to check for duplicates
// in the traits because that is a semantic error
// in this context.
ITraitVisitor tv = this.currentScope.traitsVisitor.visitSlotTrait(ABCConstants.TRAIT_Const, var_name, ITraitsVisitor.RUNTIME_SLOT, var_type, LexicalScope.noInitializer);
this.currentScope.declareVariableName(var_name);
this.currentScope.processMetadata(tv, varDef.getAllMetaTags());
tv.visitEnd();
}
// Run the BURM to process any initialization instructions.
processDirective(var);
}
/**
* Declare a bindable variable.
*/
@Override
void declareBindableVariable(VariableNode var)
{
currentScope.addProblem(new GlobalBindablePropertyProblem(var));
}
/**
* Declare an MXML document.
*/
@Override
void declareMXMLDocument(IMXMLDocumentNode d)
{
verifySkinning((ClassDefinition)d.getDefinition());
MXMLClassDirectiveProcessor dp = new MXMLClassDirectiveProcessor(d, this.currentScope, this.emitter);
((MXMLDocumentNode)d).cdp = dp;
dp.processMainClassDefinitionNode(d);
dp.finishClassDefinition();
}
/**
* Process a namespace directive.
*/
@Override
void processNamespaceIdentifierDirective(NamespaceIdentifierNode ns)
{
traverse(ns);
}
/**
* Process a random directive, which at the global level
* is probably a loose instruction.
*/
@Override
void processDirective(IASNode n)
{
// Handle a loose statement.
InstructionList stmt_insns = currentScope.getGenerator().generateInstructions(n, CmcEmitter.__statement_NT, currentScope);
if ( stmt_insns != null )
directiveInsns.addAll(stmt_insns);
}
/**
* Block until all function generation is complete and flush all ABC data to
* the {@link IABCVisitor} we are generating code into.
*
* @throws InterruptedException
*/
void finish() throws InterruptedException
{
try
{
for (GenerateFunctionInParallelResult parallelCodeGen : this.parallelCodeGenList)
{
parallelCodeGen.finish();
}
}
catch (ExecutionException ex)
{
throw new RuntimeException(ex);
}
}
}