rune/
fns.rs

1//! General purpose lisp functions
2use 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    // TODO: implement text properties
74    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    // TODO: this function should never fail. Need to implement safe iterator
81    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    // TODO: Handle the unibyte case
119    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            // TODO: remove this intermediate vector
146            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            // TODO: remove this intermediate vector
158            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    // TODO: Remove this temp vector
302    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// TODO: Handle sorting vectors
449#[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    // TODO: Should we specialize some common predicates (<, >, string<, etc)?
463    vec.sort_by(|a, b| {
464        use std::cmp::Ordering;
465        if err.is_some() {
466            // We previously hit an error and don't want to call predicate
467            // anymore, but still need to wait for sort to finish.
468            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    // TODO: implement
493    new_alias
494}
495
496#[defun]
497// TODO: implement
498pub(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    // TODO: Fix this unsafe into_root
509    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            // TODO: need to correctly handle unibyte strings (no unicode codepoints)
544            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    // TODO: check if byte strings are supported
690    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            //TODO: use case-table to determine the uppercase of a character
696            (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    // Initialize work vectors
725    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    // Initialize v0
731    for (i, v0i) in v0.iter_mut().enumerate() {
732        *v0i = i as i64;
733    }
734
735    // Calculate v1 from previous row v0
736    for (i, si) in s.iter().enumerate() {
737        // First element of v1 is A[i+1][0]
738        // Edit distance is delete (i+1) chars from s to match empty t
739        v1[0] = i as i64 + 1;
740
741        // fills in the rest of the row
742        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        // Swap v1 and v0
750        std::mem::swap(&mut v0, &mut v1);
751    }
752    // Return the final result
753    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
833///////////////
834// HashTable //
835///////////////
836
837defsym!(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            // TODO: we are currently only using `equal', but eq should be okay
852            bail!("only `eq' and `equal' keywords support for make-hash-table :test. Found {val}");
853        }
854    }
855    // TODO, the rest of the keywords need to be supported here
856    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    // If the removed element is before our iterator, then we need to shift the
891    // iterator back one because the whole map get's shifted when something is
892    // removed.
893    let iter_idx = table.get_iter_index();
894    if idx < iter_idx {
895        table.set_iter_index(iter_idx - 1);
896    }
897    // TODO: can we use swap_remove?
898    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            // TODO: remove this temp vector
938            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    // https://crates.io/crates/md-5
982    // https://crates.io/crates/sha1
983    // https://crates.io/crates/sha2
984    // https://crates.io/crates/digest ?
985    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/// Base64-encode STRING and return the result.
1006///
1007/// Optional second argument NO-LINE-BREAK means do not break long lines
1008/// into shorter lines.
1009#[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/// Base64URL-encode STRING and return the result.
1019///
1020/// Optional second argument NO-PAD means do not add padding char =.
1021///
1022/// This produces the URL variant of base 64 encoding defined in RFC 4648.
1023#[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        // assert_lisp("(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\" t)", "\"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg\nc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu\nYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0\naW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1\nYXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2\nZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRl\ndXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1\naSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVt\"");
1055    }
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        // String Tests
1159        // Test Equality
1160        assert_lisp("(string-lessp \"less\" \"less\")", "nil");
1161        // Test Length
1162        assert_lisp("(string-lessp \"les\" \"less\")", "t");
1163        assert_lisp("(string-lessp \"less\" \"les\")", "nil");
1164        // Check for less than
1165        assert_lisp("(string-lessp \"abc\" \"bcd\")", "t");
1166        assert_lisp("(string-lessp \"abx\" \"abcd\")", "nil");
1167
1168        // Symbol Tests
1169        // Test Equality
1170        assert_lisp("(string-lessp 'less 'less)", "nil");
1171        // Test Length
1172        assert_lisp("(string-lessp 'les 'less)", "t");
1173        assert_lisp("(string-lessp 'less 'les)", "nil");
1174        // Check for less than
1175        assert_lisp("(string-lessp 'abc 'bcd)", "t");
1176
1177        // Mixed Tests
1178        assert_lisp("(string-lessp 'less \"less\")", "nil");
1179        assert_lisp("(string-lessp 'les \"less\")", "t");
1180    }
1181
1182    #[test]
1183    #[cfg(not(miri))] // slow
1184    fn test_string_version_lessp() {
1185        // String Tests
1186        // Test Equality
1187        assert_lisp("(string-version-lessp \"less\" \"less\")", "nil");
1188        // Test Length
1189        assert_lisp("(string-version-lessp \"les\" \"less\")", "t");
1190        assert_lisp("(string-version-lessp \"less\" \"les\")", "nil");
1191        // Test for less than
1192        assert_lisp("(string-version-lessp \"abc\" \"bcd\")", "t");
1193        // Test Equality with number
1194        assert_lisp("(string-version-lessp \"less1\" \"less1\")", "nil");
1195        assert_lisp("(string-version-lessp \"100a\" \"100b\")", "t");
1196        // Test Differing numeric position does matter
1197        assert_lisp("(string-version-lessp \"1less\" \"less1\")", "t");
1198        // Test Greater than numericaly
1199        assert_lisp("(string-version-lessp \"less12\" \"less1\")", "nil");
1200        // Test Less than numericaly
1201        assert_lisp("(string-version-lessp \"less1\" \"less12\")", "t");
1202        // Test that later numbers override previous ones
1203        assert_lisp("(string-version-lessp \"133less1\" \"less12\")", "t");
1204        // Test that digits don't disappear
1205        assert_lisp("(string-version-lessp \"112a\" \"512a\")", "t");
1206        // Test Numbers take higher precedence over characters
1207        assert_lisp("(string-version-lessp \"101a\" \"100b\")", "nil");
1208        // Test Numbers that leading zeros are ignored
1209        assert_lisp("(string-version-lessp \"10\" \"0100\")", "t");
1210        // Test that that the first number is compared immediately at a char comparison
1211        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        // Symbol Tests
1231        // Test Equality
1232        assert_lisp("(string-version-lessp 'less 'less)", "nil");
1233        // Test Length
1234        assert_lisp("(string-version-lessp 'les 'less)", "t");
1235        assert_lisp("(string-version-lessp 'less 'les)", "nil");
1236        // Check for less than
1237        assert_lisp("(string-version-lessp 'abc 'bcd)", "t");
1238        // Test Equality with number
1239        assert_lisp("(string-version-lessp 'less1 'less1)", "nil");
1240        assert_lisp("(string-version-lessp '100a '100b)", "t");
1241        // Test Differing numeric position does matter
1242        assert_lisp("(string-version-lessp '1less 'less1)", "t");
1243        // Test Greater than numericaly
1244        assert_lisp("(string-version-lessp 'less12 'less1)", "nil");
1245        // Test Less than numericaly
1246        assert_lisp("(string-version-lessp 'less1 'less12)", "t");
1247        // Test that later numbers override previous ones
1248        assert_lisp("(string-version-lessp '133less1 'less12)", "t");
1249        // Test Numbers take higher precedence over characters
1250        assert_lisp("(string-version-lessp '101a '100b)", "nil");
1251        // Test Numbers that leading zeros are ignored
1252        // Test that that the first number is compared immediately at a char comparison
1253        assert_lisp("(string-version-lessp '10a100 '0100a)", "t");
1254        assert_lisp("(string-version-lessp 'a100 '0100a)", "nil");
1255
1256        // Mixed Tests
1257        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        // This test does not assert anything, but allows this to be checked by
1281        // miri
1282        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}