// XIRT attributes
//
// Copyright (C) 2014-2022 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//! XIRT attributes.
//!
//! Attributes are represented by [`Attr`].
//!
//! See [parent module](super) for additional documentation.
mod parse;
use super::QName;
use crate::{
parse::Token,
span::{Span, SpanLenSize},
sym::SymbolId,
};
use std::fmt::Display;
pub use parse::{AttrParseError, AttrParseState};
/// Element attribute.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attr(pub QName, pub SymbolId, pub AttrSpan);
/// Spans associated with attribute key and value.
///
/// The diagram below illustrates the behavior of `AttrSpan`.
/// Note that the extra spaces surrounding the `=` are intentional to
/// illustrate what the behavior ought to be.
/// Spans are represented by `|---|` intervals,
/// with the byte offset at each end,
/// and the single-letter span name centered below the interval.
/// `+` represents intersecting `-` and `|` lines.
///
/// ```text
///
/// |-| |+-+|
/// 5 7 13| |17
/// |K |Q|
/// | 14 16
/// | V |
/// |-----------|
/// A
/// ```
///
/// Above we have
///
/// - `A` = [`AttrSpan::span`];
/// - `K` = [`AttrSpan::key_span`];
/// - `V` = [`AttrSpan::value_span`]; and
/// - `Q` = [`AttrSpan::value_span_with_quotes`].
///
/// Note that this object assumes that the key and value span are adjacent
/// to one-another in the same [`span::Context`](crate::span::Context).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttrSpan(pub Span, pub Span);
impl AttrSpan {
/// A [`Span`] covering the entire attribute token,
/// including the key,
/// _quoted_ value,
/// and everything in-between.
pub fn span(&self) -> Span {
let AttrSpan(k, _) = self;
// TODO: Move much of this into `Span`.
k.context().span(
k.offset(),
self.value_span_with_quotes()
.endpoints_saturated()
.1
.offset()
.saturating_sub(k.offset())
.try_into()
.unwrap_or(SpanLenSize::MAX),
)
}
/// The span associated with the name of the key.
///
/// This does _not_ include the following `=` or any surrounding
/// whitespace.
pub fn key_span(&self) -> Span {
let AttrSpan(k, _) = self;
*k
}
/// The span associated with the string value _inside_ the quotes,
/// not including the quotes themselves.
///
/// See [`AttrSpan`]'s documentation for an example.
pub fn value_span(&self) -> Span {
let AttrSpan(_, v) = self;
*v
}
/// The span associated with the string value _including_ the
/// surrounding quotes.
///
/// See [`AttrSpan`]'s documentation for an example.
pub fn value_span_with_quotes(&self) -> Span {
let AttrSpan(_, v) = self;
v.context()
.span(v.offset().saturating_sub(1), v.len().saturating_add(2))
}
}
impl Attr {
/// Construct a new simple attribute with a name, value, and respective
/// [`Span`]s.
#[inline]
pub fn new(name: QName, value: SymbolId, span: (Span, Span)) -> Self {
Self(name, value, AttrSpan(span.0, span.1))
}
/// Attribute name.
#[inline]
pub fn name(&self) -> QName {
self.0
}
/// Retrieve the value from the attribute.
///
/// Since [`SymbolId`] implements [`Copy`],
/// this returns an owned value.
#[inline]
pub fn value(&self) -> SymbolId {
self.1
}
}
impl Token for Attr {
fn span(&self) -> Span {
match self {
Attr(.., attr_span) => attr_span.span(),
}
}
}
impl crate::parse::Object for Attr {}
impl Display for Attr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "`@{}=\"{}\"` at {}", self.0, self.1, self.2 .0)
}
}
/// List of attributes.
///
/// Attributes are ordered in XIR so that this IR will be suitable for code
/// formatters and linters.
///
/// This abstraction will allow us to manipulate the internal data so that
/// it is suitable for a particular task in the future
/// (e.g. O(1) lookups by attribute name).
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct AttrList {
attrs: Vec,
}
impl AttrList {
/// Construct a new, empty attribute list.
pub fn new() -> Self {
Self { attrs: vec![] }
}
/// Add an attribute to the end of the attribute list.
pub fn push(mut self, attr: Attr) -> Self {
self.attrs.push(attr);
self
}
/// Search for an attribute of the given `name`.
///
/// _You should use this method only when a linear search makes sense._
///
/// This performs an `O(n)` linear search in the worst case.
/// Future implementations may perform an `O(1)` lookup under certain
/// circumstances,
/// but this should not be expected.
pub fn find(&self, name: QName) -> Option<&Attr> {
self.attrs.iter().find(|attr| attr.name() == name)
}
/// Returns [`true`] if the list contains no attributes.
pub fn is_empty(&self) -> bool {
self.attrs.is_empty()
}
}
impl From> for AttrList {
fn from(attrs: Vec) -> Self {
AttrList { attrs }
}
}
impl FromIterator for AttrList {
fn from_iter>(iter: T) -> Self {
iter.into_iter().collect::>().into()
}
}
impl From<[Attr; N]> for AttrList {
fn from(attrs: [Attr; N]) -> Self {
AttrList {
attrs: attrs.into(),
}
}
}
#[cfg(test)]
mod test {
use crate::span::DUMMY_CONTEXT as DC;
use super::*;
// See docblock for [`AttrSpan`].
const A: Span = DC.span(5, 13); // Entire attribute token
const K: Span = DC.span(5, 3); // Key
const V: Span = DC.span(14, 3); // Value without quotes
const Q: Span = DC.span(13, 5); // Value with quotes
#[test]
fn attr_span_token() {
assert_eq!(AttrSpan(K, V).span(), A);
}
#[test]
fn attr_span_value_with_quotes() {
assert_eq!(AttrSpan(K, V).value_span_with_quotes(), Q);
}
#[test]
fn attr_span_key() {
assert_eq!(AttrSpan(K, V).key_span(), K);
}
#[test]
fn attr_span_value() {
assert_eq!(AttrSpan(K, V).value_span(), V);
}
}