Skip to content

Commit 7f45ed3

Browse files
committed
Provide caret diagnostics for SyntaxError
visualize syntax error with caret diagnostics in shell, eval, exec, when the error statement and error location are provided.
1 parent 8267ea4 commit 7f45ed3

5 files changed

Lines changed: 57 additions & 14 deletions

File tree

compiler/src/compile.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ impl<O: OutputStream> Compiler<O> {
255255
self.compile_expression(expression)?;
256256
} else {
257257
return Err(CompileError {
258+
statement: None,
258259
error: CompileErrorType::ExpectExpr,
259260
location: statement.location.clone(),
260261
});
@@ -533,6 +534,7 @@ impl<O: OutputStream> Compiler<O> {
533534
Break => {
534535
if !self.ctx.in_loop {
535536
return Err(CompileError {
537+
statement: None,
536538
error: CompileErrorType::InvalidBreak,
537539
location: statement.location.clone(),
538540
});
@@ -542,6 +544,7 @@ impl<O: OutputStream> Compiler<O> {
542544
Continue => {
543545
if !self.ctx.in_loop {
544546
return Err(CompileError {
547+
statement: None,
545548
error: CompileErrorType::InvalidContinue,
546549
location: statement.location.clone(),
547550
});
@@ -551,6 +554,7 @@ impl<O: OutputStream> Compiler<O> {
551554
Return { value } => {
552555
if !self.ctx.in_func() {
553556
return Err(CompileError {
557+
statement: None,
554558
error: CompileErrorType::InvalidReturn,
555559
location: statement.location.clone(),
556560
});
@@ -628,6 +632,7 @@ impl<O: OutputStream> Compiler<O> {
628632
}
629633
_ => {
630634
return Err(CompileError {
635+
statement: None,
631636
error: CompileErrorType::Delete(expression.name()),
632637
location: self.current_source_location.clone(),
633638
});
@@ -1331,6 +1336,7 @@ impl<O: OutputStream> Compiler<O> {
13311336
if let ast::ExpressionType::Starred { .. } = &element.node {
13321337
if seen_star {
13331338
return Err(CompileError {
1339+
statement: None,
13341340
error: CompileErrorType::StarArgs,
13351341
location: self.current_source_location.clone(),
13361342
});
@@ -1360,6 +1366,7 @@ impl<O: OutputStream> Compiler<O> {
13601366
}
13611367
_ => {
13621368
return Err(CompileError {
1369+
statement: None,
13631370
error: CompileErrorType::Assign(target.name()),
13641371
location: self.current_source_location.clone(),
13651372
});
@@ -1644,6 +1651,7 @@ impl<O: OutputStream> Compiler<O> {
16441651
Yield { value } => {
16451652
if !self.ctx.in_func() {
16461653
return Err(CompileError {
1654+
statement: Option::None,
16471655
error: CompileErrorType::InvalidYield,
16481656
location: self.current_source_location.clone(),
16491657
});
@@ -1738,6 +1746,7 @@ impl<O: OutputStream> Compiler<O> {
17381746
}
17391747
Starred { .. } => {
17401748
return Err(CompileError {
1749+
statement: Option::None,
17411750
error: CompileErrorType::SyntaxError(std::string::String::from(
17421751
"Invalid starred expression",
17431752
)),

compiler/src/error.rs

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@ use std::fmt;
77

88
#[derive(Debug)]
99
pub struct CompileError {
10+
pub statement: Option<String>,
1011
pub error: CompileErrorType,
1112
pub location: Location,
1213
}
1314

15+
impl CompileError {
16+
pub fn update_statement_info(&mut self, statement: String) {
17+
self.statement = Some(statement);
18+
}
19+
}
20+
1421
impl From<ParseError> for CompileError {
1522
fn from(error: ParseError) -> Self {
1623
CompileError {
24+
statement: None,
1725
error: CompileErrorType::Parse(error.error),
1826
location: error.location,
1927
}
@@ -70,21 +78,31 @@ impl CompileError {
7078

7179
impl fmt::Display for CompileError {
7280
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73-
match &self.error {
74-
CompileErrorType::Assign(target) => write!(f, "can't assign to {}", target),
75-
CompileErrorType::Delete(target) => write!(f, "can't delete {}", target),
76-
CompileErrorType::ExpectExpr => write!(f, "Expecting expression, got statement"),
77-
CompileErrorType::Parse(err) => write!(f, "{}", err),
78-
CompileErrorType::SyntaxError(err) => write!(f, "{}", err),
79-
CompileErrorType::StarArgs => write!(f, "Two starred expressions in assignment"),
80-
CompileErrorType::InvalidBreak => write!(f, "'break' outside loop"),
81-
CompileErrorType::InvalidContinue => write!(f, "'continue' outside loop"),
82-
CompileErrorType::InvalidReturn => write!(f, "'return' outside function"),
83-
CompileErrorType::InvalidYield => write!(f, "'yield' outside function"),
84-
}?;
81+
let error_desc = match &self.error {
82+
CompileErrorType::Assign(target) => format!("can't assign to {}", target),
83+
CompileErrorType::Delete(target) => format!("can't delete {}", target),
84+
CompileErrorType::ExpectExpr => "Expecting expression, got statement".to_string(),
85+
CompileErrorType::Parse(err) => err.to_string(),
86+
CompileErrorType::SyntaxError(err) => err.to_string(),
87+
CompileErrorType::StarArgs => "Two starred expressions in assignment".to_string(),
88+
CompileErrorType::InvalidBreak => "'break' outside loop".to_string(),
89+
CompileErrorType::InvalidContinue => "'continue' outside loop".to_string(),
90+
CompileErrorType::InvalidReturn => "'return' outside function".to_string(),
91+
CompileErrorType::InvalidYield => "'yield' outside function".to_string(),
92+
};
8593

86-
// Print line number:
87-
write!(f, " at {}", self.location)
94+
if self.statement.is_some() && self.location.column() > 0 {
95+
// visualize the error, when location and statement are provided
96+
write!(
97+
f,
98+
"\n{}\n{}",
99+
self.statement.clone().unwrap(),
100+
self.location.visualize(&error_desc)
101+
)
102+
} else {
103+
// print line number
104+
write!(f, "{} at {}", error_desc, self.location)
105+
}
88106
}
89107
}
90108

compiler/src/symboltable.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub struct SymbolTableError {
142142
impl From<SymbolTableError> for CompileError {
143143
fn from(error: SymbolTableError) -> Self {
144144
CompileError {
145+
statement: None,
145146
error: CompileErrorType::SyntaxError(error.error),
146147
location: error.location,
147148
}

parser/src/location.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ impl fmt::Display for Location {
1515
}
1616
}
1717

18+
impl Location {
19+
pub fn visualize(&self, desc: &str) -> String {
20+
format!(
21+
"{}↑\n{}{}",
22+
" ".repeat(self.column - 1),
23+
" ".repeat(self.column - 1),
24+
desc
25+
)
26+
}
27+
}
28+
1829
impl Location {
1930
pub fn new(row: usize, column: usize) -> Self {
2031
Location { row, column }

vm/src/vm.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,10 @@ impl VirtualMachine {
10071007
) -> Result<PyCodeRef, CompileError> {
10081008
compile::compile(source, mode, source_path, self.settings.optimize)
10091009
.map(|codeobj| PyCode::new(codeobj).into_ref(self))
1010+
.map_err(|mut compile_error| {
1011+
compile_error.update_statement_info(source.trim_end().to_string());
1012+
compile_error
1013+
})
10101014
}
10111015

10121016
pub fn _sub(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult {

0 commit comments

Comments
 (0)