• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mattwparas / steel / 17772690385

16 Sep 2025 04:36PM UTC coverage: 43.331% (+0.04%) from 43.289%
17772690385

Pull #519

github

web-flow
Merge 98d4fd22c into 3c10433b9
Pull Request #519: fix a bunch more clippy lints

56 of 123 new or added lines in 30 files covered. (45.53%)

8 existing lines in 3 files now uncovered.

12416 of 28654 relevant lines covered (43.33%)

2985398.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.07
/crates/steel-core/src/parser/expander.rs
1
use crate::compiler::program::{BEGIN, DEFINE, ELLIPSES_SYMBOL, IF, LAMBDA};
2
use crate::parser::ast::{Atom, ExprKind, List, Macro, PatternPair, Vector};
3
use crate::parser::parser::SyntaxObject;
4
use crate::parser::rename_idents::RenameIdentifiersVisitor;
5
use crate::parser::replace_idents::replace_identifiers;
6
use crate::parser::tokens::TokenType;
7

8
use crate::parser::span::Span;
9

10
use crate::rvals::{IntoSteelVal, Result};
11
use std::cell::RefCell;
12
use std::sync::Arc;
13
use std::{
14
    collections::HashMap,
15
    fs::File,
16
    io::{Read, Write},
17
    iter::FromIterator,
18
    path::{Path, PathBuf},
19
};
20

21
use fxhash::{FxHashMap, FxHashSet};
22
use serde::{Deserialize, Serialize};
23
use smallvec::SmallVec;
24
#[cfg(test)]
25
use steel_parser::tokens::IntLiteral;
26
use steel_parser::tokens::NumberLiteral;
27

28
use super::{ast::Quote, interner::InternedString, parser::Parser};
29

30
// Given path, update the extension
31
fn update_extension(mut path: PathBuf, extension: &str) -> PathBuf {
×
32
    path.set_extension(extension);
×
33
    path
×
34
}
35

36
// Prepend the given path with the working directory
37
pub fn path_from_working_dir<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
×
38
    let mut working_dir = std::env::current_dir()?;
×
39
    working_dir.push(path);
×
40
    Ok(working_dir)
×
41
}
42

43
/// Manages macros for a single namespace
44
#[derive(Default, Serialize, Deserialize, Debug)]
45
pub struct LocalMacroManager {
46
    macros: FxHashMap<InternedString, SteelMacro>,
47
}
48

49
impl LocalMacroManager {
50
    /// Look to see if it exists on disk, otherwise parse from the associated file
51
    pub fn initialize_from_path(path: PathBuf, force_update: bool) -> Result<Self> {
×
52
        let raw_path = update_extension(path.clone(), "rkt");
×
53
        let compiled_path = update_extension(path, "macro");
×
54

55
        // println!("Raw path: {:?}", raw_path);
56
        // println!("Compiled path: {:?}", compiled_path);
57

58
        if compiled_path.exists() && !force_update {
×
59
            Self::from_file(compiled_path)
×
60
        } else if raw_path.exists() {
×
61
            let parsed = Self::from_expression_file(raw_path)?;
×
62
            parsed.write_to_file(compiled_path)?;
×
63

64
            Ok(parsed)
×
65
        } else {
66
            stop!(Generic => "path to macro file does not exist")
×
67
        }
68
    }
69

70
    fn write_to_file(&self, path: PathBuf) -> Result<()> {
×
71
        let mut file = File::create(path)?;
×
72

73
        match bincode::serialize(self) {
×
74
            Ok(bytes) => {
×
75
                file.write_all(&bytes)?;
×
76
                Ok(())
×
77
            }
78
            Err(e) => stop!(Generic => format!("Unable to write macro to file: {e:?}")),
×
79
        }
80
    }
81

82
    /// Expand the expressions according to the macros in the current scope
83
    pub fn expand(&self, exprs: &mut Vec<ExprKind>) -> Result<()> {
×
84
        for expr in exprs.iter_mut() {
×
85
            crate::parser::expand_visitor::expand(expr, &self.macros)?
×
86
        }
87

88
        Ok(())
×
89
    }
90

91
    /// Read in a file of expressions containing macros, parse then, and create the data struture
92
    fn from_expression_file(path: PathBuf) -> Result<Self> {
×
93
        let mut file = File::open(path)?;
×
94
        let mut raw_exprs = String::new();
×
95
        file.read_to_string(&mut raw_exprs)?;
×
96
        let parsed_exprs = Parser::parse(&raw_exprs)?;
×
97
        Self::from_exprs(parsed_exprs)
×
98
    }
99

100
    /// After serializing the macro manager to a file and read from that file
101
    fn from_file(path: PathBuf) -> Result<Self> {
×
102
        let mut file = File::open(path)?;
×
103
        let mut buffer = Vec::new();
×
104
        let _ = file.read_to_end(&mut buffer)?;
×
105
        match bincode::deserialize(&buffer) {
×
106
            Ok(inner) => Ok(inner),
×
107
            Err(e) => {
×
108
                stop!(Generic => format!("Unable to deserialize local macro manager from file: {e:?}"))
×
109
            }
110
        }
111
    }
112

113
    /// Initialize the macro manager from a vec of macro expressions
114
    /// Note: this will fail if passed other expressions
115
    pub fn from_exprs(exprs: impl IntoIterator<Item = ExprKind>) -> Result<Self> {
×
116
        exprs
×
117
            .into_iter()
118
            .map(|x| {
×
119
                if let ExprKind::Macro(m) = x {
×
120
                    SteelMacro::parse_from_ast_macro(m)
×
121
                } else {
122
                    stop!(Generic => "Unexpected non-macro definition")
×
123
                }
124
            })
125
            .collect()
126
    }
127
}
128

129
impl FromIterator<SteelMacro> for LocalMacroManager {
130
    fn from_iter<T>(iter: T) -> Self
×
131
    where
132
        T: IntoIterator<Item = SteelMacro>,
133
    {
134
        Self {
135
            macros: iter.into_iter().map(|x| (x.name.clone(), x)).collect(),
×
136
        }
137
    }
138
}
139

140
// Global macro manager, manages macros across modules
141
pub struct GlobalMacroManager {
142
    _scopes: HashMap<PathBuf, HashMap<String, SteelMacro>>,
143
}
144

145
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
146
pub struct SteelMacro {
147
    name: InternedString,
148
    special_forms: Vec<InternedString>,
149
    pub(crate) cases: Vec<MacroCase>,
150
    mangled: bool,
151
    pub(crate) location: Span,
152
    pub(crate) special_mangled: bool,
153
}
154

155
impl SteelMacro {
156
    #[cfg(test)]
157
    pub fn new(
1✔
158
        name: InternedString,
159
        special_forms: Vec<InternedString>,
160
        cases: Vec<MacroCase>,
161
        location: Span,
162
    ) -> Self {
163
        SteelMacro {
164
            name,
165
            special_forms,
166
            cases,
167
            mangled: false,
168
            location,
169
            special_mangled: false,
170
        }
171
    }
172

173
    pub fn name(&self) -> &InternedString {
10,240✔
174
        &self.name
10,240✔
175
    }
176

177
    pub fn exprs_mut(&mut self) -> impl Iterator<Item = &mut ExprKind> {
17,609✔
178
        self.cases.iter_mut().map(|x| &mut x.body)
35,218✔
179
    }
180

181
    pub fn exprs(&self) -> impl Iterator<Item = &ExprKind> {
90,278✔
182
        self.cases.iter().map(|x| &x.body)
180,556✔
183
    }
184

185
    pub fn mark_mangled(&mut self) {
2,975,458✔
186
        self.mangled = true;
2,975,458✔
187
    }
188

189
    pub fn is_mangled(&self) -> bool {
3,440,359✔
190
        self.mangled
3,440,359✔
191
    }
192

193
    pub fn parse_from_ast_macro(mut ast_macro: Box<Macro>) -> Result<Self> {
10,248✔
194
        // HACK: Parse the token as a specific kind of identifier
195
        if let Some(ident) = ast_macro.name.atom_syntax_object_mut() {
20,496✔
196
            if ident.ty == TokenType::DefineSyntax {
197
                ident.ty = TokenType::Identifier("define-syntax".into())
×
198
            }
199
        }
200

201
        let name = *ast_macro
20,496✔
202
            .name
10,248✔
203
            .atom_identifier_or_else(throw!(BadSyntax => "macros only currently support
20,496✔
204
                    identifiers as the name"; ast_macro.location.span))?;
×
205

206
        let sp = ast_macro.location.span;
207

208
        let special_forms = ast_macro
10,248✔
209
            .syntax_rules
210
            .syntax
211
            .into_iter()
212
            .map(|x| {
5,517✔
213
                x.atom_identifier_or_else(
11,034✔
214
                    throw!(BadSyntax => format!("macros only support identifiers for special syntax, found: {}", x); sp),
5,517✔
215
                )
216
                .cloned()
5,517✔
217
            })
218
            .collect::<Result<Vec<_>>>()?;
219

220
        let cases = ast_macro
10,240✔
221
            .syntax_rules
222
            .patterns
223
            .into_iter()
224
            .map(|x| MacroCase::parse_from_pattern_pair(x, &name, &special_forms))
139,416✔
225
            .collect::<Result<Vec<_>>>()?;
226

227
        Ok(SteelMacro {
228
            name,
229
            special_forms,
230
            cases,
231
            mangled: false,
232
            location: ast_macro.location.span,
233
            special_mangled: false,
234
        })
235
    }
236

237
    // TODO the case matching should be a little bit more informed than this
238
    // I think it should also not be greedy, and should report if there are ambiguous matchings
239
    pub(crate) fn match_case(&self, expr: &List) -> Result<&MacroCase> {
256,688✔
240
        for case in &self.cases {
5,028,110✔
241
            // println!("Matching: {:?}", case.args);
242
            if case.recursive_match(expr) {
7,542,156✔
243
                return Ok(case);
256,682✔
244
            }
245
        }
246

247
        if let Some(ExprKind::Atom(a)) = expr.first() {
248
            // for case in &self.cases {
249
            //     println!("{:?}", case.args);
250
            //     println!("{}", expr);
251
            // }
252

253
            stop!(BadSyntax => format!("macro expansion unable to match case: {expr}"); a.syn.span);
254
        } else {
255
            unreachable!()
256
        }
257
    }
258

259
    pub(crate) fn match_case_index(&self, expr: &List) -> Result<(&MacroCase, usize)> {
3✔
260
        // TODO: Ellipses need to not be mangled?
261
        for (index, case) in self.cases.iter().enumerate() {
12✔
262
            if case.recursive_match(expr) {
9✔
263
                return Ok((case, index));
3✔
264
            }
265
        }
266

267
        if let Some(ExprKind::Atom(a)) = expr.first() {
268
            stop!(BadSyntax => format!("macro expansion unable to match case: {expr}"); a.syn.span);
269
        } else {
270
            unreachable!()
271
        }
272
    }
273

274
    pub fn expand(&self, expr: List, span: Span) -> Result<ExprKind> {
256,688✔
275
        let case_to_expand = self.match_case(&expr)?;
1,026,752✔
276
        let expanded_expr = case_to_expand.expand(expr, span)?;
256,682✔
277

278
        Ok(expanded_expr)
279
    }
280
}
281

282
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
283
pub struct PatternList {
284
    args: Vec<MacroPattern>,
285
    improper: bool,
286
}
287

288
impl PatternList {
289
    pub fn new(args: Vec<MacroPattern>) -> Self {
27,822✔
290
        Self {
291
            args,
292
            improper: false,
293
        }
294
    }
295

296
    pub fn with_improper(mut self, improper: bool) -> Self {
62,564✔
297
        self.improper = improper;
62,564✔
298
        self
62,564✔
299
    }
300
}
301

302
#[derive(Clone, Debug, PartialEq)]
303
pub struct PatternListRef<'a> {
304
    args: &'a [MacroPattern],
305
    improper: bool,
306
}
307

308
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
309
pub struct MacroCase {
310
    // TODO: Label this as an improper list
311
    // args: Vec<MacroPattern>,
312
    args: PatternList,
313

314
    pub(crate) body: ExprKind,
315
}
316

317
impl MacroCase {
318
    #[cfg(test)]
319
    pub fn new(args: PatternList, body: ExprKind) -> Self {
1✔
320
        MacroCase { args, body }
321
    }
322

323
    pub fn all_bindings(&self) -> Vec<String> {
3✔
324
        fn walk_bindings(pattern: &MacroPattern, idents: &mut Vec<String>) {
9✔
325
            match pattern {
7✔
NEW
326
                MacroPattern::Rest(r) => walk_bindings(r, idents),
×
327
                MacroPattern::Single(s) | MacroPattern::Quote(s)
7✔
328
                    if *s != InternedString::from_static("_") =>
20✔
329
                {
330
                    idents.push(s.resolve().to_owned());
6✔
331
                }
332
                MacroPattern::Nested(n, _) => {
×
333
                    for p in &n.args {
×
334
                        walk_bindings(p, idents)
335
                    }
336
                }
337
                MacroPattern::Many(pat) => walk_bindings(pat, idents),
8✔
338
                _ => {}
1✔
339
            }
340
        }
341

342
        let mut idents = Vec::new();
6✔
343

344
        for pattern in &self.args.args {
17✔
345
            walk_bindings(pattern, &mut idents)
346
        }
347

348
        idents
3✔
349
    }
350

351
    fn parse_from_pattern_pair(
34,854✔
352
        pattern_pair: PatternPair,
353
        name: &InternedString,
354
        special_forms: &[InternedString],
355
    ) -> Result<Self> {
356
        let PatternPair { pattern, mut body } = pattern_pair;
104,562✔
357

358
        let mut bindings = FxHashSet::default();
69,708✔
359

360
        let ((mut args, macro_keyword), improper) = if let ExprKind::List(l) = pattern {
104,554✔
361
            let improper = l.improper;
362

363
            (
364
                MacroPattern::parse_from_list(l, name, special_forms, &mut bindings, true)?,
8✔
365
                improper,
366
            )
367
        } else {
368
            stop!(Generic => "unable to parse macro");
×
369
        };
370

371
        if let Some(macro_keyword) = macro_keyword {
2,786✔
372
            bindings.remove(&macro_keyword);
373
        };
374

375
        let args_str: SmallVec<[_; 8]> = bindings.iter().copied().collect();
376

377
        // dbg!(args_str.iter().map(|x| x.resolve()).collect::<Vec<_>>());
378

379
        // let syntaxes: Vec<&str> = args
380
        //     .iter()
381
        //     .map(|x| x.deconstruct_syntax())
382
        //     .flatten()
383
        //     .collect();
384

385
        // TODO: @Matt - if the
386
        RenameIdentifiersVisitor::new(&args_str, special_forms).rename_identifiers(&mut body);
387

388
        args.iter_mut().for_each(|x| x.mangle(special_forms));
321,816✔
389

390
        // println!("Found args: {:?}", args);
391
        // println!("Renamed body: {:?}", &body);
392

393
        Ok(MacroCase {
394
            args: PatternList::new(args).with_improper(improper),
395
            body,
396
        })
397
    }
398

399
    fn recursive_match(&self, list: &List) -> bool {
2,514,055✔
400
        match_list_pattern(&self.args.args[1..], &list.args[1..], list.improper)
10,056,220✔
401
    }
402

403
    pub(crate) fn gather_bindings(
3✔
404
        &self,
405
        expr: List,
406
    ) -> Result<(
407
        FxHashMap<InternedString, ExprKind>,
408
        FxHashMap<InternedString, BindingKind>,
409
    )> {
410
        let mut bindings = Default::default();
6✔
411
        let mut binding_kind = Default::default();
6✔
412

413
        collect_bindings(
414
            &self.args.args[1..],
3✔
415
            &expr[1..],
3✔
416
            &mut bindings,
3✔
417
            &mut binding_kind,
3✔
418
            expr.improper,
3✔
419
        )?;
420

421
        Ok((bindings, binding_kind))
3✔
422
    }
423

424
    fn expand(&self, expr: List, span: Span) -> Result<ExprKind> {
256,683✔
425
        thread_local! {
426
            static BINDINGS: RefCell<FxHashMap<InternedString, ExprKind>> = RefCell::new(FxHashMap::default());
427
            static BINDINGS_KIND: RefCell<FxHashMap<InternedString, BindingKind>> = RefCell::new(FxHashMap::default());
428
            static FALLBACK_BINDINGS: RefCell<FxHashMap<InternedString, ExprKind>> = RefCell::new(FxHashMap::default());
429
        }
430

431
        BINDINGS.with(|bindings| {
770,049✔
432
            BINDINGS_KIND.with(|binding_kind| {
770,049✔
433
                FALLBACK_BINDINGS.with(|fallback_bindings| {
770,049✔
434
                    let mut bindings = bindings.borrow_mut();
770,049✔
435
                    let mut binding_kind = binding_kind.borrow_mut();
770,049✔
436
                    let mut fallback_bindings = fallback_bindings.borrow_mut();
770,049✔
437

438
                    bindings.clear();
256,683✔
439
                    binding_kind.clear();
256,683✔
440
                    fallback_bindings.clear();
256,683✔
441

442
                    collect_bindings(
256,683✔
443
                        &self.args.args[1..],
256,683✔
444
                        &expr[1..],
256,683✔
445
                        &mut bindings,
256,683✔
446
                        &mut binding_kind,
256,683✔
447
                        expr.improper,
256,683✔
448
                    )?;
449

450
                    let mut body = self.body.clone();
256,683✔
451

452
                    replace_identifiers(
453
                        &mut body,
454
                        &mut bindings,
455
                        &mut binding_kind,
456
                        &mut fallback_bindings,
457
                        span,
458
                    )?;
459

460
                    Ok(body)
256,683✔
461
                })
462
            })
463
        })
464

465
        // TODO: Consider using a thread local allocation, and just
466
        // clear the hashmap after each use?
467
        // let mut bindings = FxHashMap::default();
468
        // let mut binding_kind = FxHashMap::default();
469
        // let mut fallback_bindings = FxHashMap::default();
470
        // collect_bindings(
471
        //     &self.args[1..],
472
        //     &expr[1..],
473
        //     &mut bindings,
474
        //     &mut binding_kind,
475
        // )?;
476
        // replace_identifiers(
477
        //     self.body.clone(),
478
        //     &mut bindings,
479
        //     &mut binding_kind,
480
        //     &mut fallback_bindings,
481
        //     span,
482
        // )
483
    }
484
}
485

486
#[derive(Clone, PartialEq, Serialize, Deserialize)]
487
pub enum MacroPattern {
488
    Rest(Box<MacroPattern>),
489
    Single(InternedString),
490
    Syntax(InternedString),
491
    Many(Box<MacroPattern>),
492
    Nested(PatternList, bool),
493
    CharacterLiteral(char),
494
    BytesLiteral(Vec<u8>),
495
    NumberLiteral(NumberLiteral),
496
    StringLiteral(Arc<String>),
497
    BooleanLiteral(bool),
498
    QuotedExpr(Box<Quote>),
499
    Quote(InternedString),
500
    Keyword(InternedString),
501
}
502

503
impl std::fmt::Debug for MacroPattern {
504
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1✔
505
        match self {
1✔
506
            MacroPattern::Single(s) => f.debug_tuple("Single").field(&s.resolve()).finish(),
×
507
            MacroPattern::Syntax(s) => f.debug_tuple("Syntax").field(&s.resolve()).finish(),
6✔
508
            MacroPattern::Many(m) => f.debug_tuple("Many").field(&m).finish(),
×
509
            MacroPattern::Nested(n, v) => f.debug_tuple("Nested").field(n).field(v).finish(),
×
510
            MacroPattern::CharacterLiteral(c) => {
×
511
                f.debug_tuple("CharacterLiteral").field(c).finish()
×
512
            }
513
            MacroPattern::NumberLiteral(n) => f.debug_tuple("NumberLiteral").field(n).finish(),
×
514
            MacroPattern::StringLiteral(s) => f.debug_tuple("StringLiteral").field(s).finish(),
×
515
            MacroPattern::BooleanLiteral(b) => f.debug_tuple("BooleanLiteral").field(b).finish(),
×
516
            MacroPattern::QuotedExpr(s) => f.debug_tuple("QuotedExpr").field(s).finish(),
×
517
            MacroPattern::Quote(i) => f.debug_tuple("Quote").field(&i.resolve()).finish(),
×
518
            MacroPattern::Keyword(k) => f.debug_tuple("Keyword").field(k).finish(),
×
519
            MacroPattern::Rest(r) => f.debug_tuple("Rest").field(r).finish(),
×
520
            MacroPattern::BytesLiteral(v) => f.debug_tuple("BytesLiteral").field(v).finish(),
×
521
        }
522
    }
523
}
524

525
impl MacroPattern {
526
    pub fn is_many(&self) -> bool {
5,600,059✔
527
        matches!(self, MacroPattern::Many(..))
10,312,597✔
528
    }
529

530
    fn mangle(&mut self, special_forms: &[InternedString]) {
191,425✔
531
        use MacroPattern::*;
532
        match self {
191,425✔
533
            Single(s) => {
85,200✔
534
                let special = special_forms.contains(s) || *s == InternedString::from_static("_");
426,000✔
535

536
                if !special {
85,200✔
537
                    *self = Single(("##".to_string() + s.resolve()).into())
82,848✔
538
                }
539
            }
540
            Nested(v, _) => {
28,364✔
541
                v.args.iter_mut().for_each(|x| x.mangle(special_forms));
224,701✔
542
            }
543
            Many(pat) => pat.mangle(special_forms),
109,176✔
544
            Rest(v) => {
868✔
545
                v.mangle(special_forms);
546
            }
547
            _ => {}
49,699✔
548
        }
549
    }
550

551
    fn parse_from_list(
63,224✔
552
        list: List,
553
        macro_name: &InternedString,
554
        special_forms: &[InternedString],
555
        bindings: &mut FxHashSet<InternedString>,
556
        top_level: bool,
557
    ) -> Result<(Vec<MacroPattern>, Option<InternedString>)> {
558
        let mut pattern_vec: Vec<MacroPattern> = Vec::with_capacity(4);
189,672✔
559
        let len = list.args.len();
189,672✔
560
        let mut exprs_iter = list.args.into_iter().enumerate().peekable();
316,120✔
561
        let mut ellipsis = false;
126,448✔
562

563
        let wildcard = InternedString::from_static("_");
126,448✔
564

565
        macro_rules! add_binding {
566
            ($key:expr, $span:expr) => {
567
                if $key != wildcard && !bindings.insert($key) {
568
                    let var = $key.resolve();
569
                    stop!(BadSyntax => "repeated pattern variable {}", var ; $span);
570
                }
571
            };
572
        }
573

574
        macro_rules! check_ellipsis {
575
            ($improper:expr, $span:expr) => {
576
                if ellipsis {
577
                    stop!(BadSyntax => "pattern with more than one ellipsis"; $span);
578
                } else if $improper {
579
                    stop!(BadSyntax => "ellipsis cannot appear as list tail"; $span);
580
                }
581

582
                ellipsis = true;
583
            }
584
        }
585

586
        let mut macro_keyword = None;
126,448✔
587

588
        while let Some((i, expr)) = exprs_iter.next() {
390,251✔
589
            match expr {
590
                ExprKind::Atom(Atom {
591
                    syn: SyntaxObject { ty: s, span, .. },
135,145✔
592
                }) => match s {
593
                    TokenType::Identifier(t) => {
130,698✔
594
                        if t == *macro_name || special_forms.contains(&t) {
229,335✔
595
                            pattern_vec.push(MacroPattern::Syntax(t))
45,481✔
596
                        } else {
597
                            let peek = exprs_iter.peek().map(|(_, expr)| expr);
85,217✔
598

599
                            let ellipsis_span = match peek {
28,686✔
600
                                Some(ExprKind::Atom(Atom {
601
                                    syn:
602
                                        SyntaxObject {
603
                                            ty: TokenType::Ellipses,
604
                                            span,
27,066✔
605
                                            ..
606
                                        },
607
                                })) => Some(*span),
608

609
                                // HACK: Somewhere, the ellipses are getting converted back into the raw
610
                                // form when doing syntax-case expansion. This is duplicated code of the
611
                                // above.
612
                                Some(ExprKind::Atom(Atom {
613
                                    syn:
614
                                        SyntaxObject {
615
                                            ty: TokenType::Identifier(ty),
28,686✔
616
                                            span,
28,686✔
617
                                            ..
618
                                        },
619
                                })) if *ty == *ELLIPSES_SYMBOL => Some(*span),
28,690✔
620

621
                                _ => None,
58,149✔
622
                            };
623

624
                            if let Some(span) = ellipsis_span {
27,068✔
625
                                let last_proper = list.improper && i + 2 == len;
217✔
626
                                check_ellipsis!(last_proper, span);
27,068✔
627

628
                                exprs_iter.next();
27,066✔
629

630
                                if i == 0 && top_level {
1,300✔
631
                                    stop!(BadSyntax => "macro name cannot be followed by ellipsis"; span);
6✔
632
                                }
633

634
                                add_binding!(t, span);
×
635
                                pattern_vec.push(MacroPattern::Many(MacroPattern::Single(t).into()))
27,064✔
636
                            } else {
637
                                if i == 0 && top_level {
73,630✔
638
                                    macro_keyword = Some(t);
2,791✔
639
                                }
640

641
                                add_binding!(t, span);
58,151✔
642
                                pattern_vec.push(MacroPattern::Single(t));
58,147✔
643
                            }
644
                        }
645
                    }
646
                    // TODO: Add all of the tokens here
647
                    TokenType::Begin => {
×
648
                        pattern_vec.push(MacroPattern::Syntax(*BEGIN));
×
649
                    }
650
                    TokenType::Define => {
860✔
651
                        pattern_vec.push(MacroPattern::Syntax(*DEFINE));
1,720✔
652
                    }
653
                    TokenType::Lambda => {
×
654
                        pattern_vec.push(MacroPattern::Syntax(*LAMBDA));
×
655
                    }
656
                    TokenType::If => {
×
657
                        pattern_vec.push(MacroPattern::Syntax(*IF));
×
658
                    }
659
                    TokenType::BooleanLiteral(b) => {
8✔
660
                        pattern_vec.push(MacroPattern::BooleanLiteral(b));
8✔
661
                    }
662
                    TokenType::Number(n) => {
4✔
663
                        pattern_vec.push(MacroPattern::NumberLiteral(*n));
4✔
664
                    }
665
                    TokenType::CharacterLiteral(c) => {
×
666
                        pattern_vec.push(MacroPattern::CharacterLiteral(c));
×
667
                    }
668
                    TokenType::StringLiteral(s) => {
3,894✔
669
                        pattern_vec.push(MacroPattern::StringLiteral(s));
3,894✔
670
                    }
671
                    TokenType::Keyword(k) => {
1,512✔
672
                        pattern_vec.push(MacroPattern::Keyword(k));
1,512✔
673
                    }
674
                    // TODO: Crunch the quoted values here, and pull them in so that this
675
                    // holds a body of possible quoted values
676
                    TokenType::Quote => {
677
                        // pattern_vec.push(MacroPattern::Quote);
678
                        // match peek_token_iter.peek() {
679
                        //     Some(ExprKind::Atom(Atom {
680
                        //         syn:
681
                        //             SyntaxObject {
682
                        //                 ty: TokenType::Ellipses,
683
                        //                 ..
684
                        //             },
685
                        //     })) => {
686
                        //         peek_token_iter.next();
687
                        //         pattern_vec.push(MacroPattern::Many(t.clone()));
688
                        //     }
689
                        //     _ => {
690
                        //         pattern_vec.push(MacroPattern::Single(t.clone()));
691
                        //     }
692
                        // }
693

694
                        let next = exprs_iter.next().map(|(_, expr)| expr);
2,580✔
695

696
                        match next {
645✔
697
                            Some(ExprKind::Atom(Atom {
698
                                syn:
699
                                    SyntaxObject {
700
                                        ty: TokenType::Identifier(s),
645✔
701
                                        ..
702
                                    },
703
                            })) => {
704
                                pattern_vec.push(MacroPattern::Quote(s));
705
                            }
706
                            _ => {
707
                                stop!(TypeMismatch => "syntax-rules with quote don't
×
708
                                    yet support arbitrary constants")
709
                                // pattern_vec.push(MacroPattern::Single(t.clone()));
710
                            }
711
                        }
712
                    }
713
                    TokenType::Ellipses => {
714
                        let last = pattern_vec.pop();
699✔
715

716
                        match last {
233✔
717
                            Some(MacroPattern::Nested(inner, vec)) => {
231✔
718
                                let improper = list.improper && i + 1 == len;
4✔
719
                                check_ellipsis!(improper, span);
232✔
720

721
                                pattern_vec.push(MacroPattern::Many(
230✔
722
                                    MacroPattern::Nested(inner, vec).into(),
723
                                ));
724
                            }
725

726
                            Some(
727
                                pat @ (MacroPattern::BytesLiteral(..)
1✔
728
                                | MacroPattern::CharacterLiteral(..)
729
                                | MacroPattern::StringLiteral(..)
730
                                | MacroPattern::NumberLiteral(..)
731
                                | MacroPattern::BooleanLiteral(..)),
732
                            ) => {
1✔
733
                                pattern_vec.push(MacroPattern::Many(pat.into()));
3✔
734
                            }
735

736
                            Some(other) => {
1✔
737
                                stop!(BadSyntax => format!("cannot bind pattern to ellipsis: {:?}", other); span)
4✔
738
                            }
739
                            None => {
740
                                stop!(BadSyntax => "cannot bind pattern to ellipsis"; span)
×
741
                            }
742
                        }
743
                    }
744
                    _ => {
745
                        stop!(BadSyntax => "syntax-rules requires identifiers in the pattern"; span);
×
746
                    }
747
                },
748
                ExprKind::List(l) => {
27,723✔
749
                    let improper = l.improper;
55,446✔
750
                    let (patterns, _) =
27,718✔
751
                        Self::parse_from_list(l, macro_name, special_forms, bindings, false)?;
138,620✔
752

753
                    pattern_vec.push(MacroPattern::Nested(
754
                        PatternList::new(patterns).with_improper(improper),
755
                        false,
756
                    ))
757
                }
758
                ExprKind::Quote(q) => pattern_vec.push(MacroPattern::QuotedExpr(q)),
20✔
759
                ExprKind::Vector(Vector {
760
                    args, bytes: false, ..
647✔
761
                }) => {
762
                    let list = List::new(args);
763

764
                    let (patterns, _) =
647✔
765
                        Self::parse_from_list(list, macro_name, special_forms, bindings, false)?;
×
766

767
                    pattern_vec.push(MacroPattern::Nested(PatternList::new(patterns), true))
768
                }
769
                ExprKind::Vector(v @ Vector { bytes: true, .. }) => {
×
770
                    let bytes = v.as_bytes().collect();
×
771

772
                    pattern_vec.push(MacroPattern::BytesLiteral(bytes));
×
773
                }
774
                _ => {
775
                    // TODO: Add pattern matching on other kinds of forms here - probably just a special IR
776
                    // for the pattern to match against here
777
                    stop!(BadSyntax => format!("illegal special form found in macro pattern: {}", expr) ; expr.span());
×
778
                }
779
            }
780
        }
781

782
        if list.improper {
63,211✔
783
            let rest = pattern_vec.pop().map(|pat| MacroPattern::Rest(pat.into()));
5,208✔
784
            pattern_vec.extend(rest);
2,604✔
785
        }
786

787
        Ok((pattern_vec, macro_keyword))
63,211✔
788
    }
789

790
    fn variables(&self) -> FxHashSet<InternedString> {
39,648✔
791
        let mut vars = Default::default();
79,296✔
792

793
        fn accumulate_vars(pattern: &MacroPattern, acc: &mut FxHashSet<InternedString>) {
39,650✔
794
            match pattern {
39,650✔
795
                MacroPattern::Many(pat) | MacroPattern::Rest(pat) => {
×
796
                    accumulate_vars(pat, acc);
×
797
                }
798
                MacroPattern::Single(ident) => {
79,298✔
799
                    acc.insert(*ident);
79,298✔
800
                }
801
                MacroPattern::Nested(pats, _) => {
1✔
802
                    for pat in &pats.args {
5✔
803
                        accumulate_vars(pat, acc);
804
                    }
805
                }
806
                _ => {}
×
807
            }
808
        }
809

810
        accumulate_vars(self, &mut vars);
118,944✔
811

812
        vars
39,648✔
813
    }
814
}
815

816
fn match_list_pattern(patterns: &[MacroPattern], list: &[ExprKind], improper: bool) -> bool {
4,185,217✔
817
    let (proper_patterns, rest_pattern) = match patterns.split_last() {
16,740,868✔
818
        Some((MacroPattern::Rest(pat), rest)) => (rest, Some(&**pat)),
2,170✔
819
        _ => (patterns, None),
4,183,047✔
820
    };
821

822
    let proper_list = if improper {
8,370,434✔
823
        &list[..(list.len() - 1)]
426✔
824
    } else {
825
        list
4,185,004✔
826
    };
827

828
    let has_ellipsis = patterns.iter().any(|pat| pat.is_many());
23,755,347✔
829
    let multi_arity = has_ellipsis || rest_pattern.is_some();
11,668,138✔
830

831
    let len_matches = if multi_arity {
8,370,434✔
832
        proper_list.len() + 1 >= proper_patterns.len()
1,779,364✔
833
    } else {
834
        proper_list.len() == proper_patterns.len()
3,295,535✔
835
    };
836

837
    if !len_matches {
4,185,217✔
838
        return false;
912,083✔
839
    }
840

841
    let unmatched_tail = if has_ellipsis {
6,546,268✔
842
        &list[proper_list.len()..]
1,343,874✔
843
    } else {
844
        list.get(proper_patterns.len()..).unwrap_or(&[])
2,601,197✔
845
    };
846

847
    let tail_to_match = match rest_pattern {
6,544,095✔
848
        None if unmatched_tail.is_empty() => None,
6,541,922✔
849
        None => return false,
3✔
850
        Some(pat) => Some((pat, unmatched_tail)),
4,340✔
851
    };
852

853
    let mut exprs_iter = proper_list.iter().enumerate();
854

855
    let expected_many_captures = proper_list.len() + 1 - proper_patterns.len();
856

857
    for pat in proper_patterns {
7,599,604✔
858
        match &pat {
193,012✔
859
            MacroPattern::Many(subpat) if has_ellipsis => {
386,024✔
860
                for _ in 0..expected_many_captures {
193,012✔
861
                    let Some((_, item)) = exprs_iter.next() else {
604,594✔
862
                        return false;
×
863
                    };
864

865
                    if !match_single_pattern(subpat, item) {
866
                        return false;
1✔
867
                    }
868
                }
869
                continue;
193,011✔
870
            }
871
            _ => {}
3,386,467✔
872
        }
873

874
        let Some((_, expr)) = exprs_iter.next() else {
3,386,467✔
875
            return false;
×
876
        };
877

878
        if !match_single_pattern(pat, expr) {
879
            return false;
2,832,484✔
880
        }
881
    }
882

883
    if let Some((pat, exprs)) = tail_to_match {
442,815✔
884
        let tail_matches = if improper && exprs.len() == 1 {
209✔
885
            match_single_pattern(pat, &exprs[0])
621✔
886
        } else {
887
            match_rest_pattern(pat, exprs, improper)
1,962✔
888
        };
889

890
        if !tail_matches {
891
            return false;
1✔
892
        }
893
    }
894

895
    true
440,645✔
896
}
897

898
fn match_rest_pattern(pattern: &MacroPattern, exprs: &[ExprKind], improper: bool) -> bool {
1,962✔
899
    match pattern {
1,962✔
900
        MacroPattern::Single(_) => true,
1,962✔
901
        MacroPattern::Nested(patterns, false) => {
×
902
            match_list_pattern(&patterns.args, exprs, improper)
×
903
        }
904
        _ => false,
×
905
    }
906
}
907

908
fn match_single_pattern(pattern: &MacroPattern, expr: &ExprKind) -> bool {
3,688,975✔
909
    match pattern {
3,688,975✔
910
        MacroPattern::Many(_) => unreachable!(),
911
        MacroPattern::Rest(_) => unreachable!(),
912
        MacroPattern::Single(_) => true,
600,898✔
913
        MacroPattern::Syntax(v) => match expr {
260,027✔
914
            ExprKind::Atom(Atom {
915
                syn:
916
                    SyntaxObject {
917
                        ty: TokenType::Identifier(s),
173,118✔
918
                        ..
919
                    },
920
            }) if s == v => true,
301,486✔
921
            ExprKind::Atom(Atom {
922
                syn:
923
                    SyntaxObject {
924
                        ty: TokenType::Define,
925
                        ..
926
                    },
927
            }) if *v == *DEFINE => true,
8,853✔
928
            ExprKind::Atom(Atom {
929
                syn:
930
                    SyntaxObject {
931
                        ty: TokenType::Begin,
932
                        ..
933
                    },
934
            }) if *v == *BEGIN => true,
×
935
            ExprKind::Atom(Atom {
936
                syn: SyntaxObject {
937
                    ty: TokenType::If, ..
938
                },
939
            }) if *v == *IF => true,
×
940
            ExprKind::Atom(Atom {
941
                syn:
942
                    SyntaxObject {
943
                        ty: TokenType::Ellipses,
944
                        ..
945
                    },
946
            }) => true,
×
947
            _ => false,
192,892✔
948
        },
949
        MacroPattern::Keyword(k) => match expr {
3✔
950
            ExprKind::Atom(Atom {
951
                syn:
952
                    SyntaxObject {
953
                        ty: TokenType::Keyword(s),
×
954
                        ..
955
                    },
956
            }) if s == k => true,
×
957
            _ => false,
3✔
958
        },
959
        MacroPattern::BooleanLiteral(b) => match expr {
9✔
960
            ExprKind::Atom(Atom {
961
                syn:
962
                    SyntaxObject {
963
                        ty: TokenType::BooleanLiteral(s),
4✔
964
                        ..
965
                    },
966
            }) if s == b => true,
12✔
967
            _ => false,
1✔
968
        },
969
        MacroPattern::NumberLiteral(n) => match expr {
6✔
970
            ExprKind::Atom(Atom {
971
                syn:
972
                    SyntaxObject {
973
                        ty: TokenType::Number(t),
3✔
974
                        ..
975
                    },
976
            }) => {
977
                let Ok(n) = n.into_steelval() else {
3✔
978
                    return false;
×
979
                };
980

981
                let Ok(t) = t.clone().into_steelval() else {
3✔
982
                    return false;
×
983
                };
984

985
                n == t
986
            }
987
            _ => false,
×
988
        },
989
        MacroPattern::CharacterLiteral(c) => match expr {
×
990
            ExprKind::Atom(Atom {
991
                syn:
992
                    SyntaxObject {
993
                        ty: TokenType::CharacterLiteral(s),
×
994
                        ..
995
                    },
996
            }) if s == c => true,
×
997
            _ => false,
×
998
        },
999
        MacroPattern::StringLiteral(s) => match expr {
7,096✔
1000
            ExprKind::Atom(Atom {
1001
                syn:
1002
                    SyntaxObject {
1003
                        ty: TokenType::StringLiteral(b),
3,548✔
1004
                        ..
1005
                    },
1006
            }) if s.as_str() == b.as_str() => true,
17,728✔
1007
            _ => false,
6✔
1008
        },
1009
        MacroPattern::BytesLiteral(v) => match expr {
×
1010
            ExprKind::Vector(Vector {
1011
                args, bytes: true, ..
×
1012
            }) if args.len() == v.len() => {
×
1013
                let byte_atoms = args.iter().map(|expr| {
×
1014
                    if let ExprKind::Atom(atom) = expr {
×
1015
                        atom.byte()
1016
                    } else {
1017
                        None
×
1018
                    }
1019
                });
1020

1021
                byte_atoms
×
1022
                    .zip(v.iter().copied())
×
1023
                    .all(|(atom, byte)| atom == Some(byte))
×
1024
            }
1025
            _ => false,
×
1026
        },
1027
        MacroPattern::QuotedExpr(q) => {
682✔
1028
            // println!("MATCHING QUOTED EXPR: {}", q);
1029
            match expr {
×
1030
                ExprKind::Quote(boxed_q) if q == boxed_q => true,
×
1031
                _ => false,
682✔
1032
            }
1033
        }
1034
        MacroPattern::Quote(_q) => {
39,135✔
1035
            // println!("MATCHING QUOTE {} with val: {}", q, val);
1036
            match expr {
39,135✔
1037
                ExprKind::Quote(_) => true,
×
1038

1039
                // ExprKind::Atom(Atom {
1040
                //     syn:
1041
                //         SyntaxObject {
1042
                //             ty: TokenType::Quote | TokenType::QuoteTick,
1043
                //             ..
1044
                //         },
1045
                // }) => {
1046
                //     println!("MATCHING ON QUOTE");
1047
                //     true;
1048
                // }
1049

1050
                // ExprKind::Atom(Atom {
1051
                //     syn:
1052
                //         SyntaxObject {
1053
                //             ty: TokenType::Identifier(s),
1054
                //             ..
1055
                //         },
1056
                // }) if s.resolve() == "QUOTE" => {
1057
                //     println!("MATCHING ON QUOTE");
1058
                //     true;
1059
                // }
1060
                // ExprKind::Quote()
1061
                _ => false,
39,135✔
1062
            }
1063
        }
1064
        MacroPattern::Nested(patterns, is_vec) => {
5,569,348✔
1065
            match expr {
2,784,674✔
1066
                // Make the recursive call on the next layer
1067
                ExprKind::List(l) => !is_vec && match_list_pattern(&patterns.args, l, l.improper),
10,527,810✔
1068
                ExprKind::Vector(v) => {
15✔
1069
                    *is_vec && !v.bytes && match_list_pattern(&patterns.args, &v.args, false)
31✔
1070
                }
1071
                // TODO: Come back here
1072
                ExprKind::Quote(_) => {
1073
                    !is_vec && matches!(patterns.args.as_slice(), &[MacroPattern::Quote(_)])
4,745✔
1074
                }
1075
                _ => {
1076
                    if let Some(pat) = non_list_match(&patterns.args) {
862,111✔
1077
                        match_single_pattern(pat, expr)
1078
                    } else {
1079
                        false
862,103✔
1080
                    }
1081
                }
1082
            }
1083
        }
1084
    }
1085
}
1086

1087
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1088
pub enum BindingKind {
1089
    Many,
1090
    Single,
1091
}
1092

1093
fn collect_bindings(
745,113✔
1094
    patterns: &[MacroPattern],
1095
    list: &[ExprKind],
1096
    bindings: &mut FxHashMap<InternedString, ExprKind>,
1097
    binding_kind: &mut FxHashMap<InternedString, BindingKind>,
1098
    improper: bool,
1099
) -> Result<()> {
1100
    let mut expr_iter = list.iter().enumerate();
2,980,452✔
1101

1102
    let expected_many_captures = list.len() + 1 - patterns.len();
2,235,339✔
1103

1104
    for pattern in patterns {
2,745,951✔
1105
        match pattern {
193,011✔
1106
            // bind the expression to a single variable
1107
            MacroPattern::Single(s) => {
577,110✔
1108
                if let Some((_, e)) = expr_iter.next() {
1,154,220✔
1109
                    bindings.insert(*s, e.clone());
1110
                } else {
1111
                    stop!(ArityMismatch => "macro invocation expected a value, found none");
×
1112
                }
1113
            }
1114
            // actually check if the syntax matches
1115
            MacroPattern::Syntax(s) => {
39,686✔
1116
                let error_func = throw!(BadSyntax => format!("macro expand expected keyword {s} - within {:?}", list));
79,372✔
1117

1118
                let (_, e) = expr_iter.next().ok_or_else(error_func)?;
198,430✔
1119

1120
                if let ExprKind::Atom(Atom {
1121
                    syn:
1122
                        SyntaxObject {
1123
                            ty: TokenType::Identifier(syn),
37,332✔
1124
                            ..
1125
                        },
1126
                }) = e
39,686✔
1127
                {
1128
                    if s != syn {
1129
                        stop!(BadSyntax => "macro expansion expected keyword, found: {}", syn)
×
1130
                    }
1131
                }
1132
            }
1133
            MacroPattern::Many(pat) if expected_many_captures == 0 => {
425,670✔
1134
                for ident in pat.variables() {
118,945✔
1135
                    bindings.insert(ident, List::new(vec![]).into());
1136
                    binding_kind.insert(ident, BindingKind::Many);
1137
                }
1138
            }
1139
            MacroPattern::Many(pat) => {
153,363✔
1140
                let mut nested_bindings = FxHashMap::default();
1141
                let mut list_bindings = FxHashMap::default();
1142

1143
                for _ in 0..expected_many_captures {
1144
                    if let Some((_, expr)) = expr_iter.next() {
604,592✔
1145
                        collect_bindings(
1146
                            std::slice::from_ref(pat),
1147
                            std::slice::from_ref(expr),
1148
                            &mut nested_bindings,
1149
                            binding_kind,
1150
                            false,
1151
                        )?;
1152
                    }
1153

1154
                    for (ident, captured) in nested_bindings.drain() {
604,694✔
1155
                        list_bindings.entry(ident).or_insert(vec![]).push(captured);
1156
                    }
1157
                }
1158

1159
                for (ident, captured_list) in list_bindings {
460,243✔
1160
                    bindings.insert(ident, List::new(captured_list).into());
1161
                    binding_kind.insert(ident, BindingKind::Many);
1162
                }
1163
            }
1164
            MacroPattern::Nested(children, is_vec) => {
369,816✔
1165
                let (_, child) = expr_iter
369,816✔
1166
                    .next()
1167
                    .ok_or_else(throw!(ArityMismatch => "Macro expected a pattern"))?;
369,816✔
1168

1169
                match child {
1170
                    ExprKind::List(l) => {
183,953✔
1171
                        if !is_vec {
183,953✔
1172
                            collect_bindings(
1173
                                &children.args,
183,953✔
1174
                                l,
183,953✔
1175
                                bindings,
183,953✔
1176
                                binding_kind,
183,953✔
1177
                                l.improper,
183,953✔
1178
                            )?;
1179
                        }
1180
                    }
1181
                    ExprKind::Vector(v) => {
2✔
1182
                        if *is_vec {
2✔
1183
                            collect_bindings(
1184
                                &children.args,
2✔
1185
                                &v.args,
2✔
1186
                                bindings,
2✔
1187
                                binding_kind,
2✔
1188
                                false,
1189
                            )?;
1190
                        }
1191
                    }
1192
                    ExprKind::Quote(q) => {
949✔
1193
                        if let &[MacroPattern::Quote(x)] = children.args.as_slice() {
1,898✔
1194
                            bindings.insert(x, q.expr.clone());
3,796✔
1195
                        } else {
1196
                            stop!(BadSyntax => "macro expected a list of values, not including keywords, found: {}", child)
×
1197
                        }
1198
                    }
1199
                    _ => {
1200
                        if let Some(pat) = non_list_match(&children.args) {
8✔
1201
                            collect_bindings(
1202
                                std::slice::from_ref(pat),
1203
                                std::slice::from_ref(child),
1204
                                bindings,
1205
                                binding_kind,
1206
                                false,
1207
                            )?;
1208
                        } else {
1209
                            stop!(BadSyntax => "macro expected a list of values, not including keywords, found: {}", child)
×
1210
                        }
1211
                    }
1212
                }
1213
            }
1214
            MacroPattern::Quote(s) => {
×
1215
                if let Some((_, e)) = expr_iter.next() {
×
1216
                    if let ExprKind::Quote(inner) = e {
×
1217
                        bindings.insert(*s, inner.expr.clone());
1218
                    } else {
1219
                        stop!(Generic => "something went wrong with macro expansion")
×
1220
                    }
1221
                } else {
1222
                    stop!(ArityMismatch => "macro invocation expected a value, found none");
×
1223
                }
1224
            }
1225

1226
            MacroPattern::Rest(pat) => {
2,168✔
1227
                let list = match expr_iter.next() {
1228
                    Some((i, expr)) if improper && i + 1 == list.len() => [expr.clone()],
3,613✔
1229
                    Some((i, _)) => {
1,959✔
1230
                        let list = List::new_maybe_improper(list[i..].to_vec(), improper);
1231

1232
                        [ExprKind::List(list)]
1233
                    }
1234
                    None => [ExprKind::List(List::new(vec![]))],
3✔
1235
                };
1236

1237
                collect_bindings(&[(**pat).clone()], &list, bindings, binding_kind, false)?;
×
1238
            }
1239

1240
            // Matching on literals
1241
            _ => {
3,536✔
1242
                expr_iter.next();
3,536✔
1243
            }
1244
        }
1245
    }
1246

1247
    Ok(())
745,113✔
1248
}
1249

1250
#[cfg(test)]
1251
mod match_list_pattern_tests {
1252
    use steel_parser::tokens::RealLiteral;
1253

1254
    use super::*;
1255

1256
    fn atom(t: TokenType<InternedString>) -> ExprKind {
1257
        ExprKind::Atom(Atom::new(SyntaxObject::default(t)))
1258
    }
1259

1260
    fn atom_identifier(s: &str) -> ExprKind {
1261
        atom(TokenType::Identifier(s.into()))
1262
    }
1263

1264
    fn atom_int(n: isize) -> ExprKind {
1265
        atom(IntLiteral::Small(n).into())
1266
    }
1267

1268
    #[test]
1269
    fn test_match_basic() {
1270
        let pattern_args = vec![
1271
            MacroPattern::Syntax("and".into()),
1272
            MacroPattern::Single("a".into()),
1273
            MacroPattern::Single("b".into()),
1274
        ];
1275
        let list_expr = List::new(vec![
1276
            atom_identifier("and"),
1277
            ExprKind::List(List::new(vec![
1278
                atom_identifier("+"),
1279
                atom_identifier("x"),
1280
                atom_identifier("y"),
1281
            ])),
1282
            ExprKind::List(List::new(vec![
1283
                atom_identifier("+"),
1284
                atom_identifier("x"),
1285
                atom_identifier("y"),
1286
            ])),
1287
        ]);
1288

1289
        assert!(match_list_pattern(&pattern_args, &list_expr, false));
1290
    }
1291

1292
    #[test]
1293
    fn test_match_many() {
1294
        let pattern_args = vec![
1295
            MacroPattern::Syntax("and".into()),
1296
            MacroPattern::Single("a".into()),
1297
            MacroPattern::Many(MacroPattern::Single("b".into()).into()),
1298
        ];
1299
        let list_expr = List::new(vec![
1300
            atom_identifier("and"),
1301
            ExprKind::List(List::new(vec![
1302
                atom_identifier("+"),
1303
                atom_identifier("x"),
1304
                atom_identifier("y"),
1305
            ])),
1306
            ExprKind::List(List::new(vec![
1307
                atom_identifier("+"),
1308
                atom_identifier("x"),
1309
                atom_identifier("y"),
1310
            ])),
1311
        ]);
1312
        assert!(match_list_pattern(&pattern_args, &list_expr, false));
1313
    }
1314

1315
    #[test]
1316
    fn test_match_many_multiple() {
1317
        let pattern_args = vec![
1318
            MacroPattern::Syntax("and".into()),
1319
            MacroPattern::Single("a".into()),
1320
            MacroPattern::Many(MacroPattern::Single("b".into()).into()),
1321
        ];
1322
        let list_expr = List::new(vec![
1323
            atom_identifier("and"),
1324
            ExprKind::List(List::new(vec![
1325
                atom_identifier("+"),
1326
                atom_identifier("x"),
1327
                atom_identifier("y"),
1328
            ])),
1329
            atom_identifier("+"),
1330
            atom_identifier("x"),
1331
            atom_identifier("y"),
1332
        ]);
1333
        assert!(match_list_pattern(&pattern_args, &list_expr, false));
1334
    }
1335

1336
    #[test]
1337
    fn test_nested() {
1338
        let pattern_args = vec![
1339
            MacroPattern::Syntax("->>".into()),
1340
            MacroPattern::Single("a".into()),
1341
            MacroPattern::Nested(
1342
                PatternList::new(vec![
1343
                    MacroPattern::Single("b".into()),
1344
                    MacroPattern::Many(MacroPattern::Single("c".into()).into()),
1345
                ]),
1346
                false,
1347
            ),
1348
        ];
1349

1350
        let list_expr = List::new(vec![
1351
            atom_identifier("->>"),
1352
            atom_int(1),
1353
            ExprKind::List(List::new(vec![
1354
                atom_identifier("apple"),
1355
                atom_identifier("sauce"),
1356
                atom_identifier("is-good"),
1357
            ])),
1358
        ]);
1359
        assert!(match_list_pattern(&pattern_args, &list_expr, false));
1360
    }
1361

1362
    #[test]
1363
    fn test_no_match_simple() {
1364
        let pattern_args = vec![
1365
            MacroPattern::Syntax("->>".into()),
1366
            MacroPattern::Single("a".into()),
1367
            MacroPattern::Single("bad".into()),
1368
        ];
1369

1370
        let list_expr = List::new(vec![
1371
            atom_identifier("->>"),
1372
            atom_int(1),
1373
            atom_int(2),
1374
            atom_int(3),
1375
        ]);
1376

1377
        assert!(!match_list_pattern(&pattern_args, &list_expr, false));
1378
    }
1379

1380
    #[test]
1381
    fn test_nested_no_match() {
1382
        let pattern_args = vec![
1383
            MacroPattern::Syntax("->>".into()),
1384
            MacroPattern::Single("a".into()),
1385
            MacroPattern::Single("bad".into()),
1386
            MacroPattern::Nested(
1387
                PatternList::new(vec![
1388
                    MacroPattern::Single("b".into()),
1389
                    MacroPattern::Many(MacroPattern::Single("c".into()).into()),
1390
                ]),
1391
                false,
1392
            ),
1393
        ];
1394

1395
        let list_expr = List::new(vec![
1396
            atom_identifier("->>"),
1397
            atom_int(1),
1398
            ExprKind::List(List::new(vec![
1399
                atom_identifier("apple"),
1400
                atom_identifier("sauce"),
1401
                atom_identifier("is-good"),
1402
            ])),
1403
        ]);
1404

1405
        assert!(!match_list_pattern(&pattern_args, &list_expr, false));
1406
    }
1407

1408
    #[test]
1409
    fn test_number_literals() {
1410
        let pattern_args = vec![
1411
            MacroPattern::Syntax("->>".into()),
1412
            MacroPattern::NumberLiteral(NumberLiteral::Real(RealLiteral::Rational(
1413
                IntLiteral::Small(3),
1414
                IntLiteral::Small(4),
1415
            ))),
1416
        ];
1417

1418
        let list_expr = List::new(vec![
1419
            atom_identifier("->>"),
1420
            atom(TokenType::Number(
1421
                NumberLiteral::Real(RealLiteral::Rational(
1422
                    IntLiteral::Small(18),
1423
                    IntLiteral::Small(24),
1424
                ))
1425
                .into(),
1426
            )),
1427
        ]);
1428

1429
        assert!(match_list_pattern(&pattern_args, &list_expr, false));
1430
    }
1431
}
1432

1433
#[cfg(test)]
1434
mod collect_bindings_tests {
1435

1436
    use super::*;
1437

1438
    fn atom_identifier(s: &str) -> ExprKind {
1439
        ExprKind::Atom(Atom::new(SyntaxObject::default(TokenType::Identifier(
1440
            s.into(),
1441
        ))))
1442
    }
1443

1444
    fn atom_int(n: isize) -> ExprKind {
1445
        ExprKind::Atom(Atom::new(SyntaxObject::default(
1446
            IntLiteral::Small(n).into(),
1447
        )))
1448
    }
1449

1450
    #[test]
1451
    fn test_collect_basic() {
1452
        let mut bindings = FxHashMap::default();
1453
        let mut binding_kind = FxHashMap::default();
1454
        let pattern_args = vec![
1455
            MacroPattern::Syntax("and".into()),
1456
            MacroPattern::Single("a".into()),
1457
            MacroPattern::Single("b".into()),
1458
        ];
1459
        let list_expr = List::new(vec![
1460
            atom_identifier("and"),
1461
            ExprKind::List(List::new(vec![
1462
                atom_identifier("+"),
1463
                atom_identifier("x"),
1464
                atom_identifier("y"),
1465
            ])),
1466
            ExprKind::List(List::new(vec![
1467
                atom_identifier("+"),
1468
                atom_identifier("x"),
1469
                atom_identifier("y"),
1470
            ])),
1471
        ]);
1472

1473
        collect_bindings(
1474
            &pattern_args,
1475
            &list_expr,
1476
            &mut bindings,
1477
            &mut binding_kind,
1478
            false,
1479
        )
1480
        .unwrap();
1481

1482
        let mut post_bindings = FxHashMap::default();
1483
        post_bindings.insert(
1484
            "a".into(),
1485
            ExprKind::List(List::new(vec![
1486
                atom_identifier("+"),
1487
                atom_identifier("x"),
1488
                atom_identifier("y"),
1489
            ])),
1490
        );
1491

1492
        post_bindings.insert(
1493
            "b".into(),
1494
            ExprKind::List(List::new(vec![
1495
                atom_identifier("+"),
1496
                atom_identifier("x"),
1497
                atom_identifier("y"),
1498
            ])),
1499
        );
1500

1501
        assert_eq!(bindings, post_bindings);
1502
    }
1503

1504
    #[test]
1505
    fn test_collect_many() {
1506
        let mut bindings = FxHashMap::default();
1507
        let mut binding_kind = FxHashMap::default();
1508
        let pattern_args = vec![
1509
            MacroPattern::Syntax("and".into()),
1510
            MacroPattern::Single("a".into()),
1511
            MacroPattern::Many(MacroPattern::Single("b".into()).into()),
1512
        ];
1513
        let list_expr = List::new(vec![
1514
            atom_identifier("and"),
1515
            ExprKind::List(List::new(vec![
1516
                atom_identifier("+"),
1517
                atom_identifier("x"),
1518
                atom_identifier("y"),
1519
            ])),
1520
            ExprKind::List(List::new(vec![
1521
                atom_identifier("+"),
1522
                atom_identifier("x"),
1523
                atom_identifier("y"),
1524
            ])),
1525
        ]);
1526

1527
        collect_bindings(
1528
            &pattern_args,
1529
            &list_expr,
1530
            &mut bindings,
1531
            &mut binding_kind,
1532
            false,
1533
        )
1534
        .unwrap();
1535

1536
        let mut post_bindings = FxHashMap::default();
1537
        post_bindings.insert(
1538
            "a".into(),
1539
            ExprKind::List(List::new(vec![
1540
                atom_identifier("+"),
1541
                atom_identifier("x"),
1542
                atom_identifier("y"),
1543
            ])),
1544
        );
1545
        post_bindings.insert(
1546
            "b".into(),
1547
            ExprKind::List(List::new(vec![ExprKind::List(List::new(vec![
1548
                atom_identifier("+"),
1549
                atom_identifier("x"),
1550
                atom_identifier("y"),
1551
            ]))])),
1552
        );
1553

1554
        assert_eq!(bindings, post_bindings);
1555
    }
1556

1557
    #[test]
1558
    fn test_collect_many_multiple_singles() {
1559
        let mut bindings = FxHashMap::default();
1560
        let mut binding_kind = FxHashMap::default();
1561
        let pattern_args = vec![
1562
            MacroPattern::Syntax("and".into()),
1563
            MacroPattern::Single("a".into()),
1564
            MacroPattern::Many(MacroPattern::Single("b".into()).into()),
1565
        ];
1566
        let list_expr = List::new(vec![
1567
            atom_identifier("and"),
1568
            ExprKind::List(List::new(vec![
1569
                atom_identifier("+"),
1570
                atom_identifier("x"),
1571
                atom_identifier("y"),
1572
            ])),
1573
            atom_identifier("+"),
1574
            atom_identifier("x"),
1575
            atom_identifier("y"),
1576
        ]);
1577

1578
        collect_bindings(
1579
            &pattern_args,
1580
            &list_expr,
1581
            &mut bindings,
1582
            &mut binding_kind,
1583
            false,
1584
        )
1585
        .unwrap();
1586
        let mut post_bindings = FxHashMap::default();
1587

1588
        post_bindings.insert(
1589
            "a".into(),
1590
            ExprKind::List(List::new(vec![
1591
                atom_identifier("+"),
1592
                atom_identifier("x"),
1593
                atom_identifier("y"),
1594
            ])),
1595
        );
1596

1597
        post_bindings.insert(
1598
            "b".into(),
1599
            ExprKind::List(List::new(vec![
1600
                atom_identifier("+"),
1601
                atom_identifier("x"),
1602
                atom_identifier("y"),
1603
            ])),
1604
        );
1605

1606
        assert_eq!(bindings, post_bindings);
1607
    }
1608

1609
    #[test]
1610
    fn test_nested() {
1611
        let mut bindings = FxHashMap::default();
1612
        let mut binding_kind = FxHashMap::default();
1613
        // (->> a (b c ...))
1614
        let pattern_args = vec![
1615
            MacroPattern::Syntax("->>".into()),
1616
            MacroPattern::Single("a".into()),
1617
            MacroPattern::Nested(
1618
                PatternList::new(vec![
1619
                    MacroPattern::Single("b".into()),
1620
                    MacroPattern::Many(MacroPattern::Single("c".into()).into()),
1621
                ]),
1622
                false,
1623
            ),
1624
        ];
1625

1626
        let list_expr = List::new(vec![
1627
            atom_identifier("->>"),
1628
            atom_int(1),
1629
            ExprKind::List(List::new(vec![
1630
                atom_identifier("apple"),
1631
                atom_identifier("sauce"),
1632
                atom_identifier("is-good"),
1633
            ])),
1634
        ]);
1635

1636
        let mut post_bindings = FxHashMap::default();
1637

1638
        collect_bindings(
1639
            &pattern_args,
1640
            &list_expr,
1641
            &mut bindings,
1642
            &mut binding_kind,
1643
            false,
1644
        )
1645
        .unwrap();
1646
        post_bindings.insert("a".into(), atom_int(1));
1647
        post_bindings.insert("b".into(), atom_identifier("apple"));
1648
        post_bindings.insert(
1649
            "c".into(),
1650
            ExprKind::List(List::new(vec![
1651
                atom_identifier("sauce"),
1652
                atom_identifier("is-good"),
1653
            ])),
1654
        );
1655

1656
        assert_eq!(bindings, post_bindings);
1657
    }
1658
}
1659

1660
fn non_list_match(patterns: &[MacroPattern]) -> Option<&MacroPattern> {
862,111✔
1661
    let rest = patterns.last().and_then(|pat| match pat {
4,309,734✔
1662
        MacroPattern::Rest(pat) => Some(&**pat),
410✔
1663
        _ => None,
860,880✔
1664
    });
1665

1666
    rest.filter(|_| patterns.len() == 2 && patterns[0].is_many())
1,725,054✔
1667
}
1668

1669
#[cfg(test)]
1670
mod macro_case_expand_test {
1671
    use steel_parser::parser::SourceId;
1672

1673
    use super::MacroCase;
1674

1675
    use super::*;
1676

1677
    // macro_rules! map {
1678
    //     ($ ( $key:expr => $value:expr ), *,) => {{
1679
    //         let mut hm: HashMap<String, ExprKind> = HashMap::new();
1680
    //         $ (hm.insert($key.to_string(), $value); ) *
1681
    //         hm
1682
    //     }};
1683
    // }
1684

1685
    fn atom_identifier(s: &str) -> ExprKind {
1686
        ExprKind::Atom(Atom::new(SyntaxObject::default(TokenType::Identifier(
1687
            s.into(),
1688
        ))))
1689
    }
1690

1691
    fn atom_int(n: isize) -> ExprKind {
1692
        ExprKind::Atom(Atom::new(SyntaxObject::default(
1693
            IntLiteral::Small(n).into(),
1694
        )))
1695
    }
1696

1697
    #[test]
1698
    fn test_basic_expansion() {
1699
        let case = MacroCase {
1700
            args: PatternList::new(vec![
1701
                MacroPattern::Syntax("test".into()),
1702
                MacroPattern::Single("a".into()),
1703
                MacroPattern::Single("b".into()),
1704
                MacroPattern::Single("c".into()),
1705
            ]),
1706
            body: List::new(vec![
1707
                atom_identifier("fun-call"),
1708
                atom_identifier("inserted-variable"),
1709
                atom_identifier("a"),
1710
                atom_identifier("b"),
1711
                atom_identifier("c"),
1712
            ])
1713
            .into(),
1714
        };
1715

1716
        let input = List::new(vec![
1717
            atom_identifier("test"),
1718
            atom_int(1),
1719
            atom_identifier("apple"),
1720
            atom_int(2),
1721
        ]);
1722

1723
        let expected: ExprKind = List::new(vec![
1724
            atom_identifier("fun-call"),
1725
            atom_identifier("inserted-variable"),
1726
            atom_int(1),
1727
            atom_identifier("apple"),
1728
            atom_int(2),
1729
        ])
1730
        .into();
1731

1732
        let output = case
1733
            .expand(input, Span::new(0, 0, SourceId::none()))
1734
            .unwrap();
1735

1736
        assert_eq!(output, expected);
1737
    }
1738
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc