use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct SourceId(u32);
impl SourceId {
pub const fn new(value: u32) -> Self {
Self(value)
}
pub const fn get(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct TextRange {
start: usize,
end: usize,
}
impl TextRange {
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub const fn start(self) -> usize {
self.start
}
pub const fn end(self) -> usize {
self.end
}
pub const fn len(self) -> usize {
self.end.saturating_sub(self.start)
}
pub const fn is_empty(self) -> bool {
self.start == self.end
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Span {
source_id: SourceId,
range: TextRange,
}
impl Span {
pub const fn new(source_id: SourceId, range: TextRange) -> Self {
Self { source_id, range }
}
pub const fn at(offset: usize, length: usize) -> Self {
Self::new(
SourceId::new(0),
TextRange::new(offset, offset.saturating_add(length)),
)
}
pub const fn source_id(self) -> SourceId {
self.source_id
}
pub const fn range(self) -> TextRange {
self.range
}
pub const fn with_source_id(mut self, value: SourceId) -> Self {
self.source_id = value;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Severity {
Warning,
Error,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiagnosticLabel {
span: Span,
message: Option<String>,
}
impl DiagnosticLabel {
pub fn new(span: Span, message: Option<String>) -> Self {
Self { span, message }
}
pub const fn span(&self) -> Span {
self.span
}
pub fn message(&self) -> Option<&str> {
self.message.as_deref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
severity: Severity,
code: &'static str,
message: String,
primary: Option<DiagnosticLabel>,
secondary: Vec<DiagnosticLabel>,
notes: Vec<String>,
}
impl Diagnostic {
pub fn error(code: &'static str, message: impl Into<String>, span: Option<Span>) -> Self {
Self {
severity: Severity::Error,
code,
message: message.into(),
primary: span.map(|span| DiagnosticLabel::new(span, None)),
secondary: Vec::new(),
notes: Vec::new(),
}
}
pub fn warning(code: &'static str, message: impl Into<String>, span: Option<Span>) -> Self {
Self {
severity: Severity::Warning,
code,
message: message.into(),
primary: span.map(|span| DiagnosticLabel::new(span, None)),
secondary: Vec::new(),
notes: Vec::new(),
}
}
pub const fn severity(&self) -> Severity {
self.severity
}
pub const fn code(&self) -> &'static str {
self.code
}
pub fn message(&self) -> &str {
&self.message
}
pub fn primary(&self) -> Option<&DiagnosticLabel> {
self.primary.as_ref()
}
pub fn secondary(&self) -> &[DiagnosticLabel] {
&self.secondary
}
pub fn notes(&self) -> &[String] {
&self.notes
}
pub fn with_primary_message(mut self, message: impl Into<String>) -> Self {
if let Some(primary) = &mut self.primary {
primary.message = Some(message.into());
}
self
}
pub fn with_secondary(mut self, span: Span, message: impl Into<String>) -> Self {
self.secondary
.push(DiagnosticLabel::new(span, Some(message.into())));
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecodeErrorKind {
Truncated,
BadMagic(u16),
NegativeSize,
SizeLimit,
InvalidBoolean(u8),
InvalidSentinel(i64),
InvalidStringOffset,
UnterminatedString,
InvalidName,
TrailingData,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeError {
offset: usize,
kind: DecodeErrorKind,
}
impl DecodeError {
pub(crate) const fn new(offset: usize, kind: DecodeErrorKind) -> Self {
Self { offset, kind }
}
pub const fn offset(&self) -> usize {
self.offset
}
pub const fn kind(&self) -> DecodeErrorKind {
self.kind
}
}
impl fmt::Display for DecodeErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Truncated => f.write_str("input ended before the section was complete"),
Self::BadMagic(magic) => write!(f, "unsupported header magic {magic:#06o}"),
Self::NegativeSize => f.write_str("a section count was negative"),
Self::SizeLimit => f.write_str("a section exceeded a format or defensive size limit"),
Self::InvalidBoolean(value) => write!(f, "unknown boolean sentinel {value}"),
Self::InvalidSentinel(value) => write!(f, "invalid numeric or string sentinel {value}"),
Self::InvalidStringOffset => f.write_str("a string offset points outside its table"),
Self::UnterminatedString => f.write_str("a string table item has no terminating NUL"),
Self::InvalidName => f.write_str("a terminal or capability name is invalid"),
Self::TrailingData => f.write_str("bytes remain after the final recognized section"),
}
}
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"invalid compiled terminfo at byte {}: {}",
self.offset, self.kind
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum EncodeError {
SizeLimit(&'static str),
LegacyNumber(i32),
InvalidNumber(i32),
InvalidName(String),
StringContainsNul(String),
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SizeLimit(section) => write!(f, "{section} exceeds the compiled-format limit"),
Self::LegacyNumber(value) => write!(f, "{value} does not fit the legacy number format"),
Self::InvalidNumber(value) => {
write!(f, "{value} is not a valid terminfo numeric value")
}
Self::InvalidName(name) => write!(f, "invalid terminal or capability name {name:?}"),
Self::StringContainsNul(name) => write!(f, "capability {name:?} contains a NUL byte"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum BuildError {
EmptyPrimaryName,
InvalidName(String),
DuplicateAlias(String),
InvalidCapabilityName(String),
InvalidNumber(i64),
StringContainsNul(String),
ExtendedKindRequired(String),
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyPrimaryName => f.write_str("a terminal entry needs a primary name"),
Self::InvalidName(name) => write!(f, "invalid terminal name {name:?}"),
Self::DuplicateAlias(name) => write!(f, "duplicate terminal name {name:?}"),
Self::InvalidCapabilityName(name) => write!(f, "invalid capability name {name:?}"),
Self::InvalidNumber(value) => {
write!(f, "{value} is not a valid terminfo numeric value")
}
Self::StringContainsNul(name) => {
write!(f, "capability {name:?} contains a NUL byte")
}
Self::ExtendedKindRequired(name) => write!(
f,
"cancelling the unknown extension {name:?} requires its capability type"
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
pub(crate) diagnostic: Box<Diagnostic>,
}
impl ParseError {
pub fn new(diagnostic: Diagnostic) -> Self {
Self {
diagnostic: Box::new(diagnostic),
}
}
pub fn diagnostic(&self) -> &Diagnostic {
&self.diagnostic
}
pub fn into_diagnostic(self) -> Diagnostic {
*self.diagnostic
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.diagnostic.code, self.diagnostic.message)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CompileErrorKind {
Parse,
MissingUse {
name: String,
},
UseCycle {
chain: Vec<String>,
},
ExtendedTypeConflict {
name: String,
},
Provider {
name: String,
message: String,
},
Build(BuildError),
Encode(EncodeError),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompileError {
pub(crate) kind: CompileErrorKind,
pub(crate) diagnostics: Vec<Diagnostic>,
}
impl CompileError {
pub fn new(kind: CompileErrorKind, diagnostics: Vec<Diagnostic>) -> Self {
Self { kind, diagnostics }
}
pub fn kind(&self) -> &CompileErrorKind {
&self.kind
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
}
impl fmt::Display for CompileError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
CompileErrorKind::Parse => f.write_str("terminfo source could not be parsed"),
CompileErrorKind::MissingUse { name } => write!(f, "unknown use= entry {name:?}"),
CompileErrorKind::UseCycle { chain } => write!(f, "use= cycle: {}", chain.join(" -> ")),
CompileErrorKind::ExtendedTypeConflict { name } => write!(
f,
"user-defined capability {name:?} is used with conflicting types"
),
CompileErrorKind::Provider { name, message } => {
write!(f, "entry provider failed for {name:?}: {message}")
}
CompileErrorKind::Build(error) => write!(f, "invalid terminfo entry: {error}"),
CompileErrorKind::Encode(error) => write!(f, "encoding failed: {error}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExpandErrorKind {
TruncatedOperator,
StackUnderflow,
TypeMismatch,
InvalidParameter,
InvalidVariable,
InvalidNumber,
DivideByZero,
UnbalancedConditional,
OutputLimit,
StepLimit,
StackLimit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExpandError {
pub(crate) offset: usize,
pub(crate) kind: ExpandErrorKind,
}
impl ExpandError {
pub(crate) const fn new(offset: usize, kind: ExpandErrorKind) -> Self {
Self { offset, kind }
}
pub const fn offset(&self) -> usize {
self.offset
}
pub const fn kind(&self) -> ExpandErrorKind {
self.kind
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConvertError {
LossyCapability(String),
UnsupportedExpression(String),
EntryTooLong {
length: usize,
limit: usize,
},
Compile(CompileError),
}
impl fmt::Display for ConvertError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LossyCapability(name) => write!(
f,
"capability {name:?} has no lossless termcap representation"
),
Self::UnsupportedExpression(name) => write!(
f,
"capability {name:?} uses a parameter expression termcap cannot represent"
),
Self::EntryTooLong { length, limit } => {
write!(f, "termcap entry is {length} bytes (profile limit {limit})")
}
Self::Compile(error) => error.fmt(f),
}
}
}
impl fmt::Display for ExpandErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TruncatedOperator => f.write_str("an operator is missing required bytes"),
Self::StackUnderflow => f.write_str("an operator needed more stack values"),
Self::TypeMismatch => f.write_str("a value had the wrong number or string type"),
Self::InvalidParameter => f.write_str("parameter index is out of range"),
Self::InvalidVariable => f.write_str("variable name is out of range"),
Self::InvalidNumber => {
f.write_str("a numeric literal or formatting operand is invalid")
}
Self::DivideByZero => f.write_str("division or remainder by zero"),
Self::UnbalancedConditional => f.write_str("conditional markers are unbalanced"),
Self::OutputLimit => f.write_str("expanded output exceeds the configured limit"),
Self::StepLimit => f.write_str("executed operators exceed the configured limit"),
Self::StackLimit => f.write_str("stack depth exceeds the configured limit"),
}
}
}
impl fmt::Display for ExpandError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"parameter expansion failed at byte {}: {}",
self.offset, self.kind
)
}
}
#[cfg(feature = "std")]
#[derive(Debug)]
#[non_exhaustive]
pub enum DatabaseError {
InvalidName(String),
NotFound(String),
UnsupportedBackend(String),
NameMismatch {
requested: String,
decoded: String,
},
UntrustedSymlink(std::path::PathBuf),
Decode(DecodeError),
Io(std::io::Error),
}
#[cfg(feature = "std")]
impl fmt::Display for DatabaseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidName(name) => write!(f, "unsafe terminal name {name:?}"),
Self::NotFound(name) => write!(f, "terminfo entry {name:?} was not found"),
Self::UnsupportedBackend(kind) => write!(f, "unsupported terminfo backend: {kind}"),
Self::NameMismatch { requested, decoded } => write!(
f,
"terminfo entry name mismatch: requested {requested:?}, decoded {decoded:?}"
),
Self::UntrustedSymlink(path) => {
write!(f, "refusing untrusted terminfo symlink {}", path.display())
}
Self::Decode(error) => error.fmt(f),
Self::Io(error) => error.fmt(f),
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for DatabaseError {
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
impl core::error::Error for DecodeError {}
impl core::error::Error for EncodeError {}
impl core::error::Error for BuildError {}
impl core::error::Error for ParseError {}
impl core::error::Error for CompileError {}
impl core::error::Error for ExpandError {}
impl core::error::Error for ConvertError {}
#[cfg(feature = "std")]
impl core::error::Error for DatabaseError {}