use std::{fmt, sync::Arc};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
UnknownLanguage(String),
UnknownTheme(String),
Grammar(GrammarError),
Theme(ThemeError),
Bundle(BundleError),
Diagnostic(DiagnosticError),
Render(RenderError),
StateMismatch,
InvalidLine,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownLanguage(value) => write!(f, "unknown TextMate language `{value}`"),
Self::UnknownTheme(value) => write!(f, "unknown TextMate theme `{value}`"),
Self::Grammar(error) => error.fmt(f),
Self::Theme(error) => error.fmt(f),
Self::Bundle(error) => error.fmt(f),
Self::Diagnostic(error) => error.fmt(f),
Self::Render(error) => error.fmt(f),
Self::StateMismatch => {
f.write_str("tokenizer state belongs to a different Syntaxmate tokenizer")
}
Self::InvalidLine => {
f.write_str("tokenize_line expects one logical line without a newline terminator")
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Grammar(error) => Some(error),
Self::Theme(error) => Some(error),
Self::Bundle(error) => Some(error),
Self::Diagnostic(error) => Some(error),
Self::Render(error) => Some(error),
_ => None,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct JsonError(Arc<serde_json::Error>);
impl JsonError {
pub(crate) fn new(error: serde_json::Error) -> Self {
Self(Arc::new(error))
}
pub fn line(&self) -> usize {
self.0.line()
}
pub fn column(&self) -> usize {
self.0.column()
}
}
impl PartialEq for JsonError {
fn eq(&self, other: &Self) -> bool {
self.0.classify() == other.0.classify()
&& self.line() == other.line()
&& self.column() == other.column()
&& self.0.to_string() == other.0.to_string()
}
}
impl Eq for JsonError {}
impl fmt::Display for JsonError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for JsonError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.0.as_ref())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct MissingInclude {
scope: Option<String>,
repository: Option<String>,
}
impl MissingInclude {
pub(crate) fn new(scope: Option<String>, repository: Option<String>) -> Self {
Self { scope, repository }
}
pub fn scope_name(&self) -> Option<&str> {
self.scope.as_deref()
}
pub fn repository_key(&self) -> Option<&str> {
self.repository.as_deref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct RegexError {
pattern: String,
position: usize,
message: String,
}
impl RegexError {
pub(crate) fn new(pattern: String, position: usize, message: String) -> Self {
Self {
pattern,
position,
message,
}
}
pub fn pattern(&self) -> &str {
&self.pattern
}
pub fn position(&self) -> usize {
self.position
}
pub fn message(&self) -> &str {
&self.message
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum GrammarResource {
GrammarBytes,
GrammarCount,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct LimitExceeded {
resource: GrammarResource,
limit: usize,
actual: usize,
}
impl LimitExceeded {
pub(crate) fn new(resource: GrammarResource, limit: usize, actual: usize) -> Self {
Self {
resource,
limit,
actual,
}
}
pub fn resource(&self) -> GrammarResource {
self.resource
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn actual(&self) -> usize {
self.actual
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum GrammarErrorKind {
InvalidJson(JsonError),
MissingInclude(MissingInclude),
InvalidRegex(RegexError),
LimitExceeded(LimitExceeded),
ForeignGrammarId,
PreparationLimit(String),
InvalidReference(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct GrammarError(Box<GrammarErrorInner>);
#[derive(Debug, Clone, PartialEq, Eq)]
struct GrammarErrorInner {
scope: Option<String>,
kind: GrammarErrorKind,
}
impl GrammarError {
pub(crate) fn new(scope: Option<String>, kind: GrammarErrorKind) -> Self {
Self(Box::new(GrammarErrorInner { scope, kind }))
}
pub fn scope_name(&self) -> Option<&str> {
self.0.scope.as_deref()
}
pub fn kind(&self) -> &GrammarErrorKind {
&self.0.kind
}
}
impl fmt::Display for GrammarError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(scope) = &self.0.scope {
write!(f, "{scope}: ")?;
}
match &self.0.kind {
GrammarErrorKind::InvalidJson(error) => write!(f, "JSON parse error: {error}"),
GrammarErrorKind::MissingInclude(include) => write!(
f,
"unknown include {}{}{}",
include.scope_name().unwrap_or(""),
if include.repository.is_some() {
"#"
} else {
""
},
include.repository_key().unwrap_or("")
),
GrammarErrorKind::InvalidRegex(error) => {
write!(f, "invalid regex `{}`: {}", error.pattern, error.message)
}
GrammarErrorKind::LimitExceeded(error) => write!(
f,
"grammar {:?} value {} exceeds limit {}",
error.resource, error.actual, error.limit
),
GrammarErrorKind::ForeignGrammarId => {
f.write_str("root grammar does not belong to this registry")
}
GrammarErrorKind::PreparationLimit(detail) => write!(
f,
"grammar exceeds PreparedLanguage preparation bounds ({detail}); use Tokenizer directly"
),
GrammarErrorKind::InvalidReference(message) => f.write_str(message),
}
}
}
impl std::error::Error for GrammarError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.0.kind {
GrammarErrorKind::InvalidJson(error) => Some(error),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ThemeErrorKind {
InvalidJson(JsonError),
InvalidColor(String),
InvalidRule,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ThemeError(Box<ThemeErrorInner>);
#[derive(Debug, Clone, PartialEq, Eq)]
struct ThemeErrorInner {
kind: ThemeErrorKind,
message: String,
}
impl ThemeError {
pub(crate) fn rule(message: String) -> Self {
Self::new(ThemeErrorKind::InvalidRule, message)
}
pub(crate) fn new(kind: ThemeErrorKind, message: String) -> Self {
Self(Box::new(ThemeErrorInner { kind, message }))
}
pub(crate) fn json(error: serde_json::Error) -> Self {
let message = format!("invalid TextMate theme JSON: {error}");
Self::new(ThemeErrorKind::InvalidJson(JsonError::new(error)), message)
}
pub(crate) fn color(value: &str, message: String) -> Self {
Self::new(ThemeErrorKind::InvalidColor(value.to_owned()), message)
}
pub fn kind(&self) -> &ThemeErrorKind {
&self.0.kind
}
}
impl fmt::Display for ThemeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.message)
}
}
impl std::error::Error for ThemeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.0.kind {
ThemeErrorKind::InvalidJson(error) => Some(error),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BundleErrorKind {
EmptyCatalog,
MissingGrammar,
Decode,
Invalid,
TooLarge,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct BundleError(Box<BundleErrorInner>);
#[derive(Debug, Clone, PartialEq, Eq)]
struct BundleErrorInner {
kind: BundleErrorKind,
language: Option<String>,
message: String,
}
impl BundleError {
pub(crate) fn new(kind: BundleErrorKind, language: Option<String>, message: String) -> Self {
Self(Box::new(BundleErrorInner {
kind,
language,
message,
}))
}
pub fn kind(&self) -> BundleErrorKind {
self.0.kind
}
pub fn language(&self) -> Option<&str> {
self.0.language.as_deref()
}
}
impl fmt::Display for BundleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.message)
}
}
impl std::error::Error for BundleError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DiagnosticErrorKind {
MatcherBuild,
BudgetExceeded,
InvalidStart,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DiagnosticError(Box<DiagnosticErrorInner>);
#[derive(Debug, Clone, PartialEq, Eq)]
struct DiagnosticErrorInner {
kind: DiagnosticErrorKind,
pattern: String,
position: Option<usize>,
steps: Option<usize>,
message: String,
}
impl DiagnosticError {
#[cfg(feature = "diagnostics")]
pub(crate) fn build(pattern: &str, message: String) -> Self {
Self(Box::new(DiagnosticErrorInner {
kind: DiagnosticErrorKind::MatcherBuild,
pattern: pattern.to_owned(),
position: None,
steps: None,
message,
}))
}
#[cfg(feature = "diagnostics")]
pub(crate) fn fallback(pattern: &str, error: crate::engine::regex::FallbackError) -> Self {
use crate::engine::regex::FallbackError;
let (kind, position, steps) = match error {
FallbackError::InvalidStart { from } => {
(DiagnosticErrorKind::InvalidStart, Some(from), None)
}
FallbackError::BudgetExceeded { steps } => {
(DiagnosticErrorKind::BudgetExceeded, None, Some(steps))
}
};
Self(Box::new(DiagnosticErrorInner {
kind,
pattern: pattern.to_owned(),
position,
steps,
message: format!("fallback error: {error:?}"),
}))
}
pub fn kind(&self) -> DiagnosticErrorKind {
self.0.kind
}
pub fn pattern(&self) -> &str {
&self.0.pattern
}
pub fn position(&self) -> Option<usize> {
self.0.position
}
pub fn steps(&self) -> Option<usize> {
self.0.steps
}
}
impl fmt::Display for DiagnosticError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.message)
}
}
impl std::error::Error for DiagnosticError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RenderErrorKind {
SourceMismatch,
Writer,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct RenderError(Box<RenderErrorInner>);
#[derive(Debug, Clone, PartialEq, Eq)]
struct RenderErrorInner {
kind: RenderErrorKind,
message: String,
source: Option<fmt::Error>,
}
impl RenderError {
#[cfg(any(feature = "html", feature = "ansi"))]
pub(crate) fn mismatch(message: String) -> Self {
Self(Box::new(RenderErrorInner {
kind: RenderErrorKind::SourceMismatch,
message,
source: None,
}))
}
#[cfg(any(feature = "html", feature = "ansi"))]
pub(crate) fn writer(error: fmt::Error) -> Self {
Self(Box::new(RenderErrorInner {
kind: RenderErrorKind::Writer,
message: "render output writer failed".to_owned(),
source: Some(error),
}))
}
pub fn kind(&self) -> RenderErrorKind {
self.0.kind
}
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.message)
}
}
impl std::error::Error for RenderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.source.as_ref().map(|error| error as _)
}
}
pub(crate) fn grammar_load_error(error: crate::engine::grammar::GrammarLoadError) -> Error {
use crate::engine::grammar::GrammarLoadError;
match error {
GrammarLoadError::Json { source, .. } => Error::Grammar(GrammarError::new(
None,
GrammarErrorKind::InvalidJson(JsonError::new(source)),
)),
GrammarLoadError::Validation { source, .. } => grammar_validation_error(*source),
}
}
pub(crate) fn grammar_validation_error(
error: crate::engine::grammar::GrammarValidationError,
) -> Error {
let message = error.to_string();
let kind = match error.missing_include {
Some(target) => {
let (scope, repository) = *target;
GrammarErrorKind::MissingInclude(MissingInclude::new(scope, repository))
}
None => GrammarErrorKind::InvalidReference(message),
};
Error::Grammar(GrammarError::new(Some(error.grammar), kind))
}
#[cfg(test)]
mod tests;