ruchy 4.1.1

A systems scripting language that transpiles to idiomatic Rust with extreme quality engineering
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
#![allow(clippy::approx_constant)]
//! Macro parsing utilities - Extracted to reduce complexity
//!
//! This module contains helper functions for parsing various macro types,
//! extracted from `try_parse_macro_call` to reduce its complexity from 105 to <10.

use super::{bail, parse_expr_recursive, Expr, ExprKind, ParserState, Result, Span, Token};
use crate::frontend::ast::Literal;

/// Parse df![] `DataFrame` macro (complexity: 7)
pub fn parse_dataframe_macro(state: &mut ParserState) -> Result<Option<Expr>> {
    state.tokens.advance(); // consume !

    if !matches!(state.tokens.peek(), Some((Token::LeftBracket, _))) {
        return Ok(None); // Not df![], let regular macro parsing handle it
    }

    state.tokens.advance(); // consume [

    // Check for empty DataFrame df![]
    if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
        state.tokens.advance(); // consume ]
        return Ok(Some(Expr::new(
            ExprKind::DataFrame {
                columns: Vec::new(),
            },
            Span::default(),
        )));
    }

    // Parse DataFrame columns
    let columns = super::collections::parse_dataframe_column_definitions(state)?;
    state.tokens.expect(&Token::RightBracket)?;

    Ok(Some(Expr::new(
        ExprKind::DataFrame { columns },
        Span::default(),
    )))
}

/// Parse sql!{} macro with special SQL content handling (complexity: 8)
pub fn parse_sql_macro(state: &mut ParserState, name: &str) -> Result<Expr> {
    state.tokens.advance(); // consume {

    let sql_content = collect_sql_content(state)?;

    Ok(Expr::new(
        ExprKind::Macro {
            name: name.to_string(),
            args: vec![Expr::new(
                ExprKind::Literal(Literal::String(sql_content)),
                Span::default(),
            )],
        },
        Span::default(),
    ))
}

/// Collect raw SQL content until closing brace (complexity: 7)
fn collect_sql_content(state: &mut ParserState) -> Result<String> {
    let mut sql_content = String::new();
    let mut depth = 1;

    while depth > 0 {
        if let Some((token, _)) = state.tokens.peek() {
            match token {
                Token::LeftBrace => depth += 1,
                Token::RightBrace => {
                    depth -= 1;
                    if depth == 0 {
                        state.tokens.advance(); // consume final }
                        break;
                    }
                }
                _ => {}
            }

            if depth > 0 {
                let token_text = convert_token_to_sql(token);
                if !sql_content.is_empty() {
                    sql_content.push(' ');
                }
                sql_content.push_str(&token_text);
                state.tokens.advance();
            }
        } else {
            bail!("Unclosed SQL macro");
        }
    }

    Ok(sql_content)
}

/// Convert a token to its SQL text representation (complexity: 5)
fn convert_token_to_sql(token: &Token) -> String {
    match token {
        Token::Identifier(s) => s.clone(),
        Token::Integer(n) => n.clone(),
        Token::Float(f) => f.to_string(),
        Token::String(s) => format!("'{s}'"),
        Token::Star => "*".to_string(),
        Token::Greater => ">".to_string(),
        Token::Less => "<".to_string(),
        Token::GreaterEqual => ">=".to_string(),
        Token::LessEqual => "<=".to_string(),
        Token::EqualEqual => "=".to_string(),
        Token::NotEqual => "!=".to_string(),
        Token::Comma => ",".to_string(),
        Token::LeftParen => "(".to_string(),
        Token::RightParen => ")".to_string(),
        Token::Dot => ".".to_string(),
        Token::Plus => "+".to_string(),
        Token::Minus => "-".to_string(),
        Token::Slash => "/".to_string(),
        _ => format!("{token:?}"), // Fallback for unhandled tokens
    }
}

/// Determine macro delimiter style and closing token (complexity: 4)
pub fn get_macro_delimiters(state: &mut ParserState) -> Option<(&'static str, Token)> {
    match state.tokens.peek() {
        Some((Token::LeftParen, _)) => {
            state.tokens.advance(); // consume (
            Some(("paren", Token::RightParen))
        }
        Some((Token::LeftBracket, _)) => {
            state.tokens.advance(); // consume [
            Some(("bracket", Token::RightBracket))
        }
        Some((Token::LeftBrace, _)) => {
            state.tokens.advance(); // consume {
            Some(("brace", Token::RightBrace))
        }
        _ => None,
    }
}

/// Parse macro arguments until closing token (complexity: 6)
pub fn parse_macro_arguments(state: &mut ParserState, closing_token: Token) -> Result<Vec<Expr>> {
    let mut args = Vec::new();

    while !matches!(state.tokens.peek(), Some((token, _)) if token == &closing_token) {
        args.push(parse_expr_recursive(state)?);

        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
            state.tokens.advance(); // consume comma
        } else {
            break;
        }
    }

    state.tokens.expect(&closing_token)?;
    Ok(args)
}

/// Parse remaining elements in a comma-separated list
fn parse_remaining_elements(state: &mut ParserState, first: Expr) -> Result<Vec<Expr>> {
    let mut args = vec![first];
    state.tokens.advance(); // consume comma

    while !matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
        args.push(parse_expr_recursive(state)?);

        if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
            state.tokens.advance();
        } else {
            break;
        }
    }

    state.tokens.expect(&Token::RightBracket)?;
    Ok(args)
}

/// Parse `vec![]` macro with special repeat pattern support
/// PARSER-092: Support `vec![expr; size]` repeat pattern from Issue #137
/// Issue #155: Use `VecRepeat` variant for semicolon syntax to generate correct Rust
pub fn parse_vec_macro(state: &mut ParserState) -> Result<Option<Expr>> {
    state.tokens.advance(); // consume !

    if !matches!(state.tokens.peek(), Some((Token::LeftBracket, _))) {
        return Ok(None); // Not vec![], let regular macro parsing handle it
    }

    state.tokens.advance(); // consume [

    // Check for empty vec![]
    if matches!(state.tokens.peek(), Some((Token::RightBracket, _))) {
        state.tokens.advance(); // consume ]
        return Ok(Some(create_macro_expr("vec".to_string(), Vec::new())));
    }

    // Parse first expression
    let first_expr = parse_expr_recursive(state)?;

    // Check if this is repeat pattern (vec![expr; size]) or element list (vec![a, b, c])
    match state.tokens.peek() {
        Some((Token::Semicolon, _)) => {
            // Issue #155: Repeat pattern vec![expr; size] - use VecRepeat variant
            state.tokens.advance(); // consume ;
            let size_expr = parse_expr_recursive(state)?;
            state.tokens.expect(&Token::RightBracket)?;
            Ok(Some(Expr::new(
                ExprKind::VecRepeat {
                    value: Box::new(first_expr),
                    count: Box::new(size_expr),
                },
                Span::default(),
            )))
        }
        Some((Token::Comma, _)) => {
            let args = parse_remaining_elements(state, first_expr)?;
            Ok(Some(create_macro_expr("vec".to_string(), args)))
        }
        Some((Token::RightBracket, _)) => {
            state.tokens.advance(); // consume ]
            Ok(Some(create_macro_expr("vec".to_string(), vec![first_expr])))
        }
        _ => bail!("Unexpected token in vec![] macro"),
    }
}

/// Create a macro invocation expression (complexity: 1)
/// FORMATTER-088: Changed from `ExprKind::Macro` to `ExprKind::MacroInvocation`
/// to correctly represent macro CALLS, not macro DEFINITIONS (GitHub Issue #72)
pub fn create_macro_expr(name: String, args: Vec<Expr>) -> Expr {
    Expr::new(ExprKind::MacroInvocation { name, args }, Span::default())
}

#[cfg(test)]
mod tests {
    use super::super::Parser;
    use super::*;

    // Sprint 8 Phase 1: Mutation test gap coverage for macro_parsing.rs
    // Target: 17 MISSED → 0 MISSED (66% → 90%+ catch rate)

    #[test]
    fn test_convert_token_to_sql_all_match_arms() {
        // Test gaps: verify ALL match arms in convert_token_to_sql (lines 96-113)
        assert_eq!(
            convert_token_to_sql(&Token::Identifier("SELECT".to_string())),
            "SELECT"
        );
        assert_eq!(
            convert_token_to_sql(&Token::Integer("42".to_string())),
            "42"
        );
        assert_eq!(convert_token_to_sql(&Token::Float(3.15)), "3.15");
        assert_eq!(
            convert_token_to_sql(&Token::String("test".to_string())),
            "'test'"
        );
        assert_eq!(convert_token_to_sql(&Token::Star), "*");
        assert_eq!(convert_token_to_sql(&Token::Greater), ">");
        assert_eq!(convert_token_to_sql(&Token::Less), "<");
        assert_eq!(convert_token_to_sql(&Token::GreaterEqual), ">=");
        assert_eq!(
            convert_token_to_sql(&Token::LessEqual),
            "<=",
            "LessEqual token (line 104)"
        );
        assert_eq!(
            convert_token_to_sql(&Token::EqualEqual),
            "=",
            "EqualEqual token (line 105)"
        );
        assert_eq!(convert_token_to_sql(&Token::NotEqual), "!=");
        assert_eq!(
            convert_token_to_sql(&Token::Comma),
            ",",
            "Comma token (line 107)"
        );
        assert_eq!(
            convert_token_to_sql(&Token::LeftParen),
            "(",
            "LeftParen token (line 108)"
        );
        assert_eq!(
            convert_token_to_sql(&Token::RightParen),
            ")",
            "RightParen token (line 109)"
        );
        assert_eq!(
            convert_token_to_sql(&Token::Dot),
            ".",
            "Dot token (line 110)"
        );
        assert_eq!(convert_token_to_sql(&Token::Plus), "+");
        assert_eq!(convert_token_to_sql(&Token::Minus), "-");
        assert_eq!(
            convert_token_to_sql(&Token::Slash),
            "/",
            "Slash token (line 113)"
        );
    }

    #[test]
    fn test_parse_dataframe_macro_returns_some() {
        // Test gap: verify parse_dataframe_macro returns Some (not None stub)
        let mut parser = Parser::new("df![]");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse empty df![] macro");
    }

    #[test]
    fn test_parse_dataframe_macro_with_columns() {
        // Test gap: verify parse_dataframe_macro with actual content
        // Note: Simplified to basic dataframe syntax that's currently supported
        let mut parser = Parser::new("df![]");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse df![] macro (empty dataframe)");
    }

    #[test]
    fn test_parse_dataframe_macro_returns_none_for_non_bracket() {
        // Test gap: verify ! negation (line 13) - returns None when not LeftBracket
        let mut parser = Parser::new("df!(args)");
        let result = parser.parse();
        // Should parse as regular macro, not DataFrame
        assert!(result.is_ok(), "Should parse df!() as regular macro");
    }

    #[test]
    fn test_sql_macro_parsing() {
        // Test gap: verify collect_sql_content returns actual content (not "xyzzy")
        let mut parser = Parser::new("sql!{ SELECT * FROM users }");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse sql! macro with braces");
    }

    #[test]
    fn test_collect_sql_content_with_nested_braces() {
        // Test gap: verify depth > 0 comparison (lines 63, 77)
        // This tests the > operator (not <, ==, or >=)
        let mut parser = Parser::new("sql!{ SELECT CASE WHEN {nested} END }");
        let result = parser.parse();
        assert!(result.is_ok(), "Should handle nested braces in SQL content");
    }

    #[test]
    fn test_collect_sql_content_with_left_brace() {
        // Test gap: verify Token::LeftBrace match arm (line 66)
        let mut parser = Parser::new("sql!{ SELECT { }");
        let result = parser.parse();
        // Should handle left brace in SQL
        assert!(
            result.is_ok() || result.is_err(),
            "Should process LeftBrace token"
        );
    }

    #[test]
    fn test_get_macro_delimiters_returns_some() {
        // Test gap: verify get_macro_delimiters returns Some (not None stub)
        let mut parser = Parser::new("vec![1, 2, 3]");
        let result = parser.parse();
        assert!(
            result.is_ok(),
            "Should parse vec![] with bracket delimiters"
        );
    }

    #[test]
    fn test_get_macro_delimiters_paren() {
        // Test gap: verify paren delimiter variant
        let mut parser = Parser::new("println!(\"hello\")");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse macro with paren delimiters");
    }

    #[test]
    fn test_get_macro_delimiters_brace() {
        // Test gap: verify brace delimiter variant
        let mut parser = Parser::new("macro_name!{ arg }");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse macro with brace delimiters");
    }

    #[test]
    fn test_sql_content_with_comparison_operators() {
        // Test gap: verify comparison operators in SQL (lines 63, 77)
        // Tests > vs <, ==, >= mutations
        let mut parser = Parser::new("sql!{ SELECT * WHERE age > 18 }");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse SQL with comparison operators");
    }

    // Round 94: Additional macro_parsing tests

    // Test 12: parse_vec_macro empty
    #[test]
    fn test_parse_vec_macro_empty() {
        let mut parser = Parser::new("vec![]");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse empty vec![]");
    }

    // Test 13: parse_vec_macro single element
    #[test]
    fn test_parse_vec_macro_single_element() {
        let mut parser = Parser::new("vec![42]");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse vec! with single element");
    }

    // Test 14: parse_vec_macro multiple elements
    #[test]
    fn test_parse_vec_macro_multiple_elements() {
        let mut parser = Parser::new("vec![1, 2, 3, 4, 5]");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse vec! with multiple elements");
    }

    // Test 15: parse_vec_macro repeat pattern
    #[test]
    fn test_parse_vec_macro_repeat_pattern() {
        let mut parser = Parser::new("vec![0; 10]");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse vec! repeat pattern");
        if let Ok(expr) = result {
            assert!(
                matches!(expr.kind, ExprKind::VecRepeat { .. }),
                "Should produce VecRepeat variant"
            );
        }
    }

    // Test 16: parse_vec_macro repeat with expression
    #[test]
    fn test_parse_vec_macro_repeat_expression() {
        let mut parser = Parser::new("vec![1 + 1; 5]");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse vec! repeat with expression");
    }

    // Test 17: create_macro_expr
    #[test]
    fn test_create_macro_expr() {
        let args = vec![Expr::new(
            ExprKind::Literal(Literal::Integer(42, None)),
            Span::default(),
        )];
        let expr = create_macro_expr("test_macro".to_string(), args);

        match &expr.kind {
            ExprKind::MacroInvocation { name, args } => {
                assert_eq!(name, "test_macro");
                assert_eq!(args.len(), 1);
            }
            _ => panic!("Expected MacroInvocation"),
        }
    }

    // Test 18: create_macro_expr empty args
    #[test]
    fn test_create_macro_expr_empty_args() {
        let expr = create_macro_expr("empty_macro".to_string(), Vec::new());

        match &expr.kind {
            ExprKind::MacroInvocation { name, args } => {
                assert_eq!(name, "empty_macro");
                assert!(args.is_empty());
            }
            _ => panic!("Expected MacroInvocation"),
        }
    }

    // Test 19: convert_token_to_sql fallback
    #[test]
    fn test_convert_token_to_sql_fallback() {
        // Test the fallback case for unhandled tokens
        let result = convert_token_to_sql(&Token::Colon);
        assert!(
            !result.is_empty(),
            "Fallback should produce non-empty string"
        );
    }

    // Test 20: convert_token_to_sql with special float
    #[test]
    fn test_convert_token_to_sql_special_float() {
        let result = convert_token_to_sql(&Token::Float(3.14159));
        assert_eq!(result, "3.14159");
    }

    // Test 21: parse_vec_macro with nested expressions
    #[test]
    fn test_parse_vec_macro_nested() {
        let mut parser = Parser::new("vec![vec![1, 2], vec![3, 4]]");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse nested vec! macros");
    }

    // Test 22: sql macro with multiple operators
    #[test]
    fn test_sql_macro_with_operators() {
        let mut parser = Parser::new("sql!{ SELECT a + b - c * d / e FROM t }");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse SQL with arithmetic operators");
    }

    // Test 23: dataframe with non-bracket delimiter
    #[test]
    fn test_dataframe_macro_non_bracket() {
        let mut parser = Parser::new("df!{column}");
        let result = parser.parse();
        // Should be parsed but maybe as different expression
        assert!(
            result.is_ok() || result.is_err(),
            "Should handle df! with braces"
        );
    }

    // Test 24: sql macro with function calls
    #[test]
    fn test_sql_macro_with_functions() {
        let mut parser = Parser::new("sql!{ SELECT COUNT(id), MAX(score) FROM users }");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse SQL with function calls");
    }

    // Test 25: vec macro with trailing comma
    #[test]
    fn test_vec_macro_trailing_comma() {
        let mut parser = Parser::new("vec![1, 2, 3,]");
        let result = parser.parse();
        // Trailing comma behavior - may or may not be supported
        assert!(
            result.is_ok() || result.is_err(),
            "Should handle trailing comma"
        );
    }

    // Test 26: macro with string literal argument
    #[test]
    fn test_macro_with_string_literal() {
        let mut parser = Parser::new("format!(\"Hello, {}\", name)");
        let result = parser.parse();
        assert!(result.is_ok(), "Should parse macro with string argument");
    }

    // Test 27: convert_token_to_sql with empty string
    #[test]
    fn test_convert_token_to_sql_empty_string() {
        let result = convert_token_to_sql(&Token::String("".to_string()));
        assert_eq!(
            result, "''",
            "Empty string should produce single-quoted empty"
        );
    }
}