1use crate::{
3 core::{
4 cons::Cons,
5 env::{Env, sym},
6 error::{Type, TypeError},
7 gc::{Context, Rt, Rto},
8 object::{
9 Function, Gc, HashTable, IntoObject, LispHashTable, LispString, LispVec, List,
10 ListType, NIL, Object, ObjectType, OptionalFlag, Symbol, WithLifetime,
11 },
12 },
13 data::aref,
14 library::filevercmp::filevercmp,
15 rooted_iter,
16};
17use anyhow::{Result, anyhow, bail, ensure};
18use base64::Engine;
19use fallible_iterator::FallibleIterator;
20use fallible_streaming_iterator::FallibleStreamingIterator;
21use rune_core::macros::{call, list, rebind, root};
22use rune_macros::defun;
23
24#[defun]
25fn identity(arg: Object) -> Object {
26 arg
27}
28
29pub(crate) fn slice_into_list<'ob>(
30 slice: &[Object<'ob>],
31 tail: Option<Object<'ob>>,
32 cx: &'ob Context,
33) -> Object<'ob> {
34 let from_end = slice.iter().rev();
35 from_end.fold(tail.into(), |acc, obj| Cons::new(*obj, acc, cx).into())
36}
37
38pub(crate) fn build_list<'ob, E>(
39 mut iter: impl Iterator<Item = Result<Object<'ob>, E>>,
40 cx: &'ob Context,
41) -> Result<Object<'ob>, E> {
42 let Some(first) = iter.next() else { return Ok(NIL) };
43 let head = Cons::new1(first?, cx);
44 let mut prev = head;
45 for elem in iter {
46 let new = Cons::new1(elem?, cx);
47 prev.set_cdr(new.into()).unwrap();
48 prev = new;
49 }
50 Ok(head.into())
51}
52
53#[defun]
54pub(crate) fn eq(obj1: Object, obj2: Object) -> bool {
55 obj1.ptr_eq(obj2)
56}
57
58#[defun]
59pub(crate) fn equal<'ob>(obj1: Object<'ob>, obj2: Object<'ob>) -> bool {
60 obj1 == obj2
61}
62
63#[defun]
64pub(crate) fn eql<'ob>(obj1: Object<'ob>, obj2: Object<'ob>) -> bool {
65 match (obj1.untag(), obj2.untag()) {
66 (ObjectType::Float(f1), ObjectType::Float(f2)) => f1.to_bits() == f2.to_bits(),
67 _ => obj1.ptr_eq(obj2),
68 }
69}
70
71#[defun]
72fn equal_including_properties<'ob>(o1: Object<'ob>, o2: Object<'ob>) -> bool {
73 equal(o1, o2)
75}
76
77#[defun]
78pub fn plist_get<'ob>(plist: Object<'ob>, prop: Object<'ob>) -> Result<Object<'ob>> {
79 let Ok(plist) = Gc::<ListType>::try_from(plist) else { return Ok(NIL) };
80 let mut iter = plist.elements();
82 while let Some(cur_prop) = iter.next() {
83 let Some(value) = iter.next() else { return Ok(NIL) };
84 if eq(cur_prop?, prop) {
85 return Ok(value?);
86 }
87 }
88 Ok(NIL)
89}
90
91#[defun]
92fn plist_member<'ob>(
93 plist: Object<'ob>,
94 prop: Object<'ob>,
95 predicate: Option<Object>,
96) -> Result<Object<'ob>> {
97 ensure!(predicate.is_none(), "plist-member predicate support not implemented");
98 let plist: List = plist.try_into()?;
99 for (idx, value) in plist.conses().enumerate() {
100 if idx % 2 != 0 {
101 continue;
102 }
103 let value = value?;
104 if eq(value.car(), prop) {
105 return Ok(value.into());
106 }
107 }
108 Ok(NIL)
109}
110
111#[defun]
112pub(crate) fn prin1_to_string(object: Object, _noescape: Option<Object>) -> String {
113 format!("{object}")
114}
115
116#[defun]
117fn string_to_multibyte(string: &LispString) -> &LispString {
118 string
120}
121
122#[defun]
123fn string_search(needle: &str, haystack: &str, start_pos: Option<usize>) -> Option<usize> {
124 let start = start_pos.unwrap_or(0);
125 haystack[start..].find(needle).map(|x| x + start)
126}
127
128#[defun]
129pub(crate) fn mapcar<'ob>(
130 function: &Rto<Function>,
131 sequence: &Rto<Object>,
132 env: &mut Rt<Env>,
133 cx: &'ob mut Context,
134) -> Result<Object<'ob>> {
135 let sequence = sequence.bind(cx);
136 match sequence.untag() {
137 ObjectType::NIL => Ok(NIL),
138 ObjectType::Cons(cons) => {
139 rooted_iter!(iter, cons, cx);
140 root!(outputs, new(Vec), cx);
141 while let Some(obj) = iter.next()? {
142 let output = call!(function, obj; env, cx)?;
143 outputs.push(output);
144 }
145 Ok(slice_into_list(Rt::bind_slice(outputs, cx), None, cx))
147 }
148 ObjectType::ByteFn(fun) => {
149 let len = fun.len();
150 root!(fun, cx);
151 root!(outputs, new(Vec), cx);
152 for i in 0..len {
153 let val = fun.bind(cx).index(i, cx).unwrap();
154 let output = call!(function, val; env, cx)?;
155 outputs.push(output);
156 }
157 Ok(slice_into_list(Rt::bind_slice(outputs, cx), None, cx))
159 }
160 _ => Err(TypeError::new(Type::Sequence, sequence).into()),
161 }
162}
163
164#[defun]
165pub(crate) fn mapc<'ob>(
166 function: &Rto<Function>,
167 sequence: &Rto<List>,
168 env: &mut Rt<Env>,
169 cx: &'ob mut Context,
170) -> Result<Object<'ob>> {
171 match sequence.untag(cx) {
172 ListType::Nil => Ok(NIL),
173 ListType::Cons(cons) => {
174 rooted_iter!(elements, cons, cx);
175 while let Some(elem) = elements.next()? {
176 call!(function, elem; env, cx)?;
177 }
178 Ok(sequence.bind(cx).into())
179 }
180 }
181}
182
183#[defun]
184pub(crate) fn mapcan<'ob>(
185 function: &Rto<Function>,
186 sequence: &Rto<Object>,
187 env: &mut Rt<Env>,
188 cx: &'ob mut Context,
189) -> Result<Object<'ob>> {
190 let mapped = mapcar(function, sequence, env, cx)?;
191 let mut lists = Vec::new();
192 for list in mapped.as_list()? {
193 lists.push(list?.try_into()?);
194 }
195 nconc(&lists)
196}
197
198#[defun]
199pub(crate) fn mapconcat(
200 function: &Rto<Function>,
201 sequence: &Rto<Object>,
202 seperator: Option<&Rto<Gc<&LispString>>>,
203 env: &mut Rt<Env>,
204 cx: &mut Context,
205) -> Result<String> {
206 let mapped = rebind!(mapcar(function, sequence, env, cx)?);
207 let sep = match seperator {
208 Some(sep) => sep.bind(cx).untag(),
209 _ => "",
210 };
211 let mut string = String::new();
212 let mut first = true;
213 for element in mapped.as_list()? {
214 if first {
215 first = false;
216 } else {
217 string.push_str(sep);
218 }
219 let element: &str = element?.try_into()?;
220 string.push_str(element);
221 }
222 Ok(string)
223}
224
225#[defun]
226pub(crate) fn nreverse(seq: List) -> Result<Object> {
227 let mut prev = NIL;
228 for tail in seq.conses() {
229 let tail = tail?;
230 tail.set_cdr(prev)?;
231 prev = tail.into();
232 }
233 Ok(prev)
234}
235
236#[defun]
237pub(crate) fn reverse<'ob>(seq: List, cx: &'ob Context) -> Result<Object<'ob>> {
238 let mut tail = NIL;
239 for elem in seq {
240 tail = Cons::new(elem?, tail, cx).into();
241 }
242 Ok(tail)
243}
244
245#[defun]
246pub(crate) fn nconc<'ob>(lists: &[List<'ob>]) -> Result<Object<'ob>> {
247 let mut tail: Option<&Cons> = None;
248 for list in lists {
249 if let Some(cons) = tail {
250 cons.set_cdr((*list).into())?;
251 }
252 if let Some(last) = list.conses().last() {
253 tail = Some(last?);
254 }
255 }
256
257 Ok(match lists.iter().find(|&&x| x != ListType::empty()) {
258 Some(x) => (*x).into(),
259 None => NIL,
260 })
261}
262
263fn join<'ob>(list: &mut Vec<Object<'ob>>, seq: List<'ob>) -> Result<()> {
264 if let Some(cons) = seq.cons() {
265 for elt in cons {
266 list.push(elt?);
267 }
268 }
269 Ok(())
270}
271
272#[defun]
273fn take<'ob>(n: i64, list: List<'ob>, cx: &'ob Context) -> Result<Object<'ob>> {
274 let Ok(n) = usize::try_from(n) else { return Ok(NIL) };
275 Ok(build_list(list.elements().take(n), cx)?)
276}
277
278#[defun]
279pub(crate) fn append<'ob>(
280 append: Object<'ob>,
281 sequences: &[Object<'ob>],
282 cx: &'ob Context,
283) -> Result<Object<'ob>> {
284 let mut list = Vec::new();
285 match append.untag() {
286 ObjectType::String(string) => {
287 for ch in string.chars() {
288 list.push((ch as i64).into());
289 }
290 }
291 ObjectType::ByteString(string) => {
292 for ch in string.iter() {
293 list.push((*ch as i64).into());
294 }
295 }
296 _ => join(&mut list, append.try_into()?)?,
297 }
298 for seq in sequences {
299 join(&mut list, (*seq).try_into()?)?;
300 }
301 Ok(slice_into_list(&list, None, cx))
303}
304
305#[defun]
306pub(crate) fn assq<'ob>(key: Object<'ob>, alist: List<'ob>) -> Result<Object<'ob>> {
307 for elem in alist {
308 if let Some(cons) = elem?.cons()
309 && eq(key, cons.car())
310 {
311 return Ok(cons.into());
312 }
313 }
314 Ok(NIL)
315}
316
317#[defun]
318fn rassq<'ob>(key: Object<'ob>, alist: List<'ob>) -> Result<Object<'ob>> {
319 for elem in alist {
320 if let Some(cons) = elem?.cons()
321 && eq(key, cons.cdr())
322 {
323 return Ok(cons.into());
324 }
325 }
326 Ok(NIL)
327}
328
329#[defun]
330pub(crate) fn assoc<'ob>(
331 key: &Rto<Object<'ob>>,
332 alist: &Rto<List<'ob>>,
333 testfn: Option<&Rto<Object>>,
334 cx: &'ob mut Context,
335 env: &mut Rt<Env>,
336) -> Result<Object<'ob>> {
337 match testfn {
338 Some(x) => {
339 let func: Function = x.bind(cx).try_into()?;
340 root!(func, cx);
341 rooted_iter!(iter, alist, cx);
342 while let Some(elem) = iter.next()? {
343 if let Some(cons) = elem.bind(cx).cons() {
344 let val = cons.car();
345 root!(cons, cx);
346 let result = call!(func, key, val; env, cx)?;
347 if result != NIL {
348 return Ok(cons.bind(cx).into());
349 }
350 }
351 }
352 }
353 None => {
354 let alist = alist.bind(cx);
355 let key = key.bind(cx);
356 for elem in alist {
357 if let Some(cons) = elem?.cons()
358 && equal(key, cons.car())
359 {
360 return Ok(cons.into());
361 }
362 }
363 }
364 };
365 Ok(NIL)
366}
367
368type EqFunc = for<'ob> fn(Object<'ob>, Object<'ob>) -> bool;
369
370#[defun]
371fn copy_alist<'ob>(alist: List<'ob>, cx: &'ob Context) -> Result<Object<'ob>> {
372 match alist.untag() {
373 ListType::Nil => Ok(NIL),
374 ListType::Cons(cons) => {
375 let first = copy_alist_elem(cons.car(), cx);
376 let head = Cons::new1(first, cx);
377 let mut tail = head;
378
379 for elem in cons.cdr().as_list()? {
380 let elem = copy_alist_elem(elem?, cx);
381 let copy = Cons::new1(elem, cx);
382 tail.set_cdr(copy.into()).unwrap();
383 tail = copy;
384 }
385 Ok(head.into())
386 }
387 }
388}
389
390fn copy_alist_elem<'ob>(elem: Object<'ob>, cx: &'ob Context) -> Object<'ob> {
391 match elem.untag() {
392 ObjectType::Cons(cons) => Cons::new(cons.car(), cons.cdr(), cx).into(),
393 _ => elem,
394 }
395}
396
397fn delete_from_list<'ob>(elt: Object<'ob>, list: List<'ob>, eq_fn: EqFunc) -> Result<Object<'ob>> {
398 let mut head = list.into();
399 let mut prev: Option<&'ob Cons> = None;
400 for tail in list.conses() {
401 let tail = tail?;
402 if eq_fn(tail.car(), elt) {
403 if let Some(prev_tail) = &mut prev {
404 prev_tail.set_cdr(tail.cdr())?;
405 } else {
406 head = tail.cdr();
407 }
408 } else {
409 prev = Some(tail);
410 }
411 }
412 Ok(head)
413}
414
415#[defun]
416pub(crate) fn delete<'ob>(elt: Object<'ob>, list: List<'ob>) -> Result<Object<'ob>> {
417 delete_from_list(elt, list, equal)
418}
419
420#[defun]
421pub(crate) fn delq<'ob>(elt: Object<'ob>, list: List<'ob>) -> Result<Object<'ob>> {
422 delete_from_list(elt, list, eq)
423}
424
425fn member_of_list<'ob>(elt: Object<'ob>, list: List<'ob>, eq_fn: EqFunc) -> Result<Object<'ob>> {
426 let val = list.conses().fallible().find(|x| Ok(eq_fn(x.car(), elt)))?;
427 match val {
428 Some(elem) => Ok(elem.into()),
429 None => Ok(NIL),
430 }
431}
432
433#[defun]
434pub(crate) fn memq<'ob>(elt: Object<'ob>, list: List<'ob>) -> Result<Object<'ob>> {
435 member_of_list(elt, list, eq)
436}
437
438#[defun]
439pub(crate) fn memql<'ob>(elt: Object<'ob>, list: List<'ob>) -> Result<Object<'ob>> {
440 member_of_list(elt, list, eql)
441}
442
443#[defun]
444pub(crate) fn member<'ob>(elt: Object<'ob>, list: List<'ob>) -> Result<Object<'ob>> {
445 member_of_list(elt, list, equal)
446}
447
448#[defun]
450fn sort<'ob>(
451 seq: &Rto<List>,
452 predicate: &Rto<Function>,
453 env: &mut Rt<Env>,
454 cx: &'ob mut Context,
455) -> Result<Object<'ob>> {
456 let vec: Vec<_> = seq.bind(cx).elements().fallible().collect()?;
457 if vec.len() <= 1 {
458 return Ok(seq.bind(cx).into());
459 }
460 root!(vec, cx);
461 let mut err = None;
462 vec.sort_by(|a, b| {
464 use std::cmp::Ordering;
465 if err.is_some() {
466 return Ordering::Equal;
469 }
470 let result = call!(predicate, a, b; env, cx);
471 match result {
472 Ok(x) if x == NIL => Ordering::Greater,
473 Ok(_) => Ordering::Less,
474 Err(e) => {
475 err = Some(e.into());
476 Ordering::Equal
477 }
478 }
479 });
480 match err {
481 Some(e) => Err(e),
482 None => Ok(slice_into_list(Rt::bind_slice(vec, cx), None, cx)),
483 }
484}
485
486#[defun]
487pub(crate) fn defvaralias<'ob>(
488 new_alias: Symbol<'ob>,
489 _base_variable: Symbol,
490 _docstring: Option<&str>,
491) -> Symbol<'ob> {
492 new_alias
494}
495
496#[defun]
497pub(crate) fn featurep(_feature: Symbol, _subfeature: Option<Symbol>) {}
499
500#[defun]
501pub(crate) fn require<'ob>(
502 feature: &Rto<Gc<Symbol>>,
503 filename: Option<&Rto<Gc<&LispString>>>,
504 noerror: OptionalFlag,
505 env: &mut Rt<Env>,
506 cx: &'ob mut Context,
507) -> Result<Symbol<'ob>> {
508 let feat = unsafe { feature.untag(cx).with_lifetime() };
510 if crate::data::FEATURES.lock().unwrap().contains(&feat) {
511 return Ok(feature.untag(cx));
512 }
513 let file = match filename {
514 Some(file) => file.untag(cx),
515 None => feature.untag(cx).get().name(),
516 };
517 let file = file.into_obj(cx);
518 root!(file, cx);
519 match crate::lread::load(file, noerror, None, cx, env) {
520 Ok(_) => Ok(feature.untag(cx)),
521 Err(e) => Err(e),
522 }
523}
524
525#[defun]
526pub(crate) fn concat(sequences: &[Object]) -> Result<String> {
527 let mut concat = String::new();
528 for elt in sequences {
529 match elt.untag() {
530 ObjectType::String(string) => concat += string,
531 ObjectType::NIL => continue,
532 _ => bail!("Currently only concatenating strings are supported"),
533 }
534 }
535 Ok(concat)
536}
537
538#[defun]
539pub(crate) fn vconcat<'ob>(sequences: &[Object], cx: &'ob Context) -> Result<Gc<&'ob LispVec>> {
540 let mut concated: Vec<Object> = Vec::new();
541 for elt in sequences {
542 match elt.untag() {
543 ObjectType::String(string) => {
545 for chr in string.chars() {
546 concated.push((chr as i64).into());
547 }
548 }
549 ObjectType::Cons(cons) => {
550 for x in cons {
551 concated.push(x?);
552 }
553 }
554 ObjectType::Vec(vec) => {
555 for x in vec.iter() {
556 concated.push(x.get());
557 }
558 }
559 ObjectType::NIL => {}
560 obj => bail!(TypeError::new(Type::Sequence, obj)),
561 }
562 }
563 Ok(concated.into_obj(cx))
564}
565
566#[defun]
567pub(crate) fn length(sequence: Object) -> Result<usize> {
568 let size = match sequence.untag() {
569 ObjectType::Cons(x) => x.elements().len()?,
570 ObjectType::Vec(x) => x.len(),
571 ObjectType::String(x) => x.len(),
572 ObjectType::ByteString(x) => x.len(),
573 ObjectType::ByteFn(x) => x.len(),
574 ObjectType::NIL => 0,
575 obj => bail!(TypeError::new(Type::Sequence, obj)),
576 };
577 Ok(size)
578}
579
580#[defun]
581pub(crate) fn safe_length(sequence: Object) -> usize {
582 match sequence.untag() {
583 ObjectType::Cons(cons) => cons.elements().take_while(|x| x.is_ok()).count(),
584 _ => 0,
585 }
586}
587
588#[defun]
589pub(crate) fn proper_list_p(object: Object) -> Option<usize> {
590 match object.untag() {
591 ObjectType::Cons(x) => x.elements().len().ok(),
592 ObjectType::NIL => Some(0),
593 _ => None,
594 }
595}
596
597#[defun]
598pub(crate) fn nth(n: usize, list: List) -> Result<Object> {
599 Ok(list.elements().fallible().nth(n)?.unwrap_or_default())
600}
601
602#[defun]
603pub(crate) fn nthcdr(n: usize, list: List) -> Result<Object> {
604 let ListType::Cons(mut cons) = list.untag() else { return Ok(NIL) };
605 let mut tail = list.as_obj_copy();
606 for _ in 0..n {
607 tail = cons.cdr();
608 if let Some(next) = tail.cons() {
609 cons = next;
610 } else {
611 break;
612 }
613 }
614 Ok(tail)
615}
616
617#[defun]
618pub(crate) fn elt<'ob>(sequence: Object<'ob>, n: usize, cx: &'ob Context) -> Result<Object<'ob>> {
619 match sequence.untag() {
620 ObjectType::Cons(x) => nth(n, x.into()),
621 ObjectType::NIL => Ok(NIL),
622 ObjectType::Vec(x) => aref(x.into(), n, cx),
623 ObjectType::Record(x) => aref(x.into(), n, cx),
624 ObjectType::String(x) => aref(x.into(), n, cx),
625 ObjectType::ByteFn(x) => aref(x.into(), n, cx),
626 other => Err(TypeError::new(Type::Sequence, other).into()),
627 }
628}
629
630#[defun]
631pub(crate) fn string_equal<'ob>(s1: Object<'ob>, s2: Object<'ob>) -> Result<bool> {
632 let s1 = match s1.untag() {
633 ObjectType::String(x) => x.as_bytes(),
634 ObjectType::ByteString(x) => x.inner(),
635 ObjectType::Symbol(x) => x.get().as_bytes(),
636 _ => bail!(TypeError::new(Type::String, s1)),
637 };
638 let s2 = match s2.untag() {
639 ObjectType::String(x) => x.as_bytes(),
640 ObjectType::ByteString(x) => x.inner(),
641 ObjectType::Symbol(x) => (x.get()).as_bytes(),
642 _ => bail!(TypeError::new(Type::String, s2)),
643 };
644
645 Ok(s1 == s2)
646}
647
648#[defun]
649pub(crate) fn compare_strings<'ob>(
650 string1: &str,
651 start1: Object<'ob>,
652 end1: Object<'ob>,
653 string2: &str,
654 start2: Object<'ob>,
655 end2: Object<'ob>,
656 ignore_case: OptionalFlag,
657) -> Result<Object<'ob>> {
658 let start1 = match start1.untag() {
659 ObjectType::Int(x) => x,
660 ObjectType::NIL => 0,
661 _ => bail!(TypeError::new(Type::Int, start1)),
662 };
663 let end1 = match end1.untag() {
664 ObjectType::Int(x) => x,
665 ObjectType::NIL => string1.chars().count() as i64,
666 _ => bail!(TypeError::new(Type::Int, end1)),
667 };
668
669 if end1 < start1 {
670 bail!("Args out of range: {string1}, {start1}, {end1}");
671 }
672
673 let s1 = string1.chars().skip(start1 as usize).take((end1 - start1) as usize);
674
675 let start2 = match start2.untag() {
676 ObjectType::Int(x) => x,
677 ObjectType::NIL => 0,
678 _ => bail!(TypeError::new(Type::Int, start2)),
679 };
680 let end2 = match end2.untag() {
681 ObjectType::Int(x) => x,
682 ObjectType::NIL => string2.chars().count() as i64,
683 _ => bail!(TypeError::new(Type::Int, end2)),
684 };
685
686 if end2 < start2 {
687 bail!("Args out of range: {string2}, {start2}, {end2}");
688 }
689 let s2 = string2.chars().skip(start2 as usize).take((end2 - start2) as usize);
691
692 let mut leading = 1;
693 for (c1, c2) in s1.zip(s2) {
694 let (c1, c2) = if ignore_case.is_some() {
695 (c1.to_uppercase().next().unwrap(), c2.to_uppercase().next().unwrap())
697 } else {
698 (c1, c2)
699 };
700
701 match c1.cmp(&c2) {
702 std::cmp::Ordering::Less => return Ok((-leading).into()),
703 std::cmp::Ordering::Greater => return Ok(leading.into()),
704 std::cmp::Ordering::Equal => {}
705 }
706 leading += 1;
707 }
708
709 Ok(true.into())
710}
711
712#[defun]
713pub(crate) fn string_distance(string1: &str, string2: &str, bytecompare: OptionalFlag) -> i64 {
714 if bytecompare.is_none() {
715 levenshtein_distance(string1.chars(), string2.chars())
716 } else {
717 levenshtein_distance(string1.as_bytes().iter(), string2.as_bytes().iter())
718 }
719}
720
721#[inline]
722pub(crate) fn levenshtein_distance<T: PartialEq, I: Iterator<Item = T>>(s1: I, s2: I) -> i64 {
723 use std::cmp::min;
724 let s = s1.collect::<Vec<_>>();
726 let t = s2.collect::<Vec<_>>();
727 let mut v0 = vec![0; t.len() + 1];
728 let mut v1 = vec![0; t.len() + 1];
729
730 for (i, v0i) in v0.iter_mut().enumerate() {
732 *v0i = i as i64;
733 }
734
735 for (i, si) in s.iter().enumerate() {
737 v1[0] = i as i64 + 1;
740
741 for (j, tj) in t.iter().enumerate() {
743 let deletion_cost = v0[j + 1] + 1;
744 let insertion_cost = v1[j] + 1;
745 let substitution_cost = v0[j] + if si == tj { 0 } else { 1 };
746 v1[j + 1] = min(deletion_cost, min(insertion_cost, substitution_cost));
747 }
748
749 std::mem::swap(&mut v0, &mut v1);
751 }
752 v0[t.len()]
754}
755
756#[defun]
757pub(crate) fn string_bytes(string: &str) -> usize {
758 string.len()
759}
760
761#[derive(Debug, Clone, Copy)]
762pub(crate) enum StringOrChar<'ob> {
763 String(&'ob str),
764 Char(u64),
765}
766
767impl<'ob> TryFrom<Object<'ob>> for StringOrChar<'ob> {
768 type Error = TypeError;
769
770 fn try_from(obj: Object<'ob>) -> Result<Self, Self::Error> {
771 match obj.untag() {
772 ObjectType::String(s) => Ok(Self::String(s)),
773 ObjectType::Int(c) if c >= 0 => Ok(Self::Char(c as u64)),
774 _ => Err(TypeError::new(Type::StringOrChar, obj)),
775 }
776 }
777}
778
779impl<'ob> From<&'ob str> for StringOrChar<'ob> {
780 fn from(s: &'ob str) -> Self {
781 Self::String(s)
782 }
783}
784
785impl From<char> for StringOrChar<'_> {
786 fn from(s: char) -> Self {
787 Self::Char(s as u64)
788 }
789}
790
791#[derive(Debug, Clone, Copy)]
792pub(crate) struct StringOrSymbol<'ob>(&'ob str);
793
794impl<'ob> TryFrom<Object<'ob>> for StringOrSymbol<'ob> {
795 type Error = TypeError;
796
797 fn try_from(obj: Object<'ob>) -> Result<Self, Self::Error> {
798 match obj.untag() {
799 ObjectType::String(x) => Ok(Self(x)),
800 ObjectType::Symbol(x) => Ok(Self(x.get().name())),
801 _ => Err(TypeError::new(Type::String, obj)),
802 }
803 }
804}
805
806#[defun]
807pub(crate) fn string_lessp<'ob>(
808 string1: StringOrSymbol<'ob>,
809 string2: StringOrSymbol<'ob>,
810) -> Result<bool> {
811 for (c1, c2) in string1.0.chars().zip(string2.0.chars()) {
812 if c1 != c2 {
813 return Ok(c1 < c2);
814 }
815 }
816 Ok(string1.0.len() < string2.0.len())
817}
818
819#[defun]
820pub(crate) fn string_version_lessp<'ob>(
821 string1: StringOrSymbol<'ob>,
822 string2: StringOrSymbol<'ob>,
823) -> Result<bool> {
824 Ok(filevercmp(string1.0.as_bytes(), string2.0.as_bytes()) == std::cmp::Ordering::Less)
825}
826
827#[defun]
828pub(crate) fn clear_string(string: &LispString) -> Result<Object<'_>> {
829 string.clear();
830 Ok(NIL)
831}
832
833defsym!(KW_TEST);
838defsym!(KW_DOCUMENTATION);
839
840#[defun]
841pub(crate) fn make_hash_table<'ob>(
842 keyword_args: &[Object<'ob>],
843 cx: &'ob Context,
844) -> Result<Object<'ob>> {
845 let kw_test_pos = keyword_args.iter().step_by(2).position(|&x| x == sym::KW_TEST);
846 if let Some(i) = kw_test_pos {
847 let Some(val) = keyword_args.get((i * 2) + 1) else {
848 bail!("Missing keyword value for :test")
849 };
850 if *val != sym::EQ && *val != sym::EQUAL && *val != sym::EQL {
851 bail!("only `eq' and `equal' keywords support for make-hash-table :test. Found {val}");
853 }
854 }
855 let map = HashTable::with_hasher(std::hash::BuildHasherDefault::default());
857 Ok(cx.add(map))
858}
859
860#[defun]
861pub(crate) fn hash_table_p(obj: Object) -> bool {
862 matches!(obj.untag(), ObjectType::HashTable(_))
863}
864
865#[defun]
866pub(crate) fn gethash<'ob>(
867 key: Object<'ob>,
868 table: &'ob LispHashTable,
869 dflt: Option<Object<'ob>>,
870) -> Option<Object<'ob>> {
871 match table.get(key) {
872 Some(x) => Some(x),
873 None => dflt,
874 }
875}
876
877#[defun]
878pub(crate) fn puthash<'ob>(
879 key: Object<'ob>,
880 value: Object<'ob>,
881 table: &'ob LispHashTable,
882) -> Object<'ob> {
883 table.insert(key, value);
884 value
885}
886
887#[defun]
888fn remhash(key: Object, table: &LispHashTable) -> Result<()> {
889 let Some(idx) = table.get_index_of(key) else { return Ok(()) };
890 let iter_idx = table.get_iter_index();
894 if idx < iter_idx {
895 table.set_iter_index(iter_idx - 1);
896 }
897 table.shift_remove(key);
899 Ok(())
900}
901
902#[defun]
903fn maphash(
904 function: &Rto<Function>,
905 table: &Rto<Gc<&LispHashTable>>,
906 env: &mut Rt<Env>,
907 cx: &mut Context,
908) -> Result<bool> {
909 let loop_idx = |table: &LispHashTable| {
910 let end = table.len();
911 let idx = table.get_iter_index();
912 table.set_iter_index(idx + 1);
913 if idx >= end { None } else { Some(idx) }
914 };
915
916 loop {
917 let (key, val) = {
918 let table = table.untag(cx);
919 let Some(idx) = loop_idx(table) else { break };
920 table.get_index(idx).unwrap()
921 };
922 let result = call!(function, key, val; env, cx);
923 if let Err(e) = result {
924 table.untag(cx).set_iter_index(0);
925 return Err(e.into());
926 }
927 }
928 table.untag(cx).set_iter_index(0);
929 Ok(false)
930}
931
932#[defun]
933fn copy_sequence<'ob>(arg: Object<'ob>, cx: &'ob Context) -> Result<Object<'ob>> {
934 match arg.untag() {
935 ObjectType::Vec(x) => Ok(cx.add(x.to_vec())),
936 ObjectType::Cons(x) => {
937 let mut elements = Vec::new();
939 let mut tail = None;
940 for cons in x.conses() {
941 let cons = cons?;
942 elements.push(cons.car());
943 if !matches!(cons.cdr().untag(), ObjectType::Cons(_)) {
944 tail = Some(cons.cdr());
945 }
946 }
947 Ok(slice_into_list(&elements, tail, cx))
948 }
949 ObjectType::String(x) => Ok(cx.add(x.to_owned())),
950 ObjectType::NIL => Ok(NIL),
951 _ => Err(TypeError::new(Type::Sequence, arg).into()),
952 }
953}
954
955#[defun]
956fn substring(string: &str, from: Option<usize>, to: Option<usize>) -> Result<String> {
957 if from.unwrap_or_default() > string.len() || to.unwrap_or_default() > string.len() {
958 bail!("substring args out of range for {string} : {from:?} {to:?}");
959 }
960 let new_string = match (from, to) {
961 (None, None) => string,
962 (None, Some(t)) => &string[..t],
963 (Some(f), None) => &string[f..],
964 (Some(f), Some(t)) => {
965 let range = if f > t { t..f } else { f..t };
966 &string[range]
967 }
968 };
969 Ok(new_string.to_owned())
970}
971
972defsym!(MD5);
973defsym!(SHA1);
974defsym!(SHA224);
975defsym!(SHA256);
976defsym!(SHA384);
977defsym!(SHA512);
978
979#[defun]
980fn secure_hash_algorithms<'ob>(cx: &'ob Context) -> Object<'ob> {
981 list![sym::MD5, sym::SHA1, sym::SHA224, sym::SHA256, sym::SHA384, sym::SHA512; cx]
986}
987
988#[defun]
989fn enable_debug() -> bool {
990 crate::debug::enable_debug();
991 true
992}
993
994#[defun]
995fn debug_enabled() -> bool {
996 crate::debug::debug_enabled()
997}
998
999#[defun]
1000fn disable_debug() -> bool {
1001 crate::debug::disable_debug();
1002 false
1003}
1004
1005#[defun]
1010fn base64_encode_string(string: &str, line_break: OptionalFlag) -> Result<String> {
1011 if string.is_ascii() {
1012 Ok(base64_encode(string, line_break.is_some(), true, false))
1013 } else {
1014 Err(anyhow!("Multibyte character in data for base64 encoding"))
1015 }
1016}
1017
1018#[defun]
1024fn base64url_encode_string(string: &str, no_pad: OptionalFlag) -> Result<String> {
1025 if string.is_ascii() {
1026 Ok(base64_encode(string, false, no_pad.is_none(), true))
1027 } else {
1028 Err(anyhow!("Multibyte character in data for base64 encoding"))
1029 }
1030}
1031
1032fn base64_encode(string: &str, _line_break: bool, pad: bool, base64url: bool) -> String {
1033 let config = base64::engine::GeneralPurposeConfig::new().with_encode_padding(pad);
1034 let alphabets = if base64url { base64::alphabet::URL_SAFE } else { base64::alphabet::STANDARD };
1035 let engine = base64::engine::GeneralPurpose::new(&alphabets, config);
1036 engine.encode(string)
1037}
1038
1039#[cfg(test)]
1040mod test {
1041 use crate::{
1042 assert_elprop, fns::levenshtein_distance, interpreter::assert_lisp,
1043 library::elprop::arb_custom_string,
1044 };
1045 use proptest::prelude::*;
1046
1047 #[test]
1048 fn test_base64_encode_string() {
1049 assert_lisp("(base64-encode-string \"hello\")", "\"aGVsbG8=\"");
1050 assert_lisp(
1051 "(base64-encode-string \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum\")",
1052 "\"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVt\"",
1053 );
1054 }
1056
1057 #[test]
1058 #[cfg_attr(miri, ignore)]
1059 fn test_base64_encode_string_prop() {
1060 proptest! {|(string in arb_custom_string("[\x00-\x7F]*"))| {
1061 assert_elprop![r#"(base64-encode-string {} t)"#, string];
1062 }};
1063 }
1064
1065 #[test]
1066 fn test_take() {
1067 assert_lisp("(take 2 '(1 2 3 4))", "(1 2)");
1068 }
1069
1070 #[test]
1071 fn test_delq() {
1072 assert_lisp("(delq 1 '(1 2 3 1 4 1))", "(2 3 4)");
1073 assert_lisp("(delq t '(t t t))", "nil");
1074 }
1075
1076 #[test]
1077 fn test_nthcdr() {
1078 assert_lisp("(nthcdr 0 nil)", "nil");
1079 assert_lisp("(nthcdr 3 nil)", "nil");
1080 assert_lisp("(nthcdr 0 '(1 2 3))", "(1 2 3)");
1081 assert_lisp("(nthcdr 1 '(1 2 3))", "(2 3)");
1082 assert_lisp("(nthcdr 2 '(1 2 3))", "(3)");
1083 assert_lisp("(nthcdr 3 '(1 2 3))", "nil");
1084 assert_lisp("(nthcdr 1 '(1 . 2))", "2");
1085 assert_lisp("(nthcdr 2 '(1 2 . 3))", "3");
1086 }
1087
1088 #[test]
1089 fn test_reverse() {
1090 assert_lisp("(nreverse nil)", "nil");
1091 assert_lisp("(nreverse '(1))", "(1)");
1092 assert_lisp("(nreverse '(1 2))", "(2 1)");
1093 assert_lisp("(nreverse '(1 2 3))", "(3 2 1)");
1094 assert_lisp("(nreverse '(1 2 3 4))", "(4 3 2 1)");
1095 }
1096
1097 #[test]
1098 fn test_nconc() {
1099 assert_lisp("(nconc nil)", "nil");
1100 assert_lisp("(nconc '(1 2))", "(1 2)");
1101 assert_lisp("(nconc '(1 2) '(3 4))", "(1 2 3 4)");
1102 assert_lisp("(nconc '(1 2) '(3 4) '(5 6))", "(1 2 3 4 5 6)");
1103 assert_lisp("(nconc nil '(1 2))", "(1 2)");
1104 assert_lisp("(nconc '(1 2) nil)", "(1 2)");
1105 }
1106
1107 #[test]
1108 fn test_append() {
1109 assert_lisp("(append \"hello\")", "(104 101 108 108 111)");
1110 }
1111
1112 #[test]
1113 fn test_assq() {
1114 assert_lisp("(assq 5 '((1 . 2) (3 . 4) (5 . 6)))", "(5 . 6)");
1115 assert_lisp("(assq 6 '((1 . 2) (3 . 4) (5 . 6)))", "nil");
1116 }
1117
1118 #[test]
1119 fn test_string_equal() {
1120 assert_lisp("(string-equal \"hello\" \"hello\")", "t");
1121 assert_lisp("(string-equal \"hello\" \"world\")", "nil");
1122 }
1123
1124 #[test]
1125 fn test_compare_strings() {
1126 assert_lisp("(compare-strings \"hello\" 0 6 \"hello\" 0 6)", "t");
1127 assert_lisp("(compare-strings \"hello\" 0 6 \"world\" 0 6)", "-1");
1128 assert_lisp("(compare-strings \"hello\" 0 6 \"HELLO\" 0 6 t)", "t");
1129 }
1130
1131 #[test]
1132 fn test_string_distance() {
1133 assert_lisp("(string-distance \"hello\" \"hello\")", "0");
1134 assert_lisp("(string-distance \"hello\" \"jello\")", "1");
1135 assert_lisp("(string-distance \"hello\" \"world\")", "4");
1136 }
1137
1138 #[test]
1139 fn test_levenstein_distance() {
1140 assert_eq!(4, levenshtein_distance("hello".chars(), "world".chars()));
1141 assert_eq!(3, levenshtein_distance("kitten".chars(), "sitting".chars()));
1142 assert_eq!(2, levenshtein_distance("book".chars(), "back".chars()));
1143 assert_eq!(5, levenshtein_distance("table".chars(), "dinner".chars()));
1144 assert_eq!(2, levenshtein_distance("person".chars(), "pardon".chars()));
1145 assert_eq!(1, levenshtein_distance("person".chars(), "persons".chars()));
1146 assert_eq!(4, levenshtein_distance("book".chars(), "".chars()));
1147 assert_eq!(4, levenshtein_distance("".chars(), "book".chars()));
1148 assert_eq!(0, levenshtein_distance("".chars(), "".chars()));
1149 assert_eq!(2, levenshtein_distance("Späße".chars(), "Spaß".chars()));
1150 assert_eq!(5, levenshtein_distance("さようなら".chars(), "こんにちは".chars()));
1151 assert_eq!(1, levenshtein_distance("さようなら".chars(), "さようなう".chars()));
1152 assert_eq!(4, levenshtein_distance("こんにちは".chars(), "こんにちは abc".chars()));
1153 assert_eq!(1, levenshtein_distance("༆༃ʘ".chars(), "༆˥ʘ".chars()));
1154 }
1155
1156 #[test]
1157 fn test_string_lessp() {
1158 assert_lisp("(string-lessp \"less\" \"less\")", "nil");
1161 assert_lisp("(string-lessp \"les\" \"less\")", "t");
1163 assert_lisp("(string-lessp \"less\" \"les\")", "nil");
1164 assert_lisp("(string-lessp \"abc\" \"bcd\")", "t");
1166 assert_lisp("(string-lessp \"abx\" \"abcd\")", "nil");
1167
1168 assert_lisp("(string-lessp 'less 'less)", "nil");
1171 assert_lisp("(string-lessp 'les 'less)", "t");
1173 assert_lisp("(string-lessp 'less 'les)", "nil");
1174 assert_lisp("(string-lessp 'abc 'bcd)", "t");
1176
1177 assert_lisp("(string-lessp 'less \"less\")", "nil");
1179 assert_lisp("(string-lessp 'les \"less\")", "t");
1180 }
1181
1182 #[test]
1183 #[cfg(not(miri))] fn test_string_version_lessp() {
1185 assert_lisp("(string-version-lessp \"less\" \"less\")", "nil");
1188 assert_lisp("(string-version-lessp \"les\" \"less\")", "t");
1190 assert_lisp("(string-version-lessp \"less\" \"les\")", "nil");
1191 assert_lisp("(string-version-lessp \"abc\" \"bcd\")", "t");
1193 assert_lisp("(string-version-lessp \"less1\" \"less1\")", "nil");
1195 assert_lisp("(string-version-lessp \"100a\" \"100b\")", "t");
1196 assert_lisp("(string-version-lessp \"1less\" \"less1\")", "t");
1198 assert_lisp("(string-version-lessp \"less12\" \"less1\")", "nil");
1200 assert_lisp("(string-version-lessp \"less1\" \"less12\")", "t");
1202 assert_lisp("(string-version-lessp \"133less1\" \"less12\")", "t");
1204 assert_lisp("(string-version-lessp \"112a\" \"512a\")", "t");
1206 assert_lisp("(string-version-lessp \"101a\" \"100b\")", "nil");
1208 assert_lisp("(string-version-lessp \"10\" \"0100\")", "t");
1210 assert_lisp("(string-version-lessp \"10a100\" \"0100a\")", "t");
1212 assert_lisp("(string-version-lessp \"a100\" \"0100a\")", "nil");
1213 assert_lisp("(string-version-lessp \"01\" \"1\")", "nil");
1214 assert_lisp("(string-version-lessp \"a1a10aa\" \"a1a0100\")", "t");
1215 assert_lisp("(string-version-lessp \"a1b2\" \"a1a3\")", "nil");
1216 assert_lisp("(string-version-lessp \".\" \".\")", "nil");
1217 assert_lisp("(string-version-lessp \"..\" \"..\")", "nil");
1218 assert_lisp("(string-version-lessp \"..\" \".a\")", "t");
1219 assert_lisp("(string-version-lessp \"101a\" \"100b\")", "nil");
1220 assert_lisp("(string-version-lessp \"10\" \"0100\")", "t");
1221 assert_lisp("(string-version-lessp \"001\" \"01\")", "nil");
1222 assert_lisp("(string-version-lessp \"a1a10\" \"a1a0100\")", "t");
1223 assert_lisp("(string-version-lessp \"A\" \":\")", "t");
1224 assert_lisp("(string-version-lessp \"\" \"\")", "nil");
1225 assert_lisp("(string-version-lessp \"\" \".\")", "t");
1226 assert_lisp("(string-version-lessp \".\" \".\")", "nil");
1227 assert_lisp("(string-version-lessp \".\" \"~\")", "t");
1228 assert_lisp("(string-version-lessp \"~\" \"a\")", "t");
1229
1230 assert_lisp("(string-version-lessp 'less 'less)", "nil");
1233 assert_lisp("(string-version-lessp 'les 'less)", "t");
1235 assert_lisp("(string-version-lessp 'less 'les)", "nil");
1236 assert_lisp("(string-version-lessp 'abc 'bcd)", "t");
1238 assert_lisp("(string-version-lessp 'less1 'less1)", "nil");
1240 assert_lisp("(string-version-lessp '100a '100b)", "t");
1241 assert_lisp("(string-version-lessp '1less 'less1)", "t");
1243 assert_lisp("(string-version-lessp 'less12 'less1)", "nil");
1245 assert_lisp("(string-version-lessp 'less1 'less12)", "t");
1247 assert_lisp("(string-version-lessp '133less1 'less12)", "t");
1249 assert_lisp("(string-version-lessp '101a '100b)", "nil");
1251 assert_lisp("(string-version-lessp '10a100 '0100a)", "t");
1254 assert_lisp("(string-version-lessp 'a100 '0100a)", "nil");
1255
1256 assert_lisp("(string-version-lessp 'less \"less\")", "nil");
1258 assert_lisp("(string-version-lessp 'less1 \"less10\")", "t");
1259 }
1260
1261 #[test]
1262 fn test_base64url_encode_string() {
1263 assert_lisp("(base64url-encode-string \"hello\")", "\"aGVsbG8=\"");
1264 assert_lisp("(base64url-encode-string \"hello\" nil)", "\"aGVsbG8=\"");
1265 assert_lisp("(base64url-encode-string \"hello\" t)", "\"aGVsbG8\"");
1266 assert_lisp("(base64url-encode-string \"hello\" 0)", "\"aGVsbG8\"");
1267 }
1268
1269 #[test]
1270 #[cfg_attr(miri, ignore)]
1271 fn test_base64url_encode_string_prop() {
1272 proptest! {|(string in arb_custom_string("[\x00-\x7F]*"))| {
1273 assert_elprop![r#"(base64url-encode-string {} t)"#, string];
1274 }};
1275 }
1276
1277 #[test]
1278 #[cfg(miri)]
1279 fn test_maphash() {
1280 assert_lisp(
1283 "(let ((h (make-hash-table))) (puthash 1 6 h) (puthash 2 8 h) (puthash 3 10 h) (maphash 'eq h))",
1284 "nil",
1285 );
1286 }
1287
1288 #[test]
1289 fn test_legnth() {
1290 assert_lisp("(length nil)", "0");
1291 assert_lisp("(length '(1 2 3))", "3");
1292 assert_lisp("(length \"hello\")", "5");
1293 assert_lisp("(length [1 2 3])", "3");
1294 assert_lisp("(safe-length '(1 . 2))", "1");
1295 assert_lisp("(safe-length '(1 2 3 . 4))", "3");
1296 assert_lisp("(safe-length 'foo)", "0");
1297 }
1298
1299 #[test]
1300 fn test_sort() {
1301 assert_lisp("(sort nil '<)", "nil");
1302 assert_lisp("(sort '(1) '<)", "(1)");
1303 assert_lisp("(sort '(2 1) '<)", "(1 2)");
1304 assert_lisp("(sort '(1 2 3) '<)", "(1 2 3)");
1305 assert_lisp("(sort '(3 2 1) '<)", "(1 2 3)");
1306 assert_lisp("(sort '(3 1 2) '<)", "(1 2 3)");
1307 assert_lisp("(sort '(1 2 3 4 5) '>)", "(5 4 3 2 1)");
1308 assert_lisp(
1309 "(sort '((1 . 1) (1 . 2) (1 . 3)) 'car-less-than-car)",
1310 "((1 . 1) (1 . 2) (1 . 3))",
1311 );
1312 assert_lisp("(condition-case nil (sort '(3 2 1) 'length) (error 7))", "7");
1313 }
1314
1315 #[test]
1316 fn test_copy_alist() {
1317 assert_lisp("(copy-alist '((1 . 2) (3 . 4) (5 . 6)))", "((1 . 2) (3 . 4) (5 . 6))");
1318 }
1319
1320 #[test]
1321 fn test_clear_string() {
1322 assert_lisp(
1323 "(let ((str \"test string\")) (clear-string str) str)",
1324 "\"\0\0\0\0\0\0\0\0\0\0\0\"",
1325 );
1326 assert_lisp("(let ((str \"\")) (clear-string str) str)", "\"\"");
1327 }
1328}