Enjoy 3.2 release ^_^

This commit is contained in:
James
2017-07-31 22:34:15 +08:00
parent 46a7c60813
commit b82af8e219
107 changed files with 12029 additions and 192 deletions

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2011-2017, 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;
/**
* CharTable 空间换时间优化字符判断性能
* 负值参数强转 char 会自动变正值,无需判断负值数组下标
* isLetter(EOF) 不会下标越界
*/
public class CharTable {
private static final char[] letterChars = buildLetterChars();
private static final char[] letterOrDigitChars = buildLetterOrDigitChars();
private static final char[] exprChars = buildExprChars();
private static final char NULL = 0;
private static final char SIZE = 128;
private CharTable(){}
private static char[] createCharArray() {
char[] ret = new char[SIZE];
for (char i=0; i<SIZE; i++) {
ret[i] = NULL;
}
return ret;
}
private static char[] buildLetterChars() {
char[] ret = createCharArray();
for (char i='a'; i<='z'; i++) {
ret[i] = i;
}
for (char i='A'; i<='Z'; i++) {
ret[i] = i;
}
ret['_'] = '_'; // 包含下划线字符 '_'
return ret;
}
private static char[] buildLetterOrDigitChars() {
char[] ret = buildLetterChars();
for (char i='0'; i<='9'; i++) {
ret[i] = i;
}
return ret;
}
private static char[] buildExprChars() {
char[] ret = createCharArray();
ret['\t'] = '\t';
ret['\n'] = '\n';
ret['\r'] = '\r';
for (char i=' '; i<='}'; i++) {
ret[i] = i;
}
ret['#'] = NULL;
ret['$'] = NULL;
ret['@'] = NULL;
ret['\\'] = NULL;
ret['^'] = NULL;
ret['`'] = NULL;
return ret;
}
public static boolean isLetter(char c) {
return c < SIZE && letterChars[c] != NULL;
}
public static boolean isLetterOrDigit(char c) {
return c < SIZE && letterOrDigitChars[c] != NULL;
}
public static boolean isExprChar(char c) {
return c < SIZE && exprChars[c] != NULL;
}
public static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
public static boolean isBlank(char c) {
return c == ' ' || c == '\t'; // \t\r\u000C
}
public static boolean isBlankOrLineFeed(char c) {
return c == ' ' || c == '\t' || c == '\r' || c == '\n'; // \t\r\n\u000C
}
public static boolean isHexadecimalDigit(char c) {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
public static boolean isOctalDigit(char c) {
return c >= '0' && c <= '7';
}
}

View File

@@ -0,0 +1,118 @@
/**
* Copyright (c) 2011-2017, 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;
/**
* Ctrl
*
* 封装 AST 执行过程中的控制状态,避免使用 Scope.data 保存控制状态
* 从而污染用户空间数据,目前仅用于 nullSafe、break、continue、return 控制
* 未来可根据需求引入更多控制状态
*/
public class Ctrl {
private static final int JUMP_NONE = 0;
private static final int JUMP_BREAK = 1;
private static final int JUMP_CONTINUE = 2;
private static final int JUMP_RETURN = 3;
private static final int WISDOM_ASSIGNMENT = 0;
private static final int LOCAL_ASSIGNMENT = 1;
private static final int GLOBAL_ASSIGNMENT = 2;
private int jump = JUMP_NONE;
private int assignmentType = WISDOM_ASSIGNMENT;
private boolean nullSafe = false;
public boolean isJump() {
return jump != JUMP_NONE;
}
public boolean notJump() {
return jump == JUMP_NONE;
}
public boolean isBreak() {
return jump == JUMP_BREAK;
}
public void setBreak() {
jump = JUMP_BREAK;
}
public boolean isContinue() {
return jump == JUMP_CONTINUE;
}
public void setContinue() {
jump = JUMP_CONTINUE;
}
public boolean isReturn() {
return jump == JUMP_RETURN;
}
public void setReturn() {
jump = JUMP_RETURN;
}
public void setJumpNone() {
jump = JUMP_NONE;
}
public boolean isWisdomAssignment() {
return assignmentType == WISDOM_ASSIGNMENT;
}
public void setWisdomAssignment() {
assignmentType = WISDOM_ASSIGNMENT;
}
public boolean isLocalAssignment() {
return assignmentType == LOCAL_ASSIGNMENT;
}
public void setLocalAssignment() {
assignmentType = LOCAL_ASSIGNMENT;
}
public boolean isGlobalAssignment() {
return assignmentType == GLOBAL_ASSIGNMENT;
}
public void setGlobalAssignment() {
assignmentType = GLOBAL_ASSIGNMENT;
}
public boolean isNullSafe() {
return nullSafe;
}
public boolean notNullSafe() {
return !nullSafe;
}
public void setNullSafe(boolean nullSafe) {
this.nullSafe = nullSafe;
}
}

View File

@@ -0,0 +1,526 @@
/**
* Copyright (c) 2011-2017, 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;
import java.util.ArrayList;
import java.util.List;
/**
* DKFF(Dynamic Key Feature Forward) Lexer
*/
class Lexer {
static final char EOF = (char)-1;
static final int TEXT_STATE_DIAGRAM = 999;
char[] buf;
int state = 0;
int lexemeBegin = 0;
int forward = 0;
int beginRow = 1;
int forwardRow = 1;
TextToken previousTextToken = null;
List<Token> tokens = new ArrayList<Token>();
String fileName;
public Lexer(StringBuilder content, String fileName) {
int len = content.length();
buf = new char[len + 1];
content.getChars(0, content.length(), buf, 0);
buf[len] = EOF;
this.fileName = fileName;
}
/**
* 进入每个扫描方法之前 peek() 处于可用状态,不需要 next()
* 每个扫描方法内部是否要 next() 移动,取决定具体情况
* 每个扫描方法成功返回前,将 forward 置于下一次扫描需要处理的地方
* 让下个扫描方法不必 next()
* 紧靠 scanText() 之前的扫描方法在失败后必须保持住forward
* 这是 scanText() 可以一直向前的保障
*/
public List<Token> scan() {
while (peek() != EOF) {
if (peek() == '#') {
if (scanDire()) {
continue ;
}
if (scanSingleLineComment()) {
continue ;
}
if (scanMultiLineComment()) {
continue ;
}
if (scanNoParse()) {
continue ;
}
}
scanText();
}
return tokens;
}
/**
* 指令模式与解析规则
* 1指令 pattern
* #(p)
* #id(p)
* #define id(p)
* #@id(p) / #@id?(p)
* #else / #end
*
* 2关键字类型指令在获取到关键字以后必须要正确解析出后续内容否则抛异常
*
* 3非关键字类型指令只有在本行内出现 # id ( 三个序列以后,才要求正确解析出后续内容
* 否则当成普通文本
*/
boolean scanDire() {
String id = null;
StringBuilder para = null;
Token idToken = null;
Token paraToken = null;
while(true) {
switch (state) {
case 0:
if (peek() == '#') { // #
next();
skipBlanks();
state = 1;
continue ;
}
return fail();
case 1:
if (peek() == '(') { // # (
para = scanPara("");
idToken = new Token(Symbol.OUTPUT, beginRow);
paraToken = new ParaToken(para, beginRow);
return addOutputToken(idToken, paraToken);
}
if (CharTable.isLetter(peek())) { // # id
state = 10;
continue ;
}
if (peek() == '@') { // # @
next();
skipBlanks();
if (CharTable.isLetter(peek())) { // # @ id
state = 20;
continue ;
}
}
return fail();
// -----------------------------------------------------
case 10: // # id
id = scanId();
Symbol symbol = Symbol.getKeywordSym(id);
// 非关键字指令
if (symbol == null) {
state = 11;
continue ;
}
// define 指令
if (symbol == Symbol.DEFINE) {
state = 12;
continue ;
}
// 无参关键字指令
if (symbol.noPara()) {
return addNoParaToken(new Token(symbol, id, beginRow));
}
// 有参关键字指令
skipBlanks();
if (peek() == '(') {
para = scanPara(id);
idToken = new Token(symbol, beginRow);
paraToken = new ParaToken(para, beginRow);
return addIdParaToken(idToken, paraToken);
}
throw new ParseException("#" + id + " directive requires parentheses \"()\"", new Location(fileName, beginRow));
case 11: // 用户自定义指令必须有参数
skipBlanks();
if (peek() == '(') {
para = scanPara(id);
idToken = new Token(Symbol.ID, id, beginRow);
paraToken = new ParaToken(para, beginRow);
return addIdParaToken(idToken, paraToken);
}
return fail(); // 用户自定义指令在没有左括号的情况下当作普通文本
case 12: // 处理 "# define id (para)" 指令
skipBlanks();
if (CharTable.isLetter(peek())) {
id = scanId(); // 模板函数名称
skipBlanks();
if (peek() == '(') {
para = scanPara("define " + id);
idToken = new Token(Symbol.DEFINE, id, beginRow);
paraToken = new ParaToken(para, beginRow);
return addIdParaToken(idToken, paraToken);
}
throw new ParseException("#define " + id + " : template function definition requires parentheses \"()\"", new Location(fileName, beginRow));
}
throw new ParseException("#define directive requires identifier as a function name", new Location(fileName, beginRow));
case 20: // # @ id
id = scanId();
skipBlanks();
boolean hasQuestionMark = peek() == '?';
if (hasQuestionMark) {
next();
skipBlanks();
}
if (peek() == '(') {
para = scanPara(hasQuestionMark ? "@" + id + "?" : "@" + id);
idToken = new Token(hasQuestionMark ? Symbol.CALL_IF_DEFINED : Symbol.CALL, id, beginRow);
paraToken = new ParaToken(para, beginRow);
return addIdParaToken(idToken, paraToken);
}
return fail();
default :
return fail();
}
}
}
/**
* 调用者已确定以字母或下划线开头,故一定可以获取到 id值
*/
String scanId() {
int idStart = forward;
while (CharTable.isLetterOrDigit(next())) {
;
}
return subBuf(idStart, forward - 1).toString();
}
/**
* 扫描指令参数,成功则返回,否则抛出词法分析异常
*/
StringBuilder scanPara(String id) {
char quotes = '"';
int localState = 0;
int parenDepth = 1; // 指令后面参数的第一个 '(' 深度为 1
next();
int paraStart = forward;
while (true) {
switch (localState) {
case 0:
for (char c=peek(); true; c=next()) {
if (c == ')') {
parenDepth--;
if (parenDepth == 0) { // parenDepth 不可能小于0因为初始值为 1
next();
return subBuf(paraStart, forward - 2);
}
continue ;
}
if (c == '(') {
parenDepth++;
continue ;
}
if (c == '"' || c == '\'') {
quotes = c;
localState = 1;
break ;
}
if (CharTable.isExprChar(c)) {
continue ;
}
if (c == EOF) {
throw new ParseException("#" + id + " parameter can not match the end char ')'", new Location(fileName, beginRow));
}
throw new ParseException("#" + id + " parameter exists illegal char: '" + c + "'", new Location(fileName, beginRow));
}
break ;
case 1:
for (char c=next(); true; c=next()) {
if (c == quotes) {
if (buf[forward - 1] != '\\') { // 前一个字符不是转义字符
next();
localState = 0;
break ;
} else {
continue ;
}
}
if (c == EOF) {
throw new ParseException("#" + id + " parameter error, the string parameter not ending", new Location(fileName, beginRow));
}
}
break ;
}
}
}
/**
* 单行注释,开始状态 100关注换行与 EOF
*/
boolean scanSingleLineComment() {
while (true) {
switch (state) {
case 100:
if (peek() == '#' && next() == '#' && next() == '#') {
state = 101;
continue ;
}
return fail();
case 101:
for (char c=next(); true; c=next()) {
if (c == '\n') {
if (deletePreviousTextTokenBlankTails()) {
return prepareNextScan(1);
} else {
return prepareNextScan(0);
}
}
if (c == EOF) {
deletePreviousTextTokenBlankTails();
return prepareNextScan(0);
}
}
default :
return fail();
}
}
}
/**
* 多行注释,开始状态 200关注结尾标记与 EOF
*/
boolean scanMultiLineComment() {
while (true) {
switch (state) {
case 200:
if (peek() == '#' && next() == '-' && next() == '-') {
state = 201;
continue ;
}
return fail();
case 201:
for (char c=next(); true; c=next()) {
if (c == '-' && buf[forward + 1] == '-' && buf[forward + 2] == '#') {
forward = forward + 3;
if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) {
return prepareNextScan(peek() != EOF ? 1 : 0);
} else {
return prepareNextScan(0);
}
}
if (c == EOF) {
throw new ParseException("The multiline comment start block \"#--\" can not match the end block: \"--#\"", new Location(fileName, beginRow));
}
}
default :
return fail();
}
}
}
/**
* 非解析块,开始状态 300关注结尾标记与 EOF
*/
boolean scanNoParse() {
while (true) {
switch (state) {
case 300:
if (peek() == '#' && next() == '[' && next() == '[') {
state = 301;
continue ;
}
return fail();
case 301:
for (char c=next(); true; c=next()) {
if (c == ']' && buf[forward + 1] == ']' && buf[forward + 2] == '#') {
addTextToken(subBuf(lexemeBegin + 3, forward - 1)); // NoParse 块使用 TextToken
return prepareNextScan(3);
}
if (c == EOF) {
throw new ParseException("The \"no parse\" start block \"#[[\" can not match the end block: \"]]#\"", new Location(fileName, beginRow));
}
}
default :
return fail();
}
}
}
boolean scanText() {
for (char c=peek(); true; c=next()) {
if (c == '#' || c == EOF) {
addTextToken(subBuf(lexemeBegin, forward - 1));
return prepareNextScan(0);
}
}
}
boolean fail() {
if (state < 300) {
forward = lexemeBegin;
forwardRow = beginRow;
}
if (state < 100) {
state = 100;
} else if (state < 200) {
state = 200;
} else if (state < 300) {
state = 300;
} else {
state = TEXT_STATE_DIAGRAM;
}
return false;
}
char next() {
if (buf[forward] == '\n') {
forwardRow++;
}
return buf[++forward];
}
char peek() {
return buf[forward];
}
void skipBlanks() {
while(CharTable.isBlank(buf[forward])) {
next();
}
}
/**
* scanPara 与 scanNoParse 存在 start > end 的情况
*/
StringBuilder subBuf(int start, int end) {
if (start > end) {
return null;
}
StringBuilder ret = new StringBuilder(end - start + 1);
for (int i=start; i<=end; i++) {
ret.append(buf[i]);
}
return ret;
}
boolean prepareNextScan(int moveForward) {
for (int i=0; i<moveForward; i++) {
next();
}
state = 0;
lexemeBegin = forward;
beginRow = forwardRow;
return true;
}
void addTextToken(StringBuilder text) {
if (text == null || text.length() == 0) {
return ;
}
if (previousTextToken != null) {
previousTextToken.append(text);
} else {
previousTextToken = new TextToken(text, beginRow);
tokens.add(previousTextToken);
}
}
// 输出指令不对前后空白与换行进行任何处理,直接调用 tokens.add(...)
boolean addOutputToken(Token idToken, Token paraToken) {
tokens.add(idToken);
tokens.add(paraToken);
previousTextToken = null;
return prepareNextScan(0);
}
// 向前看后续是否跟随的是空白 + 换行或者是空白 + EOF是则表示当前指令后续没有其它有用内容
boolean lookForwardLineFeedAndEof() {
int forwardBak = forward;
int forwardRowBak = forwardRow;
for (char c=peek(); true; c=next()) {
if (CharTable.isBlank(c)) {
continue ;
}
if (c == '\n' || c == EOF) {
return true;
}
forward = forwardBak;
forwardRow = forwardRowBak;
return false;
}
}
/**
* 带参指令处于独立行时删除前后空白字符,并且再删除一个后续的换行符
* 处于独立行是指:向前看无有用内容,在前面情况成立的基础之上
* 再向后看如果也无可用内容,前一个条件成立才开执行后续动作
*
* 向前看时 forward 在移动,意味着正在删除空白字符(通过 lookForwardLineFeed()方法)
* 向后看时也会在碰到空白 + '\n' 时删空白字符 (通过 deletePreviousTextTokenBlankTails()方法)
*/
boolean addIdParaToken(Token idToken, Token paraToken) {
tokens.add(idToken);
tokens.add(paraToken);
// if (lookForwardLineFeed() && (deletePreviousTextTokenBlankTails() || lexemeBegin == 0)) {
if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) {
prepareNextScan(peek() != EOF ? 1 : 0);
} else {
prepareNextScan(0);
}
previousTextToken = null;
return true;
}
// 处理前后空白的逻辑与 addIdParaToken() 基本一样,仅仅多了一个对于紧随空白的 next() 操作
boolean addNoParaToken(Token noParaToken) {
tokens.add(noParaToken);
if (CharTable.isBlank(peek())) {
next(); // 无参指令之后紧随的一个空白字符仅为分隔符,不参与后续扫描
}
if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) {
prepareNextScan(peek() != EOF ? 1 : 0);
} else {
prepareNextScan(0);
}
previousTextToken = null;
return true;
}
/**
* 1当前指令前方仍然是指令 (previousTextToken 为 null),直接返回 true
* 2当前指令前方为 TextToken 时的处理逻辑与返回值完全依赖于 TextToken.deleteBlankTails()
*/
boolean deletePreviousTextTokenBlankTails() {
// return previousTextToken != null ? previousTextToken.deleteBlankTails() : false;
return previousTextToken == null || previousTextToken.deleteBlankTails();
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2011-2017, 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;
/**
* Location
* 生成异常发生的位置消息
*/
public class Location {
private String templateFile;
private int row;
private String msg;
public Location(String templateFile, int row) {
this.templateFile = templateFile;
this.row = row;
this.msg = null;
}
public String toString() {
if (msg == null) {
StringBuilder buf = new StringBuilder();
if (templateFile != null) {
buf.append("\nTemplate: \"").append(templateFile).append("\". Line: ").append(row);
} else {
buf.append("\nString template line: ").append(row);
}
msg = buf.toString();
}
return msg;
}
public String getTemplateFile() {
return templateFile;
}
public int getRow() {
return row;
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2011-2017, 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;
/**
* ParaToken
*/
public class ParaToken extends Token {
// 接管父类的 valuecontent 可能为 null
private StringBuilder content;
public ParaToken(StringBuilder content, int row) {
super(Symbol.PARA, row);
this.content = content;
}
public String value() {
return content.toString();
}
public StringBuilder getContent() {
return content;
}
public String toString() {
return content != null ? content.toString() : "null";
}
public void print() {
System.out.print("[");
System.out.print(row);
System.out.print(", PARA, ");
System.out.print(toString());
System.out.println("]");
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2011-2017, 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;
/**
* ParseException
* 词法、语法错误
*/
@SuppressWarnings("serial")
public class ParseException extends RuntimeException {
public ParseException(String msg, Location loc) {
super(loc != null ? msg + loc : msg);
}
public ParseException(String msg, Location loc, Throwable t) {
super(loc != null ? msg + loc : msg, t);
}
}

View File

@@ -0,0 +1,258 @@
/**
* Copyright (c) 2011-2017, 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;
import java.util.ArrayList;
import java.util.List;
import com.jfinal.template.Env;
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;
/**
* DLRD (Double Layer Recursive Descent) Parser
*/
public class Parser {
private static final Token EOF = new Token(Symbol.EOF, -1);
private int forward = 0;
private List<Token> tokenList;
private StringBuilder content;
private String fileName;
private Env env;
public Parser(Env env, StringBuilder content, String fileName) {
this.env = env;
this.content = content;
this.fileName = fileName;
}
private Token peek() {
return tokenList.get(forward);
}
private Token move() {
return tokenList.get(++forward);
}
private Token matchPara(Token name) {
Token current = peek();
if (current.symbol == Symbol.PARA) {
move();
return current;
}
throw new ParseException("Can not match the parameter of directive #" + name.value(), getLocation(name.row));
}
private void matchEnd(Token name) {
if (peek().symbol == Symbol.END) {
move();
return ;
}
throw new ParseException("Can not match the #end of directive #" + name.value(), getLocation(name.row));
}
public Stat parse() {
tokenList = new Lexer(content, fileName).scan();
tokenList.add(EOF);
Stat statList = statList();
if (peek() != EOF) {
throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row));
}
return statList;
}
private StatList statList() {
List<Stat> statList = new ArrayList<Stat>();
while (true) {
Stat stat = stat();
if (stat == null) {
break ;
}
if (stat instanceof Define) {
env.addFunction((Define)stat);
continue ;
}
// 过滤内容为空的 Text 节点,通常是处于两个指令之间的空白字符被移除以后的结果,详见 TextToken.deleteBlankTails()
if (stat instanceof Text && ((Text)stat).isEmpty()) {
continue ;
}
statList.add(stat);
}
return new StatList(statList);
}
private Stat stat() {
Token name = peek();
switch (name.symbol) {
case TEXT:
move();
return new Text(((TextToken)name).getContent()).setLocation(getLocation(name.row));
case OUTPUT:
move();
Token para = matchPara(name);
Location loc = getLocation(name.row);
return env.getEngineConfig().getOutputDirective(parseExprList(para), loc).setLocation(loc);
case INCLUDE:
move();
para = matchPara(name);
return new Include(env, parseExprList(para), fileName, getLocation(name.row));
case FOR:
move();
para = matchPara(name);
StatList statList = statList();
Stat _else = null;
if (peek().symbol == Symbol.ELSE) {
move();
StatList elseStats = statList();
_else = new Else(elseStats);
}
matchEnd(name);
return new For(parseForCtrl(para), statList, _else).setLocation(getLocation(name.row));
case IF:
move();
para = matchPara(name);
statList = statList();
Stat ret = new If(parseExprList(para), statList, getLocation(name.row));
Stat current = ret;
for (Token elseIfToken=peek(); elseIfToken.symbol == Symbol.ELSEIF; elseIfToken=peek()) {
move();
para = matchPara(elseIfToken);
statList = statList();
Stat elseIf = new ElseIf(parseExprList(para), statList, getLocation(elseIfToken.row));
current.setStat(elseIf);
current = elseIf;
}
if (peek().symbol == Symbol.ELSE) {
move();
statList = statList();
_else = new Else(statList);
current.setStat(_else);
}
matchEnd(name);
return ret;
case DEFINE:
String functionName = name.value();
move();
para = matchPara(name);
Stat stat = statList();
matchEnd(name);
return new Define(functionName, parseExprList(para), stat, getLocation(name.row));
case CALL:
functionName = name.value();
move();
para = matchPara(name);
return new Call(functionName, parseExprList(para), false).setLocation(getLocation(name.row));
case CALL_IF_DEFINED:
functionName = name.value();
move();
para = matchPara(name);
return new Call(functionName, parseExprList(para), true).setLocation(getLocation(name.row));
case SET:
move();
para = matchPara(name);
return new Set(parseExprList(para), getLocation(name.row));
case SET_LOCAL:
move();
para = matchPara(name);
return new SetLocal(parseExprList(para), getLocation(name.row));
case SET_GLOBAL:
move();
para = matchPara(name);
return new SetGlobal(parseExprList(para), getLocation(name.row));
case CONTINUE:
move();
return Continue.me;
case BREAK:
move();
return Break.me;
case RETURN:
move();
return Return.me;
case ID:
Stat dire = env.getEngineConfig().getDirective(name.value());
if (dire == null) {
throw new ParseException("Directive not found: #" + name.value(), getLocation(name.row));
}
ret = createDirective(dire, name).setLocation(getLocation(name.row));
move();
para = matchPara(name);
ret.setExprList(parseExprList(para));
if (dire.hasEnd()) {
statList = statList();
ret.setStat(statList);
matchEnd(name);
}
return ret;
case PARA:
case ELSEIF:
case ELSE:
case END:
case EOF:
return null;
default :
throw new ParseException("Syntax error: can not match the token: " + name.value(), getLocation(name.row));
}
}
private Location getLocation(int row) {
return new Location(fileName, row);
}
private Stat createDirective(Stat dire, Token name) {
try {
return dire.getClass().newInstance();
} catch (Exception e) {
throw new ParseException(e.getMessage(), getLocation(name.row), e);
}
}
private ExprList parseExprList(Token paraToken) {
return new ExprParser((ParaToken)paraToken, env.getEngineConfig(), fileName).parseExprList();
}
private ForCtrl parseForCtrl(Token paraToken) {
return new ExprParser((ParaToken)paraToken, env.getEngineConfig(), fileName).parseForCtrl();
}
}

View File

@@ -0,0 +1,235 @@
/**
* Copyright (c) 2011-2017, 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;
import java.util.HashMap;
import java.util.Map;
/**
* Scope
* 1顶层 scope.parent 为 null
* 2scope.set(...) 自内向外查找赋值
* 3scope.get(...) 自内向外查找获取
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class Scope {
private final Scope parent;
private final Ctrl ctrl;
private Map data;
private Map<String, Object> sharedObjectMap;
/**
* 构建顶层 Scope parent 为 null 是顶层 Scope 的标志
* @param data 用于在模板中使用的数据data 支持 null 值
* @param sharedObjectMap 共享对象
*/
public Scope(Map data, Map<String, Object> sharedObjectMap) {
this.parent = null;
this.ctrl = new Ctrl();
this.data = data;
this.sharedObjectMap = sharedObjectMap;
}
/**
* 构建 AST 执行过程中作用域栈
*/
public Scope(Scope parent) {
if (parent == null) {
throw new IllegalArgumentException("parent can not be null.");
}
this.parent = parent;
this.ctrl = parent.ctrl;
this.data = null;
this.sharedObjectMap = parent.sharedObjectMap;
}
public Ctrl getCtrl() {
return ctrl;
}
/**
* 设置变量
* 自内向外在作用域栈中查找变量,如果找到则改写变量值,否则将变量存放到顶层 Scope
*/
public void set(Object key, Object value) {
for (Scope cur=this; true; cur=cur.parent) {
// HashMap 允许有 null 值 value必须要做 containsKey 判断
if (cur.data != null && cur.data.containsKey(key)) {
cur.data.put(key, value);
return ;
}
if (cur.parent == null) {
if (cur.data == null) { // 支持顶层 data 为 null 值
cur.data = new HashMap();
}
cur.data.put(key, value);
return ;
}
}
}
/**
* 获取变量
* 自内向外在作用域栈中查找变量,返回最先找到的变量
*/
public Object get(Object key) {
for (Scope cur=this; cur!=null; cur=cur.parent) {
if (cur.data != null && cur.data.containsKey(key)) {
return cur.data.get(key);
}
}
// return null;
return sharedObjectMap != null ? sharedObjectMap.get(key) : null;
}
/**
* 移除变量
* 自内向外在作用域栈中查找变量,移除最先找到的变量
*/
public void remove(Object key) {
for (Scope cur=this; cur!=null; cur=cur.parent) {
if (cur.data != null && cur.data.containsKey(key)) {
cur.data.remove(key);
return ;
}
}
}
/**
* 设置局部变量
*/
public void setLocal(Object key, Object value) {
if (data == null) {
data = new HashMap();
}
data.put(key, value);
}
/**
* 获取局部变量
*/
public Object getLocal(Object key) {
return data != null ? data.get(key) : null;
}
/**
* 移除局部变量
*/
public void removeLocal(Object key) {
if (data != null) {
data.remove(key);
}
}
/**
* 设置全局变量
* 全局作用域是指本次请求的整个 template
*/
public void setGlobal(Object key, Object value) {
for (Scope cur=this; true; cur=cur.parent) {
if (cur.parent == null) {
cur.data.put(key, value);
return ;
}
}
}
/**
* 获取全局变量
* 全局作用域是指本次请求的整个 template
*/
public Object getGlobal(Object key) {
for (Scope cur=this; true; cur=cur.parent) {
if (cur.parent == null) {
return cur.data.get(key);
}
}
}
/**
* 移除全局变量
* 全局作用域是指本次请求的整个 template
*/
public void removeGlobal(Object key) {
for (Scope cur=this; true; cur=cur.parent) {
if (cur.parent == null) {
cur.data.remove(key);
return ;
}
}
}
/**
* 自内向外在作用域栈中查找变量,获取变量所在的 Map主要用于 IncDec
*/
public Map getMapOfValue(Object key) {
for (Scope cur=this; cur!=null; cur=cur.parent) {
if (cur.data != null && cur.data.containsKey(key)) {
return cur.data;
}
}
return null;
}
/**
* 获取本层作用域 data可能为 null 值
*/
public Map getData() {
return data;
}
/**
* 设置/替换本层作用域 data通常用于在扩展指令中使用现成可用的 Map 来存放数据,
* 从而避免 Scope 内部创建 data节省时空
*
* 注意:本方法会替换掉已经存在的 data 对象
*/
public void setData(Map data) {
this.data = data;
}
/**
* 获取顶层作用域 data可能为 null 值
*/
public Map getRootData() {
for (Scope cur=this; true; cur=cur.parent) {
if (cur.parent == null) {
return cur.data;
}
}
}
/**
* 设置/替换顶层作用域 data可以在扩展指令之中通过此方法切换掉顶层作用域
* 实现作用域完全隔离的功能
*
* 注意:本方法会替换掉顶层已经存在的 data 对象
*/
public void setRootData(Map data) {
for (Scope cur=this; true; cur=cur.parent) {
if (cur.parent == null) {
cur.data = data;
return ;
}
}
}
}

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2011-2017, 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;
import java.util.HashMap;
import java.util.Map;
/**
* Symbol
*/
enum Symbol {
TEXT("text", false),
OUTPUT("output", true),
DEFINE("define", true),
CALL("call", true),
CALL_IF_DEFINED("callIfDefined", true),
SET("set", true),
SET_LOCAL("setLocal", true),
SET_GLOBAL("setGlobal", true),
INCLUDE("include", true),
FOR("for", true),
IF("if", true),
ELSEIF("elseif", true),
ELSE("else", false),
END("end", false),
CONTINUE("continue", false),
BREAK("break", false),
RETURN("return", false),
ID("ID", false), // 标识符:下划线或字母开头 ^[A-Za-z_][A-Za-z0-9_]*$
PARA("PARA", false),
EOF("EOF", false);
private final String name;
private final boolean hasPara; // 是否有参
/**
* Lexer 中确定为系统指令以后,必须得到正确的后续 Token 序列,否则报异常
* 扩展指令在得到 # id ( 序列以后才要求得到正确的后续 Token 序列,否则仅仅 return fail()
*/
@SuppressWarnings("serial")
private static final Map<String, Symbol> keywords = new HashMap<String, Symbol>() {{
put(Symbol.IF.getName(), IF);
put(Symbol.ELSEIF.getName(), ELSEIF);
put(Symbol.ELSE.getName(), ELSE);
put(Symbol.END.getName(), END);
put(Symbol.FOR.getName(), FOR);
put(Symbol.BREAK.getName(), BREAK);
put(Symbol.CONTINUE.getName(), CONTINUE);
put(Symbol.RETURN.getName(), RETURN);
put(Symbol.DEFINE.getName(), DEFINE);
put(Symbol.SET.getName(), SET);
put(Symbol.SET_LOCAL.getName(), SET_LOCAL);
put(Symbol.SET_GLOBAL.getName(), SET_GLOBAL);
put(Symbol.INCLUDE.getName(), INCLUDE);
}};
private Symbol(String name, boolean hasPara) {
this.name = name;
this.hasPara = hasPara;
}
public String getName() {
return name;
}
public String toString() {
return name;
}
boolean hasPara() {
return hasPara;
}
boolean noPara() {
return !hasPara;
}
public static Symbol getKeywordSym(String name) {
return keywords.get(name);
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2011-2017, 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;
/**
* TextToken
* 词法分析时,合并相邻 TextToken
*/
class TextToken extends Token {
// 接管父类的 value
private StringBuilder text;
public TextToken(StringBuilder value, int row) {
super(Symbol.TEXT, row);
this.text = value;
}
public void append(StringBuilder content) {
if (content != null) {
text.append(content); // 不要使用 toString(),性能不如直接这样快
}
}
/**
* 1当前指令"后方"全是空白字符并且以 '\n' 或 EOF 结尾,当前指令"前方"为 TextToken 时调用此方法
* 2当前指令本行内前方为空白字符(必须遭遇 '\n'),则删掉前方的空白字符
* 3当前指令前方全为空白字符(不含 '\n'),表明是两个指令之间全为空白字符的情况,
* 或者两指令不在同一行且第二个指令前方全是空白字符的情况,则删掉这两指令之间的全部空白字符
* 4返回 true告知调用方需要吃掉本指令行尾的 '\n'
*
* 简单描述:
* 1当前指令独占一行删除当前指令前方空白字符并告知调用方吃掉行尾 '\n'
* 2当前指令前方仍然是指令两指令之间有空白字符吃掉前方(即所有)的空白字符,并告知调用方吃掉行尾 '\n'
* 3情况 2 时,相当于本 TextToken 内容变成了空字符串,后续的 Parser 将过滤掉这类节点
*/
public boolean deleteBlankTails() {
for (int i = text.length() - 1; i >= 0; i--) {
if (CharTable.isBlank(text.charAt(i))) {
continue ;
}
if (text.charAt(i) == '\n') {
text.delete(i+1, text.length());
return true;
} else {
return false;
}
}
// 两个指令之间全是空白字符, 设置其长度为 0为 Parser 过滤内容为空的 Text 节点做准备
text.setLength(0);
return true; // 当两指令之间全为空白字符时,告知调用方需要吃掉行尾的 '\n'
}
public String value() {
return text.toString();
}
public StringBuilder getContent() {
return text;
}
public String toString() {
return text.toString();
}
public void print() {
System.out.print("[");
System.out.print(row);
System.out.print(", TEXT, ");
System.out.print(text.toString());
System.out.println("]");
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2011-2017, 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;
/**
* Token
*/
class Token {
final Symbol symbol;
final int row;
private final String value;
Token(Symbol symbol, String value, int row) {
if (symbol == null || value == null) {
throw new IllegalArgumentException("symbol and value can not be null");
}
this.symbol = symbol;
this.value = value;
this.row = row;
}
Token(Symbol symbol, int row) {
this(symbol, symbol.getName(), row);
}
boolean hasPara() {
return symbol.hasPara();
}
boolean noPara() {
return symbol.noPara();
}
public String value() {
return value;
}
public String toString() {
return value;
}
public int getRow() {
return row;
}
public void print() {
System.out.print("[");
System.out.print(row);
System.out.print(", ");
System.out.print(symbol.getName());
System.out.print(", ");
System.out.print(value());
System.out.println("]");
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.stat.Scope;
/**
* Break
* java 中 break、continue 可出现在 for 中的最后一行,不一定要套在 if 中
*/
public class Break extends Stat {
public static final Break me = new Break();
private Break() {
}
public void exec(Env env, Scope scope, Writer writer) {
scope.getCtrl().setBreak();
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.stat.Scope;
/**
* Call 调用模板函数,两种用法:
* 1常规调用
* #@funcName(p1, p2, ..., pn)
* 2安全调用函数被定义才调用否则跳过
* #@funcName?(p1, p2, ..., pn)
*
* 注意:在函数名前面引入 '@' 字符是为了区分模板函数和指令
*/
public class Call extends Stat {
private String funcName;
private ExprList exprList;
private boolean callIfDefined;
public Call(String funcName, ExprList exprList, boolean callIfDefined) {
this.funcName = funcName;
this.exprList = exprList;
this.callIfDefined = callIfDefined;
}
public void exec(Env env, Scope scope, Writer writer) {
Define function = env.getFunction(funcName);
if (function != null) {
function.call(env, scope, exprList, writer);
} else if (callIfDefined) {
return ;
} else {
throw new TemplateException("Template function not defined: " + funcName, location);
}
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.stat.Scope;
/**
* Continue
*/
public class Continue extends Stat {
public static final Continue me = new Continue();
private Continue() {
}
public void exec(Env env, Scope scope, Writer writer) {
scope.getCtrl().setContinue();
}
}

View File

@@ -0,0 +1,141 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Id;
/**
* Define 定义模板函数:
* #define funcName(p1, p2, ..., pn)
* body
* #end
*
* 模板函数类型:
* 1全局共享的模板函数
* 通过 engine.addSharedFunction(...) 添加,所有模板中可调用
* 2模板中定义的局部模板函数
* 在模板中定义的模板函数,只在本模板中有效
*
* 高级用法:
* 1局部模板函数可以与全局共享模板函数同名调用时优先调用模板内模板数
* 2模板内部不能定义同名的局部模板函数
*/
public class Define extends Stat {
private static final String[] NULL_PARAMETER_NAMES = new String[0];
private String functionName;
private String[] parameterNames;
private Stat stat;
public Define(String functionName, ExprList exprList, Stat stat, Location location) {
setLocation(location);
this.functionName = functionName;
this.stat = stat;
Expr[] exprArray = exprList.getExprArray();
if (exprArray.length == 0) {
this.parameterNames = NULL_PARAMETER_NAMES;
return ;
}
parameterNames = new String[exprArray.length];
for (int i=0; i<exprArray.length; i++) {
if (exprArray[i] instanceof Id) {
parameterNames[i] = ((Id)exprArray[i]).getId();
} else {
throw new ParseException("The parameter of template function definition must be identifier", location);
}
}
}
public String getFunctionName() {
return functionName;
}
public String[] getParameterNames() {
return parameterNames;
}
/**
* Define 的继承类可以覆盖此方法实现一些 register 类的动作
*/
public void exec(Env env, Scope scope, Writer writer) {
}
/**
* 真正调用模板函数
*/
public void call(Env env, Scope scope, ExprList exprList, Writer writer) {
if (exprList.length() != parameterNames.length) {
throw new TemplateException("Wrong number of argument to call the template function, right number is: " + parameterNames.length, location);
}
scope = new Scope(scope);
if (exprList.length() > 0) {
Object[] parameterValues = exprList.evalExprList(scope);
for (int i=0; i<parameterValues.length; i++) {
scope.setLocal(parameterNames[i], parameterValues[i]); // 参数赋值
}
}
stat.exec(env, scope, writer);
scope.getCtrl().setJumpNone(); // #define 中的 return、continue、break 全部在此消化
}
public String toString() {
StringBuilder ret = new StringBuilder();
ret.append("#define ").append(functionName).append("(");
for (int i=0; i<parameterNames.length; i++) {
if (i > 0) {
ret.append(", ");
}
ret.append(parameterNames[i]);
}
return ret.append(")").toString();
}
// -----------------------------------------------------------------------
/**
* envForDevMode 属性性以及相关方法仅用于 devMode 判断当前 #define 指令所在资源是否被修改
* 仅用于 EngineConfig 中处理 shared function 的逻辑
*/
private Env envForDevMode;
public void setEnvForDevMode(Env envForDevMode) {
this.envForDevMode = envForDevMode;
}
public boolean isSourceModifiedForDevMode() {
if (envForDevMode == null) {
throw new IllegalStateException("Check engine config: setDevMode(...) must be invoked before addSharedFunction(...)");
}
return envForDevMode.isSourceListModified();
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.stat.Scope;
/**
* Else
*/
public class Else extends Stat {
private Stat stat;
public Else(Stat stat) {
this.stat = stat;
}
public void exec(Env env, Scope scope, Writer writer) {
stat.exec(env, scope, writer);
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* ElseIf
*/
public class ElseIf extends Stat {
private ExprList cond;
private Stat stat;
private Stat elseIfOrElse;
public ElseIf(ExprList cond, Stat stat, Location location) {
if (cond.length() == 0) {
throw new ParseException("The condition expression of #elseif statement can not be blank", location);
}
this.cond = cond;
this.stat = stat;
}
/**
* take over setStat(...) method of super class
*/
public void setStat(Stat elseIfOrElse) {
this.elseIfOrElse = elseIfOrElse;
}
public void exec(Env env, Scope scope, Writer writer) {
if (Logic.isTrue(cond.eval(scope))) {
stat.exec(env, scope, writer);
} else if (elseIfOrElse != null) {
elseIfOrElse.exec(env, scope, writer);
}
}
}

View File

@@ -0,0 +1,140 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import java.util.Iterator;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ForCtrl;
import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Scope;
/**
* For 循环控制,支持 List、Map、数组、Collection、Iterator、Iterable
* Enumeration、null 以及任意单个对象的迭代,简单说是支持所有对象迭代
*
* 主要用法:
* 1#for(item : list) #(item) #end
* 2#for(item : list) #(item) #else content #end
* 3#for(i=0; i<9; i++) #(item) #end
* 4#for(i=0; i<9; i++) #(item) #else content #end
*/
public class For extends Stat {
private ForCtrl forCtrl;
private StatList statList;
private Stat _else;
public For(ForCtrl forCtrl, StatList statList, Stat _else) {
this.forCtrl = forCtrl;
this.statList = statList;
this._else = _else;
}
public void exec(Env env, Scope scope, Writer writer) {
scope = new Scope(scope);
if (forCtrl.isIterator()) {
forIterator(env, scope, writer);
} else {
forLoop(env, scope, writer);
}
}
/**
* #for( id : expr)
*/
private void forIterator(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl();
Object outer = scope.get("for");
ctrl.setLocalAssignment();
ForIteratorStatus forIteratorStatus = new ForIteratorStatus(outer, forCtrl.getExpr().eval(scope), location);
ctrl.setWisdomAssignment();
scope.setLocal("for", forIteratorStatus);
Iterator<?> it = forIteratorStatus.getIterator();
String itemName = forCtrl.getId();
while(it.hasNext()) {
scope.setLocal(itemName, it.next());
statList.exec(env, scope, writer);
forIteratorStatus.nextState();
if (ctrl.isJump()) {
if (ctrl.isBreak()) {
ctrl.setJumpNone();
break ;
} else if (ctrl.isContinue()) {
ctrl.setJumpNone();
continue ;
} else {
return ;
}
}
}
if (_else != null && forIteratorStatus.getIndex() == 0) {
_else.exec(env, scope, writer);
}
}
/**
* #for(exprList; cond; update)
*/
private void forLoop(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl();
Object outer = scope.get("for");
ForLoopStatus forLoopStatus = new ForLoopStatus(outer);
scope.setLocal("for", forLoopStatus);
Expr init = forCtrl.getInit();
Expr cond = forCtrl.getCond();
Expr update = forCtrl.getUpdate();
ctrl.setLocalAssignment();
for (init.eval(scope); cond == null || Logic.isTrue(cond.eval(scope)); update.eval(scope)) {
ctrl.setWisdomAssignment();
statList.exec(env, scope, writer);
ctrl.setLocalAssignment();
forLoopStatus.nextState();
if (ctrl.isJump()) {
if (ctrl.isBreak()) {
ctrl.setJumpNone();
break ;
} else if (ctrl.isContinue()) {
ctrl.setJumpNone();
continue ;
} else {
ctrl.setWisdomAssignment();
return ;
}
}
}
ctrl.setWisdomAssignment();
if (_else != null && forLoopStatus.getIndex() == 0) {
_else.exec(env, scope, writer);
}
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2011-2017, 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 java.util.Map.Entry;
/**
* ForEntry 包装 HashMap、LinkedHashMap 等 Map 类型的 Entry 对象
*/
public class ForEntry implements Entry<Object, Object> {
private Entry<Object, Object> entry;
public ForEntry(Entry<Object, Object> entry) {
this.entry = entry;
}
public Object getKey() {
return entry.getKey();
}
public Object getValue() {
return entry.getValue();
}
public Object setValue(Object value) {
return entry.setValue(value);
}
}

View File

@@ -0,0 +1,244 @@
/**
* Copyright (c) 2011-2017, 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 java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Location;
/**
* ForIteratorStatus
* 封装 #for( id : expr) 迭代语句状态,便于模板中获取
*
* 使用以下表达式可以模板中获取迭代状态:
* for.size 被迭代集合元素数量,不支持 Iterator 与 Iterable
* for.index 从 0 下始的下标
* for.count 从 1 开始的计数器
* for.first 是否第一个元素
* for.last 是否最后一个元素
* for.odd 是否第奇数个元素
* for.even 是否第偶数个元素
* for.outer 获取外层 for 对象,便于获取外层 for 循环状态
* 例如: for.outer.index
*/
public class ForIteratorStatus {
private Object outer;
private int index;
private int size;
private Iterator<?> iterator;
private Location location;
public ForIteratorStatus(Object outer, Object target, Location location) {
this.outer = outer;
this.index = 0;
this.location = location;
init(target);
}
@SuppressWarnings("unchecked")
private void init(Object target) {
if (target == null) {
size = 0;
iterator = NullIterator.me;
return ;
}
if (target instanceof Collection) {
size = ((Collection<?>)target).size();
iterator = ((Collection<?>)target).iterator();
return ;
}
if (target instanceof Map<?, ?>) {
size = ((Map<?, ?>)target).size();
iterator = new MapIterator(((Map<Object, Object>)target).entrySet().iterator());
return ;
}
if (target.getClass().isArray()) {
size = Array.getLength(target);
iterator = new ArrayIterator(target, size);
return ;
}
if (target instanceof Iterator) {
size = -1;
iterator = (Iterator<?>)target;
return ;
}
if (target instanceof Iterable) {
size = -1;
iterator = ((Iterable<?>)target).iterator();
return ;
}
if (target instanceof Enumeration) {
ArrayList<?> list = Collections.list((Enumeration<?>)target);
size = list.size();
iterator = list.iterator();
return ;
}
size = 1;
iterator = new SingleObjectIterator(target);
}
Iterator<?> getIterator() {
return iterator;
}
void nextState() {
index++;
}
public Object getOuter() {
return outer;
}
public int getIndex() {
return index;
}
public int getCount() {
return index + 1;
}
public int getSize() {
if (size >= 0) {
return size;
}
throw new TemplateException("No such method getSize() of the iterator", location);
}
public boolean getFirst() {
return index == 0;
}
public boolean getLast() {
return !iterator.hasNext();
}
public boolean getOdd() {
return index % 2 == 0;
}
public boolean getEven() {
return index % 2 != 0;
}
}
class MapIterator implements Iterator<Entry<Object, Object>> {
private Iterator<Entry<Object, Object>> iterator;
public MapIterator(Iterator<Entry<Object, Object>> iterator) {
this.iterator = iterator;
}
public boolean hasNext() {
return iterator.hasNext();
}
public Entry<Object, Object> next() {
return new ForEntry((Entry<Object, Object>)iterator.next());
}
public void remove() {
throw new UnsupportedOperationException();
}
}
class ArrayIterator implements Iterator<Object> {
private Object array;
private int size;
private int index;
ArrayIterator(Object array, int size) {
this.array = array;
this.size = size;
this.index = 0;
}
public boolean hasNext() {
return index < size;
}
public Object next() {
return Array.get(array, index++);
}
public void remove() {
throw new UnsupportedOperationException();
}
}
class SingleObjectIterator implements Iterator<Object> {
private Object target;
private boolean hasNext = true;
public SingleObjectIterator(Object target) {
this.target = target;
}
public boolean hasNext() {
return hasNext;
}
public Object next() {
if (hasNext) {
hasNext = false;
return target;
}
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
class NullIterator implements Iterator<Object> {
static final Iterator<?> me = new NullIterator();
private NullIterator() {
}
public boolean hasNext() {
return false;
}
public Object next() {
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2011-2017, 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;
/**
* ForLoopStatus
* 封装 #for( init; cond; update) 循环的状态,便于模板中获取
*
* 如下表达式可从模板中获取循环状态:
* for.index 从 0 下始的下标
* for.count 从 1 开始的计数器
* for.first 是否第一个元素
* for.odd 是否第奇数个元素
* for.even 是否第偶数个元素
* for.outer 获取外层 for 对象,便于获取外层 for 循环状态
* 例如: for.outer.index
*
* 注意比迭代型循环语句少支持两个状态取值表达式for.size、for.last
*/
public class ForLoopStatus {
private Object outer;
private int index;
public ForLoopStatus(Object outer) {
this.outer = outer;
this.index = 0;
}
void nextState() {
index++;
}
public Object getOuter() {
return outer;
}
public int getIndex() {
return index;
}
public int getCount() {
return index + 1;
}
public boolean getFirst() {
return index == 0;
}
public boolean getOdd() {
return index % 2 == 0;
}
public boolean getEven() {
return index % 2 != 0;
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* If
*/
public class If extends Stat {
private ExprList cond;
private Stat stat;
private Stat elseIfOrElse;
public If(ExprList cond, Stat stat, Location location) {
if (cond.length() == 0) {
throw new ParseException("The condition expression of #if statement can not be blank", location);
}
this.cond = cond;
this.stat = stat;
}
/**
* take over setStat(...) method of super class
*/
public void setStat(Stat elseIfOrElse) {
this.elseIfOrElse = elseIfOrElse;
}
public void exec(Env env, Scope scope, Writer writer) {
if (Logic.isTrue(cond.eval(scope))) {
stat.exec(env, scope, writer);
} else if (elseIfOrElse != null) {
elseIfOrElse.exec(env, scope, writer);
}
}
}

View File

@@ -0,0 +1,161 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.EngineConfig;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign;
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.source.ISource;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Parser;
import com.jfinal.template.stat.Scope;
/**
* Include
*
* 1父模板被缓存时被 include 的模板会被间接缓存,无需关心缓存问题
* 2同一个模板文件被多个父模板 include所处的背景环境不同例如各父模板中定义的模板函数不同
* 各父模板所处的相对路径不同,所以多个父模板不能共用一次 parse 出来的结果而是在每个被include
* 的地方重新 parse
*
* <pre>
* 两种用法:
* 1只传入一个参数参数必须是 String 常量,如果希望第一个参数是变量可以使用 #render 指令去实现
* #include("_hot.html")
*
* 2传入任意多个参数除第一个参数以外的所有参数必须是赋值表达式用于实现参数传递功能
* #include("_hot.html", title = "热门新闻", list = newsList)
*
* 上例中传递了 title、list 两个参数,可以代替父模板中的 #set 指令传参方式
* 并且此方式传入的参数只在子模板作用域有效,不会污染父模板作用域
*
* 这种传参方式有利于将子模板模块化,例如上例的调用改成如下的参数:
* #include("_hot.html", title = "热门项目", list = projectList)
* 通过这种传参方式在子模板 _hot.html 之中,完全不需要修改对于 title 与 list
* 这两个变量的处理代码,就实现了对 “热门项目” 数据的渲染
* </pre>
*/
public class Include extends Stat {
private Assign[] assignArray;
private Stat stat;
public Include(Env env, ExprList exprList, String parentFileName, Location location) {
int len = exprList.length();
if (len == 0) {
throw new ParseException("The parameter of #include directive can not be blank", location);
}
// 第一个参数必须为 String 类型
Expr expr = exprList.getExpr(0);
if (expr instanceof Const && ((Const)expr).isStr()) {
} else {
throw new ParseException("The first parameter of #include directive must be String", location);
}
// 其它参数必须为赋值表达式
if (len > 1) {
for (int i = 1; i < len; i++) {
if (!(exprList.getExpr(i) instanceof Assign)) {
throw new ParseException("The " + i + "th parameter of #include directive must be an assignment expression", location);
}
}
}
parseSubTemplate(env, ((Const)expr).getStr(), parentFileName, location);
getAssignExpression(exprList);
}
private void parseSubTemplate(Env env, String fileName, String parentFileName, Location location) {
String subFileName = getSubFileName(fileName, parentFileName);
EngineConfig config = env.getEngineConfig();
// FileSource fileSource = new FileSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
ISource fileSource = config.getSourceFactory().getSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
try {
Parser parser = new Parser(env, fileSource.getContent(), subFileName);
if (config.isDevMode()) {
env.addSource(fileSource);
}
this.stat = parser.parse();
} catch (Exception e) {
// 文件路径不正确抛出异常时添加 location 信息
throw new ParseException(e.getMessage(), location, e);
}
}
/**
* 获取在父模板之下子模板的最终文件名,子模板目录相对于父模板文件目录来确定
* 以 "/" 打头则以 baseTemplatePath 为根,否则以父文件所在路径为根
*/
public static String getSubFileName(String fileName, String parentFileName) {
if (parentFileName == null) {
return fileName;
}
if (fileName.startsWith("/")) {
return fileName;
}
int index = parentFileName.lastIndexOf('/');
if (index == -1) {
return fileName;
}
return parentFileName.substring(0, index + 1) + fileName;
}
private void getAssignExpression(ExprList exprList) {
int len = exprList.length();
if (len > 1) {
assignArray = new Assign[len - 1];
for (int i = 0; i < assignArray.length; i++) {
assignArray[i] = (Assign)exprList.getExpr(i + 1);
}
} else {
assignArray = null;
}
}
public void exec(Env env, Scope scope, Writer writer) {
scope = new Scope(scope);
if (assignArray != null) {
evalAssignExpression(scope);
}
stat.exec(env, scope, writer);
scope.getCtrl().setJumpNone();
}
private void evalAssignExpression(Scope scope) {
Ctrl ctrl = scope.getCtrl();
try {
ctrl.setLocalAssignment();
for (Assign assign : assignArray) {
assign.eval(scope);
}
} finally {
ctrl.setWisdomAssignment();
}
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* Output 输出指令
*
* 用法:
* 1#(value)
* 2#(x = 1, y = 2, x + y)
* 3#(seoTitle ?? 'JFinal 极速开发社区')
*/
public class Output extends Stat {
private ExprList exprList;
public Output(ExprList exprList, Location location) {
if (exprList.length() == 0) {
throw new ParseException("The expression of output directive like #(expression) can not be blank", location);
}
this.exprList = exprList;
}
public void exec(Env env, Scope scope, Writer writer) {
try {
Object value = exprList.eval(scope);
if (value != null) {
String str = value.toString();
writer.write(str, 0, str.length());
}
} catch(TemplateException e) {
throw e;
} catch(Exception e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.stat.Scope;
/**
* Return
* 通常用于 #define 指令内部,不支持返回值
*/
public class Return extends Stat {
public static final Return me = new Return();
private Return() {
}
public void exec(Env env, Scope scope, Writer writer) {
scope.getCtrl().setReturn();
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* Set 赋值,从内向外作用域查找变量,找到则替换变量值,否则在顶层作用域赋值
*
* 用法:
* 1#set(k = v)
* 2#set(k1 = v1, k2 = v2, ..., kn = vn)
* 3#set(x = 1+2)
* 4#set(x = 1+2, y = 3>4, ..., z = c ? a : b)
*/
public class Set extends Stat {
private ExprList exprList;
public Set(ExprList exprList, Location location) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #set directive can not be blank", location);
}
for (Expr expr : exprList.getExprArray()) {
if ( !(expr instanceof Assign) ) {
throw new ParseException("#set directive only supports assignment expressions", location);
}
}
this.exprList = exprList;
}
public void exec(Env env, Scope scope, Writer writer) {
scope.getCtrl().setWisdomAssignment();
exprList.eval(scope);
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* SetLocal 设置全局变量,全局作用域是指本次请求的整个 template
*
* 适用于极少数的在内层作用域中希望直接操作顶层作用域的场景
*/
public class SetGlobal extends Stat {
private ExprList exprList;
public SetGlobal(ExprList exprList, Location location) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #setGlobal directive can not be blank", location);
}
for (Expr expr : exprList.getExprArray()) {
if ( !(expr instanceof Assign) ) {
throw new ParseException("#setGlobal directive only supports assignment expressions", location);
}
}
this.exprList = exprList;
}
public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl();
try {
ctrl.setGlobalAssignment();
exprList.eval(scope);
} finally {
ctrl.setWisdomAssignment();
}
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* SetLocal 设置局部变量
*
* 通常用于 #define #include 指令内部需要与外层作用域区分,以便于定义重用型模块的场景
* 也常用于 #for 循环内部的临时变量
*/
public class SetLocal extends Stat {
final ExprList exprList;
public SetLocal(ExprList exprList, Location location) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #setLocal directive can not be blank", location);
}
for (Expr expr : exprList.getExprArray()) {
if ( !(expr instanceof Assign) ) {
throw new ParseException("#setLocal directive only supports assignment expressions", location);
}
}
this.exprList = exprList;
}
public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl();
try {
ctrl.setLocalAssignment();
exprList.eval(scope);
} finally {
ctrl.setWisdomAssignment();
}
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2011-2017, 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 java.io.IOException;
import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.Scope;
/**
* Stat
*/
public abstract class Stat {
protected Location location;
public Stat setLocation(Location location) {
this.location = location;
return this;
}
public Location getLocation() {
return location;
}
public void setExprList(ExprList exprList) {
}
public void setStat(Stat stat) {
}
public abstract void exec(Env env, Scope scope, Writer writer);
public boolean hasEnd() {
return false;
}
protected void write(Writer writer, String str) {
try {
writer.write(str, 0, str.length());
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
protected void write(Writer writer, char[] chars) {
try {
writer.write(chars, 0, chars.length);
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2011-2017, 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 java.io.Writer;
import java.util.List;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Scope;
/**
* StatList
*/
public class StatList extends Stat {
public static final Stat[] NULL_STATS = new Stat[0];
private Stat[] statArray;
public StatList(List<Stat> statList) {
if (statList.size() > 0) {
this.statArray = statList.toArray(new Stat[statList.size()]);
} else {
this.statArray = NULL_STATS;
}
}
public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl();
for (Stat stat : statArray) {
if (ctrl.isJump()) {
break ;
}
stat.exec(env, scope, writer);
}
}
public int length() {
return statArray.length;
}
public Stat getStat(int index) {
if (index < 0 || index >= statArray.length) {
throw new TemplateException("Index out of bounds: index = " + index + ", length = " + statArray.length, location);
}
return statArray[index];
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2011-2017, 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 java.io.IOException;
import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Scope;
/**
* Text 输出纯文本块以及使用 "#[[" 与 "]]#" 指定的非解析块
*/
public class Text extends Stat {
private char[] text;
public Text(StringBuilder content) {
this.text = new char[content.length()];
content.getChars(0, content.length(), this.text, 0);
}
public void exec(Env env, Scope scope, Writer writer) {
try {
writer.write(text, 0, text.length);
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
public boolean isEmpty() {
return text.length == 0;
}
public String getContent() {
return text != null ? new String(text) : null;
}
public String toString() {
return text != null ? new String(text) : "";
}
}