use std::borrow::Cow;
#[cfg(feature = "properties")]
use std::cell::{Cell, RefCell};
#[cfg(feature = "properties")]
use std::collections::HashMap;
use std::mem;
use std::ops::{Deref, DerefMut};
#[cfg(feature = "properties")]
use std::rc::Rc;
use granit_parser::ScalarStyle;
use smallvec::{SmallVec, smallvec};
use super::error::Error;
#[cfg(feature = "properties")]
use super::options::PropertySyntax;
use super::tags::SfTag;
#[cfg(feature = "properties")]
use crate::Budget;
#[cfg(feature = "properties")]
use crate::budget::BudgetBreach;
use crate::location::{Location, Locations};
#[cfg(feature = "properties")]
#[derive(Clone)]
pub(crate) struct PropertyInterpolation {
property_map: Option<Rc<HashMap<String, String>>>,
syntax: PropertySyntax,
max_expansion_depth: usize,
max_total_work: usize,
total_work: Rc<Cell<usize>>,
breach: Rc<RefCell<Option<BudgetBreach>>>,
}
#[cfg(feature = "properties")]
impl PropertyInterpolation {
pub(super) fn new(
property_map: Option<Rc<HashMap<String, String>>>,
syntax: PropertySyntax,
budget: Option<&Budget>,
) -> Self {
let (max_expansion_depth, max_total_work) =
budget.map_or((usize::MAX, usize::MAX), |budget| {
(
budget.max_property_expansion_depth,
budget.max_total_property_interpolation_work,
)
});
let property_map = property_map
.or_else(|| (syntax == PropertySyntax::DockerCompose).then(|| Rc::new(HashMap::new())));
Self {
property_map,
syntax,
max_expansion_depth,
max_total_work,
total_work: Rc::new(Cell::new(0)),
breach: Rc::new(RefCell::new(None)),
}
}
pub(super) fn for_key(&self) -> Self {
let mut properties = self.clone();
if properties.syntax == PropertySyntax::DockerCompose {
properties.property_map = None;
}
properties
}
pub(super) fn property_map(&self) -> Option<&Rc<HashMap<String, String>>> {
self.property_map.as_ref()
}
pub(super) fn syntax(&self) -> PropertySyntax {
self.syntax
}
pub(super) fn max_expansion_depth(&self) -> usize {
self.max_expansion_depth
}
pub(super) fn max_total_work(&self) -> usize {
self.max_total_work
}
pub(super) fn total_work(&self) -> &Cell<usize> {
self.total_work.as_ref()
}
pub(super) fn record_breach(&self, breach: BudgetBreach) {
let mut recorded = self.breach.borrow_mut();
if recorded.is_none() {
*recorded = Some(breach);
}
}
pub(super) fn breach(&self) -> Option<BudgetBreach> {
self.breach.borrow().clone()
}
}
#[inline]
#[allow(deprecated)] pub(super) fn attach_alias_locations_if_missing(
mut err: Error,
alias_reference_location: Option<Location>,
defined_location: Location,
) -> Error {
let reference_location = alias_reference_location.unwrap_or(defined_location);
if reference_location != Location::UNKNOWN
&& defined_location != Location::UNKNOWN
&& reference_location != defined_location
{
if let Error::AliasError { locations, .. } = &mut err
&& locations.reference_location == reference_location
{
locations.defined_location = defined_location;
return err;
}
Error::AliasError {
msg: err.to_string(),
error: Box::new(err),
locations: Locations {
reference_location,
defined_location,
},
}
} else if err.location().is_some() {
err
} else {
let loc = if reference_location == Location::UNKNOWN {
defined_location
} else {
reference_location
};
err.with_location(loc)
}
}
#[derive(Clone, Debug)]
pub(crate) enum Ev<'a> {
Scalar {
value: Cow<'a, str>,
tag: SfTag,
raw_tag: Option<Cow<'a, str>>,
style: ScalarStyle,
anchor: usize,
location: Location,
},
SeqStart {
anchor: usize,
tag: SfTag,
raw_tag: Option<Cow<'a, str>>,
location: Location,
},
SeqEnd { location: Location },
MapStart {
anchor: usize,
tag: SfTag,
raw_tag: Option<Cow<'a, str>>,
location: Location,
},
MapEnd { location: Location },
RecursiveAlias { anchor: usize, location: Location },
Taken { location: Location },
}
impl Default for Ev<'_> {
fn default() -> Self {
Ev::Taken {
location: Location::UNKNOWN,
}
}
}
impl Ev<'_> {
pub(crate) fn location(&self) -> Location {
match self {
Ev::Scalar { location, .. }
| Ev::SeqStart { location, .. }
| Ev::SeqEnd { location }
| Ev::MapStart { location, .. }
| Ev::MapEnd { location }
| Ev::RecursiveAlias { location, .. }
| Ev::Taken { location } => *location,
}
}
pub(crate) fn owned_payload_bytes(&self) -> usize {
match self {
Ev::Scalar { value, raw_tag, .. } => {
let value_bytes = match value {
Cow::Borrowed(_) => 0,
Cow::Owned(value) => value.len(),
};
let raw_tag_bytes = match raw_tag {
Some(Cow::Owned(raw_tag)) => raw_tag.len(),
Some(Cow::Borrowed(_)) | None => 0,
};
value_bytes.saturating_add(raw_tag_bytes)
}
Ev::SeqStart { raw_tag, .. } | Ev::MapStart { raw_tag, .. } => match raw_tag {
Some(Cow::Owned(raw_tag)) => raw_tag.len(),
Some(Cow::Borrowed(_)) | None => 0,
},
Ev::SeqEnd { .. }
| Ev::MapEnd { .. }
| Ev::RecursiveAlias { .. }
| Ev::Taken { .. } => 0,
}
}
pub(super) fn strip_node_tag(&mut self) -> bool {
match self {
Ev::Scalar { tag, raw_tag, .. }
| Ev::SeqStart { tag, raw_tag, .. }
| Ev::MapStart { tag, raw_tag, .. } => {
*tag = SfTag::None;
*raw_tag = None;
true
}
Ev::SeqEnd { .. }
| Ev::MapEnd { .. }
| Ev::RecursiveAlias { .. }
| Ev::Taken { .. } => false,
}
}
pub(super) fn resolve_recursive_alias(&mut self) -> Result<(), Error> {
if let Self::RecursiveAlias { anchor, location } = *self {
if !crate::anchor_store::recursive_anchor_in_progress(anchor)
&& !crate::anchor_store::recursive_anchor_registered(anchor)
{
return Err(Error::RecursiveReferencesRequireWeakTypes { location });
}
*self = Self::Scalar {
value: Cow::Borrowed(""),
tag: SfTag::Null,
raw_tag: None,
style: ScalarStyle::Plain,
anchor,
location,
};
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) enum NodeOrigin {
#[default]
Direct,
Alias(Location),
Merge(Location),
}
#[derive(Debug, Default)]
pub(super) struct RecordedEvents<'a> {
events: Vec<Ev<'a>>,
origins: SmallVec<[(usize, NodeOrigin); 1]>,
}
impl<'a> RecordedEvents<'a> {
pub(super) fn new() -> Self {
Self::default()
}
pub(super) fn from_event(event: Ev<'a>, origin: NodeOrigin) -> Self {
Self {
events: vec![event],
origins: if origin == NodeOrigin::Direct {
SmallVec::new()
} else {
smallvec![(0, origin)]
},
}
}
pub(super) fn reserve(&mut self, additional: usize) {
self.events.reserve(additional);
}
pub(super) fn push(&mut self, event: Ev<'a>, origin: NodeOrigin) {
if origin != NodeOrigin::Direct {
self.origins.push((self.events.len(), origin));
}
self.events.push(event);
}
pub(super) fn extend(&mut self, mut other: Self) {
let offset = self.events.len();
self.origins.extend(
other
.origins
.drain(..)
.map(|(index, origin)| (offset + index, origin)),
);
self.events.append(&mut other.events);
}
pub(super) fn origin(&self, index: usize) -> NodeOrigin {
self.origins
.binary_search_by_key(&index, |(index, _)| *index)
.map_or(NodeOrigin::Direct, |index| self.origins[index].1)
}
}
impl<'a> From<Vec<Ev<'a>>> for RecordedEvents<'a> {
fn from(events: Vec<Ev<'a>>) -> Self {
Self {
events,
origins: SmallVec::new(),
}
}
}
impl<'a> Deref for RecordedEvents<'a> {
type Target = [Ev<'a>];
fn deref(&self) -> &Self::Target {
&self.events
}
}
impl DerefMut for RecordedEvents<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.events
}
}
pub(crate) trait Events<'de> {
fn defer_recursive_aliases(&mut self, defer: bool) -> bool;
fn next(&mut self) -> Result<Option<Ev<'de>>, Error>;
fn peek(&mut self) -> Result<Option<&Ev<'de>>, Error>;
fn strip_peeked_node_tag(&mut self) -> Result<(), Error>;
fn last_location(&self) -> Location;
fn reference_location(&self) -> Location;
fn node_origin(&self) -> NodeOrigin {
NodeOrigin::Direct
}
fn alias_reference_location(&self) -> Option<Location> {
match self.node_origin() {
NodeOrigin::Alias(location) => Some(location),
NodeOrigin::Direct | NodeOrigin::Merge(_) => None,
}
}
fn take_leading_comments_for_next_node(&mut self) -> Result<Vec<Cow<'de, str>>, Error> {
Ok(Vec::new())
}
fn take_separator_comments_before_mapping_value(
&mut self,
) -> Result<Vec<Cow<'de, str>>, Error> {
Ok(Vec::new())
}
fn take_separator_comments_before_sequence_item_value(
&mut self,
) -> Result<Vec<Cow<'de, str>>, Error> {
Ok(Vec::new())
}
#[cfg(feature = "comments")]
fn take_trailing_comments_after_node(&mut self) -> Result<Vec<Cow<'de, str>>, Error> {
Ok(Vec::new())
}
fn input_for_borrowing(&self) -> Option<&'de str> {
None }
#[cfg(feature = "properties")]
fn property_interpolation(&self) -> &PropertyInterpolation;
}
pub(super) fn with_deferred_recursive_aliases<'de, T>(
ev: &mut dyn Events<'de>,
capture: impl FnOnce(&mut dyn Events<'de>) -> Result<T, Error>,
) -> Result<T, Error> {
let previous = ev.defer_recursive_aliases(true);
let result = capture(ev);
ev.defer_recursive_aliases(previous);
result
}
#[cold]
pub(super) fn eof_with_loc(events: &dyn Events<'_>) -> Error {
Error::eof().with_location(events.last_location())
}
pub(super) struct ReplayEvents<'a> {
buf: RecordedEvents<'a>,
defer_recursive_aliases: bool,
idx: usize,
ref_override: Option<Location>,
#[cfg(feature = "properties")]
property_interpolation: PropertyInterpolation,
}
impl<'a> ReplayEvents<'a> {
pub(super) fn new(
buf: impl Into<RecordedEvents<'a>>,
#[cfg(feature = "properties")] property_interpolation: PropertyInterpolation,
) -> Self {
Self {
buf: buf.into(),
defer_recursive_aliases: false,
idx: 0,
ref_override: None,
#[cfg(feature = "properties")]
property_interpolation,
}
}
pub(super) fn with_reference(
buf: impl Into<RecordedEvents<'a>>,
reference: Location,
#[cfg(feature = "properties")] property_interpolation: PropertyInterpolation,
) -> Self {
Self {
buf: buf.into(),
defer_recursive_aliases: false,
idx: 0,
ref_override: Some(reference),
#[cfg(feature = "properties")]
property_interpolation,
}
}
}
impl<'a> Events<'a> for ReplayEvents<'a> {
fn defer_recursive_aliases(&mut self, defer: bool) -> bool {
mem::replace(&mut self.defer_recursive_aliases, defer)
}
fn next(&mut self) -> Result<Option<Ev<'a>>, Error> {
if self.idx >= self.buf.len() {
return Ok(None);
}
if !self.defer_recursive_aliases {
self.buf[self.idx].resolve_recursive_alias()?;
}
let location = self.buf[self.idx].location();
let ev = mem::replace(&mut self.buf[self.idx], Ev::Taken { location });
self.idx += 1;
Ok(Some(ev))
}
fn peek(&mut self) -> Result<Option<&Ev<'a>>, Error> {
if !self.defer_recursive_aliases
&& let Some(ev) = self.buf.get_mut(self.idx)
{
ev.resolve_recursive_alias()?;
}
Ok(self.buf.get(self.idx))
}
fn strip_peeked_node_tag(&mut self) -> Result<(), Error> {
let location = self
.buf
.get(self.idx)
.map_or_else(|| self.last_location(), Ev::location);
match self.buf.get_mut(self.idx) {
Some(event) => event
.strip_node_tag()
.then_some(())
.ok_or_else(|| Error::unexpected("tagged node").with_location(location)),
None => Err(Error::unexpected("tagged node").with_location(location)),
}
}
fn last_location(&self) -> Location {
let last = self.idx.saturating_sub(1);
self.buf.get(last).map_or(Location::UNKNOWN, Ev::location)
}
fn reference_location(&self) -> Location {
match self.node_origin() {
NodeOrigin::Alias(location) | NodeOrigin::Merge(location) => location,
NodeOrigin::Direct => self
.buf
.get(self.idx)
.map_or_else(|| self.last_location(), Ev::location),
}
}
fn node_origin(&self) -> NodeOrigin {
let origin = self.buf.origin(self.idx);
match (origin, self.ref_override) {
(NodeOrigin::Alias(_), _) => origin,
(_, Some(reference)) => NodeOrigin::Merge(reference),
(_, None) => origin,
}
}
#[cfg(feature = "properties")]
fn property_interpolation(&self) -> &PropertyInterpolation {
&self.property_interpolation
}
}
#[cfg(test)]
mod tests {
use super::*;
fn scalar(location: Location) -> Ev<'static> {
Ev::Scalar {
value: Cow::Borrowed("value"),
tag: SfTag::None,
raw_tag: None,
style: ScalarStyle::Plain,
anchor: 0,
location,
}
}
fn replay(
events: RecordedEvents<'static>,
reference: Option<Location>,
) -> ReplayEvents<'static> {
#[cfg(feature = "properties")]
let properties = PropertyInterpolation::new(None, PropertySyntax::Braced, None);
match reference {
Some(reference) => ReplayEvents::with_reference(
events,
reference,
#[cfg(feature = "properties")]
properties,
),
None => ReplayEvents::new(
events,
#[cfg(feature = "properties")]
properties,
),
}
}
#[test]
fn recorded_origins_follow_events_when_buffers_are_joined() {
let location = Location::new(1, 1);
let alias = NodeOrigin::Alias(Location::new(4, 3));
let merge = NodeOrigin::Merge(Location::new(7, 5));
let mut prefix = RecordedEvents::from(vec![scalar(location)]);
let mut suffix = RecordedEvents::new();
suffix.push(scalar(location), alias);
suffix.push(scalar(location), NodeOrigin::Direct);
suffix.push(scalar(location), merge);
prefix.extend(suffix);
assert_eq!(prefix.len(), 4);
assert_eq!(prefix.origin(0), NodeOrigin::Direct);
assert_eq!(prefix.origin(1), alias);
assert_eq!(prefix.origin(2), NodeOrigin::Direct);
assert_eq!(prefix.origin(3), merge);
assert_eq!(prefix.origin(4), NodeOrigin::Direct);
assert_eq!(prefix.origins.len(), 2);
}
#[test]
fn replay_keeps_each_direct_location_and_nested_alias_origin() {
let first = Location::new(1, 1);
let defined = Location::new(2, 3);
let alias = Location::new(5, 7);
let last = Location::new(8, 9);
let mut events = RecordedEvents::new();
events.push(scalar(first), NodeOrigin::Direct);
events.push(scalar(defined), NodeOrigin::Alias(alias));
events.push(scalar(last), NodeOrigin::Direct);
let mut replay = replay(events, None);
assert_eq!(replay.reference_location(), first);
assert_eq!(replay.alias_reference_location(), None);
replay.next().unwrap();
assert_eq!(replay.peek().unwrap().unwrap().location(), defined);
assert_eq!(replay.reference_location(), alias);
assert_eq!(replay.alias_reference_location(), Some(alias));
replay.next().unwrap();
assert_eq!(replay.reference_location(), last);
assert_eq!(replay.alias_reference_location(), None);
}
#[test]
fn merge_reference_is_not_an_alias_and_does_not_replace_captured_aliases() {
let defined = Location::new(1, 1);
let alias = Location::new(3, 5);
let merge = Location::new(6, 2);
let mut events = RecordedEvents::new();
events.push(scalar(defined), NodeOrigin::Direct);
events.push(scalar(defined), NodeOrigin::Alias(alias));
let mut replay = replay(events, Some(merge));
assert_eq!(replay.node_origin(), NodeOrigin::Merge(merge));
assert_eq!(replay.reference_location(), merge);
assert_eq!(replay.alias_reference_location(), None);
let error = attach_alias_locations_if_missing(
Error::msg("invalid value"),
replay.alias_reference_location(),
defined,
);
assert!(!matches!(error, Error::AliasError { .. }));
assert_eq!(error.location(), Some(defined));
replay.next().unwrap();
assert_eq!(replay.node_origin(), NodeOrigin::Alias(alias));
assert_eq!(replay.reference_location(), alias);
assert_eq!(replay.alias_reference_location(), Some(alias));
let error = attach_alias_locations_if_missing(
Error::msg("invalid value"),
replay.alias_reference_location(),
defined,
);
assert!(matches!(error, Error::AliasError { .. }));
}
}