Skip to main content

ghostscope_compiler/script/
parser.rs

1use pest::iterators::{Pair, Pairs};
2use pest::Parser;
3use pest::RuleType;
4use pest_derive::Parser;
5
6use crate::script::ast::{
7    infer_type, BacktraceStatement, BinaryOp, Expr, PrintStatement, Program, Statement,
8    TracePattern,
9};
10use crate::script::format_validator::FormatValidator;
11use tracing::{debug, info};
12
13#[derive(Parser)]
14#[grammar = "script/grammar.pest"]
15pub struct GhostScopeParser;
16
17#[derive(Debug, thiserror::Error)]
18pub enum ParseError {
19    #[error("Pest parser error: {0}")]
20    Pest(#[from] Box<pest::error::Error<Rule>>),
21
22    #[error("Unexpected token: {0:?}")]
23    UnexpectedToken(Rule),
24
25    #[error("Invalid expression")]
26    InvalidExpression,
27
28    #[error("Syntax error: {0}")]
29    SyntaxError(String),
30
31    #[error("Type error: {0}")]
32    TypeError(String),
33
34    #[error("Unsupported feature: {0}")]
35    UnsupportedFeature(String),
36}
37
38impl From<pest::error::Error<Rule>> for ParseError {
39    fn from(err: pest::error::Error<Rule>) -> Self {
40        ParseError::Pest(Box::new(err))
41    }
42}
43
44pub type Result<T> = std::result::Result<T, ParseError>;
45
46// Custom chunks function with RuleType constraint
47fn chunks_of_two<'a, T: RuleType>(pairs: Pairs<'a, T>) -> Vec<Vec<Pair<'a, T>>> {
48    let pairs_vec: Vec<_> = pairs.collect();
49    let mut result = Vec::new();
50
51    let mut i = 0;
52    // Only produce full (op, rhs) pairs; ignore any trailing leftover defensively
53    while i + 1 < pairs_vec.len() {
54        result.push(vec![pairs_vec[i].clone(), pairs_vec[i + 1].clone()]);
55        i += 2;
56    }
57
58    result
59}
60
61pub fn parse(input: &str) -> Result<Program> {
62    debug!("Starting to parse input: {}", input.trim());
63
64    let pairs = match GhostScopeParser::parse(Rule::program, input) {
65        Ok(p) => p,
66        Err(e) => {
67            // Heuristic: detect unclosed string in print lines to provide a clearer hint
68            if let Some(msg) = detect_unclosed_print_string(input) {
69                return Err(ParseError::SyntaxError(msg));
70            }
71            if let Some(msg) = detect_backtrace_depth_argument(input) {
72                return Err(ParseError::SyntaxError(msg));
73            }
74            // Heuristic: detect likely misspelled or unknown keywords and suggest fixes
75            if let Some(msg) = detect_unknown_keyword(input) {
76                return Err(ParseError::SyntaxError(msg));
77            }
78            return Err(ParseError::Pest(Box::new(e)));
79        }
80    };
81    let mut program = Program::new();
82
83    for pair in pairs {
84        debug!(
85            "Parsing top-level rule: {:?} = '{}'",
86            pair.as_rule(),
87            pair.as_str().trim()
88        );
89        match pair.as_rule() {
90            Rule::statement => {
91                let statement = parse_statement(pair)?;
92                program.add_statement(statement);
93            }
94            Rule::EOI => {}
95            _ => return Err(ParseError::UnexpectedToken(pair.as_rule())),
96        }
97    }
98
99    debug!("Parsing completed successfully");
100    Ok(program)
101}
102
103// Best-effort heuristic: if a line contains a print statement with an opening quote
104// but no closing quote before arguments, give a clearer error.
105fn detect_unclosed_print_string(input: &str) -> Option<String> {
106    for (i, raw_line) in input.lines().enumerate() {
107        let line = raw_line.trim_start();
108        if !line.contains("print ") && !line.starts_with("print") {
109            continue;
110        }
111        // Toggle on '"' to detect unclosed string; ignore escaped quotes for simplicity
112        let mut open = false;
113        for ch in line.chars() {
114            if ch == '"' {
115                open = !open;
116            }
117        }
118        if open {
119            // Common case: missing closing quote before comma and arguments
120            if line.contains(',') {
121                return Some(format!(
122                    "Unclosed string literal in print at line {}. Did you forget a closing \"\" before ',' and arguments?",
123                    i + 1
124                ));
125            } else {
126                return Some(format!(
127                    "Unclosed string literal in print at line {}.",
128                    i + 1
129                ));
130            }
131        }
132    }
133    None
134}
135
136fn detect_backtrace_depth_argument(input: &str) -> Option<String> {
137    fn boundary_before(line: &str, idx: usize) -> bool {
138        idx == 0
139            || line[..idx]
140                .chars()
141                .next_back()
142                .is_some_and(|ch| ch.is_whitespace() || matches!(ch, '{' | ';' | '}'))
143    }
144
145    for (line_idx, raw_line) in input.lines().enumerate() {
146        let line = raw_line.split("//").next().unwrap_or(raw_line);
147        for command in ["bt", "backtrace"] {
148            for (idx, _) in line.match_indices(command) {
149                if !boundary_before(line, idx) {
150                    continue;
151                }
152                let after = &line[idx + command.len()..];
153                if !after.starts_with(char::is_whitespace) {
154                    continue;
155                }
156                let arg = after.trim_start();
157                if arg.starts_with("depth")
158                    || arg.chars().next().is_some_and(|ch| ch.is_ascii_digit())
159                {
160                    return Some(format!(
161                        "bt depth is no longer a script option at line {}. Set the global limit with --backtrace-depth <N> or [ebpf] backtrace_depth = N.",
162                        line_idx + 1
163                    ));
164                }
165            }
166        }
167    }
168    None
169}
170
171// Try to detect lines that start with an unknown/misspelled keyword and suggest known ones.
172fn detect_unknown_keyword(input: &str) -> Option<String> {
173    // Suggest only currently supported top-level keywords.
174    const SUGGEST: &[&str] = &["trace", "print", "if", "else", "let"];
175    // Valid statement starters that should not be flagged as unknown
176    const SUPPORTED_HEADS: &[&str] = &["trace", "print", "if", "else", "let", "backtrace", "bt"];
177    // Builtin call names allowed at expression head
178    const BUILTIN_CALLS: &[&str] = &["memcmp", "strncmp", "starts_with", "hex", "cast"];
179
180    // Helper: simple Levenshtein distance (small strings, few keywords)
181    fn levenshtein(a: &str, b: &str) -> usize {
182        let (n, m) = (a.len(), b.len());
183        let mut dp = vec![0usize; (n + 1) * (m + 1)];
184        let idx = |i: usize, j: usize| i * (m + 1) + j;
185        for i in 0..=n {
186            dp[idx(i, 0)] = i;
187        }
188        for j in 0..=m {
189            dp[idx(0, j)] = j;
190        }
191        let ac: Vec<char> = a.chars().collect();
192        let bc: Vec<char> = b.chars().collect();
193        for i in 1..=n {
194            for j in 1..=m {
195                let cost = if ac[i - 1] == bc[j - 1] { 0 } else { 1 };
196                let del = dp[idx(i - 1, j)] + 1;
197                let ins = dp[idx(i, j - 1)] + 1;
198                let sub = dp[idx(i - 1, j - 1)] + cost;
199                dp[idx(i, j)] = del.min(ins).min(sub);
200            }
201        }
202        dp[idx(n, m)]
203    }
204
205    // Helper: check a slice for a command-like unknown keyword
206    fn check_slice(slice: &str, line_no_1based: usize) -> Option<String> {
207        let mut s = slice.trim_start();
208        if s.is_empty() || s.starts_with("//") {
209            return None;
210        }
211
212        // If this slice begins with an if/else-if header, jump inside the condition
213        if let Some(rest) = s.strip_prefix("if") {
214            if rest.starts_with(char::is_whitespace) {
215                s = rest.trim_start();
216            }
217        } else if let Some(rest) = s.strip_prefix("else") {
218            let rest = rest.trim_start();
219            if let Some(rest2) = rest.strip_prefix("if") {
220                if rest2.starts_with(char::is_whitespace) {
221                    s = rest2.trim_start();
222                }
223            } else {
224                // 'else { ... }' — nothing to inspect here
225            }
226        }
227        // Keywords must start with a letter or underscore; skip numeric heads
228        let mut iter = s.chars();
229        let first = iter.next()?;
230        if !(first.is_ascii_alphabetic() || first == '_') {
231            return None;
232        }
233        let mut token = String::new();
234        token.push(first);
235        for ch in iter {
236            if ch.is_ascii_alphanumeric() || ch == '_' {
237                token.push(ch);
238            } else {
239                break;
240            }
241        }
242        if token.is_empty() {
243            return None;
244        }
245        if SUPPORTED_HEADS.iter().any(|k| *k == token) {
246            return None;
247        }
248        let rest_untrimmed = &s[token.len()..];
249        let rest = rest_untrimmed.trim_start();
250        if rest.starts_with('=') || rest.starts_with('[') || rest.starts_with('.') {
251            // likely an expression starting with identifier
252            return None;
253        }
254        // Allow builtin calls as expression statements
255        if BUILTIN_CALLS.iter().any(|k| *k == token) && rest.starts_with('(') {
256            return None;
257        }
258        if rest.starts_with('(')
259            || rest.starts_with('{')
260            || rest.starts_with('"')
261            || rest_untrimmed.starts_with(char::is_whitespace)
262        {
263            // If it looks like a call (token + '('), include builtin calls in suggestion candidates
264            let candidates: Vec<&str> = if rest.starts_with('(') {
265                let mut v = Vec::new();
266                v.extend_from_slice(SUGGEST);
267                v.extend_from_slice(BUILTIN_CALLS);
268                v
269            } else {
270                SUGGEST.to_vec()
271            };
272            let mut suggestions: Vec<(&str, usize)> = candidates
273                .iter()
274                .map(|&k| (k, levenshtein(&token, k)))
275                .collect();
276            suggestions.sort_by_key(|&(_, d)| d);
277            if let Some((cand, dist)) = suggestions.first().copied() {
278                if dist <= 2 {
279                    return Some(format!(
280                        "Unknown keyword '{token}' at line {line_no_1based}. Did you mean '{cand}'?"
281                    ));
282                }
283            }
284            return Some(format!(
285                "Unknown keyword '{token}' at line {}. Expected one of: {}",
286                line_no_1based,
287                SUGGEST.join(", ")
288            ));
289        }
290        None
291    }
292
293    for (i, raw_line) in input.lines().enumerate() {
294        let line = raw_line;
295        // Scan potential statement starts: at line start, and right after '{', ';', '}', '(', ',' (outside strings)
296        let mut quote_open = false;
297        let mut positions: Vec<usize> = vec![0]; // include start-of-line
298        for (idx, ch) in line.char_indices() {
299            if ch == '"' {
300                quote_open = !quote_open;
301            }
302            if !quote_open && (ch == '{' || ch == ';' || ch == '}' || ch == '(' || ch == ',') {
303                let next = idx + ch.len_utf8();
304                if next < line.len() {
305                    positions.push(next);
306                }
307            }
308        }
309        for &pos in &positions {
310            if let Some(msg) = check_slice(&line[pos..], i + 1) {
311                return Some(msg);
312            }
313        }
314    }
315    None
316}
317
318fn parse_backtrace_stmt(pair: Pair<Rule>) -> Result<BacktraceStatement> {
319    let mut stmt = BacktraceStatement::default();
320
321    for arg in pair.into_inner() {
322        if arg.as_rule() == Rule::backtrace_flag {
323            match arg.as_str() {
324                "raw" => stmt.raw = true,
325                "full" => stmt.full = true,
326                "inline" => stmt.inline = true,
327                "noinline" => stmt.inline = false,
328                other => {
329                    return Err(ParseError::SyntaxError(format!(
330                        "Unknown bt option '{other}'"
331                    )))
332                }
333            }
334        }
335    }
336
337    Ok(stmt)
338}
339
340fn parse_statement(pair: Pair<Rule>) -> Result<Statement> {
341    debug!(
342        "parse_statement: {:?} = '{}'",
343        pair.as_rule(),
344        pair.as_str().trim()
345    );
346    let inner = pair
347        .into_inner()
348        .next()
349        .ok_or(ParseError::InvalidExpression)?;
350    debug!(
351        "parse_statement inner: {:?} = '{}'",
352        inner.as_rule(),
353        inner.as_str().trim()
354    );
355
356    match inner.as_rule() {
357        Rule::trace_stmt => {
358            let mut inner_pairs = inner.into_inner();
359            let pattern_pair = inner_pairs.next().ok_or(ParseError::InvalidExpression)?;
360            let pattern = parse_trace_pattern(pattern_pair)?;
361
362            let mut body = Vec::new();
363            for stmt_pair in inner_pairs {
364                // Disallow nested trace statements (trace is top-level only)
365                if stmt_pair.as_rule() == Rule::statement {
366                    let mut peek = stmt_pair.clone().into_inner();
367                    if let Some(first) = peek.next() {
368                        if first.as_rule() == Rule::trace_stmt {
369                            return Err(ParseError::SyntaxError(
370                                "'trace' cannot be nested; it is only allowed at the top level"
371                                    .to_string(),
372                            ));
373                        }
374                    }
375                }
376                let stmt = parse_statement(stmt_pair)?;
377                body.push(stmt);
378            }
379
380            Ok(Statement::TracePoint { pattern, body })
381        }
382        Rule::print_stmt => {
383            let print_content = inner
384                .into_inner()
385                .next()
386                .ok_or(ParseError::InvalidExpression)?;
387            let print_stmt = parse_print_content(print_content)?;
388            Ok(Statement::Print(print_stmt))
389        }
390        Rule::backtrace_stmt => Ok(Statement::Backtrace(parse_backtrace_stmt(inner)?)),
391        Rule::assign_stmt => {
392            // Friendly error for immutable variables (no assignment supported)
393            let mut it = inner.into_inner();
394            let name = it
395                .next()
396                .ok_or(ParseError::InvalidExpression)?
397                .as_str()
398                .to_string();
399            // consume rhs expr
400            let _ = it.next();
401            Err(ParseError::TypeError(format!(
402                "Assignment is not supported: variables are immutable. Use 'let {name} = ...' to bind once."
403            )))
404        }
405        Rule::expr_stmt => {
406            let expr = inner
407                .into_inner()
408                .next()
409                .ok_or(ParseError::InvalidExpression)?;
410            let parsed_expr = parse_expr(expr)?;
411
412            // Check expression type to ensure consistent operation types
413            if let Err(err) = infer_type(&parsed_expr) {
414                return Err(ParseError::TypeError(err));
415            }
416
417            Ok(Statement::Expr(parsed_expr))
418        }
419        Rule::var_decl_stmt => {
420            let mut inner_pairs = inner.into_inner();
421            let name = inner_pairs
422                .next()
423                .ok_or(ParseError::InvalidExpression)?
424                .as_str()
425                .to_string();
426            let expr = inner_pairs.next().ok_or(ParseError::InvalidExpression)?;
427            let parsed_expr = parse_expr(expr)?;
428
429            // Check expression type to ensure consistent operation types
430            if let Err(err) = infer_type(&parsed_expr) {
431                return Err(ParseError::TypeError(err));
432            }
433
434            if is_alias_expr(&parsed_expr) {
435                Ok(Statement::AliasDeclaration {
436                    name,
437                    target: parsed_expr,
438                })
439            } else {
440                Ok(Statement::VarDeclaration {
441                    name,
442                    value: parsed_expr,
443                })
444            }
445        }
446        Rule::if_stmt => {
447            debug!("Parsing if_stmt");
448            let mut inner_pairs = inner.into_inner();
449            let condition_pair = inner_pairs.next().ok_or(ParseError::InvalidExpression)?;
450            debug!(
451                "if_stmt condition_pair: {:?} = '{}'",
452                condition_pair.as_rule(),
453                condition_pair.as_str().trim()
454            );
455            let condition = parse_condition(condition_pair)?;
456
457            // Parse then body statements
458            let mut then_body = Vec::new();
459            let mut else_body = None;
460
461            for pair in inner_pairs {
462                match pair.as_rule() {
463                    Rule::statement => {
464                        then_body.push(parse_statement(pair)?);
465                    }
466                    Rule::else_clause => {
467                        else_body = Some(Box::new(parse_else_clause(pair)?));
468                        break;
469                    }
470                    _ => return Err(ParseError::UnexpectedToken(pair.as_rule())),
471                }
472            }
473
474            Ok(Statement::If {
475                condition,
476                then_body,
477                else_body,
478            })
479        }
480        _ => Err(ParseError::UnexpectedToken(inner.as_rule())),
481    }
482}
483
484fn parse_expr(pair: Pair<Rule>) -> Result<Expr> {
485    match pair.as_rule() {
486        Rule::expr => {
487            let inner = pair
488                .into_inner()
489                .next()
490                .ok_or(ParseError::InvalidExpression)?;
491            parse_logical_or(inner)
492        }
493        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
494    }
495}
496
497/// Determine if an expression should be treated as a DWARF alias binding.
498/// This is a purely syntactic check (parser phase) and does not consult DWARF.
499fn integer_literal_value(e: &Expr) -> Option<i64> {
500    use crate::script::ast::BinaryOp as BO;
501    use crate::script::ast::Expr as E;
502
503    match e {
504        E::Int(value) => Some(*value),
505        E::BinaryOp {
506            left,
507            op: BO::Add,
508            right,
509        } => integer_literal_value(left)?.checked_add(integer_literal_value(right)?),
510        E::BinaryOp {
511            left,
512            op: BO::Subtract,
513            right,
514        } => integer_literal_value(left)?.checked_sub(integer_literal_value(right)?),
515        E::BinaryOp {
516            left,
517            op: BO::Multiply,
518            right,
519        } => integer_literal_value(left)?.checked_mul(integer_literal_value(right)?),
520        E::BinaryOp {
521            left,
522            op: BO::Divide,
523            right,
524        } => integer_literal_value(left)?.checked_div(integer_literal_value(right)?),
525        E::BinaryOp {
526            left,
527            op: BO::Modulo,
528            right,
529        } => integer_literal_value(left)?.checked_rem(integer_literal_value(right)?),
530        E::BinaryOp {
531            left,
532            op: BO::BitAnd,
533            right,
534        } => Some(integer_literal_value(left)? & integer_literal_value(right)?),
535        E::BinaryOp {
536            left,
537            op: BO::BitXor,
538            right,
539        } => Some(integer_literal_value(left)? ^ integer_literal_value(right)?),
540        E::BinaryOp {
541            left,
542            op: BO::BitOr,
543            right,
544        } => Some(integer_literal_value(left)? | integer_literal_value(right)?),
545        E::BinaryOp {
546            left,
547            op: BO::ShiftLeft,
548            right,
549        } => {
550            let shift = u32::try_from(integer_literal_value(right)?).ok()?;
551            integer_literal_value(left)?.checked_shl(shift)
552        }
553        E::BinaryOp {
554            left,
555            op: BO::ShiftRight,
556            right,
557        } => {
558            let shift = u32::try_from(integer_literal_value(right)?).ok()?;
559            integer_literal_value(left)?.checked_shr(shift)
560        }
561        E::UnaryBitNot(inner) => Some(!integer_literal_value(inner)?),
562        _ => None,
563    }
564}
565
566fn is_alias_expr(e: &Expr) -> bool {
567    use crate::script::ast::BinaryOp as BO;
568    use crate::script::ast::Expr as E;
569    match e {
570        E::AddressOf(_) => true,
571        // Constant offset on top of an alias-eligible expression
572        E::BinaryOp {
573            left,
574            op: BO::Add,
575            right,
576        } => {
577            (is_alias_expr(left) && integer_literal_value(right).is_some())
578                || (is_alias_expr(right) && integer_literal_value(left).is_some())
579        }
580        _ => false,
581    }
582}
583
584fn parse_logical_or(pair: Pair<Rule>) -> Result<Expr> {
585    match pair.as_rule() {
586        Rule::logical_or => {
587            let mut pairs = pair.into_inner();
588            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
589            let mut left = parse_logical_and(first)?;
590
591            for chunk in chunks_of_two(pairs) {
592                if chunk.len() != 2 {
593                    return Err(ParseError::InvalidExpression);
594                }
595                if chunk[0].as_rule() != Rule::or_op {
596                    return Err(ParseError::UnexpectedToken(chunk[0].as_rule()));
597                }
598                let right = parse_logical_and(chunk[1].clone())?;
599                let expr = Expr::BinaryOp {
600                    left: Box::new(left),
601                    op: BinaryOp::LogicalOr,
602                    right: Box::new(right),
603                };
604                if let Err(err) = infer_type(&expr) {
605                    return Err(ParseError::TypeError(err));
606                }
607                left = expr;
608            }
609            Ok(left)
610        }
611        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
612    }
613}
614
615fn parse_logical_and(pair: Pair<Rule>) -> Result<Expr> {
616    match pair.as_rule() {
617        Rule::logical_and => {
618            let mut pairs = pair.into_inner();
619            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
620            let mut left = parse_bitwise_or(first)?;
621
622            for chunk in chunks_of_two(pairs) {
623                if chunk.len() != 2 {
624                    return Err(ParseError::InvalidExpression);
625                }
626                if chunk[0].as_rule() != Rule::and_op {
627                    return Err(ParseError::UnexpectedToken(chunk[0].as_rule()));
628                }
629                let right = parse_bitwise_or(chunk[1].clone())?;
630                let expr = Expr::BinaryOp {
631                    left: Box::new(left),
632                    op: BinaryOp::LogicalAnd,
633                    right: Box::new(right),
634                };
635                if let Err(err) = infer_type(&expr) {
636                    return Err(ParseError::TypeError(err));
637                }
638                left = expr;
639            }
640            Ok(left)
641        }
642        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
643    }
644}
645
646fn parse_bitwise_or(pair: Pair<Rule>) -> Result<Expr> {
647    match pair.as_rule() {
648        Rule::bitwise_or => {
649            let mut pairs = pair.into_inner();
650            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
651            let mut left = parse_bitwise_xor(first)?;
652
653            for chunk in chunks_of_two(pairs) {
654                if chunk.len() != 2 {
655                    return Err(ParseError::InvalidExpression);
656                }
657                if chunk[0].as_rule() != Rule::bit_or_op {
658                    return Err(ParseError::UnexpectedToken(chunk[0].as_rule()));
659                }
660                let right = parse_bitwise_xor(chunk[1].clone())?;
661                let expr = Expr::BinaryOp {
662                    left: Box::new(left),
663                    op: BinaryOp::BitOr,
664                    right: Box::new(right),
665                };
666                if let Err(err) = infer_type(&expr) {
667                    return Err(ParseError::TypeError(err));
668                }
669                left = expr;
670            }
671            Ok(left)
672        }
673        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
674    }
675}
676
677fn parse_bitwise_xor(pair: Pair<Rule>) -> Result<Expr> {
678    match pair.as_rule() {
679        Rule::bitwise_xor => {
680            let mut pairs = pair.into_inner();
681            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
682            let mut left = parse_bitwise_and(first)?;
683
684            for chunk in chunks_of_two(pairs) {
685                if chunk.len() != 2 {
686                    return Err(ParseError::InvalidExpression);
687                }
688                if chunk[0].as_rule() != Rule::bit_xor_op {
689                    return Err(ParseError::UnexpectedToken(chunk[0].as_rule()));
690                }
691                let right = parse_bitwise_and(chunk[1].clone())?;
692                let expr = Expr::BinaryOp {
693                    left: Box::new(left),
694                    op: BinaryOp::BitXor,
695                    right: Box::new(right),
696                };
697                if let Err(err) = infer_type(&expr) {
698                    return Err(ParseError::TypeError(err));
699                }
700                left = expr;
701            }
702            Ok(left)
703        }
704        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
705    }
706}
707
708fn parse_bitwise_and(pair: Pair<Rule>) -> Result<Expr> {
709    match pair.as_rule() {
710        Rule::bitwise_and => {
711            let mut pairs = pair.into_inner();
712            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
713            let mut left = parse_equality(first)?;
714
715            for chunk in chunks_of_two(pairs) {
716                if chunk.len() != 2 {
717                    return Err(ParseError::InvalidExpression);
718                }
719                if chunk[0].as_rule() != Rule::bit_and_op {
720                    return Err(ParseError::UnexpectedToken(chunk[0].as_rule()));
721                }
722                let right = parse_equality(chunk[1].clone())?;
723                let expr = Expr::BinaryOp {
724                    left: Box::new(left),
725                    op: BinaryOp::BitAnd,
726                    right: Box::new(right),
727                };
728                if let Err(err) = infer_type(&expr) {
729                    return Err(ParseError::TypeError(err));
730                }
731                left = expr;
732            }
733            Ok(left)
734        }
735        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
736    }
737}
738
739fn parse_equality(pair: Pair<Rule>) -> Result<Expr> {
740    match pair.as_rule() {
741        Rule::equality => {
742            let mut pairs = pair.into_inner();
743            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
744            let mut left = parse_relational(first)?;
745
746            for chunk in chunks_of_two(pairs) {
747                if chunk.len() != 2 {
748                    return Err(ParseError::InvalidExpression);
749                }
750                if chunk[0].as_rule() != Rule::eq_op {
751                    return Err(ParseError::UnexpectedToken(chunk[0].as_rule()));
752                }
753                let op = match chunk[0].as_str() {
754                    "==" => BinaryOp::Equal,
755                    "!=" => BinaryOp::NotEqual,
756                    _ => return Err(ParseError::UnexpectedToken(chunk[0].as_rule())),
757                };
758                let right = parse_relational(chunk[1].clone())?;
759                let expr = Expr::BinaryOp {
760                    left: Box::new(left),
761                    op,
762                    right: Box::new(right),
763                };
764                // Type check literals only
765                if let Err(err) = infer_type(&expr) {
766                    return Err(ParseError::TypeError(err));
767                }
768                left = expr;
769            }
770            Ok(left)
771        }
772        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
773    }
774}
775
776fn parse_relational(pair: Pair<Rule>) -> Result<Expr> {
777    match pair.as_rule() {
778        Rule::relational => {
779            let mut pairs = pair.into_inner();
780            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
781            let mut left = parse_shift(first)?;
782
783            for chunk in chunks_of_two(pairs) {
784                if chunk.len() != 2 {
785                    return Err(ParseError::InvalidExpression);
786                }
787                if chunk[0].as_rule() != Rule::rel_op {
788                    return Err(ParseError::UnexpectedToken(chunk[0].as_rule()));
789                }
790                let op = match chunk[0].as_str() {
791                    "<" => BinaryOp::LessThan,
792                    "<=" => BinaryOp::LessEqual,
793                    ">" => BinaryOp::GreaterThan,
794                    ">=" => BinaryOp::GreaterEqual,
795                    _ => return Err(ParseError::UnexpectedToken(chunk[0].as_rule())),
796                };
797                let right = parse_shift(chunk[1].clone())?;
798                let expr = Expr::BinaryOp {
799                    left: Box::new(left),
800                    op,
801                    right: Box::new(right),
802                };
803                if let Err(err) = infer_type(&expr) {
804                    return Err(ParseError::TypeError(err));
805                }
806                left = expr;
807            }
808            Ok(left)
809        }
810        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
811    }
812}
813
814fn parse_shift(pair: Pair<Rule>) -> Result<Expr> {
815    match pair.as_rule() {
816        Rule::shift => {
817            let mut pairs = pair.into_inner();
818            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
819            let mut left = parse_additive(first)?;
820
821            for chunk in chunks_of_two(pairs) {
822                if chunk.len() != 2 {
823                    return Err(ParseError::InvalidExpression);
824                }
825                let op = match chunk[0].as_str() {
826                    "<<" => BinaryOp::ShiftLeft,
827                    ">>" => BinaryOp::ShiftRight,
828                    _ => return Err(ParseError::UnexpectedToken(chunk[0].as_rule())),
829                };
830                let right = parse_additive(chunk[1].clone())?;
831                let expr = Expr::BinaryOp {
832                    left: Box::new(left),
833                    op,
834                    right: Box::new(right),
835                };
836                if let Err(err) = infer_type(&expr) {
837                    return Err(ParseError::TypeError(err));
838                }
839                left = expr;
840            }
841            Ok(left)
842        }
843        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
844    }
845}
846
847fn parse_additive(pair: Pair<Rule>) -> Result<Expr> {
848    match pair.as_rule() {
849        Rule::additive => {
850            let mut pairs = pair.into_inner();
851            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
852            let mut left = parse_term(first)?;
853
854            for chunk in chunks_of_two(pairs) {
855                if chunk.len() != 2 {
856                    return Err(ParseError::InvalidExpression);
857                }
858                let op = match chunk[0].as_str() {
859                    "+" => BinaryOp::Add,
860                    "-" => BinaryOp::Subtract,
861                    _ => return Err(ParseError::UnexpectedToken(chunk[0].as_rule())),
862                };
863                let right = parse_term(chunk[1].clone())?;
864                let expr = Expr::BinaryOp {
865                    left: Box::new(left),
866                    op,
867                    right: Box::new(right),
868                };
869                if let Err(err) = infer_type(&expr) {
870                    return Err(ParseError::TypeError(err));
871                }
872                left = expr;
873            }
874            Ok(left)
875        }
876        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
877    }
878}
879
880fn parse_condition(pair: Pair<Rule>) -> Result<Expr> {
881    debug!(
882        "parse_condition: {:?} = '{}'",
883        pair.as_rule(),
884        pair.as_str().trim()
885    );
886    match pair.as_rule() {
887        Rule::condition => {
888            // Condition now accepts a full expression (equality/relational/additive/etc.)
889            let inner_expr_pair = pair
890                .into_inner()
891                .next()
892                .ok_or(ParseError::InvalidExpression)?;
893            let expr = parse_expr(inner_expr_pair)?;
894            // Basic type check of the resulting expression
895            if let Err(err) = infer_type(&expr) {
896                return Err(ParseError::TypeError(err));
897            }
898            Ok(expr)
899        }
900        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
901    }
902}
903
904fn parse_else_clause(pair: Pair<Rule>) -> Result<Statement> {
905    let inner = pair
906        .into_inner()
907        .next()
908        .ok_or(ParseError::InvalidExpression)?;
909    match inner.as_rule() {
910        Rule::if_stmt => {
911            // Directly parse if statement for else if
912            debug!("Parsing else if statement");
913            let mut inner_pairs = inner.into_inner();
914            let condition_pair = inner_pairs.next().ok_or(ParseError::InvalidExpression)?;
915            debug!(
916                "else if condition_pair: {:?} = '{}'",
917                condition_pair.as_rule(),
918                condition_pair.as_str().trim()
919            );
920            let condition = parse_condition(condition_pair)?;
921
922            // Parse then body statements
923            let mut then_body = Vec::new();
924            let mut else_body = None;
925
926            for pair in inner_pairs {
927                match pair.as_rule() {
928                    Rule::statement => {
929                        then_body.push(parse_statement(pair)?);
930                    }
931                    Rule::else_clause => {
932                        else_body = Some(Box::new(parse_else_clause(pair)?));
933                        break;
934                    }
935                    _ => return Err(ParseError::UnexpectedToken(pair.as_rule())),
936                }
937            }
938
939            Ok(Statement::If {
940                condition,
941                then_body,
942                else_body,
943            })
944        }
945        _ => {
946            // Parse else block statements
947            let mut else_body = Vec::new();
948            for node in inner.into_inner() {
949                match node.as_rule() {
950                    Rule::statement => {
951                        else_body.push(parse_statement(node)?);
952                    }
953                    // Some grammars flatten block children to concrete statements (e.g., print_stmt)
954                    Rule::print_stmt => {
955                        let content = node
956                            .into_inner()
957                            .next()
958                            .ok_or(ParseError::InvalidExpression)?;
959                        let pr = parse_print_content(content)?;
960                        else_body.push(Statement::Print(pr));
961                    }
962                    _ => return Err(ParseError::UnexpectedToken(node.as_rule())),
963                }
964            }
965            Ok(Statement::Block(else_body))
966        }
967    }
968}
969
970fn parse_term(pair: Pair<Rule>) -> Result<Expr> {
971    match pair.as_rule() {
972        Rule::term => {
973            let mut pairs = pair.into_inner();
974            let first = pairs.next().ok_or(ParseError::InvalidExpression)?;
975            let mut left = parse_unary(first)?;
976
977            for chunk in chunks_of_two(pairs) {
978                if chunk.len() != 2 {
979                    return Err(ParseError::InvalidExpression);
980                }
981
982                let op = match chunk[0].as_str() {
983                    "*" => BinaryOp::Multiply,
984                    "/" => BinaryOp::Divide,
985                    "%" => BinaryOp::Modulo,
986                    _ => return Err(ParseError::UnexpectedToken(chunk[0].as_rule())),
987                };
988
989                let right = parse_unary(chunk[1].clone())?;
990
991                // Check type consistency for binary operations
992                let expr = Expr::BinaryOp {
993                    left: Box::new(left),
994                    op,
995                    right: Box::new(right),
996                };
997
998                // Only check type consistency for literals here
999                if let Err(err) = infer_type(&expr) {
1000                    return Err(ParseError::TypeError(err));
1001                }
1002
1003                left = expr;
1004            }
1005
1006            Ok(left)
1007        }
1008        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
1009    }
1010}
1011
1012fn parse_unary(pair: Pair<Rule>) -> Result<Expr> {
1013    match pair.as_rule() {
1014        Rule::unary => {
1015            let mut inner = pair.into_inner();
1016            let first = inner.next().ok_or(ParseError::InvalidExpression)?;
1017            match first.as_rule() {
1018                Rule::factor => parse_factor(first),
1019                // '-' ~ unary
1020                Rule::neg_unary => {
1021                    let u = first
1022                        .into_inner()
1023                        .next()
1024                        .ok_or(ParseError::InvalidExpression)?;
1025                    let right = parse_unary(u)?;
1026                    let expr = Expr::BinaryOp {
1027                        left: Box::new(Expr::Int(0)),
1028                        op: BinaryOp::Subtract,
1029                        right: Box::new(right),
1030                    };
1031                    if let Err(err) = infer_type(&expr) {
1032                        return Err(ParseError::TypeError(err));
1033                    }
1034                    Ok(expr)
1035                }
1036                // '!' ~ unary
1037                Rule::not_unary => {
1038                    let u = first
1039                        .into_inner()
1040                        .next()
1041                        .ok_or(ParseError::InvalidExpression)?;
1042                    let right = parse_unary(u)?;
1043                    Ok(Expr::UnaryNot(Box::new(right)))
1044                }
1045                Rule::bit_not_unary => {
1046                    let u = first
1047                        .into_inner()
1048                        .next()
1049                        .ok_or(ParseError::InvalidExpression)?;
1050                    let right = parse_unary(u)?;
1051                    let expr = Expr::UnaryBitNot(Box::new(right));
1052                    if let Err(err) = infer_type(&expr) {
1053                        return Err(ParseError::TypeError(err));
1054                    }
1055                    Ok(expr)
1056                }
1057                _ => Err(ParseError::UnexpectedToken(first.as_rule())),
1058            }
1059        }
1060        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
1061    }
1062}
1063
1064fn parse_factor(pair: Pair<Rule>) -> Result<Expr> {
1065    match pair.as_rule() {
1066        Rule::factor => {
1067            let inner = pair
1068                .into_inner()
1069                .next()
1070                .ok_or(ParseError::InvalidExpression)?;
1071            match inner.as_rule() {
1072                Rule::memcmp_call => parse_builtin_call(inner),
1073                Rule::strncmp_call => parse_builtin_call(inner),
1074                Rule::starts_with_call => parse_builtin_call(inner),
1075                Rule::hex_call => parse_builtin_call(inner),
1076                Rule::postfix_access => parse_postfix_access(inner),
1077                Rule::cast_call => parse_cast_call(inner),
1078                Rule::chain_access => parse_chain_access(inner),
1079                Rule::pointer_deref => parse_pointer_deref(inner),
1080                Rule::address_of => parse_address_of(inner),
1081                Rule::int => match inner.as_str().parse::<i64>() {
1082                    Ok(value) => Ok(Expr::Int(value)),
1083                    Err(_) => Err(ParseError::TypeError(
1084                        "invalid decimal integer literal".to_string(),
1085                    )),
1086                },
1087                Rule::hex_int => {
1088                    // strip 0x and parse as hex
1089                    let s = inner.as_str();
1090                    match i64::from_str_radix(&s[2..], 16) {
1091                        Ok(v) => Ok(Expr::Int(v)),
1092                        Err(_) => Err(ParseError::TypeError(
1093                            "invalid hex integer literal".to_string(),
1094                        )),
1095                    }
1096                }
1097                Rule::oct_int => {
1098                    let s = inner.as_str();
1099                    match i64::from_str_radix(&s[2..], 8) {
1100                        Ok(v) => Ok(Expr::Int(v)),
1101                        Err(_) => Err(ParseError::TypeError(
1102                            "invalid octal integer literal".to_string(),
1103                        )),
1104                    }
1105                }
1106                Rule::bin_int => {
1107                    let s = inner.as_str();
1108                    match i64::from_str_radix(&s[2..], 2) {
1109                        Ok(v) => Ok(Expr::Int(v)),
1110                        Err(_) => Err(ParseError::TypeError(
1111                            "invalid binary integer literal".to_string(),
1112                        )),
1113                    }
1114                }
1115                // Floats are not supported by scripts/runtime; reject early with friendly error
1116                Rule::float => Err(ParseError::TypeError(
1117                    "float literals are not supported".to_string(),
1118                )),
1119                Rule::string => {
1120                    // Remove quotes at the beginning and end
1121                    let raw_value = inner.as_str();
1122                    let value = &raw_value[1..raw_value.len() - 1];
1123                    Ok(Expr::String(value.to_string()))
1124                }
1125                Rule::bool => {
1126                    let val = inner.as_str() == "true";
1127                    Ok(Expr::Bool(val))
1128                }
1129                Rule::identifier => {
1130                    let name = inner.as_str().to_string();
1131                    Ok(Expr::Variable(name))
1132                }
1133                Rule::array_access => parse_array_access(inner),
1134                Rule::member_access => parse_member_access(inner),
1135                Rule::special_var => {
1136                    let var_name = inner.as_str().to_string();
1137                    Ok(Expr::SpecialVar(var_name))
1138                }
1139                Rule::expr => parse_expr(inner),
1140                _ => Err(ParseError::UnexpectedToken(inner.as_rule())),
1141            }
1142        }
1143        _ => Err(ParseError::UnexpectedToken(pair.as_rule())),
1144    }
1145}
1146
1147fn parse_builtin_call(pair: Pair<Rule>) -> Result<Expr> {
1148    // pair is memcmp_call / strncmp_call / starts_with_call / hex_call
1149    let rule = pair.as_rule();
1150    let mut it = pair.into_inner();
1151    // First token inside is the function name as identifier within the rule text; easier approach: use rule to select
1152    match rule {
1153        Rule::memcmp_call => {
1154            // grammar: memcmp("(" expr "," expr ["," expr] ")")
1155            let mut nodes: Vec<_> = it.collect();
1156            if nodes.len() < 2 || nodes.len() > 3 {
1157                return Err(ParseError::InvalidExpression);
1158            }
1159            let a_expr = parse_expr(nodes.remove(0))?;
1160            let b_expr = parse_expr(nodes.remove(0))?;
1161
1162            // Disallow obviously invalid types early
1163            if matches!(a_expr, Expr::Bool(_)) || matches!(b_expr, Expr::Bool(_)) {
1164                return Err(ParseError::TypeError(
1165                    "memcmp pointer arguments cannot be boolean; use an address or hex(...)"
1166                        .to_string(),
1167                ));
1168            }
1169            if matches!(a_expr, Expr::String(_)) || matches!(b_expr, Expr::String(_)) {
1170                return Err(ParseError::TypeError(
1171                    "memcmp does not accept string literals; use strncmp for strings".to_string(),
1172                ));
1173            }
1174
1175            // Helper to get hex length (bytes)
1176            let hex_len = |e: &Expr| -> Option<usize> {
1177                if let Expr::BuiltinCall { name, args } = e {
1178                    if name == "hex" {
1179                        if let Some(Expr::String(s)) = args.first() {
1180                            return Some(s.len() / 2);
1181                        }
1182                    }
1183                }
1184                None
1185            };
1186
1187            let n_expr = if let Some(n_node) = nodes.first() {
1188                // With explicit len: reuse previous literal checks
1189                let n_expr = parse_expr(n_node.clone())?;
1190                if matches!(n_expr, Expr::Bool(_)) {
1191                    return Err(ParseError::TypeError(
1192                        "memcmp length must be an integer or expression, not boolean".to_string(),
1193                    ));
1194                }
1195                let literal_len_opt: Option<isize> = match &n_expr {
1196                    Expr::Int(n) => Some(*n as isize),
1197                    Expr::BinaryOp {
1198                        left,
1199                        op: BinaryOp::Subtract,
1200                        right,
1201                    } => {
1202                        if matches!(left.as_ref(), Expr::Int(0)) {
1203                            if let Expr::Int(k) = right.as_ref() {
1204                                Some(-(*k as isize))
1205                            } else {
1206                                None
1207                            }
1208                        } else {
1209                            None
1210                        }
1211                    }
1212                    _ => None,
1213                };
1214                if let Some(n) = literal_len_opt {
1215                    if n < 0 {
1216                        return Err(ParseError::TypeError(
1217                            "memcmp length must be non-negative".to_string(),
1218                        ));
1219                    }
1220                    let l = n as usize;
1221                    if let Some(la) = hex_len(&a_expr) {
1222                        if l > la {
1223                            return Err(ParseError::TypeError(format!(
1224                                "memcmp length ({l}) exceeds hex pattern size on left side ({la} bytes)"
1225                            )));
1226                        }
1227                    }
1228                    if let Some(lb) = hex_len(&b_expr) {
1229                        if l > lb {
1230                            return Err(ParseError::TypeError(format!(
1231                                "memcmp length ({l}) exceeds hex pattern size on right side ({lb} bytes)"
1232                            )));
1233                        }
1234                    }
1235                }
1236                n_expr
1237            } else {
1238                // No len provided: allow only when at least one side is hex(...)
1239                let la = hex_len(&a_expr);
1240                let lb = hex_len(&b_expr);
1241                match (la, lb) {
1242                    (Some(l), None) | (None, Some(l)) => Expr::Int(l as i64),
1243                    (Some(la), Some(lb)) => {
1244                        if la != lb {
1245                            return Err(ParseError::TypeError(
1246                                "memcmp hex operands have different sizes; provide explicit len"
1247                                    .to_string(),
1248                            ));
1249                        }
1250                        Expr::Int(la as i64)
1251                    }
1252                    _ => {
1253                        return Err(ParseError::TypeError(
1254                            "memcmp without len requires at least one hex(...) operand".to_string(),
1255                        ))
1256                    }
1257                }
1258            };
1259
1260            // Constant folding: memcmp(hex(...), hex(...), N)
1261            let as_hex = |e: &Expr| -> Option<String> {
1262                if let Expr::BuiltinCall { name, args } = e {
1263                    if name == "hex" {
1264                        if let Some(Expr::String(s)) = args.first() {
1265                            return Some(s.clone());
1266                        }
1267                    }
1268                }
1269                None
1270            };
1271
1272            if let (Some(h1), Some(h2), Expr::Int(n)) = (as_hex(&a_expr), as_hex(&b_expr), &n_expr)
1273            {
1274                // Safe hex -> bytes (sanitized earlier to hex digits only)
1275                fn hex_to_bytes(s: &str) -> std::result::Result<Vec<u8>, ParseError> {
1276                    let mut out = Vec::with_capacity(s.len() / 2);
1277                    let bytes = s.as_bytes();
1278                    let mut i = 0;
1279                    while i + 1 < bytes.len() {
1280                        let h = bytes[i] as char;
1281                        let l = bytes[i + 1] as char;
1282                        let hv = h
1283                            .to_digit(16)
1284                            .ok_or_else(|| ParseError::TypeError("invalid hex digit".to_string()))?
1285                            as u8;
1286                        let lv = l
1287                            .to_digit(16)
1288                            .ok_or_else(|| ParseError::TypeError("invalid hex digit".to_string()))?
1289                            as u8;
1290                        out.push((hv << 4) | lv);
1291                        i += 2;
1292                    }
1293                    Ok(out)
1294                }
1295
1296                let v1 = hex_to_bytes(&h1)?;
1297                let v2 = hex_to_bytes(&h2)?;
1298                let ln = (*n).max(0) as usize;
1299                let eq = v1.iter().take(ln).eq(v2.iter().take(ln));
1300                return Ok(Expr::Bool(eq));
1301            }
1302
1303            Ok(Expr::BuiltinCall {
1304                name: "memcmp".to_string(),
1305                args: vec![a_expr, b_expr, n_expr],
1306            })
1307        }
1308        Rule::strncmp_call => {
1309            // grammar: strncmp("(" expr "," expr "," expr ")")
1310            let arg0 = parse_expr(it.next().ok_or(ParseError::InvalidExpression)?)?;
1311            let arg1 = parse_expr(it.next().ok_or(ParseError::InvalidExpression)?)?;
1312            let n_expr_parsed = parse_expr(it.next().ok_or(ParseError::InvalidExpression)?)?;
1313            let literal_len_opt: Option<isize> = match &n_expr_parsed {
1314                Expr::Int(n) => Some(*n as isize),
1315                Expr::BinaryOp {
1316                    left,
1317                    op: BinaryOp::Subtract,
1318                    right,
1319                } => {
1320                    if matches!(left.as_ref(), Expr::Int(0)) {
1321                        if let Expr::Int(k) = right.as_ref() {
1322                            Some(-(*k as isize))
1323                        } else {
1324                            None
1325                        }
1326                    } else {
1327                        None
1328                    }
1329                }
1330                _ => None,
1331            };
1332            if literal_len_opt.is_some_and(|n| n < 0) {
1333                return Err(ParseError::TypeError(
1334                    "strncmp third argument must be non-negative".to_string(),
1335                ));
1336            }
1337            // Optional constant fold when both sides are string literals
1338            if let (Expr::String(a), Expr::String(b), Expr::Int(n_val)) =
1339                (&arg0, &arg1, &n_expr_parsed)
1340            {
1341                let ln = (*n_val).max(0) as usize;
1342                let eq = a
1343                    .as_bytes()
1344                    .iter()
1345                    .take(ln)
1346                    .eq(b.as_bytes().iter().take(ln));
1347                return Ok(Expr::Bool(eq));
1348            }
1349            Ok(Expr::BuiltinCall {
1350                name: "strncmp".to_string(),
1351                args: vec![arg0, arg1, n_expr_parsed],
1352            })
1353        }
1354        Rule::starts_with_call => {
1355            // grammar: starts_with("(" expr "," expr ")")
1356            let arg0 = parse_expr(it.next().ok_or(ParseError::InvalidExpression)?)?;
1357            let arg1 = parse_expr(it.next().ok_or(ParseError::InvalidExpression)?)?;
1358            // Constant fold when both are string literals
1359            if let (Expr::String(a), Expr::String(b)) = (&arg0, &arg1) {
1360                return Ok(Expr::Bool(a.as_bytes().starts_with(b.as_bytes())));
1361            }
1362            Ok(Expr::BuiltinCall {
1363                name: "starts_with".to_string(),
1364                args: vec![arg0, arg1],
1365            })
1366        }
1367        Rule::hex_call => {
1368            // grammar: hex("HEX...")
1369            // Validate at parse time: allow only hex digits with optional whitespace separators.
1370            let lit_node = it.next().ok_or(ParseError::InvalidExpression)?;
1371            if lit_node.as_rule() != Rule::string {
1372                return Err(ParseError::TypeError(
1373                    "hex expects a string literal".to_string(),
1374                ));
1375            }
1376            let raw = lit_node.as_str();
1377            let inner = &raw[1..raw.len() - 1];
1378            let mut sanitized = String::with_capacity(inner.len());
1379            for ch in inner.chars() {
1380                if ch.is_ascii_hexdigit() {
1381                    sanitized.push(ch);
1382                } else if ch == ' ' {
1383                    // allow spaces as separators (tabs not allowed)
1384                    continue;
1385                } else {
1386                    return Err(ParseError::TypeError(format!(
1387                        "hex literal contains non-hex character: '{ch}'"
1388                    )));
1389                }
1390            }
1391            if sanitized.len() % 2 == 1 {
1392                return Err(ParseError::TypeError(
1393                    "hex literal must contain an even number of hex digits".to_string(),
1394                ));
1395            }
1396            Ok(Expr::BuiltinCall {
1397                name: "hex".to_string(),
1398                // Store sanitized hex-only string; codegen will convert to bytes
1399                args: vec![Expr::String(sanitized)],
1400            })
1401        }
1402        _ => Err(ParseError::UnexpectedToken(rule)),
1403    }
1404}
1405
1406fn parse_cast_call(pair: Pair<Rule>) -> Result<Expr> {
1407    let mut inner = pair.into_inner();
1408    let expr_pair = inner.next().ok_or(ParseError::InvalidExpression)?;
1409    let type_pair = inner.next().ok_or(ParseError::InvalidExpression)?;
1410    let raw_type = type_pair.as_str();
1411    let target_type = raw_type
1412        .strip_prefix('"')
1413        .and_then(|s| s.strip_suffix('"'))
1414        .ok_or_else(|| ParseError::SyntaxError("cast target type must be a string".to_string()))?
1415        .to_string();
1416
1417    Ok(Expr::Cast {
1418        expr: Box::new(parse_expr(expr_pair)?),
1419        target_type,
1420    })
1421}
1422
1423fn parse_postfix_access(pair: Pair<Rule>) -> Result<Expr> {
1424    let mut inner = pair.into_inner();
1425    let base = inner.next().ok_or(ParseError::InvalidExpression)?;
1426    let mut expr = match base.as_rule() {
1427        Rule::postfix_base => {
1428            let base_inner = base
1429                .into_inner()
1430                .next()
1431                .ok_or(ParseError::InvalidExpression)?;
1432            match base_inner.as_rule() {
1433                Rule::cast_call => parse_cast_call(base_inner)?,
1434                Rule::special_var => Expr::SpecialVar(base_inner.as_str().to_string()),
1435                Rule::identifier => Expr::Variable(base_inner.as_str().to_string()),
1436                Rule::expr => parse_expr(base_inner)?,
1437                _ => return Err(ParseError::UnexpectedToken(base_inner.as_rule())),
1438            }
1439        }
1440        _ => return Err(ParseError::UnexpectedToken(base.as_rule())),
1441    };
1442
1443    for suffix in inner {
1444        let suffix_inner = suffix
1445            .into_inner()
1446            .next()
1447            .ok_or(ParseError::InvalidExpression)?;
1448        match suffix_inner.as_rule() {
1449            Rule::member_suffix => {
1450                let field = suffix_inner
1451                    .into_inner()
1452                    .next()
1453                    .ok_or(ParseError::InvalidExpression)?
1454                    .as_str()
1455                    .to_string();
1456                expr = Expr::MemberAccess(Box::new(expr), field);
1457            }
1458            Rule::index_suffix => {
1459                let index_pair = suffix_inner
1460                    .into_inner()
1461                    .next()
1462                    .ok_or(ParseError::InvalidExpression)?;
1463                let parsed_index = parse_expr(index_pair)?;
1464                let parsed_index = integer_literal_value(&parsed_index)
1465                    .map(Expr::Int)
1466                    .unwrap_or(parsed_index);
1467                expr = Expr::ArrayAccess(Box::new(expr), Box::new(parsed_index));
1468            }
1469            _ => return Err(ParseError::UnexpectedToken(suffix_inner.as_rule())),
1470        }
1471    }
1472
1473    Ok(expr)
1474}
1475
1476fn parse_trace_pattern(pair: Pair<Rule>) -> Result<TracePattern> {
1477    let inner = pair
1478        .into_inner()
1479        .next()
1480        .ok_or(ParseError::InvalidExpression)?;
1481
1482    match inner.as_rule() {
1483        Rule::module_hex_address => {
1484            let mut parts = inner.into_inner();
1485            let module = parts
1486                .next()
1487                .ok_or(ParseError::InvalidExpression)?
1488                .as_str()
1489                .to_string();
1490            let hex = parts.next().ok_or(ParseError::InvalidExpression)?.as_str();
1491            let addr = match u64::from_str_radix(&hex[2..], 16) {
1492                Ok(v) => v,
1493                Err(_) => {
1494                    return Err(ParseError::SyntaxError(format!(
1495                        "module-qualified address '{hex}' is invalid or too large for u64"
1496                    )))
1497                }
1498            };
1499            Ok(TracePattern::AddressInModule {
1500                module,
1501                address: addr,
1502            })
1503        }
1504        Rule::hex_address => {
1505            let addr_str = inner.as_str();
1506            // Remove "0x" prefix and parse as hex
1507            let addr_hex = &addr_str[2..];
1508            let addr = match u64::from_str_radix(addr_hex, 16) {
1509                Ok(v) => v,
1510                Err(_) => {
1511                    return Err(ParseError::SyntaxError(format!(
1512                        "address '{addr_str}' is invalid or too large for u64"
1513                    )))
1514                }
1515            };
1516            Ok(TracePattern::Address(addr))
1517        }
1518        Rule::wildcard_pattern => {
1519            let pattern = inner.as_str().to_string();
1520            Ok(TracePattern::Wildcard(pattern))
1521        }
1522        Rule::function_name => {
1523            let func_name = inner
1524                .into_inner()
1525                .next()
1526                .ok_or(ParseError::InvalidExpression)?
1527                .as_str()
1528                .to_string();
1529            Ok(TracePattern::FunctionName(func_name))
1530        }
1531        Rule::source_line => {
1532            let mut parts = inner.into_inner();
1533            let file_path = parts
1534                .next()
1535                .ok_or(ParseError::InvalidExpression)?
1536                .as_str()
1537                .to_string();
1538            let line_pair = parts.next().ok_or(ParseError::InvalidExpression)?;
1539            let line_number = line_pair
1540                .as_str()
1541                .parse::<u32>()
1542                .map_err(|_| ParseError::InvalidExpression)?;
1543            Ok(TracePattern::SourceLine {
1544                file_path,
1545                line_number,
1546            })
1547        }
1548        _ => Err(ParseError::UnexpectedToken(inner.as_rule())),
1549    }
1550}
1551
1552fn parse_print_content(pair: Pair<Rule>) -> Result<PrintStatement> {
1553    info!(
1554        "parse_print_content: rule={:?} text=\"{}\"",
1555        pair.as_rule(),
1556        pair.as_str().trim()
1557    );
1558    // Flatten any nested print_content nodes into a single list of children
1559    fn collect_flattened<'a>(p: Pair<'a, Rule>, out: &mut Vec<Pair<'a, Rule>>) {
1560        if p.as_rule() == Rule::print_content {
1561            for c in p.into_inner() {
1562                collect_flattened(c, out);
1563            }
1564        } else {
1565            out.push(p);
1566        }
1567    }
1568
1569    let mut flat: Vec<Pair<Rule>> = Vec::new();
1570    collect_flattened(pair, &mut flat);
1571    info!(
1572        "parse_print_content: flat_rules=[{}]",
1573        flat.iter()
1574            .map(|p| format!("{:?}", p.as_rule()))
1575            .collect::<Vec<_>>()
1576            .join(", ")
1577    );
1578    if flat.is_empty() {
1579        return Err(ParseError::InvalidExpression);
1580    }
1581
1582    // Prefer an explicit format_expr if present
1583    if let Some(fmt_idx) = flat.iter().position(|p| p.as_rule() == Rule::format_expr) {
1584        let fmt_pair = flat.remove(fmt_idx);
1585        info!("parse_print_content: branch=format_expr");
1586        let mut inner_pairs = fmt_pair.into_inner();
1587        let format_string = inner_pairs.next().ok_or(ParseError::InvalidExpression)?;
1588        let format_content = &format_string.as_str()[1..format_string.as_str().len() - 1];
1589        let mut args = Vec::new();
1590        for arg_pair in inner_pairs {
1591            args.push(parse_expr(arg_pair)?);
1592        }
1593        info!(
1594            "parse_print_content: fmt='{}' argc={}",
1595            format_content,
1596            args.len()
1597        );
1598        FormatValidator::validate_format_arguments(format_content, &args)?;
1599        return Ok(PrintStatement::Formatted {
1600            format: format_content.to_string(),
1601            args,
1602        });
1603    }
1604
1605    // Else, if first is a string and followed by one or more exprs, treat as flattened format
1606    if flat[0].as_rule() == Rule::string && flat.len() >= 2 {
1607        info!("parse_print_content: branch=flattened_string_with_args");
1608        let content_quoted = flat[0].as_str();
1609        let content = &content_quoted[1..content_quoted.len() - 1];
1610        let mut args = Vec::new();
1611        for p in flat.iter().skip(1) {
1612            if p.as_rule() != Rule::expr {
1613                return Err(ParseError::UnexpectedToken(p.as_rule()));
1614            }
1615            args.push(parse_expr(p.clone())?);
1616        }
1617        info!("parse_print_content: fmt='{}' argc={}", content, args.len());
1618        FormatValidator::validate_format_arguments(content, &args)?;
1619        return Ok(PrintStatement::Formatted {
1620            format: content.to_string(),
1621            args,
1622        });
1623    }
1624
1625    // Single string or single expr
1626    match flat[0].as_rule() {
1627        Rule::string => {
1628            info!("parse_print_content: branch=plain_string");
1629            let content = flat[0].as_str();
1630            let content = &content[1..content.len() - 1];
1631            Ok(PrintStatement::String(content.to_string()))
1632        }
1633        Rule::expr => {
1634            info!("parse_print_content: branch=complex_variable");
1635            let expr = parse_expr(flat[0].clone())?;
1636            Ok(PrintStatement::ComplexVariable(expr))
1637        }
1638        other => {
1639            info!("parse_print_content: branch=unexpected rule={:?}", other);
1640            Err(ParseError::UnexpectedToken(other))
1641        }
1642    }
1643}
1644
1645// Parse complex variable expressions (person.name, arr[0], etc.)
1646fn parse_complex_variable(pair: Pair<Rule>) -> Result<Expr> {
1647    debug!(
1648        "parse_complex_variable: {:?} = \"{}\"",
1649        pair.as_rule(),
1650        pair.as_str().trim()
1651    );
1652
1653    let inner = pair
1654        .into_inner()
1655        .next()
1656        .ok_or(ParseError::InvalidExpression)?;
1657    match inner.as_rule() {
1658        Rule::chain_access => parse_chain_access(inner),
1659        Rule::array_access => parse_array_access(inner),
1660        Rule::member_access => parse_member_access(inner),
1661        Rule::pointer_deref => parse_pointer_deref(inner),
1662        Rule::address_of => parse_address_of(inner),
1663        _ => Err(ParseError::UnexpectedToken(inner.as_rule())),
1664    }
1665}
1666
1667// Parse chain access: person.name.first
1668fn parse_chain_access(pair: Pair<Rule>) -> Result<Expr> {
1669    let mut chain: Vec<String> = Vec::new();
1670    let mut opt_index: Option<Expr> = None;
1671    for inner_pair in pair.into_inner() {
1672        match inner_pair.as_rule() {
1673            Rule::identifier => {
1674                chain.push(inner_pair.as_str().to_string());
1675            }
1676            Rule::expr => {
1677                // Array tail index can be a literal or a runtime expression.
1678                let parsed = parse_expr(inner_pair)?;
1679                opt_index = Some(
1680                    integer_literal_value(&parsed)
1681                        .map(Expr::Int)
1682                        .unwrap_or(parsed),
1683                );
1684            }
1685            _ => {}
1686        }
1687    }
1688
1689    if chain.is_empty() {
1690        return Err(ParseError::InvalidExpression);
1691    }
1692
1693    // Build base expression from the chain identifiers
1694    let mut expr = Expr::Variable(chain[0].clone());
1695    for seg in &chain[1..] {
1696        expr = Expr::MemberAccess(Box::new(expr), seg.clone());
1697    }
1698
1699    // If there's a trailing index, convert to ArrayAccess on the built base
1700    if let Some(idx) = opt_index {
1701        expr = Expr::ArrayAccess(Box::new(expr), Box::new(idx));
1702    }
1703
1704    Ok(expr)
1705}
1706
1707// Parse array access: arr[index]
1708fn parse_array_access(pair: Pair<Rule>) -> Result<Expr> {
1709    let mut inner_pairs = pair.into_inner();
1710    let array_name = inner_pairs.next().ok_or(ParseError::InvalidExpression)?;
1711    let index_expr = inner_pairs.next().ok_or(ParseError::InvalidExpression)?;
1712
1713    let _array_expr = Box::new(Expr::Variable(array_name.as_str().to_string()));
1714    let parsed_index = parse_expr(index_expr)?;
1715    let parsed_index = integer_literal_value(&parsed_index)
1716        .map(Expr::Int)
1717        .unwrap_or(parsed_index);
1718
1719    // Build base array access expression
1720    let mut expr = Expr::ArrayAccess(
1721        Box::new(Expr::Variable(array_name.as_str().to_string())),
1722        Box::new(parsed_index),
1723    );
1724
1725    // Consume trailing .field segments if present
1726    for next in inner_pairs {
1727        // Any remaining tokens are member identifiers
1728        let m = next.as_str().to_string();
1729        expr = Expr::MemberAccess(Box::new(expr), m);
1730    }
1731
1732    Ok(expr)
1733}
1734
1735// Parse member access: person.name
1736fn parse_member_access(pair: Pair<Rule>) -> Result<Expr> {
1737    let mut parts = pair.into_inner();
1738    let base = parts
1739        .next()
1740        .ok_or(ParseError::InvalidExpression)?
1741        .as_str()
1742        .to_string();
1743
1744    // Collect all subsequent identifiers after the base
1745    let mut tail: Vec<String> = Vec::new();
1746    for p in parts {
1747        tail.push(p.as_str().to_string());
1748    }
1749
1750    // If there is only one member, keep MemberAccess for simplicity.
1751    // For multi-level chains like a.b.c, normalize to ChainAccess([a, b, c])
1752    match tail.len() {
1753        0 => Err(ParseError::InvalidExpression),
1754        1 => Ok(Expr::MemberAccess(
1755            Box::new(Expr::Variable(base)),
1756            tail.remove(0),
1757        )),
1758        _ => {
1759            let mut chain = Vec::with_capacity(1 + tail.len());
1760            chain.push(base);
1761            chain.extend(tail);
1762            Ok(Expr::ChainAccess(chain))
1763        }
1764    }
1765}
1766
1767// Parse pointer dereference: *ptr
1768fn parse_pointer_deref(pair: Pair<Rule>) -> Result<Expr> {
1769    let mut inner = pair.into_inner();
1770    let target = inner.next().ok_or(ParseError::InvalidExpression)?;
1771    let parsed = match target.as_rule() {
1772        Rule::expr => parse_expr(target)?,
1773        Rule::postfix_access => parse_postfix_access(target)?,
1774        Rule::cast_call => parse_cast_call(target)?,
1775        Rule::complex_variable => parse_complex_variable(target)?,
1776        Rule::special_var => Expr::SpecialVar(target.as_str().to_string()),
1777        Rule::identifier => Expr::Variable(target.as_str().to_string()),
1778        _ => return Err(ParseError::UnexpectedToken(target.as_rule())),
1779    };
1780    // Early normalization: *(&x) => x
1781    match parsed {
1782        Expr::AddressOf(inner_expr) => Ok(*inner_expr),
1783        other => Ok(Expr::PointerDeref(Box::new(other))),
1784    }
1785}
1786
1787// Parse address-of: &expr
1788fn parse_address_of(pair: Pair<Rule>) -> Result<Expr> {
1789    let mut inner = pair.into_inner();
1790    let target = inner.next().ok_or(ParseError::InvalidExpression)?;
1791    let parsed = match target.as_rule() {
1792        Rule::expr => parse_expr(target)?,
1793        Rule::postfix_access => parse_postfix_access(target)?,
1794        Rule::cast_call => parse_cast_call(target)?,
1795        Rule::complex_variable => parse_complex_variable(target)?,
1796        Rule::special_var => Expr::SpecialVar(target.as_str().to_string()),
1797        Rule::identifier => Expr::Variable(target.as_str().to_string()),
1798        _ => return Err(ParseError::UnexpectedToken(target.as_rule())),
1799    };
1800    // Early normalization: &(*p) => p
1801    match parsed {
1802        Expr::PointerDeref(inner_expr) => Ok(*inner_expr),
1803        other => Ok(Expr::AddressOf(Box::new(other))),
1804    }
1805}
1806
1807#[cfg(test)]
1808mod tests {
1809    use super::*;
1810
1811    #[test]
1812    fn parse_memcmp_builtin_in_if_should_succeed() {
1813        let script = r#"
1814trace foo {
1815    if memcmp(&buf[0], &buf[1], 16) { print "EQ"; }
1816}
1817"#;
1818        let r = parse(script);
1819        assert!(r.is_ok(), "parse failed: {:?}", r.err());
1820    }
1821
1822    #[test]
1823    fn parse_cast_member_and_index_access() {
1824        let script = r#"
1825trace foo {
1826    print cast($arg0, "struct request *").id;
1827    print cast($arg1, "u32 *")[2];
1828    print *cast($arg1, "u32 *");
1829    print &cast($arg0, "struct request *").id;
1830}
1831"#;
1832        let program = parse(script).expect("parse should succeed");
1833        let Statement::TracePoint { body, .. } = &program.statements[0] else {
1834            panic!("expected trace point");
1835        };
1836        assert!(matches!(
1837            &body[0],
1838            Statement::Print(PrintStatement::ComplexVariable(Expr::MemberAccess(obj, field)))
1839                if field == "id" && matches!(obj.as_ref(), Expr::Cast { .. })
1840        ));
1841        assert!(matches!(
1842            &body[1],
1843            Statement::Print(PrintStatement::ComplexVariable(Expr::ArrayAccess(base, index)))
1844                if matches!(base.as_ref(), Expr::Cast { .. })
1845                    && matches!(index.as_ref(), Expr::Int(2))
1846        ));
1847        assert!(matches!(
1848            &body[2],
1849            Statement::Print(PrintStatement::ComplexVariable(Expr::PointerDeref(inner)))
1850                if matches!(inner.as_ref(), Expr::Cast { .. })
1851        ));
1852        assert!(matches!(
1853            &body[3],
1854            Statement::Print(PrintStatement::ComplexVariable(Expr::AddressOf(inner)))
1855                if matches!(
1856                    inner.as_ref(),
1857                    Expr::MemberAccess(obj, field)
1858                        if field == "id" && matches!(obj.as_ref(), Expr::Cast { .. })
1859                )
1860        ));
1861    }
1862
1863    #[test]
1864    fn parse_memcmp_with_dynamic_len() {
1865        let script = r#"
1866trace foo {
1867    let n = 10;
1868    if memcmp(&buf[0], &buf[0], n) { print "OK"; }
1869}
1870"#;
1871        let r = parse(script);
1872        assert!(r.is_ok(), "parse failed: {:?}", r.err());
1873    }
1874
1875    #[test]
1876    fn parse_if_else_with_flattened_format_and_star_len() {
1877        // else branch contains a flattened format print with {:s.*} and two args
1878        let script = r#"
1879trace src/http/ngx_http_request.c:1845 {
1880    if strncmp(host.data, "ghostscope", 10) {
1881        print "We got the request {}", *r;
1882    } else {
1883        print "The other hostname is {:s.*}", host.len, host.data;
1884    }
1885}
1886"#;
1887        let r = parse(script);
1888        assert!(r.is_ok(), "parse failed: {:?}", r.err());
1889    }
1890
1891    #[test]
1892    fn parse_memcmp_len_zero_and_negative() {
1893        let script = r#"
1894trace foo {
1895    if memcmp(&p[0], &q[0], 0) { print "Z0"; }
1896    let k = -5;
1897    if memcmp(&p[0], &q[0], k) { print "NEG"; }
1898}
1899"#;
1900        let r = parse(script);
1901        assert!(r.is_ok(), "parse failed: {:?}", r.err());
1902    }
1903
1904    #[test]
1905    fn parse_numeric_literals_hex_oct_bin_and_memcmp_usage() {
1906        let script = r#"
1907trace foo {
1908    let a = 0x10;   // 16
1909    let b = 0o755;  // 493
1910    let c = 0b1010; // 10
1911    // use in memcmp length
1912    if memcmp(&buf[0], &buf[0], 0x20) { print "H"; }
1913    if memcmp(&buf[0], &buf[0], 0o40) { print "O"; }
1914    if memcmp(&buf[0], &buf[0], 0b100000) { print "B"; }
1915    // use numeric literal as pointer address for second arg
1916    if memcmp(&buf[0], 0x7fff0000, 16) { print "P"; }
1917}
1918"#;
1919        let r = parse(script);
1920        assert!(r.is_ok(), "parse failed: {:?}", r.err());
1921    }
1922
1923    #[test]
1924    fn parse_memcmp_hex_builtin() {
1925        let script = r#"
1926trace foo {
1927    if memcmp(&buf[0], hex("504F"), 2) { print "OK"; }
1928}
1929"#;
1930        let r = parse(script);
1931        assert!(r.is_ok(), "parse failed: {:?}", r.err());
1932    }
1933
1934    #[test]
1935    fn parse_memcmp_with_numeric_pointers_and_len_bases() {
1936        let script = r#"
1937trace foo {
1938    let n = 0x10;
1939    if memcmp(0x1000, 0x2000, n) { print "NP"; }
1940    if memcmp(0o4000, 0b1000000000000, 0o20) { print "NP2"; }
1941}
1942"#;
1943        let r = parse(script);
1944        assert!(r.is_ok(), "parse failed: {:?}", r.err());
1945    }
1946
1947    #[test]
1948    fn parse_hex_with_non_hex_char_should_fail() {
1949        let script = r#"
1950trace foo {
1951    if memcmp(&buf[0], hex("G0"), 1) { print "X"; }
1952}
1953"#;
1954        let r = parse(script);
1955        match r {
1956            Ok(_) => panic!("expected parse error for non-hex char"),
1957            Err(ParseError::TypeError(msg)) => {
1958                assert!(
1959                    msg.contains("hex literal contains non-hex character"),
1960                    "unexpected msg: {msg}"
1961                );
1962            }
1963            Err(e) => panic!("unexpected error variant: {e:?}"),
1964        }
1965    }
1966
1967    #[test]
1968    fn parse_hex_with_odd_digits_should_fail() {
1969        let script = r#"
1970trace foo {
1971    if memcmp(&buf[0], hex("123"), 1) { print "X"; }
1972}
1973"#;
1974        let r = parse(script);
1975        match r {
1976            Ok(_) => panic!("expected parse error for odd-length hex"),
1977            Err(ParseError::TypeError(msg)) => {
1978                assert!(
1979                    msg.contains("even number of hex digits"),
1980                    "unexpected msg: {msg}"
1981                );
1982            }
1983            Err(e) => panic!("unexpected error variant: {e:?}"),
1984        }
1985    }
1986
1987    #[test]
1988    fn parse_hex_with_spaces_should_succeed() {
1989        let script = r#"
1990trace foo {
1991    if memcmp(&buf[0], hex("4c 49 42 5f"), 4) { print "OK"; }
1992}
1993"#;
1994        let r = parse(script);
1995        assert!(r.is_ok(), "parse failed: {:?}", r.err());
1996    }
1997
1998    #[test]
1999    fn parse_alias_declaration_address_of_and_member_access() {
2000        let script = r#"
2001trace foo {
2002    let p = &buf[0];
2003    let s = obj.field;
2004}
2005"#;
2006        let prog = parse(script).expect("parse ok");
2007        let stmt0 = prog.statements.first().expect("trace");
2008        match stmt0 {
2009            Statement::TracePoint { body, .. } => {
2010                // Only the address-of form should be alias; member access is a value binding
2011                assert!(matches!(body[0], Statement::AliasDeclaration { .. }));
2012                assert!(matches!(body[1], Statement::VarDeclaration { .. }));
2013            }
2014            other => panic!("expected TracePoint, got {other:?}"),
2015        }
2016    }
2017
2018    #[test]
2019    fn parse_alias_declaration_with_constant_offset() {
2020        let script = r#"
2021trace foo {
2022    let p = &arr[0] + 16;
2023    let q = 32 + &arr[0];
2024}
2025"#;
2026        let prog = parse(script).expect("parse ok");
2027        let stmt0 = prog.statements.first().expect("trace");
2028        match stmt0 {
2029            Statement::TracePoint { body, .. } => {
2030                assert!(matches!(body[0], Statement::AliasDeclaration { .. }));
2031                assert!(matches!(body[1], Statement::AliasDeclaration { .. }));
2032            }
2033            other => panic!("expected TracePoint, got {other:?}"),
2034        }
2035    }
2036
2037    #[test]
2038    fn parse_member_access_scalar_not_alias() {
2039        let script = r#"
2040trace foo {
2041    let level = record.level;
2042}
2043"#;
2044        let prog = parse(script).expect("parse ok");
2045        let stmt0 = prog.statements.first().expect("trace");
2046        match stmt0 {
2047            Statement::TracePoint { body, .. } => {
2048                assert!(matches!(body[0], Statement::VarDeclaration { .. }));
2049            }
2050            other => panic!("expected TracePoint, got {other:?}"),
2051        }
2052    }
2053
2054    #[test]
2055    fn parse_memcmp_rejects_string_literal() {
2056        let script = r#"
2057trace foo {
2058    if memcmp(&buf[0], "PO", 2) { print "X"; }
2059}
2060"#;
2061        let r = parse(script);
2062        assert!(
2063            matches!(r, Err(ParseError::TypeError(ref msg)) if msg.contains("memcmp does not accept string literals")),
2064            "expected type error, got: {r:?}"
2065        );
2066    }
2067
2068    #[test]
2069    fn parse_memcmp_rejects_bool_args_and_len() {
2070        // Bool as pointer argument
2071        let s1 = r#"
2072trace foo { if memcmp(true, hex("00"), 1) { print "X"; } }
2073"#;
2074        let r1 = parse(s1);
2075        assert!(r1.is_err());
2076
2077        // Bool as length
2078        let s2 = r#"
2079trace foo { if memcmp(&p[0], hex("00"), false) { print "X"; } }
2080"#;
2081        let r2 = parse(s2);
2082        assert!(
2083            matches!(r2, Err(ParseError::TypeError(ref msg)) if msg.contains("length must be")),
2084            "unexpected: {r2:?}"
2085        );
2086    }
2087
2088    #[test]
2089    fn parse_strncmp_constant_folds_on_two_literals() {
2090        // equal for first 2 bytes
2091        let s = r#"
2092trace foo {
2093    if strncmp("abc", "abd", 2) { print "T"; } else { print "F"; }
2094}
2095"#;
2096        let prog = parse(s).expect("parse ok");
2097        // Walk down to the If condition and ensure it became a Bool(true)
2098        let stmt0 = prog.statements.first().expect("one trace");
2099        match stmt0 {
2100            Statement::TracePoint { body, .. } => match &body[0] {
2101                Statement::If { condition, .. } => {
2102                    assert!(matches!(condition, Expr::Bool(true)));
2103                }
2104                other => panic!("expected If, got {other:?}"),
2105            },
2106            other => panic!("expected TracePoint, got {other:?}"),
2107        }
2108    }
2109
2110    #[test]
2111    fn parse_strncmp_requires_one_string_side_error() {
2112        let s = r#"
2113trace foo {
2114    if strncmp(1, 2, 1) { print "X"; }
2115}
2116"#;
2117        let r = parse(s);
2118        // Parser now accepts generic expr, so error will occur in compiler stage; ensure parse ok here
2119        assert!(
2120            r.is_ok(),
2121            "parse should succeed; semantic error in compiler"
2122        );
2123    }
2124
2125    #[test]
2126    fn parse_memcmp_constant_folds_on_two_hex() {
2127        let s = r#"
2128trace foo {
2129    if memcmp(hex("504f"), hex("504F"), 2) { print "EQ"; } else { print "NE"; }
2130}
2131"#;
2132        let prog = parse(s).expect("parse ok");
2133        let stmt0 = prog.statements.first().expect("one trace");
2134        match stmt0 {
2135            Statement::TracePoint { body, .. } => match &body[0] {
2136                Statement::If { condition, .. } => assert!(matches!(condition, Expr::Bool(true))),
2137                other => panic!("expected If, got {other:?}"),
2138            },
2139            other => panic!("expected TracePoint, got {other:?}"),
2140        }
2141
2142        // Mismatch without explicit len but equal sizes
2143        let s2 = r#"
2144trace foo {
2145    if memcmp(hex("504f"), hex("514f")) { print "EQ"; } else { print "NE"; }
2146}
2147"#;
2148        let prog2 = parse(s2).expect("parse ok");
2149        let stmt02 = prog2.statements.first().expect("one trace");
2150        match stmt02 {
2151            Statement::TracePoint { body, .. } => match &body[0] {
2152                Statement::If { condition, .. } => assert!(matches!(condition, Expr::Bool(false))),
2153                other => panic!("expected If, got {other:?}"),
2154            },
2155            other => panic!("expected TracePoint, got {other:?}"),
2156        }
2157    }
2158
2159    #[test]
2160    fn parse_starts_with_constant_folds_on_two_literals() {
2161        let s = r#"
2162trace foo {
2163    if starts_with("abcdef", "abc") { print "T"; } else { print "F"; }
2164}
2165"#;
2166        let prog = parse(s).expect("parse ok");
2167        let stmt0 = prog.statements.first().expect("one trace");
2168        match stmt0 {
2169            Statement::TracePoint { body, .. } => match &body[0] {
2170                Statement::If { condition, .. } => assert!(matches!(condition, Expr::Bool(true))),
2171                other => panic!("expected If, got {other:?}"),
2172            },
2173            other => panic!("expected TracePoint, got {other:?}"),
2174        }
2175
2176        let s2 = r#"
2177trace foo {
2178    if starts_with("ab", "abc") { print "T"; } else { print "F"; }
2179}
2180"#;
2181        let prog2 = parse(s2).expect("parse ok");
2182        let stmt02 = prog2.statements.first().expect("one trace");
2183        match stmt02 {
2184            Statement::TracePoint { body, .. } => match &body[0] {
2185                Statement::If { condition, .. } => assert!(matches!(condition, Expr::Bool(false))),
2186                other => panic!("expected If, got {other:?}"),
2187            },
2188            other => panic!("expected TracePoint, got {other:?}"),
2189        }
2190    }
2191
2192    #[test]
2193    fn parse_memcmp_hex_len_exceeds_left_should_fail() {
2194        // hex has 2 bytes, len=3 should error on left side
2195        let script = r#"
2196trace foo {
2197    if memcmp(hex("504f"), &buf[0], 3) { print "X"; }
2198}
2199"#;
2200        let r = parse(script);
2201        match r {
2202            Ok(_) => panic!("expected parse error for len > hex(left) size"),
2203            Err(ParseError::TypeError(msg)) => {
2204                assert!(
2205                    msg.contains("exceeds hex pattern size on left side"),
2206                    "unexpected msg: {msg}"
2207                );
2208            }
2209            Err(e) => panic!("unexpected error variant: {e:?}"),
2210        }
2211    }
2212
2213    #[test]
2214    fn parse_memcmp_hex_len_exceeds_right_should_fail() {
2215        // hex has 2 bytes, len=5 should error on right side
2216        let script = r#"
2217trace foo {
2218    if memcmp(&buf[0], hex("50 4f"), 5) { print "X"; }
2219}
2220"#;
2221        let r = parse(script);
2222        match r {
2223            Ok(_) => panic!("expected parse error for len > hex(right) size"),
2224            Err(ParseError::TypeError(msg)) => {
2225                assert!(
2226                    msg.contains("exceeds hex pattern size on right side"),
2227                    "unexpected msg: {msg}"
2228                );
2229            }
2230            Err(e) => panic!("unexpected error variant: {e:?}"),
2231        }
2232    }
2233
2234    #[test]
2235    fn parse_memcmp_hex_negative_len_should_fail() {
2236        let script = r#"
2237trace foo {
2238    if memcmp(&buf[0], hex("50 4f"), -1) { print "X"; }
2239}
2240"#;
2241        let r = parse(script);
2242        match r {
2243            Ok(_) => panic!("expected parse error for negative len"),
2244            Err(ParseError::TypeError(msg)) => {
2245                assert!(
2246                    msg.contains("length must be non-negative"),
2247                    "unexpected msg: {msg}"
2248                );
2249            }
2250            Err(e) => panic!("unexpected error variant: {e:?}"),
2251        }
2252    }
2253
2254    #[test]
2255    fn parse_memcmp_hex_len_equal_should_succeed() {
2256        // hex has 4 bytes, len=4 OK
2257        let script = r#"
2258trace foo {
2259    if memcmp(&buf[0], hex("de ad be ef"), 4) { print "OK"; }
2260}
2261"#;
2262        let r = parse(script);
2263        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2264    }
2265
2266    #[test]
2267    fn parse_memcmp_hex_infers_len_left_should_succeed() {
2268        let script = r#"
2269trace foo {
2270    if memcmp(hex("50 4f"), &buf[0]) { print "OK"; }
2271}
2272"#;
2273        let r = parse(script);
2274        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2275    }
2276
2277    #[test]
2278    fn parse_memcmp_hex_infers_len_right_should_succeed() {
2279        let script = r#"
2280trace foo {
2281    if memcmp(&buf[0], hex("de ad be ef")) { print "OK"; }
2282}
2283"#;
2284        let r = parse(script);
2285        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2286    }
2287
2288    #[test]
2289    fn parse_assignment_is_rejected_with_friendly_message() {
2290        let script = r#"
2291trace foo {
2292    let a = 1;
2293    a = 2;
2294}
2295"#;
2296        let r = parse(script);
2297        match r {
2298            Ok(_) => panic!("expected assignment error for immutable variables"),
2299            Err(ParseError::TypeError(msg)) => {
2300                assert!(
2301                    msg.contains("Assignment is not supported"),
2302                    "unexpected msg: {msg}"
2303                );
2304            }
2305            Err(e) => panic!("unexpected error variant: {e:?}"),
2306        }
2307    }
2308
2309    #[test]
2310    fn parse_starts_with_accepts_two_exprs() {
2311        // Both sides are expr (identifiers); grammar should accept
2312        let script = r#"
2313trace foo {
2314    if starts_with(name, s) { print "OK"; }
2315}
2316"#;
2317        let r = parse(script);
2318        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2319    }
2320
2321    #[test]
2322    fn parse_strncmp_accepts_two_exprs_and_len() {
2323        let script = r#"
2324trace foo {
2325    if strncmp(lhs, rhs, 3) { print "EQ"; }
2326}
2327"#;
2328        let r = parse(script);
2329        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2330    }
2331
2332    #[test]
2333    fn parse_strncmp_negative_len_rejected() {
2334        // Negative literal lengths are rejected early.
2335        let script = r#"
2336trace foo {
2337    if strncmp(lhs, rhs, -1) { print "X"; }
2338}
2339"#;
2340        let r = parse(script);
2341        assert!(r.is_err(), "expected parse error for negative length");
2342        if let Err(ParseError::TypeError(msg)) = r {
2343            assert!(msg.contains("non-negative"), "unexpected msg: {msg}");
2344        }
2345    }
2346
2347    #[test]
2348    fn parse_strncmp_accepts_nonliteral_len() {
2349        let script = r#"
2350trace foo {
2351    let n = 3;
2352    if strncmp(lhs, rhs, n) { print "X"; }
2353}
2354"#;
2355        let r = parse(script);
2356        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2357    }
2358
2359    #[test]
2360    fn parse_memcmp_missing_len_without_hex_should_fail() {
2361        let script = r#"
2362trace foo {
2363    if memcmp(&buf[0], &buf[1]) { print "OK"; }
2364}
2365"#;
2366        let r = parse(script);
2367        assert!(
2368            r.is_err(),
2369            "expected parse error for missing len without hex"
2370        );
2371    }
2372
2373    #[test]
2374    fn parse_memcmp_both_hex_mismatch_should_fail() {
2375        let script = r#"
2376trace foo {
2377    if memcmp(hex("50"), hex("504f")) { print "OK"; }
2378}
2379"#;
2380        let r = parse(script);
2381        match r {
2382            Ok(_) => panic!("expected parse error for mismatched hex sizes"),
2383            Err(ParseError::TypeError(msg)) => {
2384                assert!(msg.contains("different sizes"), "unexpected msg: {msg}");
2385            }
2386            Err(e) => panic!("unexpected error variant: {e:?}"),
2387        }
2388    }
2389
2390    #[test]
2391    fn parse_format_static_len_bases_in_prints() {
2392        // Validate that static length .N supports 0x/0o/0b in formatted prints
2393        let script = r#"
2394trace foo {
2395    print "HX={:x.0x10}", buf;
2396    print "HS={:s.0o20}", buf;
2397    print "HB={:X.0b1000}", buf;
2398}
2399"#;
2400        let r = parse(script);
2401        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2402    }
2403
2404    #[test]
2405    fn parse_trace_patterns_function_line_address_wildcard() {
2406        // Function name
2407        let s1 = r#"trace main { print "OK"; }"#;
2408        assert!(parse(s1).is_ok());
2409
2410        // Source line with path and hyphen
2411        let s2 = r#"trace /tmp/test-file.c:42 { print "L"; }"#;
2412        assert!(parse(s2).is_ok());
2413
2414        // Hex address
2415        let s3 = r#"trace 0x401234 { print "A"; }"#;
2416        assert!(parse(s3).is_ok());
2417
2418        // Wildcard
2419        let s4 = r#"trace printf* { print "W"; }"#;
2420        assert!(parse(s4).is_ok());
2421
2422        // Module-qualified address
2423        let s5 = r#"trace /lib/x86_64-linux-gnu/libc.so.6:0x1234 { print "M"; }"#;
2424        assert!(parse(s5).is_ok());
2425    }
2426
2427    #[test]
2428    fn parse_identifiers_can_start_with_underscore() {
2429        let function = r#"trace __UpdateTicketInformation { print "OK"; }"#;
2430        assert!(parse(function).is_ok());
2431
2432        let wildcard = r#"trace __builtin_* { print "W"; }"#;
2433        assert!(parse(wildcard).is_ok());
2434
2435        let script = r#"
2436trace _start {
2437    let _ticket = __dwarf_value;
2438    print _ticket;
2439}
2440"#;
2441        assert!(parse(script).is_ok());
2442    }
2443
2444    #[test]
2445    fn parse_module_hex_address_overflow_should_error() {
2446        // Address exceeds u64 (17 hex digits) -> parse error, not 0 fallback
2447        let s = r#"trace libfoo.so:0x10000000000000000 { print "X"; }"#;
2448        let r = parse(s);
2449        match r {
2450            Err(ParseError::SyntaxError(msg)) => assert!(msg.contains("too large for u64")),
2451            other => panic!("expected friendly SyntaxError, got {other:?}"),
2452        }
2453    }
2454
2455    #[test]
2456    fn parse_hex_address_overflow_should_error() {
2457        let s = r#"trace 0x10000000000000000 { print "X"; }"#;
2458        let r = parse(s);
2459        match r {
2460            Err(ParseError::SyntaxError(msg)) => assert!(msg.contains("too large for u64")),
2461            other => panic!("expected friendly SyntaxError, got {other:?}"),
2462        }
2463    }
2464
2465    #[test]
2466    fn parse_special_variables_basic() {
2467        // $pid/$tid/$host_pid/$input_pid/$timestamp in expressions and prints
2468        let script = r#"
2469trace foo {
2470    if $pid == 123 && $tid != 0 && $host_pid != 0 && $input_pid == 123 { print "PID_TID"; }
2471    print $timestamp;
2472    print "P:{} T:{} HP:{} IN:{} TS:{}", $pid, $tid, $host_pid, $input_pid, $timestamp;
2473}
2474"#;
2475        let r = parse(script);
2476        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2477    }
2478
2479    #[test]
2480    fn parse_chain_and_array_access() {
2481        // Member/chain and array tail index
2482        let script = r#"
2483trace foo {
2484    print person.name.first;
2485    print arr[0];
2486    // Supported: top-level array access with trailing member
2487    print ifaces[0].mtu;
2488}
2489"#;
2490        let r = parse(script);
2491        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2492    }
2493
2494    #[test]
2495    fn parse_pointer_and_address_of() {
2496        let script = r#"
2497trace foo {
2498    print *ptr;
2499    print &var;
2500    print *(arr_ptr);
2501}
2502"#;
2503        let r = parse(script);
2504        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2505    }
2506
2507    #[test]
2508    fn parse_nested_trace_is_rejected() {
2509        let s = r#"
2510trace foo {
2511    trace bar { print "X"; }
2512}
2513"#;
2514        let r = parse(s);
2515        match r {
2516            Err(ParseError::SyntaxError(msg)) => assert!(msg.contains("cannot be nested")),
2517            other => panic!("expected SyntaxError for nested trace, got {other:?}"),
2518        }
2519    }
2520
2521    #[test]
2522    fn parse_float_literal_is_rejected() {
2523        let s = r#"
2524trace foo {
2525    let x = 1.23;
2526}
2527"#;
2528        let r = parse(s);
2529        match r {
2530            Err(ParseError::TypeError(msg)) => {
2531                assert!(msg.contains("float literals are not supported"))
2532            }
2533            other => panic!("expected TypeError for float literal, got {other:?}"),
2534        }
2535    }
2536
2537    #[test]
2538    fn parse_unclosed_print_string_reports_friendly_error() {
2539        let bad = r#"
2540trace foo {
2541    print "Unclosed {}, value
2542}
2543"#;
2544        let r = parse(bad);
2545        match r {
2546            Err(ParseError::SyntaxError(msg)) => assert!(msg.contains("Unclosed string literal")),
2547            other => panic!("expected SyntaxError, got {other:?}"),
2548        }
2549    }
2550
2551    #[test]
2552    fn parse_array_index_accepts_dynamic_expr() {
2553        // Dynamic index on top-level array
2554        let s1 = r#"
2555trace foo {
2556    print arr[i];
2557}
2558"#;
2559        let r1 = parse(s1).expect("dynamic top-level index should parse");
2560        match r1.statements.first().expect("trace") {
2561            Statement::TracePoint { body, .. } => match &body[0] {
2562                Statement::Print(PrintStatement::ComplexVariable(Expr::ArrayAccess(_, index))) => {
2563                    assert!(matches!(index.as_ref(), Expr::Variable(name) if name == "i"))
2564                }
2565                other => panic!("unexpected first print body: {other:?}"),
2566            },
2567            other => panic!("expected TracePoint, got {other:?}"),
2568        }
2569
2570        // Dynamic index at chain tail
2571        let s2 = r#"
2572trace foo {
2573    print obj.arr[i - (i / 0x8) * 0x8];
2574}
2575"#;
2576        let r2 = parse(s2).expect("dynamic chain index should parse");
2577        match r2.statements.first().expect("trace") {
2578            Statement::TracePoint { body, .. } => match &body[0] {
2579                Statement::Print(PrintStatement::ComplexVariable(Expr::ArrayAccess(_, index))) => {
2580                    assert!(matches!(index.as_ref(), Expr::BinaryOp { .. }))
2581                }
2582                other => panic!("unexpected first print body: {other:?}"),
2583            },
2584            other => panic!("expected TracePoint, got {other:?}"),
2585        }
2586    }
2587
2588    #[test]
2589    fn parse_integer_modulo_and_bitwise_ops() {
2590        let script = r#"
2591trace foo {
2592    let value = 0x1 | 0x2 ^ 0x3 & 0x4 << 0x1 + 0x2 % 0x3;
2593    let inverse = ~value;
2594}
2595"#;
2596        let prog = parse(script).expect("integer and bitwise ops should parse");
2597        let Statement::TracePoint { body, .. } = prog.statements.first().expect("trace") else {
2598            panic!("expected trace point");
2599        };
2600        let Statement::VarDeclaration { value, .. } = &body[0] else {
2601            panic!("expected var declaration");
2602        };
2603        let Expr::BinaryOp { op, left, right } = value else {
2604            panic!("expected bitwise-or root");
2605        };
2606        assert_eq!(*op, BinaryOp::BitOr);
2607        assert!(matches!(left.as_ref(), Expr::Int(1)));
2608        assert!(matches!(
2609            right.as_ref(),
2610            Expr::BinaryOp {
2611                op: BinaryOp::BitXor,
2612                ..
2613            }
2614        ));
2615        assert!(matches!(
2616            &body[1],
2617            Statement::VarDeclaration {
2618                value: Expr::UnaryBitNot(_),
2619                ..
2620            }
2621        ));
2622    }
2623
2624    #[test]
2625    fn parse_array_index_accepts_constant_negative_literal() {
2626        let script = r#"
2627trace foo {
2628    print arr[-0x1];
2629    print obj.arr[0b10 - 0x3];
2630}
2631"#;
2632        let prog = parse(script).expect("parse ok");
2633        let trace = prog.statements.first().expect("trace");
2634        match trace {
2635            Statement::TracePoint { body, .. } => {
2636                match &body[0] {
2637                    Statement::Print(PrintStatement::ComplexVariable(Expr::ArrayAccess(
2638                        _,
2639                        index,
2640                    ))) => {
2641                        assert!(matches!(index.as_ref(), Expr::Int(-1)));
2642                    }
2643                    other => panic!("unexpected first print body: {other:?}"),
2644                }
2645                match &body[1] {
2646                    Statement::Print(PrintStatement::ComplexVariable(Expr::ArrayAccess(
2647                        _,
2648                        index,
2649                    ))) => {
2650                        assert!(matches!(index.as_ref(), Expr::Int(-1)));
2651                    }
2652                    other => panic!("unexpected second print body: {other:?}"),
2653                }
2654            }
2655            other => panic!("expected TracePoint, got {other:?}"),
2656        }
2657    }
2658
2659    #[test]
2660    fn parse_print_format_arg_mismatch_reports_error() {
2661        // format_expr form
2662        let s1 = r#"
2663trace foo {
2664    print "A {} {}", x;
2665}
2666"#;
2667        let r1 = parse(s1);
2668        match r1 {
2669            Err(ParseError::TypeError(msg)) => {
2670                assert!(msg.contains("expects 2 argument(s)"), "unexpected: {msg}");
2671            }
2672            other => panic!("expected TypeError from format arg mismatch, got {other:?}"),
2673        }
2674
2675        // flattened string + args form
2676        let s2 = r#"
2677trace foo {
2678    print "B {} {}", y;
2679}
2680"#;
2681        let r2 = parse(s2);
2682        match r2 {
2683            Err(ParseError::TypeError(msg)) => {
2684                assert!(msg.contains("expects 2 argument(s)"));
2685            }
2686            other => panic!("expected TypeError from format arg mismatch, got {other:?}"),
2687        }
2688    }
2689
2690    #[test]
2691    fn parse_print_invalid_format_specifier_errors() {
2692        // Missing ':' prefix inside { }
2693        let s1 = r#"
2694trace foo { print "Bad {x}", 1; }
2695"#;
2696        let r1 = parse(s1);
2697        match r1 {
2698            Err(ParseError::TypeError(msg)) => {
2699                assert!(msg.contains("Invalid format specifier"), "{msg}");
2700            }
2701            other => panic!("expected TypeError, got {other:?}"),
2702        }
2703
2704        // Unsupported conversion {:q}
2705        let s2 = r#"
2706trace foo { print "Bad {:q}", 1; }
2707"#;
2708        let r2 = parse(s2);
2709        match r2 {
2710            Err(ParseError::TypeError(msg)) => {
2711                assert!(msg.contains("Unsupported format conversion"), "{msg}");
2712            }
2713            other => panic!("expected TypeError, got {other:?}"),
2714        }
2715    }
2716
2717    #[test]
2718    fn parse_hex_with_tab_is_rejected() {
2719        let s = r#"
2720trace foo {
2721    if memcmp(&buf[0], hex("50\t4f"), 2) { print "X"; }
2722}
2723"#;
2724        let r = parse(s);
2725        match r {
2726            Err(ParseError::TypeError(msg)) => {
2727                assert!(msg.contains("non-hex character"), "{msg}");
2728            }
2729            other => panic!("expected TypeError for tab in hex literal, got {other:?}"),
2730        }
2731    }
2732
2733    #[test]
2734    fn parse_starts_with_constant_folds_on_literals() {
2735        let s = r#"
2736trace foo {
2737    if starts_with("abcdef", "abc") { print "T"; } else { print "F"; }
2738}
2739"#;
2740        let prog = parse(s).expect("parse ok");
2741        let stmt0 = prog.statements.first().expect("trace");
2742        match stmt0 {
2743            Statement::TracePoint { body, .. } => match &body[0] {
2744                Statement::If { condition, .. } => {
2745                    assert!(matches!(condition, Expr::Bool(true)));
2746                }
2747                other => panic!("expected If, got {other:?}"),
2748            },
2749            other => panic!("expected TracePoint, got {other:?}"),
2750        }
2751    }
2752
2753    #[test]
2754    fn parse_backtrace_and_bt_statements() {
2755        let s = r#"
2756	trace foo {
2757	    backtrace;
2758	    bt;
2759	    bt raw;
2760	    bt full noinline;
2761	}
2762	"#;
2763        let program = parse(s).expect("parse ok");
2764        let Statement::TracePoint { body, .. } = &program.statements[0] else {
2765            panic!("expected trace");
2766        };
2767        assert_eq!(body.len(), 4);
2768        match &body[2] {
2769            Statement::Backtrace(bt) => {
2770                assert!(bt.raw);
2771                assert!(bt.inline);
2772            }
2773            other => panic!("expected backtrace, got {other:?}"),
2774        }
2775        match &body[3] {
2776            Statement::Backtrace(bt) => {
2777                assert!(bt.full);
2778                assert!(!bt.inline);
2779            }
2780            other => panic!("expected backtrace, got {other:?}"),
2781        }
2782    }
2783
2784    #[test]
2785    fn parse_backtrace_rejects_named_depth_option() {
2786        let s = r#"
2787	trace foo {
2788	    bt depth=8;
2789	}
2790	"#;
2791        let r = parse(s);
2792        match r {
2793            Err(ParseError::SyntaxError(msg)) => {
2794                assert!(msg.contains("no longer a script option"), "{msg}");
2795                assert!(msg.contains("--backtrace-depth"), "{msg}");
2796            }
2797            other => panic!("expected SyntaxError, got {other:?}"),
2798        }
2799    }
2800
2801    #[test]
2802    fn parse_backtrace_rejects_positional_depth_option() {
2803        let s = r#"
2804	trace foo {
2805	    bt 4 raw;
2806	}
2807	"#;
2808        let r = parse(s);
2809        match r {
2810            Err(ParseError::SyntaxError(msg)) => {
2811                assert!(msg.contains("no longer a script option"), "{msg}");
2812            }
2813            other => panic!("expected SyntaxError, got {other:?}"),
2814        }
2815    }
2816
2817    #[test]
2818    fn parse_print_capture_len_suffix() {
2819        // {:s.name$} uses capture; does not consume extra arg
2820        let s = r#"
2821trace foo {
2822    let n = 3;
2823    print "tail={:s.n$}", p;
2824}
2825"#;
2826        let r = parse(s);
2827        assert!(r.is_ok(), "parse failed: {:?}", r.err());
2828    }
2829
2830    #[test]
2831    fn parse_unknown_keyword_inside_trace_suggests_print() {
2832        let s = r#"
2833trace foo {
2834    pront "hello";
2835}
2836"#;
2837        let r = parse(s);
2838        match r {
2839            Err(ParseError::SyntaxError(msg)) => {
2840                assert!(
2841                    msg.contains("Unknown keyword 'pront'"),
2842                    "unexpected msg: {msg}"
2843                );
2844                assert!(
2845                    msg.contains("Did you mean 'print'"),
2846                    "no suggestion in msg: {msg}"
2847                );
2848            }
2849            other => panic!("expected friendly SyntaxError for unknown keyword, got {other:?}"),
2850        }
2851    }
2852
2853    #[test]
2854    fn parse_unknown_keyword_same_line_after_brace_suggests_print() {
2855        // Unknown keyword immediately after '{' on the same line
2856        let s = r#"trace foo {pirnt \"sa\";}"#;
2857        let r = parse(s);
2858        match r {
2859            Err(ParseError::SyntaxError(msg)) => {
2860                assert!(
2861                    msg.contains("Unknown keyword 'pirnt'"),
2862                    "unexpected msg: {msg}"
2863                );
2864                assert!(
2865                    msg.contains("Did you mean 'print'"),
2866                    "no suggestion in msg: {msg}"
2867                );
2868            }
2869            other => {
2870                panic!("expected friendly SyntaxError for same-line unknown keyword, got {other:?}")
2871            }
2872        }
2873    }
2874
2875    #[test]
2876    fn parse_unknown_top_level_keyword_suggests_trace() {
2877        let s = r#"
2878traec bar {
2879    print "x";
2880}
2881"#;
2882        let r = parse(s);
2883        match r {
2884            Err(ParseError::SyntaxError(msg)) => {
2885                assert!(
2886                    msg.contains("Unknown keyword 'traec'"),
2887                    "unexpected msg: {msg}"
2888                );
2889                assert!(
2890                    msg.contains("Did you mean 'trace'"),
2891                    "no suggestion in msg: {msg}"
2892                );
2893            }
2894            other => panic!("expected friendly SyntaxError for unknown keyword, got {other:?}"),
2895        }
2896    }
2897
2898    #[test]
2899    fn parse_builtin_then_misspelled_keyword_should_point_to_misspell() {
2900        // Ensure builtin calls are not flagged; the real typo should be reported
2901        let s = r#"
2902trace foo {
2903    starts_with("a", "b"); prnit "oops";
2904}
2905"#;
2906        let r = parse(s);
2907        match r {
2908            Err(ParseError::SyntaxError(msg)) => {
2909                assert!(
2910                    msg.contains("prnit"),
2911                    "should point to misspelled 'prnit': {msg}"
2912                );
2913                assert!(
2914                    !msg.contains("starts_with"),
2915                    "should not flag builtin call: {msg}"
2916                );
2917            }
2918            other => panic!("expected friendly SyntaxError for misspelled print, got {other:?}"),
2919        }
2920    }
2921
2922    #[test]
2923    fn parse_misspelled_builtin_suggests_starts_with() {
2924        // Misspelled builtin should suggest the correct builtin name
2925        let s = r#"
2926trace foo {
2927    starst_with("a", "b");
2928}
2929"#;
2930        let r = parse(s);
2931        match r {
2932            Err(ParseError::SyntaxError(msg)) => {
2933                assert!(msg.contains("Unknown keyword 'starst_with'"), "{msg}");
2934                assert!(msg.contains("Did you mean 'starts_with'"), "{msg}");
2935            }
2936            other => panic!("expected friendly suggestion for misspelled builtin, got {other:?}"),
2937        }
2938    }
2939
2940    #[test]
2941    fn parse_misspelled_builtin_suggests_memcmp() {
2942        let s = r#"
2943trace foo {
2944    memcpm(&buf[0], &buf[1], 16);
2945}
2946"#;
2947        let r = parse(s);
2948        match r {
2949            Err(ParseError::SyntaxError(msg)) => {
2950                assert!(msg.contains("Unknown keyword 'memcpm'"), "{msg}");
2951                assert!(msg.contains("Did you mean 'memcmp'"), "{msg}");
2952            }
2953            other => panic!("expected friendly suggestion for misspelled builtin, got {other:?}"),
2954        }
2955    }
2956
2957    #[test]
2958    fn parse_if_condition_misspelled_builtin_suggests() {
2959        let s = r#"
2960trace foo {
2961    if starst_with("a", "b") { print "ok"; }
2962}
2963"#;
2964        let r = parse(s);
2965        match r {
2966            Err(ParseError::SyntaxError(msg)) => {
2967                assert!(msg.contains("Unknown keyword 'starst_with'"), "{msg}");
2968                assert!(msg.contains("Did you mean 'starts_with'"), "{msg}");
2969            }
2970            other => panic!("expected friendly suggestion inside if(), got {other:?}"),
2971        }
2972    }
2973
2974    #[test]
2975    fn parse_else_if_condition_misspelled_builtin_suggests() {
2976        let s = r#"
2977trace foo {
2978    if 1 { print "a"; } else if starst_with("a", "b") { print "b"; }
2979}
2980"#;
2981        let r = parse(s);
2982        match r {
2983            Err(ParseError::SyntaxError(msg)) => {
2984                assert!(msg.contains("Unknown keyword 'starst_with'"), "{msg}");
2985                assert!(msg.contains("Did you mean 'starts_with'"), "{msg}");
2986            }
2987            other => panic!("expected friendly suggestion inside else if(), got {other:?}"),
2988        }
2989    }
2990    #[test]
2991    fn parse_unknown_keyword_generic_expected_list() {
2992        let s = r#"
2993foobarbaz {
2994    print "x";
2995}
2996"#;
2997        let r = parse(s);
2998        match r {
2999            Err(ParseError::SyntaxError(msg)) => {
3000                assert!(
3001                    msg.contains("Unknown keyword 'foobarbaz'"),
3002                    "unexpected msg: {msg}"
3003                );
3004                assert!(
3005                    msg.contains("Expected one of"),
3006                    "missing expected list in msg: {msg}"
3007                );
3008            }
3009            other => panic!("expected friendly SyntaxError with expected list, got {other:?}"),
3010        }
3011    }
3012}