1#![doc(
2 html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
3 html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
4)]
5use anyhow::{bail, Result};
6use arcstr::ArcStr;
7use bytes::Bytes;
8use chrono::Utc;
9use graphix_compiler::{
10 errf, typ::FnType, typ::Type, ExecCtx, Node, Rt, Scope, TypecheckPhase, UserEvent,
11};
12use graphix_package_core::{
13 extract_cast_type, is_struct, CachedArgs, CachedArgsAsync, CachedVals, EvalCached,
14 EvalCachedAsync,
15};
16use graphix_package_sys::{get_stream, StreamKind};
17use netidx_value::{PBytes, ValArray, Value};
18use poolshark::local::LPooled;
19use std::sync::Arc;
20use tokio::{io::AsyncReadExt, io::AsyncWriteExt, sync::Mutex};
21use triomphe::Arc as TArc;
22
23fn toml_to_value(v: toml::Value) -> Value {
26 match v {
27 toml::Value::String(s) => Value::String(ArcStr::from(s.as_str())),
28 toml::Value::Integer(i) => Value::I64(i),
29 toml::Value::Float(f) => Value::F64(f),
30 toml::Value::Boolean(b) => Value::Bool(b),
31 toml::Value::Datetime(dt) => {
32 let s = dt.to_string();
33 match chrono::DateTime::parse_from_rfc3339(&s) {
34 Ok(parsed) => Value::DateTime(TArc::new(parsed.with_timezone(&Utc))),
35 Err(_) => Value::String(ArcStr::from(s.as_str())),
36 }
37 }
38 toml::Value::Array(arr) => {
39 let mut vals: LPooled<Vec<Value>> =
40 arr.into_iter().map(toml_to_value).collect();
41 Value::Array(ValArray::from_iter_exact(vals.drain(..)))
42 }
43 toml::Value::Table(table) => {
44 let mut pairs: LPooled<Vec<(String, Value)>> =
45 table.into_iter().map(|(k, v)| (k, toml_to_value(v))).collect();
46 pairs.sort_by(|a, b| a.0.cmp(&b.0));
47 let mut vals: LPooled<Vec<Value>> = pairs
48 .drain(..)
49 .map(|(k, v)| {
50 Value::Array(ValArray::from([
51 Value::String(ArcStr::from(k.as_str())),
52 v,
53 ]))
54 })
55 .collect();
56 Value::Array(ValArray::from_iter_exact(vals.drain(..)))
57 }
58 }
59}
60
61fn value_to_toml(value: &Value) -> Result<toml::Value, String> {
62 match value {
63 Value::Null => Err("cannot represent null in TOML".into()),
64 Value::Bool(b) => Ok(toml::Value::Boolean(*b)),
65 Value::I8(n) => Ok(toml::Value::Integer(i64::from(*n))),
66 Value::I16(n) => Ok(toml::Value::Integer(i64::from(*n))),
67 Value::I32(n) => Ok(toml::Value::Integer(i64::from(*n))),
68 Value::I64(n) => Ok(toml::Value::Integer(*n)),
69 Value::U8(n) => Ok(toml::Value::Integer(i64::from(*n))),
70 Value::U16(n) => Ok(toml::Value::Integer(i64::from(*n))),
71 Value::U32(n) => Ok(toml::Value::Integer(i64::from(*n))),
72 Value::U64(n) => i64::try_from(*n)
73 .map(toml::Value::Integer)
74 .map_err(|_| format!("u64 value {n} exceeds TOML i64 range")),
75 Value::V32(n) => Ok(toml::Value::Integer(i64::from(*n))),
76 Value::V64(n) => i64::try_from(*n)
77 .map(toml::Value::Integer)
78 .map_err(|_| format!("v64 value {n} exceeds TOML i64 range")),
79 Value::Z32(n) => Ok(toml::Value::Integer(i64::from(*n))),
80 Value::Z64(n) => Ok(toml::Value::Integer(*n)),
81 Value::F32(n) => Ok(toml::Value::Float(*n as f64)),
82 Value::F64(n) => Ok(toml::Value::Float(*n)),
83 Value::String(s) => Ok(toml::Value::String(s.to_string())),
84 Value::DateTime(dt) => {
85 let s = dt.to_rfc3339();
86 s.parse()
87 .map(toml::Value::Datetime)
88 .map_err(|e| format!("cannot convert datetime to TOML: {e}"))
89 }
90 Value::Array(arr) => {
91 if is_struct(arr) {
92 let mut table = toml::map::Map::new();
93 for v in arr.iter() {
94 if let Value::Array(pair) = v {
95 if let Value::String(k) = &pair[0] {
96 table.insert(k.to_string(), value_to_toml(&pair[1])?);
97 }
98 }
99 }
100 Ok(toml::Value::Table(table))
101 } else {
102 let mut vals: LPooled<Vec<toml::Value>> =
103 arr.iter().map(value_to_toml).collect::<Result<_, _>>()?;
104 Ok(toml::Value::Array(vals.drain(..).collect()))
105 }
106 }
107 Value::Bytes(_) => Err("cannot represent bytes in TOML".into()),
108 Value::Duration(_) => Err("cannot represent duration in TOML".into()),
109 Value::Decimal(_) => Err("cannot represent decimal in TOML".into()),
110 Value::Map(_) => Err("cannot represent map in TOML".into()),
111 Value::Error(_) => Err("cannot serialize Error to TOML".into()),
112 Value::Abstract(_) => Err("cannot serialize abstract type to TOML".into()),
113 }
114}
115
116#[derive(Debug)]
119enum ReadInput {
120 Str(ArcStr),
121 Bytes(Bytes),
122 Stream(Arc<Mutex<Option<StreamKind>>>),
123}
124
125#[derive(Debug, Default)]
126struct TomlReadEv {
127 cast_typ: Option<Type>,
128}
129
130impl EvalCachedAsync for TomlReadEv {
131 const NAME: &str = "toml_read";
132 const NEEDS_CALLSITE: bool = true;
133 type Args = ReadInput;
134
135 fn init<R: Rt, E: UserEvent>(
136 _ctx: &mut ExecCtx<R, E>,
137 _typ: &FnType,
138 resolved: Option<&FnType>,
139 _scope: &Scope,
140 _from: &[Node<R, E>],
141 _top_id: graphix_compiler::expr::ExprId,
142 ) -> Self {
143 Self { cast_typ: extract_cast_type(resolved) }
144 }
145
146 fn typecheck<R: Rt, E: UserEvent>(
147 &mut self,
148 _ctx: &mut ExecCtx<R, E>,
149 _from: &mut [Node<R, E>],
150 phase: TypecheckPhase<'_>,
151 ) -> Result<()> {
152 match phase {
153 TypecheckPhase::Lambda => Ok(()),
154 TypecheckPhase::CallSite(resolved) => {
155 self.cast_typ = extract_cast_type(Some(resolved));
156 if self.cast_typ.is_none() {
157 bail!("toml::read requires a concrete return type")
158 }
159 Ok(())
160 }
161 }
162 }
163
164 fn map_value<R: Rt, E: UserEvent>(
165 &mut self,
166 ctx: &mut ExecCtx<R, E>,
167 v: Value,
168 ) -> Option<Value> {
169 match &self.cast_typ {
170 Some(typ) => Some(typ.cast_value(&ctx.env, v)),
171 None => Some(errf!("TomlErr", "no concrete return type found")),
172 }
173 }
174
175 fn prepare_args(&mut self, cached: &CachedVals) -> Option<Self::Args> {
176 let v = cached.0.first()?.as_ref()?;
177 match v {
178 Value::String(s) => Some(ReadInput::Str(s.clone())),
179 Value::Bytes(b) => Some(ReadInput::Bytes((**b).clone())),
180 Value::Abstract(_) => Some(ReadInput::Stream(get_stream(cached, 0)?)),
181 _ => None,
182 }
183 }
184
185 fn eval(input: Self::Args) -> impl Future<Output = Value> + Send {
186 async move {
187 match input {
188 ReadInput::Str(s) => match toml::from_str::<toml::Value>(&s) {
189 Ok(t) => toml_to_value(t),
190 Err(e) => errf!("TomlErr", "{e}"),
191 },
192 ReadInput::Bytes(b) => {
193 let s = match std::str::from_utf8(&b) {
194 Ok(s) => s,
195 Err(e) => return errf!("TomlErr", "invalid UTF-8: {e}"),
196 };
197 match toml::from_str::<toml::Value>(s) {
198 Ok(t) => toml_to_value(t),
199 Err(e) => errf!("TomlErr", "{e}"),
200 }
201 }
202 ReadInput::Stream(stream) => {
203 let mut guard = stream.lock().await;
204 let s = match guard.as_mut() {
205 Some(s) => s,
206 None => return errf!("IOErr", "stream unavailable"),
207 };
208 let mut buf: LPooled<Vec<u8>> = LPooled::take();
209 if let Err(e) = s.read_to_end(&mut buf).await {
210 return errf!("IOErr", "read failed: {e}");
211 }
212 let text = match std::str::from_utf8(&buf) {
213 Ok(s) => s,
214 Err(e) => return errf!("TomlErr", "invalid UTF-8: {e}"),
215 };
216 match toml::from_str::<toml::Value>(text) {
217 Ok(t) => toml_to_value(t),
218 Err(e) => errf!("TomlErr", "{e}"),
219 }
220 }
221 }
222 }
223 }
224}
225
226type TomlRead = CachedArgsAsync<TomlReadEv>;
227
228#[derive(Debug, Default)]
231struct TomlWriteStrEv;
232
233impl<R: Rt, E: UserEvent> EvalCached<R, E> for TomlWriteStrEv {
234 const NAME: &str = "toml_write_str";
235 const NEEDS_CALLSITE: bool = false;
236
237 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, cached: &CachedVals) -> Option<Value> {
238 let pretty = cached.get::<bool>(0)?;
239 let v = cached.0.get(1)?.as_ref()?;
240 let toml_val = match value_to_toml(v) {
241 Ok(t) => t,
242 Err(e) => return Some(errf!("TomlErr", "{e}")),
243 };
244 let res = if pretty {
245 toml::to_string_pretty(&toml_val)
246 } else {
247 toml::to_string(&toml_val)
248 };
249 Some(match res {
250 Ok(s) => Value::String(ArcStr::from(s.as_str())),
251 Err(e) => errf!("TomlErr", "{e}"),
252 })
253 }
254}
255
256type TomlWriteStr = CachedArgs<TomlWriteStrEv>;
257
258#[derive(Debug, Default)]
261struct TomlWriteBytesEv;
262
263impl<R: Rt, E: UserEvent> EvalCached<R, E> for TomlWriteBytesEv {
264 const NAME: &str = "toml_write_bytes";
265 const NEEDS_CALLSITE: bool = false;
266
267 fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, cached: &CachedVals) -> Option<Value> {
268 let pretty = cached.get::<bool>(0)?;
269 let v = cached.0.get(1)?.as_ref()?;
270 let toml_val = match value_to_toml(v) {
271 Ok(t) => t,
272 Err(e) => return Some(errf!("TomlErr", "{e}")),
273 };
274 let res = if pretty {
275 toml::to_string_pretty(&toml_val)
276 } else {
277 toml::to_string(&toml_val)
278 };
279 Some(match res {
280 Ok(s) => Value::Bytes(PBytes::new(Bytes::from(s.into_bytes()))),
281 Err(e) => errf!("TomlErr", "{e}"),
282 })
283 }
284}
285
286type TomlWriteBytes = CachedArgs<TomlWriteBytesEv>;
287
288#[derive(Debug, Default)]
291struct TomlWriteStreamEv;
292
293impl EvalCachedAsync for TomlWriteStreamEv {
294 const NAME: &str = "toml_write_stream";
295 const NEEDS_CALLSITE: bool = false;
296 type Args = (bool, Arc<Mutex<Option<StreamKind>>>, toml::Value);
297
298 fn prepare_args(&mut self, cached: &CachedVals) -> Option<Self::Args> {
299 let pretty = cached.get::<bool>(0)?;
300 let stream = get_stream(cached, 1)?;
301 let v = cached.0.get(2)?.as_ref()?;
302 let toml_val = value_to_toml(v).ok()?;
303 Some((pretty, stream, toml_val))
304 }
305
306 fn eval(
307 (pretty, stream, toml_val): Self::Args,
308 ) -> impl Future<Output = Value> + Send {
309 async move {
310 let s = if pretty {
311 toml::to_string_pretty(&toml_val)
312 } else {
313 toml::to_string(&toml_val)
314 };
315 let s = match s {
316 Ok(s) => s,
317 Err(e) => return errf!("TomlErr", "{e}"),
318 };
319 let mut guard = stream.lock().await;
320 let st = match guard.as_mut() {
321 Some(s) => s,
322 None => return errf!("IOErr", "stream unavailable"),
323 };
324 match st.write_all(s.as_bytes()).await {
325 Ok(()) => Value::Null,
326 Err(e) => errf!("IOErr", "write failed: {e}"),
327 }
328 }
329 }
330}
331
332type TomlWriteStream = CachedArgsAsync<TomlWriteStreamEv>;
333
334graphix_derive::defpackage! {
337 builtins => [
338 TomlRead,
339 TomlWriteStr,
340 TomlWriteBytes,
341 TomlWriteStream,
342 ],
343}