tame/tamer/src/xir/attr.rs

263 lines
7.1 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

// 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 <http://www.gnu.org/licenses/>.
//! 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
/// <foo bar = "baz" />
/// [-] [+-+]
/// 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<Attr>,
}
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<Vec<Attr>> for AttrList {
fn from(attrs: Vec<Attr>) -> Self {
AttrList { attrs }
}
}
impl FromIterator<Attr> for AttrList {
fn from_iter<T: IntoIterator<Item = Attr>>(iter: T) -> Self {
iter.into_iter().collect::<Vec<Attr>>().into()
}
}
impl<const N: usize> 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);
}
}