enjoy 3.6 release ^_^

This commit is contained in:
James 2019-01-30 21:09:07 +08:00
parent 972c7e7673
commit 4c63d00157
24 changed files with 456 additions and 85 deletions

1
.gitignore vendored
View File

@ -51,3 +51,4 @@ a_little_config_pro.txt
dev_plan.txt

View File

@ -192,3 +192,5 @@ third-party archives.

View File

@ -1,9 +1,9 @@
### Enjoy
Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K 并且不依赖任何第三方。极简设计仅 if、for、set、define、include、render 六个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。
Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K 并且不依赖任何第三方。极简设计仅 if、for、switch、set、define、include、render 七个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。
#### Enjoy 主要特点
- 消灭传统模板引擎中大量繁杂概念,仅个核心指令,学习成本极低
- 消灭传统模板引擎中大量繁杂概念,仅个核心指令,学习成本极低
- 独创 DKFF 词法分析算法与 DLRD 语法分析算法,避免使用 javacc、antlr
- 功能强大,极为简单覆盖掉 freemarker、velocity 的核心功能
- 扩展性强,支持多种扩展方式,且是唯一支持指令级扩展的模板引擎

View File

@ -73,8 +73,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
@ -121,7 +121,7 @@
</execution>
</executions>
<configuration>
<skip>true</skip>
<skip>false</skip>
</configuration>
</plugin>

View File

@ -113,6 +113,20 @@ public class Kv extends HashMap {
return this;
}
public Kv setIfNotBlank(Object key, String value) {
if (StrKit.notBlank(value)) {
set(key, value);
}
return this;
}
public Kv setIfNotNull(Object key, Object value) {
if (value != null) {
set(key, value);
}
return this;
}
public Kv set(Map map) {
super.putAll(map);
return this;

View File

@ -24,7 +24,7 @@ public class ReflectKit {
public static Object newInstance(Class<?> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}

View File

@ -520,8 +520,8 @@ public class Engine {
* Engine.addFieldGetter(1, new IsMethodFieldGetter());
*
* IsMethodFieldGetter 系统已经提供只是默认没有启用该实现类通过调用
* target.isXxx() 方法获取 target.xxx 表达式的值其中 xxx 字段必须是
* Boolean/boolean 类型
* target.isXxx() 方法获取 target.xxx 表达式的值其中 isXxx() 返回值
* 必须是 Boolean/boolean 类型才会被调用
*/
public static void addFieldGetter(int index, FieldGetter fieldGetter) {
FieldKit.addFieldGetter(index, fieldGetter);

View File

@ -73,9 +73,9 @@ public class EngineConfig {
addDirective("string", StringDirective.class);
addDirective("random", RandomDirective.class);
addDirective("number", NumberDirective.class);
addDirective("call", CallDirective.class);
// Add official shared method of Template Engine
// addSharedMethod(new Json());
addSharedMethod(new SharedMethodLib());
}
@ -83,6 +83,7 @@ public class EngineConfig {
* Add shared function with file
*/
public void addSharedFunction(String fileName) {
fileName = fileName.replace("\\", "/");
// FileSource fileSource = new FileSource(baseTemplatePath, fileName, encoding);
ISource source = sourceFactory.getSource(baseTemplatePath, fileName, encoding);
doAddSharedFunction(source, fileName);
@ -261,8 +262,9 @@ public class EngineConfig {
throw new IllegalArgumentException("baseTemplatePath can not be blank");
}
baseTemplatePath = baseTemplatePath.trim();
baseTemplatePath = baseTemplatePath.replace("\\", "/");
if (baseTemplatePath.length() > 1) {
if (baseTemplatePath.endsWith("/") || baseTemplatePath.endsWith("\\")) {
if (baseTemplatePath.endsWith("/")) {
baseTemplatePath = baseTemplatePath.substring(0, baseTemplatePath.length() - 1);
}
}

View File

@ -21,28 +21,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import com.jfinal.template.EngineConfig;
import com.jfinal.template.expr.Sym;
import com.jfinal.template.expr.ast.Arith;
import com.jfinal.template.expr.ast.Array;
import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Compare;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Field;
import com.jfinal.template.expr.ast.ForCtrl;
import com.jfinal.template.expr.ast.Id;
import com.jfinal.template.expr.ast.IncDec;
import com.jfinal.template.expr.ast.Index;
import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.expr.ast.Map;
import com.jfinal.template.expr.ast.Method;
import com.jfinal.template.expr.ast.NullSafe;
import com.jfinal.template.expr.ast.RangeArray;
import com.jfinal.template.expr.ast.SharedMethod;
import com.jfinal.template.expr.ast.StaticField;
import com.jfinal.template.expr.ast.StaticMethod;
import com.jfinal.template.expr.ast.Ternary;
import com.jfinal.template.expr.ast.Unary;
import com.jfinal.template.expr.ast.*;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParaToken;
import com.jfinal.template.stat.ParseException;

View File

@ -73,9 +73,7 @@ public class Field extends Expr {
if (fieldGetter.notNull()) {
return fieldGetter.get(target, fieldName);
}
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);

View File

@ -69,9 +69,9 @@ public class FieldGetters {
java.lang.reflect.Method[] methodArray = targetClass.getMethods();
for (java.lang.reflect.Method method : methodArray) {
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0) {
if (MethodKit.isForbiddenMethod(getterName)) {
throw new RuntimeException("Forbidden method: " + getterName);
}
// if (MethodKit.isForbiddenMethod(getterName)) {
// throw new RuntimeException("Forbidden method: " + getterName);
// }
return new GetterMethodFieldGetter(method);
}

View File

@ -88,9 +88,7 @@ public class Method extends Expr {
}
throw new TemplateException(buildMethodNotFoundSignature("public method not found: " + target.getClass().getName() + ".", methodName, argValues), location);
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch (TemplateException | ParseException e) {
throw e;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();

View File

@ -63,9 +63,7 @@ public class SharedMethod extends Expr {
throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location);
}
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);

View File

@ -72,9 +72,7 @@ public class StaticMethod extends Expr {
throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location);
}
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);

View File

@ -0,0 +1,109 @@
/**
* Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
*
* 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.jfinal.template.ext.directive;
import java.util.ArrayList;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
import com.jfinal.template.stat.ast.Define;
/**
* CallDirective 动态调用模板函数
*
* 模板函数的名称与参数都可以动态指定提升模板函数调用的灵活性
*
* 例如
* #call(funcName, p1, p2, ..., pn)
* 其中 funcName为函数名p1p2pn 为被调用函数所使用的参数
*
*
* 如果希望模板函数不存在时忽略其调用添加常量值 true 在第一个参数位置即可
* 例如
* #call(true, funcName, p1, p2, ..., pn)
*
*
* TODO 后续优化看一下 ast.Call.java
*/
public class CallDirective extends Directive {
protected Expr funcNameExpr;
protected ExprList paraExpr;
protected boolean nullSafe = false; // 是否支持函数名不存在时跳过
public void setExprList(ExprList exprList) {
int len = exprList.length();
if (len == 0) {
throw new ParseException("模板函数名不能缺失", location);
}
int index = 0;
Expr expr = exprList.getExpr(index);
if (expr instanceof Const && ((Const)expr).isBoolean()) {
if (len == 1) {
throw new ParseException("模板函数名不能缺失", location);
}
nullSafe = ((Const)expr).getBoolean();
index++;
}
funcNameExpr = exprList.getExpr(index++);
ArrayList<Expr> list = new ArrayList<Expr>();
for (int i=index; i<len; i++) {
list.add(exprList.getExpr(i));
}
paraExpr = new ExprList(list);
}
public void exec(Env env, Scope scope, Writer writer) {
Object funcNameValue = funcNameExpr.eval(scope);
if (funcNameValue == null) {
if (nullSafe) {
return ;
}
throw new TemplateException("模板函数名为 null", location);
}
if (!(funcNameValue instanceof String)) {
throw new TemplateException("模板函数名必须是字符串", location);
}
Define func = env.getFunction(funcNameValue.toString());
if (func == null) {
if (nullSafe) {
return ;
}
throw new TemplateException("模板函数未找到 : " + funcNameValue, location);
}
func.call(env, scope, paraExpr, writer);
}
}

View File

@ -46,16 +46,8 @@ public class ByteWriter extends Writer {
}
public void close() {
try {
if (out != null) {
out.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
out = null;
}
}
public void write(String str, int offset, int len) throws IOException {
while (len > chars.length) {

View File

@ -40,16 +40,8 @@ public class CharWriter extends Writer {
}
public void close() {
try {
if (out != null) {
out.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
out = null;
}
}
public void write(String str, int offset, int len) throws IOException {
while (len > chars.length) {

View File

@ -24,22 +24,7 @@ import com.jfinal.template.expr.ExprParser;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.ForCtrl;
import com.jfinal.template.stat.Symbol;
import com.jfinal.template.stat.ast.Break;
import com.jfinal.template.stat.ast.Call;
import com.jfinal.template.stat.ast.Continue;
import com.jfinal.template.stat.ast.Define;
import com.jfinal.template.stat.ast.Else;
import com.jfinal.template.stat.ast.ElseIf;
import com.jfinal.template.stat.ast.For;
import com.jfinal.template.stat.ast.If;
import com.jfinal.template.stat.ast.Include;
import com.jfinal.template.stat.ast.Return;
import com.jfinal.template.stat.ast.Set;
import com.jfinal.template.stat.ast.SetGlobal;
import com.jfinal.template.stat.ast.SetLocal;
import com.jfinal.template.stat.ast.Stat;
import com.jfinal.template.stat.ast.StatList;
import com.jfinal.template.stat.ast.Text;
import com.jfinal.template.stat.ast.*;
/**
* DLRD (Double Layer Recursive Descent) Parser
@ -227,7 +212,41 @@ public class Parser {
case ELSE:
case END:
case EOF:
case CASE:
case DEFAULT:
return null;
case SWITCH:
move();
para = matchPara(name);
Switch _switch = new Switch(parseExprList(para), getLocation(name.row));
CaseSetter currentCaseSetter = _switch;
for (Token currentToken=peek(); ; currentToken=peek()) {
if (currentToken.symbol == Symbol.CASE) {
move();
para = matchPara(currentToken);
statList = statList();
Case nextCase = new Case(parseExprList(para), statList, getLocation(currentToken.row));
currentCaseSetter.setNextCase(nextCase);
currentCaseSetter = nextCase;
} else if (currentToken.symbol == Symbol.DEFAULT) {
move();
statList = statList();
Default _default = new Default(statList);
_switch.setDefault(_default, getLocation(currentToken.row));
} else if (currentToken.symbol == Symbol.TEXT) {
TextToken tt = (TextToken)currentToken;
if (tt.getContent().toString().trim().length() != 0) {
throw new ParseException("Syntax error: expect #case or #default directive", getLocation(currentToken.row));
}
move();
} else {
break ;
}
}
matchEnd(name);
return _switch;
default :
throw new ParseException("Syntax error: can not match the token: " + name.value(), getLocation(name.row));
}

View File

@ -45,6 +45,10 @@ enum Symbol {
BREAK("break", false),
RETURN("return", false),
SWITCH("switch", true),
CASE("case", true),
DEFAULT("default", false),
ID("ID", false), // 标识符下划线或字母开头 ^[A-Za-z_][A-Za-z0-9_]*$
PARA("PARA", false),
@ -68,6 +72,10 @@ enum Symbol {
put(Symbol.CONTINUE.getName(), CONTINUE);
put(Symbol.RETURN.getName(), RETURN);
put(Symbol.SWITCH.getName(), SWITCH);
put(Symbol.CASE.getName(), CASE);
put(Symbol.DEFAULT.getName(), DEFAULT);
put(Symbol.DEFINE.getName(), DEFINE);
put(Symbol.SET.getName(), SET);
put(Symbol.SET_LOCAL.getName(), SET_LOCAL);

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
*
* 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.jfinal.template.stat.ast;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* Case
*/
public class Case extends Stat implements CaseSetter {
private Expr[] exprArray;
private Stat stat;
private Case nextCase;
public Case(ExprList exprList, StatList statList, Location location) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #case directive can not be blank", location);
}
this.exprArray = exprList.getExprArray();
this.stat = statList.getActualStat();
}
public void setNextCase(Case nextCase) {
this.nextCase = nextCase;
}
public void exec(Env env, Scope scope, Writer writer) {
throw new TemplateException("#case 指令的 exec 不能被调用", location);
}
boolean execIfMatch(Object switchValue, Env env, Scope scope, Writer writer) {
if (exprArray.length == 1) {
Object value = exprArray[0].eval(scope);
// 照顾 null == null 以及数值比较小的整型数据比较
if (value == switchValue) {
stat.exec(env, scope, writer);
return true;
}
if (value != null && value.equals(switchValue)) {
stat.exec(env, scope, writer);
return true;
}
} else {
for (Expr expr : exprArray) {
Object value = expr.eval(scope);
// 照顾 null == null 以及数值比较小的整型数据比较
if (value == switchValue) {
stat.exec(env, scope, writer);
return true;
}
if (value != null && value.equals(switchValue)) {
stat.exec(env, scope, writer);
return true;
}
}
}
return nextCase != null ? nextCase.execIfMatch(switchValue, env, scope, writer) : false;
}
}

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
*
* 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.jfinal.template.stat.ast;
/**
* CaseSetter
*/
public interface CaseSetter {
public void setNextCase(Case nextCase);
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
*
* 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.jfinal.template.stat.ast;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
* Default
*
* #switch 指令内部的 #default 指令
*/
public class Default extends Stat {
private Stat stat;
public Default(StatList statList) {
this.stat = statList.getActualStat();
}
public void exec(Env env, Scope scope, Writer writer) {
stat.exec(env, scope, writer);
}
}

View File

@ -71,9 +71,7 @@ public class Output extends Stat {
} else if (value != null) {
writer.write(value.toString());
}
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch(TemplateException | ParseException e) {
throw e;
} catch(Exception e) {
throw new TemplateException(e.getMessage(), location, e);

View File

@ -0,0 +1,111 @@
/**
* Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
*
* 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.jfinal.template.stat.ast;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* Switch
*
* #switch 指令与 Java 12 switch 新特性的设计相似: http://openjdk.java.net/jeps/325
*
* 在与 java 老版本指令基本用法相同的基础上主要变化与特性有
* 1: 移除 java 语法中的 fall-through semantics即不需要 break 关键字进行断开
* 2: 不引入 #break 指令代码更少更优雅
* 3: #case 参数可使用多个用逗号分隔的表达式每个表达式求值后与 #switch 参数求值后比较
* 从根本上消除了 #break 指令的必要性
* 4: #case 支持任意类型数据与表达式java 语言只支持少数常量类型
*
* <pre>
* 示例
* #switch (month)
* #case (1, 3, 5, 7, 8, 10, 12)
* #(month) 月有 31
* #case (2)
* #(month) 月平年有28天闰年有29天
* #default
* 月份错误: #(month ?? "null")
* #end
*
* 如上代码所示如果 #case 指令参数有多个值那么可以用逗号分隔
* 上述逗号表达式的值 1, 3, 5, 7, 8, 10, 12 之中只要有一个与
* switch 指令参数 month 相等的话 case 分支就会被执行
* 该特性从根本上消灭了 #break 指令的必要性
*
*
* 除了常量值以外 #case 参数还可以是任意表达式
* 例如
* #case (a, b, c, x + y, obj.method(z))
*
* 上述代码中 #case 参数中的所有表达式先会被求值然后逐一与 #switch
* 参数进行对比同样也是只要有一个对比相等则该 case 分支就会被执行
*
* </pre>
*/
public class Switch extends Stat implements CaseSetter {
private Expr expr;
private Case nextCase;
private Default _default;
public Switch(ExprList exprList, Location location) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #switch directive can not be blank", location);
}
this.expr = exprList.getActualExpr();
}
public void setNextCase(Case nextCase) {
this.nextCase = nextCase;
}
public void setDefault(Default _default, Location location) {
if (this._default != null) {
throw new ParseException("The #default case of #switch is already defined", location);
}
this._default = _default;
}
public void exec(Env env, Scope scope, Writer writer) {
Object switchValue = expr.eval(scope);
if (nextCase != null && nextCase.execIfMatch(switchValue, env, scope, writer)) {
return ;
}
if (_default != null) {
_default.exec(env, scope, writer);
}
}
public boolean hasEnd() {
return true;
}
}