+14
CHANGELOG.md
+14
CHANGELOG.md
···
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
8
+
## Unreleased
9
+
10
+
### Library
11
+
12
+
#### Changed
13
+
14
+
- The method `ParseError::display(source: &str, verbose: bool)` has been replaced by two methods `ParseError::display(path: &path, source: &str)` and `ParseError::display_pretty(path: &Path, source: &str)`.
15
+
16
+
### CLI
17
+
18
+
#### Changed
19
+
20
+
- Parse errors are now displayed with the same excerpt style as execution errors.
21
+
8
22
## 0.8.0 -- 2023-03-29
9
23
10
24
### Library
+2
-10
src/bin/tree-sitter-graph/main.rs
+2
-10
src/bin/tree-sitter-graph/main.rs
···
114
114
let parse_errors = ParseError::all(&tree);
115
115
if !parse_errors.is_empty() {
116
116
for parse_error in parse_errors.iter().take(MAX_PARSE_ERRORS) {
117
-
let line = parse_error.node().start_position().row;
118
-
let column = parse_error.node().start_position().column;
119
-
eprintln!(
120
-
"{}:{}:{}: {}",
121
-
source_path.display(),
122
-
line + 1,
123
-
column + 1,
124
-
parse_error.display(&source, true)
125
-
);
117
+
eprintln!("{}", parse_error.display_pretty(source_path, &source));
126
118
}
127
119
if parse_errors.len() > MAX_PARSE_ERRORS {
128
120
let more_errors = parse_errors.len() - MAX_PARSE_ERRORS;
···
141
133
let graph = match file.execute(&tree, &source, &mut config, &NoCancellation) {
142
134
Ok(graph) => graph,
143
135
Err(e) => {
144
-
eprint!("{}", e.display_pretty(source_path, &source, tsg_path, &tsg));
136
+
eprintln!("{}", e.display_pretty(source_path, &source, tsg_path, &tsg));
145
137
return Err(anyhow!("Cannot execute TSG file {}", tsg_path.display()));
146
138
}
147
139
};
+24
-102
src/execution/error.rs
+24
-102
src/execution/error.rs
···
5
5
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6
6
// ------------------------------------------------------------------------------------------------
7
7
8
-
#[cfg(feature = "term-colors")]
9
-
use colored::Colorize;
10
8
use std::path::Path;
11
9
use thiserror::Error;
12
10
13
-
use crate::ast::{Stanza, Statement};
11
+
use crate::ast::Stanza;
12
+
use crate::ast::Statement;
14
13
use crate::execution::CancellationError;
14
+
use crate::parse_error::Excerpt;
15
15
use crate::Location;
16
16
17
17
/// An error that can occur while executing a graph DSL file
···
213
213
write!(
214
214
f,
215
215
"{}",
216
-
Excerpt::from_source(self.tsg_path, self.tsg, statement_location, 7)
216
+
Excerpt::from_source(
217
+
self.tsg_path,
218
+
self.tsg,
219
+
statement_location.row,
220
+
statement_location.to_column_range(),
221
+
7
222
+
)
217
223
)?;
218
224
writeln!(f, "{}in stanza", " ".repeat(7))?;
219
225
write!(
220
226
f,
221
227
"{}",
222
-
Excerpt::from_source(self.tsg_path, self.tsg, stanza_location, 7)
228
+
Excerpt::from_source(
229
+
self.tsg_path,
230
+
self.tsg,
231
+
stanza_location.row,
232
+
stanza_location.to_column_range(),
233
+
7
234
+
)
223
235
)?;
224
236
writeln!(f, "{}matching ({}) node", " ".repeat(7), node_kind)?;
225
237
write!(
226
238
f,
227
239
"{}",
228
-
Excerpt::from_source(self.source_path, self.source, source_location, 7)
240
+
Excerpt::from_source(
241
+
self.source_path,
242
+
self.source,
243
+
source_location.row,
244
+
source_location.to_column_range(),
245
+
7
246
+
)
229
247
)?;
230
248
Ok(())
231
249
}
···
237
255
}
238
256
}
239
257
}
240
-
241
-
/// Excerpts of source from either the target language file or the tsg rules file.
242
-
struct Excerpt<'a> {
243
-
path: &'a Path,
244
-
source: Option<&'a str>,
245
-
location: &'a Location,
246
-
indent: usize,
247
-
}
248
-
249
-
impl<'a> Excerpt<'a> {
250
-
pub fn from_source(
251
-
path: &'a Path,
252
-
source: &'a str,
253
-
location: &'a Location,
254
-
indent: usize,
255
-
) -> Excerpt<'a> {
256
-
Excerpt {
257
-
path,
258
-
source: source.lines().nth(location.row),
259
-
location,
260
-
indent,
261
-
}
262
-
}
263
-
264
-
fn gutter_width(&self) -> usize {
265
-
((self.location.row + 1) as f64).log10() as usize + 1
266
-
}
267
-
}
268
-
269
-
impl<'a> std::fmt::Display for Excerpt<'a> {
270
-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
271
-
// path and line/col
272
-
write!(
273
-
f,
274
-
"{}{}:{}:{}:",
275
-
" ".repeat(self.indent),
276
-
white_bold(&self.path.to_str().unwrap_or("<unknown file>")),
277
-
white_bold(&format!("{}", self.location.row + 1)),
278
-
white_bold(&format!("{}", self.location.column + 1)),
279
-
)?;
280
-
if let Some(source) = self.source {
281
-
writeln!(f)?;
282
-
// first line: line number & source
283
-
writeln!(
284
-
f,
285
-
"{}{}{}{}",
286
-
" ".repeat(self.indent),
287
-
blue(&format!("{}", self.location.row + 1)),
288
-
blue(" | "),
289
-
source,
290
-
)?;
291
-
// second line: caret
292
-
writeln!(
293
-
f,
294
-
"{}{}{}{}{}",
295
-
" ".repeat(self.indent),
296
-
" ".repeat(self.gutter_width()),
297
-
blue(" | "),
298
-
" ".repeat(self.location.column),
299
-
green_bold("^")
300
-
)?;
301
-
} else {
302
-
writeln!(f, " <missing source>")?;
303
-
}
304
-
Ok(())
305
-
}
306
-
}
307
-
308
-
// coloring functions
309
-
310
-
#[cfg(feature = "term-colors")]
311
-
fn blue(str: &str) -> impl std::fmt::Display {
312
-
str.blue()
313
-
}
314
-
#[cfg(not(feature = "term-colors"))]
315
-
fn blue<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
316
-
str
317
-
}
318
-
319
-
#[cfg(feature = "term-colors")]
320
-
fn green_bold(str: &str) -> impl std::fmt::Display {
321
-
str.green().bold()
322
-
}
323
-
#[cfg(not(feature = "term-colors"))]
324
-
fn green_bold<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
325
-
str
326
-
}
327
-
328
-
#[cfg(feature = "term-colors")]
329
-
fn white_bold(str: &str) -> impl std::fmt::Display {
330
-
str.white().bold()
331
-
}
332
-
#[cfg(not(feature = "term-colors"))]
333
-
fn white_bold<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
334
-
str
335
-
}
+181
-36
src/parse_error.rs
+181
-36
src/parse_error.rs
···
7
7
8
8
//! Data types and functions for finding and displaying tree-sitter parse errors.
9
9
10
+
#[cfg(feature = "term-colors")]
11
+
use colored::Colorize;
12
+
use std::ops::Range;
13
+
use std::path::Path;
10
14
use tree_sitter::Node;
11
15
use tree_sitter::Tree;
12
16
···
57
61
}
58
62
}
59
63
60
-
pub fn display(&self, source: &'tree str, verbose: bool) -> ParseErrorDisplay {
64
+
pub fn display(
65
+
&'tree self,
66
+
path: &'tree Path,
67
+
source: &'tree str,
68
+
) -> impl std::fmt::Display + 'tree {
61
69
ParseErrorDisplay {
62
70
error: self,
71
+
path,
63
72
source,
64
-
verbose,
73
+
}
74
+
}
75
+
76
+
pub fn display_pretty(
77
+
&'tree self,
78
+
path: &'tree Path,
79
+
source: &'tree str,
80
+
) -> impl std::fmt::Display + 'tree {
81
+
ParseErrorDisplayPretty {
82
+
error: self,
83
+
path,
84
+
source,
65
85
}
66
86
}
67
87
}
68
88
69
-
pub struct ParseErrorDisplay<'tree> {
89
+
struct ParseErrorDisplay<'tree> {
70
90
error: &'tree ParseError<'tree>,
91
+
path: &'tree Path,
71
92
source: &'tree str,
72
-
verbose: bool,
73
93
}
74
94
75
95
impl std::fmt::Display for ParseErrorDisplay<'_> {
76
96
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
97
+
let node = self.error.node();
98
+
write!(
99
+
f,
100
+
"{}:{}:{}: ",
101
+
self.path.display(),
102
+
node.start_position().row + 1,
103
+
node.start_position().column + 1
104
+
)?;
77
105
let node = match self.error {
78
106
ParseError::Missing(node) => {
79
-
write!(f, "Missing syntax")?;
107
+
write!(f, "missing syntax")?;
80
108
node
81
109
}
82
110
ParseError::Unexpected(node) => {
83
-
write!(f, "Unexpected syntax")?;
111
+
write!(f, "unexpected syntax")?;
84
112
node
85
113
}
86
114
};
87
-
let line = node.start_position().row;
88
-
let start_column = node.start_position().column;
89
115
if node.byte_range().is_empty() {
90
116
writeln!(f, "")?;
91
117
} else {
92
-
let (end_column, end_byte) = self.source[node.byte_range()]
118
+
let end_byte = self.source[node.byte_range()]
93
119
.chars()
94
120
.take_while(|c| *c != '\n')
95
-
.fold(
96
-
(node.start_position().column, node.start_byte()),
97
-
|(column, byte), c| (column + 1, byte + c.len_utf8()),
98
-
);
99
-
if !self.verbose {
100
-
let text = &self.source[node.start_byte()..end_byte];
101
-
write!(f, ": {}", text)?;
102
-
} else {
103
-
let text = self
104
-
.source
105
-
.lines()
106
-
.nth(line)
107
-
.expect("parse error has invalid row");
108
-
writeln!(f, ":")?;
109
-
writeln!(f, "")?;
110
-
writeln!(f, "| {}", text)?;
111
-
write!(
112
-
f,
113
-
" {}{}",
114
-
" ".repeat(start_column),
115
-
"^".repeat(end_column - start_column)
116
-
)?;
117
-
if node.end_position().row == line {
118
-
writeln!(f, "")?;
119
-
} else {
120
-
writeln!(f, "...")?;
121
-
}
121
+
.map(|c| c.len_utf8())
122
+
.sum();
123
+
let text = &self.source[node.start_byte()..end_byte];
124
+
write!(f, ": {}", text)?;
125
+
}
126
+
Ok(())
127
+
}
128
+
}
129
+
130
+
struct ParseErrorDisplayPretty<'tree> {
131
+
error: &'tree ParseError<'tree>,
132
+
path: &'tree Path,
133
+
source: &'tree str,
134
+
}
135
+
136
+
impl std::fmt::Display for ParseErrorDisplayPretty<'_> {
137
+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
138
+
let node = match self.error {
139
+
ParseError::Missing(node) => {
140
+
writeln!(f, "missing syntax")?;
141
+
node
122
142
}
143
+
ParseError::Unexpected(node) => {
144
+
writeln!(f, "unexpected syntax")?;
145
+
node
146
+
}
147
+
};
148
+
if node.byte_range().is_empty() {
149
+
writeln!(f, "")?;
150
+
} else {
151
+
let start_column = node.start_position().column;
152
+
let end_column = node.start_position().column
153
+
+ self.source[node.byte_range()]
154
+
.chars()
155
+
.take_while(|c| *c != '\n')
156
+
.count();
157
+
write!(
158
+
f,
159
+
"{}",
160
+
Excerpt::from_source(
161
+
self.path,
162
+
self.source,
163
+
node.start_position().row,
164
+
start_column..end_column,
165
+
0,
166
+
),
167
+
)?;
123
168
}
124
169
Ok(())
125
170
}
···
308
353
// This is okay because Send and Sync _are_ implemented for Tree, which also holds ffi::TSTree
309
354
unsafe impl Send for TreeWithParseErrorVec {}
310
355
unsafe impl Sync for TreeWithParseErrorVec {}
356
+
357
+
//-----------------------------------------------------------------------------
358
+
359
+
/// Excerpts of source from either the target language file or the tsg rules file.
360
+
pub(crate) struct Excerpt<'a> {
361
+
path: &'a Path,
362
+
source: Option<&'a str>,
363
+
row: usize,
364
+
columns: Range<usize>,
365
+
indent: usize,
366
+
}
367
+
368
+
impl<'a> Excerpt<'a> {
369
+
pub fn from_source(
370
+
path: &'a Path,
371
+
source: &'a str,
372
+
row: usize,
373
+
columns: Range<usize>,
374
+
indent: usize,
375
+
) -> Excerpt<'a> {
376
+
Excerpt {
377
+
path,
378
+
source: source.lines().nth(row),
379
+
row,
380
+
columns,
381
+
indent,
382
+
}
383
+
}
384
+
385
+
fn gutter_width(&self) -> usize {
386
+
((self.row + 1) as f64).log10() as usize + 1
387
+
}
388
+
}
389
+
390
+
impl<'a> std::fmt::Display for Excerpt<'a> {
391
+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
392
+
// path and line/col
393
+
writeln!(
394
+
f,
395
+
"{}{}:{}:{}:",
396
+
" ".repeat(self.indent),
397
+
white_bold(&self.path.to_str().unwrap_or("<unknown file>")),
398
+
white_bold(&format!("{}", self.row + 1)),
399
+
white_bold(&format!("{}", self.columns.start + 1)),
400
+
)?;
401
+
if let Some(source) = self.source {
402
+
// first line: line number & source
403
+
writeln!(
404
+
f,
405
+
"{}{}{}{}",
406
+
" ".repeat(self.indent),
407
+
blue(&format!("{}", self.row + 1)),
408
+
blue(" | "),
409
+
source,
410
+
)?;
411
+
// second line: caret
412
+
writeln!(
413
+
f,
414
+
"{}{}{}{}{}",
415
+
" ".repeat(self.indent),
416
+
" ".repeat(self.gutter_width()),
417
+
blue(" | "),
418
+
" ".repeat(self.columns.start),
419
+
green_bold(&"^".repeat(self.columns.len()))
420
+
)?;
421
+
} else {
422
+
writeln!(f, "{}{}", " ".repeat(self.indent), "<missing source>",)?;
423
+
}
424
+
Ok(())
425
+
}
426
+
}
427
+
428
+
// coloring functions
429
+
430
+
#[cfg(feature = "term-colors")]
431
+
fn blue(str: &str) -> impl std::fmt::Display {
432
+
str.blue()
433
+
}
434
+
#[cfg(not(feature = "term-colors"))]
435
+
fn blue<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
436
+
str
437
+
}
438
+
439
+
#[cfg(feature = "term-colors")]
440
+
fn green_bold(str: &str) -> impl std::fmt::Display {
441
+
str.green().bold()
442
+
}
443
+
#[cfg(not(feature = "term-colors"))]
444
+
fn green_bold<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
445
+
str
446
+
}
447
+
448
+
#[cfg(feature = "term-colors")]
449
+
fn white_bold(str: &str) -> impl std::fmt::Display {
450
+
str.white().bold()
451
+
}
452
+
#[cfg(not(feature = "term-colors"))]
453
+
fn white_bold<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
454
+
str
455
+
}
+5
src/parser.rs
+5
src/parser.rs
···
7
7
8
8
use std::fmt::Display;
9
9
use std::iter::Peekable;
10
+
use std::ops::Range;
10
11
use std::str::Chars;
11
12
12
13
use regex::Regex;
···
91
92
} else {
92
93
self.column += 1;
93
94
}
95
+
}
96
+
97
+
pub(crate) fn to_column_range(&self) -> Range<usize> {
98
+
self.column..self.column + 1
94
99
}
95
100
}
96
101