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