diff --git a/implants/lib/eldritchv2/eldritch-core/src/interpreter/core.rs b/implants/lib/eldritchv2/eldritch-core/src/interpreter/core.rs index 23d8237bd..46cfca9d7 100644 --- a/implants/lib/eldritchv2/eldritch-core/src/interpreter/core.rs +++ b/implants/lib/eldritchv2/eldritch-core/src/interpreter/core.rs @@ -224,10 +224,36 @@ impl Interpreter { if error.span.line > 0 && error.span.line <= lines.len() { let line_idx = error.span.line - 1; let line_content = lines[line_idx]; + + // Calculate column offset relative to trimmed line + let leading_whitespace = line_content.len() - line_content.trim_start().len(); + + // Calculate line start byte offset + let mut line_start = error.span.start; + let source_bytes = source.as_bytes(); + // Walk backwards from error start to find newline + // If error.span.start is beyond source len (shouldn't happen for valid error), clamp it. + if line_start > source_bytes.len() { + line_start = source_bytes.len(); + } + while line_start > 0 && source_bytes[line_start - 1] != b'\n' { + line_start -= 1; + } + + // Calculate raw column (byte offset from start of line) + let raw_col = error.span.start.saturating_sub(line_start); + + // Calculate display column relative to trimmed string + let display_col = raw_col.saturating_sub(leading_whitespace); + + // Create dynamic padding + let padding = format!("{:>width$}", "", width = display_col); + output.push_str(&format!( - "\n\nError location:\n at line {}:\n {}\n ^-- here", + "\n\nError location:\n at line {}:\n {}\n {}^-- here", error.span.line, - line_content.trim() + line_content.trim(), + padding )); } output diff --git a/implants/lib/eldritchv2/eldritch-core/tests/error_alignment.rs b/implants/lib/eldritchv2/eldritch-core/tests/error_alignment.rs new file mode 100644 index 000000000..4baf05dc8 --- /dev/null +++ b/implants/lib/eldritchv2/eldritch-core/tests/error_alignment.rs @@ -0,0 +1,50 @@ +use eldritch_core::Interpreter; + +#[test] +fn test_error_caret_alignment() { + let mut interp = Interpreter::new(); + + // Test case 1: Error not at start, with indentation + // " x = z + 1" + // ^ ^ + // 0 4 (relative to trimmed) + // In raw string: 4 spaces + 'x' + ' ' + '=' + ' ' + 'z' + // 'z' is at index 8. + // Trimmed line: "x = z + 1" (starts at index 4) + // Error at 8. Relative to trimmed start: 8 - 4 = 4. + // Display indent: 4 (base) + 4 (relative) = 8 spaces. + + let code = " x = z + 1"; + let res = interp.interpret(code); + match res { + Ok(_) => panic!("Expected error for 'x = z + 1'"), + Err(msg) => { + let lines: Vec<&str> = msg.lines().collect(); + // Expected output: + // ... + // Error location: + // at line 1: + // x = z + 1 + // ^-- here + + let last_line = lines.last().expect("Error message empty"); + // " ^-- here" + // 8 spaces + ^ + assert!(last_line.starts_with(" ^-- here"), "Incorrect alignment: '{}'", last_line); + } + } + + // Test case 2: Error at start of trimmed line + // Just accessing undefined 'z' + let code2 = " z"; + let res2 = interp.interpret(code2); + match res2 { + Ok(_) => panic!("Expected error for 'z'"), + Err(msg) => { + let lines: Vec<&str> = msg.lines().collect(); + let last_line = lines.last().expect("Error message empty"); + // " ^-- here" (4 spaces indent) + assert!(last_line.starts_with(" ^-- here"), "Incorrect alignment for start of line: '{}'", last_line); + } + } +}