Skip to content

Commit

Permalink
Merge pull request #236 from casid/226-syntax-proposal-for-else
Browse files Browse the repository at this point in the history
226 syntax proposal for else
  • Loading branch information
casid committed Jun 8, 2023
2 parents bcb3099 + 08c8f0f commit 6d40ab8
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import gg.jte.TemplateConfig;
import gg.jte.TemplateException;
import gg.jte.compiler.*;
import gg.jte.compiler.CodeBuilder.CodeMarker;
import gg.jte.runtime.ClassInfo;
import gg.jte.runtime.Constants;
import gg.jte.runtime.DebugInfo;
import gg.jte.compiler.TemplateType;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static gg.jte.runtime.Constants.TEXT_PART_BINARY;
Expand All @@ -29,9 +28,11 @@ public class KotlinCodeGenerator implements CodeGenerator {
private final List<ParamInfo> parameters = new ArrayList<>();
private final List<String> imports = new ArrayList<>();
private final List<byte[]> binaryTextParts = new ArrayList<>();
private final Deque<ForLoopStart> forLoopStack = new ArrayDeque<>();

private boolean hasWrittenPackage;
private boolean hasWrittenClass;
private CodeMarker fieldsMarker;

public KotlinCodeGenerator(TemplateCompiler compiler, TemplateConfig config, ConcurrentHashMap<String, List<ParamInfo>> paramOrder, ClassInfo classInfo, LinkedHashSet<ClassDefinition> classDefinitions, LinkedHashSet<TemplateDependency> templateDependencies) {
this.compiler = compiler;
Expand Down Expand Up @@ -78,7 +79,7 @@ private void writeClass() {
kotlinCode.append("@Suppress(\"UNCHECKED_CAST\", \"UNUSED_PARAMETER\")").append('\n');
kotlinCode.append("class ").append(classInfo.className).append(" {\n");
kotlinCode.append("companion object {\n");
kotlinCode.markFieldsIndex();
fieldsMarker = kotlinCode.getMarkerOfCurrentPosition();
kotlinCode.append("\t@JvmStatic fun render(");
writeTemplateOutputParam();
kotlinCode.append(", jteHtmlInterceptor:gg.jte.html.HtmlInterceptor?");
Expand Down Expand Up @@ -121,18 +122,19 @@ public void onLineFinished() {

@Override
public void onComplete() {
// Line information must be updated before insert, otherwise the line info field is not up-to-date
int lineCount = 2;
if (!binaryTextParts.isEmpty()) {
lineCount += binaryTextParts.size() + 1;
}
kotlinCode.insertFieldLines(lineCount);
kotlinCode.fillLines(fieldsMarker, lineCount);

StringBuilder fields = new StringBuilder(64 + 32 * lineCount);
StringBuilder fields = new StringBuilder();
addNameField(fields, classInfo.name);
addLineInfoField(fields);
writeBinaryTextParts(fields);

kotlinCode.insertFields(fields);
kotlinCode.insert(fieldsMarker, fields, false);

kotlinCode.append("\t}\n");

Expand Down Expand Up @@ -378,12 +380,45 @@ public void onConditionEnd(int depth) {

@Override
public void onForLoopStart(int depth, String codePart) {
CodeMarker beforeLoop = kotlinCode.getMarkerOfCurrentPosition();

writeIndentation(depth);
kotlinCode.append("for (").append(codePart).append(") {\n");

CodeMarker inLoop = kotlinCode.getMarkerOfCurrentPosition();

forLoopStack.push(new ForLoopStart(beforeLoop, inLoop, depth));
}

@Override
public void onForLoopElse(int depth) {
ForLoopStart forLoopStart = forLoopStack.peek();

if (forLoopStart != null) {
String variableName = "__jte_for_loop_entered_" + forLoopStack.size();

StringBuilder variableDeclaration = new StringBuilder();
writeIndentation(variableDeclaration, forLoopStart.indentation);
variableDeclaration.append("var ").append(variableName).append(" = false\n");
kotlinCode.insert(forLoopStart.beforeLoop, variableDeclaration);

StringBuilder variableAssignment = new StringBuilder();
writeIndentation(variableAssignment, forLoopStart.indentation + 1);
variableAssignment.append(variableName).append(" = true\n");
kotlinCode.insert(forLoopStart.inLoop, variableAssignment);

writeIndentation(depth);
kotlinCode.append("}\n");

writeIndentation(depth);
kotlinCode.append("if (!").append(variableName).append(") {\n");
}
}

@Override
public void onForLoopEnd(int depth) {
forLoopStack.pop();

writeIndentation(depth);
kotlinCode.append("}\n");
}
Expand Down Expand Up @@ -521,6 +556,13 @@ private void writeIndentation(int depth) {
}
}

@SuppressWarnings("StringRepeatCanBeUsed")
private void writeIndentation(StringBuilder code, int depth) {
for (int i = 0; i < depth + 2; ++i) {
code.append('\t');
}
}

@Override
public String getCode() {
return kotlinCode.getCode();
Expand Down Expand Up @@ -602,4 +644,6 @@ public ParamCallInfo(String param) {
}
}
}

private record ForLoopStart(CodeMarker beforeLoop, CodeMarker inLoop, int indentation) {}
}
93 changes: 93 additions & 0 deletions jte-kotlin/src/test/java/gg/jte/kotlin/TemplateEngineTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,99 @@ void loopInContentBlock() {
thenOutputIs("123");
}


@Test
void loopElse_empty() {
model.array = new int[]{};
givenTemplate("@for (i in model.array)" +
"${i}" +
"@else" +
"Empty" +
"@endfor");
thenOutputIs("Empty");
}

@Test
void loopElse_notEmpty() {
model.array = new int[]{1};
givenTemplate("@for (i in model.array)" +
"${i}" +
"@else" +
"Empty" +
"@endfor");
thenOutputIs("1");
}

@Test
void loopElseNested_innerEmpty() {
templateEngine.setTrimControlStructures(true);
model.array = new int[]{1};
model.doubleArray = new double[]{};
givenTemplate("""
@for (i in model.array)
@for (d in model.doubleArray)
${d}
@else
Inner empty
@endfor
@else
Outer empty
@endfor""");
thenOutputIs("Inner empty\n");
}

@Test
void loopElseNestedContentBlock_innerEmpty() {
templateEngine.setTrimControlStructures(true);
model.array = new int[]{1};
model.doubleArray = new double[]{};
givenTemplate("""
!{var content = @`@for (i in model.array)
@for (d in model.doubleArray)
${d}
@else
Inner empty
@endfor
@else
Outer empty
@endfor`;}
Result: ${content}""");
thenOutputIs("\nResult: Inner empty\n");
}

@Test
void loopElse_if() {
model.array = new int[]{};
givenTemplate("@for (i in model.array)" +
"@if(i > 0)${i}@else@endif" +
"@else" +
"Empty" +
"@endfor");
thenOutputIs("Empty");
}

@Test
void loopElseNested_innerEmptyThrowsException() {
templateEngine.setTrimControlStructures(true);
model.array = new int[]{1};
model.doubleArray = new double[]{};
givenTemplate("""
@for (i in model.array)
@for (d in model.doubleArray)
${d}
@else
Inner empty
${model.getThatThrows()}
@endfor
@else
Outer empty
@endfor""");

thenRenderingFailsWithException()
.hasCauseInstanceOf(NullPointerException.class)
.hasMessage("Failed to render test/template.kte, error at test/template.kte:7");
}

@Test
void unsafeInContentBlock() {
model.array = new int[]{1, 2, 3};
Expand Down
62 changes: 49 additions & 13 deletions jte/src/main/java/gg/jte/compiler/CodeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import gg.jte.runtime.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@SuppressWarnings("UnusedReturnValue")
public final class CodeBuilder {
Expand All @@ -13,10 +15,8 @@ public final class CodeBuilder {
private final StringBuilder code = new StringBuilder(1024);
private int currentCodeLine;
private int currentTemplateLine;
private int fieldsIndex;
private int fieldsCodeLine;
private int fieldsTemplateLine;
private int[] lineInfo = new int[INITIAL_CAPACITY];
private final List<CodeMarker> codeMarkers = new ArrayList<>();

public CodeBuilder(CodeType codeType) {
this.codeType = codeType;
Expand Down Expand Up @@ -86,11 +86,39 @@ public CodeBuilder finishTemplateLine() {
return this;
}

public CodeBuilder insertFieldLines(int count) {
fillLines(fieldsCodeLine, fieldsTemplateLine, count);
public CodeBuilder insert(CodeMarker position, CharSequence codeToInsert) {
return insert(position, codeToInsert, true);
}

public CodeBuilder insert(CodeMarker position, CharSequence codeToInsert, boolean fillLines) {
code.insert(position.codeIndex, codeToInsert);

int insertedLineCount = 0;
for (int i = 0; i < codeToInsert.length(); ++i) {
if (codeToInsert.charAt(i) == '\n') {
++insertedLineCount;
}
}

if (fillLines) {
fillLines(position, insertedLineCount);
}

// Adjust any created markers that point to code after this marker.
for (CodeMarker codeMarker : codeMarkers) {
if (codeMarker.codeIndex > position.codeIndex) {
codeMarker.codeIndex += codeToInsert.length();
codeMarker.codeLine += insertedLineCount;
}
}

return this;
}

public void fillLines(CodeMarker position, int insertedLineCount) {
fillLines(position.codeLine, position.templateLine, insertedLineCount);
}

public int getCurrentTemplateLine() {
return currentTemplateLine;
}
Expand All @@ -99,14 +127,10 @@ public String getCode() {
return code.toString();
}

public void markFieldsIndex() {
fieldsIndex = code.length();
fieldsCodeLine = currentCodeLine;
fieldsTemplateLine = currentTemplateLine;
}

public void insertFields(StringBuilder fields) {
code.insert(fieldsIndex, fields);
public CodeMarker getMarkerOfCurrentPosition() {
CodeMarker codeMarker = new CodeMarker(code.length(), currentCodeLine, currentTemplateLine);
codeMarkers.add(codeMarker);
return codeMarker;
}

private void addLine(int templateLine) {
Expand Down Expand Up @@ -152,4 +176,16 @@ public int getLineInfo(int index) {
public void setCurrentTemplateLine(int templateLine) {
currentTemplateLine = templateLine;
}

public static class CodeMarker {
private int codeIndex;
private int codeLine;
private final int templateLine;

private CodeMarker(int codeIndex, int codeLine, int templateLine) {
this.codeIndex = codeIndex;
this.codeLine = codeLine;
this.templateLine = templateLine;
}
}
}
6 changes: 5 additions & 1 deletion jte/src/main/java/gg/jte/compiler/TemplateParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ private void doParse(int startingDepth) {

pop();

visitor.onConditionElse(depth);
if (currentMode == Mode.ForLoop) {
visitor.onForLoopElse(depth);
} else {
visitor.onConditionElse(depth);
}
push(Mode.Text);
} else if (currentMode == Mode.Text && regionMatches("@elseif")) {
extractTextPart(i - 6, Mode.ConditionElse);
Expand Down
2 changes: 2 additions & 0 deletions jte/src/main/java/gg/jte/compiler/TemplateParserVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public interface TemplateParserVisitor {

void onForLoopStart(int depth, String codePart);

void onForLoopElse(int depth);

void onForLoopEnd(int depth);

default void onRawStart(int depth) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public void onForLoopStart(int depth, String codePart) {

}

@Override
public void onForLoopElse(int depth) {

}

@Override
public void onForLoopEnd(int depth) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public void onForLoopStart(int depth, String codePart) {
incrementAmount();
}

@Override
public void onForLoopElse(int depth) {
incrementAmount();
}

@Override
public void onForLoopEnd(int depth) {
incrementAmount();
Expand Down
Loading

0 comments on commit 6d40ab8

Please sign in to comment.