use llvmkit_support::Span;
use crate::file_loc::FileLocRange;
use crate::ll_lexer::LexError;
use crate::numbered_values::AddError as SlotAddError;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct DiagLoc {
pub span: Span,
pub file: Option<FileLocRange>,
}
impl DiagLoc {
#[inline]
pub const fn span(span: Span) -> Self {
Self { span, file: None }
}
#[inline]
pub const fn with_file(self, file: FileLocRange) -> Self {
Self {
span: self.span,
file: Some(file),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum SymbolKind {
Global,
Local,
Type,
Block,
Metadata,
AttrGroup,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum SymbolId {
Named(String),
Numbered(u32),
}
impl core::fmt::Display for SymbolId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SymbolId::Named(n) => f.write_str(n),
SymbolId::Numbered(n) => write!(f, "%{n}"),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ParseError {
#[error(transparent)]
Lex(#[from] LexError),
#[error("expected {expected} at {loc:?}")]
Expected { expected: String, loc: DiagLoc },
#[error("redefinition of {kind:?} '{id}'")]
Redefinition {
kind: SymbolKind,
id: SymbolId,
loc: DiagLoc,
},
#[error("use of undefined {kind:?} '{id}'")]
UndefinedSymbol {
kind: SymbolKind,
id: SymbolId,
loc: DiagLoc,
},
#[error("invalid slot id at {loc:?}: {source}")]
InvalidSlotId {
#[source]
source: SlotAddError,
loc: DiagLoc,
},
#[error("integer width {width} out of range (1..={max})")]
IntegerWidthOutOfRange { width: u64, max: u32, loc: DiagLoc },
#[error("I/O error reading source: {0}")]
Io(String),
}
impl From<std::io::Error> for ParseError {
#[inline]
fn from(e: std::io::Error) -> Self {
ParseError::Io(e.to_string())
}
}
impl ParseError {
pub fn loc(&self) -> Option<DiagLoc> {
match self {
ParseError::Lex(e) => Some(DiagLoc::span(e.span())),
ParseError::Expected { loc, .. }
| ParseError::Redefinition { loc, .. }
| ParseError::UndefinedSymbol { loc, .. }
| ParseError::InvalidSlotId { loc, .. }
| ParseError::IntegerWidthOutOfRange { loc, .. } => Some(*loc),
ParseError::Io(_) => None,
}
}
}
pub type ParseResult<T> = Result<T, ParseError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expected_carries_location() {
let span = Span::new(5, 9);
let err = ParseError::Expected {
expected: "type".into(),
loc: DiagLoc::span(span),
};
let loc = err.loc().unwrap();
assert_eq!(loc.span, span);
assert!(loc.file.is_none());
}
#[test]
fn redefinition_records_symbol() {
let err = ParseError::Redefinition {
kind: SymbolKind::Global,
id: SymbolId::Named("@foo".into()),
loc: DiagLoc::span(Span::new(0, 4)),
};
if let ParseError::Redefinition { kind, id, .. } = &err {
assert_eq!(*kind, SymbolKind::Global);
assert_eq!(*id, SymbolId::Named("@foo".into()));
} else {
panic!("wrong variant");
}
}
#[test]
fn lex_error_passes_through() {
let lex = LexError::UnknownToken {
span: Span::new(0, 1),
};
let err: ParseError = lex.clone().into();
assert_eq!(err.loc().map(|l| l.span), Some(lex.span()));
}
#[test]
fn integer_width_out_of_range_is_typed() {
let err = ParseError::IntegerWidthOutOfRange {
width: 1 << 30,
max: (1 << 24) - 1,
loc: DiagLoc::span(Span::new(2, 10)),
};
let rendered = format!("{err}");
assert!(rendered.contains("integer width"));
assert!(rendered.contains("out of range"));
}
}