blob: 7e68592611269015891e53e9e602325cd3430c75 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Eugene Melekhov and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eugene Melekhov - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.jsdt.core.dom.flatten;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.wst.jsdt.core.UnimplementedException;
import org.eclipse.wst.jsdt.core.dom.AST;
import org.eclipse.wst.jsdt.core.dom.ASTNode;
import org.eclipse.wst.jsdt.core.dom.ASTVisitor;
import org.eclipse.wst.jsdt.core.dom.ArrayAccess;
import org.eclipse.wst.jsdt.core.dom.ArrayInitializer;
import org.eclipse.wst.jsdt.core.dom.ArrayName;
import org.eclipse.wst.jsdt.core.dom.ArrowFunctionExpression;
import org.eclipse.wst.jsdt.core.dom.Assignment;
import org.eclipse.wst.jsdt.core.dom.AssignmentName;
import org.eclipse.wst.jsdt.core.dom.Block;
import org.eclipse.wst.jsdt.core.dom.BooleanLiteral;
import org.eclipse.wst.jsdt.core.dom.BreakStatement;
import org.eclipse.wst.jsdt.core.dom.CatchClause;
import org.eclipse.wst.jsdt.core.dom.CharacterLiteral;
import org.eclipse.wst.jsdt.core.dom.ClassInstanceCreation;
import org.eclipse.wst.jsdt.core.dom.ConditionalExpression;
import org.eclipse.wst.jsdt.core.dom.ContinueStatement;
import org.eclipse.wst.jsdt.core.dom.DebuggerStatement;
import org.eclipse.wst.jsdt.core.dom.DoStatement;
import org.eclipse.wst.jsdt.core.dom.EmptyStatement;
import org.eclipse.wst.jsdt.core.dom.ExportDeclaration;
import org.eclipse.wst.jsdt.core.dom.Expression;
import org.eclipse.wst.jsdt.core.dom.ExpressionStatement;
import org.eclipse.wst.jsdt.core.dom.FieldAccess;
import org.eclipse.wst.jsdt.core.dom.ForInStatement;
import org.eclipse.wst.jsdt.core.dom.ForOfStatement;
import org.eclipse.wst.jsdt.core.dom.ForStatement;
import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
import org.eclipse.wst.jsdt.core.dom.FunctionDeclarationStatement;
import org.eclipse.wst.jsdt.core.dom.FunctionExpression;
import org.eclipse.wst.jsdt.core.dom.FunctionInvocation;
import org.eclipse.wst.jsdt.core.dom.IfStatement;
import org.eclipse.wst.jsdt.core.dom.ImportDeclaration;
import org.eclipse.wst.jsdt.core.dom.InfixExpression;
import org.eclipse.wst.jsdt.core.dom.JSdoc;
import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
import org.eclipse.wst.jsdt.core.dom.LabeledStatement;
import org.eclipse.wst.jsdt.core.dom.LineComment;
import org.eclipse.wst.jsdt.core.dom.ListExpression;
import org.eclipse.wst.jsdt.core.dom.MetaProperty;
import org.eclipse.wst.jsdt.core.dom.Modifier;
import org.eclipse.wst.jsdt.core.dom.ModuleSpecifier;
import org.eclipse.wst.jsdt.core.dom.Name;
import org.eclipse.wst.jsdt.core.dom.NullLiteral;
import org.eclipse.wst.jsdt.core.dom.NumberLiteral;
import org.eclipse.wst.jsdt.core.dom.ObjectLiteral;
import org.eclipse.wst.jsdt.core.dom.ObjectLiteralField;
import org.eclipse.wst.jsdt.core.dom.ObjectName;
import org.eclipse.wst.jsdt.core.dom.ParenthesizedExpression;
import org.eclipse.wst.jsdt.core.dom.PostfixExpression;
import org.eclipse.wst.jsdt.core.dom.PrefixExpression;
import org.eclipse.wst.jsdt.core.dom.RegularExpressionLiteral;
import org.eclipse.wst.jsdt.core.dom.RestElementName;
import org.eclipse.wst.jsdt.core.dom.ReturnStatement;
import org.eclipse.wst.jsdt.core.dom.SimpleName;
import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration;
import org.eclipse.wst.jsdt.core.dom.SpreadElement;
import org.eclipse.wst.jsdt.core.dom.StringLiteral;
import org.eclipse.wst.jsdt.core.dom.SuperMethodInvocation;
import org.eclipse.wst.jsdt.core.dom.SwitchCase;
import org.eclipse.wst.jsdt.core.dom.SwitchStatement;
import org.eclipse.wst.jsdt.core.dom.TemplateElement;
import org.eclipse.wst.jsdt.core.dom.TemplateLiteral;
import org.eclipse.wst.jsdt.core.dom.ThisExpression;
import org.eclipse.wst.jsdt.core.dom.ThrowStatement;
import org.eclipse.wst.jsdt.core.dom.TryStatement;
import org.eclipse.wst.jsdt.core.dom.TypeDeclaration;
import org.eclipse.wst.jsdt.core.dom.TypeDeclarationExpression;
import org.eclipse.wst.jsdt.core.dom.TypeDeclarationStatement;
import org.eclipse.wst.jsdt.core.dom.UndefinedLiteral;
import org.eclipse.wst.jsdt.core.dom.VariableDeclaration;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationExpression;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment;
import org.eclipse.wst.jsdt.core.dom.VariableDeclarationStatement;
import org.eclipse.wst.jsdt.core.dom.WhileStatement;
import org.eclipse.wst.jsdt.core.dom.WithStatement;
import org.eclipse.wst.jsdt.core.dom.YieldExpression;
/**
* Generator that creates internal representation of the AST suitable for
* further processing (generating source code). For now the vocabulary of the
* internal representation language is rather small, though it's enough to
* describe JS source code. In case if more complex emit processing is
* required additional elements can be added.
*
* It's not complete, some elements like import and export require work;
* future modifications according to the upcoming changes in the ASTDom model
* will be done as well.
*
* @author Eugene Melekhov
* @since 2.0
*
*/
@SuppressWarnings("nls")
public class JsCodeIRGenerator extends ASTVisitor {
/**
*
* Basic class for code elements - building blocks used to create internal
* representation. Derived classes must implement <code>emit</code>
* function which outputs element presentation to the output stream.
*
*/
public static abstract class JsCodeElement {
/**
* Emit element to given stream
*
* @param out
*/
public abstract void emit(JsCodeOutputStream out);
}
/**
* Generate the presentation of the given node
*
* @param node
* node to generate
* @return Code Element that represents given node
*/
public JsCodeElement generate(ASTNode node) {
return v(node);
}
/**
* Factory for JsCode elements
*/
protected IJsCodeElementFactory factory;
/**
* "accumulator register" to pass return value from <code>visitor</code>
* functions. Since visitor supposed to return boolean value to it's
* caller we use this method to pass "real" return values. Visitor
* function that wants to return value should set it upon return.
*/
protected JsCodeElement value = null;
/**
* Creates BasicJsCodeGenerator with provided factory which is used to
* create elements.
*
* @param factory
* factory creating program elements
*/
public JsCodeIRGenerator(IJsCodeElementFactory factory) {
this.factory = factory;
}
@Override
public boolean visit(Modifier node) {
value = token(node.getKeyword().toString());
return false;
}
@Override
public boolean visit(ArrayAccess node) {
value = seqVa(v(node.getArray()), brack(v(node.getIndex())));
return false;
}
@Override
public boolean visit(ArrayInitializer node) {
value = brack(seqCsMap(node.expressions()));
return false;
}
@Override
public boolean visit(Assignment node) {
JsCodeElement lhs = parenOpt(ASSIGNMENT, node.getLeftHandSide());
JsCodeElement rhs = parenOpt(ASSIGNMENT, node.getRightHandSide());
value = seqVa(lhs, token(node.getOperator().toString()), rhs);
return false;
}
@Override
public boolean visit(Block node) {
value = braces(seqMap(node.statements()));
return false;
}
@Override
public boolean visit(BooleanLiteral node) {
value = token(node.booleanValue() ? "true" : "false");
return false;
}
@Override
public boolean visit(BreakStatement node) {
value = seqVa(token("break"), v(node.getLabel()), semiOpt());
return false;
}
@Override
public boolean visit(CatchClause node) {
value = seqVa(token("catch"), paren(v(node.getException())), v(node.getBody()));
return false;
}
@Override
public boolean visit(RegularExpressionLiteral node) {
value = token(node.getRegularExpression());
return false;
}
@Override
public boolean visit(ClassInstanceCreation node) {
value = seqVa(token("new"), v(node.getMember()), paren(seqCsMap(node.arguments())));
return false;
}
@Override
public boolean visit(JavaScriptUnit node) {
List<JsCodeElement> l = map(node.imports());
l.addAll(map(node.exports()));
l.addAll(map(node.statements()));
value = seq(l);
return false;
}
@Override
public boolean visit(ConditionalExpression node) {
value = seqVa(parenOpt(LOGICAL_OR, node.getExpression()), token("?"), parenOpt(ASSIGNMENT, node.getThenExpression()), token(":"), parenOpt(ASSIGNMENT, node.getElseExpression()));
return false;
}
@Override
public boolean visit(ContinueStatement node) {
value = seqVa(token("continue"), v(node.getLabel()), semiOpt());
return false;
}
@Override
public boolean visit(DoStatement node) {
value = seqVa(token("do"), v(node.getBody()), token("while"), paren(v(node.getExpression())), semiOpt());
return false;
}
@Override
public boolean visit(EmptyStatement node) {
value = semi();
return false;
}
@Override
public boolean visit(ExpressionStatement node) {
value = v(node.getExpression());
// TODO (Eugene Melekhov) it's not very elegant, but I don't know
// other way to do it yet
if (node.getParent() != null) {
int nt = node.getParent().getNodeType();
if (nt != ASTNode.FOR_IN_STATEMENT && nt != ASTNode.FOR_OF_STATEMENT) {
value = seqVa(value, semiOpt());
}
}
return false;
}
@Override
public boolean visit(FieldAccess node) {
value = seqVa(v(node.getExpression()), token("."), v(node.getName()));
return false;
}
@Override
public boolean visit(ForStatement node) {
// Handle "in" operator. Is there better way to do it?
List<JsCodeElement> initializers = new ArrayList<>();
for (ASTNode initializer : (List<ASTNode>) node.initializers()) {
JsCodeElement element = v(initializer);
if (element != null) {
if (initializer.getNodeType() == ASTNode.INFIX_EXPRESSION && ("in".equals(((InfixExpression) initializer).getOperator().toString()))) {
initializers.add(paren(element));
}
else {
initializers.add(element);
}
}
}
value = seqVa(token("for"), paren(seqVa(seqCs(initializers), semi(), v(node.getExpression()), semi(), seqCsMap(node.updaters()))), v(node.getBody()));
return false;
}
@Override
public boolean visit(ForInStatement node) {
// TODO check correct "let" etc in left part
JsCodeIRGenerator.JsCodeElement left = v(node.getIterationVariable());
JsCodeIRGenerator.JsCodeElement right = v(node.getCollection());
value = seqVa(token("for"), paren(seqVa(left, token("in"), right)), v(node.getBody()));
return false;
}
@Override
public boolean visit(ForOfStatement node) {
// TODO check correct "let" etc in left part
JsCodeIRGenerator.JsCodeElement left = v(node.getIterationVariable());
JsCodeIRGenerator.JsCodeElement right = v(node.getCollection());
// TODO make space before/after "of" optional depending on context
value = seqVa(token("for"), paren(seqVa(left, token("of"), right)), v(node.getBody()));
return false;
}
@Override
public boolean visit(IfStatement node) {
List<JsCodeElement> result = new ArrayList<>();
result.add(token("if"));
result.add(paren(v(node.getExpression())));
result.add(v(node.getThenStatement()));
// TODO handle empty statements more accurate
JsCodeElement alternate = v(node.getElseStatement());
if (alternate != null) {
result.add(token("else"));
result.add(alternate);
}
value = seq(result);
return false;
}
@Override
public boolean visit(InfixExpression node) {
int precedence = expressionPrecedence(node);
List<JsCodeElement> result = new ArrayList<>();
result.add(parenOpt(precedence, node.getLeftOperand()));
result.add(token(node.getOperator().toString()));
result.add(parenOptRight(precedence, node.getRightOperand()));
final List<ASTNode> extendedOperands = node.extendedOperands();
if (extendedOperands.size() != 0) {
for (ASTNode e : extendedOperands) {
result.add(token(node.getOperator().toString()));
result.add(parenOpt(precedence, e));
}
}
value = seq(result);
return false;
}
public boolean visit(JSdoc node) {
value = seqVa(token("/** "), seqMap(node.tags()), token(" */"));
return false;
}
public boolean visit(LineComment node) {
value = seqVa(token("//"), token("\n"));
return false;
}
@Override
public boolean visit(LabeledStatement node) {
value = seqVa(v(node.getLabel()), token(":"), v(node.getBody()));
return false;
}
@Override
public boolean visit(ListExpression node) {
value = seqCsMap(node.expressions());
return false;
}
@Override
public boolean visit(FunctionDeclaration node) {
List<JsCodeElement> result = new ArrayList<>();
result.add(v(node.getJavadoc()));
result.add(seqMap(node.modifiers()));
if (!node.isConstructor() && !isGetterOrSetter(node.modifiers())) {
result.add(token("function" + (node.isGenerator() ? "*" : "")));
}
result.add(v(node.getMethodName()));
result.add(paren(seqCsMap(node.parameters())));
// TODO What is this?
for (int i = 0; i < node.getExtraDimensions(); i++) {
result.add(token("[]"));
}
result.add(v(node.getBody()));
value = seq(result);
return false;
}
@Override
public boolean visit(FunctionInvocation node) {
List<JsCodeElement> result = new ArrayList<>();
if (node.getExpression() != null) {
result.add(v(node.getExpression()));
if (node.getName() != null) {
result.add(token("."));
}
}
// TODO What is this
if (node.getAST().apiLevel() >= AST.JLS3) {
if (!node.typeArguments().isEmpty()) {
result.add(token("<"));
result.add(seqCsMap(node.typeArguments()));
result.add(token(">"));
}
}
result.add(v(node.getName()));
result.add(paren(seqCsMap(node.arguments())));
value = seq(result);
return false;
}
@Override
public boolean visit(NullLiteral node) {
value = token("null");
return false;
}
@Override
public boolean visit(UndefinedLiteral node) {
value = token("undefined");
return false;
}
@Override
public boolean visit(NumberLiteral node) {
value = token(node.getToken());
return false;
}
@Override
public boolean visit(PostfixExpression node) {
value = seqVa(v(node.getOperand()), token(node.getOperator().toString()));
return false;
}
@Override
public boolean visit(PrefixExpression node) {
value = seqVa(token(node.getOperator().toString()), parenOpt(PREFIX, node.getOperand()));
return false;
}
@Override
public boolean visit(ReturnStatement node) {
value = seqVa(token("return"), v(node.getExpression()), semiOpt());
return false;
}
@Override
public boolean visit(SimpleName node) {
value = token(node.getIdentifier());
return false;
}
@Override
public boolean visit(SingleVariableDeclaration node) {
// TODO Require some clarification. What is the Type for example
List<JsCodeIRGenerator.JsCodeElement> result = new ArrayList<>();
result.add(v(node.getType()));
if (node.isVarargs()) {
result.add(token("..."));
}
result.add(v(node.getPattern()));
for (int i = 0; i < node.getExtraDimensions(); i++) {
result.add(token("[]"));
}
if (node.getInitializer() != null) {
result.add(token("="));
result.add(v(node.getInitializer()));
}
value = seq(result);
return false;
}
@Override
public boolean visit(StringLiteral node) {
value = token(node.getEscapedValue());
return false;
}
@Override
public boolean visit(SuperMethodInvocation node) {
List<JsCodeElement> args = map(node.arguments());
value = seqVa(token("super"), paren(seqCs(args)));
return false;
}
@Override
public boolean visit(SwitchCase node) {
value = seqVa(node.isDefault() ? token("default") : seqVa(token("case"), v(node.getExpression())), token(":"));
return false;
}
@Override
public boolean visit(SwitchStatement node) {
value = seqVa(token("switch"), paren(v(node.getExpression())), braces(seqMap(node.statements())));
return false;
}
@Override
public boolean visit(ThisExpression node) {
value = token("this");
return false;
}
@Override
public boolean visit(ThrowStatement node) {
value = seqVa(token("throw"), v(node.getExpression()), semiOpt());
return false;
}
@Override
public boolean visit(TryStatement node) {
value = seqVa(token("try"), v(node.getBody()), seqMap(node.catchClauses()), (node.getFinally() != null ? seqVa(token("finally"), v(node.getFinally())) : null));
return false;
}
@Override
public boolean visit(TypeDeclaration node) {
JsCodeElement result = seqVa(v(node.getJavadoc()), token("class"), v(node.getName()));
JsCodeElement superType = v(node.getSuperclassExpression());
if (superType != null) {
result = seqVa(result, token("extends"), superType);
}
value = seqVa(result, braces(seqMap(node.bodyDeclarations())));
return false;
}
@Override
public boolean visit(TypeDeclarationStatement node) {
value = v(node.getDeclaration());
return false;
}
@Override
public boolean visit(VariableDeclarationExpression node) {
List<JsCodeIRGenerator.JsCodeElement> result = new ArrayList<>();
switch (node.getKind()) {
case LET :
result.add(token("let"));
break;
case CONST :
result.add(token("const"));
break;
case VAR :// intentional
default :
result.add(token("var"));
break;
}
result.add(v(node.getType()));
result.add(seqCsMap(node.fragments()));
value = seq(result);
return false;
}
@Override
public boolean visit(VariableDeclarationStatement node) {
List<JsCodeIRGenerator.JsCodeElement> result = new ArrayList<>();
switch (node.getKind()) {
case LET :
result.add(token("let"));
break;
case CONST :
result.add(token("const"));
break;
case VAR :// intentional
default :
result.add(token("var"));
break;
}
result.add(seqCsMap(node.fragments()));
value = seq(result);
return false;
}
@Override
public boolean visit(VariableDeclarationFragment node) {
List<JsCodeIRGenerator.JsCodeElement> result = new ArrayList<>();
result.add(v(node.getName()));
for (int i = 0; i < node.getExtraDimensions(); i++) {
result.add(token("[]"));
}
if (node.getInitializer() != null) {
result.add(token("="));
if (node.getInitializer().getNodeType() == ASTNode.INFIX_EXPRESSION && ("in".equals(((InfixExpression) node.getInitializer()).getOperator().toString())) && node.getParent().getParent().getNodeType() == ASTNode.FOR_STATEMENT) {
result.add(paren(v(node.getInitializer())));
}
else {
result.add(v(node.getInitializer()));
}
}
value = seq(result);
return false;
}
@Override
public boolean visit(WhileStatement node) {
value = seqVa(token("while"), paren(v(node.getExpression())), v(node.getBody()));
return false;
}
@Override
public boolean visit(WithStatement node) {
value = seqVa(token("with"), paren(v(node.getExpression())), v(node.getBody()));
return false;
}
@Override
public boolean visit(ObjectLiteral node) {
value = braces(seqCsMap(node.fields()));
return false;
}
@Override
public boolean visit(ObjectLiteralField node) {
value = seqVa(v(node.getFieldName()), token(":"), v(node.getInitializer()));
return false;
}
@Override
public boolean visit(FunctionExpression node) {
value = v(node.getMethod());
return false;
}
@Override
public boolean visit(YieldExpression yieldExpression) {
value = seqVa(token("yield"), parenOpt(expressionPrecedence(yieldExpression), yieldExpression.getArgument()));
return false;
}
@Override
public boolean visit(ArrowFunctionExpression arrowFunctionExpression) {
List<ASTNode> params = arrowFunctionExpression.parameters();
JsCodeElement paramsC = seqCsMap(params);
// TODO Not very clear way to check for BindingIdentifier
// And model seems to lack "defaults" in ESTree format terms
if (params == null || params.size() != 1 || params.get(0).getNodeType() != ASTNode.SINGLE_VARIABLE_DECLARATION || ((VariableDeclaration) params.get(0)).getInitializer() != null) {
paramsC = paren(paramsC);
}
value = seqVa(paramsC, token("=>"), v(arrowFunctionExpression.getBody()), v(arrowFunctionExpression.getExpression()));
return false;
}
@Override
public boolean visit(DebuggerStatement debuggerStatement) {
value = seqVa(factory.token("debugger"), factory.semiOpt());
return false;
}
@Override
public boolean visit(ArrayName node) {
value = brack(seqCsMap(node.elements()));
return false;
}
@Override
public boolean visit(ObjectName node) {
value = braces(seqCsMap(node.objectProperties()));
return false;
}
@Override
public boolean visit(TemplateElement templateElement) {
String rawValue = templateElement.getRawValue();
if (rawValue != null && !rawValue.isEmpty()) {
value = token(rawValue);
}
return false;
}
@Override
public boolean visit(TemplateLiteral node) {
List<JsCodeElement> templateBody = new ArrayList<>();
templateBody.add(token("`"));
for (int i = 0; i < node.elements().size(); i++) {
TemplateElement te = (TemplateElement) node.elements().get(i);
templateBody.add(v(te));
if (!te.isTail()) {
Expression exp = (Expression) node.expressions().get(i);
templateBody.add(token("${"));
templateBody.add(v(exp));
templateBody.add(token("}"));
}
}
templateBody.add(token("`"));
value = seqVa(parenOpt(CALL, node.getTag()), factory.seqRaw(templateBody));
return false;
}
@Override
public boolean visit(AssignmentName node) {
JsCodeElement lhs = v(node.getLeft());
JsCodeElement rhs = v(node.getRight());
// TODO Handle precedence
value = seqVa(lhs, token("="), rhs);
return false;
}
@Override
public boolean visit(RestElementName node) {
value = seqVa(token("..."), v(node.getArgument()));
return false;
}
@Override
public boolean visit(SpreadElement node) {
value = seqVa(token("..."), parenOpt(ASSIGNMENT, node.getArgument()));
return false;
}
@Override
public boolean visit(MetaProperty metaProperty) {
value = token(metaProperty.getMeta() + "." + metaProperty.getPropertyName());
return false;
}
@Override
public boolean visit(ExportDeclaration node) {
// TODO This is just the copy of the import declaration. It's to be
// changed when real ExportDeclaration is
// available
List<JsCodeElement> specifiers = map(node.specifiers());
value = seqVa(token("export"), !specifiers.isEmpty() ? seqVa(seqCs(specifiers), token("from")) : null, v(node.getSource()), semiOpt());
return false;
}
@Override
public boolean visit(ImportDeclaration node) {
// TODO This is just initial vanilla implementation. It is not
// thoroughly tested and may contain bugs.
List<JsCodeElement> specifiers = map(node.specifiers());
value = seqVa(token("import"), !specifiers.isEmpty() ? seqVa(seqCs(specifiers), token("from")) : null, v(node.getSource()), semiOpt());
return false;
}
@Override
public boolean visit(ModuleSpecifier moduleSpecifier) {
// TODO This is just initial vanilla implementation. It is not
// thoroughly tested and may contain bugs.
SimpleName l = moduleSpecifier.getLocal();
SimpleName d = moduleSpecifier.getDiscoverableName();
JsCodeElement result = null;
if (moduleSpecifier.isNamespace()) {
result = seqVa(token("*"), token("as"), v(l));
}
else {
if (d == null || l.getIdentifier().equals(d.getIdentifier())) {
result = v(l);
}
else {
result = seqVa(v(d), token("as"), v(l));
}
if (!moduleSpecifier.isDefault()) {
result = braces(result);
}
}
value = result;
return false;
}
@Override
public boolean visit(TypeDeclarationExpression typeDeclarationExpression) {
value = v(typeDeclarationExpression.getDeclaration());
return false;
}
@Override
public boolean visit(FunctionDeclarationStatement functionDeclarationStatement) {
value = v(functionDeclarationStatement.getDeclaration());
return false;
}
/*
* Following functions are just shortcuts for factory calls.
*/
protected JsCodeElement token(String token) {
return factory.token(token);
}
protected JsCodeElement semi() {
return factory.semi();
}
protected JsCodeElement semiOpt() {
return factory.semiOpt();
}
protected JsCodeElement wrap(String start, JsCodeElement element, String end) {
return factory.wrap(start, element, end);
}
protected JsCodeElement paren(JsCodeElement element) {
return factory.paren(element);
}
protected JsCodeElement brack(JsCodeElement element) {
return factory.brack(element);
}
protected JsCodeElement braces(JsCodeElement element) {
return factory.braces(element);
}
protected JsCodeElement seq(JsCodeElement[] elements) {
return factory.seq(elements);
}
protected JsCodeElement seq(List<JsCodeElement> elements) {
return factory.seq(elements);
}
protected JsCodeElement seqCs(JsCodeElement[] elements) {
return factory.seqCs(elements);
}
protected JsCodeElement seqCs(List<JsCodeElement> elements) {
return factory.seqCs(elements);
}
/**
* Visit given node and return JsCodeElement value created in that
* visitor.
*
* @param node
* node to visit
* @return JsCodeElement created by the visitor
*/
protected JsCodeElement v(ASTNode node) {
if (node != null) {
value = null;
node.accept(this);
return value;
}
else {
return null;
}
}
/**
* Loop through given list of AST Nodes, visit each of them and collect
* returned JsCodeElements into resulting list. If visitor returns
* <code>null</code> this value is not included into the result.
*
* @param nodes
* list of nodes to visit
* @return list of visit results for nodes in the given list
*/
protected List<JsCodeElement> map(List<ASTNode> nodes) {
List<JsCodeElement> result = new ArrayList<>();
for (ASTNode node : nodes) {
JsCodeElement element = v(node);
if (element != null) {
result.add(element);
}
}
return result;
}
/**
* Loop through given list of AST Nodes, visit each of them and collect
* returned JsCodeElements as JsCode sequence element
*
* @param nodes
* nodes to visit
* @return resulting JsCode sequence element
*/
protected JsCodeElement seqMap(List<ASTNode> nodes) {
return seq(map(nodes));
}
/**
* Loop through given list of AST Nodes, visit each of them and collect
* returned JsCodeElements as JsCode comma separated sequence element
*
* @param nodes
* nodes nodes to visit
* @return resulting JsCode comma separated sequence element
*/
protected JsCodeElement seqCsMap(List<ASTNode> nodes) {
return seqCs(map(nodes));
}
/**
* Create JsCodeElement sequence from varArgs list of elements
*
* @param elements
* elements to create sequence from
* @return resulting JsCodeElement sequence
*/
protected JsCodeElement seqVa(JsCodeElement... elements) {
return seq(elements);
}
/**
* Check if given list of modifiers denotes "getter" or "setter"
*
* @param modifiers
* list of modifiers to process
* @return <code>true</code> if given sequence denotes "getter" or
* "setter", <code>false</code> otherwise
*/
protected boolean isGetterOrSetter(List<Modifier> modifiers) {
for (Modifier node : modifiers) {
String kw = node.getKeyword().toString();
switch (kw) {
case "get" :
case "set" :
return true;
default :
break;
}
}
return false;
}
/**
* Wrap given child in optional parens in case if its precedence lower
* than given parent precedence. Used for correct presentation expressions
* like (a+b)*c
*
* @param parentPrec
* precedence of the parent expression
* @param child
* child node to process
* @return optionally enclosed in parens JsCodeElement for child
*/
protected JsCodeElement parenOpt(int parentPrec, ASTNode child) {
if (child != null && child instanceof Expression) {
if (expressionPrecedence((Expression) child) < parentPrec) {
return paren(v(child));
}
else {
return v(child);
}
}
return v(child);
}
/**
* Wrap given child in optional parens in case if its precedence lower
* than or equals given parent precedence. Used for correct presentation
* of right hand expressions in situations like this a << (b << c)
*
* @param parentPrec
* precedence of the parent expression
* @param child
* child node to process
* @return optionally enclosed in parens JsCodeElement for child
*/
protected JsCodeElement parenOptRight(int rootPrec, ASTNode child) {
if (child != null && child instanceof Expression) {
if (expressionPrecedence((Expression) child) <= rootPrec) {
return paren(v(child));
}
else {
return v(child);
}
}
return v(child);
}
/**
* Expressions precedence codes
*/
protected static final int SEQUENCE = 0;
protected static final int ASSIGNMENT = 1;
protected static final int CONDITIONAL = 2;
protected static final int LOGICAL_OR = 3;
protected static final int LOGICAL_AND = 4;
protected static final int BITWISE_OR = 5;
protected static final int BITWISE_XOR = 6;
protected static final int BITWISE_AND = 7;
protected static final int EQUALITY = 8;
protected static final int RELATIONAL = 9;
protected static final int SHIFT = 10;
protected static final int ADDITIVE = 11;
protected static final int MULTIPLICATIVE = 12;
protected static final int PREFIX = 13;
protected static final int POSTFIX = 14;
protected static final int NEW = 15;
protected static final int CALL = 16;
protected static final int MEMBER = 17;
protected static final int PRIMARY = 18;
/**
* Return precedence for given expression. Implemented here because of
* lack precedence in AST
*
* @param e
* expression to analyze
* @return expression's precedence
*/
protected int expressionPrecedence(Expression e) {
if (e instanceof ArrayAccess) {
return MEMBER;
}
else if (e instanceof ArrayInitializer) {
return PRIMARY;
}
else if (e instanceof ArrowFunctionExpression || e instanceof Assignment) {
return ASSIGNMENT;
}
else if (e instanceof BooleanLiteral || e instanceof CharacterLiteral || e instanceof NullLiteral || e instanceof NumberLiteral || e instanceof ObjectLiteral || e instanceof StringLiteral || e instanceof Name || e instanceof RegularExpressionLiteral || e instanceof UndefinedLiteral || e instanceof TemplateLiteral || e instanceof FunctionExpression || e instanceof ThisExpression || e instanceof ObjectLiteralField || e instanceof TypeDeclarationExpression) {
return PRIMARY;
}
else if (e instanceof ClassInstanceCreation) {
return NEW;
}
else if (e instanceof ConditionalExpression) {
return CONDITIONAL;
}
else if (e instanceof FieldAccess) {
return MEMBER;
}
else if (e instanceof FunctionInvocation) {
return CALL;
}
else if (e instanceof ListExpression) {
return SEQUENCE;
}
else if (e instanceof MetaProperty) {
return MEMBER;
}
else if (e instanceof InfixExpression) {
return operatorPrecedence(((InfixExpression) e).getOperator().toString());
}
else if (e instanceof ParenthesizedExpression) {
return expressionPrecedence(((ParenthesizedExpression)e).getExpression());
}
else if (e instanceof PostfixExpression) {
return POSTFIX;
}
else if (e instanceof PrefixExpression) {
return PREFIX;
}
else if (e instanceof SuperMethodInvocation) {
return MEMBER;
}
else if (e instanceof YieldExpression) {
return ASSIGNMENT;
}
throw new UnimplementedException();
}
/**
* Return operators precedence. Implemented here because of absence of
* operator precedence in AST
*
* @param op
* operator to analyze
* @return operator's precedence
*/
private int operatorPrecedence(String op) {
switch (op) {
case "*" :
case "/" :
case "%" :
return MULTIPLICATIVE;
case "+" :
case "-" :
return ADDITIVE;
case "<<" :
case ">>" :
case ">>>" :
return SHIFT;
case "==" :
case "!=" :
case "===" :
case "!==" :
return EQUALITY;
case "|" :
return BITWISE_OR;
case "^" :
return BITWISE_XOR;
case "&" :
return BITWISE_AND;
case "||" :
return LOGICAL_OR;
case "&&" :
return LOGICAL_AND;
case "<" :
case ">" :
case "<=" :
case ">=" :
case "in" :
case "instanceof" :
return RELATIONAL;
}
throw new UnimplementedException();
}
}