1use crate::{
3 error::{ExportErrorKind, PointedExportErrorData},
4 eval::value::{ArrayData, Container, EnumVariantData, NickelValue, ValueContentRef},
5 identifier::{Ident, LocIdent},
6 metrics,
7 term::{IndexMap, Number, TypeAnnotation, record::RecordData},
8};
9
10use serde::{
11 de::{Deserialize, Deserializer},
12 ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
13};
14
15use malachite::base::{
16 num::{
17 basic::floats::PrimitiveFloat,
18 conversion::traits::{IsInteger, RoundingFrom},
19 },
20 rounding_modes::RoundingMode,
21};
22use once_cell::sync::Lazy;
23
24use std::{fmt, io};
25
26pub mod yaml;
27
28#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
30#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
31pub enum ExportFormat {
32 #[cfg_attr(feature = "clap", value(alias("raw")))]
35 Text,
36 #[default]
37 Json,
38 Yaml,
39 YamlDocuments,
43 Toml,
44}
45
46impl fmt::Display for ExportFormat {
47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48 match self {
49 Self::Text => write!(f, "text"),
50 Self::Json => write!(f, "json"),
51 Self::Yaml => write!(f, "yaml"),
52 Self::YamlDocuments => write!(f, "yaml-documents"),
53 Self::Toml => write!(f, "toml"),
54 }
55 }
56}
57
58#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
60#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
61pub enum MetadataExportFormat {
62 #[default]
63 Markdown,
64 Json,
65 Yaml,
66 Toml,
67}
68
69impl fmt::Display for MetadataExportFormat {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 match self {
72 Self::Markdown => write!(f, "markdown"),
73 Self::Json => write!(f, "json"),
74 Self::Yaml => write!(f, "yaml"),
75 Self::Toml => write!(f, "toml"),
76 }
77 }
78}
79
80#[derive(Clone, Eq, PartialEq, Debug)]
81pub struct ParseFormatError(String);
82
83impl fmt::Display for ParseFormatError {
84 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85 write!(f, "unsupported export format {}", self.0)
86 }
87}
88
89pub fn serialize_num<S>(n: &Number, serializer: S) -> Result<S::Ok, S::Error>
100where
101 S: Serializer,
102{
103 if n.is_integer() {
104 if *n < 0 {
105 if let Ok(n_as_integer) = i64::try_from(n) {
106 return n_as_integer.serialize(serializer);
107 }
108 } else if let Ok(n_as_uinteger) = u64::try_from(n) {
109 return n_as_uinteger.serialize(serializer);
110 }
111 }
112
113 f64::rounding_from(n, RoundingMode::Nearest)
114 .0
115 .serialize(serializer)
116}
117
118fn number_from_float<F: PrimitiveFloat, E: serde::de::Error>(float_value: F) -> Result<Number, E>
121where
122 Number: TryFrom<
123 F,
124 Error = malachite_q::conversion::from_primitive_float::RationalFromPrimitiveFloatError,
125 >,
126{
127 Number::try_from_float_simplest(float_value).map_err(|_| {
128 E::custom(format!(
129 "couldn't convert {float_value} to a Nickel number: Nickel doesn't support NaN nor infinity"
130 ))
131 })
132}
133
134pub fn deserialize_num<'de, D>(deserializer: D) -> Result<Number, D::Error>
136where
137 D: Deserializer<'de>,
138{
139 let as_f64 = f64::deserialize(deserializer)?;
140 number_from_float::<f64, D::Error>(as_f64)
141}
142
143pub fn serialize_annotated_value<S>(
145 _annot: &TypeAnnotation,
146 t: &NickelValue,
147 serializer: S,
148) -> Result<S::Ok, S::Error>
149where
150 S: Serializer,
151{
152 t.serialize(serializer)
153}
154
155pub fn serialize_record<S>(record: &RecordData, serializer: S) -> Result<S::Ok, S::Error>
157where
158 S: Serializer,
159{
160 let mut entries = record
161 .iter_serializable()
162 .collect::<Result<Vec<_>, _>>()
163 .map_err(|missing_def_err| {
164 serde::ser::Error::custom(format!(
165 "missing field definition for `{}`",
166 missing_def_err.id
167 ))
168 })?;
169
170 entries.sort_by_key(|(k, _)| *k);
171
172 let mut map_ser = serializer.serialize_map(Some(entries.len()))?;
173 for (id, t) in entries.iter() {
174 map_ser.serialize_entry(&id.to_string(), &t)?
175 }
176
177 map_ser.end()
178}
179
180pub fn deserialize_record<'de, D>(deserializer: D) -> Result<RecordData, D::Error>
182where
183 D: Deserializer<'de>,
184{
185 let fields = IndexMap::<LocIdent, _>::deserialize(deserializer)?;
186 Ok(RecordData::with_field_values(fields))
187}
188
189impl Serialize for NickelValue {
190 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
191 where
192 S: Serializer,
193 {
194 match self.content_ref() {
195 ValueContentRef::Null => serializer.serialize_none(),
196 ValueContentRef::Bool(b) => serializer.serialize_bool(b),
197 ValueContentRef::Number(n) => serialize_num(n, serializer),
198 ValueContentRef::String(s) => serializer.serialize_str(s),
199 ValueContentRef::EnumVariant(EnumVariantData { tag, arg: None }) => {
200 serializer.serialize_str(tag.label())
201 }
202 ValueContentRef::EnumVariant(EnumVariantData { tag, arg: Some(_) }) => {
203 Err(serde::ser::Error::custom(format!(
204 "cannot serialize enum variant `'{tag}` with non-empty argument"
205 )))
206 }
207 ValueContentRef::Record(Container::Empty) => {
208 let map_ser = serializer.serialize_map(Some(0))?;
209 map_ser.end()
210 }
211 ValueContentRef::Record(Container::Alloc(record)) => {
212 serialize_record(record, serializer)
213 }
214 ValueContentRef::Array(Container::Empty) => {
215 let seq_ser = serializer.serialize_seq(Some(0))?;
216 seq_ser.end()
217 }
218 ValueContentRef::Array(Container::Alloc(ArrayData { array, .. })) => {
219 let mut seq_ser = serializer.serialize_seq(Some(array.len()))?;
220 for elt in array.iter() {
221 seq_ser.serialize_element(elt)?
222 }
223 seq_ser.end()
224 }
225 _ => Err(serde::ser::Error::custom(format!(
226 "cannot serialize non-fully evaluated terms of type {}",
227 self.type_of().unwrap_or("unknown")
228 ))),
229 }
230 }
231}
232
233macro_rules! def_number_visitor {
236 ($($ty:ty),*) => {
237 $(
238 paste::paste! {
239 fn [<visit_ $ty>]<E>(self, v: $ty) -> Result<Self::Value, E>
240 where
241 E: serde::de::Error,
242 {
243 Ok(NickelValue::number_posless(v))
244 }
245 }
246 )*
247 };
248}
249
250impl<'de> Deserialize<'de> for NickelValue {
251 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252 where
253 D: Deserializer<'de>,
254 {
255 struct NickelValueVisitor;
256
257 impl<'de> serde::de::Visitor<'de> for NickelValueVisitor {
258 type Value = NickelValue;
259
260 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
261 formatter.write_str("a valid Nickel value")
262 }
263
264 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
265 where
266 E: serde::de::Error,
267 {
268 Ok(NickelValue::bool_value_posless(v))
269 }
270
271 def_number_visitor!(i8, u8, i16, u16, i32, u32, i64, u64, i128, u128);
272
273 fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
274 where
275 E: serde::de::Error,
276 {
277 let n = number_from_float::<f32, E>(v)?;
278 Ok(NickelValue::number_posless(n))
279 }
280
281 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
282 where
283 E: serde::de::Error,
284 {
285 let n = number_from_float::<f64, E>(v)?;
286 Ok(NickelValue::number_posless(n))
287 }
288
289 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
290 where
291 E: serde::de::Error,
292 {
293 Ok(NickelValue::string_posless(v))
294 }
295
296 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
297 where
298 E: serde::de::Error,
299 {
300 Ok(NickelValue::string_posless(v))
301 }
302
303 fn visit_none<E>(self) -> Result<Self::Value, E>
304 where
305 E: serde::de::Error,
306 {
307 Ok(NickelValue::null())
308 }
309
310 fn visit_unit<E>(self) -> Result<Self::Value, E>
311 where
312 E: serde::de::Error,
313 {
314 Ok(NickelValue::null())
315 }
316
317 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
318 where
319 A: serde::de::SeqAccess<'de>,
320 {
321 let mut elts = Vec::with_capacity(seq.size_hint().unwrap_or(0));
322
323 while let Some(elem) = seq.next_element()? {
324 elts.push(elem);
325 }
326
327 Ok(NickelValue::array_posless(
328 elts.into_iter().collect(),
329 Vec::new(),
330 ))
331 }
332
333 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
334 where
335 A: serde::de::MapAccess<'de>,
336 {
337 let mut fields = IndexMap::with_capacity(map.size_hint().unwrap_or(0));
338
339 while let Some((key, value)) = map.next_entry::<String, NickelValue>()? {
340 fields.insert(key.into(), value);
341 }
342
343 Ok(NickelValue::record_posless(RecordData::with_field_values(
344 fields,
345 )))
346 }
347 }
348
349 deserializer.deserialize_any(NickelValueVisitor)
350 }
351}
352
353#[derive(Debug, PartialEq, Clone)]
355pub enum NickelPointerElem {
356 Field(Ident),
357 Index(usize),
358}
359
360impl fmt::Display for NickelPointerElem {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 match self {
363 NickelPointerElem::Field(id) => write!(f, "{id}"),
364 NickelPointerElem::Index(i) => write!(f, "[{i}]"),
365 }
366 }
367}
368
369#[derive(Debug, PartialEq, Clone, Default)]
392pub struct NickelPointer(pub Vec<NickelPointerElem>);
393
394impl NickelPointer {
395 pub fn new() -> Self {
396 Self::default()
397 }
398}
399
400impl fmt::Display for NickelPointer {
401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402 let mut it = self.0.iter();
403 let Some(first) = it.next() else {
404 return Ok(());
405 };
406
407 write!(f, "{first}")?;
408
409 for elem in it {
410 if let NickelPointerElem::Field(_) = elem {
411 write!(f, ".{elem}")?
412 } else {
413 write!(f, "{elem}")?
414 }
415 }
416
417 Ok(())
418 }
419}
420
421pub fn validate(format: ExportFormat, value: &NickelValue) -> Result<(), PointedExportErrorData> {
424 static NUMBER_MIN: Lazy<Number> = Lazy::new(|| Number::try_from(f64::MIN).unwrap());
429 static NUMBER_MAX: Lazy<Number> = Lazy::new(|| Number::try_from(f64::MAX).unwrap());
430
431 fn do_validate(
450 format: ExportFormat,
451 value: &NickelValue,
452 ) -> Result<(), PointedExportErrorData> {
453 match value.content_ref() {
454 ValueContentRef::Null
456 if format == ExportFormat::Json || format == ExportFormat::Yaml =>
457 {
458 Ok(())
459 }
460 ValueContentRef::Null => {
461 Err(ExportErrorKind::UnsupportedNull(format, value.clone()).into())
462 }
463 ValueContentRef::Bool(_)
464 | ValueContentRef::Record(Container::Empty)
465 | ValueContentRef::Array(Container::Empty)
466 | ValueContentRef::String(_) => Ok(()),
467 ValueContentRef::EnumVariant(EnumVariantData { arg: None, .. }) => Ok(()),
468 ValueContentRef::EnumVariant(EnumVariantData { arg: Some(_), .. }) => {
469 Err(ExportErrorKind::NonSerializable(value.clone()).into())
470 }
471 ValueContentRef::Number(n) => {
472 if *n >= *NUMBER_MIN && *n <= *NUMBER_MAX {
473 Ok(())
474 } else {
475 Err(ExportErrorKind::NumberOutOfRange {
476 term: value.clone(),
477 value: n.clone(),
478 }
479 .into())
480 }
481 }
482 ValueContentRef::Record(Container::Alloc(record)) => {
483 record.iter_serializable().try_for_each(|binding| {
484 let (id, value) = binding.unwrap_or_else(|err| {
487 panic!(
488 "encountered field without definition `{}` \
489 during pre-serialization validation",
490 err.id
491 )
492 });
493
494 do_validate(format, value)
495 .map_err(|err| err.with_elem(NickelPointerElem::Field(id)))
496 })?;
497 Ok(())
498 }
499 ValueContentRef::Array(Container::Alloc(array_data)) => {
500 array_data
501 .array
502 .iter()
503 .enumerate()
504 .try_for_each(|(index, val)| {
505 do_validate(format, val)
506 .map_err(|err| err.with_elem(NickelPointerElem::Index(index)))
507 })?;
508 Ok(())
509 }
510 _ => Err(ExportErrorKind::NonSerializable(value.clone()).into()),
511 }
512 }
513
514 if format == ExportFormat::Text {
515 if value.as_string().is_some() {
516 Ok(())
517 } else {
518 Err(ExportErrorKind::NotAString(value.clone()).into())
519 }
520 } else {
521 let mut result = do_validate(format, value);
522
523 if let Err(PointedExportErrorData { path, .. }) = &mut result {
524 path.0.reverse();
525 }
526
527 result
528 }
529}
530
531pub fn to_writer_metadata<W, T>(
532 mut writer: W,
533 format: MetadataExportFormat,
534 item: &T,
535) -> Result<(), PointedExportErrorData>
536where
537 W: io::Write,
538 T: ?Sized + Serialize,
539{
540 match format {
542 MetadataExportFormat::Markdown => unimplemented!(),
543 MetadataExportFormat::Json => serde_json::to_writer_pretty(writer, &item)
544 .map_err(|err| ExportErrorKind::Other(err.to_string())),
545 MetadataExportFormat::Yaml => serde_yaml::to_writer(writer, &item)
546 .map_err(|err| ExportErrorKind::Other(err.to_string())),
547 MetadataExportFormat::Toml => toml::to_string_pretty(item)
548 .map_err(|err| ExportErrorKind::Other(err.to_string()))
549 .and_then(|s| {
550 writer
551 .write_all(s.as_bytes())
552 .map_err(|err| ExportErrorKind::Other(err.to_string()))
553 }),
554 }?;
555
556 Ok(())
557}
558
559pub fn to_writer<W>(
560 mut writer: W,
561 format: ExportFormat,
562 value: &NickelValue,
563) -> Result<(), PointedExportErrorData>
564where
565 W: io::Write,
566{
567 #[cfg(feature = "metrics")]
568 let start_time = std::time::Instant::now();
569
570 match format {
571 ExportFormat::Json => serde_json::to_writer_pretty(writer, &value)
572 .map_err(|err| ExportErrorKind::Other(err.to_string())),
573 ExportFormat::Yaml => serde_yaml::to_writer(writer, &value)
574 .map_err(|err| ExportErrorKind::Other(err.to_string())),
575 ExportFormat::YamlDocuments => {
576 if let Some(arr) = value.as_array() {
577 for value in arr.iter() {
578 writeln!(writer, "---")
579 .map_err(|err| ExportErrorKind::Other(err.to_string()))?;
580 serde_yaml::to_writer(&mut writer, value)
581 .map_err(|err| ExportErrorKind::Other(err.to_string()))?;
582 }
583 Ok(())
584 } else {
585 Err(ExportErrorKind::ExpectedArray {
586 value: value.clone(),
587 })
588 }
589 }
590 ExportFormat::Toml => toml::to_string_pretty(value)
591 .map_err(|err| ExportErrorKind::Other(err.to_string()))
592 .and_then(|s| {
593 writer
594 .write_all(s.as_bytes())
595 .map_err(|err| ExportErrorKind::Other(err.to_string()))
596 }),
597 ExportFormat::Text => match value.as_string() {
598 Some(s) => writer
599 .write_all(s.as_bytes())
600 .map_err(|err| ExportErrorKind::Other(err.to_string())),
601 _ => Err(ExportErrorKind::Other(format!(
602 "raw export requires a `String`, got {}",
603 value.type_of().unwrap()
606 ))),
607 },
608 }?;
609
610 metrics::increment!("runtime:serialize", start_time.elapsed().as_millis() as u64);
611
612 Ok(())
613}
614
615pub fn to_string(format: ExportFormat, rt: &NickelValue) -> Result<String, PointedExportErrorData> {
616 let mut buffer: Vec<u8> = Vec::new();
617 to_writer(&mut buffer, format, rt)?;
618
619 Ok(String::from_utf8_lossy(&buffer).into_owned())
620}
621
622pub mod toml_deser {
631 use crate::{
632 eval::value::NickelValue,
633 files::FileId,
634 identifier::LocIdent,
635 position::{PosTable, RawSpan, TermPos},
636 term::record::{RecordAttrs, RecordData},
637 };
638 use codespan::ByteIndex;
639 use malachite::{base::num::conversion::traits::ExactFrom as _, rational::Rational};
640 use nickel_lang_parser::ast::{
641 Ast, AstAlloc, Node,
642 record::{FieldDef, FieldMetadata, FieldPathElem},
643 };
644 use std::ops::Range;
645 use toml_edit::Value;
646
647 fn range_pos(range: Option<Range<usize>>, src_id: FileId) -> TermPos {
648 range.map_or(TermPos::None, |span| {
649 RawSpan {
650 src_id,
651 start: ByteIndex(span.start as u32),
652 end: ByteIndex(span.end as u32),
653 }
654 .into()
655 })
656 }
657
658 trait ToNickelValue {
660 fn to_value(&self, pos_table: &mut PosTable, src_id: FileId) -> NickelValue;
661
662 fn to_value_with_pos(
663 &self,
664 pos_table: &mut PosTable,
665 range: Option<Range<usize>>,
666 src_id: FileId,
667 ) -> NickelValue {
668 let pos = range_pos(range, src_id);
669 self.to_value(pos_table, src_id).with_pos(pos_table, pos)
670 }
671 }
672
673 impl ToNickelValue for toml_edit::Table {
674 fn to_value(&self, pos_table: &mut PosTable, src_id: FileId) -> NickelValue {
675 NickelValue::record_posless(RecordData::new(
676 self.iter()
677 .map(|(key, val)| {
678 (
679 LocIdent::new(key),
680 val.to_value_with_pos(pos_table, val.span(), src_id).into(),
681 )
682 })
683 .collect(),
684 RecordAttrs::default(),
685 None,
686 ))
687 }
688 }
689
690 impl ToNickelValue for toml_edit::Value {
691 fn to_value(&self, pos_table: &mut PosTable, src_id: FileId) -> NickelValue {
692 match self {
693 Value::String(s) => NickelValue::string_posless(s.value()),
694 Value::Integer(i) => NickelValue::number_posless(*i.value()),
695 Value::Float(f) => NickelValue::number_posless(Rational::exact_from(*f.value())),
696 Value::Boolean(b) => NickelValue::bool_value_posless(*b.value()),
697 Value::Array(vs) => NickelValue::array_posless(
698 vs.iter()
699 .map(|val| val.to_value_with_pos(pos_table, val.span(), src_id))
700 .collect(),
701 Vec::new(),
702 ),
703 Value::InlineTable(t) => NickelValue::record_posless(RecordData::new(
704 t.iter()
705 .map(|(key, val)| {
706 (
707 LocIdent::new(key),
708 val.to_value_with_pos(pos_table, val.span(), src_id).into(),
709 )
710 })
711 .collect(),
712 RecordAttrs::default(),
713 None,
714 )),
715 Value::Datetime(dt) => NickelValue::string_posless(dt.to_string()),
718 }
719 }
720 }
721
722 impl ToNickelValue for toml_edit::Item {
723 fn to_value(&self, pos_table: &mut PosTable, src_id: FileId) -> NickelValue {
724 match self {
725 toml_edit::Item::None => NickelValue::null(),
726 toml_edit::Item::Table(t) => t.to_value(pos_table, src_id),
727 toml_edit::Item::ArrayOfTables(ts) => NickelValue::array_posless(
728 ts.iter()
729 .map(|val| val.to_value_with_pos(pos_table, val.span(), src_id))
730 .collect(),
731 Vec::new(),
732 ),
733 toml_edit::Item::Value(v) => v.to_value(pos_table, src_id),
734 }
735 }
736 }
737
738 pub fn from_str(
741 pos_table: &mut PosTable,
742 s: &str,
743 file_id: FileId,
744 ) -> Result<NickelValue, toml_edit::TomlError> {
745 let doc: toml_edit::Document<_> = s.parse()?;
746 Ok(doc
747 .as_item()
748 .to_value_with_pos(pos_table, doc.span(), file_id))
749 }
750
751 trait ToAst {
752 fn to_ast<'ast>(&self, alloc: &'ast AstAlloc, file_id: FileId) -> Ast<'ast>;
753 }
754
755 fn to_record<'a, 'ast, T: ToAst + 'a, I: Iterator<Item = (&'a str, &'a T)>>(
756 alloc: &'ast AstAlloc,
757 file_id: FileId,
758 iter: I,
759 ) -> Node<'ast> {
760 Node::Record(
761 alloc.record_data(
762 [],
763 iter.map(|(key, val)| FieldDef {
764 path: FieldPathElem::single_ident_path(alloc, LocIdent::new(key)),
765 metadata: FieldMetadata::default(),
766 value: Some(val.to_ast(alloc, file_id)),
767 pos: TermPos::default(),
768 })
769 .collect::<Vec<_>>(),
770 false,
771 ),
772 )
773 }
774
775 impl ToAst for toml_edit::Table {
776 fn to_ast<'ast>(&self, alloc: &'ast AstAlloc, file_id: FileId) -> Ast<'ast> {
777 Ast::from(to_record(alloc, file_id, self.iter()))
778 .with_pos(range_pos(self.span(), file_id))
779 }
780 }
781
782 impl ToAst for toml_edit::Item {
783 fn to_ast<'ast>(&self, alloc: &'ast AstAlloc, file_id: FileId) -> Ast<'ast> {
784 let pos = range_pos(self.span(), file_id);
785 match self {
786 toml_edit::Item::None => Ast::from(Node::Null).with_pos(pos),
787 toml_edit::Item::Value(val) => val.to_ast(alloc, file_id),
788 toml_edit::Item::Table(t) => t.to_ast(alloc, file_id),
789 toml_edit::Item::ArrayOfTables(ts) => Ast::from(
790 alloc.array(
791 ts.iter()
792 .map(|t| t.to_ast(alloc, file_id))
793 .collect::<Vec<_>>(),
794 ),
795 )
796 .with_pos(pos),
797 }
798 }
799 }
800
801 impl ToAst for toml_edit::Value {
802 fn to_ast<'ast>(&self, alloc: &'ast AstAlloc, file_id: FileId) -> Ast<'ast> {
803 let pos = range_pos(self.span(), file_id);
804 let node = match self {
805 Value::String(s) => alloc.string(s.value()),
806 Value::Integer(i) => alloc.number((*i.value()).into()),
807 Value::Float(f) => alloc.number(Rational::exact_from(*f.value())),
808 Value::Boolean(b) => Node::Bool(*b.value()),
809 Value::Datetime(dt) => alloc.string(&dt.to_string()),
810 Value::Array(array) => alloc.array(
811 array
812 .iter()
813 .map(|v| v.to_ast(alloc, file_id))
814 .collect::<Vec<_>>(),
815 ),
816 Value::InlineTable(t) => to_record(alloc, file_id, t.iter()),
817 };
818 Ast::from(node).with_pos(pos)
819 }
820 }
821
822 pub fn ast_from_str<'ast>(
823 alloc: &'ast AstAlloc,
824 s: &str,
825 file_id: FileId,
826 ) -> Result<Ast<'ast>, toml_edit::TomlError> {
827 let doc: toml_edit::Document<_> = s.parse()?;
828 Ok(doc.as_item().to_ast(alloc, file_id))
829 }
830}
831
832#[cfg(test)]
833mod tests {
834 use super::*;
835 use crate::{
836 cache::CacheHub,
837 error::NullReporter,
838 eval::{VirtualMachine, VmContext, cache::CacheImpl},
839 program::Program,
840 term::{BinaryOp, make as mk_term},
841 };
842 use serde_json::json;
843 use std::io::Cursor;
844
845 #[track_caller]
846 fn eval(s: &str) -> NickelValue {
847 let src = Cursor::new(s);
848 let mut prog = Program::<CacheImpl>::new_from_source(
849 src,
850 "<test>",
851 std::io::stderr(),
852 NullReporter {},
853 )
854 .unwrap();
855 prog.eval_full().expect("program eval should succeed")
856 }
857
858 #[track_caller]
859 fn eval_with_ctxt(vm_ctxt: &mut VmContext<CacheHub, CacheImpl>, s: &str) -> NickelValue {
860 use crate::cache::{InputFormat, SourcePath};
861
862 let file_id = vm_ctxt
863 .import_resolver
864 .sources
865 .add_source(
866 SourcePath::Path("<test>".into(), InputFormat::Nickel),
867 Cursor::new(s),
868 )
869 .unwrap();
870 let value = vm_ctxt.prepare_eval(file_id).unwrap();
871
872 VirtualMachine::<_, CacheImpl>::new(vm_ctxt)
873 .eval_full(value)
874 .unwrap()
875 }
876
877 #[track_caller]
878 fn assert_json_eq<T: Serialize>(term: &str, expected: T) {
879 assert_eq!(
880 serde_json::to_string(&eval(term)).unwrap(),
881 serde_json::to_string(&expected).unwrap()
882 )
883 }
884
885 #[track_caller]
886 fn assert_nickel_eq(
887 vm_ctxt: &mut VmContext<CacheHub, CacheImpl>,
888 value: NickelValue,
889 expected: NickelValue,
890 ) {
891 assert!(
892 VirtualMachine::<_, CacheImpl>::new_empty_env(vm_ctxt)
893 .eval(mk_term::op2(BinaryOp::Eq, value, expected))
894 .unwrap()
895 .phys_eq(&NickelValue::bool_true())
896 );
897 }
898
899 #[track_caller]
900 fn assert_pass_validation(format: ExportFormat, term: &str) {
901 validate(format, &eval(term)).unwrap();
902 }
903
904 #[track_caller]
905 fn assert_fail_validation(format: ExportFormat, term: &str) {
906 validate(format, &eval(term)).unwrap_err();
907 }
908
909 #[track_caller]
910 fn assert_involutory(term: &str) {
911 let mut vm_ctxt = VmContext::new(CacheHub::new(), std::io::stderr(), NullReporter {});
912 let evaluated = eval_with_ctxt(&mut vm_ctxt, term);
913
914 let from_json: NickelValue =
915 serde_json::from_str(&serde_json::to_string(&evaluated).unwrap()).unwrap();
916 let from_yaml: NickelValue =
917 serde_yaml::from_str(&serde_yaml::to_string(&evaluated).unwrap()).unwrap();
918 let from_toml: NickelValue = toml::from_str(&toml::to_string(&evaluated).unwrap()).unwrap();
919
920 assert_nickel_eq(&mut vm_ctxt, from_json, evaluated.clone());
921 assert_nickel_eq(&mut vm_ctxt, from_yaml, evaluated.clone());
922 assert_nickel_eq(&mut vm_ctxt, from_toml, evaluated);
923 }
924
925 #[test]
926 fn basic() {
927 assert_json_eq("1 + 1", 2);
928
929 let null: Option<()> = None;
930 assert_json_eq("null", null);
931
932 assert_json_eq("if true then false else true", false);
933 assert_json_eq(r#""Hello, %{"world"}!""#, "Hello, world!");
934 assert_json_eq("'foo", "foo");
935 }
936
937 #[test]
938 fn arrays() {
939 assert_json_eq("[]", json!([]));
940 assert_json_eq("[null, (1+1), (2+2), (3+3)]", json!([null, 2, 4, 6]));
941 assert_json_eq(
942 r#"['a, ("b" ++ "c"), "d%{"e"}f", "g"]"#,
943 json!(["a", "bc", "def", "g"]),
944 );
945 assert_json_eq(
946 r#"std.array.fold_right (fun elt acc => [[elt]] @ acc) [] [1, 2, 3, 4]"#,
947 json!([[1], [2], [3], [4]]),
948 );
949 assert_json_eq("[\"a\", 1, false, 'foo]", json!(["a", 1, false, "foo"]));
950 }
951
952 #[test]
953 fn records() {
954 assert_json_eq(
955 "{a = 1, b = 2+2, c = 3, d = null}",
956 json!({"a": 1, "b": 4, "c": 3, "d": null}),
957 );
958
959 assert_json_eq(
960 "{a = {} & {b = {c = if true then 'richtig else 'falsch}}}",
961 json!({"a": {"b": {"c": "richtig"}}}),
962 );
963
964 assert_json_eq(
965 "{foo = let z = 0.5 + 0.5 in z, \
966 bar = [\"str\", true || false], \
967 baz = {subfoo = !false} & {subbar = 1 - 1}}",
968 json!({"foo": 1, "bar": ["str", true], "baz": {"subfoo": true, "subbar": 0}}),
969 );
970 }
971
972 #[test]
973 fn meta_values() {
974 assert_json_eq(
975 "{a | default = 1, b | doc \"doc\" = 2+2, c = 3}",
976 json!({"a": 1, "b": 4, "c": 3}),
977 );
978
979 assert_json_eq(
980 "{a = {b | default = {}} & {b.c | default = (if true then 'faux else 'vrai)}}",
981 json!({"a": {"b": {"c": "faux"}}}),
982 );
983
984 assert_json_eq(
985 "{baz | default = {subfoo | default = !false} & {subbar | default = 1 - 1}}",
986 json!({"baz": {"subfoo": true, "subbar": 0}}),
987 );
988
989 assert_json_eq(
990 "{a = {b | default = {}} & {b.c | not_exported = false} & {b.d = true}}",
991 json!({"a": {"b": {"d": true}}}),
992 );
993 }
994
995 #[test]
996 fn prevalidation() {
997 assert_fail_validation(ExportFormat::Json, "{a = 1, b = {c = fun x => x}}");
998 assert_fail_validation(
999 ExportFormat::Json,
1000 "{foo.bar = let y = \"a\" in y, b = [[fun x => x]]}",
1001 );
1002 assert_pass_validation(ExportFormat::Json, "{foo = null}");
1003 assert_fail_validation(ExportFormat::Toml, "{foo = null}");
1004 }
1005
1006 #[test]
1007 fn involution() {
1008 assert_involutory("{val = 1 + 1}");
1009 assert_involutory("{val = \"Some string\"}");
1010 assert_involutory("{val = [\"a\", 3, []]}");
1011 assert_involutory("{a.foo.bar = \"2\", b = false, c = [{d = \"e\"}, {d = \"f\"}]}");
1012 }
1013}