1use super::Error;
2use crate::diag::{Diagnostic, DiagnosticKind, Formatted, Formatter};
3use crate::grammar::Rule;
4use crate::{LineCol, Parsed, Position, Span};
5use std::borrow::Cow;
6use std::collections::BTreeSet;
7
8#[derive(Debug)]
9pub struct InvalidSyntax {
10 schema_name: String,
11 pos: Position,
12 expected: BTreeSet<Expected>,
13}
14
15impl InvalidSyntax {
16 pub(crate) fn new<S>(schema_name: S, err: pest::error::Error<Rule>) -> Self
17 where
18 S: Into<String>,
19 {
20 use pest::error::ErrorVariant;
21
22 let pos = Position::from_pest_error(&err);
23
24 let positives = match err.variant {
25 ErrorVariant::ParsingError { positives, .. } => positives,
26 ErrorVariant::CustomError { .. } => unreachable!(),
27 };
28
29 let mut expected = BTreeSet::new();
30 for rule in positives {
31 Expected::add(rule, &mut expected);
32 }
33
34 Self {
35 schema_name: schema_name.into(),
36 pos,
37 expected,
38 }
39 }
40
41 pub fn position(&self) -> Position {
42 self.pos
43 }
44
45 pub fn expected(&self) -> &BTreeSet<Expected> {
46 &self.expected
47 }
48}
49
50impl Diagnostic for InvalidSyntax {
51 fn kind(&self) -> DiagnosticKind {
52 DiagnosticKind::Error
53 }
54
55 fn schema_name(&self) -> &str {
56 &self.schema_name
57 }
58
59 fn format<'a>(&'a self, parsed: &'a Parsed) -> Formatted<'a> {
60 let mut reason = "expected ".to_owned();
61
62 let mut iter = self.expected.iter().peekable();
63 let mut first = true;
64 let mut eof = false;
65 while let Some(expected) = iter.next() {
66 let expected: Cow<'static, str> = match expected {
67 Expected::Eof => {
68 eof = true;
69 continue;
70 }
71 Expected::Ident => "an identifier".into(),
72 Expected::Keyword(kw) => format!("`{kw}`").into(),
73 Expected::LitInt => "an integer literal".into(),
74 Expected::LitPosInt => "a positive integer literal".into(),
75 Expected::LitString => "a string literal".into(),
76 Expected::LitUuid => "a uuid literal".into(),
77 Expected::SchemaName => "a schema name".into(),
78 Expected::Token(tok) => format!("`{tok}`").into(),
79 };
80
81 if first {
82 first = false;
83 } else if iter.peek().is_some() || eof {
84 reason.push_str(", ");
85 } else {
86 reason.push_str(" or ");
87 }
88
89 reason.push_str(&expected);
90 }
91
92 if eof {
93 if first {
94 reason.push_str("end of file");
95 } else {
96 reason.push_str(" or end of file");
97 }
98 }
99
100 let mut fmt = Formatter::new(self, reason);
101
102 if let Some(schema) = parsed.get_schema(&self.schema_name) {
103 let span = Span {
104 from: self.pos,
105 to: Position {
106 index: self.pos.index + 1,
107 line_col: LineCol {
108 line: self.pos.line_col.line,
109 column: self.pos.line_col.column + 1,
110 },
111 },
112 };
113
114 fmt.main_block(schema, self.pos, span, "");
115 }
116
117 fmt.format()
118 }
119}
120
121impl From<InvalidSyntax> for Error {
122 fn from(e: InvalidSyntax) -> Self {
123 Self::InvalidSyntax(e)
124 }
125}
126
127#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128pub enum Expected {
129 Eof,
130 Ident,
131 Keyword(&'static str),
132 LitInt,
133 LitPosInt,
134 LitString,
135 LitUuid,
136 SchemaName,
137 Token(&'static str),
138}
139
140impl Expected {
141 fn add(rule: Rule, set: &mut BTreeSet<Self>) {
142 const CONST_VALUE: &[Expected] = &[
143 Expected::Keyword("i16"),
144 Expected::Keyword("i32"),
145 Expected::Keyword("i64"),
146 Expected::Keyword("i8"),
147 Expected::Keyword("string"),
148 Expected::Keyword("u16"),
149 Expected::Keyword("u32"),
150 Expected::Keyword("u64"),
151 Expected::Keyword("u8"),
152 Expected::Keyword("uuid"),
153 ];
154
155 const DEF: &[Expected] = &[
156 Expected::Keyword("const"),
157 Expected::Keyword("enum"),
158 Expected::Keyword("service"),
159 Expected::Keyword("struct"),
160 Expected::Token("#"),
161 ];
162
163 const TYPE_NAME: &[Expected] = &[
164 Expected::Ident,
165 Expected::Keyword("bool"),
166 Expected::Keyword("bytes"),
167 Expected::Keyword("f32"),
168 Expected::Keyword("f64"),
169 Expected::Keyword("i16"),
170 Expected::Keyword("i32"),
171 Expected::Keyword("i64"),
172 Expected::Keyword("i8"),
173 Expected::Keyword("lifetime"),
174 Expected::Keyword("map"),
175 Expected::Keyword("object_id"),
176 Expected::Keyword("option"),
177 Expected::Keyword("receiver"),
178 Expected::Keyword("result"),
179 Expected::Keyword("sender"),
180 Expected::Keyword("service_id"),
181 Expected::Keyword("set"),
182 Expected::Keyword("string"),
183 Expected::Keyword("u16"),
184 Expected::Keyword("u32"),
185 Expected::Keyword("u64"),
186 Expected::Keyword("u8"),
187 Expected::Keyword("unit"),
188 Expected::Keyword("uuid"),
189 Expected::Keyword("value"),
190 Expected::Keyword("vec"),
191 Expected::SchemaName,
192 Expected::Token("["),
193 ];
194
195 const INLINE: &[Expected] = &[Expected::Keyword("enum"), Expected::Keyword("struct")];
196
197 const KEY_TYPE_NAME: &[Expected] = &[
198 Expected::Keyword("i16"),
199 Expected::Keyword("i32"),
200 Expected::Keyword("i64"),
201 Expected::Keyword("i8"),
202 Expected::Keyword("string"),
203 Expected::Keyword("u16"),
204 Expected::Keyword("u32"),
205 Expected::Keyword("u64"),
206 Expected::Keyword("u8"),
207 Expected::Keyword("uuid"),
208 ];
209
210 const ARRAY_LEN: &[Expected] =
211 &[Expected::Ident, Expected::LitPosInt, Expected::SchemaName];
212
213 #[allow(clippy::use_self)]
214 let add: &[&[Self]] = match rule {
215 Rule::EOI => &[&[Expected::Eof]],
216 Rule::array_len => &[ARRAY_LEN],
217 Rule::const_value => &[CONST_VALUE],
218 Rule::def => &[DEF],
219 Rule::ident => &[&[Expected::Ident]],
220 Rule::key_type_name => &[KEY_TYPE_NAME],
221 Rule::kw_args => &[&[Expected::Keyword("args")]],
222 Rule::kw_enum => &[&[Expected::Keyword("enum")]],
223 Rule::kw_err => &[&[Expected::Keyword("err")]],
224 Rule::kw_fallback => &[&[Expected::Keyword("fallback")]],
225 Rule::kw_import => &[&[Expected::Keyword("import")]],
226 Rule::kw_object_id => &[&[Expected::Keyword("object_id")]],
227 Rule::kw_ok => &[&[Expected::Keyword("ok")]],
228 Rule::kw_service_id => &[&[Expected::Keyword("service_id")]],
229 Rule::kw_struct => &[&[Expected::Keyword("struct")]],
230 Rule::kw_uuid => &[&[Expected::Keyword("uuid")]],
231 Rule::kw_version => &[&[Expected::Keyword("version")]],
232 Rule::lit_int => &[&[Expected::LitInt]],
233 Rule::lit_pos_int => &[&[Expected::LitPosInt]],
234 Rule::lit_string => &[&[Expected::LitString]],
235 Rule::lit_uuid => &[&[Expected::LitUuid]],
236 Rule::schema_name => &[&[Expected::SchemaName]],
237 Rule::service_item => &[&[Expected::Keyword("fn"), Expected::Keyword("event")]],
238 Rule::struct_field => &[&[Expected::Keyword("required"), Expected::Ident]],
239 Rule::tok_ang_close => &[&[Expected::Token(">")]],
240 Rule::tok_ang_open => &[&[Expected::Token("<")]],
241 Rule::tok_arrow => &[&[Expected::Token("->")]],
242 Rule::tok_at => &[&[Expected::Token("@")]],
243 Rule::tok_comma => &[&[Expected::Token(",")]],
244 Rule::tok_cur_close => &[&[Expected::Token("}")]],
245 Rule::tok_cur_open => &[&[Expected::Token("{")]],
246 Rule::tok_eq => &[&[Expected::Token("=")]],
247 Rule::tok_hash => &[&[Expected::Token("#")]],
248 Rule::tok_par_close => &[&[Expected::Token(")")]],
249 Rule::tok_par_open => &[&[Expected::Token("(")]],
250 Rule::tok_scope => &[&[Expected::Token("::")]],
251 Rule::tok_squ_close => &[&[Expected::Token("]")]],
252 Rule::tok_squ_open => &[&[Expected::Token("[")]],
253 Rule::tok_term => &[&[Expected::Token(";")]],
254 Rule::type_name => &[TYPE_NAME],
255 Rule::type_name_or_inline => &[TYPE_NAME, INLINE],
256 _ => return,
257 };
258
259 for &slice in add {
260 set.extend(slice);
261 }
262 }
263}