use substrate_data::binary::{Decoder, Writer};
use substrate_data::frame::{Packer, Unpacker};
use substrate_data::schema::{Expansion, NamedType};
use substrate_data::typed::{Spelled, classify};
use substrate_data::{Id, Kind, Reader, Ref, Schema, Shape, Value, ValueRef};
use crate::{Res, kinds};
pub type Lang = Id<kinds::Lang>;
#[derive(Debug, Clone, PartialEq)]
pub enum Text {
Literal(String),
Res(Res),
NameOf(Ref),
Lang(String),
}
impl Default for Text {
fn default() -> Self {
Text::Literal(String::new())
}
}
impl Text {
pub fn to_ref(&self) -> Option<Ref> {
match self {
Text::Literal(_) => None,
Text::Res(r) => Some(Id::<kinds::Res>::new(r.name()).to_ref()),
Text::NameOf(r) => Some(r.clone()),
Text::Lang(k) => Some(Lang::new(k.clone()).to_ref()),
}
}
pub fn resolve(self, cx: &mut Reader<'_>) -> Option<String> {
let Some(r) = self.to_ref() else {
return match self {
Text::Literal(s) => Some(s),
_ => None,
};
};
match cx.expand_ref(&r) {
Expansion::Kept => Some(r.to_string()),
Expansion::Value(Value::Str(s)) => Some(s),
Expansion::Value(other) => {
cx.problem(format!("`{r}` expands to {}, not a text", other.kind()));
None
}
Expansion::Failed => None,
}
}
pub fn of_ref(r: Ref) -> Option<Text> {
let kind = r.kind.as_str();
if kind == kinds::Res::NAME {
return Res::parse(r.id.as_str()).map(Text::Res);
}
if kind == kinds::Lang::NAME {
return Some(Text::Lang(r.id.as_str().to_owned()));
}
Some(Text::NameOf(r))
}
}
impl NamedType for Text {
const NAME: &'static str = yog_ids::typed::TEXT;
type Stored = Value;
fn to_stored(&self) -> Value {
match self {
Text::Literal(s) => Value::Str(substrate_data::typed::escape_at(s)),
Text::Res(r) => substrate_data::Schema::write(r),
Text::NameOf(r) => Value::Ref(r.clone()),
Text::Lang(k) => substrate_data::Schema::write(&Lang::new(k.clone())),
}
}
fn from_stored(stored: Value) -> Option<Self> {
match stored {
Value::Ref(r) => Text::of_ref(r),
Value::Str(s) => match classify(&s).ok()? {
Spelled::Plain(p) => Some(Text::Literal(p)),
Spelled::Ref(r) => Text::of_ref(r),
Spelled::Bytes(_) => None,
},
_ => None,
}
}
}
impl Schema for Text {
fn shape() -> Shape {
Shape::named::<Text>()
}
fn read(v: &ValueRef<'_>, cx: &mut Reader<'_>) -> Option<Self> {
let is_ref = match v {
ValueRef::Ref { .. } => true,
ValueRef::Str(s) => matches!(classify(s), Ok(Spelled::Ref(_))),
_ => false,
};
if !is_ref {
return String::read(v, cx).map(Text::Literal);
}
let r = substrate_data::schema::read_ref(v, cx, &[])?;
Text::of_ref(r.clone()).or_else(|| {
cx.problem(format!("`{r}` isn't a text"));
None
})
}
fn write(&self) -> Value {
self.to_stored()
}
fn encode(&self, w: &mut Writer) {
self.to_stored().encode(w);
}
fn decode(d: &mut Decoder<'_>) -> Option<Self> {
Value::decode(d).and_then(Text::from_stored)
}
fn pack(&self, p: &mut Packer) {
self.to_stored().pack(p);
}
fn unpack(u: &mut Unpacker<'_>) -> Option<Self> {
Value::unpack(u).and_then(Text::from_stored)
}
}
#[derive(Debug)]
pub struct TextOf;
impl substrate_data::with::With<String> for TextOf {
fn shape() -> Shape {
Text::shape()
}
fn read(v: &ValueRef<'_>, cx: &mut Reader<'_>) -> Option<String> {
Text::read(v, cx)?.resolve(cx)
}
fn write(f: &String) -> Value {
Text::Literal(f.clone()).write()
}
}