blob: c5a8105ae282c4f222e55ed557859ec73feba066 [file] [log] [blame]
header
{
/*
*
* 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.parsing.as;
/*
* This file is generated from ASTreeAssembler.g
* DO NOT MAKE EDITS DIRECTLY TO THIS FILE. THEY WILL BE LOST WHEN THE FILE IS GENERATED AGAIN!!!
*/
import java.util.ArrayList;
import java.util.List;
import org.apache.royale.compiler.tree.as.*;
import org.apache.royale.compiler.workspaces.IWorkspace;
import org.apache.royale.compiler.parsing.IASToken;
import org.apache.royale.compiler.parsing.IASToken.ASTokenKind;
import org.apache.royale.compiler.tree.as.IContainerNode.ContainerType;
import org.apache.royale.compiler.tree.as.ILiteralNode.LiteralType;
import org.apache.royale.compiler.internal.tree.as.*;
import org.apache.royale.compiler.internal.tree.as.metadata.*;
import org.apache.royale.compiler.asdoc.IASParserASDocDelegate;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.*;
}
/**
* ActionScript3 parser grammar. It consumes ASTokens and produces IASNode AST.
* The number of tokens in a single syntactic predicate can not be greater than
* StreamingTokenBuffer.REWIND_BUFFER_SIZE.
*
* @see <a href="https://zerowing.corp.adobe.com/display/FlashPlayer/ActionScript+Language+Specification">ActionScript Language Syntax Specification</a>
*/
class ASParser extends Parser("org.apache.royale.compiler.internal.parsing.as.BaseASParser");
options
{
exportVocab = AS;
defaultErrorHandler = false;
}
tokens
{
// These hidden tokens are matched by the raw tokenizer but are not sent to the parser.
HIDDEN_TOKEN_COMMENT;
HIDDEN_TOKEN_SINGLE_LINE_COMMENT;
HIDDEN_TOKEN_STAR_ASSIGNMENT;
HIDDEN_TOKEN_BUILTIN_NS;
HIDDEN_TOKEN_MULTI_LINE_COMMENT;
// These two tokens are used by code model's ASDoc tokenizer.
TOKEN_ASDOC_TAG;
TOKEN_ASDOC_TEXT;
// These tokens are transformed from reserved words by StreamingASTokenizer.
TOKEN_RESERVED_WORD_EACH;
TOKEN_RESERVED_WORD_CONFIG;
TOKEN_KEYWORD_INCLUDE;
TOKEN_RESERVED_WORD_GOTO;
}
{
/**
* Construct an AS3 parser from a token buffer.
*/
public ASParser(IWorkspace workspace, IRepairingTokenBuffer buffer)
{
super(workspace, buffer);
tokenNames = _tokenNames;
}
/**
* Construct an AS3 parser for parsing command line config args
*/
public ASParser(IWorkspace workspace, IRepairingTokenBuffer buffer, boolean parsingProjectConfigVariables)
{
super(workspace, buffer, parsingProjectConfigVariables);
tokenNames = _tokenNames;
}
}
/**
* Matches multiple directives. This layer is added to handle parsing error in directives.
*/
fileLevelDirectives[ContainerNode c]
: (directive[c, NO_END_TOKEN])*
;
exception catch [RecognitionException parserError] { handleParsingError(parserError); }
/**
* Matches a "directive" level input.
* The first couple of alternatives gated with semantic predicates are used to
* either disambiguate inputs, or to trap erroneous syntax.
*/
directive[ContainerNode c, int endToken]
{
final ASToken lt1 = LT(1);
final ASToken lt2 = LT(2);
final int la1 = LA(1);
final int la2 = LA(2);
final int la3 = LA(3);
final int la4 = LA(4);
}
: { la1 == TOKEN_BLOCK_OPEN }? groupDirective[c, endToken]
| { la1 == TOKEN_RESERVED_WORD_NAMESPACE && la2 == TOKEN_PAREN_OPEN }? statement[c, endToken]
| { la1 == TOKEN_IDENTIFIER && la2 == TOKEN_NAMESPACE_ANNOTATION && lt1.getLine() == lt2.getLine() }?
// Skip over the user-defined namespace name and continue.
nsT:TOKEN_IDENTIFIER attributedDefinition[c]
{ trapInvalidNamespaceAttribute((ASToken)nsT); }
| asDocComment
| importDirective[c]
| useNamespaceDirective[c]
| { la1 == TOKEN_NAMESPACE_NAME &&
la2 == TOKEN_OPERATOR_NS_QUALIFIER &&
la3 == TOKEN_IDENTIFIER &&
la4 == TOKEN_BLOCK_OPEN}?
// ns::var { ... }
groupDirectiveWithConfigVariable[c, endToken]
| { la1 == TOKEN_NAMESPACE_NAME &&
la2 == TOKEN_OPERATOR_NS_QUALIFIER &&
la3 == TOKEN_NAMESPACE_ANNOTATION }?
// ns::var private var foo:int;
attributedDefinition[c]
| { la1 == TOKEN_NAMESPACE_NAME &&
la2 == TOKEN_OPERATOR_NS_QUALIFIER }?
// "ns::var" or "ns::[x, y]"
statement[c,endToken]
| { !isFunctionClosure() }?
attributedDefinition[c]
| packageDirective[c]
| statement[c,endToken]
| configNamespace[c]
| includeDirective
// The following alternatives are error traps
| fT:TOKEN_KEYWORD_FINALLY { reportUnexpectedTokenProblem((ASToken)fT); }
| cT:TOKEN_KEYWORD_CATCH { reportUnexpectedTokenProblem((ASToken)cT); }
;
exception catch [RecognitionException ex]
{
handleParsingError(ex, endToken);
consumeUntilKeywordOrIdentifier(endToken);
}
/**
* Include processing is usually done in the lexer. However, this rule is added
* in order to support code model partitioner whose tokenizer is set to not
* "follow includes". In a normal AS3 compilation, the parser would never see
* the "include" token.
*/
includeDirective
: TOKEN_KEYWORD_INCLUDE TOKEN_LITERAL_STRING
;
/**
* Matches an attributed definition. An "attribute" can be a namespace or a
* modifier.
*/
attributedDefinition[ContainerNode c]
{
List<INamespaceDecorationNode> namespaceAttributes = new ArrayList<INamespaceDecorationNode>();
List<ModifierNode> modifiers = new ArrayList<ModifierNode>();
INamespaceDecorationNode namespaceAttr = null;
IASNode lastChild = null;
if(c.getChildCount() > 0)
{
lastChild = c.getChild(c.getChildCount() - 1);
}
ConfigConditionBlockNode configBlock = null;
boolean enabled = isDefinitionEnabled(c);
boolean eval = true;
}
: (eval=configConditionOfDefinition)?
{
// A configuration condition variable can either be matched by
// the above rule or be transformed into a LiteralNode of boolean
// type. If either is evaluated to false, the definition is disabled.
enabled &= eval;
if (!enabled)
{
// previously, we removed the entire definition from the AST,
// but some IDEs can "fade out" disabled definitions. by adding
// the children to a ConfigConditionBlockNode, they can be seen
// in the AST, but ignored when generating compiled output. -JT
configBlock = new ConfigConditionBlockNode(false);
if(lastChild != null && c.getRemovedConditionalCompileNode())
{
configBlock.startBefore(lastChild);
}
c.addItem(configBlock);
c = configBlock;
}
}
(attribute[modifiers, namespaceAttributes])*
{
// Verify that at most one namespace attribute is matched.
verifyNamespaceAttributes(namespaceAttributes);
if (!namespaceAttributes.isEmpty())
namespaceAttr = namespaceAttributes.get(0);
}
definition[c, namespaceAttr, modifiers]
{
if(configBlock != null)
{
// since this config block doesn't have braces {}, check if
// there's a semicolon at the end of the definition
Token prevToken = buffer.previous();
if(prevToken.getType() == TOKEN_SEMICOLON)
{
configBlock.endAfter(prevToken);
}
}
}
exception catch [RecognitionException ex]
{
handleParsingError(ex);
}
;
/**
* Matches an attribute such as:
* - Modifiers: dynamic, final, native, override, static, virtual, abstract.
* - Namespace names.
* - Reserved namespace names: internal, private, public, protected.
*
* A definition can have at most one "namespace attribute".
* The matched attribute is added to the lists passed in as arguments.
*/
attribute [List<ModifierNode> modifiers, List<INamespaceDecorationNode> namespaceAttributes]
{
ExpressionNodeBase namespaceNode = null;
ExpressionNodeBase configAsNamespaceNode = null;
ModifierNode modifierNode = null;
}
: modifierNode=modifierAttribute
{
if (modifierNode != null)
modifiers.add(modifierNode);
}
| namespaceNode=namespaceModifier
{
if (namespaceNode instanceof INamespaceDecorationNode)
namespaceAttributes.add((INamespaceDecorationNode) namespaceNode);
}
| configAsNamespaceNode=configConditionAsNamespaceModifier
{
if (configAsNamespaceNode instanceof INamespaceDecorationNode)
namespaceAttributes.add((INamespaceDecorationNode) configAsNamespaceNode);
}
;
configConditionAsNamespaceModifier returns [ExpressionNodeBase n]
{
n = null;
}
: ns:TOKEN_NAMESPACE_NAME op:TOKEN_OPERATOR_NS_QUALIFIER id:TOKEN_NAMESPACE_ANNOTATION
{ final NamespaceIdentifierNode nsNode = new NamespaceIdentifierNode((ASToken)ns);
nsNode.setIsConfigNamespace(isConfigNamespace(nsNode));
final IdentifierNode idNode = new IdentifierNode((ASToken)id);
final IdentifierNode idNode2 = (IdentifierNode)transformToNSAccessExpression(nsNode, (ASToken) op, idNode);
n = new NamespaceIdentifierNode(idNode2.getName());
n = n.copyForInitializer(null);
n.setSourcePath(nsNode.getSourcePath());
n.setLine(nsNode.getLine());
n.setColumn(nsNode.getColumn());
n.setEndLine(idNode.getEndLine());
n.setEndColumn(idNode.getEndColumn());
n.setStart(nsNode.getStart());
n.setEnd(idNode.getEnd());
}
;
/**
* Matches a definition of variable, function, namespace, class or interface.
*/
definition[ContainerNode c, INamespaceDecorationNode ns, List<ModifierNode> modList]
: variableDefinition[c, ns, modList]
| functionDefinition[c, ns, modList]
| namespaceDefinition[c, ns, modList]
| classDefinition[c, ns, modList]
| interfaceDefinition[c, ns, modList]
;
/**
* Matches a "group" in a "group directive".
* Entering a "Block" leaves the global context, but entering a "Group" doesn't.
*/
groupDirective[ContainerNode c, int endToken]
{
BlockNode b = new BlockNode();
enterGroup();
}
: openT:TOKEN_BLOCK_OPEN { b.startAfter(openT); }
(directive[c, endToken])*
{ if(b.getChildCount() > 0) c.addItem(b); }
closeT:TOKEN_BLOCK_CLOSE { b.endBefore(closeT); leaveGroup(); }
;
/**
* Matches a config condition such as "CONFIG::debug". This rule only applies
* to blocks gated with configuration variable.
*
* @return Evaluated result of the configuration variable.
*/
configCondition returns [boolean result]
{
result = false;
}
: ns:TOKEN_NAMESPACE_NAME op:TOKEN_OPERATOR_NS_QUALIFIER id:TOKEN_IDENTIFIER
{
result = evaluateConfigurationVariable(new NamespaceIdentifierNode((ASToken)ns), (ASToken) op, new IdentifierNode((ASToken)id));
}
;
/**
* Similar to "configCondition", only that the token type after "::" is
* "TOKEN_NAMESPACE_ANNOTATION". This rule only applies to "attributed
* definitions".
*/
configConditionOfDefinition returns [boolean result]
{
result = false;
}
: ns:TOKEN_NAMESPACE_NAME op:TOKEN_OPERATOR_NS_QUALIFIER id:TOKEN_NAMESPACE_ANNOTATION
{
result = evaluateConfigurationVariable(new NamespaceIdentifierNode((ASToken)ns), (ASToken) op, new IdentifierNode((ASToken)id));
}
;
/**
* Matches a group of directives gated with configuration variable.
*
* CONFIG::debug {
* trace("debugging code");
* }
*
* If the configuration variable evaluates to false, the following block will
* not be added to the resulting AST.
*/
groupDirectiveWithConfigVariable [ContainerNode c, int endToken]
{
boolean b;
ConfigConditionBlockNode block;
final Token lt = LT(1);
}
: b=configCondition
{
block = new ConfigConditionBlockNode(b);
block.startBefore(lt);
c.addItem(block);
}
groupDirective[block, endToken]
{
Token prevToken = buffer.previous();
if(prevToken.getType() == endToken)
{
block.endAfter(prevToken);
}
}
;
/**
* Matches a statement.
*
* Note that the "SuperStatement" in ASL syntax spec is not explicitly defined.
* The "super" statements like <code>super(args);</code> are matched as regular
* "call" expressions.
*/
statement[ContainerNode c, int exitCondition]
{
final int la1 = LA(1);
final int la2 = LA(2);
}
: breakOrContinueStatement[c]
| defaultXMLNamespaceStatement[c]
| gotoStatement[c]
| emptyStatement
| { la1 == TOKEN_IDENTIFIER && la2 == TOKEN_COLON }? labeledStatement[c, exitCondition]
| { la1 != TOKEN_SQUARE_OPEN &&
la1 != TOKEN_OPERATOR_LESS_THAN &&
la1 != TOKEN_BLOCK_OPEN }? expressionStatement[c]
| forStatement[c]
| ifStatement[c]
| meta[c]
| returnStatement[c]
| switchStatement[c]
| throwsStatement[c]
| tryStatement[c]
| whileStatement[c]
| doStatement[c]
| withStatement[c]
;
exception catch [RecognitionException ex]
{
handleParsingError(ex);
consumeUntilKeywordOrIdentifier(exitCondition);
}
/**
* Matches an "expression statement". The ASL syntax specification says the
* lookahead can not be "[", "{" or "function". Legacy code requires that "<"
* be excluded as well.
*/
expressionStatement[ContainerNode c]
{
ExpressionNodeBase e = null;
if (LA(1) == TOKEN_KEYWORD_FUNCTION)
{
// Recover: continue parsing function as an anonymous function.
logSyntaxError(LT(1));
}
}
: e=expression
{
c.addItem(e);
if (!matchOptionalSemicolon())
{
recoverFromExpressionStatementMissingSemicolon(e);
}
}
;
/**
* <h1>From ASL syntax spec:</h1>
* <quote>
* InnerSubstatement is defined in the grammar for the sole purpose of
* specifying side conditions that disambiguate various syntactic ambiguities
* in a context-sensitive manner specified in Section 5.
* </quote>
*
* It is only used in "do statement" and "if statement" to loosen the following
* two cases allowed by AS3 but not by ECMA5.
*
* <code>
* do x++ while (x < 10); // ES5 would require a ; after x++
* if (x > 10) x++ else y++; // ES5 would require a ; after x++
* <code>
*/
innerSubstatement[ContainerNode c]
: substatement[c] { afterInnerSubstatement(); }
;
/**
* Matches a sub-statement.
*/
substatement[ContainerNode c]
: ( { LA(1) != TOKEN_BLOCK_OPEN }? statement[c,NO_END_TOKEN]
| block[c]
| variableDefinition[c, null, null]
)
{
if (c.getContainerType() == ContainerType.SYNTHESIZED)
c.setContainerType(ContainerType.IMPLICIT);
}
;
/**
* Matches a "labeled statement". For example:
*
* innerLoop: x++;
*
*/
labeledStatement[ContainerNode c, int exitCondition]
{
LabeledStatementNode statementNode = null;
ASToken offendingNSToken = null;
}
: labelT:TOKEN_IDENTIFIER TOKEN_COLON
{
final NonResolvingIdentifierNode labelNode =
new NonResolvingIdentifierNode(
labelT != null ? labelT.getText() : "",
labelT);
statementNode = new LabeledStatementNode(labelNode);
c.addItem(statementNode);
}
( { LA(1) == TOKEN_RESERVED_WORD_NAMESPACE && LA(2) == TOKEN_IDENTIFIER }?
{ offendingNSToken = LT(1); }
namespaceDefinition[c, null, null]
{ trapInvalidSubstatement(offendingNSToken); }
| substatement[statementNode.getLabeledStatement()]
)
;
/**
* Matches a block.
*/
block[ContainerNode b]
: openT:TOKEN_BLOCK_OPEN
{
b.startAfter(openT);
b.setContainerType(ContainerType.BRACES);
}
(directive[b, TOKEN_BLOCK_CLOSE])*
closeT:TOKEN_BLOCK_CLOSE { b.endBefore(closeT); }
;
exception catch [RecognitionException ex]
{
handleParsingError(ex);
consumeUntilKeywordOrIdentifier(TOKEN_BLOCK_CLOSE);
endContainerAtError(ex, b);
}
/**
* Matches an import directive.
*
* import flash.display.Sprite;
* import flash.events.*;
*/
importDirective[ContainerNode c]
{
ExpressionNodeBase n = null;
ImportNode i = null;
IIdentifierNode alias = null;
}
: importT:TOKEN_KEYWORD_IMPORT
{
i = new ImportNode((ExpressionNodeBase) null);
i.startBefore(importT);
i.endAfter(importT);
c.addItem(i);
if (LA(2) == TOKEN_OPERATOR_ASSIGNMENT)
{
alias = identifier();
match(TOKEN_OPERATOR_ASSIGNMENT);
}
}
n=importName
{
if(n != null) {
if(alias != null) {
i.setImportAlias(alias.getName());
}
i.setImportTarget(n);
i.setEnd(n.getEnd());
encounteredImport(i);
}
else {
i.setImportTarget(new IdentifierNode(""));
}
matchOptionalSemicolon();
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches "use namespace ns" directive.
*/
useNamespaceDirective[ContainerNode c]
{
ExpressionNodeBase n = null;
UseNamespaceNode u = null;
}
: useT:TOKEN_KEYWORD_USE { u = new UseNamespaceNode(n); u.startBefore(useT); c.addItem(u); }
nsT:TOKEN_RESERVED_WORD_NAMESPACE { u.endAfter(nsT); } n=restrictedName
{
u.setTargetNamespace(n);
u.setEnd(n.getEnd());
matchOptionalSemicolon();
}
;
exception catch [RecognitionException ex]
{
if (u != null && u.getTargetNamespace() == null)
u.setTargetNamespace(handleMissingIdentifier(ex));
else
handleParsingError(ex);
}
/**
* Matches an ASDoc block.
*/
asDocComment
: asdocT:TOKEN_ASDOC_COMMENT
{
asDocDelegate.setCurrentASDocToken(asdocT);
}
;
/**
* Matches a "modifier attribute" such as "final", "dynamic", "override",
* "static" or "native".
*/
modifierAttribute returns [ModifierNode modifierNode]
{
modifierNode = null;
final ASToken modifierT = LT(1);
}
: ( TOKEN_MODIFIER_FINAL
| TOKEN_MODIFIER_DYNAMIC
| TOKEN_MODIFIER_OVERRIDE
| TOKEN_MODIFIER_STATIC
| TOKEN_MODIFIER_NATIVE
| TOKEN_MODIFIER_VIRTUAL
| TOKEN_MODIFIER_ABSTRACT
)
{ modifierNode = new ModifierNode((ASToken) modifierT); }
;
/**
* Matches a namespace modifier on an "attributed definition".
*/
namespaceModifier returns[ExpressionNodeBase n]
{
n = null;
}
: nsPart1T:TOKEN_NAMESPACE_ANNOTATION
{
// If our text token is a member access, then build a normal
// identifier. Otherwise, build a NS specific one.
if (LA(1) == TOKEN_OPERATOR_MEMBER_ACCESS)
{
n = new IdentifierNode((ASToken)nsPart1T) ;
}
else
{
final NamespaceIdentifierNode nsNode = new NamespaceIdentifierNode((ASToken)nsPart1T);
nsNode.setIsConfigNamespace(isConfigNamespace(nsNode));
n = nsNode;
}
}
(
dotT:TOKEN_OPERATOR_MEMBER_ACCESS
(
nsNameT:TOKEN_NAMESPACE_ANNOTATION
{
IdentifierNode id = new IdentifierNode((ASToken)nsNameT);
n = new FullNameNode(n, (ASToken) dotT, id);
}
)
)*
{
if (n instanceof FullNameNode)
n = new QualifiedNamespaceExpressionNode((FullNameNode)n);
}
;
/**
* Matches a "metadata statement".
*
* [ExcludeClass()]
* [Bindable]
*/
meta[ContainerNode c]
{
ArrayLiteralNode al = new ArrayLiteralNode();
final ASToken lt = LT(1);
}
: attributeT:TOKEN_ATTRIBUTE
{
// Note that a separate parser is invoked here for metadata.
parseMetadata(attributeT, errors);
preCheckMetadata(attributeT, c);
}
| { isIncompleteMetadataTagOnDefinition() }?
TOKEN_SQUARE_OPEN
// Error trap for "[" before a definition item.
{ logSyntaxError(LT(1)); }
| arrayInitializer[al]
// This is statement-level metadata.
{
// Synthesize a MetaTagsNode to hold the metadata offsets.
currentAttributes = new MetaTagsNode();
currentAttributes.span(al, al);
preCheckMetadata(lt, c);
}
;
exception catch [RecognitionException ex]
{
recoverFromMetadataTag(c, al);
}
/**
* Matches a "config namespace foo" directive.
*/
configNamespace[ContainerNode c]
: TOKEN_RESERVED_WORD_CONFIG TOKEN_RESERVED_WORD_NAMESPACE configN:TOKEN_IDENTIFIER
{
NamespaceNode cNode = new ConfigNamespaceNode(new IdentifierNode((ASToken)configN));
addConditionalCompilationNamespace(cNode);
matchOptionalSemicolon();
}
;
exception catch [RecognitionException ex]
{ handleParsingError(ex); }
/**
* Matches a "package" block.
*
* package mx.controls { ... }
*
*/
packageDirective[ContainerNode c]
{
PackageNode p = null;
ExpressionNodeBase name = null;
BlockNode b = null;
}
: packageT:TOKEN_KEYWORD_PACKAGE { enterPackage((ASToken)packageT); }
(name=packageName)?
{
p = new PackageNode(name != null ? name : IdentifierNode.createEmptyIdentifierNodeAfterToken(packageT), (ASToken)packageT);
p.startBefore(packageT);
c.addItem(p);
b = p.getScopedNode();
}
( openT:TOKEN_BLOCK_OPEN { b.startAfter(openT); }
packageContents[b]
)
{ leavePackage(); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); leavePackage(); }
/**
* Matches a package name such as:
* org.apache.royale
*
* A Whitespace or LineTerminator is allowed around a . in a PackageName.
* For example, the following is a syntactically valid
* <pre>
* package a .
* b
* { }
* </pre>
* The resulting PackageName value is equivalent to a PackageName without any intervening Whitespace and LineTerminators.
*/
packageName returns [ExpressionNodeBase n]
{
n = null;
ExpressionNodeBase e = null;
}
: n=identifier
(options{greedy=true;}: { LA(2) != TOKEN_OPERATOR_STAR }?
dotT:TOKEN_OPERATOR_MEMBER_ACCESS
{
n = new FullNameNode(n, (ASToken) dotT, null);
}
e=identifier
{
((FullNameNode)n).setRightOperandNode(e);
}
)*
;
exception catch [RecognitionException ex] { return handleMissingIdentifier(ex, n); }
/**
* Matches contents in a package block.
*/
packageContents[ContainerNode b]
: (directive[b, TOKEN_BLOCK_CLOSE])*
closeT:TOKEN_BLOCK_CLOSE { b.endBefore(closeT); }
;
exception catch [RecognitionException ex]
{
if(handleParsingError(ex))
{
//attempt to recover from the error so we can keep parsing within the block
packageContents(b);
}
else
{
endContainerAtError(ex, b);
}
}
/**
* Matches a namespace definition.
*
* namespace ns1;
*/
namespaceDefinition[ContainerNode c, INamespaceDecorationNode namespace, List<ModifierNode> modList]
{
NamespaceNode n = null;
IdentifierNode id = null;
ExpressionNodeBase v = null;
}
: nsT:TOKEN_RESERVED_WORD_NAMESPACE id=identifier
{
n = new NamespaceNode(id);
n.startBefore(nsT);
storeDecorations(n, c, namespace, modList);
checkNamespaceDefinition(n);
}
(initializer[n])?
{
c.addItem(n);
matchOptionalSemicolon();
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches an interface definition.
*
* interface IFoo extends IBar {...}
*/
interfaceDefinition[ContainerNode c, INamespaceDecorationNode namespace, List<ModifierNode> modList]
{
InterfaceNode interfaceNode = null;
IdentifierNode intName = null;
ExpressionNodeBase baseInterfaceName = null;
BlockNode b = null;
enterInterfaceDefinition(LT(1));
}
: intT:TOKEN_KEYWORD_INTERFACE intName=identifier
{
interfaceNode = new InterfaceNode(intName);
interfaceNode.setInterfaceKeyword((IASToken)intT);
storeDecorations(interfaceNode, c, namespace, modList);
c.addItem(interfaceNode);
// Recover from invalid interface name.
final int la1 = LA(1);
if (la1 != TOKEN_RESERVED_WORD_EXTENDS && la1 != TOKEN_BLOCK_OPEN)
{
addProblem(new SyntaxProblem(LT(1)));
consumeUntilKeywordOr(TOKEN_BLOCK_OPEN);
}
}
( extendsT:TOKEN_RESERVED_WORD_EXTENDS
{ interfaceNode.setExtendsKeyword((ASToken)extendsT); }
( baseInterfaceName=restrictedName
{
interfaceNode.addBaseInterface(baseInterfaceName);
interfaceNode.setEnd(baseInterfaceName.getEnd());
}
( commaT:TOKEN_COMMA
{ interfaceNode.endAfter(commaT); }
( baseInterfaceName=restrictedName
{
interfaceNode.addBaseInterface(baseInterfaceName);
interfaceNode.setEnd(baseInterfaceName.getEnd());
}
)
)*
)
)?
{
b = interfaceNode.getScopedNode();
}
openT:TOKEN_BLOCK_OPEN
{ b.startAfter(openT); }
classOrInterfaceBlock[b]
{
Token prevToken = buffer.previous();
if(prevToken.getType() == TOKEN_BLOCK_CLOSE)
{
interfaceNode.endAfter(prevToken);
}
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a class definition. For example:
*
* class Player extends my_ns::GameObject implements IPlayer { ... }
*
*/
classDefinition [ContainerNode c, INamespaceDecorationNode namespace, List<ModifierNode> modList]
{
IdentifierNode className = null;
ExpressionNodeBase superName = null;
ExpressionNodeBase interfaceName = null;
ClassNode classNode = null;
disableSemicolonInsertion();
enterClassDefinition(LT(1));
}
: classT:TOKEN_KEYWORD_CLASS className=identifier
{
// When class name is empty, it is a synthesized IdentifierNode
// created by the error recovery logic in "identifier" rule.
// In such case, we fast-forward the token stream to the next
// keyword to recover.
if (className.getName().isEmpty())
{
// If the parser recover from "extends", "implements" or "{",
// we are could continue parsing the class definition, because
// these tokens are the "follow set" of a class name token.
// Otherwise, the next keyword is still a good starting point.
consumeUntilKeywordOr(TOKEN_BLOCK_OPEN);
}
insideClass = true;
classNode = new ClassNode(className);
classNode.setSourcePath(((IASToken)classT).getSourcePath());
classNode.setClassKeyword((IASToken)classT);
storeDecorations(classNode, c, namespace, modList);
c.addItem(classNode);
}
( extendsT:TOKEN_RESERVED_WORD_EXTENDS
{ classNode.setExtendsKeyword((ASToken)extendsT); }
// The rule for super type should be "restrictedName". However, in
// order to trap errors like "class Foo extends Vector.<T>", the
// parser has to allow parameterized type as super name. It's up to
// semantic analysis to report this problem.
superName=type
{
classNode.setBaseClass(superName);
classNode.setEnd(superName.getEnd());
}
exception catch [RecognitionException ex] { handleParsingError(ex); }
)?
( impT: TOKEN_RESERVED_WORD_IMPLEMENTS
{ classNode.setImplementsKeyword((ASToken)impT); }
( interfaceName=restrictedName
{
classNode.addInterface(interfaceName);
classNode.setEnd(interfaceName.getEnd());
}
( commaT:TOKEN_COMMA
{ classNode.endAfter(commaT); }
interfaceName=restrictedName
{
classNode.addInterface(interfaceName);
classNode.setEnd(interfaceName.getEnd());
}
)*
exception catch [RecognitionException ex] { handleParsingError(ex); }
)
exception catch [RecognitionException ex] { handleParsingError(ex); }
)?
openT:TOKEN_BLOCK_OPEN
{ classNode.getScopedNode().startAfter(openT); }
classOrInterfaceBlock[classNode.getScopedNode()]
{
Token prevToken = buffer.previous();
if(prevToken.getType() == TOKEN_BLOCK_CLOSE)
{
classNode.endAfter(prevToken);
}
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches the content block of a class definition or an interface definition.
*/
classOrInterfaceBlock[BlockNode b]
{ enableSemicolonInsertion(); }
: (directive[b, TOKEN_BLOCK_CLOSE])*
closeT:TOKEN_BLOCK_CLOSE
{ b.endBefore(closeT); }
;
exception catch [RecognitionException ex]
{
if(handleParsingError(ex)) {
classOrInterfaceBlock(b); //attempt to retry
} else {
endContainerAtError(ex, b);
}
}
/**
* Matches an anonymous function (function closure).
*/
functionExpression returns [FunctionObjectNode n]
{
n = null;
BlockNode b = null;
FunctionNode f = null;
IdentifierNode name=null;
ContainerNode p = null;
}
: functionT:TOKEN_KEYWORD_FUNCTION
// optional function name
(name=identifier)?
{
if(name == null)
name = IdentifierNode.createEmptyIdentifierNodeAfterToken(functionT);
f = new FunctionNode((ASToken)functionT, name);
n = new FunctionObjectNode(f);
f.startBefore(functionT);
n.startBefore(functionT);
b = f.getScopedNode();
disableSemicolonInsertion();
}
// function signature
lpT:TOKEN_PAREN_OPEN
{
p = f.getParametersContainerNode();
p.startBefore(lpT);
}
formalParameters[p]
rpT:TOKEN_PAREN_CLOSE
{ p.endAfter(rpT); }
(resultType[f])?
{ enableSemicolonInsertion(); }
// non-optional function body
lbT:TOKEN_BLOCK_OPEN
{ b.startAfter(lbT);}
functionBlock[f, (ASToken)lbT]
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a function block, excluding the open "{" but including the closing "}".
*/
functionBlock[FunctionNode f, ASToken openT]
{
final BlockNode b = f.getScopedNode();
b.setContainerType(IContainerNode.ContainerType.BRACES);
skipFunctionBody(f, openT);
}
: (directive[b, TOKEN_BLOCK_CLOSE])*
rbT:TOKEN_BLOCK_CLOSE
{ b.endBefore(rbT); }
;
exception catch [RecognitionException ex]
{
IASToken prev = buffer.previous();
if (prev.getType() != ASTokenTypes.EOF)
b.endAfter(prev);
else
b.setEnd(b.getStart());
if(handleParsingError(ex)) {
functionBlock(f, openT); //attempt to retry
}
}
/**
* Matches an optional function body. It can either be a "block" or a
* "semicolon".
*/
optionalFunctionBody [FunctionNode f]
{
BlockNode blockNode = f.getScopedNode();
enableSemicolonInsertion();
}
: { LA(1) == TOKEN_BLOCK_OPEN }? lbT:TOKEN_BLOCK_OPEN
{ blockNode.startAfter(lbT); }
functionBlock[f, (ASToken)lbT]
| { buffer.matchOptionalSemicolon() }? // Matches a function without body.
| {
final Token lt = LT(1);
blockNode.startBefore(lt);
blockNode.endBefore(lt);
// Report missing left-curly problem if there's no other syntax
// problems in the function definition.
reportFunctionBodyMissingLeftBraceProblem();
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a function definition. For example:
*
* private function myFunction(name:String) : void
* {
* return;
* }
*
*/
functionDefinition[ContainerNode c, INamespaceDecorationNode namespace, List<ModifierNode> modList]
{
IdentifierNode name=null;
disableSemicolonInsertion();
}
: ( functionT:TOKEN_KEYWORD_FUNCTION
// optional accessors:
// Although "get" and "set" can be identifiers as well, here
// they can only be the reserved words, unless it's a function
// called "get()" or "set()". As a result, the parser
// needs to match it in a "greedy" fashion.
(options{greedy=true;}:
{ LA(2) != TOKEN_PAREN_OPEN}? getT:TOKEN_RESERVED_WORD_GET
| { LA(2) != TOKEN_PAREN_OPEN}? setT:TOKEN_RESERVED_WORD_SET
)?
// non-optional function name:
name=identifier
//we need to be able to keep going in case we are in the processing of typing a function name
exception catch [RecognitionException ex] { name = handleMissingIdentifier(ex); }
)
{
final FunctionNode n ;
if (getT != null)
n = new GetterNode((ASToken)functionT, (ASToken)getT, name);
else if (setT != null)
n = new SetterNode((ASToken)functionT, (ASToken)setT, name);
else
n = new FunctionNode((ASToken)functionT, name);
storeDecorations(n, c, namespace, modList);
c.addItem(n);
}
// function signature:
lpT:TOKEN_PAREN_OPEN
{
final ContainerNode parameters = n.getParametersContainerNode();
parameters.startBefore(lpT);
}
formalParameters[parameters]
( rpT:TOKEN_PAREN_CLOSE
{ parameters.endAfter(rpT); }
// error recovery for typing in-progress function definitions
exception catch [RecognitionException ex] { handleParsingError(ex); }
)
(resultType[n])?
optionalFunctionBody[n]
{
Token prevToken = buffer.previous();
if(prevToken.getType() == TOKEN_BLOCK_CLOSE)
{
n.endAfter(prevToken);
}
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches the parameters of a function definition signature (excluding the parenthesis).
*
* arg1:int, arg2:String
*/
formalParameters[ContainerNode c]
: (formal[c] (TOKEN_COMMA formal[c])*)?
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches a single parameter in a function definition.
*/
formal[ContainerNode c]
{ ParameterNode p = null; }
:(p=restParameter | p=parameter)
{ if (p != null) c.addItem(p); }
;
/**
* Matches the "rest parameters" in a function definition.
*
* ...args
*/
restParameter returns [ParameterNode p]
{ p = null; }
: e:TOKEN_ELLIPSIS p=parameter
{
if (p != null){
// ??? following is an override on default type-specification
// ??? and should be pulled soon as that gets resolved.
if (p.getTypeNode() == null){
p.span(e);
}
p.setIsRestParameter(true);
}
}
;
/**
* Matches a parameter in a function definition.
*/
parameter returns [ParameterNode p]
{
p = null;
ASToken t = null;
IdentifierNode name = null;
}
: ( t=varOrConst
{
// const allowed here, var is not...log error, keep going
if (t.getType() == TOKEN_KEYWORD_VAR)
handleParsingError(new RecognitionException());
}
)?
name=identifier
{
p = new ParameterNode(name);
if (t != null && t.getType() == TOKEN_KEYWORD_CONST)
p.setKeyword(t);
}
(resultType[p])?
(initializer[p])?
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches keyword "var" or keyword "const".
*/
varOrConst returns[ASToken token]
{
token = LT(1);
}
: TOKEN_KEYWORD_VAR
| TOKEN_KEYWORD_CONST
;
/**
* Matches a result type: either a "void" keyword or a restricted name.
*
* :void
* :String
* :int
*
*/
resultType [BaseTypedDefinitionNode result]
{
ExpressionNodeBase t = null;
}
: colon:TOKEN_COLON
( ( t=voidLiteral | t=type )
exception catch [RecognitionException ex]
{ t = handleMissingIdentifier(ex); }
)
{
if(t.getStart() == -1)
t.startAfter(colon);
if (t.getEnd() == -1)
t.endAfter(colon);
result.endAfter(colon);
result.setType((ASToken) colon, t);
}
;
/**
* Matches an initializer in a variable/constant definition.
*/
initializer [IInitializableDefinitionNode v]
{
ExpressionNodeBase e = null;
}
: assignT:TOKEN_OPERATOR_ASSIGNMENT
e=assignmentRightValue
{ v.setAssignedValue((IASToken) assignT, e); }
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches a variable/constant definition.
*/
variableDefinition[ContainerNode c, INamespaceDecorationNode namespace, List<ModifierNode> modList]
{
VariableNode v = null;
ChainedVariableNode v2 = null;
ASToken tok = null;
asDocDelegate.beforeVariable();
}
: tok=varOrConst v=singleVariable[(ASToken)tok, namespace]
{
asDocDelegate.afterVariable();
storeVariableDecorations(v, c, namespace, modList);
if(v instanceof ConfigConstNode) {
addConfigConstNode((ConfigConstNode)v);
} else {
c.addItem(v);
}
}
// don't allow chain after a config
( {!(v instanceof ConfigConstNode)}?
TOKEN_COMMA v2=chainedVariable[c]
{
if(v2 != null)
{
v.addChainedVariableNode(v2);
storeEmbedDecoration(v2, v.getMetaTags());
}
}
exception catch [RecognitionException ex] { handleParsingError(ex); }
)*
{ matchOptionalSemicolon(); setAllowErrorsInContext(true); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); setAllowErrorsInContext(true); }
/**
* Matches a single variable/constant definition.
*/
singleVariable[IASToken keyword, INamespaceDecorationNode namespace] returns [VariableNode v]
{
v = null;
IdentifierNode name = null;
}
: name=identifier
{
if(namespaceIsConfigNamespace(namespace)) {
v = new ConfigConstNode(name);
} else {
v = new VariableNode(name);
}
v.setKeyword(keyword);
v.setIsConst(keyword.getType() == TOKEN_KEYWORD_CONST);
if(name.getStart() == -1) {
name.startAfter(keyword);
name.endAfter(keyword);
}
}
(resultType[v])?
(initializer[v])?
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a chained variable/constant definition.
*/
chainedVariable[ContainerNode c] returns [ChainedVariableNode v]
{
v = null;
IdentifierNode name = null;
}
: name=identifier
{ v = new ChainedVariableNode(name); }
(resultType[v])?
(initializer[v])?
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches variable definitions in a for loop.
*/
variableDefExpression returns[NodeBase v]
{
v = null;
ContainerNode c = null;
NodeBase v1 = null;
ASToken varT = null;
}
: varT=varOrConst v=singleVariableDefExpression[varT, varT.getType() == TOKEN_KEYWORD_CONST]
( TOKEN_COMMA v1=singleVariableDefExpression[null, varT.getType() == TOKEN_KEYWORD_CONST]
{
if (c == null) {
c = new ContainerNode();
c.setStart(v.getStart());
c.addItem(v);
v = c;
}
c.addItem(v1);
c.setEnd(v1.getEnd());
}
)*
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a single variable definition in a for loop.
*/
singleVariableDefExpression[ASToken varToken, boolean isConst] returns [ExpressionNodeBase n]
{
n = null;
VariableNode variable = null;
IdentifierNode varName = null;
ExpressionNodeBase value = null;
}
: varName=identifier
{
variable = new VariableNode(varName);
if(varToken != null)
variable.setKeyword(varToken);
variable.setIsConst(isConst);
n = new VariableExpressionNode(variable);
}
(resultType[variable])?
(initializer[variable])?
;
/**
* Matches a default XML namespace statement. For example:
*
* default xml namespace = "domain";
*
*/
defaultXMLNamespaceStatement[ContainerNode c]
{ ExpressionNodeBase e = null; }
: defT:TOKEN_DIRECTIVE_DEFAULT_XML TOKEN_OPERATOR_ASSIGNMENT e=assignmentExpression
{
DefaultXMLNamespaceNode n = new DefaultXMLNamespaceNode(new KeywordNode((IASToken)defT));
c.addItem(n);
n.setExpressionNode(e);
matchOptionalSemicolon();
}
;
/**
* Matches an expression in a pair of parenthesis. It's usually used as a
* condition expression in {@code if (...)}, {@code while (...)}, etc.
*
* (....)
*/
statementParenExpression returns [ExpressionNodeBase e]
{
e = null;
}
: TOKEN_PAREN_OPEN { disableSemicolonInsertion(); }
e=expression
TOKEN_PAREN_CLOSE { enableSemicolonInsertion(); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); enableSemicolonInsertion(); }
/**
* Matches an empty statement which is an explicit semicolon.
*/
emptyStatement
: TOKEN_SEMICOLON
;
/**
* Matches a "return" statement.
*/
returnStatement[ContainerNode c]
{
ExpressionNodeBase n = null;
ExpressionNodeBase e = null;
}
: returnT:TOKEN_KEYWORD_RETURN
{
n = new ReturnNode((ASToken)returnT);
c.addItem(n);
afterRestrictedToken((ASToken)returnT);
}
e=optExpression
{
((ReturnNode)n).setStatementExpression(e);
}
{ matchOptionalSemicolon(); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a "throw" statement.
*/
throwsStatement[ContainerNode c]
{
ExpressionNodeBase n = null;
ExpressionNodeBase e = null;
}
: throwT:TOKEN_KEYWORD_THROW
{
n = new ThrowNode((ASToken)throwT);
c.addItem(n);
afterRestrictedToken((ASToken)throwT);
}
( e=expression
{
((ThrowNode)n).setStatementExpression(e);
}
exception catch [RecognitionException ex] { handleParsingError(ex); }
)
{ matchOptionalSemicolon(); }
;
/**
* Matches a "for loop" statement.
*/
forStatement[ContainerNode c]
{
ForLoopNode node = null;
ContainerNode forContainer = null;
BlockNode b = null;
NodeBase fi = null;
ExpressionNodeBase e = null;
BinaryOperatorNodeBase inNode = null;
}
: forKeyword:TOKEN_KEYWORD_FOR lparenT:TOKEN_PAREN_OPEN
{
node = new ForLoopNode((ASToken)forKeyword);
c.addItem(node);
forContainer = node.getConditionalsContainerNode();
b = node.getContentsNode();
forContainer.startAfter(lparenT);
}
{
expressionMode = ExpressionMode.noIn;
}
fi=forInitializer
{
expressionMode = ExpressionMode.normal;
}
( TOKEN_SEMICOLON { forContainer.addItem(fi); }
forCondition[forContainer]
TOKEN_SEMICOLON
forStep[forContainer]
| in:TOKEN_KEYWORD_IN
{
final ExpressionNodeBase leftOfIn;
if (fi instanceof ExpressionNodeBase)
{
leftOfIn = (ExpressionNodeBase) fi;
}
else
{
// for...in doesn't allow multiple variable definition in the initializer clause
addProblem(new InvalidForInInitializerProblem(node));
if (fi instanceof ContainerNode &&
fi.getChildCount() > 0 &&
((ContainerNode)fi).getChild(0) instanceof ExpressionNodeBase)
{
// Recover by taking the first variable initializer and
// drop the rest.
leftOfIn = (ExpressionNodeBase)((ContainerNode)fi).getChild(0);
}
else
{
// No valid variable initializer found: recover by adding
// an empty identifier node.
leftOfIn = IdentifierNode.createEmptyIdentifierNodeAfterToken((ASToken)lparenT);
}
}
inNode = BinaryOperatorNodeBase.create((ASToken)in, leftOfIn, null);
forContainer.addItem(inNode);
}
e=optExpression
{ inNode.setRightOperandNode(e); }
)? // Make optional for error handling.
{
if (forContainer.getChildCount() == 0 && fi != null)
forContainer.addItem(fi);
}
( rparenT:TOKEN_PAREN_CLOSE
{
forContainer.endBefore(rparenT);
}
exception catch [RecognitionException ex] { handleParsingError(ex); }
)
substatement[b]
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches the "initializer" part in a for loop.
*/
forInitializer returns [NodeBase n]
{
n = null;
}
: n=variableDefExpression
| n=optExpression
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches the "condition" part in a for loop.
*/
forCondition[ContainerNode c]
{
ExpressionNodeBase e = null;
}
: e=optExpression
{if (e != null) c.addItem(e);}
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches the "step" part in a for loop.
*/
forStep[ContainerNode c]
{
ExpressionNodeBase e = null;
}
: e=optExpression
{if (e != null) c.addItem(e);}
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches a "do...while" statement.
*/
doStatement[ContainerNode c]
{
DoWhileLoopNode n = null;
ExpressionNodeBase e = null;
BlockNode b = null;
}
: doT:TOKEN_KEYWORD_DO
{
n = new DoWhileLoopNode((ASToken)doT);
c.addItem(n);
b = n.getContentsNode();
}
innerSubstatement[b]
TOKEN_KEYWORD_WHILE e=statementParenExpression
{
n.setConditionalExpression(e);
matchOptionalSemicolon();
}
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches a "while" loop statement.
*
* while (x > 1) { x--; }
*/
whileStatement[ContainerNode c]
{
WhileLoopNode n = null;
ExpressionNodeBase e = null;
BlockNode b = null;
}
: whileT:TOKEN_KEYWORD_WHILE e=statementParenExpression
{
n = new WhileLoopNode((ASToken)whileT);
n.setConditionalExpression(e);
c.addItem(n);
b = n.getContentsNode();
}
substatement[b]
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches a "break statement" or a "continue statement". For example:
*
* break;
* break innerLoop;
* continue;
* continue outerLoop;
*
*/
breakOrContinueStatement[ContainerNode c]
{
IdentifierNode id = null;
IterationFlowNode n = null;
final ASToken t = LT(1);
}
: ( TOKEN_KEYWORD_CONTINUE | TOKEN_KEYWORD_BREAK )
{
n = new IterationFlowNode(t);
c.addItem(n);
afterRestrictedToken(t);
}
// "greedy" mode is required to associate the following ID with the flow control.
(options{greedy=true;}:
id=identifier
{ n.setLabel(id); }
)?
{ matchOptionalSemicolon(); }
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches a "goto" statement.
*/
gotoStatement[ContainerNode c]
{
IdentifierNode id = null;
IterationFlowNode n = null;
final ASToken t = LT(1);
}
: TOKEN_RESERVED_WORD_GOTO id=identifier
{
n = new IterationFlowNode(t);
c.addItem(n);
n.setLabel(id);
matchOptionalSemicolon();
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a "with" statement.
*/
withStatement[ContainerNode c]
{
WithNode n = null;
ExpressionNodeBase e = null;
BlockNode b = null;
}
: withT:TOKEN_KEYWORD_WITH e=statementParenExpression
{
n = new WithNode((ASToken)withT);
n.setConditionalExpression(e);
c.addItem(n);
b = n.getContentsNode();
}
substatement[b]
;
/**
* Matches a "try...catch...finally" statement.
*/
tryStatement[ContainerNode c]
{
TryNode n = null;
BlockNode b = null;
}
: tryT:TOKEN_KEYWORD_TRY
{
n = new TryNode((ASToken)tryT);
b = n.getContentsNode();
c.addItem(n);
}
block[b]
( options { greedy=true;}:
catchBlock[n]
)*
( options { greedy=true;}:
finallyT:TOKEN_KEYWORD_FINALLY
{
TerminalNode t = new TerminalNode((ASToken)finallyT);
n.addFinallyBlock(t);
b = t.getContentsNode();
}
block[b]
)?
;
/**
* Matches the "catch" block in a "try" statement.
*/
catchBlock[TryNode tryNode]
{
CatchNode n = null;
ParameterNode arg = null;
BlockNode b = null;
}
: catchT:TOKEN_KEYWORD_CATCH TOKEN_PAREN_OPEN
{ disableSemicolonInsertion(); }
arg=catchBlockArgument
{
n = new CatchNode(arg);
tryNode.addCatchClause(n);
b = n.getContentsNode();
n.startBefore(catchT);
}
( rpT:TOKEN_PAREN_CLOSE
{
enableSemicolonInsertion();
n.endAfter(rpT);
}
exception catch [RecognitionException ex]
{handleParsingError(ex); enableSemicolonInsertion(); }
)
block[b]
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches the argument in the "try...catch(arg)" statement.
*/
catchBlockArgument returns [ParameterNode p]
{
p = null;
IdentifierNode name = null;
ExpressionNodeBase t = null;
}
: name=identifier
{ p = new ParameterNode(name); }
( colonT:TOKEN_COLON
{ p.setType((ASToken)colonT, null); }
t=type
{ p.setType((ASToken)colonT, t); }
)?
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches an "if" statement.
*/
ifStatement[ContainerNode c]
{
IfNode i = null;
ExpressionNodeBase cond = null;
ContainerNode b = null;
boolean hasElse = false;
}
: ifT:TOKEN_KEYWORD_IF cond=statementParenExpression
{
i = new IfNode((ASToken)ifT);
ConditionalNode cNode = new ConditionalNode((ASToken)ifT);
cNode.setConditionalExpression(cond);
b = cNode.getContentsNode();
i.addBranch(cNode);
c.addItem(i);
}
innerSubstatement[b]
(options{greedy=true;}:
hasElse=elsePart[i]
{ if (hasElse == true) return; }
)*
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches the optional "else" block of an "if" statement.
*
* @return true if there is an "else" block.
*/
elsePart[IfNode i] returns [boolean hasElse]
{
hasElse = false;
ContainerNode b = null;
ExpressionNodeBase cond = null;
ConditionalNode elseIf = null;
}
: elseT:TOKEN_KEYWORD_ELSE
(options{greedy=true;}:
TOKEN_KEYWORD_IF cond=statementParenExpression
{
elseIf = new ConditionalNode((ASToken) elseT);
elseIf.setConditionalExpression(cond);
i.addBranch(elseIf);
b = elseIf.getContentsNode();
}
)?
{
if (elseIf == null){
hasElse = true;
TerminalNode t = new TerminalNode((ASToken) elseT);
i.addBranch(t);
b = t.getContentsNode();
}
}
substatement[b]
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches a "switch" statement.
*/
switchStatement[ContainerNode c]
{
SwitchNode sw = null;
ExpressionNodeBase e = null;
}
: switchT:TOKEN_KEYWORD_SWITCH e=statementParenExpression
{
sw = new SwitchNode((ASToken)switchT);
c.addItem(sw);
if(e != null)
sw.setConditionalExpression(e);
}
cases[sw]
;
/**
* Matches the "case" block in a "switch" statement.
*/
cases[SwitchNode sw]
{
final ContainerNode b = sw.getContentsNode();
}
: openT:TOKEN_BLOCK_OPEN { b.startBefore(openT); }
caseClauses[b]
closeT:TOKEN_BLOCK_CLOSE { b.endAfter(closeT); }
;
/**
* Matches the "case" clauses in a "switch" statement.
*/
caseClauses[ContainerNode swb]
: (caseClause[swb])*
;
/**
* Matches a single "case" clause in a "switch" statement.
*/
caseClause[ContainerNode swb]
{
ExpressionNodeBase e = null;
ContainerNode b = null;
}
: caseT:TOKEN_KEYWORD_CASE e=expression colon
{
ConditionalNode cond = new ConditionalNode((ASToken) caseT);
cond.setConditionalExpression(e);
swb.addItem(cond);
b = cond.getContentsNode();
}
caseStatementList[b]
| defaultT:TOKEN_KEYWORD_DEFAULT colon
{
TerminalNode t = new TerminalNode((ASToken)defaultT);
swb.addItem(t);
b = t.getContentsNode();
}
caseStatementList[b]
| asDocComment
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches a colon token ":" or recover from a missing colon.
*/
colon
: TOKEN_COLON
;
exception catch [RecognitionException ex] { addProblem(unexpectedTokenProblem(LT(1), ASTokenKind.COLON)); }
/**
* Matches the statements in a "case" clause.
*/
caseStatementList[ContainerNode b]
: (directive[b, TOKEN_BLOCK_CLOSE])*
;
exception catch [RecognitionException ex] {handleParsingError(ex); }
/**
* Matches an identifier token. An identifier can come from different token
* types such as:
*
* - IDENTIFIER
* - namespace
* - get
* - set
*
* This is because in AS3, these elements are not reserved keyword. However they
* have special meaning in some syntactic contexts.
* See "AS3 syntax spec - 3.5 Keywords and Punctuators" for details.
*/
identifier returns [IdentifierNode n]
{
n = null;
final ASToken token = LT(1);
}
: ( TOKEN_IDENTIFIER
| TOKEN_RESERVED_WORD_NAMESPACE
| TOKEN_RESERVED_WORD_GET
| TOKEN_RESERVED_WORD_SET
)
{ n = new IdentifierNode(token); }
;
exception
catch [NoViableAltException e1] { n = expectingIdentifier(e1); }
catch [RecognitionException e2] { n = handleMissingIdentifier(e2); }
/**
* Matches an "import-able" name.
*
* flash.display.Sprite;
* flash.events.*;
*/
importName returns [ExpressionNodeBase n]
{
n=null;
ExpressionNodeBase e = null;
}
: n=packageName
( dot:TOKEN_OPERATOR_MEMBER_ACCESS
{
n = new FullNameNode(n, (ASToken) dot, null);
}
e=starLiteral
{
((FullNameNode)n).setRightOperandNode(e);
}
)?
;
exception catch [RecognitionException ex] { return handleMissingIdentifier(ex, n); }
/**
* Matches a restricted name. For example:
*
* my.package.name.Clock;
* private::myPrivateVar;
* UnqualifiedTypeClock;
*
*/
restrictedName returns [ExpressionNodeBase nameExpression]
{
nameExpression = null;
IdentifierNode placeHolderRightNode = null;
ASToken opToken = null;
ExpressionNodeBase part = null;
}
: nameExpression=restrictedNamePart
// LL(1) grammar can only branch on the next token.
// The LA(2) semantic predicate is used to disambiguate:
// 1. "foo.bar" - a restricted name consisting two identifiers
// 2. "foo.(bar)" - a member expression whose left-hand side is an identifier
// and the right-hand side is a parenthesis expression
(options { greedy=true; }: { LA(2) != TOKEN_PAREN_OPEN }?
{
opToken = LT(1);
// The place-holder node is a safe-net in case parsing the
// "right" node fails, so that we will still have a balanced
// FullNameNode.
placeHolderRightNode = IdentifierNode.createEmptyIdentifierNodeAfterToken(opToken);
final ExpressionNodeBase nameLeft = nameExpression;
}
( TOKEN_OPERATOR_MEMBER_ACCESS
{ nameExpression = new FullNameNode(nameLeft, opToken, placeHolderRightNode); }
| TOKEN_OPERATOR_NS_QUALIFIER
{ nameExpression = new NamespaceAccessExpressionNode(nameLeft, opToken, placeHolderRightNode); }
)
( { opToken.getType() == TOKEN_OPERATOR_NS_QUALIFIER && LA(1) == TOKEN_SQUARE_OPEN }?
// matches ns::["var_in_ns"]
nameExpression=bracketExpression[nameLeft]
| part=restrictedNamePart
{
((BinaryOperatorNodeBase)nameExpression).setRightOperandNode(part);
checkForChainedNamespaceQualifierProblem(opToken, part);
}
)
)*
;
exception catch [RecognitionException ex]
{
if (nameExpression == null)
nameExpression = handleMissingIdentifier(ex);
else
consumeParsingError(ex);
}
/**
* Matches the identifier part of a restricted name. For example:
*
* private
* public
* foo
* MyType
*
*/
restrictedNamePart returns [IdentifierNode id]
{
id = null;
final ASToken lt = LT(1);
}
: id=identifier
| TOKEN_NAMESPACE_NAME
{ id = new IdentifierNode(lt); }
| TOKEN_KEYWORD_SUPER
{ id = LanguageIdentifierNode.buildSuper(lt); }
;
// "identifier", "namespace name" and "super" are all "identifiers" to
// the user. So we override the default error handling in order to emit
// "expecting identifier but found ..." syntax problem.
exception catch [NoViableAltException ex] { id = expectingIdentifier(ex); }
/**
* Keep legacy rule for transpiling.
*/
typedNameOrStar returns [ExpressionNodeBase n]
: n=type
;
/**
* Matches a type reference.
*
* String
* int
* *
* Vector.<Clock>
* foo.bar.Vector.<T>
*
*/
type returns [ExpressionNodeBase n]
{
n = null;
}
: n=starLiteral
| n=configConditionAsType
| n=restrictedName ( n=typeApplication[n] )?
;
exception catch [RecognitionException ex] { n = handleMissingIdentifier(ex); }
configConditionAsType returns [ExpressionNodeBase n]
{
n = null;
}
: ns:TOKEN_NAMESPACE_NAME op:TOKEN_OPERATOR_NS_QUALIFIER id:TOKEN_IDENTIFIER
{ final NamespaceIdentifierNode nsNode = new NamespaceIdentifierNode((ASToken)ns);
nsNode.setIsConfigNamespace(isConfigNamespace(nsNode));
final IdentifierNode idNode = new IdentifierNode((ASToken)id);
n = transformToNSAccessExpression(nsNode, (ASToken) op, idNode);
n = n.copyForInitializer(null);
n.setSourcePath(nsNode.getSourcePath());
n.setLine(nsNode.getLine());
n.setColumn(nsNode.getColumn());
n.setEndLine(idNode.getEndLine());
n.setEndColumn(idNode.getEndColumn());
n.setStart(nsNode.getStart());
n.setEnd(idNode.getEnd());
}
;
/**
* Matches a "type application" part>
*
* .<String>
* .<Clock>
* .<uint>
*
*/
typeApplication [ExpressionNodeBase root] returns[TypedExpressionNode n]
{
n = null;
ExpressionNodeBase t = null;
Token closeT = null;
enterTypeApplication(root);
}
: openT:TOKEN_TYPED_COLLECTION_OPEN
t=type
{
n = new TypedExpressionNode(root, t, (ASToken)openT);
closeT = LT(1);
}
( TOKEN_TYPED_COLLECTION_CLOSE | TOKEN_OPERATOR_GREATER_THAN )
{ n.endAfter(closeT); }
;
exception catch [RecognitionException ex] { consumeParsingError(ex); }
/**
* Matches an optional expression.
* @return NilNode or ExpressionNodeBase.
*/
optExpression returns[ExpressionNodeBase e]
{
e = null;
}
: (options{greedy=true;}: e=expression)?
{ if (e == null) e = new NilNode(); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches an expression or a comma-separated expression list.
*/
expression returns [ExpressionNodeBase n]
{
n = null;
ExpressionNodeBase e1 = null;
}
: n=assignmentExpression
(options{greedy=true;}:
op:TOKEN_COMMA
e1=assignmentExpression
{ n = BinaryOperatorNodeBase.create((ASToken)op,n,e1); }
)*
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches an "assignment expression".
*
* According to ASL sytax spec, the productions for an "assignment expression"
* is either a "conditional expression" or a "left-hand side expression" followed
* by an "assignment operator" and an "assignment expression". However, since
* "assignmentExpression" and "conditionaExpression" is ambiguous at indefinite
* look-ahead distance, this LL(1) grammar can't decide which alternative to
* choose. As a result, the implementation is more lenient in that an AST node
* for an assignment binary node will be built even the left-hand side expression
* is not a valid "LeftHandSideExpression", such as a constant.
*
* For example:
* <code>100 = "hello";</code>
* This statement will be parsed without syntax error, generating tree like:
* <pre>
* =
* / \
* 100 "hello"
* </pre>
*
* A possible solution to this is to find out the difference between "conditional
* expression" and "left-hand side expression", then insert a semantic predicate
* before matching a "assignment operator".
*/
assignmentExpression returns [ExpressionNodeBase n]
{
n = null;
ASToken op = null;
ExpressionNodeBase r = null;
}
: n=condExpr
(options{greedy=true;}:
op=assignOp
r=assignmentRightValue
{ n = BinaryOperatorNodeBase.create(op,n,r); }
)?
;
/**
* Matches the right-hand side of an assignment expression.
* "public" namespace is allowed as an R-value for backward compatibility.
* @see "CMP-335 and ASLSPEC-19"
*/
assignmentRightValue returns [ExpressionNodeBase rightExpr]
{
rightExpr = null;
}
: { isNextTokenPublicNamespace() }? p:TOKEN_NAMESPACE_ANNOTATION
{ rightExpr = new NamespaceIdentifierNode((ASToken)p); }
| rightExpr=assignmentExpression
;
assignOp returns [ASToken op]
{
op = LT(1);
}
: TOKEN_OPERATOR_ASSIGNMENT
| TOKEN_OPERATOR_LOGICAL_AND_ASSIGNMENT
| TOKEN_OPERATOR_LOGICAL_OR_ASSIGNMENT
| TOKEN_OPERATOR_PLUS_ASSIGNMENT
| TOKEN_OPERATOR_MINUS_ASSIGNMENT
| TOKEN_OPERATOR_MULTIPLICATION_ASSIGNMENT
| TOKEN_OPERATOR_DIVISION_ASSIGNMENT
| TOKEN_OPERATOR_MODULO_ASSIGNMENT
| TOKEN_OPERATOR_BITWISE_AND_ASSIGNMENT
| TOKEN_OPERATOR_BITWISE_OR_ASSIGNMENT
| TOKEN_OPERATOR_BITWISE_XOR_ASSIGNMENT
| TOKEN_OPERATOR_BITWISE_LEFT_SHIFT_ASSIGNMENT
| TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT_ASSIGNMENT
| TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT
;
/**
* Matches a ternary expression such as:
*
* (x > 2) ? "greater" : "smaller"
*/
condExpr returns [ExpressionNodeBase n]
{
n = null;
ExpressionNodeBase trueExpr = null;
ExpressionNodeBase falseExpr = null;
TernaryOperatorNode ternary = null;
}
: n=binaryExpr
( op:TOKEN_OPERATOR_TERNARY
{
ternary = new TernaryOperatorNode((ASToken)op,n,null,null);
n = ternary;
}
trueExpr=assignmentExpression { ternary.setLeftOperandNode(trueExpr); }
TOKEN_COLON
falseExpr=assignmentExpression { ternary.setRightOperandNode(falseExpr); }
)?
exception catch [RecognitionException ex] { handleParsingError(ex); }
;
/**
* Binary expression uses operator precedence parser in BaseASParser.
*/
binaryExpr returns [ExpressionNodeBase n]
{
n = precedenceParseExpression(4);
if (true) return n;
}
: fakeExpr
;
/**
* fakeExpr simulates the set of allowable follow tokens in an expression context, which allows antlr to function.
* It is unreachable.
*/
fakeExpr
{
ExpressionNodeBase n = null;
}
: n=unaryExpr (options{greedy=true;}: binaryOperators fakeExpr)?
;
/**
* Declares all the binary operators.
*/
binaryOperators
: TOKEN_OPERATOR_LOGICAL_OR
| TOKEN_OPERATOR_LOGICAL_AND
| TOKEN_OPERATOR_BITWISE_OR
| TOKEN_OPERATOR_BITWISE_XOR
| TOKEN_OPERATOR_BITWISE_AND
| TOKEN_OPERATOR_EQUAL
| TOKEN_OPERATOR_NOT_EQUAL
| TOKEN_OPERATOR_STRICT_EQUAL
| TOKEN_OPERATOR_STRICT_NOT_EQUAL
| TOKEN_OPERATOR_GREATER_THAN
| TOKEN_OPERATOR_GREATER_THAN_EQUALS
| TOKEN_OPERATOR_LESS_THAN
| TOKEN_OPERATOR_LESS_THAN_EQUALS
| TOKEN_KEYWORD_INSTANCEOF
| TOKEN_KEYWORD_IS
| TOKEN_KEYWORD_AS
| TOKEN_KEYWORD_IN
| TOKEN_OPERATOR_BITWISE_LEFT_SHIFT
| TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT
| TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT
| TOKEN_OPERATOR_MINUS
| TOKEN_OPERATOR_PLUS
| TOKEN_OPERATOR_DIVISION
| TOKEN_OPERATOR_MODULO
| TOKEN_OPERATOR_STAR
;
/**
* Matches a "prefix expression".
*
* delete x[i]
* ++i
* --i
*
* The distinction between this rule and "unary expression" makes the parser
* more strict about what expressions can follow what tokens.
*/
prefixExpression returns [ExpressionNodeBase n]
{
n = null;
final ASToken op = LT(1);
}
: n=postfixExpr
| ( TOKEN_KEYWORD_DELETE n=postfixExpr
| TOKEN_OPERATOR_INCREMENT n=lhsExpr
| TOKEN_OPERATOR_DECREMENT n=lhsExpr
)
{
if (n == null)
n = IdentifierNode.createEmptyIdentifierNodeAfterToken(op);
n = UnaryOperatorNodeBase.createPrefix(op, n);
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a "unary expression".
*
* This rule is called out of the precedence parser in BaseASParser.
* If you need to change the name of this rule, you'll also need to update
* the base class.
*/
unaryExpr returns [ExpressionNodeBase n]
{
n = null;
ASToken op = null;
}
: ( n=prefixExpression
| op=unaryOp n=unaryExpr
{
if (n == null)
n = IdentifierNode.createEmptyIdentifierNodeAfterToken(op);
n = UnaryOperatorNodeBase.createPrefix(op, n);
}
)
(options { greedy = true; }:
n=propertyAccessExpression[n]
| n=arguments[n]
)*
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a unary operator.
*/
unaryOp returns [ASToken op]
{
op = LT(1);
}
: TOKEN_KEYWORD_VOID
| TOKEN_KEYWORD_TYPEOF
| TOKEN_OPERATOR_PLUS
| TOKEN_OPERATOR_MINUS
| TOKEN_OPERATOR_BITWISE_NOT
| TOKEN_OPERATOR_LOGICAL_NOT
;
/**
* Matches "Postfix Expression" such as: i++, i--
*
* Since ECMA semicolon insertion rule requires that if a "++" or "--" is not
* on the same line as its left-hand side expression, a semicolon is inserted
* before the "--" or "++" token. The side-effect of the inserted semicolon is
* to terminate the expression parsing at this point. As a result, we have to
* return "null" to stop parsing the expression. An upstream production will
* pickup the "--" or "++" by starting a new expression.
*
* A good test case for such situation would be:
*
* var i = 99
* ++i
*
* A semicolon should be inserted after "99", resulting in two separate ASTs
* for "var i=99" and "++i". Otherwise, "var i=99++" is a bad recognition.
*/
postfixExpr returns [ExpressionNodeBase n]
{
n = null;
boolean isSemicolonInserted = false;
}
: n=lhsExpr
{
final ASToken nextToken = LT(1);
if (nextToken.getType() == ASTokenTypes.TOKEN_OPERATOR_INCREMENT ||
nextToken.getType() == ASTokenTypes.TOKEN_OPERATOR_DECREMENT)
isSemicolonInserted = beforeRestrictedToken(nextToken);
}
( {!isSemicolonInserted}? (options{greedy=true;}: n=postfixOp[n])?
| // Do nothing if optional semicolon is inserted.
// This empty alternative is required because otherwise a semantic
// predicate exception will be thrown, leading the code enter error
// handling, which will create incorrect tree shape.
)
;
/**
* Matches a "postfix" operator such as: ++, --
* The parameter "n" is the expression the postfix operator acts on.
* The return value "top" is a UnaryOperatorNode.
*/
postfixOp[ExpressionNodeBase n] returns [UnaryOperatorNodeBase top]
{
final ASToken op = LT(1);
top = null;
}
: ( TOKEN_OPERATOR_INCREMENT
| TOKEN_OPERATOR_DECREMENT )
{ top = UnaryOperatorNodeBase.createPostfix(op, n); }
;
/**
* Matches a primary expression.
*/
primaryExpression returns [ExpressionNodeBase n]
{
n = null;
ASToken token = LT(1);
}
: TOKEN_KEYWORD_NULL
{ n = new LiteralNode(token, LiteralType.NULL); }
| TOKEN_KEYWORD_TRUE
{ n = new LiteralNode(token, LiteralType.BOOLEAN); }
| TOKEN_KEYWORD_FALSE
{ n = new LiteralNode(token, LiteralType.BOOLEAN); }
| TOKEN_KEYWORD_THIS
{ n = LanguageIdentifierNode.buildThis(token); }
| token=numericLiteral
{ n = new NumericLiteralNode(token); }
| TOKEN_LITERAL_STRING
{ n = new LiteralNode(token, LiteralType.STRING); }
| TOKEN_VOID_0
{ n = new LiteralNode(token, LiteralType.OBJECT); }
| TOKEN_LITERAL_REGEXP
{ n = new RegExpLiteralNode(token, this); }
| { n = new ArrayLiteralNode(); } arrayInitializer[(ArrayLiteralNode)n]
| n=objectLiteralExpression
| n=xmlInitializer { leaveXMLLiteral(); }
| n=xmlListInitializer { leaveXMLLiteral(); }
| n=functionExpression
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a numeric literal token.
*/
numericLiteral returns [ASToken op]
{
op = LT(1);
}
: TOKEN_LITERAL_NUMBER
| TOKEN_LITERAL_HEX_NUMBER
;
/**
* Matches an "object literal".
*/
objectLiteralExpression returns [ExpressionNodeBase n]
{
ObjectLiteralNode o = new ObjectLiteralNode();
n = o;
ContainerNode b = o.getContentsNode();
ExpressionNodeBase vp = null;
}
: openT:TOKEN_BLOCK_OPEN { n.startBefore(openT); }
( vp=objectLiteralValuePair { b.addItem(vp); }
( TOKEN_COMMA vp=objectLiteralValuePair
{ if (vp != null) b.addItem(vp); }
exception catch [RecognitionException ex]
{ handleParsingError(ex); }
)*
)?
closeT:TOKEN_BLOCK_CLOSE { n.endAfter(closeT); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a "field" in an "object literal".
* The "field" can be gated with a "config condition". If the condition is
* "false", return "null" value; Otherwise, return the expression node of the
* key/value pair.
*/
objectLiteralValuePair returns [ExpressionNodeBase n]
{
ExpressionNodeBase v = null;
n = null;
boolean condition = true;
ASToken numberT = null;
}
: ( { isConfigCondition() && LA(4) != TOKEN_COLON && LA(4) != TOKEN_BLOCK_CLOSE }?
condition=configCondition
| // Skip - no config varaible.
)
// Field name:
( nameT:TOKEN_IDENTIFIER
{ n = new NonResolvingIdentifierNode(nameT != null ? nameT.getText() : "",nameT); }
| numberT=numericLiteral
{ n = new NumericLiteralNode(numberT); }
| stringT:TOKEN_LITERAL_STRING
{ n = new LiteralNode(LiteralType.STRING, stringT); }
)
c:TOKEN_COLON
// Field value:
v=assignmentExpression
{
if (condition)
n = new ObjectLiteralValuePairNode((ASToken)c,n,v);
else
n = null;
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches array literal. For example:
*
* []
* ["hello", 3.14, foo]
* [ , x, y]
* [ , ,]
*
* "Holes" (empty array elements) are allowed. See "arrayElements" rule for details.
*/
arrayInitializer [ArrayLiteralNode node]
{
final ContainerNode contents = node.getContentsNode();
}
: open:TOKEN_SQUARE_OPEN { node.startBefore(open); contents.startAfter(open); }
arrayElements[contents]
close:TOKEN_SQUARE_CLOSE { node.endAfter(close); contents.endBefore(close); }
;
exception catch [RecognitionException ex]
{
// Do not convert keywords to identifiers.
// This is for recovering from:
// [
// var x:int;
handleParsingError(ex);
// Notify the caller that the array literal failed.
throw ex;
}
/**
* Matches all the elements in an "arrayInitializer". For example:
*
* x,y,z
* x,,
* (empty)
* ,,,,,,
*
* "Holes" are compiled as "undefined".
* Leading "holes" are kept as "undefined" values.
* "Holes" in the middle are kept as "undefined" values.
* Trailing "holes" are kept as "undefined" values except that the last "hole"
* is dropped.
*
* For example: x=[,,1,,,2,,,] has 2 leading holes, 2 holes in the middle, and 3
* holes at the end. All the holes except for the last trailing holes are kept
* as undefined values:
*
* x[0]=undefined
* x[1]=undefined
* x[2]=1
* x[3]=undefined
* x[4]=undefined
* x[5]=2
* x[6]=undefined
* x[7]=undefined
* (end)
*
*/
arrayElements[ContainerNode b]
{
ExpressionNodeBase e = null;
}
: ( TOKEN_COMMA { b.addItem(new NilNode()); } )*
( { LA(1) != TOKEN_SQUARE_CLOSE}?
e=arrayElement { b.addItem(e); /*1*/ }
( TOKEN_COMMA
( { LA(1) != TOKEN_SQUARE_CLOSE && LA(1) != TOKEN_COMMA }? e=arrayElement { b.addItem(e); /*2*/}
| { LA(1) != TOKEN_SQUARE_CLOSE && LA(1) == TOKEN_COMMA }? { b.addItem(new NilNode()); }
| // Next token is "]" - pass.
)
exception catch [RecognitionException ex] { handleParsingError(ex); }
)*
| // Next token is "]" - the initializer is a list of commas: [,,,,,]
)
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches an "array element" in an "array literal". An array element can be
* gated with a config variable. If the config variable is false, the element
* will be matched as a "hole" in the array literal.
*/
arrayElement returns [ExpressionNodeBase e]
{
e = null;
boolean c = true; // config variable
}
: ( { isConfigCondition() && LA(4) != TOKEN_COMMA && LA(4) != TOKEN_SQUARE_CLOSE }?
c=configCondition
| // Skip - no config varaible.
)
e=assignmentExpression
{
if (!c)
{
final NilNode nilNode = new NilNode();
nilNode.span(e, e);
e = nilNode;
}
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a "vector initializer".
*/
vectorLiteralExpression returns [VectorLiteralNode node]
{
node = new VectorLiteralNode();
ContainerNode b = node.getContentsNode();
ExpressionNodeBase type = null;
}
: open:TOKEN_TYPED_LITERAL_OPEN { node.endAfter(open); }
type=type { node.setCollectionTypeNode(type); }
close:TOKEN_TYPED_LITERAL_CLOSE { node.endAfter(close); }
openT:TOKEN_SQUARE_OPEN { b.startAfter(openT); }
(vectorLiteralContents[b])?
closeT:TOKEN_SQUARE_CLOSE { b.endBefore(closeT); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a "vector element" in a vector initializer.
*/
vectorLiteralContents[ContainerNode b]
{
ExpressionNodeBase e = null;
}
: e=arrayElement { b.addItem(e); }
( TOKEN_COMMA
{
// A trailing comma is allowed, but
// an intermediate comma is not.
if ( LA(1) != TOKEN_SQUARE_CLOSE ){
e=arrayElement();
b.addItem(e);
}
}
exception catch [RecognitionException ex] { handleParsingError(ex); }
)*
;
/**
* Matches "XML literal expression".
*/
xmlInitializer returns [XMLLiteralNode n]
{
n = new XMLLiteralNode();
final ASToken lt = LT(1);
enterXMLLiteral();
}
: { LA(1) == TOKEN_E4X_COMMENT ||
LA(1) == TOKEN_E4X_CDATA ||
LA(1) == TOKEN_E4X_PROCESSING_INSTRUCTION
}?
xmlMarkup
{ n.appendLiteralToken(lt); }
xmlWhitespace[n]
| (options { greedy = true; }: xmlElementContent[n] )+
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Match XML whitespace tokens. If {@code ContainerNode} is null, drop the
* whitespace tokens.
*/
xmlWhitespace [BaseLiteralContainerNode n]
: (options { greedy = true; }:
ws:TOKEN_E4X_WHITESPACE
{
if (n != null)
n.appendLiteralToken((ASToken)ws);
}
)*
;
/**
* Matches an XML comment, XML CDATA or XML PI token.
*/
xmlMarkup
: TOKEN_E4X_COMMENT
| TOKEN_E4X_CDATA
| TOKEN_E4X_PROCESSING_INSTRUCTION
;
/**
* Matches an E4X token that can be aggregated in "xmlTokenAggregated".
* Instead of a full recursive descent parser for XML tags, the base class
* uses a tag name stack to check matching tags. A complete parse tree with
* XML structure is unnecessary and adds extra overhead to the parser.
*/
xmlToken [BaseLiteralContainerNode n]
{
final ASToken t = LT(1);
}
: ( xmlMarkup
| TOKEN_E4X_WHITESPACE
| TOKEN_E4X_ENTITY
| TOKEN_E4X_DECIMAL_ENTITY
| TOKEN_E4X_HEX_ENTITY
| TOKEN_E4X_TEXT
| TOKEN_E4X_STRING
)
{ n.appendLiteralToken(t); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches an XML tag.
*
* <foo>
* </foo>
* <foo />
* <{name}>
* <foo name={nameValue}>
* <foo {attrs}>
*/
xmlTag [BaseLiteralContainerNode n]
: ( openT:TOKEN_E4X_OPEN_TAG_START // <foo
{
xmlTagOpen((ASToken)openT);
n.appendLiteralToken((ASToken)openT);
}
| closeT:TOKEN_E4X_CLOSE_TAG_START // </foo
{
xmlTagClose((ASToken)closeT);
n.appendLiteralToken((ASToken)closeT);
}
| openNoNameT:HIDDEN_TOKEN_E4X // <
{
xmlTagOpenBinding((ASToken)openNoNameT);
n.appendLiteralToken((ASToken)openNoNameT);
}
// Note about compatibility:
// x = < tagName foo="bar" />;
// ^
// Whitespace isn't allowed here according to ASL spec.
// Avik from AS3 spec team confirmed it was a bug that the old ASC allowed it.
( xmlContentBlock[n]
| nT:TOKEN_E4X_NAME
{ n.appendLiteralToken((ASToken)nT); }
)
)
xmlWhitespace[n]
( ( { isXMLAttribute() }? xmlAttribute[n]
| xmlContentBlock[n] )
xmlWhitespace[n]
)*
( endT:TOKEN_E4X_TAG_END // >
{ n.appendLiteralToken((ASToken)endT); }
| emptyEndT:TOKEN_E4X_EMPTY_TAG_END // />
{
xmlEmptyTagEnd((ASToken)emptyEndT);
n.appendLiteralToken((ASToken)emptyEndT);
}
)
;
/**
* Matches an XML attribute.
*
* name="value"
* name='value'
* name={value}
* {name}="value"
* {name}='value'
* {name}={value}
*/
xmlAttribute [BaseLiteralContainerNode n]
: ( nT:TOKEN_E4X_NAME
{ n.appendLiteralToken((ASToken)nT); }
| nsT:TOKEN_E4X_XMLNS
{ n.appendLiteralToken((ASToken)nsT); }
| xmlAttributeBlock[n]
)
( dT:TOKEN_E4X_NAME_DOT
{ n.appendLiteralToken((ASToken)dT); }
dnT:TOKEN_E4X_DOTTED_NAME_PART
{ n.appendLiteralToken((ASToken)dnT); }
)*
xmlWhitespace[n]
eqT:TOKEN_E4X_EQUALS
{ n.appendLiteralToken((ASToken)eqT); }
xmlWhitespace[n]
(options { greedy = true; }:
strT:TOKEN_E4X_STRING
{ n.appendLiteralToken((ASToken)strT); }
| eT:TOKEN_E4X_ENTITY
{ n.appendLiteralToken((ASToken)eT); }
| hexT:TOKEN_E4X_HEX_ENTITY
{ n.appendLiteralToken((ASToken)hexT); }
| xmlContentBlock[n]
)+
;
/**
* Matches an expression block in XML literals.
*
* <foo>{ this.fooValue }</foo>
*/
xmlElementContent [BaseLiteralContainerNode n]
: xmlToken[n]
| xmlContentBlock[n]
| xmlTag[n]
;
/**
* Matches an E4X XML list expression.
*/
xmlListInitializer returns [XMLListLiteralNode n]
{
n = new XMLListLiteralNode();
enterXMLLiteral();
}
: xmlListT:TOKEN_LITERAL_XMLLIST
{ n.getContentsNode().addItem(new LiteralNode(LiteralType.XML, xmlListT)); }
( xmlElementContent[n] )*
closeT: TOKEN_E4X_XMLLIST_CLOSE
{ n.getContentsNode().addItem(new LiteralNode(LiteralType.XML, closeT)); }
xmlWhitespace[null]
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a binding expression in an XML literal.
*/
xmlContentBlock[BaseLiteralContainerNode n]
{
ExpressionNodeBase e = null;
}
: TOKEN_E4X_BINDING_OPEN
e=expression
{
if(e != null)
n.getContentsNode().addItem(e);
}
TOKEN_E4X_BINDING_CLOSE
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a binding expression in an XML literal attribute name.
*/
xmlAttributeBlock[BaseLiteralContainerNode n]
{
ExpressionNodeBase e = null;
}
: TOKEN_E4X_BINDING_OPEN
e=lhsExpr
{
if(e != null)
n.getContentsNode().addItem(e);
}
TOKEN_E4X_BINDING_CLOSE
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a left-hand side (of asssignment) expression.
*/
lhsExpr returns [ExpressionNodeBase n]
{
n = null;
}
: ( n=newExpression
| n=parenExpression
| n=nameExpression
| n=primaryExpression
| n=xmlAttributeName
)
(options { greedy = true; }:
n=propertyAccessExpression[n]
| n=arguments[n]
)*
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a member expression. See ASL syntax spec for details.
*/
memberExpression returns [ExpressionNodeBase n]
: ( n=primaryExpression
| n=parenExpression
| n=propertyName
| n=newExpression
)
( options { greedy = true; }: n=propertyAccessExpression[n] )*
;
/**
* Matches a new expression. See ASL syntax spec for details.
*/
newExpression returns[ExpressionNodeBase n]
{
n = null;
}
: newT:TOKEN_KEYWORD_NEW
( { LA(1) != TOKEN_KEYWORD_FUNCTION }?
( n=vectorLiteralExpression
| n=memberExpression
)
{
if (n == null)
n= handleMissingIdentifier(null);
else
n = FullNameNode.toMemberAccessExpressionNode(n);
n = new FunctionCallNode((ASToken)newT, n);
}
(options{greedy=true;}: n=arguments[n])?
| n=functionExpression { n = new FunctionCallNode((ASToken)newT, n); }
)
exception catch [RecognitionException ex] {
//if we have the 'new' keyword, but no expression, drop in a dummy identifier
if(newT != null && n == null) {
IdentifierNode identifier = handleMissingIdentifier(ex);
if(identifier != null) {
//if we're here, that means identifier fixup is turned on
n = new FunctionCallNode((ASToken)newT, identifier);
}
} else {
handleParsingError(ex);
}
}
;
/**
* Matches an expression with parenthesis.
*
* (id)
* (1 + 2)
* (name == "hello")
*
*/
parenExpression returns[ExpressionNodeBase n]
{
n = null;
}
: TOKEN_PAREN_OPEN n=expression TOKEN_PAREN_CLOSE
{ if(n != null) n.setHasParenthesis(true); }
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a property name in a member expression.
*/
propertyName returns [ExpressionNodeBase n]
{
n = null;
}
: n=starLiteral // *
| n=restrictedName // qualified & unqualified name
| n=xmlAttributeName // e4x attribute
;
/**
* This is a non-greedy and non-aggregating version of "restricted name".
* It is defined in addition to "propertyName" in order to get correct
* precedence in the name expressions and expected tree shapes.
*/
nameExpression returns[ExpressionNodeBase n]
{ n = null; }
: n=identifier
| n=starLiteral
| superT:TOKEN_KEYWORD_SUPER
{ n = LanguageIdentifierNode.buildSuper((IASToken)superT); }
| nsT:TOKEN_NAMESPACE_NAME
{
n = new NamespaceIdentifierNode((ASToken)nsT);
((NamespaceIdentifierNode)n).setIsConfigNamespace(isConfigNamespace((NamespaceIdentifierNode)n));
}
;
/**
* Matches an E4X attribute name. For example:
*
* "@*", @data, @[foo="hello"]
*
*/
xmlAttributeName returns [ExpressionNodeBase result]
{
result = UnaryOperatorNodeBase.createPrefix(LT(1), null);
ExpressionNodeBase e = null;
}
: TOKEN_OPERATOR_ATSIGN
( ( e=starLiteral
| e=identifier
| nsT:TOKEN_NAMESPACE_NAME { e = new NamespaceIdentifierNode((ASToken)nsT); }
)
{
((UnaryOperatorNodeBase)result).setExpression(e);
}
| result=bracketExpression[result]
)
;
/**
* Matches a property access expression:
* For example: (assuming 'foo' is already matched)
* foo.bar
* foo..bar
* foo::bar
* foo[bar]
* foo.<bar>
*/
propertyAccessExpression [ExpressionNodeBase l] returns [ExpressionNodeBase n]
{
n = null;
ExpressionNodeBase r = null;
final ASToken op = LT(1);
}
: TOKEN_OPERATOR_MEMBER_ACCESS r=accessPart
{ n = new MemberAccessExpressionNode(l, op, r); }
| TOKEN_OPERATOR_DESCENDANT_ACCESS r=accessPart
{ n = new MemberAccessExpressionNode(l, op, r); }
| TOKEN_OPERATOR_NS_QUALIFIER r=nsAccessPart
{ if (l instanceof NamespaceIdentifierNode)
{
final NamespaceIdentifierNode nsNode = (NamespaceIdentifierNode)l;
nsNode.setIsConfigNamespace(isConfigNamespace(nsNode));
final IdentifierNode idNode = (IdentifierNode)r;
n = transformToNSAccessExpression(nsNode, (ASToken) op, idNode);
n = n.copyForInitializer(null);
n.setSourcePath(nsNode.getSourcePath());
n.setLine(nsNode.getLine());
n.setColumn(nsNode.getColumn());
n.setEndLine(idNode.getEndLine());
n.setEndColumn(idNode.getEndColumn());
n.setStart(nsNode.getStart());
n.setEnd(idNode.getEnd());
}
else
{
n = transformToNSAccessExpression(l, (ASToken) op, r);
}
}
| n=bracketExpression[l]
| n=typeApplication[l]
;
/**
* Matches parts after the dot in a property access expression.
*/
accessPart returns [ExpressionNodeBase n]
{
n = null;
}
: n=nameExpression
| n=xmlAttributeName
| n=parenExpression
;
exception catch [RecognitionException ex] { n = handleMissingIdentifier(ex); }
/**
* Matches parts after "::" in a property access expression.
*/
nsAccessPart returns [ExpressionNodeBase n]
{
n = null;
}
: n=nameExpression
| n=xmlAttributeName
| n=parenExpression
| n=runtimeName
;
exception catch [RecognitionException ex] { n = handleMissingIdentifier(ex); }
/**
* Matches a runtime attribute name.
*
* foo["name"]
*/
runtimeName returns[ExpressionNodeBase n]
{
ExpressionNodeBase e = null;
n = null;
}
: TOKEN_SQUARE_OPEN e=expression closeT:TOKEN_SQUARE_CLOSE
{
n = new RuntimeNameExpressionNode(e);
n.endAfter(closeT);
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }
/**
* Matches a "star" literal token, and create an IdentifierNode for "*".
*/
starLiteral returns [IdentifierNode id]
{
id = LanguageIdentifierNode.buildAnyType(LT(1));
}
: TOKEN_OPERATOR_STAR
;
/**
* Matches a "void" keyword token, and create an IdentifierNode for "void".
*/
voidLiteral returns [IdentifierNode id]
{
id = LanguageIdentifierNode.buildVoid(LT(1));
}
: TOKEN_KEYWORD_VOID
;
/**
* Matches the arguments of a function call: allcharacters between (...)
* including the parenthesis.
*
* foo(true, 10, "hello");
* new MyData(100, "hundred");
*
* "newExpression" rule always creates a FunctionCallNode and passes it in as
* the "root". Since the "newExpression" rule uses "arguments" rule to consume
* its arguments, we don't need to create a new FunctionCallNode because the
* "root" parameter is the FunctionCallNode to which the arguments belongs.
*
* On the other hand, if the "root" argument is not a FunctionCallNode from a
* "new" expression, then the "root" can only be the "name expression" of a
* function call. As a result, we must create a FunctionCallNode for it.
* For example:
* myFunc() -- the name expression is identifier "myFunc"
* token.getCallBack()() -- the name expression is "token.getCallBack()"
* expecting the return value of "getCallBack" to
* be a function object.
*
* [Shaoting] We could also make newExpression rule not create a
* FunctionCallNode inside the "newExpression" rule, and pass in
* the "new" token so that all FunctionCallNode are constructed in
* this rule. However, we want the parser to construct nodes as early
* as possible for IDE features. For example: "new T" without "(..)"
* would not result in a FunctionCallNode if we don't create it right
* after we see "T".
*/
arguments[ExpressionNodeBase root] returns[ExpressionNodeBase n]
{
n = root;
ContainerNode args = null;
}
: lpT:TOKEN_PAREN_OPEN
{
final boolean isNewExpression =
(n instanceof FunctionCallNode) &&
((FunctionCallNode)n).isNewExpression();
final boolean newFunctionCallAlreadyHasArgs = isNewExpression &&
((FunctionCallNode)n).getArgumentsNode().getStart() != -1;
// the above line is a hack to try to catch "new" expressions
// where the class to instantiate is the result of a function
// call: new someFunction(someArgs)(constructorParams)
// we check to see if the arg node as a start() value which
// means that the first argument list (someargs) was already
// processed.
final FunctionCallNode oldNode = newFunctionCallAlreadyHasArgs ? (FunctionCallNode)n : null;
if (n == null || !isNewExpression || newFunctionCallAlreadyHasArgs )
n = new FunctionCallNode(n);
if (newFunctionCallAlreadyHasArgs) {
((FunctionCallNode)n).setNewKeywordNode(oldNode.getNewKeywordNode());
oldNode.setNewKeywordNode(null);
}
args = ((FunctionCallNode)n).getArgumentsNode();
args.startBefore(lpT);
args.endAfter(lpT);
disableSemicolonInsertion();
}
( argumentList[args] )
;
exception catch [RecognitionException ex]
{
//only consume this error b/c we're looking for a missing ')' Let the parser handle the next token, maybe it will correct us
consumeParsingError(ex);
if ( args != null ){
if(ex instanceof NoViableAltException) {
args.endBefore(((NoViableAltException)ex).token);
} else if(ex instanceof MismatchedTokenException) {
args.endBefore(((MismatchedTokenException)ex).token);
} else {
endContainerAtError(ex, args);
}
}
enableSemicolonInsertion();
}
/**
* Matches an argument list in a function call arguments.
*
* For argument list, we want to support the case where the user might be in the
* middle of typing, which could mean a couple of things:
* 1. The user adds an argument before another.
* 2. After which could lead to comma where we don't want it.
*/
argumentList[ContainerNode args]
{
ExpressionNodeBase n = null;
boolean foundFirstArg = false;
}
: ( n=assignmentExpression
{ foundFirstArg = true; if (args != null) args.addItem(n); }
exception catch [RecognitionException ex]
{
n = handleMissingIdentifier(ex);
if(n != null) {
foundFirstArg = true; //we don't want to add a second error for this case
if (args != null) args.addItem(n);
}
}
)? //make this optional for malformed code handling, but we will log a parser error against it
( commaT:TOKEN_COMMA
{
//if we didn't find the first arg, log an error
if(!foundFirstArg) {
logSyntaxError((ASToken)commaT);
foundFirstArg = true;
n = handleMissingIdentifier(null);
if (n!= null && args != null) args.addItem(n);
}
if (args != null) args.endAfter(commaT);
}
n=assignmentExpression
{
if(n == null)
n = handleMissingIdentifier(null);
if (args != null)
args.addItem(n);
}
exception catch [RecognitionException ex]
{
n = handleMissingIdentifier(ex);
if(n != null && args != null) {
args.addItem(n);
}
}
)*
rpT:TOKEN_PAREN_CLOSE
{ args.endAfter(rpT); enableSemicolonInsertion(); }
;
/**
* Matches a bracket expression. For example:
*
* [10]
* [idx]
*
*/
bracketExpression [ExpressionNodeBase root] returns [DynamicAccessNode result]
{
result = new DynamicAccessNode(root);
ExpressionNodeBase e = null;
}
: TOKEN_SQUARE_OPEN
e=expression
( closeT:TOKEN_SQUARE_CLOSE
exception catch [RecognitionException ex] { handleParsingError(ex); }
)
{
result.setRightOperandNode(e);
result.endAfter(closeT);
}
;
exception catch [RecognitionException ex] { handleParsingError(ex); }