mod handlers;
#[cfg(feature = "log")]
pub use handlers::log::WriteToLog;
pub use handlers::stderr::WriteToStderr;
use {
crate::xkb::{
code_map::CodeMap,
code_slice::CodeSlice,
span::{Span, Spanned},
},
bstr::ByteSlice,
debug_fn::debug_fn,
kbvm_proc::diagnostic_kind,
std::{
error::Error,
fmt::{Debug, Display, Formatter, Write},
ops::Deref,
path::{Path, PathBuf},
sync::Arc,
},
unicode_width::UnicodeWidthChar,
};
pub trait DiagnosticHandler {
fn filter(&self, kind: DiagnosticKind, is_fatal: bool) -> bool {
let _ = kind;
let _ = is_fatal;
true
}
fn handle(&mut self, diag: Diagnostic);
}
pub(crate) struct DiagnosticSink<'a, 'b> {
handler: &'a mut (dyn DiagnosticHandler + 'b),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[non_exhaustive]
pub enum Severity {
Debug,
Warning,
Error,
}
#[diagnostic_kind]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum DiagnosticKind {
#[severity = Error]
OctalStringEscapeOverflow,
#[severity = Error]
UnknownEscapeSequence,
#[severity = Error]
FileOpenFailed,
#[severity = Error]
DeserializeRegistryFailed,
#[severity = Error]
FileReadFailed,
#[severity = Warning]
FileNotFound,
#[severity = Warning]
UnexpectedItemType,
#[severity = Warning]
MultipleDefaultItems,
#[severity = Warning]
DuplicateItemName,
#[severity = Error]
MissingIncludeFileName,
#[severity = Error]
UnterminatedIncludeMapName,
#[severity = Error]
MissingIncludeMergeMode,
#[severity = Error]
InvalidIncludeGroupIndex,
#[severity = Error]
UnterminatedKeyName,
#[severity = Error]
UnterminatedString,
#[severity = Error]
InvalidFloatLiteral,
#[severity = Error]
InvalidIntegerLiteral,
#[severity = Error]
UnlexableByte,
#[severity = Error]
MaxIncludeDepthReached,
#[severity = Error]
MaxIncludesReached,
#[severity = Error]
EmptyMacroName,
#[severity = Error]
UnexpectedEof,
#[severity = Error]
UnexpectedToken,
#[severity = Error]
ExpectedEol,
#[severity = Error]
ExpectedIndexStart,
#[severity = Error]
ExpectedIndexEnd,
#[severity = Error]
InvalidMatcherIndex,
#[severity = Error]
TooDeeplyNested,
#[severity = Error]
UnexpectedDeclaration,
#[severity = Error]
U32Overflow,
#[severity = Error]
UnknownKeysym,
#[severity = Error]
UnknownGroup,
#[severity = Error]
UnknownLevel,
#[severity = Error]
UnknownRadioGroup,
#[severity = Error]
RecursiveInclude,
#[severity = Error]
InvalidNumberOfRuleKeys,
#[severity = Error]
UnexpectedRule,
#[severity = Error]
InvalidNumberOfRuleValues,
#[severity = Error]
InvalidPercentEncoding,
#[severity = Error]
InvalidIndicatorIndex,
#[severity = Error]
TooManyIndicators,
#[severity = Error]
UnknownKeycodesVariable,
#[severity = Error]
UnknownTypesVariable,
#[severity = Error]
UnknownCompatVariable,
#[severity = Error]
UnknownKeyAlias,
#[severity = Warning]
DuplicateKeyTypeDefinition,
#[severity = Error]
UnknownAction,
#[severity = Warning]
IgnoredInterpretField,
#[severity = Warning]
IgnoredIndicatorField,
#[severity = Error]
UnknownModifier,
#[severity = Warning]
IgnoredModMapEntry,
#[severity = Error]
UnknownKey,
#[severity = Error]
UnknownSymbolsVariable,
#[severity = Warning]
IgnoredKeyField,
#[severity = Error]
DiscardingGroup,
#[severity = Warning]
MissingGroupName,
#[severity = Error]
TooManyVirtualModifiers,
#[severity = Warning]
IgnoringVmodRedefinition,
#[severity = Error]
HomeNotSet,
#[severity = Error]
UnknownRulesEscapeSequence,
#[severity = Warning]
UsingFirstInsteadOfDefault,
#[severity = Error]
ModsCalculationOverflow,
#[severity = Error]
KeysymCalculationOverflow,
#[severity = Error]
LevelCalculationOverflow,
#[severity = Error]
RadioGroupCalculationOverflow,
#[severity = Error]
UnsupportedExpressionForGroup,
#[severity = Error]
UnsupportedExpressionForGroupChange,
#[severity = Error]
GroupCalculationOverflow,
#[severity = Error]
UnsupportedExpressionForLevel,
#[severity = Error]
UnsupportedExpressionForRadioGroup,
#[severity = Error]
UnsupportedExpressionForString,
#[severity = Error]
UnsupportedExpressionForBoolean,
#[severity = Error]
UnsupportedExpressionForKeysym,
#[severity = Error]
UnsupportedExpressionForModMask,
#[severity = Error]
UnsupportedExpressionForKeyRepeats,
#[severity = Error]
UnsupportedExpressionForLockModsAffect,
#[severity = Error]
UnsupportedExpressionForAction,
#[severity = Error]
UnsupportedExpressionForSymbolsOrActions,
#[severity = Error]
UnsupportedExpressionForKeycode,
#[severity = Error]
UnknownBooleanValue,
#[severity = Debug]
UnimplementedAction,
#[severity = Error]
UnknownParameterForNoAction,
#[severity = Error]
UnknownParameterForVoidAction,
#[severity = Error]
UnknownParameterForSetMods,
#[severity = Error]
UnknownParameterForLatchMods,
#[severity = Error]
UnknownParameterForLockMods,
#[severity = Error]
UnknownParameterForSetGroup,
#[severity = Error]
UnknownParameterForLatchGroup,
#[severity = Error]
UnknownParameterForLockGroup,
#[severity = Error]
MissingValueForSetModsMods,
#[severity = Error]
MissingValueForLatchModsMods,
#[severity = Error]
MissingValueForLockModsMods,
#[severity = Error]
MissingValueForLockModsAffect,
#[severity = Error]
MissingValueForSetGroupGroup,
#[severity = Error]
MissingValueForLatchGroupGroup,
#[severity = Error]
MissingValueForLockGroupGroup,
#[severity = Error]
MissingValueForRedirectKeyKey,
#[severity = Error]
MissingValueForRedirectKeyClearmods,
#[severity = Error]
MissingValueForRedirectKeyMods,
#[severity = Error]
MissingValueForSetControlsControls,
#[severity = Error]
MissingValueForLockControlsControls,
#[severity = Error]
MissingValueForLockControlsAffect,
#[severity = Error]
UnknownLockModsAffect,
#[severity = Error]
NotOneInterpretFilterArgument,
#[severity = Error]
NamedFilterArgArgument,
#[severity = Error]
UnknownFilterPredicate,
#[severity = Error]
UnknownInterpretField,
#[severity = Debug]
UnimplementedInterpretField,
#[severity = Error]
MissingInterpretActionValue,
#[severity = Error]
MissingInterpretVirtualmodValue,
#[severity = Error]
UnknownInterpretVirtualModifier,
#[severity = Error]
UnknownTypeField,
#[severity = Error]
MissingTypeModifiersValue,
#[severity = Error]
MissingTypeMapValue,
#[severity = Error]
MissingTypePreserveValue,
#[severity = Error]
MissingTypeLevelNameValue,
#[severity = Error]
MissingInterpretUseModMapModValue,
#[severity = Error]
InvalidInterpretUseModMapModValue,
#[severity = Debug]
UnimplementedIndicatorField,
#[severity = Error]
UnknownIndicatorField,
#[severity = Error]
MissingIndicatorModifiersValue,
#[severity = Error]
GroupOutOfBounds,
#[severity = Error]
LevelOutOfBounds,
#[severity = Error]
RadioGroupOutOfBounds,
#[severity = Error]
MissingIndicatorGroupsValue,
#[severity = Error]
UnknownControl,
#[severity = Error]
MissingIndicatorControlValue,
#[severity = Error]
UnknownModComponent,
#[severity = Error]
UnsupportedExpressionForModComponent,
#[severity = Error]
UnsupportedExpressionForControl,
#[severity = Error]
MissingIndicatorWhichModStateValue,
#[severity = Error]
UnknownGroupComponent,
#[severity = Error]
UnsupportedExpressionForGroupComponent,
#[severity = Error]
MissingIndicatorWhichGroupStateValue,
#[severity = Error]
UnknownKeycode,
#[severity = Error]
UnknownKeyField,
#[severity = Error]
MissingKeyGroupsRedirectValue,
#[severity = Error]
MissingKeySymbolsValue,
#[severity = Error]
MissingKeyActionsValue,
#[severity = Error]
MissingKeyVirtualModifiersValue,
#[severity = Error]
UnknownKeyRepeatingValue,
#[severity = Error]
MissingKeyTypeValue,
#[severity = Error]
UnknownKeyType,
#[severity = Error]
VirtualModifierHasRealName,
#[severity = Error]
UnterminatedKeysym,
#[severity = Error]
UnknownComposeIncludeEscape,
#[severity = Error]
LocaleComposeFileNotResolved,
#[severity = Debug]
IgnoringDuplicateComposeEntry,
#[severity = Debug]
IgnoringComposePrefix,
#[severity = Debug]
ComposeProductionOverwritten,
#[severity = Error]
NonUTF8Path,
#[severity = Error]
ComposeRuleWithoutConditions,
#[severity = Error]
MaxRuntimeReached,
#[severity = Error]
MaxComposeRulesReached,
#[severity = Error]
UnknownParameterForRedirectKey,
#[severity = Error]
UnknownParameterForSetControls,
#[severity = Error]
UnknownParameterForLockControls,
#[severity = Error]
MissingKeyOverlayValue,
#[severity = Error]
MissingKeyRadiogroupValue,
#[severity = Error]
UnterminatedUnicodeEscape,
#[severity = Error]
UnopenedUnicodeEscape,
#[severity = Error]
InvalidUnicodeEscapeRepresentation,
#[severity = Error]
InvalidUnicodeCodepoint,
#[severity = Error]
KeysymStringNotUtf8,
#[severity = Error]
MultipleKeysymsInInterpret,
}
impl DiagnosticKind {
pub fn severity(&self) -> Severity {
self.severity_()
}
}
pub struct Diagnostic {
kind: DiagnosticKind,
location: DiagnosticLocation,
}
pub struct DiagnosticLocation {
message: Option<Box<dyn Display + Send + Sync>>,
source_file: Option<Arc<PathBuf>>,
line: CodeSlice<'static>,
line_num: usize,
in_line_offset: usize,
in_line_len: usize,
inner: Option<Box<DiagnosticLocation>>,
}
struct WithCode<'a> {
diagnostic: &'a Diagnostic,
}
impl<'a, 'b> DiagnosticSink<'a, 'b> {
pub(crate) fn new(handler: &'a mut (dyn DiagnosticHandler + 'b)) -> Self {
Self { handler }
}
fn push_(
&mut self,
map: &mut CodeMap,
kind: DiagnosticKind,
message: Spanned<impl Display + Send + Sync + 'static>,
is_fatal: bool,
) {
if !self.handler.filter(kind, is_fatal) {
return;
}
let diagnostic = Diagnostic::new(map, kind, message.val, message.span);
self.handler.handle(diagnostic);
}
pub(crate) fn push(
&mut self,
map: &mut CodeMap,
kind: DiagnosticKind,
message: Spanned<impl Display + Send + Sync + 'static>,
) {
self.push_(map, kind, message, false)
}
pub(crate) fn push_fatal(
&mut self,
map: &mut CodeMap,
kind: DiagnosticKind,
message: Spanned<impl Display + Send + Sync + 'static>,
) -> Diagnostic {
let message = message.map(Arc::new);
self.push_(map, kind, message.clone(), true);
Diagnostic::new(map, kind, message.val, message.span)
}
}
impl DiagnosticLocation {
fn new(
map: &mut CodeMap,
message: Option<Box<dyn Display + Send + Sync + 'static>>,
span: Span,
inner: Option<Box<DiagnosticLocation>>,
) -> Self {
let info = map.get(span);
let lo = span.lo.max(info.span.lo) - info.lines_offset;
let hi = span.hi.min(info.span.hi) - info.lines_offset;
let line_idx = info
.lines
.binary_search_by(|r| r.cmp(&lo))
.unwrap_or_else(|i| i - 1);
let line_lo = info.lines[line_idx];
let line_hi = info
.lines
.get(line_idx + 1)
.map(|l| *l - 1)
.unwrap_or(info.span.hi - info.lines_offset);
let in_line_offset = (lo - line_lo) as usize;
let in_line_len = (hi.min(line_hi).saturating_sub(lo)) as usize;
let line_lo = (line_lo + info.lines_offset - info.span.lo) as usize;
let line_hi = (line_hi + info.lines_offset - info.span.lo) as usize;
let slice = info.code.to_slice().slice(line_lo..line_hi).to_owned();
let mut res = Self {
message,
source_file: info.file.cloned(),
line: slice,
line_num: line_idx + 1,
in_line_offset,
in_line_len,
inner,
};
if let Some(span) = info.include_span {
res = Self::new(map, None, span, Some(Box::new(res)))
}
res
}
fn fmt(&self, f: &mut Formatter<'_>, with_code: bool) -> std::fmt::Result {
write!(
f,
"at {} {}:{}: {}",
debug_fn(|f| match &self.source_file {
Some(p) => Display::fmt(&p.display(), f),
None => f.write_str("<anonymous file>"),
}),
self.line_num,
self.in_line_offset,
debug_fn(|f| match &self.message {
Some(m) => m.fmt(f),
None => f.write_str("while processing include"),
}),
)?;
if with_code {
f.write_str(":\n")?;
write!(f, ">> ")?;
let (prefix, suffix) = self.line.split_at(self.in_line_offset);
let mut in_line_len = self.in_line_len;
while in_line_len < suffix.len() && suffix[in_line_len].leading_ones() == 1 {
in_line_len += 1;
}
let (content, suffix) = suffix.split_at(in_line_len);
let mut send = |s: &[u8]| {
if s.iter().any(|c| !matches!(*c, 0x20..=0x7E)) {
let mut counter = 0;
for c in s.as_bstr().chars() {
if c == '\t' {
counter += 4;
f.write_str(" ")?;
} else {
if let Some(w) = c.width() {
counter += w;
f.write_char(c)?;
}
}
}
Ok(counter)
} else {
write!(f, "{}", s.as_bstr())?;
Ok(s.len())
}
};
let offset = send(prefix)?;
let len = send(content)?;
write!(f, "{}\n ", suffix.as_bstr())?;
for _ in 0..offset {
f.write_str(" ")?
}
f.write_str("^")?;
for _ in 1..len {
f.write_str("~")?;
}
if let Some(inner) = &self.inner {
f.write_str("\n")?;
inner.deref().fmt(f, true)?;
}
}
Ok(())
}
pub fn next(&self) -> Option<&DiagnosticLocation> {
self.inner.as_deref()
}
pub fn source_file(&self) -> Option<&Path> {
self.source_file.as_deref().map(|v| &**v)
}
pub fn line_contents(&self) -> &[u8] {
&self.line
}
pub fn file_contents(&self) -> &[u8] {
self.line.code()
}
pub fn line(&self) -> usize {
self.line_num
}
pub fn column(&self) -> usize {
self.in_line_offset
}
pub fn length(&self) -> usize {
self.in_line_len
}
}
impl Diagnostic {
pub(crate) fn new(
map: &mut CodeMap,
kind: DiagnosticKind,
message: impl Display + Send + Sync + 'static,
span: Span,
) -> Self {
Self {
kind,
location: DiagnosticLocation::new(map, Some(Box::new(message)), span, None),
}
}
pub fn with_code(&self) -> impl Display + use<'_> {
WithCode { diagnostic: self }
}
pub fn kind(&self) -> DiagnosticKind {
self.kind
}
pub fn location(&self) -> Option<&DiagnosticLocation> {
Some(&self.location)
}
}
impl Display for WithCode<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.diagnostic.location.fmt(f, true)
}
}
impl Debug for Diagnostic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.location.fmt(f, false)
}
}
impl Display for Diagnostic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.location.fmt(f, false)
}
}
impl Error for Diagnostic {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.location.source()
}
}
impl Debug for DiagnosticLocation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt(f, false)
}
}
impl Display for DiagnosticLocation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.fmt(f, false)
}
}
impl Error for DiagnosticLocation {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.inner.as_deref().map(|d| d as &dyn Error)
}
}