All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.duelengine.duel.codegen.ScriptTranslator Maven / Gradle / Ivy

There is a newer version: 0.9.7
Show newest version
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);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy