Commits

Stephen McKamey committed f28724d

- adding hooks for fallback to client-side block at runtime based upon ambient data settings

Comments (0)

Files changed (8)

src/org/duelengine/duel/codedom/CodeExpression.java

 	}
 
 	@Override
+	public CodeExpression withUserData(Object... pairs) {
+		return (CodeExpression)super.withUserData(pairs);
+	}
+
+	@Override
 	public boolean equals(Object arg) {
 		if (!(arg instanceof CodeExpression)) {
 			// includes null

src/org/duelengine/duel/codedom/CodeMember.java

 	}
 
 	@Override
+	public CodeMember withUserData(Object... pairs) {
+		return (CodeMember)super.withUserData(pairs);
+	}
+
+	@Override
 	public boolean equals(Object arg) {
 		if (!(arg instanceof CodeMember)) {
 			// includes null

src/org/duelengine/duel/codedom/CodeObject.java

 package org.duelengine.duel.codedom;
 
+import java.util.*;
+import org.duelengine.duel.DuelData;
 import org.duelengine.duel.codegen.ServerCodeGen;
 
 public abstract class CodeObject {
 
+	private Map<String, Object> userData;
+
+	public Object getUserData(String key) {
+		if (this.userData == null || !this.userData.containsKey(key)) {
+			return null;
+		}
+
+		return this.userData.get(key);
+	}
+
+	public Object putUserData(String key, Object value) {
+		if (this.userData == null) {
+			this.userData = new HashMap<String, Object>();
+		}
+
+		return this.userData.put(key, value);
+	}
+
+	public CodeObject withUserData(Object... pairs) {
+		if (pairs == null || pairs.length < 1) {
+			return this;
+		}
+
+		if (this.userData == null) {
+			this.userData = new HashMap<String, Object>();
+		}
+
+		int length = pairs.length/2;
+		for (int i=0; i<length; i++) {
+			String key = DuelData.coerceString(pairs[2*i]);
+			Object value = pairs[2*i+1];
+			this.userData.put(key, value);
+		}
+		return this;
+	}
+	
 	@Override
 	public String toString() {
 		try {

src/org/duelengine/duel/codedom/CodeStatement.java

  */
 public abstract class CodeStatement extends CodeObject {
 
+	@Override
+	public CodeStatement withUserData(Object... pairs) {
+		return (CodeStatement)super.withUserData(pairs);
+	}
 }

src/org/duelengine/duel/codedom/CodeStatementBlock.java

 	}
 
 	@Override
+	public CodeStatementBlock withUserData(Object... pairs) {
+		return (CodeStatementBlock)super.withUserData(pairs);
+	}
+
+	@Override
 	public boolean equals(Object arg) {
 		if (!(arg instanceof CodeStatementBlock)) {
 			// includes null

src/org/duelengine/duel/codegen/CodeDOMUtility.java

 	 * @return null if not able to be inlined
 	 */
 	public static CodeExpression inlineMethod(CodeMethod method) {
+		if (method.getUserData(ScriptTranslator.CLIENT_SOURCE) != null) {
+			// maintain entire code block as unit for fallback support
+			return null;
+		}
+
 		List<CodeParameterDeclarationExpression> parameters = method.getParameters();
 		if (parameters.size() != 5 || !DuelContext.class.equals(parameters.get(0).getType())) {
 			// incompatible method signature

src/org/duelengine/duel/codegen/ScriptTranslator.java

  */
 public class ScriptTranslator implements ErrorReporter {
 
+	public static final String CLIENT_SOURCE = "CLIENT";
+	
 	private final IdentifierScope scope;
+	private boolean needsFallback;
 
 	public ScriptTranslator() {
 		this(new CodeTypeDeclaration());
 			return null;
 		}
 
-		return this.visitRoot(root);
+		List<CodeMember> members = this.visitRoot(root);
+		if (this.needsFallback && members.size() > 0) {
+			// store original source code on the member to
+			// allow generation of fallback block
+			members.get(0).withUserData(CLIENT_SOURCE, jsSource);
+		}
+		return members;
 	}
 
 	private CodeStatement visitStatement(AstNode node) {
 				return new CodePrimitiveExpression(Double.POSITIVE_INFINITY);
 			}
 
-//			throw new ScriptTranslationException("Unsupported global var reference: "+ident, node);
+			// mark as potential to fail at runtime based upon data
+			this.needsFallback = true;
 			return new ScriptVariableReferenceExpression(ident);
 		}
 
 			return new CodeVariableReferenceExpression(Object.class, ident);
 		}
 
+		// mark as potential to fail at runtime based upon data
+		this.needsFallback = true;
 		return new ScriptVariableReferenceExpression(ident);
 	}
 

test/org/duelengine/duel/codegen/ScriptTranslatorTests.java

 public class ScriptTranslatorTests {
 
 	@Test
+	public void translateEmptyTest() {
+		String input = "function(data) { return ( /*...*/ ); }";
+
+		try {
+			new ScriptTranslator().translate(input);
+
+			fail("Expected to throw a ScriptTranslationException");
+
+		} catch (ScriptTranslationException ex) {
+		
+			assertEquals(35, ex.getColumn());
+		}
+	}
+
+	@Test
 	public void translateVarRefTest() {
 		String input = "function(data) { return data; }";
 
 	}
 
 	@Test
+	public void translateExternalVarRefTest() {
+		String input = "function(data) { return foo.bar; }";
+
+		CodeMethod expected =
+			new CodeMethod(
+				AccessModifierType.PRIVATE,
+				Object.class,
+				"code_1",
+				new CodeParameterDeclarationExpression[] {
+					new CodeParameterDeclarationExpression(DuelContext.class, "output"),
+					new CodeParameterDeclarationExpression(Object.class, "data"),
+					new CodeParameterDeclarationExpression(int.class, "index"),
+					new CodeParameterDeclarationExpression(int.class, "count"),
+					new CodeParameterDeclarationExpression(String.class, "key")
+				},
+				new CodeMethodReturnStatement(
+					new CodePropertyReferenceExpression(
+						new ScriptVariableReferenceExpression("foo"),
+						new CodePrimitiveExpression("bar"))));
+
+		List<CodeMember> actual = new ScriptTranslator().translate(input);
+		assertNotNull(actual);
+		assertEquals(1, actual.size());
+		assertEquals(expected, actual.get(0));
+	}
+
+	@Test
 	public void translateMapValueAccessTest() {
 		String input = "function(data) { return data['foo']; }";
 
 	@Test
 	public void translateForLoopTest() {
 		String input =
-			"function(data) {"+
-			"var str;"+
-			"for (var i=0, length=data.length; i<length; i++) {"+
-			"str += data[i].toString();"+
-			"}"+
-			"return str;"+
+			"function (data) {"+
+			"  var str;"+
+			"  for (var i=0, length=data.length; i<length; i++) {"+
+			"    str += data[i].toString();"+
+			"  }"+
+			"  return str;"+
 			"}";
 
 		CodeMethod expected =
 					new CodeParameterDeclarationExpression(String.class, "key")
 				},
 				new CodeMethodReturnStatement(
-					new CodeUnaryOperatorExpression(CodeUnaryOperatorType.NEGATION, new CodePrimitiveExpression(Double.POSITIVE_INFINITY))));
+					new CodeUnaryOperatorExpression(
+						CodeUnaryOperatorType.NEGATION,
+						new CodePrimitiveExpression(Double.POSITIVE_INFINITY))));
 
 		List<CodeMember> actual = new ScriptTranslator().translate(input);
 		assertNotNull(actual);
 		assertEquals(1, actual.size());
 		assertEquals(expected, actual.get(0));
 	}
-
-	@Test
-	public void translateEmptyTest() {
-		String input = "function(data) { return ( /*...*/ ); }";
-
-		try {
-			new ScriptTranslator().translate(input);
-
-			fail("Expected to throw a ScriptTranslationException");
-
-		} catch (ScriptTranslationException ex) {
-		
-			assertEquals(35, ex.getColumn());
-		}
-	}
 }