com.jfinal.template.expr.ExprLexer Maven / Gradle / Ivy
The newest version!
/**
* Copyright (c) 2011-2023, James Zhan 詹波 ([email protected]).
*
* 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.expr;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import com.jfinal.kit.JavaKeyword;
import com.jfinal.template.stat.CharTable;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParaToken;
import com.jfinal.template.stat.ParseException;
/**
* ExprLexer
*/
class ExprLexer {
static final char EOF = (char)-1;
static final JavaKeyword javaKeyword = new JavaKeyword();
static final Pattern DOUBLE_QUOTES_PATTERN = Pattern.compile("\\\\\"");
static final Pattern SINGLE_QUOTES_PATTERN = Pattern.compile("\\\\'");
char[] buf;
int state = 0;
int lexemeBegin = 0;
int forward = 0;
int beginRow = 1;
int forwardRow = 1;
List tokens = new ArrayList();
Location location;
public ExprLexer(ParaToken paraToken, Location location) {
this.location = location;
StringBuilder content = paraToken.getContent();
beginRow = paraToken.getRow();
forwardRow = beginRow;
if (content == null) {
buf = new char[]{EOF};
return ;
}
int len = content.length();
buf = new char[len + 1];
content.getChars(0, content.length(), buf, 0);
buf[len] = EOF;
}
public List scan() {
while (peek() != EOF) {
skipBlanks();
lexemeBegin = forward;
beginRow = forwardRow;
if (scanId()) {
continue ;
}
if (scanOperator()) {
continue ;
}
if (scanString()) {
continue ;
}
if (scanNumber()) {
continue ;
}
if (peek() != EOF) {
throw new ParseException("Expression not support the char: '" + peek() + "'", location);
}
}
return tokens;
}
/**
* 扫描 ID true false null
*/
boolean scanId() {
if (state != 0) {
return false;
}
if (!CharTable.isLetter(peek())) {
return fail();
}
while (CharTable.isLetterOrDigit(next())) {
;
}
String id = subBuf(lexemeBegin, forward - 1).toString();
if ("true".equals(id)) {
addToken(new Tok(Sym.TRUE, id, beginRow));
} else if ("false".equals(id)) {
addToken(new Tok(Sym.FALSE, id, beginRow));
} else if ("null".equals(id)) {
addToken(new Tok(Sym.NULL, id, beginRow));
} else if (CharTable.isBlankOrLineFeed(peek()) && javaKeyword.contains(id)) {
throw new ParseException("Identifier can not be java keyword : " + id, location);
} else {
addToken(new Tok(Sym.ID, id, beginRow));
}
return prepareNextScan();
}
/**
* + - * / % ++ --
* = == != < <= > >=
* ! && ||
* ? ?? ?.
* . .. : :: , ;
* ( ) [ ] { }
*/
boolean scanOperator() {
if (state != 100) {
return false;
}
Tok tok;
switch (peek()) {
case '+': // + - * / % ++ --
if (next() == '+') {
tok = new Tok(Sym.INC, beginRow);
next();
} else {
tok = new Tok(Sym.ADD, beginRow);
}
return ok(tok);
case '-':
if (next() == '-') {
tok = new Tok(Sym.DEC, beginRow);
next();
} else {
tok = new Tok(Sym.SUB, beginRow);
}
return ok(tok);
case '*':
tok = new Tok(Sym.MUL, beginRow);
next();
return ok(tok);
case '/':
tok = new Tok(Sym.DIV, beginRow);
next();
return ok(tok);
case '%':
tok = new Tok(Sym.MOD, beginRow);
next();
return ok(tok);
case '=': // = == != < <= > >=
if (next() == '=') {
tok = new Tok(Sym.EQUAL, beginRow);
next();
} else {
tok = new Tok(Sym.ASSIGN, beginRow);
}
return ok(tok);
case '!':
if (next() == '=') {
tok = new Tok(Sym.NOTEQUAL, beginRow);
next();
} else {
tok = new Tok(Sym.NOT, beginRow);
}
return ok(tok);
case '<':
if (next() == '=') {
tok = new Tok(Sym.LE, beginRow);
next();
} else {
tok = new Tok(Sym.LT, beginRow);
}
return ok(tok);
case '>':
if (next() == '=') {
tok = new Tok(Sym.GE, beginRow);
next();
} else {
tok = new Tok(Sym.GT, beginRow);
}
return ok(tok);
case '&': // ! && ||
if (next() == '&') {
tok = new Tok(Sym.AND, beginRow);
next();
} else {
throw new ParseException("Unsupported operator: '&'", location);
}
return ok(tok);
case '|':
if (next() == '|') {
tok = new Tok(Sym.OR, beginRow);
next();
} else {
throw new ParseException("Unsupported operator: '|'", location);
}
return ok(tok);
case '?': // ? ?? ?.
char c = next();
if (c == '?') {
tok = new Tok(Sym.NULL_SAFE, beginRow);
next();
} else if (c == '.') {
tok = new Tok(Sym.OPTIONAL_CHAIN, beginRow);
next();
} else {
tok = new Tok(Sym.QUESTION, beginRow);
}
return ok(tok);
case '.': // . .. : :: , ;
if (next() == '.') {
tok = new Tok(Sym.RANGE, beginRow);
next();
} else {
tok = new Tok(Sym.DOT, ".", beginRow);
}
return ok(tok);
case ':':
if (next() == ':') {
tok = new Tok(Sym.STATIC, beginRow);
next();
} else {
tok = new Tok(Sym.COLON, beginRow);
}
return ok(tok);
case ',':
tok = new Tok(Sym.COMMA, beginRow);
next();
return ok(tok);
case ';':
tok = new Tok(Sym.SEMICOLON, beginRow);
next();
return ok(tok);
case '(': // ( ) [ ] { }
tok = new Tok(Sym.LPAREN, beginRow);
next();
return ok(tok);
case ')':
tok = new Tok(Sym.RPAREN, beginRow);
next();
return ok(tok);
case '[':
tok = new Tok(Sym.LBRACK, beginRow);
next();
return ok(tok);
case ']':
tok = new Tok(Sym.RBRACK, beginRow);
next();
return ok(tok);
case '{':
tok = new Tok(Sym.LBRACE, beginRow);
next();
return ok(tok);
case '}':
tok = new Tok(Sym.RBRACE, beginRow);
next();
return ok(tok);
default :
return fail();
}
}
boolean ok(Tok tok) {
tokens.add(tok);
return prepareNextScan();
}
boolean scanString() {
if (state != 200) {
return false;
}
char quotes = peek();
if (quotes != '"' && quotes != '\'') {
return fail();
}
for (char c=next(); true; c=next()) {
if (c == quotes) {
if (buf[forward - 1] != '\\') { // 前一个字符不是转义字符
StringBuilder sb = subBuf(lexemeBegin + 1, forward -1);
String str;
if (sb != null) {
if (quotes == '"') {
str = DOUBLE_QUOTES_PATTERN.matcher(sb).replaceAll("\"");
} else {
str = SINGLE_QUOTES_PATTERN.matcher(sb).replaceAll("'");
}
} else {
str = "";
}
Tok tok = new Tok(Sym.STR, str, beginRow);
addToken(tok);
next();
return prepareNextScan();
} else {
continue ;
}
}
if (c == EOF) {
throw new ParseException("Expression error, the string not ending", location);
}
}
}
boolean scanNumber() {
if (state != 300) {
return false;
}
char c = peek();
if (!CharTable.isDigit(c)) {
return fail();
}
int numStart = lexemeBegin; // forward;
int radix = 10; // 10 进制
if (c == '0') {
c = next();
if (c == 'X' || c == 'x') {
radix = 16; // 16 进制
c = next();
numStart = numStart + 2;
} else if (c != '.') {
radix = 8; // 8 进制
// numStart = numStart + 1; // 8 进制不用去掉前缀 0,可被正确转换,去除此行便于正确处理数字 0
}
}
c = skipDigit(radix);
Sym sym = null;
if (c == '.') { // 以 '.' 字符结尾是合法的浮点数
next();
if (peek() == '.' || // 处理 [0..9] 这样的表达式
CharTable.isLetter(peek())) { // 处理 123.toInt() 这样的表达式,1.2.toInt() 及 1D.toInt() 可正常处理
StringBuilder n = subBuf(numStart, forward - 2);
if (n == null /* && radix == 16 */) {
// 16 进制数格式错误,前缀 0x 后缺少 16 进制数字(16 进制时 numStart 已增加了 2, n 为 null 必是 16 进制解析出错)
throw new ParseException("Error hex format", location);
}
NumTok tok = new NumTok(Sym.INT, n.toString(), radix, false, location);
addToken(tok);
retract(1);
return prepareNextScan();
}
sym = Sym.DOUBLE; // 浮点型默认为 double
c = skipDigit(radix);
}
boolean isScientificNotation = false;
if (c == 'E' || c == 'e') { // scientific notation 科学计数法
c = next();
if (c == '+' || c == '-') {
c = next();
}
if (!CharTable.isDigit(c)) {
// 科学计数法后面缺少数字
throw new ParseException("Error scientific notation format", location);
}
isScientificNotation = true;
sym = Sym.DOUBLE; // 科学计数法默认类型为 double
c = skipDecimalDigit(); // 科学计数法的指数部分是十进制
}
StringBuilder num;
if (c == 'L' || c == 'l') {
if (sym == Sym.DOUBLE) {
// 浮点类型不能使用 'L' 或 'l' 后缀
throw new ParseException("Error float format", location);
}
sym = Sym.LONG;
next();
num = subBuf(numStart, forward - 2);
} else if (c == 'F' || c == 'f') {
sym = Sym.FLOAT;
next();
num = subBuf(numStart, forward - 2);
} else if (c == 'D' || c == 'd') {
sym = Sym.DOUBLE;
next();
num = subBuf(numStart, forward - 2);
} else {
if (sym == null) {
sym = Sym.INT;
}
num = subBuf(numStart, forward - 1);
}
if (errorFollow()) {
// "错误的表达式元素 : " + num + peek()
throw new ParseException("Error expression: " + num + peek(), location);
}
if (num == null /* && radix == 16 */) {
// 16 进制数格式错误,前缀 0x 后缺少 16 进制数字
throw new ParseException("Error hex format", location);
}
NumTok tok = new NumTok(sym, num.toString(), radix, isScientificNotation, location);
addToken(tok);
return prepareNextScan();
}
boolean errorFollow() {
char c = peek();
return CharTable.isLetterOrDigit(c) || c == '"' || c == '\'';
}
char skipDigit(int radix) {
if (radix == 10) {
return skipDecimalDigit();
} else if (radix == 16) {
return skipHexadecimalDigit();
} else {
return skipOctalDigit();
}
}
char skipDecimalDigit() {
char c = peek();
for (; CharTable.isDigit(c);) {
c = next();
}
return c;
}
char skipHexadecimalDigit() {
char c = peek();
for (; CharTable.isHexadecimalDigit(c);) {
c = next();
}
return c;
}
char skipOctalDigit() {
char c = peek();
for (; CharTable.isOctalDigit(c);) {
c = next();
}
return c;
}
boolean fail() {
forward = lexemeBegin;
forwardRow = beginRow;
if (state < 100) {
state = 100;
} else if (state < 200) {
state = 200;
} else if (state < 300) {
state = 300;
}
return false;
}
char next() {
if (buf[forward] == '\n') {
forwardRow++;
}
return buf[++forward];
}
char peek() {
return buf[forward];
}
/**
* 表达式词法分析需要跳过换行与回车
*/
void skipBlanks() {
while(CharTable.isBlankOrLineFeed(buf[forward])) {
next();
}
}
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() {
state = 0;
lexemeBegin = forward;
beginRow = forwardRow;
return true;
}
void addToken(Tok tok) {
tokens.add(tok);
}
void retract(int n) {
for (int i=0; i
© 2015 - 2024 Weber Informatics LLC | Privacy Policy