Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion ghostscope-compiler/src/script/parser/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use super::{GhostScopeParser, Rule};
use pest::Parser;

// Best-effort heuristic: if a line contains a print statement with an opening quote
// but no closing quote before arguments, give a clearer error.
pub(super) fn detect_unclosed_print_string(input: &str) -> Option<String> {
Expand Down Expand Up @@ -100,6 +103,55 @@ pub(super) fn detect_unknown_keyword(input: &str) -> Option<String> {
dp[idx(n, m)]
}

fn double_slash_is_in_trace_path(
line: &str,
statement_start: usize,
slash_start: usize,
) -> bool {
let statement = &line[statement_start..];
let statement = statement.trim_start();
let statement_start = line.len() - statement.len();
let Some(after_trace) = statement.strip_prefix("trace") else {
return false;
};
if !after_trace.starts_with(char::is_whitespace) {
return false;
}

let pattern = after_trace.trim_start();
let pattern_start = statement_start + statement.len() - pattern.len();
let Some(block_start) = pattern.find('{') else {
return false;
};
let pattern = pattern[..block_start].trim_end();

let Ok(mut pairs) = GhostScopeParser::parse(Rule::trace_pattern, pattern) else {
return false;
};
let Some(trace_pattern) = pairs.next() else {
return false;
};
if trace_pattern.as_span().end() != pattern.len() {
return false;
}
let Some(path_pattern) = trace_pattern.into_inner().next() else {
return false;
};
if !matches!(
path_pattern.as_rule(),
Rule::source_line | Rule::module_hex_address
) {
return false;
}
let Some(file_path) = path_pattern.into_inner().next() else {
return false;
};
let path_start = pattern_start + file_path.as_span().start();
let path_end = pattern_start + file_path.as_span().end();

path_start < slash_start && slash_start + 1 < path_end
}

// Helper: check a slice for a command-like unknown keyword
fn check_slice(slice: &str, line_no_1based: usize) -> Option<String> {
let mut s = slice.trim_start();
Expand Down Expand Up @@ -190,10 +242,16 @@ pub(super) fn detect_unknown_keyword(input: &str) -> Option<String> {

for (i, raw_line) in input.lines().enumerate() {
let line = raw_line;
// Scan potential statement starts: at line start, and right after '{', ';', '}', '(', ',' (outside strings)
// Scan potential statement starts: at line start, and right after '{', ';', '}', '(', ',' (outside strings and comments)
let mut quote_open = false;
let mut positions: Vec<usize> = vec![0]; // include start-of-line
for (idx, ch) in line.char_indices() {
if !quote_open
&& line[idx..].starts_with("//")
&& !double_slash_is_in_trace_path(line, positions.last().copied().unwrap_or(0), idx)
{
break;
}
if ch == '"' {
quote_open = !quote_open;
}
Expand Down
29 changes: 29 additions & 0 deletions ghostscope-compiler/src/script/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1244,3 +1244,32 @@ foobarbaz {
other => panic!("expected friendly SyntaxError with expected list, got {other:?}"),
}
}

#[test]
fn parse_error_ignores_commas_in_line_comments() {
let s = "trace foo {\n // explain the output, including ignored words\n print somevalue -> other;\n}";
let r = parse(s);
match r {
Err(err @ ParseError::Pest(_)) => {
let msg = err.to_string();
assert!(
msg.contains("Pest parser error: --> 3:22"),
"unexpected error location: {msg}"
);
}
other => panic!("expected Pest error at '->', got {other:?}"),
}
}

#[test]
fn parse_unknown_keyword_after_trace_path_with_double_slash() {
let s = r#"trace /foo//bar.c:1 { prnit "x"; }"#;
let r = parse(s);
match r {
Err(ParseError::SyntaxError(msg)) => {
assert!(msg.contains("Unknown keyword 'prnit'"), "{msg}");
assert!(msg.contains("Did you mean 'print'"), "{msg}");
}
other => panic!("expected friendly SyntaxError after trace path, got {other:?}"),
}
}
Loading