org.duelengine.duel.codegen.ScriptTranslator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of duel-compiler Show documentation
Show all versions of duel-compiler Show documentation
Dual-side template engine for the JVM
package org.duelengine.duel.codegen;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.duelengine.duel.DuelContext;
import org.duelengine.duel.DuelData;
import org.duelengine.duel.JSUtility;
import org.duelengine.duel.codedom.AccessModifierType;
import org.duelengine.duel.codedom.CodeArrayCreateExpression;
import org.duelengine.duel.codedom.CodeBinaryOperatorExpression;
import org.duelengine.duel.codedom.CodeBinaryOperatorType;
import org.duelengine.duel.codedom.CodeExpression;
import org.duelengine.duel.codedom.CodeExpressionStatement;
import org.duelengine.duel.codedom.CodeIterationStatement;
import org.duelengine.duel.codedom.CodeMember;
import org.duelengine.duel.codedom.CodeMethod;
import org.duelengine.duel.codedom.CodeMethodInvokeExpression;
import org.duelengine.duel.codedom.CodeMethodReturnStatement;
import org.duelengine.duel.codedom.CodeObject;
import org.duelengine.duel.codedom.CodePrimitiveExpression;
import org.duelengine.duel.codedom.CodePropertyReferenceExpression;
import org.duelengine.duel.codedom.CodeStatement;
import org.duelengine.duel.codedom.CodeStatementBlock;
import org.duelengine.duel.codedom.CodeTernaryOperatorExpression;
import org.duelengine.duel.codedom.CodeTypeDeclaration;
import org.duelengine.duel.codedom.CodeTypeReferenceExpression;
import org.duelengine.duel.codedom.CodeUnaryOperatorExpression;
import org.duelengine.duel.codedom.CodeUnaryOperatorType;
import org.duelengine.duel.codedom.CodeVariableCompoundDeclarationStatement;
import org.duelengine.duel.codedom.CodeVariableDeclarationStatement;
import org.duelengine.duel.codedom.CodeVariableReferenceExpression;
import org.duelengine.duel.codedom.IdentifierScope;
import org.duelengine.duel.codedom.ScriptExpression;
import org.duelengine.duel.codedom.ScriptVariableReferenceExpression;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.ArrayLiteral;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.ConditionalExpression;
import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.ForLoop;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.InfixExpression;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NewExpression;
import org.mozilla.javascript.ast.NumberLiteral;
import org.mozilla.javascript.ast.ObjectLiteral;
import org.mozilla.javascript.ast.ObjectProperty;
import org.mozilla.javascript.ast.ParenthesizedExpression;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.ReturnStatement;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.UnaryExpression;
import org.mozilla.javascript.ast.VariableDeclaration;
import org.mozilla.javascript.ast.VariableInitializer;
/**
* Translates JavaScript source code into CodeDOM
*/
public class ScriptTranslator implements ErrorReporter {
private final IdentifierScope scope;
private Set extraRefs;
private boolean extraAssign;
public ScriptTranslator() {
this(new CodeTypeDeclaration());
}
public ScriptTranslator(IdentifierScope identScope) {
if (identScope == null) {
throw new NullPointerException("identScope");
}
scope = identScope;
}
public Set getExtraRefs() {
if (extraRefs == null) {
return Collections.emptySet();
}
return extraRefs;
}
public boolean hasExtraAssign() {
return extraAssign;
}
/**
* @param jsSource JavaScript source code
* @return Equivalent translated CodeDOM
*/
public List translate(String jsSource) {
extraRefs = null;
extraAssign = false;
String jsFilename = "anonymous.js";
ErrorReporter errorReporter = this;
Context cx = Context.enter();
cx.setLanguageVersion(Context.VERSION_1_5);
CompilerEnvirons compEnv = new CompilerEnvirons();
compEnv.initFromContext(cx);
Parser parser = new Parser(compEnv, errorReporter);
AstRoot root = null;
try {
root = parser.parse(jsSource, jsFilename, 1);
} catch (EvaluatorException ex) {
String message = ex.getMessage();
if (message == null) {
message = ex.toString();
}
throw new ScriptTranslationException(message, ex);
} finally {
Context.exit();
}
if (root == null) {
return null;
}
return visitRoot(root);
}
private CodeStatement visitStatement(AstNode node) {
CodeObject value = visit(node);
if (value instanceof CodeExpression) {
return new CodeExpressionStatement((CodeExpression)value);
} else if (value != null && !(value instanceof CodeStatement)) {
throw new ScriptTranslationException("Expected a statement: "+value.getClass(), node);
}
return (CodeStatement)value;
}
private CodeExpression visitExpression(AstNode node) {
CodeObject value = visit(node);
if (value instanceof CodeExpressionStatement) {
return ((CodeExpressionStatement)value).getExpression();
} else if (value != null && !(value instanceof CodeExpression)) {
throw new ScriptTranslationException("Expected an expression: "+value.getClass(), node);
}
return (CodeExpression)value;
}
private CodeExpression[] visitExpressionList(List nodes) {
int length = nodes.size();
if (length < 1) {
return null;
}
CodeExpression[] expressions = new CodeExpression[length];
for (int i=0; i();
}
extraRefs.add(ident);
} else {
extraAssign = true;
}
return new ScriptVariableReferenceExpression(ident);
}
private CodeObject visitForLoop(ForLoop node) {
CodeIterationStatement loop = new CodeIterationStatement(
visitStatement(node.getInitializer()),
visitExpression(node.getCondition()),
visitStatement(node.getIncrement()));
CodeObject body = visit(node.getBody());
if (body instanceof CodeStatementBlock) {
loop.getStatements().addAll((CodeStatementBlock)body);
} else {
throw new ScriptTranslationException("Expected statement block ("+body.getClass()+")", node.getBody());
}
return loop;
}
private CodeMethod visitFunction(FunctionNode node) throws IllegalArgumentException {
CodeMethod method = new CodeMethod(
AccessModifierType.PRIVATE,
Object.class,
scope.nextIdent("code_"),
null);
if (node.depth() == 1) {
method.addParameter(DuelContext.class, "context");
method.addParameter(Object.class, "data");
method.addParameter(int.class, "index");
method.addParameter(int.class, "count");
method.addParameter(String.class, "key");
} else {
// TODO: extract parameter names / types
throw new ScriptTranslationException("Nested functions not yet supported.", node);
}
CodeObject body = visit(node.getBody());
if (body instanceof CodeStatementBlock) {
method.getStatements().addAll((CodeStatementBlock)body);
} else if (body instanceof CodeStatement) {
method.getStatements().add((CodeStatement)body);
} else if (body instanceof CodeExpression) {
method.getStatements().add((CodeExpression)body);
} else if (body != null) {
throw new ScriptTranslationException("Unexpected function body: "+body.getClass(), node.getBody());
}
if ((node.depth() == 1) &&
!(method.getStatements().getLastStatement() instanceof CodeMethodReturnStatement)) {
// this is effectively an empty return in JavaScript
method.getStatements().add(new CodeMethodReturnStatement(ScriptExpression.UNDEFINED));
}
/*
// refine return type?
for (CodeStatement statement : method.getStatements()) {
if (statement instanceof CodeMethodReturnStatement &&
((CodeMethodReturnStatement)statement).getExpression() != null) {
method.setReturnType(((CodeMethodReturnStatement)statement).getExpression().getReturnType());
break;
}
}
*/
return method;
}
private CodeMethodReturnStatement visitReturn(ReturnStatement node) {
CodeExpression value = visitExpression(node.getReturnValue());
// always return a value
return new CodeMethodReturnStatement(value != null ? value : ScriptExpression.UNDEFINED);
}
private CodeObject visitFunctionCall(FunctionCall node) {
CodeExpression target = visitExpression(node.getTarget());
if (!(target instanceof CodePropertyReferenceExpression)) {
throw new ScriptTranslationException("Unsupported function call ("+node.getClass()+"):\n"+(node.debugPrint()), node.getTarget());
}
CodePropertyReferenceExpression propertyRef = (CodePropertyReferenceExpression)target;
CodeExpression nameExpr = propertyRef.getPropertyName();
if (!(nameExpr instanceof CodePrimitiveExpression)) {
throw new ScriptTranslationException("Unsupported function call ("+node.getClass()+"):\n"+(node.debugPrint()), node.getTarget());
}
String methodName = DuelData.coerceString(((CodePrimitiveExpression)nameExpr).getValue());
CodeExpression[] args = visitExpressionList(node.getArguments());
CodeObject methodCall = CodeDOMUtility.translateMethodCall(propertyRef.getResultType(), propertyRef.getTarget(), methodName, args);
if (methodCall == null) {
throw new ScriptTranslationException("Unsupported function call ("+node.getClass()+"):\n"+(node.debugPrint()), node.getTarget());
}
return methodCall;
}
private CodeObject visitNew(NewExpression node) {
AstNode target = node.getTarget();
if (target instanceof Name) {
String ident = ((Name)target).getIdentifier();
if ("Array".equals(ident)) {
return visitArrayCtor(node);
}
// TODO: add other JS types
}
throw new ScriptTranslationException("Create object type not yet supported ("+node.getClass()+"):\n"+(node.debugPrint()), node);
}
private CodeObject visitObjectLiteral(ObjectLiteral node) {
List properties = node.getElements();
int length = properties.size();
CodeExpression[] initializers = new CodeExpression[length*2];
for (int i=0; i visitRoot(AstRoot root) {
List members = new ArrayList();
for (Node node : root) {
CodeObject member = visit((AstNode)node);
if (member == null) {
continue;
} else if (member instanceof CodeMember) {
members.add((CodeMember)member);
} else {
throw new ScriptTranslationException("Unexpected member: "+member.getClass(), (AstNode)node);
}
}
return members;
}
@Override
public void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {
// do nothing with warnings for now
}
@Override
public void error(String message, String sourceName, int line, String lineSource, int lineOffset) {
throw runtimeError(message, sourceName, line, lineSource, lineOffset);
}
@Override
public EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset) {
return new EvaluatorException(message, sourceName, line, lineSource, lineOffset);
}
}