1use crate::{Map, Num, Tag, Val};
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5use core::fmt::{self, Display, Formatter};
6use toml_edit::{Document, DocumentMut, Formatted, Item, Table, Value};
7
8pub fn parse(s: &str) -> Result<Val, PError> {
10 table(s.parse::<Document<String>>()?.into_table())
11}
12
13#[derive(Debug)]
15pub enum SError {
16 Key(Val),
18 Root(Val),
20 Val(Val),
22}
23
24impl fmt::Display for SError {
25 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
26 match self {
27 Self::Key(v) => write!(f, "TOML keys must be strings, found: {v}"),
28 Self::Root(v) => write!(f, "TOML root must be an object, found: {v}"),
29 Self::Val(v) => write!(f, "could not encode {v} as TOML value"),
30 }
31 }
32}
33
34#[derive(Debug)]
36pub struct PError(toml_edit::TomlError);
37
38impl From<toml_edit::TomlError> for PError {
39 fn from(e: toml_edit::TomlError) -> Self {
40 Self(e)
41 }
42}
43
44impl fmt::Display for PError {
45 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
46 self.0.fmt(f)
47 }
48}
49
50impl std::error::Error for PError {}
51
52impl std::error::Error for SError {}
53
54fn value(v: Value) -> Result<Val, PError> {
55 Ok(match v {
56 Value::String(s) => Val::Str(s.into_value().into(), Tag::Utf8),
57 Value::Integer(i) => Val::Num(Num::from_integral(i.into_value())),
58 Value::Float(f) => Val::Num(Num::Float(f.into_value())),
59 Value::Boolean(b) => Val::Bool(b.into_value()),
60 Value::Array(a) => return a.into_iter().map(value).collect(),
61 Value::InlineTable(t) => return table(t.into_table()),
62 Value::Datetime(d) => Val::Str(d.into_value().to_string().into(), Tag::Utf8),
63 })
64}
65
66fn item(item: Item) -> Result<Val, PError> {
67 match item {
68 Item::None => panic!(),
70 Item::Value(v) => value(v),
71 Item::Table(t) => table(t),
72 Item::ArrayOfTables(a) => a.into_array().into_iter().map(value).collect(),
73 }
74}
75
76fn table(t: Table) -> Result<Val, PError> {
77 t.into_iter()
78 .map(|(k, v)| Ok((k.into(), item(v)?)))
79 .collect::<Result<_, _>>()
80 .map(Val::obj)
81}
82
83pub struct Toml(DocumentMut);
85
86impl Display for Toml {
87 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88 self.0.fmt(f)
89 }
90}
91
92impl TryFrom<&Val> for Toml {
93 type Error = SError;
94 fn try_from(v: &Val) -> Result<Self, Self::Error> {
95 let obj = val_obj(v).ok_or_else(|| SError::Root(v.clone()));
96 obj.and_then(obj_table).map(DocumentMut::from).map(Self)
97 }
98}
99
100fn obj_table(o: &Map) -> Result<Table, SError> {
101 use jaq_std::ValT;
102 let kvs = o.iter().map(|(k, v)| {
103 let k = k.as_utf8_bytes().ok_or_else(|| SError::Key(k.clone()))?;
104 Ok((String::from_utf8_lossy(k).into_owned(), val_item(v)?))
105 });
106 kvs.collect::<Result<_, _>>()
107}
108
109fn val_obj(v: &Val) -> Option<&Map> {
110 match v {
111 Val::Obj(o) => Some(o),
112 _ => None,
113 }
114}
115
116fn val_item(v: &Val) -> Result<Item, SError> {
117 if let Val::Obj(o) = v {
118 return obj_table(o).map(Item::Table);
119 } else if let Val::Arr(a) = v {
120 if let Some(objs) = a.iter().map(val_obj).collect::<Option<Vec<_>>>() {
121 let tables = objs.into_iter().map(obj_table);
122 return tables.collect::<Result<_, _>>().map(Item::ArrayOfTables);
123 }
124 }
125 val_value(v).map(Item::Value)
126}
127
128fn val_value(v: &Val) -> Result<Value, SError> {
129 let fail = || SError::Val(v.clone());
130 Ok(match v {
131 Val::Null | Val::Str(_, Tag::Bytes) => Err(fail())?,
132 Val::Bool(b) => Value::Boolean(Formatted::new(*b)),
133 Val::Str(s, Tag::Utf8) => {
134 Value::String(Formatted::new(String::from_utf8_lossy(s).into_owned()))
135 }
136 Val::Num(Num::Float(f)) => Value::Float(Formatted::new(*f)),
137 Val::Num(Num::Dec(n)) => val_value(&Val::Num(Num::from_dec_str(n)))?,
138 Val::Num(n @ (Num::Int(_) | Num::BigInt(_))) => {
139 let from_int = |n: &Num| n.as_isize()?.try_into().ok();
140 Value::Integer(Formatted::new(from_int(n).ok_or_else(fail)?))
141 }
142 Val::Arr(a) => a
143 .iter()
144 .map(val_value)
145 .collect::<Result<_, _>>()
146 .map(Value::Array)?,
147 Val::Obj(o) => obj_table(o).map(|t| Value::InlineTable(Table::into_inline_table(t)))?,
148 })
149}