// 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 } /// [`AttrSpan`] for this attribute. /// /// The attribute span allows deriving a number of different spans; /// see [`AttrSpan`] for more information. pub fn attr_span(&self) -> &AttrSpan { match self { Attr(.., span) => span, } } } impl Token for Attr { fn ir_name() -> &'static str { // This may be used by multiple things, // but it's primarily used by XIRF. "XIRF" } 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 { match self { Self(key, value, _) => write!(f, "`@{key}=\"{value}\"`"), } } } /// 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 } pub fn extend>(mut self, iter: T) -> Self { self.attrs.extend(iter); 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); } }