Skip to main content

miden_assembly_syntax/parser/
error.rs

1// Allow unused assignments - required by miette::Diagnostic derive macro
2#![allow(unused_assignments)]
3
4use alloc::{string::String, sync::Arc, vec::Vec};
5use core::{fmt, ops::Range};
6
7use miden_debug_types::SourceSpan;
8use miden_utils_diagnostics::{Diagnostic, miette};
9
10// LITERAL ERROR KIND
11// ================================================================================================
12
13#[derive(Debug, Copy, Clone, PartialEq, Eq)]
14pub enum LiteralErrorKind {
15    /// The input was empty
16    Empty,
17    /// The input contained an invalid digit
18    InvalidDigit,
19    /// The value overflows `u32::MAX`
20    U32Overflow,
21    /// The value overflows `Felt::ORDER_U64`
22    FeltOverflow,
23    /// The value was expected to be a value < 63
24    InvalidBitSize,
25}
26
27impl fmt::Display for LiteralErrorKind {
28    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29        match self {
30            Self::Empty => f.write_str("input was empty"),
31            Self::InvalidDigit => f.write_str("invalid digit"),
32            Self::U32Overflow => f.write_str("value overflowed the u32 range"),
33            Self::FeltOverflow => f.write_str("value overflowed the field modulus"),
34            Self::InvalidBitSize => {
35                f.write_str("expected value to be a valid bit size, e.g. 0..63")
36            },
37        }
38    }
39}
40
41// HEX ERROR KIND
42// ================================================================================================
43
44#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45pub enum HexErrorKind {
46    /// Expected two hex digits for every byte, but had fewer than that
47    MissingDigits,
48    /// Valid hex-encoded integers are expected to come in sizes of 8, 16, or 64 digits,
49    /// but the input consisted of an invalid number of digits.
50    Invalid,
51    /// Occurs when a hex-encoded value overflows `Felt::ORDER_U64`, the maximum integral value
52    Overflow,
53    /// Occurs when the hex-encoded value is > 64 digits
54    TooLong,
55}
56
57impl fmt::Display for HexErrorKind {
58    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59        match self {
60            Self::MissingDigits => {
61                f.write_str("expected number of hex digits to be a multiple of 2")
62            },
63            Self::Invalid => f.write_str("expected 2, 4, 8, 16, or 64 hex digits"),
64            Self::Overflow => f.write_str("value overflowed the field modulus"),
65            Self::TooLong => f.write_str(
66                "value has too many digits, long hex strings must contain exactly 64 digits",
67            ),
68        }
69    }
70}
71
72// BINARY ERROR KIND
73// ================================================================================================
74
75#[derive(Debug, Copy, Clone, PartialEq, Eq)]
76pub enum BinErrorKind {
77    /// Occurs when the bin-encoded value is > 32 digits
78    TooLong,
79}
80
81impl fmt::Display for BinErrorKind {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        match self {
84            Self::TooLong => f.write_str(
85                "value has too many digits, binary string can contain no more than 32 digits",
86            ),
87        }
88    }
89}
90
91// PARSING ERROR
92// ================================================================================================
93
94#[derive(Debug, Default, thiserror::Error, Diagnostic)]
95#[repr(u8)]
96pub enum ParsingError {
97    #[default]
98    #[error("parsing failed due to unexpected input")]
99    #[diagnostic()]
100    Failed = 0,
101    #[error("expected input to be valid utf8, but invalid byte sequences were found")]
102    #[diagnostic()]
103    InvalidUtf8 {
104        #[label("invalid byte sequence starts here")]
105        span: SourceSpan,
106    },
107    #[error(
108        "expected input to be valid utf8, but end-of-file was reached before final codepoint was read"
109    )]
110    #[diagnostic()]
111    IncompleteUtf8 {
112        #[label("the codepoint starting here is incomplete")]
113        span: SourceSpan,
114    },
115    #[error("invalid syntax")]
116    #[diagnostic()]
117    InvalidToken {
118        #[label("occurs here")]
119        span: SourceSpan,
120    },
121    #[error("invalid syntax: {message}")]
122    #[diagnostic()]
123    InvalidSyntax {
124        #[label("{message}")]
125        span: SourceSpan,
126        message: String,
127    },
128    #[error("invalid syntax")]
129    #[diagnostic(help("expected {}", expected.as_slice().join(", or ")))]
130    UnrecognizedToken {
131        #[label("found a {token} here")]
132        span: SourceSpan,
133        token: String,
134        expected: Vec<String>,
135    },
136    #[error("unexpected trailing tokens")]
137    #[diagnostic()]
138    ExtraToken {
139        #[label("{token} was found here, but was not expected")]
140        span: SourceSpan,
141        token: String,
142    },
143    #[error("unexpected end of file")]
144    #[diagnostic(help("expected {}", expected.as_slice().join(", or ")))]
145    UnrecognizedEof {
146        #[label("reached end of file here")]
147        span: SourceSpan,
148        expected: Vec<String>,
149    },
150    #[error("{error}")]
151    #[diagnostic(help(
152        "bare identifiers must be lowercase alphanumeric with '_', quoted identifiers can include any graphical character"
153    ))]
154    InvalidIdentifier {
155        #[source]
156        #[diagnostic(source)]
157        error: crate::ast::IdentError,
158        #[label]
159        span: SourceSpan,
160    },
161    #[error("unclosed quoted identifier")]
162    #[diagnostic()]
163    UnclosedQuote {
164        #[label("no match for quotation mark starting here")]
165        start: SourceSpan,
166    },
167    #[error("too many instructions in a single code block")]
168    #[diagnostic()]
169    CodeBlockTooBig {
170        #[label]
171        span: SourceSpan,
172    },
173    #[error("control-flow nesting depth exceeded")]
174    #[diagnostic(help("control-flow nesting exceeded the maximum depth of {max_depth}"))]
175    ControlFlowNestingDepthExceeded {
176        #[label("control-flow nesting exceeded the configured depth limit here")]
177        span: SourceSpan,
178        max_depth: usize,
179    },
180    #[error("invalid constant expression: division by zero")]
181    DivisionByZero {
182        #[label]
183        span: SourceSpan,
184    },
185    #[error("doc comment is too large")]
186    #[diagnostic(help("make sure it is less than u16::MAX bytes in length"))]
187    DocsTooLarge {
188        #[label]
189        span: SourceSpan,
190    },
191    #[error("invalid literal: {}", kind)]
192    #[diagnostic()]
193    InvalidLiteral {
194        #[label]
195        span: SourceSpan,
196        kind: LiteralErrorKind,
197    },
198    #[error("invalid literal: {}", kind)]
199    #[diagnostic()]
200    InvalidHexLiteral {
201        #[label]
202        span: SourceSpan,
203        kind: HexErrorKind,
204    },
205    #[error("invalid literal: {}", kind)]
206    #[diagnostic()]
207    InvalidBinaryLiteral {
208        #[label]
209        span: SourceSpan,
210        kind: BinErrorKind,
211    },
212    #[error("invalid MAST root literal")]
213    InvalidMastRoot {
214        #[label]
215        span: SourceSpan,
216    },
217    #[error("invalid library path: {}", message)]
218    InvalidLibraryPath {
219        #[label]
220        span: SourceSpan,
221        message: String,
222    },
223    #[error("invalid immediate: value must be in the range {}..{} (exclusive)", range.start, range.end)]
224    ImmediateOutOfRange {
225        #[label]
226        span: SourceSpan,
227        range: Range<usize>,
228    },
229    #[error("too many procedures in this module")]
230    #[diagnostic()]
231    ModuleTooLarge {
232        #[label]
233        span: SourceSpan,
234    },
235    #[error("too many re-exported procedures in this module")]
236    #[diagnostic()]
237    ModuleTooManyReexports {
238        #[label]
239        span: SourceSpan,
240    },
241    #[error(
242        "too many operands for `push`: tried to push {} elements, but only 16 can be pushed at one time",
243        count
244    )]
245    #[diagnostic()]
246    PushOverflow {
247        #[label]
248        span: SourceSpan,
249        count: usize,
250    },
251    #[error("expected a fully-qualified module path, e.g. `std::u64`")]
252    UnqualifiedImport {
253        #[label]
254        span: SourceSpan,
255    },
256    #[error(
257        "source-level digest re-exports are not supported; re-export a named item with `pub use {{item}} from module`"
258    )]
259    UnnamedReexportOfMastRoot {
260        #[label]
261        span: SourceSpan,
262    },
263    #[error("conflicting attributes for procedure definition")]
264    #[diagnostic()]
265    AttributeConflict {
266        #[label("this attribute conflicts with another attribute")]
267        span: SourceSpan,
268        #[label("conflicting attribute here")]
269        prev: SourceSpan,
270    },
271    #[error("conflicting key-value attributes for procedure definition")]
272    #[diagnostic()]
273    AttributeKeyValueConflict {
274        #[label(
275            "conflict occurs because a key with the same name has already been set in a previous declaration"
276        )]
277        span: SourceSpan,
278        #[label("previously defined here")]
279        prev: SourceSpan,
280    },
281    #[error("invalid Advice Map key")]
282    #[diagnostic()]
283    InvalidAdvMapKey {
284        #[label(
285            "an Advice Map key must be a word, either in 64-character hex format or in array-like format `[f0,f1,f2,f3]`"
286        )]
287        span: SourceSpan,
288    },
289    #[error("invalid slice constant")]
290    #[diagnostic()]
291    InvalidSliceConstant {
292        #[label("slices are only supported over word-sized constants")]
293        span: SourceSpan,
294    },
295    #[error("invalid slice: expected valid range")]
296    #[diagnostic()]
297    InvalidRange {
298        #[label("range used for the word constant slice is malformed: `{range:?}`")]
299        span: SourceSpan,
300        range: Range<usize>,
301    },
302    #[error("invalid slice: expected non-empty range")]
303    #[diagnostic()]
304    EmptySlice {
305        #[label("range used for the word constant slice is empty: `{range:?}`")]
306        span: SourceSpan,
307        range: Range<usize>,
308    },
309    #[error("unrecognized calling convention")]
310    #[diagnostic(help("expected one of: 'fast', 'C', 'wasm', 'canon-lift', or 'canon-lower'"))]
311    UnrecognizedCallConv {
312        #[label]
313        span: SourceSpan,
314    },
315    #[error("invalid struct annotation")]
316    #[diagnostic(help("expected one of: '@packed', '@packed(N)', '@transparent', or '@align(N)'"))]
317    InvalidStructAnnotation {
318        #[label]
319        span: SourceSpan,
320    },
321    #[error("invalid struct representation")]
322    #[diagnostic()]
323    InvalidStructRepr {
324        #[label("{message}")]
325        span: SourceSpan,
326        message: String,
327    },
328    #[error("deprecated instruction: `{instruction}` has been removed")]
329    #[diagnostic(help("use `{}` instead", replacement))]
330    DeprecatedInstruction {
331        #[label("this instruction is no longer supported")]
332        span: SourceSpan,
333        instruction: String,
334        replacement: String,
335    },
336    #[error("invalid procedure @locals attribute")]
337    #[diagnostic()]
338    InvalidLocalsAttr {
339        #[label("{message}")]
340        span: SourceSpan,
341        message: String,
342    },
343    #[error("invalid padding value for the `adv.push_mapvaln` instruction: {padding}")]
344    #[diagnostic(help("valid padding values are 0, 4, and 8"))]
345    InvalidPadValue {
346        #[label]
347        span: SourceSpan,
348        padding: u8,
349    },
350    #[error(
351        "invalid submodule declaration '{name}': could not find module sources at '{directory}/{basename}.masm' or '{directory}/{basename}/mod.masm'"
352    )]
353    UndefinedSubmodule {
354        name: crate::ast::Ident,
355        basename: alloc::boxed::Box<str>,
356        directory: miden_debug_types::Uri,
357        #[label]
358        span: SourceSpan,
359        #[source_code]
360        source_file: Option<Arc<miden_debug_types::SourceFile>>,
361    },
362    #[error(
363        "invalid submodule declaration '{name}': submodules must not have the same name as their parent"
364    )]
365    #[diagnostic(help("occurred while parsing {parent_module_uri}"))]
366    SelfReferentialSubmodule {
367        name: crate::ast::Ident,
368        parent_module_uri: miden_debug_types::Uri,
369        #[label(
370            "module source resolution rules require this declaration to resolve to the current source file"
371        )]
372        span: SourceSpan,
373        #[source_code]
374        source_file: Option<Arc<miden_debug_types::SourceFile>>,
375    },
376    #[error(
377        "conflicting submodule paths detected: '{name}' can be parsed from either '{first}' and '{second}', but not both"
378    )]
379    AmbiguousSubmoduleLocation {
380        name: crate::ast::Ident,
381        first: miden_debug_types::Uri,
382        second: miden_debug_types::Uri,
383        #[label]
384        span: SourceSpan,
385        #[source_code]
386        source_file: Option<Arc<miden_debug_types::SourceFile>>,
387    },
388    #[error(
389        "invalid submodule declaration '{name}': module source '{module_uri}' is already reachable through another submodule declaration"
390    )]
391    #[diagnostic(help("each module source file can only be owned by one module in a module tree"))]
392    DuplicateSubmoduleSource {
393        name: crate::ast::Ident,
394        module_uri: miden_debug_types::Uri,
395        #[label("this declaration resolves to an already visited module source")]
396        span: SourceSpan,
397        #[source_code]
398        source_file: Option<Arc<miden_debug_types::SourceFile>>,
399    },
400}
401
402impl ParsingError {
403    fn tag(&self) -> u8 {
404        // SAFETY: This is safe because we have given this enum a
405        // primitive representation with #[repr(u8)], with the first
406        // field of the underlying union-of-structs the discriminant
407        //
408        // See the section on "accessing the numeric value of the discriminant"
409        // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html
410        unsafe { *<*const _>::from(self).cast::<u8>() }
411    }
412}
413
414impl Eq for ParsingError {}
415
416impl PartialEq for ParsingError {
417    fn eq(&self, other: &Self) -> bool {
418        match (self, other) {
419            (Self::Failed, Self::Failed) => true,
420            (Self::InvalidSyntax { message: l, .. }, Self::InvalidSyntax { message: r, .. }) => {
421                l == r
422            },
423            (Self::InvalidLiteral { kind: l, .. }, Self::InvalidLiteral { kind: r, .. }) => l == r,
424            (Self::InvalidHexLiteral { kind: l, .. }, Self::InvalidHexLiteral { kind: r, .. }) => {
425                l == r
426            },
427            (
428                Self::InvalidLibraryPath { message: l, .. },
429                Self::InvalidLibraryPath { message: r, .. },
430            ) => l == r,
431            (
432                Self::ImmediateOutOfRange { range: l, .. },
433                Self::ImmediateOutOfRange { range: r, .. },
434            ) => l == r,
435            (Self::PushOverflow { count: l, .. }, Self::PushOverflow { count: r, .. }) => l == r,
436            (
437                Self::UnrecognizedToken { token: ltok, expected: lexpect, .. },
438                Self::UnrecognizedToken { token: rtok, expected: rexpect, .. },
439            ) => ltok == rtok && lexpect == rexpect,
440            (Self::ExtraToken { token: ltok, .. }, Self::ExtraToken { token: rtok, .. }) => {
441                ltok == rtok
442            },
443            (
444                Self::UnrecognizedEof { expected: lexpect, .. },
445                Self::UnrecognizedEof { expected: rexpect, .. },
446            ) => lexpect == rexpect,
447            (x, y) => x.tag() == y.tag(),
448        }
449    }
450}