aether/builtins/
string.rs

1// src/builtins/string.rs
2//! String manipulation built-in functions
3
4use crate::evaluator::RuntimeError;
5use crate::value::Value;
6
7/// 分割字符串
8///
9/// # 功能
10/// 使用指定的分隔符将字符串分割成数组。
11///
12/// # 参数
13/// - `string`: String - 要分割的字符串
14/// - `separator`: String - 分隔符
15///
16/// # 返回值
17/// Array - 包含分割后的子字符串的数组
18///
19/// # 示例
20/// ```aether
21/// Set text "apple,banana,cherry"
22/// Set fruits Split(text, ",")      # ["apple", "banana", "cherry"]
23/// Set sentence "Hello World"
24/// Set words Split(sentence, " ")   # ["Hello", "World"]
25/// Set csv "a|b|c|d"
26/// Set parts Split(csv, "|")        # ["a", "b", "c", "d"]
27/// ```
28pub fn split(args: &[Value]) -> Result<Value, RuntimeError> {
29    if args.len() != 2 {
30        return Err(RuntimeError::WrongArity {
31            expected: 2,
32            got: args.len(),
33        });
34    }
35
36    match (&args[0], &args[1]) {
37        (Value::String(s), Value::String(sep)) => {
38            let parts: Vec<Value> = s
39                .split(sep.as_str())
40                .map(|p| Value::String(p.to_string()))
41                .collect();
42            Ok(Value::Array(parts))
43        }
44        _ => Err(RuntimeError::TypeErrorDetailed {
45            expected: "String, String".to_string(),
46            got: format!("{:?}, {:?}", args[0], args[1]),
47        }),
48    }
49}
50
51/// 转换为大写
52///
53/// # 功能
54/// 将字符串中的所有字母转换为大写形式。
55///
56/// # 参数
57/// - `string`: String - 要转换的字符串
58///
59/// # 返回值
60/// String - 大写形式的字符串
61///
62/// # 示例
63/// ```aether
64/// Set text "hello world"
65/// Set upper Upper(text)        # "HELLO WORLD"
66/// Set mixed "Hello123"
67/// Set upper Upper(mixed)       # "HELLO123"
68/// ```
69pub fn upper(args: &[Value]) -> Result<Value, RuntimeError> {
70    if args.len() != 1 {
71        return Err(RuntimeError::WrongArity {
72            expected: 1,
73            got: args.len(),
74        });
75    }
76
77    match &args[0] {
78        Value::String(s) => Ok(Value::String(s.to_uppercase())),
79        _ => Err(RuntimeError::TypeErrorDetailed {
80            expected: "String".to_string(),
81            got: format!("{:?}", args[0]),
82        }),
83    }
84}
85
86/// 转换为小写
87///
88/// # 功能
89/// 将字符串中的所有字母转换为小写形式。
90///
91/// # 参数
92/// - `string`: String - 要转换的字符串
93///
94/// # 返回值
95/// String - 小写形式的字符串
96///
97/// # 示例
98/// ```aether
99/// Set text "HELLO WORLD"
100/// Set lower Lower(text)        # "hello world"
101/// Set mixed "Hello123"
102/// Set lower Lower(mixed)       # "hello123"
103/// ```
104pub fn lower(args: &[Value]) -> Result<Value, RuntimeError> {
105    if args.len() != 1 {
106        return Err(RuntimeError::WrongArity {
107            expected: 1,
108            got: args.len(),
109        });
110    }
111
112    match &args[0] {
113        Value::String(s) => Ok(Value::String(s.to_lowercase())),
114        _ => Err(RuntimeError::TypeErrorDetailed {
115            expected: "String".to_string(),
116            got: format!("{:?}", args[0]),
117        }),
118    }
119}
120
121/// 去除首尾空白字符
122///
123/// # 功能
124/// 移除字符串开头和结尾的空白字符(空格、制表符、换行符等)。
125///
126/// # 参数
127/// - `string`: String - 要处理的字符串
128///
129/// # 返回值
130/// String - 去除首尾空白后的字符串
131///
132/// # 示例
133/// ```aether
134/// Set text "  hello world  "
135/// Set trimmed Trim(text)       # "hello world"
136/// Set text "\t\ntest\n\t"
137/// Set trimmed Trim(text)       # "test"
138/// ```
139pub fn trim(args: &[Value]) -> Result<Value, RuntimeError> {
140    if args.len() != 1 {
141        return Err(RuntimeError::WrongArity {
142            expected: 1,
143            got: args.len(),
144        });
145    }
146
147    match &args[0] {
148        Value::String(s) => Ok(Value::String(s.trim().to_string())),
149        _ => Err(RuntimeError::TypeErrorDetailed {
150            expected: "String".to_string(),
151            got: format!("{:?}", args[0]),
152        }),
153    }
154}
155
156/// 检查是否包含子字符串
157///
158/// # 功能
159/// 检查字符串是否包含指定的子字符串。
160///
161/// # 参数
162/// - `string`: String - 要检查的字符串
163/// - `substring`: String - 要查找的子字符串
164///
165/// # 返回值
166/// Boolean - 如果包含返回 `True`,否则返回 `False`
167///
168/// # 示例
169/// ```aether
170/// Set text "Hello World"
171/// Set has Contains(text, "World")    # True
172/// Set has Contains(text, "Python")   # False
173/// Set email "user@example.com"
174/// Set has Contains(email, "@")       # True
175/// ```
176pub fn contains(args: &[Value]) -> Result<Value, RuntimeError> {
177    if args.len() != 2 {
178        return Err(RuntimeError::WrongArity {
179            expected: 2,
180            got: args.len(),
181        });
182    }
183
184    match (&args[0], &args[1]) {
185        // String contains substring
186        (Value::String(s), Value::String(substr)) => {
187            Ok(Value::Boolean(s.contains(substr.as_str())))
188        }
189        // Array contains element
190        (Value::Array(arr), item) => {
191            for elem in arr.iter() {
192                if values_equal(elem, item) {
193                    return Ok(Value::Boolean(true));
194                }
195            }
196            Ok(Value::Boolean(false))
197        }
198        // Dict contains key
199        (Value::Dict(dict), Value::String(key)) => Ok(Value::Boolean(dict.contains_key(key))),
200        _ => Err(RuntimeError::TypeErrorDetailed {
201            expected: "(String, String) or (Array, Any) or (Dict, String)".to_string(),
202            got: format!("{:?}, {:?}", args[0], args[1]),
203        }),
204    }
205}
206
207// Helper function to compare values for equality
208fn values_equal(a: &Value, b: &Value) -> bool {
209    use Value::*;
210    match (a, b) {
211        (Number(a), Number(b)) => (a - b).abs() < f64::EPSILON,
212        (String(a), String(b)) => a == b,
213        (Boolean(a), Boolean(b)) => a == b,
214        (Null, Null) => true,
215        _ => false,
216    }
217}
218
219/// 检查是否以指定前缀开头
220///
221/// # 功能
222/// 检查字符串是否以指定的前缀开头。
223///
224/// # 参数
225/// - `string`: String - 要检查的字符串
226/// - `prefix`: String - 前缀字符串
227///
228/// # 返回值
229/// Boolean - 如果以该前缀开头返回 `True`,否则返回 `False`
230///
231/// # 示例
232/// ```aether
233/// Set filename "test.txt"
234/// Set starts StartsWith(filename, "test")    # True
235/// Set starts StartsWith(filename, "data")    # False
236/// Set url "https://example.com"
237/// Set isHttps StartsWith(url, "https://")    # True
238/// ```
239pub fn starts_with(args: &[Value]) -> Result<Value, RuntimeError> {
240    if args.len() != 2 {
241        return Err(RuntimeError::WrongArity {
242            expected: 2,
243            got: args.len(),
244        });
245    }
246
247    match (&args[0], &args[1]) {
248        (Value::String(s), Value::String(prefix)) => {
249            Ok(Value::Boolean(s.starts_with(prefix.as_str())))
250        }
251        _ => Err(RuntimeError::TypeErrorDetailed {
252            expected: "String, String".to_string(),
253            got: format!("{:?}, {:?}", args[0], args[1]),
254        }),
255    }
256}
257
258/// 检查是否以指定后缀结尾
259///
260/// # 功能
261/// 检查字符串是否以指定的后缀结尾。
262///
263/// # 参数
264/// - `string`: String - 要检查的字符串
265/// - `suffix`: String - 后缀字符串
266///
267/// # 返回值
268/// Boolean - 如果以该后缀结尾返回 `True`,否则返回 `False`
269///
270/// # 示例
271/// ```aether
272/// Set filename "document.pdf"
273/// Set isPdf EndsWith(filename, ".pdf")       # True
274/// Set isTxt EndsWith(filename, ".txt")       # False
275/// Set email "user@gmail.com"
276/// Set isGmail EndsWith(email, "@gmail.com")  # True
277/// ```
278pub fn ends_with(args: &[Value]) -> Result<Value, RuntimeError> {
279    if args.len() != 2 {
280        return Err(RuntimeError::WrongArity {
281            expected: 2,
282            got: args.len(),
283        });
284    }
285
286    match (&args[0], &args[1]) {
287        (Value::String(s), Value::String(suffix)) => {
288            Ok(Value::Boolean(s.ends_with(suffix.as_str())))
289        }
290        _ => Err(RuntimeError::TypeErrorDetailed {
291            expected: "String, String".to_string(),
292            got: format!("{:?}, {:?}", args[0], args[1]),
293        }),
294    }
295}
296
297/// 替换字符串中的所有匹配项
298///
299/// # 功能
300/// 将字符串中所有出现的子字符串替换为新的字符串。
301///
302/// # 参数
303/// - `string`: String - 原始字符串
304/// - `from`: String - 要被替换的子字符串
305/// - `to`: String - 替换后的新字符串
306///
307/// # 返回值
308/// String - 替换后的字符串
309///
310/// # 示例
311/// ```aether
312/// Set text "Hello World"
313/// Set replaced Replace(text, "World", "Aether")  # "Hello Aether"
314/// Set text "foo bar foo"
315/// Set replaced Replace(text, "foo", "baz")       # "baz bar baz"
316/// Set path "C:\\Users\\Name"
317/// Set fixed Replace(path, "\\", "/")             # "C:/Users/Name"
318/// ```
319pub fn replace(args: &[Value]) -> Result<Value, RuntimeError> {
320    if args.len() != 3 {
321        return Err(RuntimeError::WrongArity {
322            expected: 3,
323            got: args.len(),
324        });
325    }
326
327    match (&args[0], &args[1], &args[2]) {
328        (Value::String(s), Value::String(from), Value::String(to)) => {
329            Ok(Value::String(s.replace(from.as_str(), to.as_str())))
330        }
331        _ => Err(RuntimeError::TypeErrorDetailed {
332            expected: "String, String, String".to_string(),
333            got: format!("{:?}, {:?}, {:?}", args[0], args[1], args[2]),
334        }),
335    }
336}
337
338/// 重复字符串
339///
340/// # 功能
341/// 将字符串重复指定的次数。
342///
343/// # 参数
344/// - `string`: String - 要重复的字符串
345/// - `count`: Number - 重复次数(必须是非负整数)
346///
347/// # 返回值
348/// String - 重复后的字符串
349///
350/// # 错误
351/// - 重复次数为负数或非整数时抛出错误
352///
353/// # 示例
354/// ```aether
355/// Set str "Ha"
356/// Set laugh Repeat(str, 3)         # "HaHaHa"
357/// Set dash "-"
358/// Set line Repeat(dash, 10)        # "----------"
359/// Set space " "
360/// Set indent Repeat(space, 4)      # "    "
361/// ```
362pub fn repeat(args: &[Value]) -> Result<Value, RuntimeError> {
363    if args.len() != 2 {
364        return Err(RuntimeError::WrongArity {
365            expected: 2,
366            got: args.len(),
367        });
368    }
369
370    match (&args[0], &args[1]) {
371        (Value::String(s), Value::Number(n)) => {
372            if *n < 0.0 || n.fract() != 0.0 {
373                return Err(RuntimeError::InvalidOperation(format!(
374                    "Repeat count must be a non-negative integer, got {}",
375                    n
376                )));
377            }
378            let count = *n as usize;
379            Ok(Value::String(s.repeat(count)))
380        }
381        _ => Err(RuntimeError::TypeErrorDetailed {
382            expected: "String, Number".to_string(),
383            got: format!("{:?}, {:?}", args[0], args[1]),
384        }),
385    }
386}
387
388/// 字符串切片
389///
390/// # 功能
391/// 提取字符串的子串(基于字节索引)。
392///
393/// # 参数
394/// - `string`: String - 原始字符串
395/// - `start`: Number - 起始索引(包含,从0开始)
396/// - `end`: Number - 结束索引(不包含)
397///
398/// # 返回值
399/// String - 提取的子串
400///
401/// # 示例
402/// ```aether
403/// Set text "Hello World"
404/// Set sub StrSlice(text, 0, 5)     # "Hello"
405/// Set sub2 StrSlice(text, 6, 11)   # "World"
406/// Set sub3 StrSlice(text, 0, 1)    # "H"
407/// ```
408pub fn substr(args: &[Value]) -> Result<Value, RuntimeError> {
409    if args.len() != 3 {
410        return Err(RuntimeError::WrongArity {
411            expected: 3,
412            got: args.len(),
413        });
414    }
415
416    match (&args[0], &args[1], &args[2]) {
417        (Value::String(s), Value::Number(start), Value::Number(end)) => {
418            if start.fract() != 0.0 || end.fract() != 0.0 {
419                return Err(RuntimeError::InvalidOperation(
420                    "String indices must be integers".to_string(),
421                ));
422            }
423
424            let start_idx = *start as i64;
425            let end_idx = *end as i64;
426            let len = s.len() as i64;
427
428            // 处理负数索引
429            let start_idx = if start_idx < 0 {
430                (len + start_idx).max(0)
431            } else {
432                start_idx.min(len)
433            } as usize;
434
435            let end_idx = if end_idx < 0 {
436                (len + end_idx).max(0)
437            } else {
438                end_idx.min(len)
439            } as usize;
440
441            if start_idx > end_idx {
442                return Ok(Value::String(String::new()));
443            }
444
445            // 使用字节切片
446            let result = s.get(start_idx..end_idx).unwrap_or("").to_string();
447
448            Ok(Value::String(result))
449        }
450        _ => Err(RuntimeError::TypeErrorDetailed {
451            expected: "String, Number, Number".to_string(),
452            got: format!("{:?}, {:?}, {:?}", args[0], args[1], args[2]),
453        }),
454    }
455}
456
457/// 获取字符串长度
458///
459/// # 功能
460/// 返回字符串的字符数(注意:多字节字符按字符数计算)。
461///
462/// # 参数
463/// - `string`: String - 要测量的字符串
464///
465/// # 返回值
466/// Number - 字符串长度
467///
468/// # 示例
469/// ```aether
470/// Set text "Hello"
471/// Set length StrLen(text)          # 5
472/// Set chinese "你好"
473/// Set length2 StrLen(chinese)      # 2
474/// ```
475pub fn strlen(args: &[Value]) -> Result<Value, RuntimeError> {
476    if args.len() != 1 {
477        return Err(RuntimeError::WrongArity {
478            expected: 1,
479            got: args.len(),
480        });
481    }
482
483    match &args[0] {
484        Value::String(s) => Ok(Value::Number(s.len() as f64)),
485        _ => Err(RuntimeError::TypeErrorDetailed {
486            expected: "String".to_string(),
487            got: format!("{:?}", args[0]),
488        }),
489    }
490}
491
492/// 查找子串位置
493///
494/// # 功能
495/// 查找子串在字符串中首次出现的位置,未找到返回 -1。
496///
497/// # 参数
498/// - `string`: String - 原始字符串
499/// - `substring`: String - 要查找的子串
500///
501/// # 返回值
502/// Number - 子串的起始位置(从0开始),未找到返回 -1
503///
504/// # 示例
505/// ```aether
506/// Set text "Hello World"
507/// Set pos IndexOf(text, "World")   # 6
508/// Set pos2 IndexOf(text, "xyz")    # -1
509/// Set pos3 IndexOf(text, "l")      # 2 (第一个l)
510/// ```
511pub fn index_of(args: &[Value]) -> Result<Value, RuntimeError> {
512    if args.len() != 2 {
513        return Err(RuntimeError::WrongArity {
514            expected: 2,
515            got: args.len(),
516        });
517    }
518
519    match (&args[0], &args[1]) {
520        (Value::String(s), Value::String(substr)) => match s.find(substr.as_str()) {
521            Some(pos) => Ok(Value::Number(pos as f64)),
522            None => Ok(Value::Number(-1.0)),
523        },
524        _ => Err(RuntimeError::TypeErrorDetailed {
525            expected: "String, String".to_string(),
526            got: format!("{:?}, {:?}", args[0], args[1]),
527        }),
528    }
529}
530
531/// 获取指定位置的字符
532///
533/// # 功能
534/// 获取字符串中指定索引位置的字符。
535///
536/// # 参数
537/// - `string`: String - 原始字符串
538/// - `index`: Number - 字符位置(从0开始)
539///
540/// # 返回值
541/// String - 该位置的字符,索引越界返回空字符串
542///
543/// # 示例
544/// ```aether
545/// Set text "Hello"
546/// Set ch CharAt(text, 0)           # "H"
547/// Set ch2 CharAt(text, 4)          # "o"
548/// Set ch3 CharAt(text, 10)         # ""
549/// ```
550pub fn char_at(args: &[Value]) -> Result<Value, RuntimeError> {
551    if args.len() != 2 {
552        return Err(RuntimeError::WrongArity {
553            expected: 2,
554            got: args.len(),
555        });
556    }
557
558    match (&args[0], &args[1]) {
559        (Value::String(s), Value::Number(idx)) => {
560            if idx.fract() != 0.0 {
561                return Err(RuntimeError::InvalidOperation(
562                    "Index must be an integer".to_string(),
563                ));
564            }
565
566            let index = *idx as i64;
567            let len = s.len() as i64;
568
569            // 处理负数索引
570            let index = if index < 0 {
571                (len + index).max(0)
572            } else {
573                index.min(len)
574            } as usize;
575
576            let ch = s
577                .chars()
578                .nth(index)
579                .map(|c| c.to_string())
580                .unwrap_or_default();
581            Ok(Value::String(ch))
582        }
583        _ => Err(RuntimeError::TypeErrorDetailed {
584            expected: "String, Number".to_string(),
585            got: format!("{:?}, {:?}", args[0], args[1]),
586        }),
587    }
588}