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}