use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub const fn empty(at: usize) -> Self {
Self { start: at, end: at }
}
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn merge(self, other: Self) -> Self {
Self {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Severity {
Error,
Lint,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorCode {
DimMismatch,
UnknownUnit,
UnknownEq,
CodePositional,
Range,
ContractDim,
PackParse,
PackBody,
DefCycle,
DefSymbolic,
DupUnit,
AffineDefine,
AnchorInvalid,
AnchorAffine,
DupAnchor,
EqInExpr,
Parse,
Eval,
TickSpace,
BareTick,
DivZeroLiteral,
AffineMixed,
}
impl ErrorCode {
pub const fn as_str(self) -> &'static str {
match self {
Self::DimMismatch => "E-DIM-MISMATCH",
Self::UnknownUnit => "E-UNKNOWN-UNIT",
Self::UnknownEq => "E-UNKNOWN-EQ",
Self::CodePositional => "E-CODE-POSITIONAL",
Self::Range => "E-RANGE",
Self::ContractDim => "E-CONTRACT-DIM",
Self::PackParse => "E-PACK-PARSE",
Self::PackBody => "E-PACK-BODY",
Self::DefCycle => "E-DEF-CYCLE",
Self::DefSymbolic => "E-DEF-SYMBOLIC",
Self::DupUnit => "E-DUP-UNIT",
Self::AffineDefine => "E-AFFINE-DEFINE",
Self::AnchorInvalid => "E-ANCHOR-INVALID",
Self::AnchorAffine => "E-ANCHOR-AFFINE",
Self::DupAnchor => "E-DUP-ANCHOR",
Self::EqInExpr => "E-EQ-IN-EXPR",
Self::Parse => "E-PARSE",
Self::Eval => "E-EVAL",
Self::TickSpace => "E-TICK-SPACE",
Self::BareTick => "E-BARE-TICK",
Self::DivZeroLiteral => "E-DIV-ZERO-LITERAL",
Self::AffineMixed => "E-AFFINE-MIXED",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LintCode {
Range,
ExactnessLost,
RationalOverflow,
FtInSpaced,
InchGe12,
CommaGroup,
SpacedCaret,
UnitShadow,
AffineDelta,
}
impl LintCode {
pub const fn as_str(self) -> &'static str {
match self {
Self::Range => "L-RANGE",
Self::ExactnessLost => "L-EXACTNESS-LOST",
Self::RationalOverflow => "L-RATIONAL-OVERFLOW",
Self::FtInSpaced => "L-FTIN-SPACED",
Self::InchGe12 => "L-INCH-GE-12",
Self::CommaGroup => "L-COMMA-GROUP",
Self::SpacedCaret => "L-SPACED-CARET",
Self::UnitShadow => "L-UNIT-SHADOW",
Self::AffineDelta => "L-AFFINE-DELTA",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Hint {
ExpectedDimension(String),
FoundDimension(String),
RelatedSpan(Span),
Note(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub severity: Severity,
pub code: String,
pub message: String,
pub span: Span,
pub hints: Vec<Hint>,
}
impl Diagnostic {
pub fn error(code: ErrorCode, message: impl Into<String>, span: Span) -> Self {
Self {
severity: Severity::Error,
code: code.as_str().into(),
message: message.into(),
span,
hints: Vec::new(),
}
}
pub fn lint(code: LintCode, message: impl Into<String>, span: Span) -> Self {
Self {
severity: Severity::Lint,
code: code.as_str().into(),
message: message.into(),
span,
hints: Vec::new(),
}
}
pub fn with_hints(mut self, hints: Vec<Hint>) -> Self {
self.hints = hints;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diag(pub Diagnostic);
impl Diag {
pub fn new(diag: Diagnostic) -> Self {
Self(diag)
}
pub fn diagnostic(&self) -> &Diagnostic {
&self.0
}
}
impl fmt::Display for Diag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} [{}]: {}",
self.0.code, self.0.span.start, self.0.message
)
}
}
impl std::error::Error for Diag {}
pub fn levenshtein(a: &str, b: &str) -> usize {
if a.len() < b.len() {
return levenshtein(b, a);
}
if b.is_empty() {
return a.len();
}
let mut prev: Vec<usize> = (0..=b.len()).collect();
for (i, ca) in a.chars().enumerate() {
let mut curr = vec![i + 1];
for (j, cb) in b.chars().enumerate() {
let cost = if ca == cb { 0 } else { 1 };
curr.push(
(prev[j + 1] + 1)
.min(curr[j] + 1)
.min(prev[j] + cost),
);
}
prev = curr;
}
prev[b.len()]
}
pub fn suggest_similar<'a>(
name: &str,
candidates: impl IntoIterator<Item = &'a str>,
max_distance: usize,
) -> Option<&'a str> {
candidates
.into_iter()
.filter(|c| *c != name)
.map(|c| (c, levenshtein(name, c)))
.filter(|(_, d)| *d <= max_distance)
.min_by_key(|(_, d)| *d)
.map(|(c, _)| c)
}