enjoy 3.3 release ^_^

This commit is contained in:
James
2017-11-21 22:43:34 +08:00
parent 28eb105ffa
commit 61aa1d2082
70 changed files with 3378 additions and 299 deletions

View File

@@ -95,7 +95,7 @@ class Lexer {
StringBuilder para = null;
Token idToken = null;
Token paraToken = null;
while(true) {
while (true) {
switch (state) {
case 0:
if (peek() == '#') { // #
@@ -141,6 +141,14 @@ class Lexer {
continue ;
}
// 在支持 #seleif 的基础上,支持 #else if
if (symbol == Symbol.ELSE) {
if (foundFollowingIf()) {
id = "else if";
symbol = Symbol.ELSEIF;
}
}
// 无参关键字指令
if (symbol.noPara()) {
return addNoParaToken(new Token(symbol, id, beginRow));
@@ -199,6 +207,22 @@ class Lexer {
}
}
boolean foundFollowingIf() {
int p = forward;
while (CharTable.isBlank(buf[p])) {p++;}
if (buf[p++] == 'i') {
if (buf[p++] == 'f') {
while (CharTable.isBlank(buf[p])) {p++;}
// 要求出现 '(' 才认定解析成功,为了支持这种场景: #else if you ...
if (buf[p] == '(') {
forward = p;
return true;
}
}
}
return false;
}
/**
* 调用者已确定以字母或下划线开头,故一定可以获取到 id值
*/
@@ -405,7 +429,7 @@ class Lexer {
}
void skipBlanks() {
while(CharTable.isBlank(buf[forward])) {
while (CharTable.isBlank(buf[forward])) {
next();
}
}

View File

@@ -18,6 +18,7 @@ package com.jfinal.template.stat;
import java.util.ArrayList;
import java.util.List;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ExprParser;
import com.jfinal.template.expr.ast.ExprList;
@@ -84,10 +85,10 @@ public class Parser {
throw new ParseException("Can not match the #end of directive #" + name.value(), getLocation(name.row));
}
public Stat parse() {
public StatList parse() {
tokenList = new Lexer(content, fileName).scan();
tokenList.add(EOF);
Stat statList = statList();
StatList statList = statList();
if (peek() != EOF) {
throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row));
}
@@ -122,7 +123,7 @@ public class Parser {
switch (name.symbol) {
case TEXT:
move();
return new Text(((TextToken)name).getContent()).setLocation(getLocation(name.row));
return new Text(((TextToken)name).getContent(), env.getEngineConfig().getEncoding()).setLocation(getLocation(name.row));
case OUTPUT:
move();
Token para = matchPara(name);
@@ -171,9 +172,9 @@ public class Parser {
String functionName = name.value();
move();
para = matchPara(name);
Stat stat = statList();
statList = statList();
matchEnd(name);
return new Define(functionName, parseExprList(para), stat, getLocation(name.row));
return new Define(functionName, parseExprList(para), statList, getLocation(name.row));
case CALL:
functionName = name.value();
move();
@@ -206,7 +207,7 @@ public class Parser {
move();
return Return.me;
case ID:
Stat dire = env.getEngineConfig().getDirective(name.value());
Class<? extends Directive> dire = env.getEngineConfig().getDirective(name.value());
if (dire == null) {
throw new ParseException("Directive not found: #" + name.value(), getLocation(name.row));
}
@@ -215,9 +216,9 @@ public class Parser {
para = matchPara(name);
ret.setExprList(parseExprList(para));
if (dire.hasEnd()) {
if (ret.hasEnd()) {
statList = statList();
ret.setStat(statList);
ret.setStat(statList.getActualStat());
matchEnd(name);
}
return ret;
@@ -236,9 +237,9 @@ public class Parser {
return new Location(fileName, row);
}
private Stat createDirective(Stat dire, Token name) {
private Stat createDirective(Class<? extends Directive> dire, Token name) {
try {
return dire.getClass().newInstance();
return dire.newInstance();
} catch (Exception e) {
throw new ParseException(e.getMessage(), getLocation(name.row), e);
}

View File

@@ -90,8 +90,19 @@ public class Scope {
*/
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);
// if (cur.data != null && cur.data.containsKey(key)) {
// return cur.data.get(key);
// }
if (cur.data != null) {
Object ret = cur.data.get(key);
if (ret != null) {
return ret;
}
if (cur.data.containsKey(key)) {
return null;
}
}
}
// return null;
@@ -229,6 +240,18 @@ public class Scope {
}
}
}
/**
* 自内向外在作用域栈中查找变量是否存在
*/
public boolean exists(Object key) {
for (Scope cur=this; cur!=null; cur=cur.parent) {
if (cur.data != null && cur.data.containsKey(key)) {
return true;
}
}
return false;
}
}

View File

@@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**

View File

@@ -16,10 +16,10 @@
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.io.Writer;
import com.jfinal.template.stat.Scope;
/**

View File

@@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**

View File

@@ -16,7 +16,6 @@
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;
@@ -25,6 +24,7 @@ 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;
import com.jfinal.template.io.Writer;
/**
* Define 定义模板函数:
@@ -50,10 +50,10 @@ public class Define extends Stat {
private String[] parameterNames;
private Stat stat;
public Define(String functionName, ExprList exprList, Stat stat, Location location) {
public Define(String functionName, ExprList exprList, StatList statList, Location location) {
setLocation(location);
this.functionName = functionName;
this.stat = stat;
this.stat = statList.getActualStat();
Expr[] exprArray = exprList.getExprArray();
if (exprArray.length == 0) {

View File

@@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
@@ -27,8 +27,8 @@ public class Else extends Stat {
private Stat stat;
public Else(Stat stat) {
this.stat = stat;
public Else(StatList statList) {
this.stat = statList.getActualStat();
}
public void exec(Env env, Scope scope, Writer writer) {

View File

@@ -16,10 +16,11 @@
package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
@@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope;
*/
public class ElseIf extends Stat {
private ExprList cond;
private Expr cond;
private Stat stat;
private Stat elseIfOrElse;
public ElseIf(ExprList cond, Stat stat, Location location) {
public ElseIf(ExprList cond, StatList statList, Location location) {
if (cond.length() == 0) {
throw new ParseException("The condition expression of #elseif statement can not be blank", location);
throw new ParseException("The condition expression of #else if statement can not be blank", location);
}
this.cond = cond;
this.stat = stat;
this.cond = cond.getActualExpr();
this.stat = statList.getActualStat();
}
/**

View File

@@ -16,12 +16,12 @@
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.io.Writer;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Scope;
@@ -38,12 +38,12 @@ import com.jfinal.template.stat.Scope;
public class For extends Stat {
private ForCtrl forCtrl;
private StatList statList;
private Stat stat;
private Stat _else;
public For(ForCtrl forCtrl, StatList statList, Stat _else) {
this.forCtrl = forCtrl;
this.statList = statList;
this.stat = statList.getActualStat();
this._else = _else;
}
@@ -71,7 +71,7 @@ public class For extends Stat {
String itemName = forCtrl.getId();
while(it.hasNext()) {
scope.setLocal(itemName, it.next());
statList.exec(env, scope, writer);
stat.exec(env, scope, writer);
forIteratorStatus.nextState();
if (ctrl.isJump()) {
@@ -108,7 +108,7 @@ public class For extends Stat {
ctrl.setLocalAssignment();
for (init.eval(scope); cond == null || Logic.isTrue(cond.eval(scope)); update.eval(scope)) {
ctrl.setWisdomAssignment();
statList.exec(env, scope, writer);
stat.exec(env, scope, writer);
ctrl.setLocalAssignment();
forLoopStatus.nextState();

View File

@@ -25,7 +25,7 @@ public class ForEntry implements Entry<Object, Object> {
private Entry<Object, Object> entry;
public ForEntry(Entry<Object, Object> entry) {
public void init(Entry<Object, Object> entry) {
this.entry = entry;
}

View File

@@ -60,11 +60,6 @@ public class ForIteratorStatus {
@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();
@@ -75,6 +70,11 @@ public class ForIteratorStatus {
iterator = new MapIterator(((Map<Object, Object>)target).entrySet().iterator());
return ;
}
if (target == null) { // 必须放在 target.getClass() 之前,避免空指针异常
size = 0;
iterator = NullIterator.me;
return ;
}
if (target.getClass().isArray()) {
size = Array.getLength(target);
iterator = new ArrayIterator(target, size);
@@ -148,6 +148,7 @@ public class ForIteratorStatus {
class MapIterator implements Iterator<Entry<Object, Object>> {
private Iterator<Entry<Object, Object>> iterator;
private ForEntry forEntry = new ForEntry();
public MapIterator(Iterator<Entry<Object, Object>> iterator) {
this.iterator = iterator;
@@ -158,7 +159,8 @@ class MapIterator implements Iterator<Entry<Object, Object>> {
}
public Entry<Object, Object> next() {
return new ForEntry((Entry<Object, Object>)iterator.next());
forEntry.init(iterator.next());
return forEntry;
}
public void remove() {

View File

@@ -16,10 +16,11 @@
package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
@@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope;
*/
public class If extends Stat {
private ExprList cond;
private Expr cond;
private Stat stat;
private Stat elseIfOrElse;
public If(ExprList cond, Stat stat, Location location) {
public If(ExprList cond, StatList statList, 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;
this.cond = cond.getActualExpr();
this.stat = statList.getActualStat();
}
/**

View File

@@ -16,13 +16,13 @@
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.io.Writer;
import com.jfinal.template.source.ISource;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location;
@@ -94,7 +94,7 @@ public class Include extends Stat {
if (config.isDevMode()) {
env.addSource(fileSource);
}
this.stat = parser.parse();
this.stat = parser.parse().getActualStat();
} catch (Exception e) {
// 文件路径不正确抛出异常时添加 location 信息
throw new ParseException(e.getMessage(), location, e);

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.stat.ast;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
* NullStat
*/
public class NullStat extends Stat {
public static final NullStat me = new NullStat();
private NullStat() {}
public void exec(Env env, Scope scope, Writer writer) {
}
}

View File

@@ -16,10 +16,11 @@
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.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
@@ -34,21 +35,41 @@ import com.jfinal.template.stat.Scope;
*/
public class Output extends Stat {
private ExprList exprList;
private Expr expr;
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;
this.expr = exprList.getActualExpr();
}
public void exec(Env env, Scope scope, Writer writer) {
try {
Object value = exprList.eval(scope);
if (value != null) {
String str = value.toString();
Object value = expr.eval(scope);
if (value instanceof String) {
String str = (String)value;
writer.write(str, 0, str.length());
} else if (value instanceof Number) {
Class<?> c = value.getClass();
if (c == Integer.class) {
writer.write((Integer)value);
} else if (c == Long.class) {
writer.write((Long)value);
} else if (c == Double.class) {
writer.write((Double)value);
} else if (c == Float.class) {
writer.write((Float)value);
} else if (c == Short.class) {
writer.write((Short)value);
} else {
writer.write(value.toString());
}
} else if (value instanceof Boolean) {
writer.write((Boolean)value);
} else if (value != null) {
writer.write(value.toString());
}
} catch(TemplateException e) {
throw e;

View File

@@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast;
import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**

View File

@@ -16,11 +16,11 @@
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.io.Writer;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
@@ -36,7 +36,7 @@ import com.jfinal.template.stat.Scope;
*/
public class Set extends Stat {
private ExprList exprList;
private Expr expr;
public Set(ExprList exprList, Location location) {
if (exprList.length() == 0) {
@@ -48,12 +48,12 @@ public class Set extends Stat {
throw new ParseException("#set directive only supports assignment expressions", location);
}
}
this.exprList = exprList;
this.expr = exprList.getActualExpr();
}
public void exec(Env env, Scope scope, Writer writer) {
scope.getCtrl().setWisdomAssignment();
exprList.eval(scope);
expr.eval(scope);
}
}

View File

@@ -16,11 +16,11 @@
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.io.Writer;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
@@ -33,7 +33,7 @@ import com.jfinal.template.stat.Scope;
*/
public class SetGlobal extends Stat {
private ExprList exprList;
private Expr expr;
public SetGlobal(ExprList exprList, Location location) {
if (exprList.length() == 0) {
@@ -45,14 +45,14 @@ public class SetGlobal extends Stat {
throw new ParseException("#setGlobal directive only supports assignment expressions", location);
}
}
this.exprList = exprList;
this.expr = exprList.getActualExpr();
}
public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl();
try {
ctrl.setGlobalAssignment();
exprList.eval(scope);
expr.eval(scope);
} finally {
ctrl.setWisdomAssignment();
}

View File

@@ -16,11 +16,11 @@
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.io.Writer;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
@@ -34,7 +34,7 @@ import com.jfinal.template.stat.Scope;
*/
public class SetLocal extends Stat {
final ExprList exprList;
final Expr expr;
public SetLocal(ExprList exprList, Location location) {
if (exprList.length() == 0) {
@@ -46,14 +46,14 @@ public class SetLocal extends Stat {
throw new ParseException("#setLocal directive only supports assignment expressions", location);
}
}
this.exprList = exprList;
this.expr = exprList.getActualExpr();
}
public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl();
try {
ctrl.setLocalAssignment();
exprList.eval(scope);
expr.eval(scope);
} finally {
ctrl.setWisdomAssignment();
}

View File

@@ -17,10 +17,10 @@
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.io.Writer;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.Scope;
@@ -59,19 +59,6 @@ public abstract class Stat {
throw new TemplateException(e.getMessage(), location, e);
}
}
protected void write(Writer writer, char[] chars) {
try {
writer.write(chars, 0, chars.length);
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
}

View File

@@ -16,10 +16,10 @@
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.io.Writer;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Scope;
@@ -28,24 +28,44 @@ import com.jfinal.template.stat.Scope;
*/
public class StatList extends Stat {
public static final Stat[] NULL_STATS = new Stat[0];
public static final Stat NULL_STAT = NullStat.me;
public static final Stat[] NULL_STAT_ARRAY = 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;
this.statArray = NULL_STAT_ARRAY;
}
}
/**
* 持有 StatList 的指令可以通过此方法提升 AST 执行性能
* 1当 statArray.length > 1 时返回 StatList 自身
* 2当 statArray.length == 1 时返回 statArray[0]
* 3其它情况返回 NullStat
*
* 意义在于,当满足前面两个条件时,避免掉了 StatList.exec(...) 方法中的判断与循环
*/
public Stat getActualStat() {
if (statArray.length > 1) {
return this;
} else if (statArray.length == 1) {
return statArray[0];
} else {
return NULL_STAT;
}
}
public void exec(Env env, Scope scope, Writer writer) {
Ctrl ctrl = scope.getCtrl();
for (Stat stat : statArray) {
for (int i=0; i<statArray.length; i++) {
if (ctrl.isJump()) {
break ;
}
stat.exec(env, scope, writer);
statArray[i].exec(env, scope, writer);
}
}

View File

@@ -17,41 +17,98 @@
package com.jfinal.template.stat.ast;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.io.IWritable;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
* Text 输出纯文本块以及使用 "#[[" 与 "]]#" 指定的非解析块
*/
public class Text extends Stat {
public class Text extends Stat implements IWritable {
private char[] text;
// content、bytes、chars 三者必有一者不为 null
// 在 OutputStream、Writer 混合模式下 bytes、chars 同时不为null
private StringBuilder content;
private Charset charset;
private byte[] bytes;
private char[] chars;
public Text(StringBuilder content) {
this.text = new char[content.length()];
content.getChars(0, content.length(), this.text, 0);
// content 初始值在 Lexer 中已确保不为 null
public Text(StringBuilder content, String encoding) {
this.content = content;
this.charset = Charset.forName(encoding);
this.bytes = null;
this.chars = null;
}
public void exec(Env env, Scope scope, Writer writer) {
try {
writer.write(text, 0, text.length);
writer.write(this);
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
public boolean isEmpty() {
return text.length == 0;
public byte[] getBytes() {
if (bytes != null) {
return bytes;
}
if (content != null) {
bytes = content.toString().getBytes(charset);
content = null;
return bytes;
}
bytes = new String(chars).getBytes(charset);
return bytes;
}
public String getContent() {
return text != null ? new String(text) : null;
public char[] getChars() {
if (chars != null) {
return chars;
}
if (content != null) {
char[] charsTemp = new char[content.length()];
content.getChars(0, content.length(), charsTemp, 0);
chars = charsTemp;
content = null;
return chars;
}
String strTemp = new String(bytes, charset);
char[] charsTemp = new char[strTemp.length()];
strTemp.getChars(0, strTemp.length(), charsTemp, 0);
chars = charsTemp;
return chars;
}
public boolean isEmpty() {
if (content != null) {
return content.length() == 0;
} else if (bytes != null) {
return bytes.length == 0;
} else {
return chars.length == 0;
}
}
// public String getContent() {
// return text != null ? new String(text) : null;
// }
public String toString() {
return text != null ? new String(text) : "";
if (bytes != null) {
return new String(bytes, charset);
} else if (chars != null) {
return new String(chars);
} else {
return content.toString();
}
}
}