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(
267            "conflict occurs because an attribute with the same name has already been defined"
268        )]
269        span: SourceSpan,
270        #[label("previously defined here")]
271        prev: SourceSpan,
272    },
273    #[error("conflicting key-value attributes for procedure definition")]
274    #[diagnostic()]
275    AttributeKeyValueConflict {
276        #[label(
277            "conflict occurs because a key with the same name has already been set in a previous declaration"
278        )]
279        span: SourceSpan,
280        #[label("previously defined here")]
281        prev: SourceSpan,
282    },
283    #[error("invalid Advice Map key")]
284    #[diagnostic()]
285    InvalidAdvMapKey {
286        #[label(
287            "an Advice Map key must be a word, either in 64-character hex format or in array-like format `[f0,f1,f2,f3]`"
288        )]
289        span: SourceSpan,
290    },
291    #[error("invalid slice constant")]
292    #[diagnostic()]
293    InvalidSliceConstant {
294        #[label("slices are only supported over word-sized constants")]
295        span: SourceSpan,
296    },
297    #[error("invalid slice: expected valid range")]
298    #[diagnostic()]
299    InvalidRange {
300        #[label("range used for the word constant slice is malformed: `{range:?}`")]
301        span: SourceSpan,
302        range: Range<usize>,
303    },
304    #[error("invalid slice: expected non-empty range")]
305    #[diagnostic()]
306    EmptySlice {
307        #[label("range used for the word constant slice is empty: `{range:?}`")]
308        span: SourceSpan,
309        range: Range<usize>,
310    },
311    #[error("unrecognized calling convention")]
312    #[diagnostic(help("expected one of: 'fast', 'C', 'wasm', 'canon-lift', or 'canon-lower'"))]
313    UnrecognizedCallConv {
314        #[label]
315        span: SourceSpan,
316    },
317    #[error("invalid struct annotation")]
318    #[diagnostic(help("expected one of: '@packed', '@packed(N)', '@transparent', or '@align(N)'"))]
319    InvalidStructAnnotation {
320        #[label]
321        span: SourceSpan,
322    },
323    #[error("invalid struct representation")]
324    #[diagnostic()]
325    InvalidStructRepr {
326        #[label("{message}")]
327        span: SourceSpan,
328        message: String,
329    },
330    #[error("deprecated instruction: `{instruction}` has been removed")]
331    #[diagnostic(help("use `{}` instead", replacement))]
332    DeprecatedInstruction {
333        #[label("this instruction is no longer supported")]
334        span: SourceSpan,
335        instruction: String,
336        replacement: String,
337    },
338    #[error("invalid procedure @locals attribute")]
339    #[diagnostic()]
340    InvalidLocalsAttr {
341        #[label("{message}")]
342        span: SourceSpan,
343        message: String,
344    },
345    #[error("invalid padding value for the `adv.push_mapvaln` instruction: {padding}")]
346    #[diagnostic(help("valid padding values are 0, 4, and 8"))]
347    InvalidPadValue {
348        #[label]
349        span: SourceSpan,
350        padding: u8,
351    },
352    #[error(
353        "invalid submodule declaration '{name}': could not find module sources at '{directory}/{basename}.masm' or '{directory}/{basename}/mod.masm'"
354    )]
355    UndefinedSubmodule {
356        name: crate::ast::Ident,
357        basename: alloc::boxed::Box<str>,
358        directory: miden_debug_types::Uri,
359        #[label]
360        span: SourceSpan,
361        #[source_code]
362        source_file: Option<Arc<miden_debug_types::SourceFile>>,
363    },
364    #[error(
365        "invalid submodule declaration '{name}': submodules must not have the same name as their parent"
366    )]
367    #[diagnostic(help("occurred while parsing {parent_module_uri}"))]
368    SelfReferentialSubmodule {
369        name: crate::ast::Ident,
370        parent_module_uri: miden_debug_types::Uri,
371        #[label(
372            "module source resolution rules require this declaration to resolve to the current source file"
373        )]
374        span: SourceSpan,
375        #[source_code]
376        source_file: Option<Arc<miden_debug_types::SourceFile>>,
377    },
378    #[error(
379        "conflicting submodule paths detected: '{name}' can be parsed from either '{first}' and '{second}', but not both"
380    )]
381    AmbiguousSubmoduleLocation {
382        name: crate::ast::Ident,
383        first: miden_debug_types::Uri,
384        second: miden_debug_types::Uri,
385        #[label]
386        span: SourceSpan,
387        #[source_code]
388        source_file: Option<Arc<miden_debug_types::SourceFile>>,
389    },
390    #[error(
391        "invalid submodule declaration '{name}': module source '{module_uri}' is already reachable through another submodule declaration"
392    )]
393    #[diagnostic(help("each module source file can only be owned by one module in a module tree"))]
394    DuplicateSubmoduleSource {
395        name: crate::ast::Ident,
396        module_uri: miden_debug_types::Uri,
397        #[label("this declaration resolves to an already visited module source")]
398        span: SourceSpan,
399        #[source_code]
400        source_file: Option<Arc<miden_debug_types::SourceFile>>,
401    },
402}
403
404impl ParsingError {
405    fn tag(&self) -> u8 {
406        // SAFETY: This is safe because we have given this enum a
407        // primitive representation with #[repr(u8)], with the first
408        // field of the underlying union-of-structs the discriminant
409        //
410        // See the section on "accessing the numeric value of the discriminant"
411        // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html
412        unsafe { *<*const _>::from(self).cast::<u8>() }
413    }
414}
415
416impl Eq for ParsingError {}
417
418impl PartialEq for ParsingError {
419    fn eq(&self, other: &Self) -> bool {
420        match (self, other) {
421            (Self::Failed, Self::Failed) => true,
422            (Self::InvalidSyntax { message: l, .. }, Self::InvalidSyntax { message: r, .. }) => {
423                l == r
424            },
425            (Self::InvalidLiteral { kind: l, .. }, Self::InvalidLiteral { kind: r, .. }) => l == r,
426            (Self::InvalidHexLiteral { kind: l, .. }, Self::InvalidHexLiteral { kind: r, .. }) => {
427                l == r
428            },
429            (
430                Self::InvalidLibraryPath { message: l, .. },
431                Self::InvalidLibraryPath { message: r, .. },
432            ) => l == r,
433            (
434                Self::ImmediateOutOfRange { range: l, .. },
435                Self::ImmediateOutOfRange { range: r, .. },
436            ) => l == r,
437            (Self::PushOverflow { count: l, .. }, Self::PushOverflow { count: r, .. }) => l == r,
438            (
439                Self::UnrecognizedToken { token: ltok, expected: lexpect, .. },
440                Self::UnrecognizedToken { token: rtok, expected: rexpect, .. },
441            ) => ltok == rtok && lexpect == rexpect,
442            (Self::ExtraToken { token: ltok, .. }, Self::ExtraToken { token: rtok, .. }) => {
443                ltok == rtok
444            },
445            (
446                Self::UnrecognizedEof { expected: lexpect, .. },
447                Self::UnrecognizedEof { expected: rexpect, .. },
448            ) => lexpect == rexpect,
449            (x, y) => x.tag() == y.tag(),
450        }
451    }
452}