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#[non_exhaustive]
95#[derive(Debug, Default, thiserror::Error, Diagnostic)]
96#[repr(u8)]
97pub enum ParsingError {
98    #[default]
99    #[error("parsing failed due to unexpected input")]
100    #[diagnostic()]
101    Failed = 0,
102    #[error("expected input to be valid utf8, but invalid byte sequences were found")]
103    #[diagnostic()]
104    InvalidUtf8 {
105        #[label("invalid byte sequence starts here")]
106        span: SourceSpan,
107    },
108    #[error(
109        "expected input to be valid utf8, but end-of-file was reached before final codepoint was read"
110    )]
111    #[diagnostic()]
112    IncompleteUtf8 {
113        #[label("the codepoint starting here is incomplete")]
114        span: SourceSpan,
115    },
116    #[error("invalid syntax")]
117    #[diagnostic()]
118    InvalidToken {
119        #[label("occurs here")]
120        span: SourceSpan,
121    },
122    #[error("invalid syntax: {message}")]
123    #[diagnostic()]
124    InvalidSyntax {
125        #[label("{message}")]
126        span: SourceSpan,
127        message: String,
128    },
129    #[error("invalid syntax")]
130    #[diagnostic(help("expected {}", expected.as_slice().join(", or ")))]
131    UnrecognizedToken {
132        #[label("found a {token} here")]
133        span: SourceSpan,
134        token: String,
135        expected: Vec<String>,
136    },
137    #[error("unexpected trailing tokens")]
138    #[diagnostic()]
139    ExtraToken {
140        #[label("{token} was found here, but was not expected")]
141        span: SourceSpan,
142        token: String,
143    },
144    #[error("unexpected end of file")]
145    #[diagnostic(help("expected {}", expected.as_slice().join(", or ")))]
146    UnrecognizedEof {
147        #[label("reached end of file here")]
148        span: SourceSpan,
149        expected: Vec<String>,
150    },
151    #[error("{error}")]
152    #[diagnostic(help(
153        "bare identifiers must be lowercase alphanumeric with '_', quoted identifiers can include any graphical character"
154    ))]
155    InvalidIdentifier {
156        #[source]
157        #[diagnostic(source)]
158        error: crate::ast::IdentError,
159        #[label]
160        span: SourceSpan,
161    },
162    #[error("unclosed quoted identifier")]
163    #[diagnostic()]
164    UnclosedQuote {
165        #[label("no match for quotation mark starting here")]
166        start: SourceSpan,
167    },
168    #[error("too many instructions in a single code block")]
169    #[diagnostic()]
170    CodeBlockTooBig {
171        #[label]
172        span: SourceSpan,
173    },
174    #[error("control-flow nesting depth exceeded")]
175    #[diagnostic(help("control-flow nesting exceeded the maximum depth of {max_depth}"))]
176    ControlFlowNestingDepthExceeded {
177        #[label("control-flow nesting exceeded the configured depth limit here")]
178        span: SourceSpan,
179        max_depth: usize,
180    },
181    #[error("constant expression nesting depth exceeded")]
182    #[diagnostic(help("constant expression nesting exceeded the maximum depth of {max_depth}"))]
183    ConstantExpressionNestingDepthExceeded {
184        #[label("constant expression nesting exceeded the configured depth limit here")]
185        span: SourceSpan,
186        max_depth: usize,
187    },
188    #[error("type expression nesting depth exceeded")]
189    #[diagnostic(help("type expression nesting exceeded the maximum depth of {max_depth}"))]
190    TypeExpressionNestingDepthExceeded {
191        #[label("type expression nesting exceeded the configured depth limit here")]
192        span: SourceSpan,
193        max_depth: usize,
194    },
195    #[error("invalid constant expression: division by zero")]
196    DivisionByZero {
197        #[label]
198        span: SourceSpan,
199    },
200    #[error("doc comment is too large")]
201    #[diagnostic(help("make sure it is less than u16::MAX bytes in length"))]
202    DocsTooLarge {
203        #[label]
204        span: SourceSpan,
205    },
206    #[error("invalid literal: {}", kind)]
207    #[diagnostic()]
208    InvalidLiteral {
209        #[label]
210        span: SourceSpan,
211        kind: LiteralErrorKind,
212    },
213    #[error("invalid literal: {}", kind)]
214    #[diagnostic()]
215    InvalidHexLiteral {
216        #[label]
217        span: SourceSpan,
218        kind: HexErrorKind,
219    },
220    #[error("invalid literal: {}", kind)]
221    #[diagnostic()]
222    InvalidBinaryLiteral {
223        #[label]
224        span: SourceSpan,
225        kind: BinErrorKind,
226    },
227    #[error("invalid MAST root literal")]
228    InvalidMastRoot {
229        #[label]
230        span: SourceSpan,
231    },
232    #[error("invalid library path: {}", message)]
233    InvalidLibraryPath {
234        #[label]
235        span: SourceSpan,
236        message: String,
237    },
238    #[error("invalid immediate: value must be in the range {}..{} (exclusive)", range.start, range.end)]
239    ImmediateOutOfRange {
240        #[label]
241        span: SourceSpan,
242        range: Range<usize>,
243    },
244    #[error("too many procedures in this module")]
245    #[diagnostic()]
246    ModuleTooLarge {
247        #[label]
248        span: SourceSpan,
249    },
250    #[error("too many re-exported procedures in this module")]
251    #[diagnostic()]
252    ModuleTooManyReexports {
253        #[label]
254        span: SourceSpan,
255    },
256    #[error(
257        "too many operands for `push`: tried to push {} elements, but only 16 can be pushed at one time",
258        count
259    )]
260    #[diagnostic()]
261    PushOverflow {
262        #[label]
263        span: SourceSpan,
264        count: usize,
265    },
266    #[error("expected a fully-qualified module path, e.g. `std::u64`")]
267    UnqualifiedImport {
268        #[label]
269        span: SourceSpan,
270    },
271    #[error(
272        "source-level digest re-exports are not supported; re-export a named item with `pub use {{item}} from module`"
273    )]
274    UnnamedReexportOfMastRoot {
275        #[label]
276        span: SourceSpan,
277    },
278    #[error("conflicting attributes for procedure definition")]
279    #[diagnostic()]
280    AttributeConflict {
281        #[label(
282            "conflict occurs because an attribute with the same name has already been defined"
283        )]
284        span: SourceSpan,
285        #[label("previously defined here")]
286        prev: SourceSpan,
287    },
288    #[error("conflicting key-value attributes for procedure definition")]
289    #[diagnostic()]
290    AttributeKeyValueConflict {
291        #[label(
292            "conflict occurs because a key with the same name has already been set in a previous declaration"
293        )]
294        span: SourceSpan,
295        #[label("previously defined here")]
296        prev: SourceSpan,
297    },
298    #[error("invalid Advice Map key")]
299    #[diagnostic()]
300    InvalidAdvMapKey {
301        #[label(
302            "an Advice Map key must be a word, either in 64-character hex format or in array-like format `[f0,f1,f2,f3]`"
303        )]
304        span: SourceSpan,
305    },
306    #[error("invalid slice constant")]
307    #[diagnostic()]
308    InvalidSliceConstant {
309        #[label("slices are only supported over word-sized constants")]
310        span: SourceSpan,
311    },
312    #[error("invalid slice: expected valid range")]
313    #[diagnostic()]
314    InvalidRange {
315        #[label("range used for the word constant slice is malformed: `{range:?}`")]
316        span: SourceSpan,
317        range: Range<usize>,
318    },
319    #[error("invalid slice: expected non-empty range")]
320    #[diagnostic()]
321    EmptySlice {
322        #[label("range used for the word constant slice is empty: `{range:?}`")]
323        span: SourceSpan,
324        range: Range<usize>,
325    },
326    #[error("unrecognized calling convention")]
327    #[diagnostic(help("expected one of: 'fast', 'C', 'wasm', 'canon-lift', or 'canon-lower'"))]
328    UnrecognizedCallConv {
329        #[label]
330        span: SourceSpan,
331    },
332    #[error("invalid struct annotation")]
333    #[diagnostic(help("expected one of: '@packed', '@packed(N)', '@transparent', or '@align(N)'"))]
334    InvalidStructAnnotation {
335        #[label]
336        span: SourceSpan,
337    },
338    #[error("invalid struct representation")]
339    #[diagnostic()]
340    InvalidStructRepr {
341        #[label("{message}")]
342        span: SourceSpan,
343        message: String,
344    },
345    #[error("deprecated instruction: `{instruction}` has been removed")]
346    #[diagnostic(help("use `{}` instead", replacement))]
347    DeprecatedInstruction {
348        #[label("this instruction is no longer supported")]
349        span: SourceSpan,
350        instruction: String,
351        replacement: String,
352    },
353    #[error("invalid procedure @locals attribute")]
354    #[diagnostic()]
355    InvalidLocalsAttr {
356        #[label("{message}")]
357        span: SourceSpan,
358        message: String,
359    },
360    #[error("invalid padding value for the `adv.push_mapvaln` instruction: {padding}")]
361    #[diagnostic(help("valid padding values are 0, 4, and 8"))]
362    InvalidPadValue {
363        #[label]
364        span: SourceSpan,
365        padding: u8,
366    },
367    #[error(
368        "invalid submodule declaration '{name}': could not find module sources at '{directory}/{basename}.masm' or '{directory}/{basename}/mod.masm'"
369    )]
370    UndefinedSubmodule {
371        name: crate::ast::Ident,
372        basename: alloc::boxed::Box<str>,
373        directory: miden_debug_types::Uri,
374        #[label]
375        span: SourceSpan,
376        #[source_code]
377        source_file: Option<Arc<miden_debug_types::SourceFile>>,
378    },
379    #[error(
380        "invalid submodule declaration '{name}': submodules must not have the same name as their parent"
381    )]
382    #[diagnostic(help("occurred while parsing {parent_module_uri}"))]
383    SelfReferentialSubmodule {
384        name: crate::ast::Ident,
385        parent_module_uri: miden_debug_types::Uri,
386        #[label(
387            "module source resolution rules require this declaration to resolve to the current source file"
388        )]
389        span: SourceSpan,
390        #[source_code]
391        source_file: Option<Arc<miden_debug_types::SourceFile>>,
392    },
393    #[error(
394        "conflicting submodule paths detected: '{name}' can be parsed from either '{first}' and '{second}', but not both"
395    )]
396    AmbiguousSubmoduleLocation {
397        name: crate::ast::Ident,
398        first: miden_debug_types::Uri,
399        second: miden_debug_types::Uri,
400        #[label]
401        span: SourceSpan,
402        #[source_code]
403        source_file: Option<Arc<miden_debug_types::SourceFile>>,
404    },
405    #[error(
406        "invalid submodule declaration '{name}': module source '{module_uri}' is already reachable through another submodule declaration"
407    )]
408    #[diagnostic(help("each module source file can only be owned by one module in a module tree"))]
409    DuplicateSubmoduleSource {
410        name: crate::ast::Ident,
411        module_uri: miden_debug_types::Uri,
412        #[label("this declaration resolves to an already visited module source")]
413        span: SourceSpan,
414        #[source_code]
415        source_file: Option<Arc<miden_debug_types::SourceFile>>,
416    },
417    #[error("conflicting attributes for procedure definition")]
418    #[diagnostic()]
419    CallConvAttributeConflict {
420        #[label(
421            "conflict occurs because @callconv conflicts with convention implied by other attribute"
422        )]
423        cc_span: SourceSpan,
424        #[label("this attribute implies @callconv(\"component-model\")")]
425        attr_span: SourceSpan,
426    },
427    #[error("conflicting attributes for procedure definition")]
428    #[diagnostic()]
429    ConflictingProtocolAbiAttribute {
430        #[label(
431            "this attribute specifies the protocol ABI of this procedure, but a different ABI was previously specified"
432        )]
433        span: SourceSpan,
434        #[label("this attribute already specifies the protocol ABI for this procedure")]
435        prev: SourceSpan,
436    },
437}
438
439impl ParsingError {
440    fn tag(&self) -> u8 {
441        // SAFETY: This is safe because we have given this enum a
442        // primitive representation with #[repr(u8)], with the first
443        // field of the underlying union-of-structs the discriminant
444        //
445        // See the section on "accessing the numeric value of the discriminant"
446        // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html
447        unsafe { *<*const _>::from(self).cast::<u8>() }
448    }
449}
450
451impl Eq for ParsingError {}
452
453impl PartialEq for ParsingError {
454    fn eq(&self, other: &Self) -> bool {
455        match (self, other) {
456            (Self::Failed, Self::Failed) => true,
457            (Self::InvalidSyntax { message: l, .. }, Self::InvalidSyntax { message: r, .. }) => {
458                l == r
459            },
460            (Self::InvalidLiteral { kind: l, .. }, Self::InvalidLiteral { kind: r, .. }) => l == r,
461            (Self::InvalidHexLiteral { kind: l, .. }, Self::InvalidHexLiteral { kind: r, .. }) => {
462                l == r
463            },
464            (
465                Self::InvalidLibraryPath { message: l, .. },
466                Self::InvalidLibraryPath { message: r, .. },
467            ) => l == r,
468            (
469                Self::ImmediateOutOfRange { range: l, .. },
470                Self::ImmediateOutOfRange { range: r, .. },
471            ) => l == r,
472            (Self::PushOverflow { count: l, .. }, Self::PushOverflow { count: r, .. }) => l == r,
473            (
474                Self::UnrecognizedToken { token: ltok, expected: lexpect, .. },
475                Self::UnrecognizedToken { token: rtok, expected: rexpect, .. },
476            ) => ltok == rtok && lexpect == rexpect,
477            (Self::ExtraToken { token: ltok, .. }, Self::ExtraToken { token: rtok, .. }) => {
478                ltok == rtok
479            },
480            (
481                Self::UnrecognizedEof { expected: lexpect, .. },
482                Self::UnrecognizedEof { expected: rexpect, .. },
483            ) => lexpect == rexpect,
484            (x, y) => x.tag() == y.tag(),
485        }
486    }
487}