freemarker 0.1.1

Apache FreeMarker 语义兼容的 Rust 模板引擎(freemarker-core 迁移)
Documentation
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
//! 内建函数 —— 对应 Java `freemarker.core.BuiltIn`(`eval`/`calculateResult`,
//! BuiltIn.java:144 注册表)及各 BuiltInsFor*.java 内建实现的聚合入口。
//! 各辅助函数对应 Java 类:loop_state_builtin/eval_item_cycle_direct →
//! BuiltInsForLoopVariables.java;builtin_interpret → Interpret.java;
//! date_type_if_unknown/is_type_test/str_builtin → BuiltInsForMultipleTypes.java;
//! parse_number → BuiltInsForNumbers.java;json_value_to_model →
//! BuiltInsForStringsMisc.java;locale_case → BuiltInsForStrings.java;
//! join 相关 → BuiltInsForSequenceBuiltin.java(BuiltInsFor*.java 的细化拆分
//! 见 builtins 对齐任务)。

use crate::core::environment::model_to_string;
use crate::core::eval::{eval, eval_lenient, trunc_i64};
use crate::core::Expr;
use crate::error::{Result, TemplateError};
use crate::template::utility::java_trim;
use crate::template::TModel;
use crate::value::{DateType, TNumber};

// ---------------------------------------------------------------------------
// 内建函数(Java BuiltInsFor*.java;docs/05 迁移清单)
// ---------------------------------------------------------------------------

/// 内建函数参数表达式视图(惰性内建按需求值)
pub(crate) struct BuiltinArgs<'a> {
    pub(crate) exprs: Option<&'a [Expr]>,
}

/// 内建函数求值(Java `BuiltIn.calculateResult(Environment)`)。
/// 分派顺序:① `crate::builtins::lookup` 注册表(内建函数智能体填表;?replace/?split/
/// ?matches/?string/?c 等 flags/模式类内建已迁入)→ ② 本文件的内建集 →
// 辅助族按 #[path] 聚合拆分(参照 grammar.rs 模式):
// - built_in_helpers.rs —— 循环状态、new 构造器、?interpret、参数解析辅助
#[path = "built_in_helpers.rs"]
mod built_in_helpers;

pub(crate) use self::built_in_helpers::check_legacy_escaping_ban;
use self::built_in_helpers::{
    arg_expr, builtin_interpret, char_index_from, date_type_if_unknown, eval_item_cycle_direct,
    is_type_test, locale_case, loop_state_builtin, parse_number, str_builtin,
    NewConstructorFunction,
};
pub(crate) fn eval_builtin(
    env: &mut crate::core::Environment,
    target: &Expr,
    name: &str,
    args: &Option<Vec<Expr>>,
) -> Result<TModel> {
    // ① `?itemCycle(a, b, ...)` 带参调用:Java item_cycleBI 的 DirectCall 语义 ——
    // 解析为方法调用 `(x?itemCycle)(...)`(FTL.jj BuiltIn 产生式对
    // BuiltInForLoopVariable 提前 return,括号成为 MethodArgs),BIMethod.exec
    // 直接按迭代 index 返回轮换值;`?itemCycle()` 零参 → 参数个数错误。
    // (无括号的 `?itemCycle` 才是方法模型,落注册表 loop_vars.rs)
    if name == "item_cycle" && args.is_some() {
        return eval_item_cycle_direct(env, target, args.as_deref().unwrap_or(&[]));
    }
    // ①b `"类名"?new(...)` 带参调用:Java FTL.jj 对 NewBI 同样提前 return,
    // 括号成为 MethodArgs(NewBI 是 BuiltIn 但 `?new(args)` 即构造调用,
    // NewBI.java:24-27 → ConstructorFunction.exec)——直接执行构造
    if name == "new" && args.is_some() {
        let m = eval(env, target)?;
        let class_name = crate::core::environment::model_to_string(env, &m)?;
        // Java NewBI._eval(NewBI.java:24-27):构造器创建时即经
        // env.getNewBuiltinClassResolver().resolve 做权限判定(含 ?new 词法所在
        // 模板名——OptIn 的 trusted_templates 匹配)
        let resolver = env.settings.new_builtin_class_resolver.clone();
        resolver.resolve(&class_name, Some(&env.current_template_name))?;
        let mut vals: Vec<TModel> = Vec::new();
        for a in args.as_deref().unwrap_or(&[]) {
            match eval(env, a) {
                Ok(v) => vals.push(v),
                Err(TemplateError::InvalidReference { .. }) => vals.push(TModel::nothing()),
                Err(e) => return Err(e),
            }
        }
        return crate::template::utility_transforms::new_utility_class(&class_name, &vals);
    }
    // ② 注册表(契约:先 lookup;参数以表达式原样传入——惰性内建 ?then/?switch 需要)
    if let Some(f) = crate::builtins::lookup(name) {
        let r = f(env, target, args.as_deref());
        // 目标类型错误 → Java `For "?{name}" left-hand operand: ... ==> {target}`
        // (BuiltInForString.calculateResult 的 coerceModelToStringOrMarkup blame;
        // 已带 blamer 的错误(?string 等自有措辞)跳过)
        let r = r.map_err(|e| blame_builtin_operand(e, env, target, name));
        if let Some(m) = r? {
            return Ok(m);
        }
        // 注册表返回 None → 落入本文件内建集(保持分派顺序兼容)
    }

    // ③ 内建集(本文件直接实现)
    let ba = BuiltinArgs {
        exprs: args.as_deref(),
    };
    let result = builtin_impl(env, target, name, &ba);
    let result = result.map_err(|e| blame_builtin_operand(e, env, target, name));
    match result {
        Ok(Some(m)) => Ok(m),
        Ok(None) => Err(TemplateError::misc(format!("Unknown built-in: ?{name}"))),
        Err(e) => Err(e),
    }
}

/// 内建左操作数类型错误 → Java `For "?{name}" left-hand operand: ... ==> {target}`
/// 形式(Java BuiltInForString / 各 BuiltIn 的 left-hand operand blame;
/// 仅未带 blamer 的 TypeMismatch——?string 等已在实现处设置自有措辞)
fn blame_builtin_operand(
    e: TemplateError,
    env: &crate::core::Environment,
    target: &Expr,
    name: &str,
) -> TemplateError {
    let lacks_blamer = matches!(
        &e,
        TemplateError::TypeMismatch { ctx, .. } if ctx.blamer.is_none()
    );
    if lacks_blamer {
        e.with_blame_at(
            &format!("?{name}"),
            "left-hand operand",
            &crate::core::environment::expr_desc(target),
            &env.current_template_name,
            target.span,
        )
    } else {
        e
    }
}

/// `?itemCycle(a, b, ...)` 直接取值 —— 对应 Java `item_cycleBI$BIMethod.exec`
/// (BuiltInsForLoopVariables.java:135-148):按迭代 index 循环取第
/// `index % args.size()` 个参数;零参 → newArgCntError 消息逐字
/// ("expects 1 or more (unlimited) arguments but has received none.")。
fn builtin_impl(
    env: &mut crate::core::Environment,
    target: &Expr,
    name: &str,
    args: &BuiltinArgs,
) -> Result<Option<TModel>> {
    // ---- 循环变量内建(Java BuiltInsForLoopVariables.java;须先于 is_* 类型测试)----
    if let Some(v) = loop_state_builtin(env, target, name, args)? {
        return Ok(Some(v));
    }
    match name {
        // ---- 存在性(Java BuiltInsForExistenceHandling.java)----
        "default" => {
            // Java defaultBI(BuiltInsForExistenceHandling.java:78-91):目标非 null →
            // ConstantMethod(惰性,参数不求值);null → FIRST_NON_NULL_METHOD.exec(args)
            // —— 遍历参数返回**首个非 null**(参数求值容忍缺失 → null,
            // Environment.getVariable 不抛错;`v?default(w, '-')` 中 w 缺失 → '-')
            // Java defaultBI:evalMaybeNonexistentTarget(仅括号目标抑制错误)
            let m = eval_lenient(env, target)?;
            if !m.is_nothing() {
                return Ok(Some(m));
            }
            // FIRST_NON_NULL_METHOD.exec(args):遍历参数返回首个非 null
            // (参数求值容忍缺失 → null,Environment.getVariable 不抛错;
            // `v?default(w, '-')` 中 w 缺失 → '-')
            let mut result = TModel::nothing();
            for a in args.exprs.unwrap_or(&[]) {
                match eval(env, a) {
                    Ok(v) if !v.is_nothing() => {
                        result = v;
                        break;
                    }
                    Ok(_) => {}
                    Err(TemplateError::InvalidReference { .. }) => {}
                    Err(e) => return Err(e),
                }
            }
            Ok(Some(result))
        }
        "exists" => {
            // Java existsBI:evalMaybeNonexistentTarget != null(仅括号目标抑制错误)
            let m = eval_lenient(env, target)?;
            Ok(Some(TModel::from_boolean(!m.is_nothing())))
        }
        "if_exists" => {
            // Java ifExistsBI:缺失/为 null → TemplateModel.NOTHING(GeneralPurposeNothing:
            // 全能空角色模型——插值输出 ""、布尔 false、序列/哈希为空;
            // 与真缺失不同,不触发 InvalidReference;Ok(nothing) 即 Java 的 null)
            let m = eval_lenient(env, target)?;
            if m.is_nothing() {
                return Ok(Some(TModel::gpn()));
            }
            Ok(Some(m))
        }
        // ---- 类型测试(Java BuiltInsForMultipleTypes.is_*BI;缺失 → false,其他错误上传)----
        "is_string" => is_type_test(env, target, |m| m.is_scalar()),
        "is_number" => is_type_test(env, target, |m| m.is_number()),
        "is_boolean" => is_type_test(env, target, |m| m.is_boolean()),
        "is_date" => is_type_test(env, target, |m| m.is_date()),
        // Java BuiltIn.java:149-150:is_date 与 is_date_like 是同一 BI(misnomer)——
        // 均为 `instanceof TemplateDateModel`(任何日期模型,含 unknown 类型)
        "is_date_like" => is_type_test(env, target, |m| m.is_date()),
        // Java is_dateOfTypeBI(BuiltInsForMultipleTypes.java:291-305):
        // is_unknown_date_like/is_date_only/is_time/is_datetime
        "is_unknown_date_like" => is_type_test(
            env,
            target,
            |m| matches!(&m.date, Some(d) if d.as_date().map(|dv| dv.kind == DateType::Unknown).unwrap_or(false)),
        ),
        "is_date_only" => is_type_test(
            env,
            target,
            |m| matches!(&m.date, Some(d) if d.as_date().map(|dv| dv.kind == DateType::Date).unwrap_or(false)),
        ),
        "is_time" => is_type_test(
            env,
            target,
            |m| matches!(&m.date, Some(d) if d.as_date().map(|dv| dv.kind == DateType::Time).unwrap_or(false)),
        ),
        "is_datetime" => is_type_test(
            env,
            target,
            |m| matches!(&m.date, Some(d) if d.as_date().map(|dv| dv.kind == DateType::DateTime).unwrap_or(false)),
        ),
        // Java dateType_if_unknownBI(BuiltInsForDates.java:45-67):未知日期类型 →
        // 转为指定类型;已知类型 → 原样返回;非日期 → NonDateException
        "datetime_if_unknown" => date_type_if_unknown(env, target, DateType::DateTime),
        "date_if_unknown" => date_type_if_unknown(env, target, DateType::Date),
        "time_if_unknown" => date_type_if_unknown(env, target, DateType::Time),
        // Java is_sequenceBI(BuiltInsForMultipleTypes.java:410-418):ICI ≥ 2.3.24
        // 时排除方法模型(SimpleMethodModel/OverloadedMethodsModel 实现
        // TemplateSequenceModel 但不可 #list);ICI < 2.3.24 不排除——
        // BeansWrapper 方法模型(GenericMethodModel,TModel.method_indexable)
        // 也算序列(type-builtins 的 min/2.3.21 变体 expected)
        "is_sequence" => {
            let ici = env.settings.incompatible_improvements.to_int();
            is_type_test(env, target, move |m| {
                if ici < 2_003_024 {
                    m.is_sequence() || m.method_indexable
                } else {
                    m.is_sequence() && !m.is_method()
                }
            })
        }
        "is_collection" => is_type_test(env, target, |m| m.is_collection()),
        // Java is_enumerableBI(:319-327):序列/集合且(ICI < 2.3.21 或非方法模型)
        "is_enumerable" => {
            let ici = env.settings.incompatible_improvements.to_int();
            is_type_test(env, target, move |m| {
                if ici < 2_003_021 {
                    // ICI < 2.3.21:方法模型(GenericMethodModel 实现
                    // TemplateSequenceModel)同样可枚举
                    m.is_sequence() || m.is_collection() || m.method_indexable
                } else {
                    (m.is_sequence() || m.is_collection()) && !m.is_method()
                }
            })
        }
        // Java is_indexableBI(:350-355):instanceof TemplateSequenceModel——
        // BeansWrapper 方法模型(GenericMethodModel)同样实现之(bean.m?is_indexable
        // → true);自定义方法模型不实现 → false(TModel.method_indexable)
        "is_indexable" => is_type_test(env, target, |m| m.is_sequence() || m.method_indexable),
        "is_hash" => is_type_test(env, target, |m| m.is_hash()),
        "is_hash_ex" => is_type_test(env, target, |m| m.is_hash_ex()),
        "is_method" => is_type_test(env, target, |m| m.is_method()),
        "is_directive" => is_type_test(env, target, |m| m.is_directive()),
        "is_macro" => is_type_test(env, target, |m| m.is_macro()),
        "is_node" => is_type_test(env, target, |m| m.is_node()),
        "is_nothing" => is_type_test(env, target, |m| {
            // GeneralPurposeNothing(Java TemplateNullModel.INSTANCE,?if_exists 缺失
            // 返回值)同样算 nothing —— type_name 均为 "nothing"(TModel::gpn :96)
            m.is_nothing() || m.type_name == "nothing"
        }),
        "is_markup_output" => is_type_test(env, target, |m| m.is_markup_output()),
        "is_transform" => is_type_test(env, target, |m| m.is_transform()),
        "has_api" => is_type_test(env, target, |m| m.api.is_some()),
        // v1 扩展:lambda 槽位测试(Java 侧为 ?is_callable 家族,P4 对齐)
        "is_lambda" => is_type_test(env, target, |m| m.is_lambda()),
        // ---- 字符串(Java BuiltInsForStringsBasic / Misc / Encoding / Regexp)----
        // Java upperCaseBI/lowerCaseBI:str.toUpperCase(locale)/toLowerCase(locale)
        // (locale 感知;tr/az 的 i→İ、I→ı 特殊规则)
        "upper_case" => {
            let locale = env.settings.locale.clone();
            str_builtin(env, target, |s| locale_case(s, &locale, true))
        }
        "lower_case" => {
            let locale = env.settings.locale.clone();
            str_builtin(env, target, |s| locale_case(s, &locale, false))
        }
        "cap_first" => str_builtin(env, target, |s| {
            // Java capFirstBI(BuiltInsForStringsBasic.java:44-58):跳过前导空白,
            // 首个非空白字符大写(Character.toUpperCase——无 locale 规则)
            let mut chars = s.chars();
            let mut skipped = String::new();
            let first = loop {
                match chars.next() {
                    Some(c) if c.is_whitespace() => skipped.push(c),
                    Some(c) => break Some(c),
                    None => break None,
                }
            };
            match first {
                Some(c) => {
                    let mut out = skipped;
                    out.extend(c.to_uppercase());
                    out.push_str(chars.as_str());
                    out
                }
                None => s.to_string(),
            }
        }),
        "trim" => str_builtin(env, target, |s| java_trim(s).to_string()),
        // Java htmlBI(BuiltInsForStringsEncoding.java:36-62)——ICIChainMember 版本链:
        // ICI ≥ 2.3.20 → StringUtil.XHTMLEnc(' → &#39;,HTML_APOS);ICI < 2.3.20 →
        // StringUtil.HTMLEnc = XMLEncNA(StringUtil.java:69-70:不转义 ')
        "html" | "web_safe" => {
            // Java FTL.jj :2230-2238 BuiltInBannedWhenAutoEscaping:auto-escaping on +
            // markup 输出格式时禁用 legacy 转义内建(防双重转义;Java 在解析期检查,
            // Rust 在求值期检查——文档化差异,消息逐字对齐)
            check_legacy_escaping_ban(env, name)?;
            // Java BuiltIn.java:312:web_safe 是 ?html 的弃用别名(deprecated; use ?html)
            let ici = env.settings.incompatible_improvements.to_int();
            str_builtin(env, target, move |s| {
                if ici < 2_003_020 {
                    crate::template::utility::html_enc_legacy(s)
                } else {
                    crate::template::utility::html_escape(s)
                }
            })
        }
        "xml" => {
            // Java FTL.jj :2230-2238 BuiltInBannedWhenAutoEscaping(同上)
            check_legacy_escaping_ban(env, name)?;
            str_builtin(env, target, crate::template::utility::xml_escape)
        }
        "contains" => {
            let arg = arg_expr(args, 0, "?contains requires one argument")?;
            let m = eval(env, target)?;
            let s = model_to_string(env, &m)?;
            let sub = eval(env, arg)?.get_scalar()?;
            Ok(Some(TModel::from_boolean(s.contains(&sub))))
        }
        "starts_with" => {
            let arg = arg_expr(args, 0, "?starts_with requires one argument")?;
            let m = eval(env, target)?;
            let s = model_to_string(env, &m)?;
            let pre = eval(env, arg)?.get_scalar()?;
            Ok(Some(TModel::from_boolean(s.starts_with(&pre))))
        }
        "ends_with" => {
            let arg = arg_expr(args, 0, "?ends_with requires one argument")?;
            let m = eval(env, target)?;
            let s = model_to_string(env, &m)?;
            let suf = eval(env, arg)?.get_scalar()?;
            Ok(Some(TModel::from_boolean(s.ends_with(&suf))))
        }
        "word_list" => {
            // Java word_listBI:按空白切分(StringUtil.split 空白语义;v1 用 split_whitespace)
            let s = eval(env, target)?.get_scalar()?;
            let v: Vec<TModel> = s
                .split_whitespace()
                .map(|p| TModel::from_scalar(p.to_string()))
                .collect();
            Ok(Some(TModel::from_sequence(v)))
        }
        "index_of" => {
            // Java string_indexOf:返回子串首次出现的字符下标(未找到 → -1);
            // 目标按 EvalUtil 强制转字符串
            let sub_expr = arg_expr(args, 0, "?index_of requires one argument")?;
            let tm = eval(env, target)?;
            let s = model_to_string(env, &tm)?;
            let sub = eval(env, sub_expr)?.get_scalar()?;
            let from: usize = if args.exprs.is_some_and(|a| a.len() > 1) {
                let e = &args.exprs.as_ref().unwrap()[1];
                let n = trunc_i64(&eval(env, e)?.get_number()?).unwrap_or(0);
                n.max(0) as usize
            } else {
                0
            };
            let idx = match char_index_from(&s, from) {
                Some(rest) => rest
                    .find(&sub)
                    .map(|b| (from + rest[..b].chars().count()) as i64),
                None => None,
            };
            Ok(Some(TModel::from_number(TNumber::Int(
                idx.unwrap_or(-1) as i32
            ))))
        }
        "substring" => {
            // Java substringBI(BuiltInsForStringsBasic.java:609-662):1-2 参数;
            // 检查顺序:begin<0 → begin>len → end<0 → end>len → begin>end(:625-642);
            // 错误消息逐字对齐("at least 0" / "greater than the length" /
            // "shouldn't be greater than the end index");字符下标(v1 按 char,UTF-16 P4)
            let a = args.exprs.unwrap_or(&[]);
            if a.is_empty() || a.len() > 2 {
                return Err(TemplateError::misc(format!(
                    "?substring(...) expects 1 or 2 arguments but has received {}.",
                    if a.is_empty() {
                        "none".to_string()
                    } else {
                        a.len().to_string()
                    }
                )));
            }
            let tm = eval(env, target)?;
            let s = model_to_string(env, &tm)?;
            let len = s.chars().count();
            let begin = trunc_i64(&eval(env, &a[0])?.get_number()?).unwrap_or(0);
            if begin < 0 {
                return Err(TemplateError::misc(format!(
                    "The index must be at least 0, but was {begin}."
                )));
            }
            if begin > len as i64 {
                return Err(TemplateError::misc(format!(
                    "The index mustn't be greater than the length of the string, {len}, but it was {begin}."
                )));
            }
            let end = if a.len() > 1 {
                let e = trunc_i64(&eval(env, &a[1])?.get_number()?).unwrap_or(0);
                if e < 0 {
                    return Err(TemplateError::misc(format!(
                        "The index must be at least 0, but was {e}."
                    )));
                }
                if e > len as i64 {
                    return Err(TemplateError::misc(format!(
                        "The index mustn't be greater than the length of the string, {len}, but it was {e}."
                    )));
                }
                if begin > e {
                    return Err(TemplateError::misc(format!(
                        "The begin index argument, {begin}, shouldn't be greater than the end index argument, {e}."
                    )));
                }
                e as usize
            } else {
                len
            };
            Ok(Some(TModel::from_scalar(
                s.chars()
                    .skip(begin as usize)
                    .take(end - begin as usize)
                    .collect(),
            )))
        }
        "length" => {
            // Java lengthBI:字符串 → UTF-16 码元数;序列 → size
            let m = eval(env, target)?;
            if let Some(sc) = &m.scalar {
                return Ok(Some(TModel::from_number(TNumber::from_i64(
                    sc.as_string()?.encode_utf16().count() as i64,
                ))));
            }
            if let Some(seq) = &m.sequence {
                return Ok(Some(TModel::from_number(TNumber::from_i64(
                    seq.size()? as i64
                ))));
            }
            Err(TemplateError::misc(format!(
                "?length is not applicable to a {} value",
                m.type_name
            )))
        }
        "number" => {
            // Java numberBI:数字原样;字符串按数字格式解析
            let m = eval(env, target)?;
            if let Some(n) = &m.number {
                return Ok(Some(TModel::from_number(n.as_number()?)));
            }
            let s = m.get_scalar()?;
            Ok(Some(TModel::from_number(parse_number(&s)?)))
        }
        "eval_json" => crate::core::built_ins_for_strings_misc::eval_json(env, target, args.exprs),
        "boolean" => {
            // Java booleanBI(BuiltInsForStringsMisc.java:37):布尔原样;字符串仅接受
            // 精确 "true"/"false" 或当前 boolean_format 的 true/false 串
            let m = eval(env, target)?;
            if let Some(b) = &m.boolean {
                return Ok(Some(TModel::from_boolean(b.as_boolean()?)));
            }
            // Java BuiltInForString:目标强制转字符串(数字 0 → "0" 再判错)
            let s = model_to_string(env, &m)?;
            let (ts, fs) = crate::core::environment::boolean_format_strings(env)
                .unwrap_or_else(|| ("true".to_string(), "false".to_string()));
            if s == "true" || s == ts {
                Ok(Some(TModel::from_boolean(true)))
            } else if s == "false" || s == fs {
                Ok(Some(TModel::from_boolean(false)))
            } else {
                Err(TemplateError::misc(format!(
                    "Can't convert this string to boolean: {s:?}"
                )))
            }
        }
        "int" | "long" | "float" | "double" => {
            // Java BuiltInsForNumbers.intBI/longBI 等:原始类型强转(**溢出回绕**,
            // Java `num.intValue()`/`num.longValue()` 语义,无范围错误);
            // 字符串先解析;日期 → 纪元毫秒(Java EvalUtil.modelToLong 的 TemplateDateModel 分支)
            let m = eval(env, target)?;
            let n = if let Some(n) = &m.number {
                n.as_number()?
            } else if m.is_date() {
                let d = m.get_date()?;
                TNumber::Long(d.dt.timestamp_millis())
            } else {
                parse_number(&m.get_scalar()?)?
            };
            Ok(Some(match name {
                // Java intValue():double/float 截断(向零),溢出按 32 位回绕
                "int" => TModel::from_number(TNumber::Int(trunc_i64(&n).unwrap_or(0) as i32)),
                // Java longValue():double/float 截断,BigInteger 溢出按 64 位回绕
                "long" => TModel::from_number(TNumber::Long(trunc_i64(&n).unwrap_or(0))),
                "float" => TModel::from_number(TNumber::Float(n.as_f32().unwrap_or(0.0))),
                "double" => TModel::from_number(TNumber::Double(n.as_f64().unwrap_or(0.0))),
                _ => unreachable!(),
            }))
        }
        // ---- 序列(Java BuiltInsForSequences.java)----
        "size" => {
            let m = eval(env, target)?;
            if let Some(seq) = &m.sequence {
                return Ok(Some(TModel::from_number(TNumber::from_i64(
                    seq.size()? as i64
                ))));
            }
            if let Some(ex) = &m.hash_ex {
                return Ok(Some(TModel::from_number(TNumber::from_i64(
                    ex.size()? as i64
                ))));
            }
            // Java:非序列/扩展哈希 → 报错(?size 在无界范围上不可用,与 Java 一致)
            Err(TemplateError::misc(format!(
                "?size is not applicable to a {} value",
                m.type_name
            )))
        }
        "first" => {
            // Java firstBI(BuiltInsForSequences.java:149-189):序列优先(2.3.x BC),
            // 否则集合迭代;空 → null(下游 InvalidReferenceException)
            let m = eval(env, target)?;
            if let Some(seq) = &m.sequence {
                if seq.size()? == 0 {
                    return Ok(Some(TModel::nothing()));
                }
                return Ok(Some(seq.get(0)?));
            }
            if let Some(c) = &m.collection {
                let mut it = c.iterator()?;
                return match it.next() {
                    Some(v) => Ok(Some(v?)),
                    None => Ok(Some(TModel::nothing())),
                };
            }
            if let Some(sc) = &m.scalar {
                let s = sc.as_string()?;
                return match s.chars().next() {
                    Some(c) => Ok(Some(TModel::from_scalar(c.to_string()))),
                    None => Err(TemplateError::misc("The string is empty, ?first failed")),
                };
            }
            Err(TemplateError::misc(format!(
                "?first is not applicable to a {} value",
                m.type_name
            )))
        }
        "last" => {
            // Java lastBI(BuiltInsForSequences.java:267-277):空 → null
            let m = eval(env, target)?;
            if let Some(seq) = &m.sequence {
                let size = seq.size()?;
                if size == 0 {
                    return Ok(Some(TModel::nothing()));
                }
                return Ok(Some(seq.get(size - 1)?));
            }
            if let Some(sc) = &m.scalar {
                let s = sc.as_string()?;
                return match s.chars().next_back() {
                    Some(c) => Ok(Some(TModel::from_scalar(c.to_string()))),
                    None => Err(TemplateError::misc("The string is empty, ?last failed")),
                };
            }
            Err(TemplateError::misc(format!(
                "?last is not applicable to a {} value",
                m.type_name
            )))
        }
        "join" => crate::core::built_ins_for_sequences::join(env, target, args.exprs),
        "reverse" => crate::core::built_ins_for_sequences::reverse(env, target, args.exprs),
        "seq_contains" => {
            crate::core::built_ins_for_sequences::seq_contains(env, target, args.exprs)
        }
        // ---- 哈希(Java BuiltInsForHashes.java)----
        "keys" => crate::core::built_ins_for_hashes::keys(env, target, args.exprs),
        "values" => crate::core::built_ins_for_hashes::values(env, target, args.exprs),
        // ---- 输出/格式化(Java BuiltInsForMultipleTypes.java)----
        "has_content" => {
            // Java hasContentBI:evalMaybeNonexistentTarget(仅括号目标抑制)→ isEmpty
            let m = eval_lenient(env, target)?;
            Ok(Some(TModel::from_boolean(m.has_content()?)))
        }
        // ---- 动态解释(Java Interpret.java;BuiltIn.java:144)----
        "interpret" => {
            // 注:Java 的 ?interpret 返回惰性变换模型;本实现同语义(变换模型槽位)
            let r = builtin_interpret(env, target)?;
            Ok(Some(r))
        }
        // ---- 类实例化(Java NewBI.java:?new 返回 ConstructorFunction 方法模型,
        // 调用 `"类名"?new(args)` 时经 BeansWrapper.newInstance 实例化)----
        "new" => {
            // Java NewBI._eval(NewBI.java:24-27):target 求值为类名字符串
            let m = eval(env, target)?;
            let class_name = crate::core::environment::model_to_string(env, &m)?;
            // Java:resolve 在构造器创建时执行(模板解析期 classname 已知时)——
            // v1 延迟到方法调用时(等价;类名解析错误消息对齐 Java);
            // 权限判定与 Java 同步在此处(NewBI.java:32-38:resolve(className, env,
            // target.getTemplate())——template 即 ?new 词法所在模板,OptIn 的
            // trusted_templates 按它匹配)
            let resolver = env.settings.new_builtin_class_resolver.clone();
            resolver.resolve(&class_name, Some(&env.current_template_name))?;
            Ok(Some(TModel::from_method(NewConstructorFunction {
                class_name,
            })))
        }
        // ---- 表达式动态求值(Java BuiltInsForStringsMisc.evalBI)----
        "eval" => {
            // Java:源码包为 `(...)` 按表达式解析 → 求值;解析/求值失败均包进
            // "Failed to \"?eval\" string with this error: ..." 消息(:87-118);
            // 但求值为 null(缺失变量)**不包装**——原样返回 null(Java exp.eval
            // 返回 null,`'fails'?eval!'-'` → evalBI null → 默认值 '-' 生效)
            let m = eval(env, target)?;
            let s = crate::core::environment::model_to_string(env, &m)?;
            let cfg = env.template.configuration.clone();
            let expr = crate::parser::parse_expression(&cfg, &s).map_err(|e| {
                TemplateError::misc(format!(
                    "Failed to \"?eval\" string with this error:\n\n{e}\n\nThe failing expression:"
                ))
            })?;
            // Java:?eval 字符串的源码没有宏上下文——`.args` 在其中静态非法
            // (FMParser 的 args 特殊变量检查;jar 实测消息逐字)
            if matches!(
                expr.kind,
                crate::core::ExprKind::BuiltinVar(crate::core::BuiltinVar::Args)
            ) {
                return Err(TemplateError::misc(format!(
                    "Failed to \"?eval\" string with this error:\n\n---begin-message---\nSyntax error in ?eval-ed string in line 1, column 3:\nThe \"args\" special variable must be inside a macro or function in the template source code.\n---end-message---\n\nThe failing expression:\n==> '{s}'?eval"
                )));
            }
            match eval(env, &expr) {
                Ok(v) => Ok(Some(v)),
                Err(TemplateError::InvalidReference { .. }) => Ok(Some(TModel::nothing())),
                Err(e) => Err(TemplateError::misc(format!(
                    "Failed to \"?eval\" string with this error:\n\n{e}\n\nThe failing expression:"
                ))),
            }
        }
        // ---- 其余未知内建:由调用方报 Unknown built-in ----
        _ => Ok(None),
    }
}