qasmsim/
error.rs

1//! Contain the error types ragarding the different tasks that QasmSim can
2//! perform.
3
4#[macro_use]
5pub(crate) mod humanize;
6
7use std::convert;
8use std::error;
9use std::fmt;
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14use self::humanize::humanize_error;
15use crate::grammar::lexer::{self, Location, Tok};
16pub use crate::interpreter::runtime::RuntimeError;
17pub use crate::linker::LinkerError;
18use crate::semantics::QasmType;
19pub use crate::semantics::SemanticError;
20
21/// Represent a parsing error.
22pub type ParseError = lalrpop_util::ParseError<Location, lexer::Tok, lexer::LexicalError<Location>>;
23
24/// An alias for a pair relating some source code with an error.
25pub type SrcAndErr<'src, E> = (&'src str, E);
26
27/// Types of errors in QasmSim. QasmSim errors contain information about
28/// the error and the location in the source code where the error happens.
29///
30/// `QasmSimError` instances can be printed. They refer to the source code and
31/// try to provide contextual information for fixing the problem.
32///
33/// Conversion between [`ParseError`], [`RuntimeError`] and [`LinkerError`] is
34/// possible thanks to the trait `From` is defined for the pair
35/// `(&'source str, T)` (see alias [`SrcAndErr`]) for all the errors listed
36/// above.
37///
38/// # Examples
39///
40/// The error type of [`simulate()`] is [`RuntimeError`].
41/// `RuntimeError` is a _sourceless_ error in the sense it does not relate with
42/// the concrete source code beyond the location in the AST at which the error
43/// happens.
44///
45/// You can use [`map_err`] for for capturing the error and converting it
46/// into a `QasmSimError` from its pairing with the source.
47///
48/// ```
49/// use qasmsim::{QasmSimError, parse_and_link, simulate};
50///
51/// let source = r#"
52/// OPENQASM 2.0;
53/// qreg q[2];
54/// CX q[1], q[2]; // Notice we are indexing out of bounds here.
55/// "#;
56/// let program = parse_and_link(source)?;
57/// let runtime_error = simulate(&program).expect_err("Index out of bounds");
58/// let qasmsim_error = QasmSimError::from((source, runtime_error));
59/// # Ok::<(), QasmSimError>(())
60/// ```
61///
62/// [`ParseError`]: ./type.ParseError.html
63/// [`RuntimeError`]: ./enum.RuntimeError.html
64/// [`LinkerError`]: ../linker/enum.LinkerError.html
65/// [`SrcAndErr`]: ./type.SrcAndErr.html
66/// [`simulate()`]: ../fn.simulate.html
67/// [`map_err`]: ../../std/result/enum.Result.html#method.map_err
68#[non_exhaustive]
69#[derive(Debug, Clone, PartialEq, Eq, Hash)]
70#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
71pub enum QasmSimError<'src> {
72    /// A generic unknown error.
73    UnknownError(String),
74    /// Found an invalid token at some position.
75    InvalidToken {
76        /// Line source.
77        source: &'src str,
78        /// Line number.
79        lineno: usize,
80        /// Position inside the line (0-based) where the invalid token starts.
81        startpos: usize,
82        /// Position inside the line (0-based) where the invalid token ends.
83        endpos: Option<usize>,
84        /// Token found.
85        token: Option<Tok>,
86        /// A list of expected tokens.
87        expected: Vec<String>,
88    },
89    /// Found an unexpected end of file.
90    UnexpectedEOF {
91        /// Line source.
92        source: &'src str,
93        /// Line number.
94        lineno: usize,
95        /// Position inside the line (0-based) where the invalid token starts.
96        startpos: usize,
97        /// Position inside the line (0-based) where the invalid token ends.
98        endpos: Option<usize>,
99        /// Token found.
100        token: Option<Tok>,
101        /// A list of expected tokens.
102        expected: Vec<String>,
103    },
104    /// Found an unexpected token.
105    UnexpectedToken {
106        /// Line source.
107        source: &'src str,
108        /// Line number.
109        lineno: usize,
110        /// Position inside the line (0-based) where the invalid token starts.
111        startpos: usize,
112        /// Position inside the line (0-based) where the invalid token ends.
113        endpos: Option<usize>,
114        /// Token found.
115        token: Option<Tok>,
116        /// A list of expected tokens.
117        expected: Vec<String>,
118    },
119    /// Found a redefinition of a register.
120    RedefinitionError {
121        /// Line source.
122        source: &'src str,
123        /// Name of the register declared for second time.
124        symbol_name: String,
125        /// Line number.
126        lineno: usize,
127        /// Line number where the register was originally declared.
128        previous_lineno: usize,
129    },
130    /// The unability of linking a library.
131    LibraryNotFound {
132        /// Line source.
133        source: &'src str,
134        /// Path to the library to be included.
135        libpath: String,
136        /// Line number.
137        lineno: usize,
138    },
139    /// Use of register index that does not fit the register size.
140    IndexOutOfBounds {
141        /// Line source.
142        source: &'src str,
143        /// Line number.
144        lineno: usize,
145        /// Name of the register being indexed.
146        symbol_name: String,
147        /// Index tried to access.
148        index: usize,
149        /// Size of the register.
150        size: usize,
151    },
152    /// Use of an unknown/undeclared symbol.
153    SymbolNotFound {
154        /// Line source.
155        source: &'src str,
156        /// Line number.
157        lineno: usize,
158        /// Name of the unknown symbol.
159        symbol_name: String,
160        /// The expected type.
161        expected: QasmType,
162    },
163    /// The attempt of applying an operation passing the wrong number of
164    /// parameters.
165    WrongNumberOfParameters {
166        /// Line source.
167        source: &'src str,
168        /// Line number.
169        lineno: usize,
170        /// Name of the operation.
171        symbol_name: String,
172        /// Indicate if the parameters are registers or real values.
173        are_registers: bool,
174        /// The number of passed parameters.
175        given: usize,
176        /// The number of expected parameters.
177        expected: usize,
178    },
179    /// Use of a gate not previously defined.
180    UndefinedGate {
181        /// Line source.
182        source: &'src str,
183        /// Line number.
184        lineno: usize,
185        /// Name of the unknown gate.
186        symbol_name: String,
187    },
188    /// Found an unexpected type of value.
189    TypeMismatch {
190        /// Line source.
191        source: &'src str,
192        /// Line number.
193        lineno: usize,
194        /// Name of the symbol with the incorrect type.
195        symbol_name: String,
196        /// Expected type.
197        expected: QasmType,
198    },
199    /// Attempt of applying an operation to different sizes registers.
200    RegisterSizeMismatch {
201        /// Line source.
202        source: &'src str,
203        /// Line number.
204        lineno: usize,
205        /// Name of the operation.
206        symbol_name: String,
207        /// Sizes of the different registers involved.
208        sizes: Vec<usize>,
209    },
210}
211
212impl fmt::Display for QasmSimError<'_> {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        let mut buffer = String::new();
215        humanize_error(&mut buffer, self)?;
216        write!(f, "{}", buffer)
217    }
218}
219
220impl error::Error for QasmSimError<'_> {}
221
222impl convert::From<String> for QasmSimError<'_> {
223    fn from(err: String) -> Self {
224        QasmSimError::UnknownError(err)
225    }
226}
227
228impl<'src> From<SrcAndErr<'src, ParseError>> for QasmSimError<'src> {
229    fn from(src_and_err: SrcAndErr<'src, ParseError>) -> Self {
230        let (input, error) = src_and_err;
231        match error {
232            ParseError::InvalidToken { location } => {
233                let (source, lineno, startpos, endpos) = extract_line(location.0, None, input);
234                QasmSimError::InvalidToken {
235                    source,
236                    lineno,
237                    startpos,
238                    endpos,
239                    token: None,
240                    expected: Vec::new(),
241                }
242            }
243            ParseError::UnrecognizedEOF { location, expected } => {
244                let (source, lineno, startpos, endpos) = extract_line(location.0, None, input);
245                QasmSimError::UnexpectedEOF {
246                    source,
247                    lineno,
248                    startpos,
249                    endpos,
250                    token: None,
251                    expected,
252                }
253            }
254            ParseError::UnrecognizedToken { token, expected } => {
255                let location = token.0;
256                let endlocation = token.2;
257                let (source, lineno, startpos, endpos) =
258                    extract_line(location.0, Some(endlocation.0), input);
259                QasmSimError::UnexpectedToken {
260                    source,
261                    lineno,
262                    startpos,
263                    endpos,
264                    token: Some(token.1),
265                    expected,
266                }
267            }
268            ParseError::ExtraToken { token } => {
269                let location = token.0;
270                let endlocation = token.2;
271                let (source, lineno, startpos, endpos) =
272                    extract_line(location.0, Some(endlocation.0), input);
273                QasmSimError::UnexpectedToken {
274                    source,
275                    lineno,
276                    startpos,
277                    endpos,
278                    token: Some(token.1),
279                    expected: Vec::new(),
280                }
281            }
282            ParseError::User { error: lexer_error } => {
283                let location = lexer_error.location;
284                let (source, lineno, startpos, endpos) = extract_line(location.0, None, input);
285                QasmSimError::InvalidToken {
286                    // XXX: Actually, this should be "InvalidInput"
287                    source,
288                    lineno,
289                    startpos,
290                    endpos,
291                    token: None,
292                    expected: Vec::new(),
293                }
294            }
295        }
296    }
297}
298
299impl<'src> From<SrcAndErr<'src, RuntimeError>> for QasmSimError<'src> {
300    fn from(source_and_error: SrcAndErr<'src, RuntimeError>) -> Self {
301        let (input, error) = source_and_error;
302        match error {
303            RuntimeError::Other => QasmSimError::UnknownError(format!("{:?}", error)),
304            RuntimeError::RegisterSizeMismatch {
305                location,
306                symbol_name,
307                sizes,
308            } => {
309                let (source, lineno, _, _) = extract_line(location.0, None, input);
310                QasmSimError::RegisterSizeMismatch {
311                    source,
312                    lineno,
313                    symbol_name,
314                    sizes,
315                }
316            }
317            RuntimeError::TypeMismatch {
318                location,
319                symbol_name,
320                expected,
321            } => {
322                let (source, lineno, _, _) = extract_line(location.0, None, input);
323                QasmSimError::TypeMismatch {
324                    source,
325                    lineno,
326                    symbol_name,
327                    expected,
328                }
329            }
330            RuntimeError::UndefinedGate {
331                location,
332                symbol_name,
333            } => {
334                let (source, lineno, _, _) = extract_line(location.0, None, input);
335                QasmSimError::UndefinedGate {
336                    source,
337                    lineno,
338                    symbol_name,
339                }
340            }
341            RuntimeError::WrongNumberOfParameters {
342                are_registers,
343                location,
344                symbol_name,
345                given,
346                expected,
347            } => {
348                let (source, lineno, _, _) = extract_line(location.0, None, input);
349                QasmSimError::WrongNumberOfParameters {
350                    are_registers,
351                    source,
352                    symbol_name,
353                    lineno,
354                    expected,
355                    given,
356                }
357            }
358            RuntimeError::SymbolNotFound {
359                location,
360                symbol_name,
361                expected,
362            } => {
363                let (source, lineno, _, _) = extract_line(location.0, None, input);
364                QasmSimError::SymbolNotFound {
365                    source,
366                    symbol_name,
367                    lineno,
368                    expected,
369                }
370            }
371            RuntimeError::IndexOutOfBounds {
372                location,
373                symbol_name,
374                index,
375                size,
376            } => {
377                let (source, lineno, _, _) = extract_line(location.0, None, input);
378                QasmSimError::IndexOutOfBounds {
379                    source,
380                    symbol_name,
381                    lineno,
382                    size,
383                    index,
384                }
385            }
386            RuntimeError::SemanticError(semantic_error) => match semantic_error {
387                SemanticError::RedefinitionError {
388                    symbol_name,
389                    location,
390                    previous_location,
391                } => {
392                    let (source, lineno, _, _) = extract_line(location.0, None, input);
393                    let (_, previous_lineno, _, _) = extract_line(previous_location.0, None, input);
394                    QasmSimError::RedefinitionError {
395                        source,
396                        symbol_name,
397                        lineno,
398                        previous_lineno,
399                    }
400                }
401            },
402        }
403    }
404}
405
406impl<'src> From<SrcAndErr<'src, LinkerError>> for QasmSimError<'src> {
407    fn from(source_and_error: SrcAndErr<'src, LinkerError>) -> Self {
408        let (input, error) = source_and_error;
409        match error {
410            LinkerError::LibraryNotFound { location, libpath } => {
411                let (source, lineno, _, _) = extract_line(location.0, None, input);
412                QasmSimError::LibraryNotFound {
413                    source,
414                    libpath,
415                    lineno,
416                }
417            }
418        }
419    }
420}
421
422fn extract_line(
423    offset: usize,
424    endoffset: Option<usize>,
425    doc: &str,
426) -> (&str, usize, usize, Option<usize>) {
427    assert!(
428        offset <= doc.len(),
429        "linestart={} must in the range 0..=doc.len()={}",
430        offset,
431        doc.len()
432    );
433
434    let mut linecount = 1;
435    let mut start = 0;
436    let mut end = start + 1;
437    for (idx, character) in doc.chars().enumerate() {
438        if idx < offset && character == '\n' {
439            start = idx + 1;
440            linecount += 1;
441        }
442        if idx >= offset {
443            end = idx + 1;
444            if character == '\n' {
445                break;
446            }
447        }
448    }
449
450    if end < start {
451        end = doc.len();
452    }
453
454    let startpos = offset - start;
455    let endpos = endoffset.map(|endoffset| endoffset - start);
456
457    (&doc[start..end], linecount, startpos, endpos)
458}
459
460#[cfg(test)]
461mod test_into_doc_coords {
462    use indoc::indoc;
463
464    use super::extract_line;
465
466    macro_rules! test_get_line_src {
467    ($source:expr, $( $name:ident: $offset:expr, $offsetend:expr => $expected:expr ),*) => {
468      $(
469        #[test]
470        fn $name() {
471          assert_eq!(extract_line($offset, $offsetend, &$source), $expected);
472        }
473      )*
474    };
475  }
476
477    test_get_line_src!(indoc!("
478      line 1
479      line 2
480      line 3"
481      ),
482      test_beginning_of_source: 0, None => ("line 1\n", 1, 0, None),
483      test_middle_of_source: 7, None => ("line 2\n", 2, 0, None),
484      test_last_character: 20, None => ("line 3", 3, 6, None)
485    );
486}