1use std::{fmt, sync::Arc};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum Error {
11 UnknownLanguage(String),
13 UnknownTheme(String),
15 Grammar(GrammarError),
17 Theme(ThemeError),
19 Bundle(BundleError),
21 Diagnostic(DiagnosticError),
23 Render(RenderError),
25 StateMismatch,
27 InvalidLine,
29}
30
31impl fmt::Display for Error {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 Self::UnknownLanguage(value) => write!(f, "unknown TextMate language `{value}`"),
35 Self::UnknownTheme(value) => write!(f, "unknown TextMate theme `{value}`"),
36 Self::Grammar(error) => error.fmt(f),
37 Self::Theme(error) => error.fmt(f),
38 Self::Bundle(error) => error.fmt(f),
39 Self::Diagnostic(error) => error.fmt(f),
40 Self::Render(error) => error.fmt(f),
41 Self::StateMismatch => {
42 f.write_str("tokenizer state belongs to a different Syntaxmate tokenizer")
43 }
44 Self::InvalidLine => {
45 f.write_str("tokenize_line expects one logical line without a newline terminator")
46 }
47 }
48 }
49}
50
51impl std::error::Error for Error {
52 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
53 match self {
54 Self::Grammar(error) => Some(error),
55 Self::Theme(error) => Some(error),
56 Self::Bundle(error) => Some(error),
57 Self::Diagnostic(error) => Some(error),
58 Self::Render(error) => Some(error),
59 _ => None,
60 }
61 }
62}
63
64pub type Result<T> = std::result::Result<T, Error>;
66
67#[derive(Debug, Clone)]
69#[non_exhaustive]
70pub struct JsonError(Arc<serde_json::Error>);
71
72impl JsonError {
73 pub(crate) fn new(error: serde_json::Error) -> Self {
74 Self(Arc::new(error))
75 }
76 pub fn line(&self) -> usize {
78 self.0.line()
79 }
80 pub fn column(&self) -> usize {
82 self.0.column()
83 }
84}
85impl PartialEq for JsonError {
86 fn eq(&self, other: &Self) -> bool {
87 self.0.classify() == other.0.classify()
88 && self.line() == other.line()
89 && self.column() == other.column()
90 && self.0.to_string() == other.0.to_string()
91 }
92}
93impl Eq for JsonError {}
94impl fmt::Display for JsonError {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 self.0.fmt(f)
97 }
98}
99impl std::error::Error for JsonError {
100 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
101 Some(self.0.as_ref())
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
107#[non_exhaustive]
108pub struct MissingInclude {
109 scope: Option<String>,
110 repository: Option<String>,
111}
112impl MissingInclude {
113 pub(crate) fn new(scope: Option<String>, repository: Option<String>) -> Self {
114 Self { scope, repository }
115 }
116 pub fn scope_name(&self) -> Option<&str> {
118 self.scope.as_deref()
119 }
120 pub fn repository_key(&self) -> Option<&str> {
122 self.repository.as_deref()
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
128#[non_exhaustive]
129pub struct RegexError {
130 pattern: String,
131 position: usize,
132 message: String,
133}
134impl RegexError {
135 pub(crate) fn new(pattern: String, position: usize, message: String) -> Self {
136 Self {
137 pattern,
138 position,
139 message,
140 }
141 }
142 pub fn pattern(&self) -> &str {
144 &self.pattern
145 }
146 pub fn position(&self) -> usize {
148 self.position
149 }
150 pub fn message(&self) -> &str {
152 &self.message
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158#[non_exhaustive]
159pub enum GrammarResource {
160 GrammarBytes,
162 GrammarCount,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
168#[non_exhaustive]
169pub struct LimitExceeded {
170 resource: GrammarResource,
171 limit: usize,
172 actual: usize,
173}
174impl LimitExceeded {
175 pub(crate) fn new(resource: GrammarResource, limit: usize, actual: usize) -> Self {
176 Self {
177 resource,
178 limit,
179 actual,
180 }
181 }
182 pub fn resource(&self) -> GrammarResource {
184 self.resource
185 }
186 pub fn limit(&self) -> usize {
188 self.limit
189 }
190 pub fn actual(&self) -> usize {
192 self.actual
193 }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
198#[non_exhaustive]
199pub enum GrammarErrorKind {
200 InvalidJson(JsonError),
202 MissingInclude(MissingInclude),
204 InvalidRegex(RegexError),
206 LimitExceeded(LimitExceeded),
208 ForeignGrammarId,
210 PreparationLimit(String),
212 InvalidReference(String),
214}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
218#[non_exhaustive]
219pub struct GrammarError(Box<GrammarErrorInner>);
220
221#[derive(Debug, Clone, PartialEq, Eq)]
222struct GrammarErrorInner {
223 scope: Option<String>,
224 kind: GrammarErrorKind,
225}
226impl GrammarError {
227 pub(crate) fn new(scope: Option<String>, kind: GrammarErrorKind) -> Self {
228 Self(Box::new(GrammarErrorInner { scope, kind }))
229 }
230 pub fn scope_name(&self) -> Option<&str> {
232 self.0.scope.as_deref()
233 }
234 pub fn kind(&self) -> &GrammarErrorKind {
236 &self.0.kind
237 }
238}
239impl fmt::Display for GrammarError {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 if let Some(scope) = &self.0.scope {
242 write!(f, "{scope}: ")?;
243 }
244 match &self.0.kind {
245 GrammarErrorKind::InvalidJson(error) => write!(f, "JSON parse error: {error}"),
246 GrammarErrorKind::MissingInclude(include) => write!(
247 f,
248 "unknown include {}{}{}",
249 include.scope_name().unwrap_or(""),
250 if include.repository.is_some() {
251 "#"
252 } else {
253 ""
254 },
255 include.repository_key().unwrap_or("")
256 ),
257 GrammarErrorKind::InvalidRegex(error) => {
258 write!(f, "invalid regex `{}`: {}", error.pattern, error.message)
259 }
260 GrammarErrorKind::LimitExceeded(error) => write!(
261 f,
262 "grammar {:?} value {} exceeds limit {}",
263 error.resource, error.actual, error.limit
264 ),
265 GrammarErrorKind::ForeignGrammarId => {
266 f.write_str("root grammar does not belong to this registry")
267 }
268 GrammarErrorKind::PreparationLimit(detail) => write!(
269 f,
270 "grammar exceeds PreparedLanguage preparation bounds ({detail}); use Tokenizer directly"
271 ),
272 GrammarErrorKind::InvalidReference(message) => f.write_str(message),
273 }
274 }
275}
276impl std::error::Error for GrammarError {
277 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
278 match &self.0.kind {
279 GrammarErrorKind::InvalidJson(error) => Some(error),
280 _ => None,
281 }
282 }
283}
284
285#[derive(Debug, Clone, PartialEq, Eq)]
287#[non_exhaustive]
288pub enum ThemeErrorKind {
289 InvalidJson(JsonError),
291 InvalidColor(String),
293 InvalidRule,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
299#[non_exhaustive]
300pub struct ThemeError(Box<ThemeErrorInner>);
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303struct ThemeErrorInner {
304 kind: ThemeErrorKind,
305 message: String,
306}
307impl ThemeError {
308 pub(crate) fn rule(message: String) -> Self {
309 Self::new(ThemeErrorKind::InvalidRule, message)
310 }
311 pub(crate) fn new(kind: ThemeErrorKind, message: String) -> Self {
312 Self(Box::new(ThemeErrorInner { kind, message }))
313 }
314 pub(crate) fn json(error: serde_json::Error) -> Self {
315 let message = format!("invalid TextMate theme JSON: {error}");
316 Self::new(ThemeErrorKind::InvalidJson(JsonError::new(error)), message)
317 }
318 pub(crate) fn color(value: &str, message: String) -> Self {
319 Self::new(ThemeErrorKind::InvalidColor(value.to_owned()), message)
320 }
321 pub fn kind(&self) -> &ThemeErrorKind {
323 &self.0.kind
324 }
325}
326impl fmt::Display for ThemeError {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 f.write_str(&self.0.message)
329 }
330}
331impl std::error::Error for ThemeError {
332 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
333 match &self.0.kind {
334 ThemeErrorKind::InvalidJson(error) => Some(error),
335 _ => None,
336 }
337 }
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342#[non_exhaustive]
343pub enum BundleErrorKind {
344 EmptyCatalog,
346 MissingGrammar,
348 Decode,
350 Invalid,
352 TooLarge,
354}
355
356#[derive(Debug, Clone, PartialEq, Eq)]
358#[non_exhaustive]
359pub struct BundleError(Box<BundleErrorInner>);
360
361#[derive(Debug, Clone, PartialEq, Eq)]
362struct BundleErrorInner {
363 kind: BundleErrorKind,
364 language: Option<String>,
365 message: String,
366}
367impl BundleError {
368 pub(crate) fn new(kind: BundleErrorKind, language: Option<String>, message: String) -> Self {
369 Self(Box::new(BundleErrorInner {
370 kind,
371 language,
372 message,
373 }))
374 }
375 pub fn kind(&self) -> BundleErrorKind {
377 self.0.kind
378 }
379 pub fn language(&self) -> Option<&str> {
381 self.0.language.as_deref()
382 }
383}
384impl fmt::Display for BundleError {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 f.write_str(&self.0.message)
387 }
388}
389impl std::error::Error for BundleError {}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
393#[non_exhaustive]
394pub enum DiagnosticErrorKind {
395 MatcherBuild,
397 BudgetExceeded,
399 InvalidStart,
401}
402
403#[derive(Debug, Clone, PartialEq, Eq)]
405#[non_exhaustive]
406pub struct DiagnosticError(Box<DiagnosticErrorInner>);
407
408#[derive(Debug, Clone, PartialEq, Eq)]
409struct DiagnosticErrorInner {
410 kind: DiagnosticErrorKind,
411 pattern: String,
412 position: Option<usize>,
413 steps: Option<usize>,
414 message: String,
415}
416impl DiagnosticError {
417 #[cfg(feature = "diagnostics")]
418 pub(crate) fn build(pattern: &str, message: String) -> Self {
419 Self(Box::new(DiagnosticErrorInner {
420 kind: DiagnosticErrorKind::MatcherBuild,
421 pattern: pattern.to_owned(),
422 position: None,
423 steps: None,
424 message,
425 }))
426 }
427 #[cfg(feature = "diagnostics")]
428 pub(crate) fn fallback(pattern: &str, error: crate::engine::regex::FallbackError) -> Self {
429 use crate::engine::regex::FallbackError;
430 let (kind, position, steps) = match error {
431 FallbackError::InvalidStart { from } => {
432 (DiagnosticErrorKind::InvalidStart, Some(from), None)
433 }
434 FallbackError::BudgetExceeded { steps } => {
435 (DiagnosticErrorKind::BudgetExceeded, None, Some(steps))
436 }
437 };
438 Self(Box::new(DiagnosticErrorInner {
439 kind,
440 pattern: pattern.to_owned(),
441 position,
442 steps,
443 message: format!("fallback error: {error:?}"),
444 }))
445 }
446 pub fn kind(&self) -> DiagnosticErrorKind {
448 self.0.kind
449 }
450 pub fn pattern(&self) -> &str {
452 &self.0.pattern
453 }
454 pub fn position(&self) -> Option<usize> {
456 self.0.position
457 }
458 pub fn steps(&self) -> Option<usize> {
460 self.0.steps
461 }
462}
463impl fmt::Display for DiagnosticError {
464 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465 f.write_str(&self.0.message)
466 }
467}
468impl std::error::Error for DiagnosticError {}
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq)]
472#[non_exhaustive]
473pub enum RenderErrorKind {
474 SourceMismatch,
476 Writer,
478}
479
480#[derive(Debug, Clone, PartialEq, Eq)]
482#[non_exhaustive]
483pub struct RenderError(Box<RenderErrorInner>);
484
485#[derive(Debug, Clone, PartialEq, Eq)]
486struct RenderErrorInner {
487 kind: RenderErrorKind,
488 message: String,
489 source: Option<fmt::Error>,
490}
491impl RenderError {
492 #[cfg(any(feature = "html", feature = "ansi"))]
493 pub(crate) fn mismatch(message: String) -> Self {
494 Self(Box::new(RenderErrorInner {
495 kind: RenderErrorKind::SourceMismatch,
496 message,
497 source: None,
498 }))
499 }
500 #[cfg(any(feature = "html", feature = "ansi"))]
501 pub(crate) fn writer(error: fmt::Error) -> Self {
502 Self(Box::new(RenderErrorInner {
503 kind: RenderErrorKind::Writer,
504 message: "render output writer failed".to_owned(),
505 source: Some(error),
506 }))
507 }
508 pub fn kind(&self) -> RenderErrorKind {
510 self.0.kind
511 }
512}
513impl fmt::Display for RenderError {
514 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
515 f.write_str(&self.0.message)
516 }
517}
518impl std::error::Error for RenderError {
519 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
520 self.0.source.as_ref().map(|error| error as _)
521 }
522}
523
524pub(crate) fn grammar_load_error(error: crate::engine::grammar::GrammarLoadError) -> Error {
525 use crate::engine::grammar::GrammarLoadError;
526 match error {
527 GrammarLoadError::Json { source, .. } => Error::Grammar(GrammarError::new(
528 None,
529 GrammarErrorKind::InvalidJson(JsonError::new(source)),
530 )),
531 GrammarLoadError::Validation { source, .. } => grammar_validation_error(*source),
532 }
533}
534
535pub(crate) fn grammar_validation_error(
536 error: crate::engine::grammar::GrammarValidationError,
537) -> Error {
538 let message = error.to_string();
539 let kind = match error.missing_include {
540 Some(target) => {
541 let (scope, repository) = *target;
542 GrammarErrorKind::MissingInclude(MissingInclude::new(scope, repository))
543 }
544 None => GrammarErrorKind::InvalidReference(message),
545 };
546 Error::Grammar(GrammarError::new(Some(error.grammar), kind))
547}
548
549#[cfg(test)]
550mod tests;