#![allow(dead_code, reason = "consumed by the tree builder and serde units")]
pub(crate) mod conflict;
mod scan;
#[cfg(test)]
mod tests;
use oneyaml_core::{Schema, Settings};
use crate::value::{Number, TagStr};
use scan::Ext;
const YAML_PREFIX: &str = "tag:yaml.org,2002:";
pub(crate) const MERGE_TAG: &str = "tag:yaml.org,2002:merge";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum TagInput<'t> {
Absent,
NonSpecific,
Named(TagStr<'t>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Style {
Plain,
NonPlain,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum CoreTag {
Null,
Bool,
Int,
Float,
Str,
Seq,
Map,
}
impl CoreTag {
fn parse(tag: TagStr<'_>) -> Option<Self> {
if tag.len() > YAML_PREFIX.len().saturating_add(5) {
return None;
}
let rest = tag.strip_prefix(YAML_PREFIX)?;
let mut name = [0u8; 5];
let mut n = 0usize;
for b in rest.bytes() {
*name.get_mut(n)? = b;
n = n.saturating_add(1);
}
Some(match name.get(..n)? {
b"null" => Self::Null,
b"bool" => Self::Bool,
b"int" => Self::Int,
b"float" => Self::Float,
b"str" => Self::Str,
b"seq" => Self::Seq,
b"map" => Self::Map,
_ => return None,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Resolved<'t> {
Null,
Bool(bool),
Number(Number),
Str,
Custom(TagStr<'t>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ResolvedCollection<'t> {
Plain,
Custom(TagStr<'t>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum CollectionKind {
Sequence,
Mapping,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ResolveError {
InvalidForTag(CoreTag),
CollectionTagOnScalar(CoreTag),
WrongTagOnCollection(CoreTag),
JsonUnmatched,
Internal,
}
mod sealed {
pub trait Sealed {}
}
pub(crate) trait ResolveSchema: sealed::Sealed {
fn plain(&self, text: &str) -> Result<Resolved<'static>, ResolveError>;
fn recognises(&self, tag: CoreTag) -> bool;
fn construct(&self, tag: CoreTag, text: &str) -> Result<Resolved<'static>, ResolveError>;
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Core {
ext: Ext,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Json;
#[derive(Clone, Copy, Debug)]
pub(crate) struct Failsafe;
impl sealed::Sealed for Core {}
impl sealed::Sealed for Json {}
impl sealed::Sealed for Failsafe {}
type Outcome = Result<Resolved<'static>, ResolveError>;
fn num(n: Option<Number>) -> Option<Outcome> {
n.map(|n| Ok(Resolved::Number(n)))
}
fn float(f: Option<Result<f64, ResolveError>>) -> Option<Outcome> {
f.map(|f| f.map(|f| Resolved::Number(Number::from(f))))
}
fn ok(r: Option<Resolved<'static>>) -> Option<Outcome> {
r.map(Ok)
}
impl ResolveSchema for Core {
fn plain(&self, t: &str) -> Result<Resolved<'static>, ResolveError> {
if scan::core_null(t) {
return Ok(Resolved::Null);
}
if let Some(b) = scan::ext_bool(t, self.ext) {
return Ok(Resolved::Bool(b));
}
num(scan::core_int(t, self.ext))
.or_else(|| float(scan::core_float(t, self.ext)))
.unwrap_or(Ok(Resolved::Str))
}
fn recognises(&self, _: CoreTag) -> bool {
true
}
fn construct(&self, tag: CoreTag, t: &str) -> Result<Resolved<'static>, ResolveError> {
let r = match tag {
CoreTag::Null => ok(scan::core_null(t).then_some(Resolved::Null)),
CoreTag::Bool => ok(scan::ext_bool(t, self.ext).map(Resolved::Bool)),
CoreTag::Int => num(scan::core_int(t, self.ext)),
CoreTag::Float => float(scan::core_float(t, self.ext)),
CoreTag::Str => Some(Ok(Resolved::Str)),
CoreTag::Seq | CoreTag::Map => {
return Err(ResolveError::CollectionTagOnScalar(tag));
}
};
r.unwrap_or(Err(ResolveError::InvalidForTag(tag)))
}
}
impl ResolveSchema for Json {
fn plain(&self, t: &str) -> Result<Resolved<'static>, ResolveError> {
if scan::json_null(t) {
return Ok(Resolved::Null);
}
if let Some(b) = scan::json_bool(t) {
return Ok(Resolved::Bool(b));
}
num(scan::json_int(t))
.or_else(|| float(scan::json_float(t)))
.unwrap_or(Err(ResolveError::JsonUnmatched))
}
fn recognises(&self, _: CoreTag) -> bool {
true
}
fn construct(&self, tag: CoreTag, t: &str) -> Result<Resolved<'static>, ResolveError> {
let r = match tag {
CoreTag::Null => ok(scan::json_null(t).then_some(Resolved::Null)),
CoreTag::Bool => ok(scan::json_bool(t).map(Resolved::Bool)),
CoreTag::Int => num(scan::json_int(t)),
CoreTag::Float => float(scan::json_float(t)),
CoreTag::Str => Some(Ok(Resolved::Str)),
CoreTag::Seq | CoreTag::Map => {
return Err(ResolveError::CollectionTagOnScalar(tag));
}
};
r.unwrap_or(Err(ResolveError::InvalidForTag(tag)))
}
}
impl ResolveSchema for Failsafe {
fn plain(&self, _: &str) -> Result<Resolved<'static>, ResolveError> {
Ok(Resolved::Str)
}
fn recognises(&self, tag: CoreTag) -> bool {
matches!(tag, CoreTag::Str | CoreTag::Seq | CoreTag::Map)
}
fn construct(&self, tag: CoreTag, _: &str) -> Result<Resolved<'static>, ResolveError> {
match tag {
CoreTag::Str => Ok(Resolved::Str),
CoreTag::Seq | CoreTag::Map => Err(ResolveError::CollectionTagOnScalar(tag)),
_ => {
debug_assert!(false, "failsafe asked to construct {tag:?}");
Err(ResolveError::Internal)
}
}
}
}
#[derive(Clone, Copy, Debug)]
enum Active {
Core(Core),
Json(Json),
Failsafe(Failsafe),
Unsupported,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Resolver {
active: Active,
merge_keys: bool,
variant_content: Option<Core>,
}
impl Resolver {
pub(crate) fn new(settings: &Settings) -> Self {
Self::for_document(settings, false)
}
pub(crate) fn for_document(settings: &Settings, yaml_1_1_directive: bool) -> Self {
let switch = yaml_1_1_directive && settings.get_honor_version_directive();
let ext = Ext {
booleans: switch || settings.get_yaml11_booleans(),
legacy_octal: switch || settings.get_legacy_octal(),
sexagesimal: switch || settings.get_sexagesimal(),
separators: switch || settings.get_digit_separators(),
int_forms: switch || settings.get_yaml11_int_forms(),
};
let active = match settings.get_schema() {
Schema::Core => Active::Core(Core { ext }),
Schema::Json => Active::Json(Json),
Schema::Failsafe => Active::Failsafe(Failsafe),
_ => {
debug_assert!(false, "no resolver for {:?}", settings.get_schema());
Active::Unsupported
}
};
Self {
active,
merge_keys: switch || settings.get_merge_keys(),
variant_content: settings
.get_resolve_tagged_variant_content()
.then_some(Core { ext }),
}
}
fn schema(&self) -> Result<&dyn ResolveSchema, ResolveError> {
match &self.active {
Active::Core(s) => Ok(s),
Active::Json(s) => Ok(s),
Active::Failsafe(s) => Ok(s),
Active::Unsupported => Err(ResolveError::Internal),
}
}
pub(crate) fn resolve_scalar<'t>(
&self,
tag: TagInput<'t>,
style: Style,
text: &str,
) -> Result<Resolved<'t>, ResolveError> {
let schema = self.schema()?;
match tag {
TagInput::Absent => match style {
Style::Plain => schema.plain(text),
Style::NonPlain => Ok(Resolved::Str),
},
TagInput::NonSpecific => Ok(Resolved::Str),
TagInput::Named(t) => match CoreTag::parse(t) {
Some(ct) if schema.recognises(ct) => schema.construct(ct, text),
_ => Ok(Resolved::Custom(t)),
},
}
}
pub(crate) fn resolve_collection<'t>(
&self,
tag: TagInput<'t>,
kind: CollectionKind,
) -> Result<ResolvedCollection<'t>, ResolveError> {
let schema = self.schema()?;
let TagInput::Named(t) = tag else {
return Ok(ResolvedCollection::Plain);
};
match CoreTag::parse(t) {
Some(ct) if schema.recognises(ct) => match (ct, kind) {
(CoreTag::Seq, CollectionKind::Sequence)
| (CoreTag::Map, CollectionKind::Mapping) => Ok(ResolvedCollection::Plain),
_ => Err(ResolveError::WrongTagOnCollection(ct)),
},
_ => Ok(ResolvedCollection::Custom(t)),
}
}
pub(crate) fn is_merge_key(&self, tag: TagInput<'_>, style: Style, text: &str) -> bool {
self.merge_keys
&& match tag {
TagInput::Absent => style == Style::Plain && text == "<<",
TagInput::Named(t) => t.is(MERGE_TAG),
TagInput::NonSpecific => false,
}
}
pub(crate) fn variant_content(
&self,
tag: TagStr<'_>,
style: Style,
text: &str,
) -> Result<Option<Resolved<'static>>, ResolveError> {
match self.variant_content {
Some(core) if self.resolves_variant_content(tag, style) => core.plain(text).map(Some),
_ => Ok(None),
}
}
pub(crate) fn resolves_variant_content(&self, tag: TagStr<'_>, style: Style) -> bool {
self.variant_content.is_some()
&& style == Style::Plain
&& tag.len() > 1
&& tag.starts_with("!")
}
pub(crate) fn merge_keys(&self) -> bool {
self.merge_keys
}
pub(crate) fn plain_is_core(&self) -> bool {
matches!(self.active, Active::Core(Core { ext }) if ext == Ext::default())
}
}