tree_sitter_graph/execution/
error.rs

1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2021, tree-sitter authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8use std::path::Path;
9use thiserror::Error;
10
11use crate::ast::Stanza;
12use crate::ast::Statement;
13use crate::execution::CancellationError;
14use crate::parse_error::Excerpt;
15use crate::Location;
16
17/// An error that can occur while executing a graph DSL file
18#[derive(Debug, Error)]
19pub enum ExecutionError {
20    #[error(transparent)]
21    Cancelled(#[from] CancellationError),
22    #[error("Cannot assign immutable variable {0}")]
23    CannotAssignImmutableVariable(String),
24    #[error("Cannot assign scoped variable {0}")]
25    CannotAssignScopedVariable(String),
26    #[error("Cannot define mutable scoped variable {0}")]
27    CannotDefineMutableScopedVariable(String),
28    #[error("Duplicate attribute {0}")]
29    DuplicateAttribute(String),
30    #[error("Duplicate edge {0}")]
31    DuplicateEdge(String),
32    #[error("Duplicate variable {0}")]
33    DuplicateVariable(String),
34    #[error("Expected a graph node reference {0}")]
35    ExpectedGraphNode(String),
36    #[error("Expected a list {0}")]
37    ExpectedList(String),
38    #[error("Expected a boolean {0}")]
39    ExpectedBoolean(String),
40    #[error("Expected an integer {0}")]
41    ExpectedInteger(String),
42    #[error("Expected a string {0}")]
43    ExpectedString(String),
44    #[error("Expected a syntax node {0}")]
45    ExpectedSyntaxNode(String),
46    #[error("Invalid parameters {0}")]
47    InvalidParameters(String),
48    #[error("Scoped variables can only be attached to syntax nodes {0}")]
49    InvalidVariableScope(String),
50    #[error("Missing global variable {0}")]
51    MissingGlobalVariable(String),
52    #[error("Recursively defined scoped variable {0}")]
53    RecursivelyDefinedScopedVariable(String),
54    #[error("Recursively defined variable {0}")]
55    RecursivelyDefinedVariable(String),
56    #[error("Undefined capture {0}")]
57    UndefinedCapture(String),
58    #[error("Undefined function {0}")]
59    UndefinedFunction(String),
60    #[error("Undefined regex capture {0}")]
61    UndefinedRegexCapture(String),
62    #[error("Undefined scoped variable {0}")]
63    UndefinedScopedVariable(String),
64    #[error("Empty regex capture {0}")]
65    EmptyRegexCapture(String),
66    #[error("Undefined edge {0}")]
67    UndefinedEdge(String),
68    #[error("Undefined variable {0}")]
69    UndefinedVariable(String),
70    #[error("Cannot add scoped variable after being forced {0}")]
71    VariableScopesAlreadyForced(String),
72    #[error("Function {0} failed: {1}")]
73    FunctionFailed(String, String),
74    #[error("{0}. Caused by: {1}")]
75    InContext(Context, Box<ExecutionError>),
76}
77
78#[derive(Clone, Debug)]
79pub enum Context {
80    Statement(Vec<StatementContext>),
81    Other(String),
82}
83
84#[derive(Clone, Debug)]
85pub struct StatementContext {
86    pub statement: String,
87    pub statement_location: Location,
88    pub stanza_location: Location,
89    pub source_location: Location,
90    pub node_kind: String,
91}
92
93impl StatementContext {
94    pub(crate) fn new(stmt: &Statement, stanza: &Stanza, source_node: &tree_sitter::Node) -> Self {
95        Self {
96            statement: format!("{}", stmt),
97            statement_location: stmt.location(),
98            stanza_location: stanza.range.start,
99            source_location: Location::from(source_node.range().start_point),
100            node_kind: source_node.kind().to_string(),
101        }
102    }
103
104    pub(crate) fn update_statement(&mut self, stmt: &Statement) {
105        self.statement = format!("{}", stmt);
106        self.statement_location = stmt.location();
107    }
108}
109
110impl From<StatementContext> for Context {
111    fn from(value: StatementContext) -> Self {
112        Self::Statement(vec![value])
113    }
114}
115
116impl From<(StatementContext, StatementContext)> for Context {
117    fn from((left, right): (StatementContext, StatementContext)) -> Self {
118        Self::Statement(vec![left, right])
119    }
120}
121
122impl From<String> for Context {
123    fn from(value: String) -> Self {
124        Self::Other(value)
125    }
126}
127
128impl std::fmt::Display for Context {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        match self {
131            Self::Statement(stmts) => {
132                let mut first = true;
133                for stmt in stmts {
134                    stmt.fmt(f, first)?;
135                    first = false;
136                }
137            }
138            Self::Other(msg) => write!(f, "{}", msg)?,
139        }
140        Ok(())
141    }
142}
143
144impl StatementContext {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, first: bool) -> std::fmt::Result {
146        if first {
147            write!(f, "Error executing",)?;
148        } else {
149            write!(f, " and executing",)?;
150        }
151        write!(
152            f,
153            " {} in stanza at {} matching ({}) node at {}",
154            self.statement, self.stanza_location, self.node_kind, self.source_location
155        )?;
156        Ok(())
157    }
158}
159
160pub(super) trait ResultWithExecutionError<R> {
161    fn with_context<F>(self, with_context: F) -> Result<R, ExecutionError>
162    where
163        F: FnOnce() -> Context;
164}
165
166impl<R> ResultWithExecutionError<R> for Result<R, ExecutionError> {
167    fn with_context<F>(self, with_context: F) -> Result<R, ExecutionError>
168    where
169        F: FnOnce() -> Context,
170    {
171        self.map_err(|e| match e {
172            cancelled @ ExecutionError::Cancelled(_) => cancelled,
173            in_other_context @ ExecutionError::InContext(Context::Other(_), _) => {
174                ExecutionError::InContext(with_context(), Box::new(in_other_context))
175            }
176            in_stmt_context @ ExecutionError::InContext(_, _) => in_stmt_context,
177            _ => ExecutionError::InContext(with_context(), Box::new(e)),
178        })
179    }
180}
181
182impl ExecutionError {
183    pub fn display_pretty<'a>(
184        &'a self,
185        source_path: &'a Path,
186        source: &'a str,
187        tsg_path: &'a Path,
188        tsg: &'a str,
189    ) -> impl std::fmt::Display + 'a {
190        DisplayExecutionErrorPretty {
191            error: self,
192            source_path,
193            source,
194            tsg_path,
195            tsg,
196        }
197    }
198}
199
200struct DisplayExecutionErrorPretty<'a> {
201    error: &'a ExecutionError,
202    source_path: &'a Path,
203    source: &'a str,
204    tsg_path: &'a Path,
205    tsg: &'a str,
206}
207
208impl std::fmt::Display for DisplayExecutionErrorPretty<'_> {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        self.fmt_entry(f, 0, self.error)
211    }
212}
213
214impl DisplayExecutionErrorPretty<'_> {
215    fn fmt_entry(
216        &self,
217        f: &mut std::fmt::Formatter<'_>,
218        index: usize,
219        error: &ExecutionError,
220    ) -> std::fmt::Result {
221        match error {
222            ExecutionError::InContext(context, cause) => {
223                match context {
224                    Context::Statement(stmts) => {
225                        let mut first = true;
226                        for stmt in stmts {
227                            stmt.fmt_pretty(
228                                f,
229                                self.source_path,
230                                self.source,
231                                self.tsg_path,
232                                self.tsg,
233                                index,
234                                first,
235                            )?;
236                            first = false;
237                        }
238                    }
239                    Context::Other(msg) => writeln!(f, "{:>5}: {}", index, msg)?,
240                };
241                self.fmt_entry(f, index + 1, cause)?;
242                Ok(())
243            }
244            other => writeln!(f, "{:>5}: {}", index, other),
245        }
246    }
247}
248
249impl StatementContext {
250    fn fmt_pretty(
251        &self,
252        f: &mut std::fmt::Formatter<'_>,
253        source_path: &Path,
254        source: &str,
255        tsg_path: &Path,
256        tsg: &str,
257        index: usize,
258        first: bool,
259    ) -> std::fmt::Result {
260        if first {
261            writeln!(
262                f,
263                "{:>5}: Error executing statement {}",
264                index, self.statement
265            )?;
266        } else {
267            writeln!(f, "     > and executing statement {}", self.statement)?;
268        }
269        write!(
270            f,
271            "{}",
272            Excerpt::from_source(
273                tsg_path,
274                tsg,
275                self.statement_location.row,
276                self.statement_location.to_column_range(),
277                7
278            )
279        )?;
280        writeln!(f, "{}in stanza", " ".repeat(7))?;
281        write!(
282            f,
283            "{}",
284            Excerpt::from_source(
285                tsg_path,
286                tsg,
287                self.stanza_location.row,
288                self.stanza_location.to_column_range(),
289                7
290            )
291        )?;
292        writeln!(f, "{}matching ({}) node", " ".repeat(7), self.node_kind)?;
293        write!(
294            f,
295            "{}",
296            Excerpt::from_source(
297                source_path,
298                source,
299                self.source_location.row,
300                self.source_location.to_column_range(),
301                7
302            )
303        )?;
304        Ok(())
305    }
306}