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, elprop};
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        _ => None,
593    }
594}
595
596#[defun]
597pub(crate) fn nth(n: usize, list: List) -> Result<Object> {
598    Ok(list.elements().fallible().nth(n)?.unwrap_or_default())
599}
600
601#[defun]
602pub(crate) fn nthcdr(n: usize, list: List) -> Result<Object> {
603    let ListType::Cons(mut cons) = list.untag() else { return Ok(NIL) };
604    let mut tail = list.as_obj_copy();
605    for _ in 0..n {
606        tail = cons.cdr();
607        if let Some(next) = tail.cons() {
608            cons = next;
609        } else {
610            break;
611        }
612    }
613    Ok(tail)
614}
615
616#[defun]
617pub(crate) fn elt<'ob>(sequence: Object<'ob>, n: usize, cx: &'ob Context) -> Result<Object<'ob>> {
618    match sequence.untag() {
619        ObjectType::Cons(x) => nth(n, x.into()),
620        ObjectType::NIL => Ok(NIL),
621        ObjectType::Vec(x) => aref(x.into(), n, cx),
622        ObjectType::Record(x) => aref(x.into(), n, cx),
623        ObjectType::String(x) => aref(x.into(), n, cx),
624        ObjectType::ByteFn(x) => aref(x.into(), n, cx),
625        other => Err(TypeError::new(Type::Sequence, other).into()),
626    }
627}
628
629#[defun]
630pub(crate) fn string_equal<'ob>(s1: Object<'ob>, s2: Object<'ob>) -> Result<bool> {
631    let s1 = match s1.untag() {
632        ObjectType::String(x) => x.as_bytes(),
633        ObjectType::ByteString(x) => x.inner(),
634        ObjectType::Symbol(x) => x.get().as_bytes(),
635        _ => bail!(TypeError::new(Type::String, s1)),
636    };
637    let s2 = match s2.untag() {
638        ObjectType::String(x) => x.as_bytes(),
639        ObjectType::ByteString(x) => x.inner(),
640        ObjectType::Symbol(x) => (x.get()).as_bytes(),
641        _ => bail!(TypeError::new(Type::String, s2)),
642    };
643
644    Ok(s1 == s2)
645}
646
647#[defun]
648pub(crate) fn compare_strings<'ob>(
649    string1: &str,
650    start1: Object<'ob>,
651    end1: Object<'ob>,
652    string2: &str,
653    start2: Object<'ob>,
654    end2: Object<'ob>,
655    ignore_case: OptionalFlag,
656) -> Result<Object<'ob>> {
657    let start1 = match start1.untag() {
658        ObjectType::Int(x) => x,
659        ObjectType::NIL => 0,
660        _ => bail!(TypeError::new(Type::Int, start1)),
661    };
662    let end1 = match end1.untag() {
663        ObjectType::Int(x) => x,
664        ObjectType::NIL => string1.chars().count() as i64,
665        _ => bail!(TypeError::new(Type::Int, end1)),
666    };
667
668    if end1 < start1 {
669        bail!("Args out of range: {string1}, {start1}, {end1}");
670    }
671
672    let s1 = string1.chars().skip(start1 as usize).take((end1 - start1) as usize);
673
674    let start2 = match start2.untag() {
675        ObjectType::Int(x) => x,
676        ObjectType::NIL => 0,
677        _ => bail!(TypeError::new(Type::Int, start2)),
678    };
679    let end2 = match end2.untag() {
680        ObjectType::Int(x) => x,
681        ObjectType::NIL => string2.chars().count() as i64,
682        _ => bail!(TypeError::new(Type::Int, end2)),
683    };
684
685    if end2 < start2 {
686        bail!("Args out of range: {string2}, {start2}, {end2}");
687    }
688    // TODO: check if byte strings are supported
689    let s2 = string2.chars().skip(start2 as usize).take((end2 - start2) as usize);
690
691    let mut leading = 1;
692    for (c1, c2) in s1.zip(s2) {
693        let (c1, c2) = if ignore_case.is_some() {
694            //TODO: use case-table to determine the uppercase of a character
695            (c1.to_uppercase().next().unwrap(), c2.to_uppercase().next().unwrap())
696        } else {
697            (c1, c2)
698        };
699
700        match c1.cmp(&c2) {
701            std::cmp::Ordering::Less => return Ok((-leading).into()),
702            std::cmp::Ordering::Greater => return Ok(leading.into()),
703            std::cmp::Ordering::Equal => {}
704        }
705        leading += 1;
706    }
707
708    Ok(true.into())
709}
710
711#[defun]
712pub(crate) fn string_distance(string1: &str, string2: &str, bytecompare: OptionalFlag) -> i64 {
713    if bytecompare.is_none() {
714        levenshtein_distance(string1.chars(), string2.chars())
715    } else {
716        levenshtein_distance(string1.as_bytes().iter(), string2.as_bytes().iter())
717    }
718}
719
720#[inline]
721pub(crate) fn levenshtein_distance<T: PartialEq, I: Iterator<Item = T>>(s1: I, s2: I) -> i64 {
722    use std::cmp::min;
723    // Initialize work vectors
724    let s = s1.collect::<Vec<_>>();
725    let t = s2.collect::<Vec<_>>();
726    let mut v0 = vec![0; t.len() + 1];
727    let mut v1 = vec![0; t.len() + 1];
728
729    // Initialize v0
730    for (i, v0i) in v0.iter_mut().enumerate() {
731        *v0i = i as i64;
732    }
733
734    // Calculate v1 from previous row v0
735    for (i, si) in s.iter().enumerate() {
736        // First element of v1 is A[i+1][0]
737        // Edit distance is delete (i+1) chars from s to match empty t
738        v1[0] = i as i64 + 1;
739
740        // fills in the rest of the row
741        for (j, tj) in t.iter().enumerate() {
742            let deletion_cost = v0[j + 1] + 1;
743            let insertion_cost = v1[j] + 1;
744            let substitution_cost = v0[j] + if si == tj { 0 } else { 1 };
745            v1[j + 1] = min(deletion_cost, min(insertion_cost, substitution_cost));
746        }
747
748        // Swap v1 and v0
749        std::mem::swap(&mut v0, &mut v1);
750    }
751    // Return the final result
752    v0[t.len()]
753}
754
755#[defun]
756pub(crate) fn string_bytes(string: &str) -> usize {
757    string.len()
758}
759
760#[derive(Debug, Clone, Copy)]
761pub(crate) enum StringOrChar<'ob> {
762    String(&'ob str),
763    Char(u64),
764}
765
766impl<'ob> TryFrom<Object<'ob>> for StringOrChar<'ob> {
767    type Error = TypeError;
768
769    fn try_from(obj: Object<'ob>) -> Result<Self, Self::Error> {
770        match obj.untag() {
771            ObjectType::String(s) => Ok(Self::String(s)),
772            ObjectType::Int(c) if c >= 0 => Ok(Self::Char(c as u64)),
773            _ => Err(TypeError::new(Type::StringOrChar, obj)),
774        }
775    }
776}
777
778impl<'ob> From<&'ob str> for StringOrChar<'ob> {
779    fn from(s: &'ob str) -> Self {
780        Self::String(s)
781    }
782}
783
784impl From<char> for StringOrChar<'_> {
785    fn from(s: char) -> Self {
786        Self::Char(s as u64)
787    }
788}
789
790#[derive(Debug, Clone, Copy)]
791pub(crate) struct StringOrSymbol<'ob>(&'ob str);
792
793impl<'ob> TryFrom<Object<'ob>> for StringOrSymbol<'ob> {
794    type Error = TypeError;
795
796    fn try_from(obj: Object<'ob>) -> Result<Self, Self::Error> {
797        match obj.untag() {
798            ObjectType::String(x) => Ok(Self(x)),
799            ObjectType::Symbol(x) => Ok(Self(x.get().name())),
800            _ => Err(TypeError::new(Type::String, obj)),
801        }
802    }
803}
804
805#[defun]
806pub(crate) fn string_lessp<'ob>(
807    string1: StringOrSymbol<'ob>,
808    string2: StringOrSymbol<'ob>,
809) -> Result<bool> {
810    for (c1, c2) in string1.0.chars().zip(string2.0.chars()) {
811        if c1 != c2 {
812            return Ok(c1 < c2);
813        }
814    }
815    Ok(string1.0.len() < string2.0.len())
816}
817
818#[defun]
819pub(crate) fn string_version_lessp<'ob>(
820    string1: StringOrSymbol<'ob>,
821    string2: StringOrSymbol<'ob>,
822) -> Result<bool> {
823    Ok(filevercmp(string1.0.as_bytes(), string2.0.as_bytes()) == std::cmp::Ordering::Less)
824}
825
826#[defun]
827pub(crate) fn clear_string(string: &LispString) -> Result<Object<'_>> {
828    string.clear();
829    Ok(NIL)
830}
831
832///////////////
833// HashTable //
834///////////////
835
836defsym!(KW_TEST);
837defsym!(KW_DOCUMENTATION);
838
839#[defun]
840pub(crate) fn make_hash_table<'ob>(
841    keyword_args: &[Object<'ob>],
842    cx: &'ob Context,
843) -> Result<Object<'ob>> {
844    let kw_test_pos = keyword_args.iter().step_by(2).position(|&x| x == sym::KW_TEST);
845    if let Some(i) = kw_test_pos {
846        let Some(val) = keyword_args.get((i * 2) + 1) else {
847            bail!("Missing keyword value for :test")
848        };
849        if *val != sym::EQ && *val != sym::EQUAL && *val != sym::EQL {
850            // TODO: we are currently only using `equal', but eq should be okay
851            bail!("only `eq' and `equal' keywords support for make-hash-table :test. Found {val}");
852        }
853    }
854    // TODO, the rest of the keywords need to be supported here
855    let map = HashTable::with_hasher(std::hash::BuildHasherDefault::default());
856    Ok(cx.add(map))
857}
858
859#[defun]
860pub(crate) fn hash_table_p(obj: Object) -> bool {
861    matches!(obj.untag(), ObjectType::HashTable(_))
862}
863
864#[defun]
865pub(crate) fn gethash<'ob>(
866    key: Object<'ob>,
867    table: &'ob LispHashTable,
868    dflt: Option<Object<'ob>>,
869) -> Option<Object<'ob>> {
870    match table.get(key) {
871        Some(x) => Some(x),
872        None => dflt,
873    }
874}
875
876#[defun]
877pub(crate) fn puthash<'ob>(
878    key: Object<'ob>,
879    value: Object<'ob>,
880    table: &'ob LispHashTable,
881) -> Object<'ob> {
882    table.insert(key, value);
883    value
884}
885
886#[defun]
887fn remhash(key: Object, table: &LispHashTable) -> Result<()> {
888    let Some(idx) = table.get_index_of(key) else { return Ok(()) };
889    // If the removed element is before our iterator, then we need to shift the
890    // iterator back one because the whole map get's shifted when something is
891    // removed.
892    let iter_idx = table.get_iter_index();
893    if idx < iter_idx {
894        table.set_iter_index(iter_idx - 1);
895    }
896    // TODO: can we use swap_remove?
897    table.shift_remove(key);
898    Ok(())
899}
900
901#[defun]
902fn maphash(
903    function: &Rto<Function>,
904    table: &Rto<Gc<&LispHashTable>>,
905    env: &mut Rt<Env>,
906    cx: &mut Context,
907) -> Result<bool> {
908    let loop_idx = |table: &LispHashTable| {
909        let end = table.len();
910        let idx = table.get_iter_index();
911        table.set_iter_index(idx + 1);
912        if idx >= end { None } else { Some(idx) }
913    };
914
915    loop {
916        let (key, val) = {
917            let table = table.untag(cx);
918            let Some(idx) = loop_idx(table) else { break };
919            table.get_index(idx).unwrap()
920        };
921        let result = call!(function, key, val; env, cx);
922        if let Err(e) = result {
923            table.untag(cx).set_iter_index(0);
924            return Err(e.into());
925        }
926    }
927    table.untag(cx).set_iter_index(0);
928    Ok(false)
929}
930
931#[defun]
932fn copy_sequence<'ob>(arg: Object<'ob>, cx: &'ob Context) -> Result<Object<'ob>> {
933    match arg.untag() {
934        ObjectType::Vec(x) => Ok(cx.add(x.to_vec())),
935        ObjectType::Cons(x) => {
936            // TODO: remove this temp vector
937            let mut elements = Vec::new();
938            let mut tail = None;
939            for cons in x.conses() {
940                let cons = cons?;
941                elements.push(cons.car());
942                if !matches!(cons.cdr().untag(), ObjectType::Cons(_)) {
943                    tail = Some(cons.cdr());
944                }
945            }
946            Ok(slice_into_list(&elements, tail, cx))
947        }
948        ObjectType::String(x) => Ok(cx.add(x.to_owned())),
949        ObjectType::NIL => Ok(NIL),
950        _ => Err(TypeError::new(Type::Sequence, arg).into()),
951    }
952}
953
954#[defun]
955fn substring(string: &str, from: Option<usize>, to: Option<usize>) -> Result<String> {
956    if from.unwrap_or_default() > string.len() || to.unwrap_or_default() > string.len() {
957        bail!("substring args out of range for {string} : {from:?} {to:?}");
958    }
959    let new_string = match (from, to) {
960        (None, None) => string,
961        (None, Some(t)) => &string[..t],
962        (Some(f), None) => &string[f..],
963        (Some(f), Some(t)) => {
964            let range = if f > t { t..f } else { f..t };
965            &string[range]
966        }
967    };
968    Ok(new_string.to_owned())
969}
970
971defsym!(MD5);
972defsym!(SHA1);
973defsym!(SHA224);
974defsym!(SHA256);
975defsym!(SHA384);
976defsym!(SHA512);
977
978#[defun]
979fn secure_hash_algorithms<'ob>(cx: &'ob Context) -> Object<'ob> {
980    // https://crates.io/crates/md-5
981    // https://crates.io/crates/sha1
982    // https://crates.io/crates/sha2
983    // https://crates.io/crates/digest ?
984    list![sym::MD5, sym::SHA1, sym::SHA224, sym::SHA256, sym::SHA384, sym::SHA512; cx]
985}
986
987#[defun]
988fn enable_debug() -> bool {
989    crate::debug::enable_debug();
990    true
991}
992
993#[defun]
994fn debug_enabled() -> bool {
995    crate::debug::debug_enabled()
996}
997
998#[defun]
999fn disable_debug() -> bool {
1000    crate::debug::disable_debug();
1001    false
1002}
1003
1004/// Base64-encode STRING and return the result.
1005///
1006/// Optional second argument NO-LINE-BREAK means do not break long lines
1007/// into shorter lines.
1008#[defun]
1009#[elprop("[\x00-\x7F]*", _)]
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]
1024#[elprop("[\x00-\x7F]*", _)]
1025fn base64url_encode_string(string: &str, no_pad: OptionalFlag) -> Result<String> {
1026    if string.is_ascii() {
1027        Ok(base64_encode(string, false, no_pad.is_none(), true))
1028    } else {
1029        Err(anyhow!("Multibyte character in data for base64 encoding"))
1030    }
1031}
1032
1033fn base64_encode(string: &str, _line_break: bool, pad: bool, base64url: bool) -> String {
1034    let config = base64::engine::GeneralPurposeConfig::new().with_encode_padding(pad);
1035    let alphabets = if base64url { base64::alphabet::URL_SAFE } else { base64::alphabet::STANDARD };
1036    let engine = base64::engine::GeneralPurpose::new(&alphabets, config);
1037    engine.encode(string)
1038}
1039
1040#[cfg(test)]
1041mod test {
1042    use crate::{fns::levenshtein_distance, interpreter::assert_lisp};
1043
1044    #[test]
1045    fn test_base64_encode_string() {
1046        assert_lisp("(base64-encode-string \"hello\")", "\"aGVsbG8=\"");
1047        assert_lisp(
1048            "(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\")",
1049            "\"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVt\"",
1050        );
1051        // 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\"");
1052    }
1053
1054    #[test]
1055    fn test_take() {
1056        assert_lisp("(take 2 '(1 2 3 4))", "(1 2)");
1057    }
1058
1059    #[test]
1060    fn test_delq() {
1061        assert_lisp("(delq 1 '(1 2 3 1 4 1))", "(2 3 4)");
1062        assert_lisp("(delq t '(t t t))", "nil");
1063    }
1064
1065    #[test]
1066    fn test_nthcdr() {
1067        assert_lisp("(nthcdr 0 nil)", "nil");
1068        assert_lisp("(nthcdr 3 nil)", "nil");
1069        assert_lisp("(nthcdr 0 '(1 2 3))", "(1 2 3)");
1070        assert_lisp("(nthcdr 1 '(1 2 3))", "(2 3)");
1071        assert_lisp("(nthcdr 2 '(1 2 3))", "(3)");
1072        assert_lisp("(nthcdr 3 '(1 2 3))", "nil");
1073        assert_lisp("(nthcdr 1 '(1 . 2))", "2");
1074        assert_lisp("(nthcdr 2 '(1 2 . 3))", "3");
1075    }
1076
1077    #[test]
1078    fn test_reverse() {
1079        assert_lisp("(nreverse nil)", "nil");
1080        assert_lisp("(nreverse '(1))", "(1)");
1081        assert_lisp("(nreverse '(1 2))", "(2 1)");
1082        assert_lisp("(nreverse '(1 2 3))", "(3 2 1)");
1083        assert_lisp("(nreverse '(1 2 3 4))", "(4 3 2 1)");
1084    }
1085
1086    #[test]
1087    fn test_nconc() {
1088        assert_lisp("(nconc nil)", "nil");
1089        assert_lisp("(nconc '(1 2))", "(1 2)");
1090        assert_lisp("(nconc '(1 2) '(3 4))", "(1 2 3 4)");
1091        assert_lisp("(nconc '(1 2) '(3 4) '(5 6))", "(1 2 3 4 5 6)");
1092        assert_lisp("(nconc nil '(1 2))", "(1 2)");
1093        assert_lisp("(nconc '(1 2) nil)", "(1 2)");
1094    }
1095
1096    #[test]
1097    fn test_append() {
1098        assert_lisp("(append \"hello\")", "(104 101 108 108 111)");
1099    }
1100
1101    #[test]
1102    fn test_assq() {
1103        assert_lisp("(assq 5 '((1 . 2) (3 . 4) (5 . 6)))", "(5 . 6)");
1104        assert_lisp("(assq 6 '((1 . 2) (3 . 4) (5 . 6)))", "nil");
1105    }
1106
1107    #[test]
1108    fn test_string_equal() {
1109        assert_lisp("(string-equal \"hello\" \"hello\")", "t");
1110        assert_lisp("(string-equal \"hello\" \"world\")", "nil");
1111    }
1112
1113    #[test]
1114    fn test_compare_strings() {
1115        assert_lisp("(compare-strings \"hello\" 0 6 \"hello\" 0 6)", "t");
1116        assert_lisp("(compare-strings \"hello\" 0 6 \"world\" 0 6)", "-1");
1117        assert_lisp("(compare-strings \"hello\" 0 6 \"HELLO\" 0 6 t)", "t");
1118    }
1119
1120    #[test]
1121    fn test_string_distance() {
1122        assert_lisp("(string-distance \"hello\" \"hello\")", "0");
1123        assert_lisp("(string-distance \"hello\" \"jello\")", "1");
1124        assert_lisp("(string-distance \"hello\" \"world\")", "4");
1125    }
1126
1127    #[test]
1128    fn test_levenstein_distance() {
1129        assert_eq!(4, levenshtein_distance("hello".chars(), "world".chars()));
1130        assert_eq!(3, levenshtein_distance("kitten".chars(), "sitting".chars()));
1131        assert_eq!(2, levenshtein_distance("book".chars(), "back".chars()));
1132        assert_eq!(5, levenshtein_distance("table".chars(), "dinner".chars()));
1133        assert_eq!(2, levenshtein_distance("person".chars(), "pardon".chars()));
1134        assert_eq!(1, levenshtein_distance("person".chars(), "persons".chars()));
1135        assert_eq!(4, levenshtein_distance("book".chars(), "".chars()));
1136        assert_eq!(4, levenshtein_distance("".chars(), "book".chars()));
1137        assert_eq!(0, levenshtein_distance("".chars(), "".chars()));
1138        assert_eq!(2, levenshtein_distance("Späße".chars(), "Spaß".chars()));
1139        assert_eq!(5, levenshtein_distance("さようなら".chars(), "こんにちは".chars()));
1140        assert_eq!(1, levenshtein_distance("さようなら".chars(), "さようなう".chars()));
1141        assert_eq!(4, levenshtein_distance("こんにちは".chars(), "こんにちは abc".chars()));
1142        assert_eq!(1, levenshtein_distance("༆༃ʘ".chars(), "༆˥ʘ".chars()));
1143    }
1144
1145    #[test]
1146    fn test_string_lessp() {
1147        // String Tests
1148        // Test Equality
1149        assert_lisp("(string-lessp \"less\" \"less\")", "nil");
1150        // Test Length
1151        assert_lisp("(string-lessp \"les\" \"less\")", "t");
1152        assert_lisp("(string-lessp \"less\" \"les\")", "nil");
1153        // Check for less than
1154        assert_lisp("(string-lessp \"abc\" \"bcd\")", "t");
1155        assert_lisp("(string-lessp \"abx\" \"abcd\")", "nil");
1156
1157        // Symbol Tests
1158        // Test Equality
1159        assert_lisp("(string-lessp 'less 'less)", "nil");
1160        // Test Length
1161        assert_lisp("(string-lessp 'les 'less)", "t");
1162        assert_lisp("(string-lessp 'less 'les)", "nil");
1163        // Check for less than
1164        assert_lisp("(string-lessp 'abc 'bcd)", "t");
1165
1166        // Mixed Tests
1167        assert_lisp("(string-lessp 'less \"less\")", "nil");
1168        assert_lisp("(string-lessp 'les \"less\")", "t");
1169    }
1170
1171    #[test]
1172    #[cfg(not(miri))] // slow
1173    fn test_string_version_lessp() {
1174        // String Tests
1175        // Test Equality
1176        assert_lisp("(string-version-lessp \"less\" \"less\")", "nil");
1177        // Test Length
1178        assert_lisp("(string-version-lessp \"les\" \"less\")", "t");
1179        assert_lisp("(string-version-lessp \"less\" \"les\")", "nil");
1180        // Test for less than
1181        assert_lisp("(string-version-lessp \"abc\" \"bcd\")", "t");
1182        // Test Equality with number
1183        assert_lisp("(string-version-lessp \"less1\" \"less1\")", "nil");
1184        assert_lisp("(string-version-lessp \"100a\" \"100b\")", "t");
1185        // Test Differing numeric position does matter
1186        assert_lisp("(string-version-lessp \"1less\" \"less1\")", "t");
1187        // Test Greater than numericaly
1188        assert_lisp("(string-version-lessp \"less12\" \"less1\")", "nil");
1189        // Test Less than numericaly
1190        assert_lisp("(string-version-lessp \"less1\" \"less12\")", "t");
1191        // Test that later numbers override previous ones
1192        assert_lisp("(string-version-lessp \"133less1\" \"less12\")", "t");
1193        // Test that digits don't disappear
1194        assert_lisp("(string-version-lessp \"112a\" \"512a\")", "t");
1195        // Test Numbers take higher precedence over characters
1196        assert_lisp("(string-version-lessp \"101a\" \"100b\")", "nil");
1197        // Test Numbers that leading zeros are ignored
1198        assert_lisp("(string-version-lessp \"10\" \"0100\")", "t");
1199        // Test that that the first number is compared immediately at a char comparison
1200        assert_lisp("(string-version-lessp \"10a100\" \"0100a\")", "t");
1201        assert_lisp("(string-version-lessp \"a100\" \"0100a\")", "nil");
1202        assert_lisp("(string-version-lessp \"01\" \"1\")", "nil");
1203        assert_lisp("(string-version-lessp \"a1a10aa\" \"a1a0100\")", "t");
1204        assert_lisp("(string-version-lessp \"a1b2\" \"a1a3\")", "nil");
1205        assert_lisp("(string-version-lessp \".\" \".\")", "nil");
1206        assert_lisp("(string-version-lessp \"..\" \"..\")", "nil");
1207        assert_lisp("(string-version-lessp \"..\" \".a\")", "t");
1208        assert_lisp("(string-version-lessp \"101a\" \"100b\")", "nil");
1209        assert_lisp("(string-version-lessp \"10\" \"0100\")", "t");
1210        assert_lisp("(string-version-lessp \"001\" \"01\")", "nil");
1211        assert_lisp("(string-version-lessp \"a1a10\" \"a1a0100\")", "t");
1212        assert_lisp("(string-version-lessp \"A\" \":\")", "t");
1213        assert_lisp("(string-version-lessp \"\" \"\")", "nil");
1214        assert_lisp("(string-version-lessp \"\" \".\")", "t");
1215        assert_lisp("(string-version-lessp \".\" \".\")", "nil");
1216        assert_lisp("(string-version-lessp \".\" \"~\")", "t");
1217        assert_lisp("(string-version-lessp \"~\" \"a\")", "t");
1218
1219        // Symbol Tests
1220        // Test Equality
1221        assert_lisp("(string-version-lessp 'less 'less)", "nil");
1222        // Test Length
1223        assert_lisp("(string-version-lessp 'les 'less)", "t");
1224        assert_lisp("(string-version-lessp 'less 'les)", "nil");
1225        // Check for less than
1226        assert_lisp("(string-version-lessp 'abc 'bcd)", "t");
1227        // Test Equality with number
1228        assert_lisp("(string-version-lessp 'less1 'less1)", "nil");
1229        assert_lisp("(string-version-lessp '100a '100b)", "t");
1230        // Test Differing numeric position does matter
1231        assert_lisp("(string-version-lessp '1less 'less1)", "t");
1232        // Test Greater than numericaly
1233        assert_lisp("(string-version-lessp 'less12 'less1)", "nil");
1234        // Test Less than numericaly
1235        assert_lisp("(string-version-lessp 'less1 'less12)", "t");
1236        // Test that later numbers override previous ones
1237        assert_lisp("(string-version-lessp '133less1 'less12)", "t");
1238        // Test Numbers take higher precedence over characters
1239        assert_lisp("(string-version-lessp '101a '100b)", "nil");
1240        // Test Numbers that leading zeros are ignored
1241        // Test that that the first number is compared immediately at a char comparison
1242        assert_lisp("(string-version-lessp '10a100 '0100a)", "t");
1243        assert_lisp("(string-version-lessp 'a100 '0100a)", "nil");
1244
1245        // Mixed Tests
1246        assert_lisp("(string-version-lessp 'less \"less\")", "nil");
1247        assert_lisp("(string-version-lessp 'less1 \"less10\")", "t");
1248    }
1249
1250    #[test]
1251    fn test_base64url_encode_string() {
1252        assert_lisp("(base64url-encode-string \"hello\")", "\"aGVsbG8=\"");
1253        assert_lisp("(base64url-encode-string \"hello\" nil)", "\"aGVsbG8=\"");
1254        assert_lisp("(base64url-encode-string \"hello\" t)", "\"aGVsbG8\"");
1255        assert_lisp("(base64url-encode-string \"hello\" 0)", "\"aGVsbG8\"");
1256    }
1257
1258    #[test]
1259    #[cfg(miri)]
1260    fn test_maphash() {
1261        // This test does not assert anything, but allows this to be checked by
1262        // miri
1263        assert_lisp(
1264            "(let ((h (make-hash-table))) (puthash 1 6 h) (puthash 2 8 h) (puthash 3 10 h) (maphash 'eq h))",
1265            "nil",
1266        );
1267    }
1268
1269    #[test]
1270    fn test_legnth() {
1271        assert_lisp("(length nil)", "0");
1272        assert_lisp("(length '(1 2 3))", "3");
1273        assert_lisp("(length \"hello\")", "5");
1274        assert_lisp("(length [1 2 3])", "3");
1275        assert_lisp("(safe-length '(1 . 2))", "1");
1276        assert_lisp("(safe-length '(1 2 3 . 4))", "3");
1277        assert_lisp("(safe-length 'foo)", "0");
1278    }
1279
1280    #[test]
1281    fn test_sort() {
1282        assert_lisp("(sort nil '<)", "nil");
1283        assert_lisp("(sort '(1) '<)", "(1)");
1284        assert_lisp("(sort '(2 1) '<)", "(1 2)");
1285        assert_lisp("(sort '(1 2 3) '<)", "(1 2 3)");
1286        assert_lisp("(sort '(3 2 1) '<)", "(1 2 3)");
1287        assert_lisp("(sort '(3 1 2) '<)", "(1 2 3)");
1288        assert_lisp("(sort '(1 2 3 4 5) '>)", "(5 4 3 2 1)");
1289        assert_lisp(
1290            "(sort '((1 . 1) (1 . 2) (1 . 3)) 'car-less-than-car)",
1291            "((1 . 1) (1 . 2) (1 . 3))",
1292        );
1293        assert_lisp("(condition-case nil (sort '(3 2 1) 'length) (error 7))", "7");
1294    }
1295
1296    #[test]
1297    fn test_copy_alist() {
1298        assert_lisp("(copy-alist '((1 . 2) (3 . 4) (5 . 6)))", "((1 . 2) (3 . 4) (5 . 6))");
1299    }
1300
1301    #[test]
1302    fn test_clear_string() {
1303        assert_lisp(
1304            "(let ((str \"test string\")) (clear-string str) str)",
1305            "\"\0\0\0\0\0\0\0\0\0\0\0\"",
1306        );
1307        assert_lisp("(let ((str \"\")) (clear-string str) str)", "\"\"");
1308    }
1309}