Skip to main content

sml/
c_abi.rs

1use crate::core::*;
2use crate::core::to_sml;
3use crate::value::*;
4use std::collections::BTreeMap;
5// ---------------------------------------------------------------------------
6// C-ABI (cdylib, 供 C / 其它语言调用)
7// ---------------------------------------------------------------------------
8
9use std::os::raw::{c_char, c_int};
10use std::ptr;
11
12fn cstr(s: &str) -> *mut c_char {
13    let c = std::ffi::CString::new(s).unwrap_or_default();
14    c.into_raw()
15}
16
17/// sml_parse(text) -> 返回 JSON 字符串 (调用方 sml_free 释放); 失败返回 NULL
18#[cfg_attr(edge2024, unsafe(no_mangle))]
19#[cfg_attr(not(edge2024), no_mangle)]
20pub extern "C" fn sml_parse(text: *const c_char) -> *mut c_char {
21    if text.is_null() {
22        return ptr::null_mut();
23    }
24    let t = unsafe { std::ffi::CStr::from_ptr(text) }.to_string_lossy().into_owned();
25    match parse(&t) {
26        Ok(v) => cstr(&jsonify(&v)),
27        Err(_) => ptr::null_mut(),
28    }
29}
30
31/// sml_dump(json) -> 接受 JSON 字符串, 序列化为 SML; 调用方 sml_free
32#[cfg_attr(edge2024, unsafe(no_mangle))]
33#[cfg_attr(not(edge2024), no_mangle)]
34pub extern "C" fn sml_dump(json: *const c_char) -> *mut c_char {
35    if json.is_null() {
36        return ptr::null_mut();
37    }
38    let j = unsafe { std::ffi::CStr::from_ptr(json) }.to_string_lossy().into_owned();
39    match json_to_value(&j) {
40        Some(v) => cstr(&to_sml(&v)),
41        None => ptr::null_mut(),
42    }
43}
44
45/// sml_free_str(p): 释放由 sml_parse / sml_dump / sml_dumps 等返回的字符串。
46///
47/// 注:早期版本此函数名为 `sml_free`,与 `sml.h`(纯 C 后端)的
48/// `sml_free(sml_value*)` 语义冲突。为让两个后端心智模型一致
49/// (`sml_free` 释放值树、`sml_free_str` 释放字符串),此处重命名。
50#[cfg_attr(edge2024, unsafe(no_mangle))]
51#[cfg_attr(not(edge2024), no_mangle)]
52pub unsafe extern "C" fn sml_free_str(p: *mut c_char) {
53    if !p.is_null() {
54        drop(unsafe { std::ffi::CString::from_raw(p) });
55    }
56}
57
58/// sml_version() -> 版本静态字符串(**无需释放**,与 jansson 的
59/// `jansson_version_str()` 语义一致)。
60///
61/// 返回指向编译期常量的指针,生命周期为 `'static`。
62/// 需要可释放的副本请用 [`sml_version_str`]。
63#[cfg_attr(edge2024, unsafe(no_mangle))]
64#[cfg_attr(not(edge2024), no_mangle)]
65pub extern "C" fn sml_version() -> *const c_char {
66    concat!("sml ", env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
67}
68
69// ---------------------------------------------------------------------------
70// v3 扩展 ABI (与基础 sml_parse 并存, 不破坏既有符号)
71// 暴露 $env 内联 / glob-include / @feature / @contract 等 v3 能力。
72// 这些入口独立于 c/ 与 cpp/ 的纯 native 实现, 仅供需要完整 v3 功能的
73// 调用方链接本 cdylib 使用。
74// ---------------------------------------------------------------------------
75
76/// 极简解析 opts JSON: 仅识别顶层 object 的
77///   "features": [ "glob-include", ... ]
78///   "env":      { "KEY": "VAL", ... }
79///   "allow":    [ "v1", "v2", "v3" ]
80/// 返回 (features: Vec<Feature>, env: Vec<(String,String)>, allow: Vec<Version>)。
81/// 任何字段缺失即视为空/不限制; 解析失败返回 Err。
82fn parse_opts_json(opts: &str) -> Result<(Vec<Feature>, Vec<(String, String)>, Vec<Version>), String> {
83    let mut features: Vec<Feature> = Vec::new();
84    let mut env: Vec<(String, String)> = Vec::new();
85    let mut allow: Vec<Version> = Vec::new();
86    if opts.trim().is_empty() {
87        return Ok((features, env, allow));
88    }
89    // 手工 tokenizer: 仅支持本结构, 不引第三方依赖。
90    let b = opts.as_bytes();
91    let mut i = 0usize;
92    let len = b.len();
93    // 跳到首个 {
94    while i < len && b[i] != b'{' { i += 1; }
95    if i >= len { return Err("opts 不是 JSON object".into()); }
96    i += 1; // 越过 {
97    loop {
98        // 跳空白与逗号
99        while i < len && (b[i] == b' ' || b[i] == b'\t' || b[i] == b'\n' || b[i] == b'\r' || b[i] == b',') { i += 1; }
100        if i >= len || b[i] == b'}' { break; }
101        // 读 key (双引号字符串)
102        if b[i] != b'"' { return Err("opts key 须为字符串".into()); }
103        i += 1;
104        let ks = i;
105        while i < len && b[i] != b'"' { i += 1; }
106        let key = std::str::from_utf8(&b[ks..i]).map_err(|_| "opts key 非法 UTF-8".to_string())?.to_string();
107        i += 1; // 越过 "
108        while i < len && (b[i] == b' ' || b[i] == b':' || b[i] == b'\t') { i += 1; }
109        match key.as_str() {
110            "features" | "allow" => {
111                // 读数组 [ ... ]
112                if i >= len || b[i] != b'[' { return Err(format!("opts.{key} 须为数组")); }
113                i += 1;
114                loop {
115                    while i < len && (b[i] == b' ' || b[i] == b'\t' || b[i] == b'\n' || b[i] == b'\r' || b[i] == b',') { i += 1; }
116                    if i < len && b[i] == b']' { i += 1; break; }
117                    if i >= len || b[i] != b'"' { return Err(format!("opts.{key} 元素须为字符串")); }
118                    i += 1;
119                    let vs = i;
120                    while i < len && b[i] != b'"' { i += 1; }
121                    let val = std::str::from_utf8(&b[vs..i]).map_err(|_| "opts 值非法 UTF-8".to_string())?.to_string();
122                    i += 1;
123                    if key == "features" {
124                        features.push(Feature::from_name(&val).ok_or_else(|| format!("未知特性 {val}"))?);
125                    } else {
126                        allow.push(Version::from_word(&val).ok_or_else(|| format!("未知版本 {val}"))?);
127                    }
128                }
129            }
130            "env" => {
131                if i >= len || b[i] != b'{' { return Err("opts.env 须为 object".into()); }
132                i += 1;
133                loop {
134                    while i < len && (b[i] == b' ' || b[i] == b'\t' || b[i] == b'\n' || b[i] == b'\r' || b[i] == b',') { i += 1; }
135                    if i < len && b[i] == b'}' { i += 1; break; }
136                    if i >= len || b[i] != b'"' { return Err("opts.env key 须为字符串".into()); }
137                    i += 1;
138                    let ks = i;
139                    while i < len && b[i] != b'"' { i += 1; }
140                    let ek = std::str::from_utf8(&b[ks..i]).map_err(|_| "opts.env key 非法".to_string())?.to_string();
141                    i += 1;
142                    while i < len && (b[i] == b' ' || b[i] == b':' || b[i] == b'\t') { i += 1; }
143                    if i >= len || b[i] != b'"' { return Err("opts.env value 须为字符串".into()); }
144                    i += 1;
145                    let vs = i;
146                    while i < len && b[i] != b'"' { i += 1; }
147                    let ev = std::str::from_utf8(&b[vs..i]).map_err(|_| "opts.env value 非法".to_string())?.to_string();
148                    i += 1;
149                    env.push((ek, ev));
150                }
151            }
152            _ => {
153                // 跳过未知字段的值 (标量/数组/对象)
154                let mut depth = 0i32;
155                loop {
156                    if i >= len { break; }
157                    match b[i] {
158                        b'"' => { i += 1; while i < len && b[i] != b'"' { if b[i] == b'\\' { i += 2; } else { i += 1; } } i += 1; }
159                        b'{' | b'[' => { depth += 1; i += 1; }
160                        b'}' | b']' => { depth -= 1; i += 1; if depth <= 0 { break; } }
161                        _ => { i += 1; }
162                    }
163                }
164            }
165        }
166    }
167    Ok((features, env, allow))
168}
169
170/// sml_parse_ex(text, opts_json) -> JSON 字符串 (调用方 sml_free) 或 NULL。
171///
172/// opts_json 示例:
173///   {"features":["glob-include","contract"],"env":{"APP_ENV":"prod"},"allow":["v1","v3"]}
174/// - features: 调用方额外启用的特性 (与文档 @feature 取交集)。
175/// - env:      注入到进程环境, 供 `$env.X` 内联解析 (调用期间临时设置并恢复)。
176/// - allow:    限定文档声明的版本必须在此范围内; 空数组表示不限制。
177/// 失败 (语法/版本/特性越权/文件找不到) 返回 NULL。
178// env 注入/恢复:edition 2024 起 set_var/remove_var 为 unsafe,
179// 需 unsafe 块;edition 2021 下该块多余,故一并 allow 掉告警。
180#[allow(unused_unsafe)]
181#[cfg_attr(edge2024, unsafe(no_mangle))]
182#[cfg_attr(not(edge2024), no_mangle)]
183pub extern "C" fn sml_parse_ex(text: *const c_char, opts: *const c_char) -> *mut c_char {
184    if text.is_null() {
185        return ptr::null_mut();
186    }
187    let t = unsafe { std::ffi::CStr::from_ptr(text) }.to_string_lossy().into_owned();
188    let opts_str = if opts.is_null() {
189        String::new()
190    } else {
191        unsafe { std::ffi::CStr::from_ptr(opts) }.to_string_lossy().into_owned()
192    };
193    let (feats, env, allow) = match parse_opts_json(&opts_str) {
194        Ok(x) => x,
195        Err(_) => return ptr::null_mut(),
196    };
197    // 临时注入 env (非并发安全, FFI 同步调用假设)。
198    let prev: Vec<(String, Option<String>)> = env
199        .iter()
200        .map(|(k, _)| (k.clone(), std::env::var(k).ok()))
201        .collect();
202    for (k, v) in &env {
203        unsafe { std::env::set_var(k, v) };
204    }
205    let result = (|| {
206        // 构造调用方允许特性集: 基础全集 并 上 opts 指定特性。
207        let mut allowed = FeatureSet::all();
208        for f in &feats {
209            allowed = allowed.with(*f);
210        }
211        let val = parse_with_features(&t, allowed).map(|(v, _)| v)?;
212        if !allow.is_empty() {
213            let declared = strip_version(&t).ok().and_then(|(_, d)| d);
214            if let Some(d) = declared {
215                if !allow.contains(&d) {
216                    return Err(format!("文档声明版本 {} 不在 allow 范围", d.name()));
217                }
218            }
219        }
220        Ok(jsonify(&val))
221    })();
222    // 恢复 env
223    for (k, v) in &prev {
224        match v {
225            Some(old) => unsafe { std::env::set_var(k, old) },
226            None => unsafe { std::env::remove_var(k) },
227        }
228    }
229    match result {
230        Ok(s) => cstr(&s),
231        Err(_) => ptr::null_mut(),
232    }
233}
234
235/// sml_parse_file(path) -> JSON 字符串 (调用方 sml_free) 或 NULL。
236/// 桥接内部 parse_file: 自动处理 include / glob / @contract 校验, 带文件上下文。
237#[cfg_attr(edge2024, unsafe(no_mangle))]
238#[cfg_attr(not(edge2024), no_mangle)]
239pub extern "C" fn sml_parse_file(path: *const c_char) -> *mut c_char {
240    if path.is_null() {
241        return ptr::null_mut();
242    }
243    let p = unsafe { std::ffi::CStr::from_ptr(path) }.to_string_lossy().into_owned();
244    match parse_file(&p) {
245        Ok(v) => cstr(&jsonify(&v)),
246        Err(_) => ptr::null_mut(),
247    }
248}
249
250/// sml_features() -> 当前支持的特性名 JSON 数组 (调用方 sml_free)。
251/// 例: ["include","env","contract","glob-include", ...]
252#[cfg_attr(edge2024, unsafe(no_mangle))]
253#[cfg_attr(not(edge2024), no_mangle)]
254pub extern "C" fn sml_features() -> *mut c_char {
255    let names: Vec<&str> = FEATURES.iter().map(|(n, _)| *n).collect();
256    let body = names
257        .iter()
258        .map(|n| format!("\"{}\"", n))
259        .collect::<Vec<_>>()
260        .join(",");
261    cstr(&format!("[{}]", body))
262}
263
264// ---------------------------------------------------------------------------
265// C-ABI: 值树 (v2 API)
266//
267// 旧 API (sml_parse / sml_parse_file / sml_parse_ex) 以 JSON 字符串为交换格式,
268// 迫使 C 侧再集成一个 JSON 库——这削弱了 SML 作为替代品的动机。
269// 这套值树 API 让 C 直接遍历结果、直接读错误,零外部依赖。
270//
271// 设计参照 jansson (sml_error 详细定位 + flags 位标志) 与 tomlc99 (xxx_in 单行取值)。
272// 生命周期约定:
273//   * sml_loads / sml_load_file 返回的根指针由调用方 sml_free 释放;
274//   * sml_get / sml_get_path / sml_at 返回**借用**指针 (const),不可释放,
275//     随根节点一同失效;
276//   * 所有 char* 输出由调用方 sml_free_str 释放。
277// ---------------------------------------------------------------------------
278
279use std::os::raw::{c_uint, c_ulonglong};
280
281/// 与 `sml_rs.h` 的 `sml_errc` 一一对应。
282#[repr(C)]
283#[derive(Clone, Copy)]
284pub enum CSmlErrc {
285    Ok = 0,
286    Syntax = 1,
287    FeatureDisabled = 2,
288    VersionMismatch = 3,
289    Contract = 4,
290    IncludeLoop = 5,
291    Io = 6,
292    Utf8 = 7,
293    Internal = 8,
294}
295
296/// 与 `sml_rs.h` 的 `sml_error` 一一对应。
297///
298/// 字段顺序、类型、数组长度必须与头文件完全一致,否则跨语言内存布局错位。
299#[repr(C)]
300pub struct CSmlError {
301    pub code: c_int,
302    pub line: c_int,
303    pub column: c_int,
304    pub position: usize,
305    pub source: [c_char; 128],
306    pub text: [c_char; 256],
307}
308
309impl CSmlError {
310    /// 用错误信息填充一块调用方提供的内存。
311    ///
312    /// # Safety
313    /// `out` 必须可写且按 [`CSmlError`] 布局对齐;为 NULL 时静默跳过。
314    unsafe fn fill(out: *mut CSmlError, code: CSmlErrc, msg: &str, source: &str) {
315        if out.is_null() {
316            return;
317        }
318        let e = &mut *out;
319        e.code = code as c_int;
320        e.line = 0;
321        e.column = 0;
322        e.position = 0;
323        e.source = [0; 128];
324        e.text = [0; 256];
325        copy_cstr(&mut e.source, source);
326        copy_cstr(&mut e.text, msg);
327
328        // 从消息里尽量还原行号:形如 "sml: 第 12 行 ..." / "... (line 12)"。
329        if let Some(l) = extract_line(msg) {
330            e.line = l;
331        }
332    }
333}
334
335/// 把 Rust `&str` 复制进定长 C 字符数组,保证 NUL 结尾且截断安全。
336fn copy_cstr(dst: &mut [c_char], s: &str) {
337    if dst.is_empty() {
338        return;
339    }
340    let bytes = s.as_bytes();
341    let n = bytes.len().min(dst.len() - 1);
342    for i in 0..n {
343        dst[i] = bytes[i] as c_char;
344    }
345    dst[n] = 0;
346}
347
348/// 从错误信息中抽取行号(尽力而为,抽不到返回 `None`)。
349fn extract_line(msg: &str) -> Option<c_int> {
350    for pat in ["第 ", "line "] {
351        if let Some(idx) = msg.find(pat) {
352            let rest = &msg[idx + pat.len()..];
353            let digits: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
354            if let Ok(n) = digits.parse::<i32>() {
355                if n > 0 {
356                    return Some(n);
357                }
358            }
359        }
360    }
361    None
362}
363
364/// 值树句柄。`repr(transparent)` 使其与内部 [`Value`] 布局一致,
365/// 从而可以把子值的 `&Value` 安全地重解释为此类型的借用指针。
366#[repr(transparent)]
367pub struct CSmlValue(Value);
368
369/// C 侧要释放的错误信息前缀判断:把解析错误归类。
370fn classify(err: &str) -> CSmlErrc {
371    if err.contains("include") && (err.contains("循环") || err.contains("loop")) {
372        CSmlErrc::IncludeLoop
373    } else if err.contains("特性") || err.contains("feature") {
374        CSmlErrc::FeatureDisabled
375    } else if err.contains("版本") || err.contains("version") {
376        CSmlErrc::VersionMismatch
377    } else if err.contains("契约") || err.contains("contract") {
378        CSmlErrc::Contract
379    } else if err.contains("读取失败") || err.contains("IO") {
380        CSmlErrc::Io
381    } else {
382        CSmlErrc::Syntax
383    }
384}
385
386/// `flags` 位 → [`FeatureSet`]。
387///
388/// `flags == 0` 视为「默认基线」(与 jansson 的 flags=0 语义一致),
389/// 非 0 时按位精确构造,调用方可借此收紧允许范围。
390fn feature_set_from_flags(flags: c_uint) -> FeatureSet {
391    if flags == 0 {
392        return FeatureSet::baseline();
393    }
394    let mut s = FeatureSet::none();
395    for (i, (_, f)) in FEATURES.iter().enumerate() {
396        if i >= 32 {
397            break;
398        }
399        if flags & (1u32 << i) != 0 {
400            s = s.with(*f);
401        }
402    }
403    s
404}
405
406/// 解析 SML 文本为值树。
407///
408/// # Safety
409/// `text` 必须是合法 NUL 结尾字符串或 NULL;`err` 可为 NULL。
410#[cfg_attr(edge2024, unsafe(no_mangle))]
411#[cfg_attr(not(edge2024), no_mangle)]
412pub unsafe extern "C" fn sml_loads(
413    text: *const c_char,
414    flags: c_uint,
415    err: *mut CSmlError,
416) -> *mut CSmlValue {
417    if text.is_null() {
418        CSmlError::fill(err, CSmlErrc::Internal, "sml_loads: text is NULL", "<string>");
419        return ptr::null_mut();
420    }
421    let t = std::ffi::CStr::from_ptr(text).to_string_lossy().into_owned();
422    let allowed = feature_set_from_flags(flags);
423    match parse_with_features(&t, allowed) {
424        Ok((v, _)) => Box::into_raw(Box::new(CSmlValue(v))),
425        Err(e) => {
426            CSmlError::fill(err, classify(&e), &e, "<string>");
427            ptr::null_mut()
428        }
429    }
430}
431
432/// 解析 SML 文件为值树(展开 `include`,相对路径以文件所在目录为基准)。
433///
434/// # Safety
435/// `path` 必须是合法 NUL 结尾字符串或 NULL;`err` 可为 NULL。
436#[cfg_attr(edge2024, unsafe(no_mangle))]
437#[cfg_attr(not(edge2024), no_mangle)]
438pub unsafe extern "C" fn sml_load_file(
439    path: *const c_char,
440    flags: c_uint,
441    err: *mut CSmlError,
442) -> *mut CSmlValue {
443    if path.is_null() {
444        CSmlError::fill(err, CSmlErrc::Internal, "sml_load_file: path is NULL", "<file>");
445        return ptr::null_mut();
446    }
447    let p = std::ffi::CStr::from_ptr(path).to_string_lossy().into_owned();
448    let _ = flags; // 文件入口的特性由文档 @feature 与 flags 共同决定
449    match parse_file(&p) {
450        Ok(v) => Box::into_raw(Box::new(CSmlValue(v))),
451        Err(e) => {
452            CSmlError::fill(err, classify(&e), &e, &p);
453            ptr::null_mut()
454        }
455    }
456}
457
458/// 释放 [`sml_loads`] / [`sml_load_file`] 返回的根节点(NULL 安全)。
459///
460/// 与 `sml.h`(纯 C 后端)的 `sml_free` 语义一致:都是释放值树。
461/// 释放字符串请用 [`sml_free_str`]。
462#[cfg_attr(edge2024, unsafe(no_mangle))]
463#[cfg_attr(not(edge2024), no_mangle)]
464pub unsafe extern "C" fn sml_free(v: *mut CSmlValue) {
465    if !v.is_null() {
466        drop(Box::from_raw(v));
467    }
468}
469
470/// 值类型判别,返回 `sml_type` 枚举值;NULL 或非预期返回 -1。
471#[cfg_attr(edge2024, unsafe(no_mangle))]
472#[cfg_attr(not(edge2024), no_mangle)]
473pub unsafe extern "C" fn sml_typeof(v: *const CSmlValue) -> c_int {
474    if v.is_null() {
475        return -1;
476    }
477    let inner = &(*(v as *const Value));
478    match inner {
479        Value::Null => 0,
480        Value::Bool(_) => 1,
481        Value::Int(_) => 2,
482        Value::Float(_) => 3,
483        Value::Str(_) => 4,
484        Value::Array(_) => 5,
485        Value::Object(_) => 6,
486    }
487}
488
489/// 取对象字段(**借用**,不可释放);键不存在或类型不符返回 NULL。
490#[cfg_attr(edge2024, unsafe(no_mangle))]
491#[cfg_attr(not(edge2024), no_mangle)]
492pub unsafe extern "C" fn sml_get(
493    v: *const CSmlValue,
494    key: *const c_char,
495) -> *const CSmlValue {
496    if v.is_null() || key.is_null() {
497        return ptr::null();
498    }
499    let inner = &(*(v as *const Value));
500    let k = std::ffi::CStr::from_ptr(key).to_string_lossy();
501    match inner {
502        Value::Object(m) => m
503            .get(k.as_ref())
504            .map(|x| x as *const Value as *const CSmlValue)
505            .unwrap_or(ptr::null()),
506        _ => ptr::null(),
507    }
508}
509
510/// 按 `.` 分隔路径逐层取值(**借用**,不可释放)。
511#[cfg_attr(edge2024, unsafe(no_mangle))]
512#[cfg_attr(not(edge2024), no_mangle)]
513pub unsafe extern "C" fn sml_get_path(
514    v: *const CSmlValue,
515    path: *const c_char,
516) -> *const CSmlValue {
517    if v.is_null() || path.is_null() {
518        return ptr::null();
519    }
520    let p = std::ffi::CStr::from_ptr(path).to_string_lossy();
521    let mut cur: *const CSmlValue = v;
522    for seg in p.split('.') {
523        if seg.is_empty() {
524            continue;
525        }
526        let c_seg = match std::ffi::CString::new(seg) {
527            Ok(c) => c,
528            Err(_) => return ptr::null(),
529        };
530        let next = sml_get(cur, c_seg.as_ptr());
531        if next.is_null() {
532            return ptr::null();
533        }
534        cur = next;
535    }
536    cur
537}
538
539/// 取数组第 `idx` 个元素(**借用**,不可释放)。
540#[cfg_attr(edge2024, unsafe(no_mangle))]
541#[cfg_attr(not(edge2024), no_mangle)]
542pub unsafe extern "C" fn sml_at(v: *const CSmlValue, idx: usize) -> *const CSmlValue {
543    if v.is_null() {
544        return ptr::null();
545    }
546    let inner = &(*(v as *const Value));
547    match inner {
548        Value::Array(a) => a
549            .get(idx)
550            .map(|x| x as *const Value as *const CSmlValue)
551            .unwrap_or(ptr::null()),
552        _ => ptr::null(),
553    }
554}
555
556/// 元素个数(数组长度 / 对象字段数);其它类型返回 0。
557#[cfg_attr(edge2024, unsafe(no_mangle))]
558#[cfg_attr(not(edge2024), no_mangle)]
559pub unsafe extern "C" fn sml_size(v: *const CSmlValue) -> usize {
560    if v.is_null() {
561        return 0;
562    }
563    match &(*(v as *const Value)) {
564        Value::Array(a) => a.len(),
565        Value::Object(m) => m.len(),
566        _ => 0,
567    }
568}
569
570/// 把字符串值拷进调用方缓冲区,返回不含 NUL 的长度;缓冲区不足时返回所需长度。
571#[cfg_attr(edge2024, unsafe(no_mangle))]
572#[cfg_attr(not(edge2024), no_mangle)]
573pub unsafe extern "C" fn sml_str_copy(
574    v: *const CSmlValue,
575    buf: *mut c_char,
576    buflen: usize,
577) -> usize {
578    if v.is_null() {
579        return 0;
580    }
581    let s = match &(*(v as *const Value)) {
582        Value::Str(s) => s.as_str(),
583        _ => return 0,
584    };
585    let need = s.len();
586    if buf.is_null() || buflen == 0 {
587        return need;
588    }
589    let n = need.min(buflen - 1);
590    let src = s.as_bytes();
591    for i in 0..n {
592        *buf.add(i) = src[i] as c_char;
593    }
594    *buf.add(n) = 0;
595    need
596}
597
598/// 字符串值的新分配副本(调用方 `sml_free_str` 释放);非字符串返回 NULL。
599#[cfg_attr(edge2024, unsafe(no_mangle))]
600#[cfg_attr(not(edge2024), no_mangle)]
601pub unsafe extern "C" fn sml_str_dup(v: *const CSmlValue) -> *mut c_char {
602    if v.is_null() {
603        return ptr::null_mut();
604    }
605    match &(*(v as *const Value)) {
606        Value::Str(s) => cstr(s),
607        _ => ptr::null_mut(),
608    }
609}
610
611/// 整数取值;非整数返回 0(用 [`sml_typeof`] 先判别类型)。
612#[cfg_attr(edge2024, unsafe(no_mangle))]
613#[cfg_attr(not(edge2024), no_mangle)]
614pub unsafe extern "C" fn sml_int_value(v: *const CSmlValue) -> i64 {
615    if v.is_null() {
616        return 0;
617    }
618    match &(*(v as *const Value)) {
619        Value::Int(i) => *i,
620        Value::Float(f) => *f as i64,
621        _ => 0,
622    }
623}
624
625/// 浮点取值;非数值返回 0.0。
626#[cfg_attr(edge2024, unsafe(no_mangle))]
627#[cfg_attr(not(edge2024), no_mangle)]
628pub unsafe extern "C" fn sml_real_value(v: *const CSmlValue) -> f64 {
629    if v.is_null() {
630        return 0.0;
631    }
632    match &(*(v as *const Value)) {
633        Value::Float(f) => *f,
634        Value::Int(i) => *i as f64,
635        _ => 0.0,
636    }
637}
638
639/// 布尔取值;非布尔返回 0。
640#[cfg_attr(edge2024, unsafe(no_mangle))]
641#[cfg_attr(not(edge2024), no_mangle)]
642pub unsafe extern "C" fn sml_bool_value(v: *const CSmlValue) -> c_int {
643    if v.is_null() {
644        return 0;
645    }
646    match &(*(v as *const Value)) {
647        Value::Bool(b) => {
648            if *b {
649                1
650            } else {
651                0
652            }
653        }
654        _ => 0,
655    }
656}
657
658// —— tomlc99 风格的单行便利取值 ——
659
660/// `sml_get_path` + [`sml_str_dup`] 的合体(调用方 `sml_free_str` 释放)。
661#[cfg_attr(edge2024, unsafe(no_mangle))]
662#[cfg_attr(not(edge2024), no_mangle)]
663pub unsafe extern "C" fn sml_str_in(
664    v: *const CSmlValue,
665    path: *const c_char,
666) -> *mut c_char {
667    let node = sml_get_path(v, path);
668    if node.is_null() {
669        return ptr::null_mut();
670    }
671    sml_str_dup(node)
672}
673
674/// `sml_get_path` + [`sml_int_value`],经 `ok` 回传是否取到(可为 NULL)。
675#[cfg_attr(edge2024, unsafe(no_mangle))]
676#[cfg_attr(not(edge2024), no_mangle)]
677pub unsafe extern "C" fn sml_int_in(
678    v: *const CSmlValue,
679    path: *const c_char,
680    ok: *mut c_int,
681) -> i64 {
682    let node = sml_get_path(v, path);
683    if node.is_null() {
684        if !ok.is_null() {
685            *ok = 0;
686        }
687        return 0;
688    }
689    let is_int = sml_typeof(node) == 2;
690    if !ok.is_null() {
691        *ok = if is_int { 1 } else { 0 };
692    }
693    sml_int_value(node)
694}
695
696/// `sml_get_path` + [`sml_bool_value`],经 `ok` 回传是否取到(可为 NULL)。
697#[cfg_attr(edge2024, unsafe(no_mangle))]
698#[cfg_attr(not(edge2024), no_mangle)]
699pub unsafe extern "C" fn sml_bool_in(
700    v: *const CSmlValue,
701    path: *const c_char,
702    ok: *mut c_int,
703) -> c_int {
704    let node = sml_get_path(v, path);
705    if node.is_null() {
706        if !ok.is_null() {
707            *ok = 0;
708        }
709        return 0;
710    }
711    let is_bool = sml_typeof(node) == 1;
712    if !ok.is_null() {
713        *ok = if is_bool { 1 } else { 0 };
714    }
715    sml_bool_value(node)
716}
717
718/// 把值树序列化为 SML 文本(调用方 `sml_free_str` 释放)。
719#[cfg_attr(edge2024, unsafe(no_mangle))]
720#[cfg_attr(not(edge2024), no_mangle)]
721pub unsafe extern "C" fn sml_dumps(v: *const CSmlValue, _flags: c_uint) -> *mut c_char {
722    if v.is_null() {
723        return ptr::null_mut();
724    }
725    cstr(&to_sml(&(*(v as *const Value))))
726}
727
728/// 返回该特性位对应的名字(静态字符串,无需释放);越界返回 NULL。
729///
730/// 这里刻意用 `match` 返回带 `\0` 的字面量:直接取 [`FEATURES`] 里的
731/// `&str` 无法保证 NUL 结尾,交给 C 会被 `printf("%s")` 越界读取。
732/// 顺序与 [`FEATURES`] 表严格对应,由 `tests/version.rs` 中的用例守护。
733#[cfg_attr(edge2024, unsafe(no_mangle))]
734#[cfg_attr(not(edge2024), no_mangle)]
735pub extern "C" fn sml_feature_name(bit: c_uint) -> *const c_char {
736    let s: &'static str = match bit {
737        0 => "bareword-string\0",
738        1 => "include\0",
739        2 => "env\0",
740        3 => "contract\0",
741        4 => "fragment\0",
742        5 => "top-level-array\0",
743        6 => "namespace\0",
744        7 => "implicit-ns\0",
745        8 => "multi-include\0",
746        9 => "glob-include\0",
747        10 => "regex-include\0",
748        11 => "ext-rewrite\0",
749        _ => return ptr::null(),
750    };
751    s.as_ptr() as *const c_char
752}
753
754/// 返回受支持特性的位掩码(可直接与 `SML_F_*` 按位与)。
755#[cfg_attr(edge2024, unsafe(no_mangle))]
756#[cfg_attr(not(edge2024), no_mangle)]
757pub extern "C" fn sml_features_mask() -> c_uint {
758    let mut m = 0u32;
759    for (i, _) in FEATURES.iter().enumerate() {
760        if i >= 32 {
761            break;
762        }
763        m |= 1u32 << i;
764    }
765    m
766}
767
768/// 库版本字符串(调用方 `sml_free_str` 释放)。
769#[cfg_attr(edge2024, unsafe(no_mangle))]
770#[cfg_attr(not(edge2024), no_mangle)]
771pub extern "C" fn sml_version_str() -> *mut c_char {
772    cstr(env!("CARGO_PKG_VERSION"))
773}
774
775// 供 `#[no_mangle]` 之外的内部代码引用,避免 `c_ulonglong` 触发未使用警告。
776#[allow(dead_code)]
777type _CUnsignedLongLong = c_ulonglong;
778
779// ---------------------------------------------------------------------------
780// 内部: JSON <-> Value (供 C-ABI 便捷桥)
781// ---------------------------------------------------------------------------
782
783pub(crate) fn jsonify(v: &Value) -> String {
784    fn esc(s: &str) -> String {
785        s.replace('\\', "\\\\").replace('"', "\\\"")
786    }
787    match v {
788        Value::Null => "null".into(),
789        Value::Bool(b) => b.to_string(),
790        Value::Int(i) => i.to_string(),
791        Value::Float(f) => f.to_string(),
792        Value::Str(s) => format!("\"{}\"", esc(s)),
793        Value::Array(a) => {
794            let parts: Vec<String> = a.iter().map(jsonify).collect();
795            format!("[{}]", parts.join(","))
796        }
797        Value::Object(m) => {
798            let parts: Vec<String> = m
799                .iter()
800                .map(|(k, val)| format!("\"{}\":{}", esc(k), jsonify(val)))
801                .collect();
802            format!("{{{}}}", parts.join(","))
803        }
804    }
805}
806
807pub(crate) fn json_to_value(s: &str) -> Option<Value> {
808    let bytes = s.as_bytes();
809    let mut i = 0;
810    let _n = bytes.len();
811    let mut skip_ws = |b: &[u8], i: &mut usize| {
812        while *i < b.len() && matches!(b[*i], b' ' | b'\t' | b'\n' | b'\r') {
813            *i += 1;
814        }
815    };
816    let mut parse_str = |b: &[u8], i: &mut usize| -> Option<String> {
817        skip_ws(b, i);
818        if *i >= b.len() || b[*i] != b'"' {
819            return None;
820        }
821        *i += 1;
822        let mut out = String::new();
823        while *i < b.len() {
824            let c = b[*i];
825            if c == b'"' {
826                *i += 1;
827                return Some(out);
828            }
829            if c == b'\\' && *i + 1 < b.len() {
830                *i += 1;
831                let e = b[*i];
832                out.push(match e {
833                    b'n' => '\n',
834                    b't' => '\t',
835                    b'r' => '\r',
836                    b'"' => '"',
837                    b'\\' => '\\',
838                    _ => e as char,
839                });
840            } else {
841                out.push(c as char);
842            }
843            *i += 1;
844        }
845        None
846    };
847    fn parse_val_impl(
848        b: &[u8],
849        i: &mut usize,
850        s: &str,
851        parse_str: &dyn Fn(&[u8], &mut usize) -> Option<String>,
852    ) -> Option<Value> {
853        let mut skip_ws = |b: &[u8], i: &mut usize| {
854            while *i < b.len() && matches!(b[*i], b' ' | b'\t' | b'\n' | b'\r') {
855                *i += 1;
856            }
857        };
858        skip_ws(b, i);
859        if *i >= b.len() {
860            return None;
861        }
862        match b[*i] {
863            b'{' => {
864                *i += 1;
865                let mut m = BTreeMap::new();
866                skip_ws(b, i);
867                if *i < b.len() && b[*i] == b'}' {
868                    *i += 1;
869                    return Some(Value::Object(m));
870                }
871                loop {
872                    skip_ws(b, i);
873                    let k = parse_str(b, i)?;
874                    skip_ws(b, i);
875                    if *i < b.len() && b[*i] == b':' {
876                        *i += 1;
877                    }
878                    let v = parse_val_impl(b, i, s, parse_str)?;
879                    m.insert(k, v);
880                    skip_ws(b, i);
881                    if *i < b.len() && b[*i] == b',' {
882                        *i += 1;
883                    } else if *i < b.len() && b[*i] == b'}' {
884                        *i += 1;
885                        break;
886                    }
887                }
888                Some(Value::Object(m))
889            }
890            b'[' => {
891                *i += 1;
892                let mut a = Vec::new();
893                skip_ws(b, i);
894                if *i < b.len() && b[*i] == b']' {
895                    *i += 1;
896                    return Some(Value::Array(a));
897                }
898                loop {
899                    a.push(parse_val_impl(b, i, s, parse_str)?);
900                    skip_ws(b, i);
901                    if *i < b.len() && b[*i] == b',' {
902                        *i += 1;
903                    } else if *i < b.len() && b[*i] == b']' {
904                        *i += 1;
905                        break;
906                    }
907                }
908                Some(Value::Array(a))
909            }
910            b'"' => parse_str(b, i).map(Value::Str),
911            b't' => {
912                if s[*i..].starts_with("true") {
913                    *i += 4;
914                    Some(Value::Bool(true))
915                } else {
916                    None
917                }
918            }
919            b'f' => {
920                if s[*i..].starts_with("false") {
921                    *i += 5;
922                    Some(Value::Bool(false))
923                } else {
924                    None
925                }
926            }
927            b'n' => {
928                if s[*i..].starts_with("null") {
929                    *i += 4;
930                    Some(Value::Null)
931                } else {
932                    None
933                }
934            }
935            _ => {
936                let start = *i;
937                while *i < b.len()
938                    && (b[*i].is_ascii_digit()
939                        || matches!(b[*i], b'-' | b'+' | b'.' | b'e' | b'E'))
940                {
941                    *i += 1;
942                }
943                let tok = s[start..*i].to_string();
944                if let Ok(iv) = tok.parse::<i64>() {
945                    Some(Value::Int(iv))
946                } else if let Ok(fv) = tok.parse::<f64>() {
947                    Some(Value::Float(fv))
948                } else {
949                    None
950                }
951            }
952        }
953    }
954    parse_val_impl(bytes, &mut i, s, &parse_str)
955}
956