org.duelengine.duel.codegen.ClientCodeGen 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.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.duelengine.duel.DataEncoder;
import org.duelengine.duel.ast.CALLCommandNode;
import org.duelengine.duel.ast.CodeBlockNode;
import org.duelengine.duel.ast.CodeCommentNode;
import org.duelengine.duel.ast.CommentNode;
import org.duelengine.duel.ast.DocTypeNode;
import org.duelengine.duel.ast.DuelNode;
import org.duelengine.duel.ast.ElementNode;
import org.duelengine.duel.ast.ExpressionNode;
import org.duelengine.duel.ast.LiteralNode;
import org.duelengine.duel.ast.VIEWCommandNode;
import org.duelengine.duel.parsing.InvalidNodeException;
/**
* Generates client-side code for views
* Inherently thread-safe as contains no mutable instance data.
*/
public class ClientCodeGen implements CodeGenerator {
private final CodeGenSettings settings;
private final DataEncoder encoder;
public ClientCodeGen() {
this(null);
}
public ClientCodeGen(CodeGenSettings codeGenSettings) {
settings = (codeGenSettings != null) ? codeGenSettings : new CodeGenSettings();
encoder = new DataEncoder(settings.getNewline(), settings.getIndent());
}
@Override
public String getFileExtension() {
return ".js";
}
/**
* Generates client-side code for the given views
* @param output
* @param views
* @throws IOException
*/
@Override
public void write(Appendable output, VIEWCommandNode... views)
throws IOException {
write(output, views != null ? Arrays.asList(views) : null);
}
/**
* Generates client-side code for the given views
* @param output
* @param views
* @throws IOException
*/
@Override
public void write(Appendable output, Iterable views)
throws IOException {
if (output == null) {
throw new NullPointerException("output");
}
if (views == null) {
throw new NullPointerException("views");
}
output.append("/*global duel */");
writeln(output, 0);
List namespaces = new ArrayList();
for (VIEWCommandNode view : views) {
if (view == null || view.isServerOnly()) {
continue;
}
writeln(output, 0);
// prepend the client-side prefix
String viewName = settings.getClientName(view.getName());
try {
if (encoder.writeNamespace(output, namespaces, viewName)) {
writeln(output, 0);
}
} catch (IllegalArgumentException ex) {
throw new InvalidNodeException("Invalid view name: "+viewName, view.getAttribute("name"), ex);
}
writeView(output, view, viewName);
}
}
private void writeView(Appendable output, VIEWCommandNode view, String viewName)
throws IOException {
// prepend the client-side prefix
if (viewName.indexOf('.') < 0) {
output.append("var ");
}
output.append(viewName);
output.append(" = duel(");
if (view.childCount() == 1) {
DuelNode child = view.getFirstChild();
if (child instanceof ElementNode &&
(((ElementNode)child).hasChildren() ||
((ElementNode)child).hasAttributes())) {
writeln(output, 1);
}
// just emit the single child
writeNode(output, child, 1, false);
} else {
// wrap in a document fragment
writeElement(output, "", view, 0, false);
}
output.append(");");
writeln(output, 0);
}
private void writeNode(Appendable output, DuelNode node, int depth, boolean preMode)
throws IOException {
if (node instanceof LiteralNode) {
writeString(output, ((LiteralNode)node).getValue(), preMode);
} else if (node instanceof ElementNode) {
writeElement(output, ((ElementNode)node).getTagName(), (ElementNode)node, depth, preMode);
} else if (node instanceof CodeBlockNode) {
writeCodeBlock(output, (CodeBlockNode)node);
} else if (node instanceof CommentNode) {
writeSpecialElement(output, "!", ((CommentNode)node).getValue(), depth, preMode);
} else if (node instanceof CodeCommentNode) {
writeComment(output, ((CodeCommentNode)node).getValue());
} else if (node instanceof DocTypeNode) {
writeSpecialElement(output, "!DOCTYPE", ((DocTypeNode)node).getValue(), depth, preMode);
} else if (node != null) {
throw new UnsupportedOperationException("Node not yet implemented: "+node.getClass());
}
}
private void writeComment(Appendable output, String value)
throws IOException {
output.append("/*");
if (value != null) {
output.append(value.replace("*/", "*\\/"));
}
output.append("*/");
}
private void writeCodeBlock(Appendable output, CodeBlockNode node)
throws IOException {
output.append(node.getClientCode(encoder.isPrettyPrint()));
}
private void writeSpecialElement(Appendable output, String name, String value, int depth, boolean preMode)
throws IOException {
output.append('[');
depth++;
writeString(output, name, preMode);
boolean hasValue = (value != null) && (value.length() > 0);
if (hasValue) {
output.append(',');
writeln(output, depth);
writeString(output, value, preMode);
}
depth--;
if (hasValue) {
writeln(output, depth);
}
output.append(']');
}
private void writeElement(Appendable output, String tagName, ElementNode node, int depth, boolean preMode)
throws IOException {
if (!preMode) {
preMode = "pre".equalsIgnoreCase(tagName) || "script".equalsIgnoreCase(tagName);
}
output.append('[');
depth++;
writeString(output, tagName, preMode);
if (node.hasAttributes()) {
Set attrs = node.getAttributeNames();
boolean singleAttr = (attrs.size() == 1);
output.append(", {");
depth++;
boolean addPrefix = (node instanceof CALLCommandNode) && settings.hasClientNamePrefix();
boolean needsDelim = false;
for (String attr : attrs) {
if (singleAttr) {
output.append(' ');
} else {
// property delimiter
if (needsDelim) {
output.append(',');
} else {
needsDelim = true;
}
writeln(output, depth);
}
writeString(output, attr, preMode);
output.append(" : ");
DuelNode attrVal = node.getAttribute(attr);
if (attrVal == null) {
output.append("null");
} else if (attrVal instanceof CommentNode) {
output.append("\"\"");
} else {
if (addPrefix && "view".equalsIgnoreCase(attr) && attrVal instanceof ExpressionNode) {
// prepend the client-side prefix
ExpressionNode nameAttr = (ExpressionNode)attrVal;
attrVal = new ExpressionNode(settings.getClientName(nameAttr.getValue()), nameAttr.getIndex(), nameAttr.getLine(), nameAttr.getColumn());
}
writeNode(output, attrVal, depth, preMode);
}
}
depth--;
if (singleAttr) {
output.append(' ');
} else {
writeln(output, depth);
}
output.append('}');
}
boolean hasChildren = false;
boolean needsDelim = false;
List children = node.getChildren();
int length=children.size();
// less efficient but allows better comma placement
for (int j=0; j 0) {
// not very efficient but allows simple normalization
value = value.replaceAll("^[\\r\\n]+", "").replaceAll("[\\r\\n]+$", "").replaceAll("\\s+", " ");
if (value.isEmpty()) {
value = " ";
}
} else if (settings.getConvertLineEndings()) {
// not very efficient but allows simple normalization
if (!"\t".equals(settings.getIndent())) {
value = value.replace("\t", settings.getIndent());
}
if (!"\n".equals(settings.getNewline())) {
value = value.replace("\n", settings.getNewline());
}
}
int start = 0,
length = value.length();
output.append('\'');
for (int i=start; i= ' ' && ch < '\u007F') {
// no need to escape ASCII chars
continue;
}
escape = String.format("\\u%04X", value.codePointAt(i));
break;
}
if (i > start) {
output.append(value, start, i);
}
start = i+1;
output.append(escape);
}
if (length > start) {
output.append(value, start, length);
}
output.append('\'');
}
private void writeln(Appendable output, int depth)
throws IOException {
output.append(settings.getNewline());
String indent = settings.getIndent();
while (depth-- > 0) {
output.append(indent);
}
}
}