runner/code/
data.rs

1use num_bigint::{BigInt, Sign};
2use proc_macro2::{TokenStream, TokenTree};
3use prop::collection::VecStrategy;
4use proptest::prelude::*;
5use serde::{Deserialize, Serialize};
6use syn::{FnArg, ItemFn};
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub(crate) enum Type {
10    String,
11    Float,
12    Cons,
13    Symbol,
14    Integer,
15    PosInteger,
16    Boolean,
17    True,
18    False,
19    Unknown,
20    Function,
21    UnibyteString,
22    Vector,
23    HashTable,
24    Record,
25    ByteFn,
26    Byte,
27    Subr,
28    Buffer,
29    Nil,
30    Char,
31    CharTable,
32    BigInt,
33    CustomString(String),
34    Multiple(Vec<Type>),
35    CustomList(Vec<Type>),
36}
37
38impl Type {
39    const SYMBOL_CHARS: &'static str = "[a-zA-Z][a-zA-Z0-9-]*";
40    const MAX_FIXNUM: i64 = i64::MAX >> 8;
41    const MIN_FIXNUM: i64 = i64::MIN >> 8;
42    // New function to create a strategy for a specific type
43    fn strategy(self) -> BoxedStrategy<ArbitraryType> {
44        match self {
45            Type::String => any::<String>().prop_map(ArbitraryType::String).boxed(),
46            Type::Float => any::<f64>().prop_map(ArbitraryType::Float).boxed(),
47            Type::Cons => Self::cons_strategy().prop_map(ArbitraryType::Cons).boxed(),
48            Type::Symbol => Self::SYMBOL_CHARS.prop_map(ArbitraryType::Symbol).boxed(),
49            Type::Integer => Self::fixnum_strategy().prop_map(ArbitraryType::Integer).boxed(),
50            Type::PosInteger => {
51                Self::pos_fixnum_strategy().prop_map(ArbitraryType::Integer).boxed()
52            }
53            Type::Boolean => any::<bool>().prop_map(ArbitraryType::Boolean).boxed(),
54            Type::True => Just(true).prop_map(ArbitraryType::Boolean).boxed(),
55            Type::False => Just(false).prop_map(ArbitraryType::Boolean).boxed(),
56            Type::Unknown => Self::any_object_strategy(),
57            Type::UnibyteString => "[a-zA-Z0-9 ]*".prop_map(ArbitraryType::UnibyteString).boxed(),
58            Type::Vector => prop::collection::vec(Self::any_object_strategy(), 0..10)
59                .prop_map(ArbitraryType::Vector)
60                .boxed(),
61            Type::Record => {
62                (Self::SYMBOL_CHARS, prop::collection::vec(Self::any_object_strategy(), 0..10))
63                    .prop_map(ArbitraryType::Record)
64                    .boxed()
65            }
66            Type::HashTable => todo!("Strategy for HashTable not implemented"),
67            Type::ByteFn => any::<u8>().prop_map(ArbitraryType::ByteFn).boxed(),
68            Type::Byte => any::<u8>().prop_map(ArbitraryType::Byte).boxed(),
69            Type::Subr => todo!("Strategy for Subr not implemented"),
70            Type::Buffer => any::<String>().prop_map(ArbitraryType::Buffer).boxed(),
71            Type::Nil => Just(ArbitraryType::Nil).boxed(),
72            Type::Char => any::<char>().prop_map(ArbitraryType::Char).boxed(),
73            Type::CharTable => todo!("Strategy for CharTable not implemented"),
74            Type::Function => todo!("Strategy for Function not implemented"),
75            Type::Multiple(s) => combined_strategy(&s),
76            Type::CustomString(s) => proptest::string::string_regex(&s)
77                .expect("Invalid proptest regex")
78                .prop_map(ArbitraryType::String)
79                .boxed(),
80            Type::CustomList(list) => {
81                let arb_list: Vec<_> = list.iter().map(|x| x.clone().strategy()).collect();
82                (arb_list, Just(false)).prop_map(ArbitraryType::Cons).boxed()
83            }
84            Type::BigInt => Self::big_int_strategy().prop_map(ArbitraryType::BigInt).boxed(),
85        }
86    }
87
88    fn fixnum_strategy() -> BoxedStrategy<i64> {
89        any::<i64>()
90            .prop_filter("Fixnum", |x| *x >= Self::MIN_FIXNUM && *x <= Self::MAX_FIXNUM)
91            .boxed()
92    }
93
94    fn pos_fixnum_strategy() -> BoxedStrategy<i64> {
95        any::<i64>()
96            .prop_filter("Fixnum", |x| *x >= 0 && *x <= Self::MAX_FIXNUM)
97            .boxed()
98    }
99
100    fn big_int_strategy() -> BoxedStrategy<BigInt> {
101        (any::<bool>(), any::<Vec<u32>>())
102            .prop_map(|(s, v)| {
103                let sign = if s { Sign::Plus } else { Sign::Minus };
104                BigInt::from_slice(sign, &v)
105            })
106            .boxed()
107    }
108
109    fn cons_strategy() -> (VecStrategy<BoxedStrategy<ArbitraryType>>, BoxedStrategy<bool>) {
110        (
111            prop::collection::vec(Self::any_object_strategy(), 0..10),
112            prop_oneof![
113                1 => Just(true),
114                3 => Just(false),
115            ]
116            .boxed(),
117        )
118    }
119
120    pub(crate) fn any_object_strategy() -> BoxedStrategy<ArbitraryType> {
121        prop_oneof![
122            Just(ArbitraryType::Nil),
123            any::<bool>().prop_map(ArbitraryType::Boolean),
124            Self::fixnum_strategy().prop_map(ArbitraryType::Integer),
125            any::<f64>().prop_map(ArbitraryType::Float),
126            any::<String>().prop_map(ArbitraryType::String),
127            Self::SYMBOL_CHARS.prop_map(ArbitraryType::Symbol),
128            "[a-zA-Z0-9 ]*".prop_map(ArbitraryType::UnibyteString),
129            any::<char>().prop_map(ArbitraryType::Char),
130        ]
131        .boxed()
132    }
133}
134
135// New function to create a combined strategy from multiple types
136pub(crate) fn combined_strategy(types: &[Type]) -> BoxedStrategy<ArbitraryType> {
137    // Combine all strategies using prop_oneof!
138    match types.len() {
139        0 => panic!("At least one type must be provided"),
140        1 => types[0].clone().strategy(),
141        2 => prop_oneof![types[0].clone().strategy(), types[1].clone().strategy()].boxed(),
142        3 => prop_oneof![
143            types[0].clone().strategy(),
144            types[1].clone().strategy(),
145            types[2].clone().strategy()
146        ]
147        .boxed(),
148        4 => prop_oneof![
149            types[0].clone().strategy(),
150            types[1].clone().strategy(),
151            types[2].clone().strategy(),
152            types[3].clone().strategy()
153        ]
154        .boxed(),
155        5 => prop_oneof![
156            types[0].clone().strategy(),
157            types[1].clone().strategy(),
158            types[2].clone().strategy(),
159            types[3].clone().strategy(),
160            types[4].clone().strategy()
161        ]
162        .boxed(),
163        n => panic!("Currently supporting up to 5 combined types, got {n}"),
164    }
165}
166
167#[derive(Clone, PartialEq, PartialOrd, Debug, Serialize, Deserialize)]
168pub(crate) enum ArbitraryType {
169    String(String),
170    Float(f64),
171    Cons((Vec<ArbitraryType>, bool)),
172    Symbol(String),
173    Integer(i64),
174    Boolean(bool),
175    Unknown(Box<ArbitraryType>),
176    UnibyteString(String),
177    Vector(Vec<ArbitraryType>),
178    HashTable(Vec<(ArbitraryType, ArbitraryType)>),
179    Record((String, Vec<ArbitraryType>)),
180    Nil,
181    Function(u8),
182    ByteFn(u8),
183    Byte(u8),
184    Char(char),
185    Buffer(String),
186    Subr(u8),
187    BigInt(BigInt),
188}
189
190pub(crate) fn print_args(args: &[Option<ArbitraryType>]) -> String {
191    args.iter()
192        .map(|x| match x {
193            Some(x) => format!("{x}"),
194            None => "nil".to_owned(),
195        })
196        .collect::<Vec<_>>()
197        .join(" ")
198}
199
200impl std::fmt::Display for ArbitraryType {
201    #[expect(clippy::too_many_lines)]
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        use std::string::ToString;
204        match self {
205            ArbitraryType::String(s) => {
206                write!(f, "\"")?;
207                for c in s.chars() {
208                    match c {
209                        '\n' => write!(f, "\\n")?,
210                        '\t' => write!(f, "\\t")?,
211                        '\r' => write!(f, "\\r")?,
212                        '\\' => write!(f, "\\\\")?,
213                        '\"' => write!(f, "\\\"")?,
214                        c => write!(f, "{c}")?,
215                    }
216                }
217                write!(f, "\"")
218            }
219            ArbitraryType::Float(n) => {
220                if n.fract() == 0.0_f64 {
221                    write!(f, "{n:.1}")
222                } else {
223                    write!(f, "{n}")
224                }
225            }
226            ArbitraryType::Cons(list) => {
227                let mut cells: Vec<_> = list.0.iter().map(ToString::to_string).collect();
228                let len = list.0.len();
229                let dot_end = list.1;
230                if dot_end && len >= 2 {
231                    cells.insert(len - 1, ".".to_owned());
232                }
233                let string = cells.join(" ");
234                write!(f, "'({string})")
235            }
236            ArbitraryType::Symbol(s) => write!(f, "'{s}"),
237            ArbitraryType::Integer(n) => write!(f, "{n}"),
238            ArbitraryType::Boolean(b) => {
239                if *b {
240                    write!(f, "t")
241                } else {
242                    write!(f, "nil")
243                }
244            }
245            ArbitraryType::Unknown(obj) => write!(f, "{obj}"),
246            ArbitraryType::UnibyteString(s) => {
247                write!(f, "\"")?;
248                for c in s.chars() {
249                    match c {
250                        '\n' => write!(f, "\\n")?,
251                        '\t' => write!(f, "\\t")?,
252                        '\r' => write!(f, "\\r")?,
253                        '\\' => write!(f, "\\\\")?,
254                        '"' => write!(f, "\\\"")?,
255                        c => write!(f, "{c}")?,
256                    }
257                }
258                write!(f, "\"")
259            }
260            ArbitraryType::Nil => write!(f, "nil"),
261            ArbitraryType::Vector(vec) => {
262                let cells: Vec<_> = vec.iter().map(ToString::to_string).collect();
263                let string = cells.join(" ");
264                write!(f, "[{string}]")
265            }
266            ArbitraryType::HashTable(vec) => {
267                write!(f, "#s(hash-table data (")?;
268                for (key, value) in vec {
269                    write!(f, "{key} {value} ")?;
270                }
271                write!(f, "))")
272            }
273            ArbitraryType::Record((name, members)) => {
274                let cells: Vec<_> = members.iter().map(ToString::to_string).collect();
275                let string = cells.join(" ");
276                write!(f, "(record '{name} {string})")
277            }
278            ArbitraryType::Function(arity) => {
279                write!(f, "(lambda (")?;
280                for i in 0..*arity {
281                    write!(f, "arg{i} ")?;
282                }
283                write!(f, ") nil)")
284            }
285            ArbitraryType::ByteFn(arity) => {
286                write!(f, "(lambda (")?;
287                for i in 0..*arity {
288                    write!(f, "arg{i} ")?;
289                }
290                write!(f, ") nil)")
291            }
292            ArbitraryType::Byte(n) => write!(f, "{n}"),
293            ArbitraryType::Buffer(name) => {
294                write!(f, "(generate-new-buffer {name})")
295            }
296            ArbitraryType::Subr(arity) => {
297                write!(f, "(lambda (")?;
298                for i in 0..*arity {
299                    write!(f, "arg{i} ")?;
300                }
301                write!(f, ") nil)")
302            }
303            ArbitraryType::Char(chr) => match chr {
304                '\n' => write!(f, "?\\n"),
305                '\t' => write!(f, "?\\t"),
306                '\r' => write!(f, "?\\r"),
307                '\u{0B}' => write!(f, "?\\v"),
308                '\u{0C}' => write!(f, "?\\f"),
309                '\u{1B}' => write!(f, "?\\e"),
310                '\u{7F}' => write!(f, "?\\d"),
311                '\u{08}' => write!(f, "?\\b"),
312                '\u{07}' => write!(f, "?\\a"),
313                '(' | ')' | '[' | ']' | '\\' | '"' => write!(f, "?\\{chr}"),
314                chr => write!(f, "?{chr}"),
315            },
316            ArbitraryType::BigInt(n) => write!(f, "{n}"),
317        }
318    }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub(crate) enum ArgType {
323    Required(Type),
324    Optional(Type),
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub(crate) struct Function {
329    pub(crate) name: String,
330    pub(crate) args: Vec<ArgType>,
331    pub(crate) ret: Option<Type>,
332    pub(crate) fallible: bool,
333}
334
335#[derive(Debug, Serialize, Deserialize)]
336pub(crate) struct Config {
337    pub(crate) test_count: u32,
338    pub(crate) functions: Vec<Function>,
339}
340
341#[allow(dead_code)]
342impl Function {
343    pub(crate) fn strategy(self) -> Vec<BoxedStrategy<Option<ArbitraryType>>> {
344        self.args
345            .into_iter()
346            .map(|arg| match arg {
347                ArgType::Required(ty) => ty.strategy().prop_map(Some).boxed(),
348                ArgType::Optional(ty) => {
349                    prop_oneof![1 => Just(None), 3 => ty.strategy().prop_map(Some)].boxed()
350                }
351            })
352            .collect::<Vec<_>>()
353    }
354
355    fn process_arg(ty: &syn::Type) -> Result<Type, String> {
356        match ty {
357            syn::Type::Array(_) => Err("Array not supported".to_string()),
358            syn::Type::BareFn(_) => Err("BareFn not supported".to_string()),
359            syn::Type::ImplTrait(_) => Err("Impl Trait not supported".to_string()),
360            syn::Type::Infer(_) => Err("Infer not supported".to_string()),
361            syn::Type::Macro(_) => Err("Macro not supported".to_string()),
362            syn::Type::Never(_) => Err("Never not supported".to_string()),
363            syn::Type::Paren(_) => Err("Paren not supported".to_string()),
364            syn::Type::Path(syn::TypePath { path, .. }) => {
365                let segments = &path.segments;
366                let last = segments.last().unwrap().ident.to_string();
367
368                match Self::get_simple_type(&last) {
369                    Some(x) => Ok(x),
370                    None => match last.as_str() {
371                        "Rto" | "Rt" | "Gc" => Self::process_arg(get_generic_arg(segments)?),
372                        s @ ("Env" | "Context") => Err(format!("{s} type not supported")),
373                        s => Err(format!("Unknown type: {s}")),
374                    },
375                }
376            }
377            syn::Type::Ptr(_) => Err("Ptr not supported".to_string()),
378            syn::Type::Reference(syn::TypeReference { elem, .. })
379            | syn::Type::Group(syn::TypeGroup { elem, .. }) => Function::process_arg(elem),
380            syn::Type::Slice(syn::TypeSlice { .. }) => Ok(Type::Cons),
381            syn::Type::TraitObject(_) => Err("TraitObject not supported".to_string()),
382            syn::Type::Tuple(_) => Ok(Type::Nil),
383            syn::Type::Verbatim(_) => Err("Verbatim type not supported".to_string()),
384            _ => Err("Unknown type".to_string()),
385        }
386    }
387
388    fn get_simple_type(ty: &str) -> Option<Type> {
389        Some(match ty {
390            "StringOrChar" => Type::Multiple(vec![Type::String, Type::Char]),
391            "StringOrSymbol" => Type::Multiple(vec![Type::String, Type::Symbol]),
392            "Symbol" => Type::Symbol,
393            "Number" | "NumberValue" => Type::Multiple(vec![Type::Integer, Type::Float]),
394            "Object" => Type::Unknown,
395            "usize" | "u64" => Type::PosInteger,
396            "u8" => Type::Byte,
397            "isize" | "i64" => Type::Integer,
398            "str" | "String" | "LispString" => Type::String,
399            "bool" | "OptionalFlag" => Type::Boolean,
400            "f64" => Type::Float,
401            "char" => Type::Char,
402            "Function" => Type::Function,
403            "Cons" | "List" | "Error" | "TypeError" | "ArgSlice" => Type::Cons,
404            "LispVec" | "LispVector" | "Vec" | "RecordBuilder" => Type::Vector,
405            "LispHashTable" => Type::HashTable,
406            "CharTable" | "CharTableInner" => Type::CharTable,
407            "ByteString" => Type::UnibyteString,
408            "Record" => Type::Record,
409            "ByteFn" => Type::ByteFn,
410            "SubrFn" => Type::Subr,
411            "LispBuffer" => Type::Buffer,
412            _ => return None,
413        })
414    }
415
416    fn custom_templates(func: &ItemFn) -> Vec<Option<Type>> {
417        for attr in &func.attrs {
418            if let syn::Meta::List(list) = &attr.meta {
419                if list.path.get_ident().unwrap() == "elprop" {
420                    let custom_args = Self::parse_template_stream(list.tokens.clone());
421                    return custom_args
422                        .into_iter()
423                        .map(|x| match x {
424                            Type::Nil => None,
425                            x => Some(x),
426                        })
427                        .collect();
428                }
429            }
430        }
431        Vec::new()
432    }
433
434    fn parse_template_stream(ts: TokenStream) -> Vec<Type> {
435        let objects = ts.into_iter().fold(vec![Vec::new()], |mut acc, token| {
436            // group by type (a, b | c, e) -> [a [b c] e]
437            match token {
438                TokenTree::Punct(punct) => match punct.as_char() {
439                    ',' => acc.push(Vec::new()),
440                    '|' => {}
441                    c => panic!("Unexpected punctuation {c}"),
442                },
443                x => acc.last_mut().unwrap().push(Self::parse_template_type(x)),
444            }
445            acc
446        });
447        // combine all groups of types into a MultiType
448        objects
449            .into_iter()
450            .map(|mut x| if x.len() == 1 { x.pop().unwrap() } else { Type::Multiple(x) })
451            .collect()
452    }
453
454    fn parse_template_type(token: TokenTree) -> Type {
455        match token {
456            TokenTree::Group(group) => {
457                Type::CustomList(Self::parse_template_stream(group.stream()))
458            }
459            TokenTree::Ident(ident) => {
460                let s = ident.to_string();
461                match Self::get_simple_type(&s) {
462                    Some(x) => x,
463                    None => match &*s {
464                        "_" => Type::Nil,
465                        "true" => Type::True,
466                        "false" => Type::False,
467                        s => panic!("Unknown type {s}"),
468                    },
469                }
470            }
471            TokenTree::Literal(literal) => {
472                let string = syn::parse_str::<syn::LitStr>(&literal.to_string())
473                    .expect("Invalid Literal")
474                    .value();
475                if let Err(e) = proptest::string::string_regex(&string) {
476                    panic!("{e}: {string}")
477                }
478                Type::CustomString(string)
479            }
480            TokenTree::Punct(p) => {
481                unreachable!("Unexpected punctuation {p}")
482            }
483        }
484    }
485
486    pub(crate) fn from_item(item: &ItemFn) -> Result<Self, String> {
487        let name = item
488            .sig
489            .ident
490            .to_string()
491            .chars()
492            .map(|c| match c {
493                '_' => '-',
494                c => c,
495            })
496            .collect();
497
498        let args = Function::get_args(item);
499
500        let (ret, fallible) = Self::get_output(item)?;
501        Ok(Function { name, args, ret, fallible })
502    }
503
504    fn get_args(item: &ItemFn) -> Vec<ArgType> {
505        let templates = Self::custom_templates(item);
506
507        item.sig
508            .inputs
509            .iter()
510            .map(|x| match x {
511                FnArg::Receiver(syn::Receiver { ty, .. })
512                | FnArg::Typed(syn::PatType { ty, .. }) => ty,
513            })
514            .enumerate()
515            .filter_map(|(i, arg)| {
516                // If a custom template is specified, use it
517                if let Some(Some(template)) = templates.get(i) {
518                    return Some(ArgType::Required(template.clone()));
519                }
520                match arg.as_ref() {
521                    syn::Type::Group(syn::TypeGroup { group_token, elem }) => {
522                        let syn::token::Group { span } = group_token;
523
524                        let source_text = span
525                            .source_text()
526                            .ok_or_else(|| "Failed to get source text".to_string())
527                            .ok()?;
528                        let optional = matches!(source_text.as_str(), "Option");
529                        Self::wrap_arg(optional, elem)
530                    }
531                    syn::Type::Path(syn::TypePath { path, .. }) => {
532                        let segments = &path.segments;
533                        let last = segments.last().unwrap().ident.to_string();
534                        if matches!(last.as_str(), "Result" | "Option") {
535                            let generic_arg = get_generic_arg(segments).ok()?;
536                            Self::wrap_arg(true, generic_arg)
537                        } else {
538                            Self::wrap_arg(false, arg)
539                        }
540                    }
541                    x => Some(ArgType::Required(Function::process_arg(x).ok()?)),
542                }
543            })
544            .collect()
545    }
546
547    fn wrap_arg(optional: bool, ty: &syn::Type) -> Option<ArgType> {
548        let arg = Function::process_arg(ty).ok()?;
549        match arg {
550            Type::Boolean => Some(ArgType::Required(arg)),
551            x => Some(if optional { ArgType::Optional(x) } else { ArgType::Required(x) }),
552        }
553    }
554
555    fn get_output(item: &ItemFn) -> Result<(Option<Type>, bool), String> {
556        Ok(match &item.sig.output {
557            syn::ReturnType::Default => (None, false),
558            syn::ReturnType::Type(_, ty) => match ty.as_ref() {
559                syn::Type::Group(syn::TypeGroup { group_token, elem }) => {
560                    let syn::token::Group { span } = group_token;
561
562                    let source_text = span
563                        .source_text()
564                        .ok_or_else(|| "Failed to get source text".to_string())?;
565                    let fallible = matches!(source_text.as_str(), "Option" | "Result");
566
567                    let ty = Function::process_arg(elem)?;
568                    (Some(ty), fallible)
569                }
570                syn::Type::Path(syn::TypePath { path, .. }) => {
571                    let segments = &path.segments;
572                    let last = segments.last().unwrap().ident.to_string();
573                    match last.as_str() {
574                        "Result" | "Option" => {
575                            let ty = get_generic_arg(segments)?;
576                            let ty = Function::process_arg(ty)?;
577                            (Some(ty), true)
578                        }
579                        _ => {
580                            let ty = Function::process_arg(ty)?;
581                            (Some(ty), false)
582                        }
583                    }
584                }
585                x => {
586                    let ty = Function::process_arg(x)?;
587                    (Some(ty), false)
588                }
589            },
590        })
591    }
592}
593
594fn get_generic_arg<'a>(
595    segments: impl IntoIterator<Item = &'a syn::PathSegment>,
596) -> Result<&'a syn::Type, String> {
597    let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
598        ref args, ..
599    }) = segments.into_iter().last().unwrap().arguments
600    else {
601        unreachable!("Expected angle bracketed arguments");
602    };
603
604    args.iter()
605        .fold(None, |acc, arg| match arg {
606            syn::GenericArgument::Type(ty) => Some(ty),
607            _ => acc,
608        })
609        .ok_or_else(|| "Expected type argument".to_string())
610}