1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
//! 模板元素 AST —— 对应 Java `freemarker.core.TemplateElement` 家族
//! (指令产生式映射见 docs/03 §4;各 variant 对应 Java 类注释内联标注)
use crate::core::Expr;
use crate::core::MacroDef;
use crate::span::Span;
#[derive(Debug, Clone)]
pub struct Element {
pub kind: ElementKind,
pub span: Span,
}
impl Element {
pub fn new(kind: ElementKind, span: Span) -> Self {
Element { kind, span }
}
}
/// 赋值操作符(对应 AssignmentInstruction 的 8 种形式)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssignOp {
Equals,
PlusEq,
MinusEq,
TimesEq,
DivideEq,
ModuloEq,
PlusPlus,
MinusMinus,
}
#[derive(Debug, Clone)]
pub enum ElementKind {
/// 模板文本(whitespace stripping 标记:解析期决定渲染期是否裁剪)
Text {
text: String,
strip_before: bool,
strip_after: bool,
/// 原始结束行(token 行号;Java TextBlock 的 endLine 在空白剥离时**不更新**,
/// 而内容裁剪会改变换行数 —— prev/next 链的行号判定须用原始值)
orig_end_line: u32,
},
/// ${expr} 插值 / #{expr[ ; mNMN]} 旧式数值插值(Java DollarVariable /
/// NumericalOutput 坍缩;旧式插值携带小数位格式)
Interpolation {
expr: Expr,
/// 旧式 `#{...}` 插值(Java NumericalOutput):Some((min, max)) = 小数位
/// (无格式串 → (0, 50));None = `${...}`(DollarVariable,走 number_format)
legacy_min_frac: Option<u32>,
legacy_max_frac: Option<u32>,
},
/// <#if>(elseif 已扁平化为嵌套 If 的 else 分支)
If {
cond: Expr,
then: Vec<Element>,
else_: Option<Vec<Element>>,
},
/// <#list>(items/sep 为就地元素,Java IteratorBlock + Items/Sep 模型;
/// var2 = `as k, v` 的键循环变量 —— hashListing,IteratorBlock.java:221-223)
List {
seq: Expr,
var: String,
var2: Option<String>,
body: Vec<Element>,
else_: Option<Vec<Element>>,
},
/// <#items as x[, y]>(就地元素 —— 对应 Java `Items.java:29`;render 时由
/// 最近的 #list 迭代上下文驱动 body 逐项执行,loopForItemsElement 语义)
Items {
var: String,
var2: Option<String>,
body: Vec<Element>,
},
/// <#sep>(就地元素 —— 对应 Java `Sep.java:29`;当前迭代 hasNext 时渲染 body)
Sep {
body: Vec<Element>,
},
/// <#assign a=1, b=2> 多赋值(对应 Java `AssignmentInstruction`)
Assignments(Vec<Element>),
/// <#assign name = expr>(含 += 等操作符)
Assign {
target: String,
expr: Expr,
op: AssignOp,
/// `<#assign x = 1 in nsExp>` 的 in 目标表达式(Java FTL.jj:`[id = <IN> nsExp = Expression()]`;
/// 运行期 eval 后检查类型——Assignment.java:112-122)
namespace: Option<Expr>,
},
/// <#assign name>body</#assign> 块捕获
BlockAssign {
target: String,
body: Vec<Element>,
op: AssignOp,
namespace: Option<Expr>,
},
/// <#global>
Global {
target: String,
expr: Option<Expr>,
body: Option<Vec<Element>>,
op: AssignOp,
},
/// <#local>
Local {
target: String,
expr: Option<Expr>,
body: Option<Vec<Element>>,
op: AssignOp,
},
/// <#macro> / <#function>
Macro {
def: MacroDef,
},
/// <@callee args>body</@callee>
Call {
callee: CallTarget,
args: Vec<(String, Expr)>,
body: Option<Vec<Element>>,
/// body 参数名(<@m ; a, b>;对应 Java UnifiedCall.bodyParameters 列表)
body_params: Vec<String>,
},
/// <#nested>(宏体回插)
Nested {
args: Vec<Expr>,
body: Option<Vec<Element>>,
},
/// <#switch>
Switch {
expr: Expr,
cases: Vec<CaseDef>,
default: Option<Vec<Element>>,
/// default 在源码序列中的位置(0 起始;Java SwitchBlock 子块按源码序,
/// legacy 怪癖:default 可不在末尾且不后落到后续 case —— switch.ftl 用例)
default_pos: Option<usize>,
},
/// <#attempt>
Attempt {
try_: Vec<Element>,
recover: Vec<Element>,
},
Break,
Continue,
/// <#return> / <#return expr>
Return {
expr: Option<Expr>,
},
/// <#stop> / <#stop "msg">
Stop {
msg: Option<Expr>,
},
Flush,
/// <#trim>
Trim(Vec<Element>),
/// <#-- comment -->
Comment {
text: String,
},
/// <#include path args...>
Include {
path: Expr,
attrs: Vec<(String, Expr)>,
},
/// <#import path as ns>
Import {
path: Expr,
ns: String,
},
/// <#escape>
Escape {
expr: Expr,
body: Vec<Element>,
},
NoEscape(Vec<Element>),
AutoEsc(Vec<Element>),
NoAutoEsc(Vec<Element>),
/// <#outputformat "HTML">
OutputFormat {
name: Expr,
body: Vec<Element>,
},
/// <#compress>
Compress(Vec<Element>),
/// <#setting key=value>
Setting {
key: String,
value: Expr,
},
/// `[<#ftl encoding="...">]`
FtlHeader {
encoding: Option<String>,
},
/// <#t>(行首裁剪)
TrimLineStart,
/// <#nt>(行首不裁剪)
NoTrimLineStart,
/// <#rt>(行尾空白裁剪 —— 对应 Java TrimInstruction(false,true),RTRIM)
TrimLineEnd,
/// <#lt>(行首空白裁剪 —— 对应 Java TrimInstruction(true,false),LTRIM;
/// 注:Java 中 `<#lt>` 是裁剪指令而非字面 "<",v1 曾映射为 RawText 属文档化偏差)
LeftTrimLine,
/// <#transform expr>body</#transform>(对应 Java `TransformBlock.java`:
/// 旧式 TemplateTransformModel 指令;`?interpret` 产物为变换模型)
Transform {
expr: Expr,
body: Vec<Element>,
},
/// <#visit expr>(对应 Java `VisitNode.java`:XML 节点访问;无参 = 当前节点)
Visit {
expr: Option<Expr>,
/// `<#visit node using target>` 的 using 目标(Java VisitNode 的
/// recurseTarget 参数;XML 场景才有意义,v1 仅解析保留)
using: Option<Expr>,
},
/// <#recurse expr>(对应 Java `RecurseNode.java`:递归访问子节点;无参 = 当前节点)
Recurse {
expr: Option<Expr>,
/// `<#recurse node using target>`(Java RecurseNode 的 recurseTarget)
using: Option<Expr>,
},
/// <#on name>body</#on>(对应 Java `On.java`:节点名分派)
On {
expr: Expr,
body: Vec<Element>,
},
/// <#fallback>(对应 Java `FallbackInstruction.java`:回退到默认节点模板)
Fallback,
/// 特殊文本 `<#gt>`(Java 无 `<#gt>` 指令,v1 契约映射为字面 ">")
RawText(String),
/// <#noparse> 原样文本(Java TextBlock(unparsed=true):与普通文本一样参与空白剥离)
NoParse {
text: String,
strip_before: bool,
strip_after: bool,
/// 同 Text.orig_end_line(Java endLine 裁剪时不更新)
orig_end_line: u32,
},
}
/// 宏/函数定义(对应 Java `Macro`)
#[derive(Debug, Clone, PartialEq)]
pub enum CallTarget {
/// 简单名(当前命名空间宏/变量)
Name(String),
/// ns.name(命名空间限定)
Namespaced { ns: String, name: String },
/// 动态表达式
Expr(Box<Expr>),
}
#[derive(Debug, Clone)]
pub struct CaseDef {
pub value: Expr,
pub body: Vec<Element>,
/// 是否 #on 分支(Java SwitchBlock.usesOnDirective):匹配后不 fall-through,
/// 且体内 break/continue 不被捕获(Java On 无 break 子元素)
pub is_on: bool,
}