Skip to content

Commit 233a761

Browse files
authored
Merge pull request #16 from entityc/bobgarner/new-assert-instruction
Assert Instruction
2 parents 75f4648 + 616452d commit 233a761

File tree

7 files changed

+216
-37
lines changed

7 files changed

+216
-37
lines changed

src/main/antlr4/org/entityc/compiler/transform/template/TemplateGrammer.g4

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ identifier
4848
| Version
4949
| Capture
5050
| Log
51+
| Assert
5152
| Let
5253
| Do
5354
| Switch
@@ -83,6 +84,7 @@ instruction
8384
| versionTag
8485
| captureTag
8586
| logTag
87+
| assertTag
8688
| promptTag
8789
| letTag
8890
| doTag
@@ -103,7 +105,7 @@ block
103105
;
104106

105107
blockEnd
106-
: BlockEndTagStart (Function|Foreach|If|File|Capture|Switch|Log|Prompt|Send|Preserve|Author|Publisher|Outlet) BlockTagEnd
108+
: BlockEndTagStart (Function|Foreach|If|File|Capture|Switch|Log|Assert|Prompt|Send|Preserve|Author|Publisher|Outlet) BlockTagEnd
107109
;
108110

109111
descriptionTag
@@ -244,6 +246,10 @@ logTag
244246
: Log identifier?
245247
;
246248

249+
assertTag
250+
: Assert expression identifier
251+
;
252+
247253
promptTag
248254
: Prompt identifier (':' primitiveType)?
249255
;

src/main/antlr4/org/entityc/compiler/transform/template/TemplateLexer.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ Let : 'let' ;
9494
Do : 'do' ;
9595
Load : 'load' ;
9696
Log : 'log' ;
97+
Assert : 'assert' ;
9798
Prompt : 'prompt' ;
9899
Extends : 'extends' ;
99100
InstanceOf : 'instanceof' ;

src/main/java/org/entityc/compiler/EntityCompiler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252

5353
public class EntityCompiler {
5454

55-
public static final String COMPILER_VERSION = "0.14.0";
56-
public static final String LANGUAGE_VERSION = "0.12.5";
55+
public static final String COMPILER_VERSION = "0.15.0";
56+
public static final String LANGUAGE_VERSION = "0.12.6";
5757
private static final Map<String, String> defineValues = new HashMap<>();
5858
private static final Set<String> templateSearchPath = new HashSet<>();
5959
private static CommandLine commandLine;

src/main/java/org/entityc/compiler/transform/template/TemplateASTVisitor.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.entityc.compiler.repository.RepositoryImportManager;
2121
import org.entityc.compiler.transform.MTBaseTransform;
2222
import org.entityc.compiler.transform.TransformManager;
23+
import org.entityc.compiler.transform.template.tree.FTAssert;
2324
import org.entityc.compiler.transform.template.tree.FTAuthor;
2425
import org.entityc.compiler.transform.template.tree.FTBreak;
2526
import org.entityc.compiler.transform.template.tree.FTCall;
@@ -1045,6 +1046,18 @@ public FTLog visitLogTag(TemplateGrammer.LogTagContext ctx) {
10451046
return logBlock;
10461047
}
10471048

1049+
@Override
1050+
public FTAssert visitAssertTag(TemplateGrammer.AssertTagContext ctx) {
1051+
FTExpression condition = visitExpression(ctx.expression());
1052+
String levelName = ctx.identifier() != null ?
1053+
ECStringUtil.ProcessParserString(ctx.identifier().getText()) :
1054+
null;
1055+
FTAssert assertBlock = new FTAssert(ctx, currentContainer(ctx), condition, levelName);
1056+
currentContainer(ctx).addChild(assertBlock);
1057+
push(assertBlock);
1058+
return assertBlock;
1059+
}
1060+
10481061
@Override
10491062
public FTPrompt visitPromptTag(TemplateGrammer.PromptTagContext ctx) {
10501063
String variableName = ctx.identifier() != null ?
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright (c) 2019-2022 The EntityC Project. All rights reserved.
3+
* Use of this file is governed by the BSD 3-clause license that
4+
* can be found in the LICENSE.md file in the project root.
5+
*/
6+
7+
package org.entityc.compiler.transform.template.tree;
8+
9+
import org.antlr.v4.runtime.ParserRuleContext;
10+
import org.entityc.compiler.doc.annotation.TemplateInstruction;
11+
import org.entityc.compiler.doc.annotation.TemplateInstructionArgument;
12+
import org.entityc.compiler.doc.annotation.TemplateInstructionCategory;
13+
import org.entityc.compiler.transform.template.TemplateLexer;
14+
import org.entityc.compiler.transform.template.formatter.TemplateFormatController;
15+
import org.entityc.compiler.transform.template.tree.expression.FTExpression;
16+
import org.entityc.compiler.util.ECLog;
17+
18+
import static org.entityc.compiler.transform.template.formatter.ConfigurableElement.InstructionArgument;
19+
20+
@TemplateInstruction(category = TemplateInstructionCategory.FILE_IO,
21+
name = "assert",
22+
usage = "`assert `*condition* *level*",
23+
summary = "Tests the provided condition expression and if fails will print the provided message. "
24+
+ "If the *level* is not specified or is `fatal` the compiler will exit.",
25+
description = "This is similar to a `$[log fatal]` but with some advantages. Firstly, it is more compact "
26+
+ "since it basically includes a surrounding `if` block. Secondly, you have the ability to assert "
27+
+ "to a non-fatal level but still have that exit (such as when you are debugging). Thirdly, you can "
28+
+ "perform multiple asserts at an `error` level followed by a final `fatal` assert that will fire "
29+
+ "if any error` asserts occurred previously - thus allowing you to find and report possibly "
30+
+ "multiple issues before exiting.",
31+
contents = "If the assert condition is **not** met, the contents of this assert block is sent to the log as the level "
32+
+ "provided."
33+
)
34+
public class FTAssert extends FTContainerNode implements FTBodyBlock {
35+
36+
private final FTBody body = new FTBody();
37+
private FTExpression condition = null;
38+
private FTLog.Level level = FTLog.Level.FATAL;
39+
40+
public FTAssert(ParserRuleContext ctx, FTContainerNode parent,
41+
@TemplateInstructionArgument(optional = false,
42+
description = "The assert executes when this condition evaluates to **false**. The condition "
43+
+ "can involve one of the special assert global variables (e.g., `__assert_error`, "
44+
+ "`__assert_warning`, ...) to gather up many asserts into a fatal assert. For "
45+
+ "instance, `$[assert !__assert_error fatal]Cannot proceed.$[/assert]`")
46+
FTExpression condition,
47+
@TemplateInstructionArgument(optional = true,
48+
description = "The level can be used to classify the level of severity of the log output. "
49+
+ "The options are `info`, `debug`, `warning`, `error` and `fatal`. For the "
50+
+ "first three the only difference in the output is the prefix of each line "
51+
+ "which is the level name in all caps followed by `> `. For `error`, output "
52+
+ "is directed to system error. For `fatal`, output is also sent to system "
53+
+ "error but after the output, the compiler terminates, stopping any further "
54+
+ "execution of the template.")
55+
String level) {
56+
super(ctx, parent);
57+
this.condition = condition;
58+
if (level != null) {
59+
this.level = FTLog.Level.FromName(level);
60+
if (this.level == null) {
61+
ECLog.logFatal(ctx, "Unknown log level: " + level);
62+
}
63+
}
64+
}
65+
66+
@Override
67+
public FTBody getBody() {
68+
return body;
69+
}
70+
71+
public void runAssert(FTTransformSession session) {
72+
73+
String text = body.getText();
74+
75+
if (FTIf.IsConditionMet(condition, session)) {
76+
return; // do nothing
77+
}
78+
79+
switch (getLevel()) {
80+
case INFO:
81+
session.setValue("__assert_info", true);
82+
ECLog.logInfo(text);
83+
break;
84+
case DEBUG:
85+
session.setValue("__assert_debug", true);
86+
ECLog.logInfo(text);
87+
break;
88+
case WARNING:
89+
session.setValue("__assert_warning", true);
90+
ECLog.logWarning(text);
91+
break;
92+
case ERROR:
93+
session.setValue("__assert_error", true);
94+
ECLog.logError(text);
95+
break;
96+
case FATAL:
97+
session.setValue("__assert_fatal", true);
98+
ECLog.logFatal(text);
99+
break;
100+
}
101+
}
102+
103+
public FTLog.Level getLevel() {
104+
return level;
105+
}
106+
107+
@Override
108+
public void transform(FTTransformSession session) {
109+
session.pushAssertBlock(this);
110+
super.transformChildren(session, false);
111+
session.popAssertBlock();
112+
}
113+
114+
@Override
115+
public int getTemplateLexerSymbol() {
116+
return TemplateLexer.Assert;
117+
}
118+
119+
@Override
120+
public boolean format(TemplateFormatController formatController, int indentLevel) {
121+
boolean success = true;
122+
123+
formatController.addInstructionStart(indentLevel, this);
124+
if (!condition.format(formatController, indentLevel)) {
125+
success = false;
126+
}
127+
formatController.addInstructionInside(InstructionArgument, level.name, this.getStartLineNumber());
128+
formatController.addInstructionEnd(this);
129+
super.formatChildren(formatController, indentLevel);
130+
formatController.addInstructionBlockEnd(indentLevel, this);
131+
return success;
132+
}
133+
134+
@Override
135+
public boolean hasOwnBody() {
136+
return true;
137+
}
138+
139+
}

src/main/java/org/entityc/compiler/transform/template/tree/FTIf.java

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,29 @@
1717
import org.entityc.compiler.transform.template.tree.expression.FTExpression;
1818

1919
@TemplateInstruction(category = TemplateInstructionCategory.CONTROL_FLOW,
20-
name = "if",
21-
usage = "`if `*expression*",
22-
summary = "Allows you to conditionally execute template code.",
23-
description = "This instruction evaluates the provided instruction and if it resolves to `true` it will "
24-
+ "execute its template code. If it is `false` and is followed by an `elseif`, execution will "
25-
+ "move to that instruction. If `false` and is instead followed by an `else` statement, the "
26-
+ "code contained in the `else` instruction is executed.",
27-
contents = "This instruction represents the top of a possible `if`...`elseif`...`else` structure, where the "
28-
+ "`elseif` and `else` are optional. Between this instruction and either the next `elseif`, `else` "
29-
+ "or this instructions terminator is template code.",
30-
seeAlso = {"elseif", "else"})
20+
name = "if",
21+
usage = "`if `*expression*",
22+
summary = "Allows you to conditionally execute template code.",
23+
description = "This instruction evaluates the provided instruction and if it resolves to `true` it will "
24+
+ "execute its template code. If it is `false` and is followed by an `elseif`, execution will "
25+
+ "move to that instruction. If `false` and is instead followed by an `else` statement, the "
26+
+ "code contained in the `else` instruction is executed.",
27+
contents = "This instruction represents the top of a possible `if`...`elseif`...`else` structure, where the "
28+
+ "`elseif` and `else` are optional. Between this instruction and either the next `elseif`, `else` "
29+
+ "or this instructions terminator is template code.",
30+
seeAlso = {"elseif", "else"})
3131
public class FTIf extends FTContainerNode {
3232

3333
private final FTExpression condition;
3434
private boolean elseif = false;
3535

3636
public FTIf(ParserRuleContext ctx, FTContainerNode parent,
3737
@TemplateInstructionArgument(
38-
description = "The expression that is evaluated to determine whether to execute the template code "
39-
+ "of this instruction."
38+
description =
39+
"The expression that is evaluated to determine whether to execute the template code "
40+
+ "of this instruction."
4041
)
41-
FTExpression expression) {
42+
FTExpression expression) {
4243
super(ctx, parent);
4344
if (ctx instanceof TemplateGrammer.ElseifTagContext) {
4445
elseif = true;
@@ -52,42 +53,40 @@ public FTExpression getCondition() {
5253

5354
@Override
5455
public void transform(FTTransformSession session) {
56+
if (IsConditionMet(condition, session)) {
57+
super.transformChildren(session, true);
58+
} else if (getChildren().size() > 0) {
59+
// look for else and pickup there
60+
FTNode lastNode = getChildren().get(getChildren().size() - 1);
61+
if (lastNode instanceof FTElseIf || lastNode instanceof FTElse) {
62+
lastNode.transform(session);
63+
}
64+
}
65+
}
66+
67+
public static boolean IsConditionMet(FTExpression condition, FTTransformSession session) {
5568
boolean conditionMet = false;
5669
Object value = condition.getValue(session);
5770
if (value != null) {
5871
if (value instanceof Boolean) {
5972
if (((Boolean) value).booleanValue()) {
6073
conditionMet = true;
6174
}
62-
}
63-
else if (value instanceof String) {
75+
} else if (value instanceof String) {
6476
if ((((String) value).length() > 0)) {
6577
conditionMet = true;
6678
}
67-
}
68-
else if ((value instanceof MTEnum)) {
79+
} else if ((value instanceof MTEnum)) {
6980
conditionMet = true;
70-
}
71-
else if (value instanceof Integer) {
81+
} else if (value instanceof Integer) {
7282
conditionMet = (((Integer) value) != 0);
73-
}
74-
else if (value instanceof Long) {
83+
} else if (value instanceof Long) {
7584
conditionMet = (((Long) value) != 0);
7685
}
77-
}
78-
else {
86+
} else {
7987
conditionMet = false;
8088
}
81-
if (conditionMet) {
82-
super.transformChildren(session, true);
83-
}
84-
else if (getChildren().size() > 0) {
85-
// look for else and pickup there
86-
FTNode lastNode = getChildren().get(getChildren().size() - 1);
87-
if (lastNode instanceof FTElseIf || lastNode instanceof FTElse) {
88-
lastNode.transform(session);
89-
}
90-
}
89+
return conditionMet;
9190
}
9291

9392
@Override

src/main/java/org/entityc/compiler/transform/template/tree/FTTransformSession.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ public FTTransformSession(MTRoot root, MTConfiguration configuration, FTTemplate
7474
if (template.getDefaultDomainName() != null) {
7575
addReadonlyNamedValue("domain", space.getDomainWithName(template.getDefaultDomainName()));
7676
}
77+
setValue("__assert_info", false);
78+
setValue("__assert_debug", false);
79+
setValue("__assert_warning", false);
80+
setValue("__assert_error", false);
81+
setValue("__assert_fatal", false);
7782
} else {
7883
stringOutputBuffer = new StringBuffer();
7984
}
@@ -225,6 +230,22 @@ public void popLogBlock() {
225230
((FTLog) logBlock).log();
226231
}
227232

233+
public void pushAssertBlock(FTAssert assertBlock) {
234+
assertBlock.getBody().clear();
235+
bodyBlockStack.push(assertBlock);
236+
}
237+
238+
public void popAssertBlock() {
239+
if (bodyBlockStack.empty()) {
240+
ECLog.logFatal("ERROR: An end of a block set was specified with no set block start.");
241+
}
242+
FTBodyBlock assertBlock = bodyBlockStack.pop();
243+
if (!(assertBlock instanceof FTAssert)) {
244+
ECLog.logFatal("Unbalanced body blocks.");
245+
}
246+
((FTAssert) assertBlock).runAssert(this);
247+
}
248+
228249
public void pushPromptBlock(FTPrompt promptBlock) {
229250
promptBlock.getBody().clear();
230251
bodyBlockStack.push(promptBlock);

0 commit comments

Comments
 (0)