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
30 changes: 28 additions & 2 deletions implants/lib/eldritchv2/eldritch-core/src/interpreter/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions implants/lib/eldritchv2/eldritch-core/tests/error_alignment.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}