+8
CHANGELOG.md
+8
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
+
### CLI
11
+
12
+
#### Added
13
+
14
+
- Errors from parsing and checking the TSG file are now displayed with source excerpts.
15
+
8
16
## v0.9.0 -- 2023-03-31
9
17
10
18
### Library
+7
-2
src/bin/tree-sitter-graph/main.rs
+7
-2
src/bin/tree-sitter-graph/main.rs
···
98
98
let tsg = std::fs::read(tsg_path)
99
99
.with_context(|| format!("Cannot read TSG file {}", tsg_path.display()))?;
100
100
let tsg = String::from_utf8(tsg)?;
101
-
let file = File::from_str(language, &tsg)
102
-
.with_context(|| format!("Cannot parsing TSG file {}", tsg_path.display()))?;
101
+
let file = match File::from_str(language, &tsg) {
102
+
Ok(file) => file,
103
+
Err(err) => {
104
+
eprintln!("{}", err.display_pretty(tsg_path, &tsg));
105
+
return Err(anyhow!("Cannot parse TSG file {}", tsg_path.display()));
106
+
}
107
+
};
103
108
104
109
let source = std::fs::read(source_path)
105
110
.with_context(|| format!("Cannot read source file {}", source_path.display()))?;
+57
-4
src/checker.rs
+57
-4
src/checker.rs
···
5
5
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6
6
// ------------------------------------------------------------------------------------------------
7
7
8
+
use std::path::Path;
9
+
8
10
use thiserror::Error;
9
11
use tree_sitter::CaptureQuantifier;
10
12
use tree_sitter::CaptureQuantifier::One;
···
14
16
use tree_sitter::Query;
15
17
16
18
use crate::ast;
19
+
use crate::parse_error::Excerpt;
17
20
use crate::parser::FULL_MATCH;
18
21
use crate::variables::MutVariables;
19
22
use crate::variables::VariableError;
···
41
44
UndefinedSyntaxCapture(String, Location),
42
45
#[error("Undefined variable {0} at {1}")]
43
46
UndefinedVariable(String, Location),
44
-
#[error("{0}: {1}")]
45
-
Variable(VariableError, String),
47
+
#[error("{0}: {1} at {2}")]
48
+
Variable(VariableError, String, Location),
49
+
}
50
+
51
+
impl CheckError {
52
+
pub fn display_pretty<'a>(
53
+
&'a self,
54
+
path: &'a Path,
55
+
source: &'a str,
56
+
) -> impl std::fmt::Display + 'a {
57
+
DisplayCheckErrorPretty {
58
+
error: self,
59
+
path,
60
+
source,
61
+
}
62
+
}
63
+
}
64
+
65
+
struct DisplayCheckErrorPretty<'a> {
66
+
error: &'a CheckError,
67
+
path: &'a Path,
68
+
source: &'a str,
69
+
}
70
+
71
+
impl std::fmt::Display for DisplayCheckErrorPretty<'_> {
72
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73
+
let location = match self.error {
74
+
CheckError::CannotHideGlobalVariable(_, location) => *location,
75
+
CheckError::CannotSetGlobalVariable(_, location) => *location,
76
+
CheckError::DuplicateGlobalVariable(_, location) => *location,
77
+
CheckError::ExpectedListValue(location) => *location,
78
+
CheckError::ExpectedLocalValue(location) => *location,
79
+
CheckError::ExpectedOptionalValue(location) => *location,
80
+
CheckError::NullableRegex(_, location) => *location,
81
+
CheckError::UndefinedSyntaxCapture(_, location) => *location,
82
+
CheckError::UndefinedVariable(_, location) => *location,
83
+
CheckError::Variable(_, _, location) => *location,
84
+
};
85
+
writeln!(f, "{}", self.error)?;
86
+
write!(
87
+
f,
88
+
"{}",
89
+
Excerpt::from_source(
90
+
self.path,
91
+
self.source,
92
+
location.row,
93
+
location.to_column_range(),
94
+
0
95
+
)
96
+
)?;
97
+
Ok(())
98
+
}
46
99
}
47
100
48
101
/// Checker context
···
567
620
}
568
621
ctx.locals
569
622
.add(self.name.clone(), value, mutable)
570
-
.map_err(|e| CheckError::Variable(e, format!("{}", self.name)))
623
+
.map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))
571
624
}
572
625
573
626
fn check_set(
···
589
642
value.is_local = false;
590
643
ctx.locals
591
644
.set(self.name.clone(), value)
592
-
.map_err(|e| CheckError::Variable(e, format!("{}", self.name)))
645
+
.map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))
593
646
}
594
647
595
648
fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
+71
src/parser.rs
+71
src/parser.rs
···
8
8
use std::fmt::Display;
9
9
use std::iter::Peekable;
10
10
use std::ops::Range;
11
+
use std::path::Path;
11
12
use std::str::Chars;
12
13
13
14
use regex::Regex;
···
23
24
use tree_sitter::QueryError;
24
25
25
26
use crate::ast;
27
+
use crate::parse_error::Excerpt;
26
28
use crate::Identifier;
27
29
28
30
pub const FULL_MATCH: &str = "__tsg__full_match";
···
45
47
Parser::new(content).parse_into_file(self)
46
48
}
47
49
}
50
+
51
+
// ----------------------------------------------------------------------------
52
+
// Parse errors
48
53
49
54
/// An error that can occur while parsing a graph DSL file
50
55
#[derive(Debug, Error)]
···
77
82
Check(#[from] crate::checker::CheckError),
78
83
}
79
84
85
+
impl ParseError {
86
+
pub fn display_pretty<'a>(
87
+
&'a self,
88
+
path: &'a Path,
89
+
source: &'a str,
90
+
) -> impl std::fmt::Display + 'a {
91
+
DisplayParseErrorPretty {
92
+
error: self,
93
+
path,
94
+
source,
95
+
}
96
+
}
97
+
}
98
+
99
+
struct DisplayParseErrorPretty<'a> {
100
+
error: &'a ParseError,
101
+
path: &'a Path,
102
+
source: &'a str,
103
+
}
104
+
105
+
impl std::fmt::Display for DisplayParseErrorPretty<'_> {
106
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107
+
let location = match self.error {
108
+
ParseError::ExpectedQuantifier(location) => *location,
109
+
ParseError::ExpectedToken(_, location) => *location,
110
+
ParseError::ExpectedVariable(location) => *location,
111
+
ParseError::ExpectedUnscopedVariable(location) => *location,
112
+
ParseError::InvalidRegex(_, location) => *location,
113
+
ParseError::InvalidRegexCapture(location) => *location,
114
+
ParseError::QueryError(err) => Location {
115
+
row: err.row,
116
+
column: err.column,
117
+
},
118
+
ParseError::UnexpectedCharacter(_, _, location) => *location,
119
+
ParseError::UnexpectedEOF(location) => *location,
120
+
ParseError::UnexpectedKeyword(_, location) => *location,
121
+
ParseError::UnexpectedLiteral(_, location) => *location,
122
+
ParseError::UnexpectedQueryPatterns(location) => *location,
123
+
ParseError::Check(err) => {
124
+
write!(f, "{}", err.display_pretty(self.path, self.source))?;
125
+
return Ok(());
126
+
}
127
+
};
128
+
writeln!(f, "{}", self.source)?;
129
+
writeln!(f, "{}", self.error)?;
130
+
write!(
131
+
f,
132
+
"{}",
133
+
Excerpt::from_source(
134
+
self.path,
135
+
self.source,
136
+
location.row,
137
+
location.to_column_range(),
138
+
0
139
+
)
140
+
)?;
141
+
Ok(())
142
+
}
143
+
}
144
+
145
+
// ----------------------------------------------------------------------------
146
+
// Location
147
+
80
148
/// The location of a graph DSL entity within its file
81
149
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
82
150
pub struct Location {
···
104
172
write!(f, "({}, {})", self.row + 1, self.column + 1)
105
173
}
106
174
}
175
+
176
+
// ----------------------------------------------------------------------------
177
+
// Parser
107
178
108
179
struct Parser<'a> {
109
180
source: &'a str,