blob: a75d8451c249938a10eb539507df672c545dc155 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed 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 com.google.clearsilver.jsilver.compiler;
import com.google.clearsilver.jsilver.data.TypeConverter;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* Represents a node of a Java expression.
*
* This class contains static helper methods for common types of expressions, or you can just create
* your own subclass.
*/
public abstract class JavaExpression {
/**
* Simple type enumeration to allow us to compare the return types of expressions easily and cast
* expressions nicely.
*/
public enum Type {
STRING("String") {
@Override
protected JavaExpression cast(JavaExpression expression) {
if (expression.getType() == VAR_NAME) {
expression = expression.cast(DATA);
}
return call(Type.STRING, "asString", expression);
}
},
INT("int") {
@Override
protected JavaExpression cast(JavaExpression expression) {
if (expression.getType() == VAR_NAME) {
expression = expression.cast(DATA);
}
return call(Type.INT, "asInt", expression);
}
},
BOOLEAN("boolean") {
@Override
protected JavaExpression cast(JavaExpression expression) {
if (expression.getType() == VAR_NAME) {
expression = expression.cast(DATA);
}
return call(Type.BOOLEAN, "asBoolean", expression);
}
},
VALUE("Value") {
@Override
protected JavaExpression cast(JavaExpression expression) {
if (expression.getType() == VAR_NAME) {
return call(Type.VALUE, "asVariableValue", expression, TemplateTranslator.DATA_CONTEXT);
} else {
return call(Type.VALUE, "asValue", expression);
}
}
},
DATA("Data") {
@Override
protected JavaExpression cast(JavaExpression expression) {
if (expression.getType() == VAR_NAME) {
return callFindVariable(expression, false);
} else {
throw new JSilverCompilationException("Cannot cast to 'Data' for expression:\n"
+ expression.toString());
}
}
},
// This is a string that represents the name of a Data path.
VAR_NAME("String") {
@Override
protected JavaExpression cast(JavaExpression expression) {
final JavaExpression stringExpr = expression.cast(Type.STRING);
return new JavaExpression(Type.VAR_NAME) {
public void write(PrintWriter out) {
stringExpr.write(out);
}
};
}
},
// This is a special type because we only cast from DataContext, never to it.
DATA_CONTEXT("DataContext") {
@Override
protected JavaExpression cast(JavaExpression expression) {
throw new JSilverCompilationException("Cannot cast to 'DataContext' for expression:\n"
+ expression.toString());
}
},
// This is a special type because we only cast from Data, never to it.
MACRO("Macro") {
@Override
protected JavaExpression cast(JavaExpression expression) {
throw new JSilverCompilationException("Cannot cast to 'Macro' for expression:\n"
+ expression.toString());
}
},
// Use this type for JavaExpressions that have no type (such as method
// calls with no return value). Wraps the input expression with a
// JavaExpression of Type VOID.
VOID("Void") {
@Override
protected JavaExpression cast(final JavaExpression expression) {
return new JavaExpression(Type.VOID) {
@Override
public void write(PrintWriter out) {
expression.write(out);
}
};
}
};
/** Useful constant for unknown types */
public static final Type UNKNOWN = null;
/**
* The Java literal representing the type (e.g. "int", "boolean", "Value")
*/
public final String symbol;
/**
* Unconditionally casts the given expression to the type. This should only be called after it
* has been determined that the destination type is not the same as the expression type.
*/
protected abstract JavaExpression cast(JavaExpression expression);
private Type(String symbol) {
this.symbol = symbol;
}
}
private final Type type;
/**
* Creates a typed expression. Typed expressions allow for greater optimization by avoiding
* unnecessary casting operations.
*
* @param type the Type of the expression. Must be from the enum above and represent a primitive
* or a Class name or void.
*/
public JavaExpression(Type type) {
this.type = type;
}
/**
* Cast this expression to the destination type (possibly a no-op)
*/
public JavaExpression cast(Type destType) {
return (type != destType) ? destType.cast(this) : this;
}
/**
* Gets the type of this expression (or {@code null} if unknown).
*/
public Type getType() {
return type;
}
/**
* Implementations use this to output the expression as Java code.
*/
public abstract void write(PrintWriter out);
@Override
public String toString() {
StringWriter out = new StringWriter();
write(new PrintWriter(out));
return out.toString();
}
/**
* An untyped method call (e.g. doStuff(x, "y")).
*/
public static JavaExpression call(final String method, final JavaExpression... params) {
return call(null, method, params);
}
/**
* A typed method call (e.g. doStuff(x, "y")).
*/
public static JavaExpression call(Type type, final String method, final JavaExpression... params) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
JavaSourceWriter.writeJavaSymbol(out, method);
out.append('(');
boolean seenAnyParams = false;
for (JavaExpression param : params) {
if (seenAnyParams) {
out.append(", ");
} else {
seenAnyParams = true;
}
param.write(out);
}
out.append(')');
}
};
}
/**
* An untyped method call on an instance (e.g. thingy.doStuff(x, "y")). We assume it returns VOID
* and thus there is no return value.
*/
public static JavaExpression callOn(final JavaExpression instance, final String method,
final JavaExpression... params) {
return callOn(Type.VOID, instance, method, params);
}
/**
* A typed method call on an instance (e.g. thingy.doStuff(x, "y")).
*/
public static JavaExpression callOn(Type type, final JavaExpression instance,
final String method, final JavaExpression... params) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
instance.write(out);
out.append('.');
call(method, params).write(out);
}
};
}
/**
* A Java string (e.g. "hello\nworld").
*/
public static JavaExpression string(String value) {
return new StringExpression(value);
}
public static class StringExpression extends JavaExpression {
private final String value;
public StringExpression(String value) {
super(Type.STRING);
this.value = value;
}
public String getValue() {
return value;
}
@Override
public void write(PrintWriter out) {
// TODO: This is not production ready yet - needs more
// thorough escaping mechanism.
out.append('"');
char[] chars = value.toCharArray();
for (char c : chars) {
switch (c) {
// Single quote (') does not need to be escaped as it's in a
// double-quoted (") string.
case '\n':
out.append("\\n");
break;
case '\r':
out.append("\\r");
break;
case '\t':
out.append("\\t");
break;
case '\\':
out.append("\\\\");
break;
case '"':
out.append("\\\"");
break;
case '\b':
out.append("\\b");
break;
case '\f':
out.append("\\f");
break;
default:
out.append(c);
}
}
out.append('"');
}
}
/**
* A JavaExpression to represent boolean literal values ('true' or 'false').
*/
public static class BooleanLiteralExpression extends JavaExpression {
private final boolean value;
public static final BooleanLiteralExpression FALSE = new BooleanLiteralExpression(false);
public static final BooleanLiteralExpression TRUE = new BooleanLiteralExpression(true);
private BooleanLiteralExpression(boolean value) {
super(Type.BOOLEAN);
this.value = value;
}
public boolean getValue() {
return value;
}
@Override
public void write(PrintWriter out) {
out.append(String.valueOf(value));
}
}
/**
* An integer.
*/
public static JavaExpression integer(String value) {
// Just parse it to to check that it is valid
TypeConverter.parseNumber(value);
return literal(Type.INT, value);
}
/**
* An integer.
*/
public static JavaExpression integer(int value) {
return literal(Type.INT, String.valueOf(value));
}
/**
* A boolean
*/
public static JavaExpression bool(boolean value) {
return literal(Type.BOOLEAN, value ? "true" : "false");
}
/**
* An untyped symbol (e.g. myVariable).
*/
public static JavaExpression symbol(final String value) {
return new JavaExpression(Type.UNKNOWN) {
@Override
public void write(PrintWriter out) {
JavaSourceWriter.writeJavaSymbol(out, value);
}
};
}
/**
* A typed symbol (e.g. myVariable).
*/
public static JavaExpression symbol(Type type, final String value) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
JavaSourceWriter.writeJavaSymbol(out, value);
}
};
}
public static JavaExpression macro(final String value) {
return symbol(Type.MACRO, value);
}
/**
* A typed assignment (e.g. stuff = doSomething()).
*/
public static JavaExpression assign(Type type, final String name, final JavaExpression value) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
JavaSourceWriter.writeJavaSymbol(out, name);
out.append(" = ");
value.write(out);
}
};
}
/**
* A typed assignment with declaration (e.g. String stuff = doSomething()). Use this in preference
* when declaring variables from typed expressions.
*/
public static JavaExpression declare(final Type type, final String name,
final JavaExpression value) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
JavaSourceWriter.writeJavaSymbol(out, type.symbol);
out.append(' ');
assign(type, name, value).write(out);
}
};
}
/**
* An infix expression (e.g. (a + b) ).
*/
public static JavaExpression infix(Type type, final String operator, final JavaExpression left,
final JavaExpression right) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
out.append("(");
left.write(out);
out.append(" ").append(operator).append(" ");
right.write(out);
out.append(")");
}
};
}
/**
* An prefix expression (e.g. (-a) ).
*/
public static JavaExpression prefix(Type type, final String operator,
final JavaExpression expression) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
out.append("(").append(operator);
expression.write(out);
out.append(")");
}
};
}
/**
* A three term inline if expression (e.g. (a ? b : c) ).
*/
public static JavaExpression inlineIf(Type type, final JavaExpression query,
final JavaExpression trueExp, final JavaExpression falseExp) {
if (query.getType() != Type.BOOLEAN) {
throw new IllegalArgumentException("Expect BOOLEAN expression");
}
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
out.append("(");
query.write(out);
out.append(" ? ");
trueExp.write(out);
out.append(" : ");
falseExp.write(out);
out.append(")");
}
};
}
/**
* An increment statement (e.g. a += b). The difference with infix is that this does not wrap the
* expression in parentheses as that is not a valid statement.
*/
public static JavaExpression increment(Type type, final JavaExpression accumulator,
final JavaExpression incr) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
accumulator.write(out);
out.append(" += ");
incr.write(out);
}
};
}
/**
* A literal expression (e.g. anything!). This method injects whatever string it is given into the
* Java code - use only in cases where there can be no ambiguity about how the string could be
* interpreted by the compiler.
*/
public static JavaExpression literal(Type type, final String value) {
return new JavaExpression(type) {
@Override
public void write(PrintWriter out) {
out.append(value);
}
};
}
public static JavaExpression callFindVariable(JavaExpression expression, boolean create) {
if (expression.getType() != Type.VAR_NAME) {
throw new IllegalArgumentException("Expect VAR_NAME expression");
}
return callOn(Type.DATA, TemplateTranslator.DATA_CONTEXT, "findVariable", expression,
JavaExpression.bool(create));
}
}