#![allow(unused_assignments)]
use alloc::{string::String, sync::Arc, vec::Vec};
use core::{fmt, ops::Range};
use miden_debug_types::{SourceId, SourceSpan};
use miden_utils_diagnostics::{Diagnostic, miette};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LiteralErrorKind {
Empty,
InvalidDigit,
U32Overflow,
FeltOverflow,
InvalidBitSize,
}
impl fmt::Display for LiteralErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Empty => f.write_str("input was empty"),
Self::InvalidDigit => f.write_str("invalid digit"),
Self::U32Overflow => f.write_str("value overflowed the u32 range"),
Self::FeltOverflow => f.write_str("value overflowed the field modulus"),
Self::InvalidBitSize => {
f.write_str("expected value to be a valid bit size, e.g. 0..63")
},
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum HexErrorKind {
MissingDigits,
Invalid,
Overflow,
TooLong,
}
impl fmt::Display for HexErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::MissingDigits => {
f.write_str("expected number of hex digits to be a multiple of 2")
},
Self::Invalid => f.write_str("expected 2, 4, 8, 16, or 64 hex digits"),
Self::Overflow => f.write_str("value overflowed the field modulus"),
Self::TooLong => f.write_str(
"value has too many digits, long hex strings must contain exactly 64 digits",
),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BinErrorKind {
TooLong,
}
impl fmt::Display for BinErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::TooLong => f.write_str(
"value has too many digits, binary string can contain no more than 32 digits",
),
}
}
}
#[derive(Debug, Default, thiserror::Error, Diagnostic)]
#[repr(u8)]
pub enum ParsingError {
#[default]
#[error("parsing failed due to unexpected input")]
#[diagnostic()]
Failed = 0,
#[error("expected input to be valid utf8, but invalid byte sequences were found")]
#[diagnostic()]
InvalidUtf8 {
#[label("invalid byte sequence starts here")]
span: SourceSpan,
},
#[error(
"expected input to be valid utf8, but end-of-file was reached before final codepoint was read"
)]
#[diagnostic()]
IncompleteUtf8 {
#[label("the codepoint starting here is incomplete")]
span: SourceSpan,
},
#[error("invalid syntax")]
#[diagnostic()]
InvalidToken {
#[label("occurs here")]
span: SourceSpan,
},
#[error("invalid syntax: {message}")]
#[diagnostic()]
InvalidSyntax {
#[label("{message}")]
span: SourceSpan,
message: String,
},
#[error("invalid syntax")]
#[diagnostic(help("expected {}", expected.as_slice().join(", or ")))]
UnrecognizedToken {
#[label("found a {token} here")]
span: SourceSpan,
token: String,
expected: Vec<String>,
},
#[error("unexpected trailing tokens")]
#[diagnostic()]
ExtraToken {
#[label("{token} was found here, but was not expected")]
span: SourceSpan,
token: String,
},
#[error("unexpected end of file")]
#[diagnostic(help("expected {}", expected.as_slice().join(", or ")))]
UnrecognizedEof {
#[label("reached end of file here")]
span: SourceSpan,
expected: Vec<String>,
},
#[error("{error}")]
#[diagnostic(help(
"bare identifiers must be lowercase alphanumeric with '_', quoted identifiers can include any graphical character"
))]
InvalidIdentifier {
#[source]
#[diagnostic(source)]
error: crate::ast::IdentError,
#[label]
span: SourceSpan,
},
#[error("unclosed quoted identifier")]
#[diagnostic()]
UnclosedQuote {
#[label("no match for quotation mark starting here")]
start: SourceSpan,
},
#[error("too many instructions in a single code block")]
#[diagnostic()]
CodeBlockTooBig {
#[label]
span: SourceSpan,
},
#[error("invalid constant expression: division by zero")]
DivisionByZero {
#[label]
span: SourceSpan,
},
#[error("doc comment is too large")]
#[diagnostic(help("make sure it is less than u16::MAX bytes in length"))]
DocsTooLarge {
#[label]
span: SourceSpan,
},
#[error("invalid literal: {}", kind)]
#[diagnostic()]
InvalidLiteral {
#[label]
span: SourceSpan,
kind: LiteralErrorKind,
},
#[error("invalid literal: {}", kind)]
#[diagnostic()]
InvalidHexLiteral {
#[label]
span: SourceSpan,
kind: HexErrorKind,
},
#[error("invalid literal: {}", kind)]
#[diagnostic()]
InvalidBinaryLiteral {
#[label]
span: SourceSpan,
kind: BinErrorKind,
},
#[error("invalid MAST root literal")]
InvalidMastRoot {
#[label]
span: SourceSpan,
},
#[error("invalid library path: {}", message)]
InvalidLibraryPath {
#[label]
span: SourceSpan,
message: String,
},
#[error("invalid immediate: value must be in the range {}..{} (exclusive)", range.start, range.end)]
ImmediateOutOfRange {
#[label]
span: SourceSpan,
range: Range<usize>,
},
#[error("too many procedures in this module")]
#[diagnostic()]
ModuleTooLarge {
#[label]
span: SourceSpan,
},
#[error("too many re-exported procedures in this module")]
#[diagnostic()]
ModuleTooManyReexports {
#[label]
span: SourceSpan,
},
#[error(
"too many operands for `push`: tried to push {} elements, but only 16 can be pushed at one time",
count
)]
#[diagnostic()]
PushOverflow {
#[label]
span: SourceSpan,
count: usize,
},
#[error("expected a fully-qualified module path, e.g. `std::u64`")]
UnqualifiedImport {
#[label]
span: SourceSpan,
},
#[error(
"source-level digest re-exports are not supported; re-export a named item with `pub use {{item}} from module`"
)]
UnnamedReexportOfMastRoot {
#[label]
span: SourceSpan,
},
#[error("conflicting attributes for procedure definition")]
#[diagnostic()]
AttributeConflict {
#[label(
"conflict occurs because an attribute with the same name has already been defined"
)]
span: SourceSpan,
#[label("previously defined here")]
prev: SourceSpan,
},
#[error("conflicting key-value attributes for procedure definition")]
#[diagnostic()]
AttributeKeyValueConflict {
#[label(
"conflict occurs because a key with the same name has already been set in a previous declaration"
)]
span: SourceSpan,
#[label("previously defined here")]
prev: SourceSpan,
},
#[error("invalid Advice Map key")]
#[diagnostic()]
InvalidAdvMapKey {
#[label(
"an Advice Map key must be a word, either in 64-character hex format or in array-like format `[f0,f1,f2,f3]`"
)]
span: SourceSpan,
},
#[error("invalid slice constant")]
#[diagnostic()]
InvalidSliceConstant {
#[label("slices are only supported over word-sized constants")]
span: SourceSpan,
},
#[error("invalid slice: expected valid range")]
#[diagnostic()]
InvalidRange {
#[label("range used for the word constant slice is malformed: `{range:?}`")]
span: SourceSpan,
range: Range<usize>,
},
#[error("invalid slice: expected non-empty range")]
#[diagnostic()]
EmptySlice {
#[label("range used for the word constant slice is empty: `{range:?}`")]
span: SourceSpan,
range: Range<usize>,
},
#[error("unrecognized calling convention")]
#[diagnostic(help("expected one of: 'fast', 'C', 'wasm', 'canon-lift', or 'canon-lower'"))]
UnrecognizedCallConv {
#[label]
span: SourceSpan,
},
#[error("invalid struct annotation")]
#[diagnostic(help(
"expected one of: '@packed', '@packed(N)', '@transparent', '@bigendian', or '@align(N)'"
))]
InvalidStructAnnotation {
#[label]
span: SourceSpan,
},
#[error("invalid struct representation")]
#[diagnostic()]
InvalidStructRepr {
#[label("{message}")]
span: SourceSpan,
message: String,
},
#[error("deprecated instruction: `{instruction}` has been removed")]
#[diagnostic(help("use `{}` instead", replacement))]
DeprecatedInstruction {
#[label("this instruction is no longer supported")]
span: SourceSpan,
instruction: String,
replacement: String,
},
#[error("invalid procedure @locals attribute")]
#[diagnostic()]
InvalidLocalsAttr {
#[label("{message}")]
span: SourceSpan,
message: String,
},
#[error("invalid padding value for the `adv.push_mapvaln` instruction: {padding}")]
#[diagnostic(help("valid padding values are 0, 4, and 8"))]
InvalidPadValue {
#[label]
span: SourceSpan,
padding: u8,
},
#[error(
"invalid submodule declaration '{name}': could not find module sources at '{directory}/{basename}.masm' or '{directory}/{basename}/mod.masm'"
)]
UndefinedSubmodule {
name: crate::ast::Ident,
basename: alloc::boxed::Box<str>,
directory: miden_debug_types::Uri,
#[label]
span: SourceSpan,
#[source_code]
source_file: Option<Arc<miden_debug_types::SourceFile>>,
},
#[error(
"invalid submodule declaration '{name}': submodules must not have the same name as their parent"
)]
#[diagnostic(help("occurred while parsing {parent_module_uri}"))]
SelfReferentialSubmodule {
name: crate::ast::Ident,
parent_module_uri: miden_debug_types::Uri,
#[label(
"module source resolution rules require this declaration to resolve to the current source file"
)]
span: SourceSpan,
#[source_code]
source_file: Option<Arc<miden_debug_types::SourceFile>>,
},
#[error(
"conflicting submodule paths detected: '{name}' can be parsed from either '{first}' and '{second}', but not both"
)]
AmbiguousSubmoduleLocation {
name: crate::ast::Ident,
first: miden_debug_types::Uri,
second: miden_debug_types::Uri,
#[label]
span: SourceSpan,
#[source_code]
source_file: Option<Arc<miden_debug_types::SourceFile>>,
},
#[error(
"invalid submodule declaration '{name}': module source '{module_uri}' is already reachable through another submodule declaration"
)]
#[diagnostic(help("each module source file can only be owned by one module in a module tree"))]
DuplicateSubmoduleSource {
name: crate::ast::Ident,
module_uri: miden_debug_types::Uri,
#[label("this declaration resolves to an already visited module source")]
span: SourceSpan,
#[source_code]
source_file: Option<Arc<miden_debug_types::SourceFile>>,
},
}
impl ParsingError {
fn tag(&self) -> u8 {
unsafe { *<*const _>::from(self).cast::<u8>() }
}
}
impl Eq for ParsingError {}
impl PartialEq for ParsingError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Failed, Self::Failed) => true,
(Self::InvalidSyntax { message: l, .. }, Self::InvalidSyntax { message: r, .. }) => {
l == r
},
(Self::InvalidLiteral { kind: l, .. }, Self::InvalidLiteral { kind: r, .. }) => l == r,
(Self::InvalidHexLiteral { kind: l, .. }, Self::InvalidHexLiteral { kind: r, .. }) => {
l == r
},
(
Self::InvalidLibraryPath { message: l, .. },
Self::InvalidLibraryPath { message: r, .. },
) => l == r,
(
Self::ImmediateOutOfRange { range: l, .. },
Self::ImmediateOutOfRange { range: r, .. },
) => l == r,
(Self::PushOverflow { count: l, .. }, Self::PushOverflow { count: r, .. }) => l == r,
(
Self::UnrecognizedToken { token: ltok, expected: lexpect, .. },
Self::UnrecognizedToken { token: rtok, expected: rexpect, .. },
) => ltok == rtok && lexpect == rexpect,
(Self::ExtraToken { token: ltok, .. }, Self::ExtraToken { token: rtok, .. }) => {
ltok == rtok
},
(
Self::UnrecognizedEof { expected: lexpect, .. },
Self::UnrecognizedEof { expected: rexpect, .. },
) => lexpect == rexpect,
(x, y) => x.tag() == y.tag(),
}
}
}
impl ParsingError {
pub fn from_utf8_error(source_id: SourceId, err: core::str::Utf8Error) -> Self {
let start = u32::try_from(err.valid_up_to()).ok().unwrap_or(u32::MAX);
match err.error_len() {
None => Self::IncompleteUtf8 { span: SourceSpan::at(source_id, start) },
Some(len) => Self::InvalidUtf8 {
span: SourceSpan::new(source_id, start..(start + len as u32)),
},
}
}
}