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
46fn 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 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 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 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
103fn 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 let mut open = false;
113 for ch in line.chars() {
114 if ch == '"' {
115 open = !open;
116 }
117 }
118 if open {
119 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
171fn detect_unknown_keyword(input: &str) -> Option<String> {
173 const SUGGEST: &[&str] = &["trace", "print", "if", "else", "let"];
175 const SUPPORTED_HEADS: &[&str] = &["trace", "print", "if", "else", "let", "backtrace", "bt"];
177 const BUILTIN_CALLS: &[&str] = &["memcmp", "strncmp", "starts_with", "hex", "cast"];
179
180 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 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 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 }
226 }
227 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 return None;
253 }
254 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 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 let mut quote_open = false;
297 let mut positions: Vec<usize> = vec![0]; 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 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 let mut it = inner.into_inner();
394 let name = it
395 .next()
396 .ok_or(ParseError::InvalidExpression)?
397 .as_str()
398 .to_string();
399 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 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 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 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
497fn 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 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 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 let inner_expr_pair = pair
890 .into_inner()
891 .next()
892 .ok_or(ParseError::InvalidExpression)?;
893 let expr = parse_expr(inner_expr_pair)?;
894 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 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 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 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 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 let expr = Expr::BinaryOp {
993 left: Box::new(left),
994 op,
995 right: Box::new(right),
996 };
997
998 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 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 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 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 Rule::float => Err(ParseError::TypeError(
1117 "float literals are not supported".to_string(),
1118 )),
1119 Rule::string => {
1120 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 let rule = pair.as_rule();
1150 let mut it = pair.into_inner();
1151 match rule {
1153 Rule::memcmp_call => {
1154 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 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 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 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 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 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 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 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 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 let arg0 = parse_expr(it.next().ok_or(ParseError::InvalidExpression)?)?;
1357 let arg1 = parse_expr(it.next().ok_or(ParseError::InvalidExpression)?)?;
1358 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 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 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 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 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 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 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 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 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
1645fn 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
1667fn 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 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 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 let Some(idx) = opt_index {
1701 expr = Expr::ArrayAccess(Box::new(expr), Box::new(idx));
1702 }
1703
1704 Ok(expr)
1705}
1706
1707fn 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 let mut expr = Expr::ArrayAccess(
1721 Box::new(Expr::Variable(array_name.as_str().to_string())),
1722 Box::new(parsed_index),
1723 );
1724
1725 for next in inner_pairs {
1727 let m = next.as_str().to_string();
1729 expr = Expr::MemberAccess(Box::new(expr), m);
1730 }
1731
1732 Ok(expr)
1733}
1734
1735fn 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 let mut tail: Vec<String> = Vec::new();
1746 for p in parts {
1747 tail.push(p.as_str().to_string());
1748 }
1749
1750 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
1767fn 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 match parsed {
1782 Expr::AddressOf(inner_expr) => Ok(*inner_expr),
1783 other => Ok(Expr::PointerDeref(Box::new(other))),
1784 }
1785}
1786
1787fn 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let s1 = r#"trace main { print "OK"; }"#;
2408 assert!(parse(s1).is_ok());
2409
2410 let s2 = r#"trace /tmp/test-file.c:42 { print "L"; }"#;
2412 assert!(parse(s2).is_ok());
2413
2414 let s3 = r#"trace 0x401234 { print "A"; }"#;
2416 assert!(parse(s3).is_ok());
2417
2418 let s4 = r#"trace printf* { print "W"; }"#;
2420 assert!(parse(s4).is_ok());
2421
2422 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 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 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 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 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 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 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 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 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 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 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 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 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 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}