Enjoy 3.2 release ^_^
This commit is contained in:
114
src/main/java/com/jfinal/template/stat/CharTable.java
Normal file
114
src/main/java/com/jfinal/template/stat/CharTable.java
Normal 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';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
118
src/main/java/com/jfinal/template/stat/Ctrl.java
Normal file
118
src/main/java/com/jfinal/template/stat/Ctrl.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
526
src/main/java/com/jfinal/template/stat/Lexer.java
Normal file
526
src/main/java/com/jfinal/template/stat/Lexer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
61
src/main/java/com/jfinal/template/stat/Location.java
Normal file
61
src/main/java/com/jfinal/template/stat/Location.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
52
src/main/java/com/jfinal/template/stat/ParaToken.java
Normal file
52
src/main/java/com/jfinal/template/stat/ParaToken.java
Normal 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 {
|
||||
|
||||
// 接管父类的 value,content 可能为 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("]");
|
||||
}
|
||||
}
|
||||
|
34
src/main/java/com/jfinal/template/stat/ParseException.java
Normal file
34
src/main/java/com/jfinal/template/stat/ParseException.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
258
src/main/java/com/jfinal/template/stat/Parser.java
Normal file
258
src/main/java/com/jfinal/template/stat/Parser.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
235
src/main/java/com/jfinal/template/stat/Scope.java
Normal file
235
src/main/java/com/jfinal/template/stat/Scope.java
Normal 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
|
||||
* 2:scope.set(...) 自内向外查找赋值
|
||||
* 3:scope.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 ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
106
src/main/java/com/jfinal/template/stat/Symbol.java
Normal file
106
src/main/java/com/jfinal/template/stat/Symbol.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
91
src/main/java/com/jfinal/template/stat/TextToken.java
Normal file
91
src/main/java/com/jfinal/template/stat/TextToken.java
Normal 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("]");
|
||||
}
|
||||
}
|
||||
|
||||
|
72
src/main/java/com/jfinal/template/stat/Token.java
Normal file
72
src/main/java/com/jfinal/template/stat/Token.java
Normal 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("]");
|
||||
}
|
||||
}
|
||||
|
||||
|
40
src/main/java/com/jfinal/template/stat/ast/Break.java
Normal file
40
src/main/java/com/jfinal/template/stat/ast/Break.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
57
src/main/java/com/jfinal/template/stat/ast/Call.java
Normal file
57
src/main/java/com/jfinal/template/stat/ast/Call.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
src/main/java/com/jfinal/template/stat/ast/Continue.java
Normal file
40
src/main/java/com/jfinal/template/stat/ast/Continue.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
141
src/main/java/com/jfinal/template/stat/ast/Define.java
Normal file
141
src/main/java/com/jfinal/template/stat/ast/Define.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
40
src/main/java/com/jfinal/template/stat/ast/Else.java
Normal file
40
src/main/java/com/jfinal/template/stat/ast/Else.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
63
src/main/java/com/jfinal/template/stat/ast/ElseIf.java
Normal file
63
src/main/java/com/jfinal/template/stat/ast/ElseIf.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
140
src/main/java/com/jfinal/template/stat/ast/For.java
Normal file
140
src/main/java/com/jfinal/template/stat/ast/For.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
46
src/main/java/com/jfinal/template/stat/ast/ForEntry.java
Normal file
46
src/main/java/com/jfinal/template/stat/ast/ForEntry.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
61
src/main/java/com/jfinal/template/stat/ast/If.java
Normal file
61
src/main/java/com/jfinal/template/stat/ast/If.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
161
src/main/java/com/jfinal/template/stat/ast/Include.java
Normal file
161
src/main/java/com/jfinal/template/stat/ast/Include.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
63
src/main/java/com/jfinal/template/stat/ast/Output.java
Normal file
63
src/main/java/com/jfinal/template/stat/ast/Output.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
44
src/main/java/com/jfinal/template/stat/ast/Return.java
Normal file
44
src/main/java/com/jfinal/template/stat/ast/Return.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
59
src/main/java/com/jfinal/template/stat/ast/Set.java
Normal file
59
src/main/java/com/jfinal/template/stat/ast/Set.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
65
src/main/java/com/jfinal/template/stat/ast/SetGlobal.java
Normal file
65
src/main/java/com/jfinal/template/stat/ast/SetGlobal.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
66
src/main/java/com/jfinal/template/stat/ast/SetLocal.java
Normal file
66
src/main/java/com/jfinal/template/stat/ast/SetLocal.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
77
src/main/java/com/jfinal/template/stat/ast/Stat.java
Normal file
77
src/main/java/com/jfinal/template/stat/ast/Stat.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
64
src/main/java/com/jfinal/template/stat/ast/StatList.java
Normal file
64
src/main/java/com/jfinal/template/stat/ast/StatList.java
Normal 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];
|
||||
}
|
||||
}
|
||||
|
||||
|
59
src/main/java/com/jfinal/template/stat/ast/Text.java
Normal file
59
src/main/java/com/jfinal/template/stat/ast/Text.java
Normal 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) : "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user