1use crate::identifier::Ident;
28
29use super::{
30 MergePriority,
31 record::{FieldMetadata, FieldPathElem, Include},
32 typ::Type,
33 *,
34};
35
36type StaticPath = Vec<Ident>;
37
38pub struct Incomplete();
40
41pub struct Complete<'ast>(Option<Ast<'ast>>);
43
44#[derive(Debug)]
46pub struct Field<'ast, State> {
47 state: State,
56 path: StaticPath,
57 metadata: FieldMetadata<'ast>,
58 doc: Option<String>,
64 contracts: Vec<Type<'ast>>,
70}
71
72impl<'ast, A> Field<'ast, A> {
73 pub fn doc(self, doc: impl AsRef<str>) -> Self {
75 self.some_doc(Some(doc))
76 }
77
78 pub fn some_doc(mut self, some_doc: Option<impl AsRef<str>>) -> Self {
80 self.doc = some_doc.map(|s| s.as_ref().to_string());
81 self
82 }
83
84 pub fn optional(mut self, opt: bool) -> Self {
86 self.metadata.opt = opt;
87 self
88 }
89
90 pub fn not_exported(mut self, not_exported: bool) -> Self {
92 self.metadata.not_exported = not_exported;
93 self
94 }
95
96 pub fn contract(mut self, contract: impl Into<Type<'ast>>) -> Self {
98 self.contracts.push(contract.into());
99 self
100 }
101
102 pub fn contracts<I>(mut self, contracts: I) -> Self
104 where
105 I: IntoIterator<Item = Type<'ast>>,
106 {
107 self.contracts.extend(contracts);
108 self
109 }
110
111 pub fn types(mut self, typ: impl Into<Type<'ast>>) -> Self {
113 self.metadata.annotation.typ = Some(typ.into());
114 self
115 }
116
117 pub fn priority(mut self, priority: MergePriority) -> Self {
119 self.metadata.priority = priority;
120 self
121 }
122
123 pub fn metadata(mut self, metadata: FieldMetadata<'ast>) -> Self {
125 self.metadata = metadata;
126 self
127 }
128}
129
130impl<'ast> Field<'ast, Incomplete> {
131 pub fn path<I, It>(path: It) -> Self
133 where
134 I: AsRef<str>,
135 It: IntoIterator<Item = I>,
136 {
137 Field {
138 state: Incomplete(),
139 path: path.into_iter().map(|e| e.as_ref().into()).collect(),
140 metadata: Default::default(),
141 doc: None,
142 contracts: Vec::new(),
143 }
144 }
145
146 pub fn name(name: impl AsRef<str>) -> Self {
148 Self::path([name])
149 }
150
151 pub fn no_value(self) -> Field<'ast, Complete<'ast>> {
153 Field {
154 state: Complete(None),
155 path: self.path,
156 metadata: self.metadata,
157 doc: self.doc,
158 contracts: self.contracts,
159 }
160 }
161
162 pub fn value(self, value: impl Into<Ast<'ast>>) -> Field<'ast, Complete<'ast>> {
164 Field {
165 state: Complete(Some(value.into())),
166 path: self.path,
167 metadata: self.metadata,
168 doc: self.doc,
169 contracts: self.contracts,
170 }
171 }
172}
173
174impl<'ast> Field<'ast, Complete<'ast>> {
175 pub fn attach(self, alloc: &'ast AstAlloc, record: Record<'ast>) -> Record<'ast> {
177 let value = self.state;
178
179 let field = Field {
180 state: record,
181 path: self.path,
182 metadata: self.metadata,
183 doc: self.doc,
184 contracts: self.contracts,
185 };
186 match value {
187 Complete(Some(v)) => field.value(alloc, v),
188 Complete(None) => field.no_value(alloc),
189 }
190 }
191}
192
193impl<'ast> Field<'ast, Record<'ast>> {
194 pub fn no_value(mut self, alloc: &'ast AstAlloc) -> Record<'ast> {
196 self.finalize_contracts(alloc);
197 self.metadata.doc = self.doc.map(|s| alloc.alloc_str(&s));
198
199 self.state.field_defs.push(record::FieldDef {
200 path: alloc.alloc_many(
201 self.path
202 .into_iter()
203 .map(|id| FieldPathElem::Ident(id.into())),
204 ),
205 metadata: self.metadata,
206 value: None,
207 pos: TermPos::None,
208 });
209 self.state
210 }
211
212 pub fn value(mut self, alloc: &'ast AstAlloc, value: impl Into<Ast<'ast>>) -> Record<'ast> {
214 self.finalize_contracts(alloc);
215 self.metadata.doc = self.doc.map(|s| alloc.alloc_str(&s));
216
217 self.state.field_defs.push(record::FieldDef {
218 path: alloc.alloc_many(
219 self.path
220 .into_iter()
221 .map(|id| FieldPathElem::Ident(id.into())),
222 ),
223 metadata: self.metadata,
224 value: Some(value.into()),
225 pos: TermPos::None,
226 });
227
228 self.state
229 }
230
231 fn finalize_contracts(&mut self, alloc: &'ast AstAlloc) {
235 self.metadata.annotation.contracts = alloc.alloc_many(self.contracts.drain(..));
236 }
237}
238
239#[derive(Debug, Default)]
241pub struct Record<'ast> {
242 field_defs: Vec<record::FieldDef<'ast>>,
243 includes: Vec<Include<'ast>>,
244 open: bool,
245}
246
247impl<'ast> Record<'ast> {
248 pub fn new() -> Self {
250 Record::default()
251 }
252
253 pub fn field(self, name: impl AsRef<str>) -> Field<'ast, Record<'ast>> {
255 Field {
256 state: self,
257 path: vec![Ident::new(name)],
258 metadata: Default::default(),
259 doc: None,
260 contracts: Vec::new(),
261 }
262 }
263
264 pub fn fields<I, It>(mut self, alloc: &'ast AstAlloc, fields: It) -> Self
266 where
267 I: Into<Field<'ast, Complete<'ast>>>,
268 It: IntoIterator<Item = I>,
269 {
270 for f in fields {
271 self = f.into().attach(alloc, self)
272 }
273 self
274 }
275
276 pub fn include(self, ident: LocIdent) -> Self {
278 self.include_with_metadata(ident, Default::default())
279 }
280
281 pub fn include_with_metadata(mut self, ident: LocIdent, metadata: FieldMetadata<'ast>) -> Self {
283 self.includes.push(Include { ident, metadata });
284 self
285 }
286
287 pub fn path<It, I>(self, path: It) -> Field<'ast, Record<'ast>>
289 where
290 I: AsRef<str>,
291 It: IntoIterator<Item = I>,
292 {
293 Field {
294 state: self,
295 path: path.into_iter().map(|e| Ident::new(e)).collect(),
296 metadata: Default::default(),
297 doc: None,
298 contracts: Vec::new(),
299 }
300 }
301
302 pub fn open(mut self) -> Self {
304 self.open = true;
305 self
306 }
307
308 pub fn set_open(mut self, open: bool) -> Self {
310 self.open = open;
311 self
312 }
313
314 pub fn build(self, alloc: &'ast AstAlloc) -> Ast<'ast> {
316 alloc
317 .record(record::Record {
318 field_defs: alloc.alloc_many(self.field_defs),
319 includes: alloc.alloc_many(self.includes),
320 open: self.open,
321 })
322 .into()
323 }
324
325 pub fn from_iterator<I, It>(alloc: &'ast AstAlloc, fields: It) -> Self
330 where
331 I: Into<Field<'ast, Complete<'ast>>>,
332 It: IntoIterator<Item = I>,
333 {
334 Record::new().fields(alloc, fields)
335 }
336}
337
338#[macro_export]
340macro_rules! app {
341 ( $alloc:expr, $f:expr , $arg:expr $(,)?) => {
343 $crate::ast::Ast::from(
344 $alloc.app(
345 $crate::ast::Ast::from($f),
346 std::iter::once($crate::ast::Ast::from($arg))
347 )
348 )
349 };
350 ( $alloc:expr, $f:expr, $arg1:expr $(, $args:expr )+ $(,)?) => {
351 {
352 let args = vec![
353 $crate::ast::Ast::from($arg1)
354 $(, $crate::ast::Ast::from($args) )+
355 ];
356
357 $crate::ast::Ast::from($alloc.app($crate::ast::Ast::from($f), args))
358 }
359 };
360}
361
362#[macro_export]
363macro_rules! primop_app {
365 ( $alloc:expr, $op:expr , $arg:expr $(,)?) => {
367 $crate::ast::Ast::from(
368 $alloc.prim_op(
369 $op,
370 std::iter::once($crate::ast::Ast::from($arg))
371 )
372 )
373 };
374 ( $alloc:expr, $op:expr, $arg1:expr $(, $args:expr )+ $(,)?) => {
375 {
376 let args = vec![
377 $crate::ast::Ast::from($arg1)
378 $(, $crate::ast::Ast::from($args) )+
379 ];
380 $crate::ast::Ast::from($alloc.prim_op($op, args))
381 }
382 };
383}
384
385#[macro_export]
386macro_rules! fun {
389 ($alloc:expr, args=[ $( $args:expr, )+ ], $body:expr) => {
399 {
400 let args = vec![
401 $($crate::ast::pattern::Pattern::any($crate::identifier::LocIdent::from($args)), )+
402 ];
403
404 $crate::ast::Ast::from(
405 $alloc.fun(args, $crate::ast::Ast::from($body))
406 )
407 }
408 };
409 ($alloc:expr, args=[ $( $args:expr, )* ], $next_arg:expr $(, $rest:expr )+) => {
411 fun!($alloc, args=[ $( $args, )* $next_arg, ] $(, $rest )+)
412 };
413 ( $alloc:expr, $arg:expr, $body:expr $(,)?) => {
415 $crate::ast::Ast::from(
416 $alloc.fun(
417 std::iter::once($crate::ast::pattern::Pattern::any($crate::identifier::LocIdent::from($arg))),
418 $crate::ast::Ast::from($body)
419 )
420 )
421 };
422 ( $alloc:expr, $arg1:expr, $arg2:expr $(, $rest:expr )+ $(,)?) => {
423 fun!($alloc, args=[ $arg1, $arg2, ] $(, $rest )+)
424 };
425}
426
427pub fn var<'ast>(id: impl Into<LocIdent>) -> Ast<'ast> {
428 Ast::from(Node::Var(id.into()))
429}
430
431pub fn enum_tag<'ast>(tag: impl Into<LocIdent>) -> Ast<'ast> {
432 Ast::from(Node::EnumVariant {
433 tag: tag.into(),
434 arg: None,
435 })
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use pretty_assertions::assert_eq;
442 use std::iter;
443
444 fn build_simple_record<'ast, I, Id>(alloc: &'ast AstAlloc, fields: I, open: bool) -> Ast<'ast>
445 where
446 Id: Into<LocIdent>,
447 I: IntoIterator<Item = (Id, Node<'ast>)>,
448 I::IntoIter: ExactSizeIterator,
449 {
450 build_record(
451 alloc,
452 fields
453 .into_iter()
454 .map(|(id, node)| (id, Default::default(), Some(node))),
455 open,
456 )
457 }
458
459 fn build_record<'ast, I, Id>(alloc: &'ast AstAlloc, fields: I, open: bool) -> Ast<'ast>
460 where
461 Id: Into<LocIdent>,
462 I: IntoIterator<Item = (Id, FieldMetadata<'ast>, Option<Node<'ast>>)>,
463 I::IntoIter: ExactSizeIterator,
464 {
465 alloc
466 .record(record::Record {
467 field_defs: alloc.alloc_many(fields.into_iter().map(|(id, metadata, node)| {
468 record::FieldDef {
469 path: FieldPathElem::single_ident_path(alloc, id.into()),
470 value: node.map(Ast::from),
471 metadata,
472 pos: TermPos::None,
473 }
474 })),
475 includes: &[],
476 open,
477 })
478 .into()
479 }
480
481 #[test]
482 fn trivial() {
483 let alloc = AstAlloc::new();
484
485 let ast: Ast = Record::new()
486 .field("foo")
487 .value(&alloc, alloc.string("bar"))
488 .build(&alloc);
489
490 assert_eq!(
491 ast,
492 build_simple_record(&alloc, vec![("foo", alloc.string("bar"))], false)
493 );
494 }
495
496 #[test]
497 fn from_iter() {
498 let alloc = AstAlloc::new();
499
500 let ast: Ast = Record::from_iterator(
501 &alloc,
502 [
503 Field::name("foo").value(Node::Null),
504 Field::name("bar").value(Node::Null),
505 ],
506 )
507 .build(&alloc);
508
509 assert_eq!(
510 ast,
511 build_simple_record(
512 &alloc,
513 vec![("foo", Node::Null), ("bar", Node::Null),],
514 false
515 )
516 );
517 }
518
519 #[test]
520 fn some_doc() {
521 let alloc = AstAlloc::new();
522
523 let ast: Ast = Record::from_iterator(
524 &alloc,
525 [
526 Field::name("foo").some_doc(Some("foo")).no_value(),
527 Field::name("bar").some_doc(None as Option<&str>).no_value(),
528 Field::name("baz").doc("baz").no_value(),
529 ],
530 )
531 .build(&alloc);
532
533 assert_eq!(
534 ast,
535 build_record(
536 &alloc,
537 vec![
538 (
539 "foo",
540 FieldMetadata {
541 doc: Some(alloc.alloc_str("foo")),
542 ..Default::default()
543 },
544 None
545 ),
546 ("bar", Default::default(), None),
547 (
548 "baz",
549 FieldMetadata {
550 doc: Some(alloc.alloc_str("baz")),
551 ..Default::default()
552 },
553 None,
554 )
555 ],
556 false,
557 )
558 );
559 }
560
561 #[test]
562 fn fields() {
563 let alloc = AstAlloc::new();
564
565 let ast: Ast = Record::new()
566 .fields(
567 &alloc,
568 [
569 Field::name("foo").value(alloc.string("foo")),
570 Field::name("bar").value(alloc.string("bar")),
571 ],
572 )
573 .build(&alloc);
574
575 assert_eq!(
576 ast,
577 build_simple_record(
578 &alloc,
579 vec![("foo", alloc.string("foo")), ("bar", alloc.string("bar")),],
580 false,
581 )
582 );
583 }
584
585 #[test]
586 fn fields_metadata() {
587 let alloc = AstAlloc::new();
588
589 let ast: Ast = Record::new()
590 .fields(
591 &alloc,
592 [
593 Field::name("foo").optional(true).no_value(),
594 Field::name("bar").optional(true).no_value(),
595 ],
596 )
597 .build(&alloc);
598
599 assert_eq!(
600 ast,
601 build_record(
602 &alloc,
603 vec![
604 (
605 "foo",
606 FieldMetadata {
607 opt: true,
608 ..Default::default()
609 },
610 None,
611 ),
612 (
613 "bar",
614 FieldMetadata {
615 opt: true,
616 ..Default::default()
617 },
618 None,
619 ),
620 ],
621 false,
622 )
623 );
624 }
625
626 #[test]
627 fn overriding() {
628 let alloc = AstAlloc::new();
629
630 let ast: Ast = Record::new()
631 .path(vec!["terraform", "required_providers"])
632 .value(
633 &alloc,
634 Record::from_iterator(
635 &alloc,
636 [
637 Field::name("foo").value(Node::Null),
638 Field::name("bar").value(Node::Null),
639 ],
640 )
641 .build(&alloc),
642 )
643 .path(vec!["terraform", "required_providers", "foo"])
644 .value(&alloc, alloc.string("hello world!"))
645 .build(&alloc);
646
647 eprintln!("{ast:?}");
648
649 assert_eq!(
650 ast,
651 alloc
652 .record(record::Record {
653 field_defs: alloc.alloc_many(vec![
654 record::FieldDef {
655 path: alloc.alloc_many(vec![
656 FieldPathElem::Ident("terraform".into()),
657 FieldPathElem::Ident("required_providers".into())
658 ]),
659 metadata: Default::default(),
660 value: Some(
661 alloc
662 .record(record::Record {
663 field_defs: alloc.alloc_many(vec![
664 record::FieldDef {
665 path: FieldPathElem::single_ident_path(
666 &alloc,
667 "foo".into()
668 ),
669 metadata: Default::default(),
670 value: Some(Node::Null.into()),
671 pos: TermPos::None,
672 },
673 record::FieldDef {
674 path: FieldPathElem::single_ident_path(
675 &alloc,
676 "bar".into()
677 ),
678 metadata: Default::default(),
679 value: Some(Node::Null.into()),
680 pos: TermPos::None,
681 },
682 ]),
683 ..Default::default()
684 })
685 .into()
686 ),
687 pos: TermPos::None,
688 },
689 record::FieldDef {
690 path: alloc.alloc_many(vec![
691 FieldPathElem::Ident("terraform".into()),
692 FieldPathElem::Ident("required_providers".into()),
693 FieldPathElem::Ident("foo".into())
694 ]),
695 metadata: Default::default(),
696 value: Some(alloc.string("hello world!").into()),
697 pos: TermPos::None,
698 }
699 ]),
700 ..Default::default()
701 })
702 .into()
703 );
704 }
705
706 #[test]
707 fn open_record() {
708 let alloc = AstAlloc::new();
709
710 let ast: Ast = Record::new().open().build(&alloc);
711
712 assert_eq!(ast, alloc.record(record::Record::empty().open()).into());
713 }
714
715 #[test]
716 fn prio_metadata() {
717 let alloc = AstAlloc::new();
718
719 let ast: Ast = Record::new()
720 .field("foo")
721 .priority(MergePriority::Top)
722 .no_value(&alloc)
723 .build(&alloc);
724
725 assert_eq!(
726 ast,
727 build_record(
728 &alloc,
729 iter::once((
730 "foo",
731 FieldMetadata {
732 priority: MergePriority::Top,
733 ..Default::default()
734 },
735 None,
736 )),
737 false
738 )
739 );
740 }
741
742 #[test]
743 fn contract() {
744 let alloc = AstAlloc::new();
745
746 let ast: Ast = Record::new()
747 .field("foo")
748 .contract(TypeF::String)
749 .no_value(&alloc)
750 .build(&alloc);
751
752 assert_eq!(
753 ast,
754 build_record(
755 &alloc,
756 iter::once((
757 "foo",
758 record::FieldMetadata::from(Annotation {
759 contracts: alloc.alloc_singleton(Type {
760 typ: TypeF::String,
761 pos: TermPos::None
762 }),
763 ..Default::default()
764 }),
765 None
766 )),
767 false
768 )
769 );
770 }
771
772 #[test]
773 fn exercise_metadata() {
774 let alloc = AstAlloc::new();
775
776 let ast: Ast = Record::new()
777 .field("foo")
778 .priority(MergePriority::Bottom)
779 .doc("foo?")
780 .contract(TypeF::String)
781 .types(TypeF::Number)
782 .optional(true)
783 .not_exported(true)
784 .no_value(&alloc)
785 .build(&alloc);
786
787 assert_eq!(
788 ast,
789 build_record(
790 &alloc,
791 iter::once((
792 "foo",
793 FieldMetadata {
794 doc: Some(alloc.alloc_str("foo?")),
795 opt: true,
796 priority: MergePriority::Bottom,
797 not_exported: true,
798 annotation: Annotation {
799 typ: Some(Type {
800 typ: TypeF::Number,
801 pos: TermPos::None,
802 }),
803 contracts: alloc.alloc_singleton(Type {
804 typ: TypeF::String,
805 pos: TermPos::None
806 }),
807 },
808 },
809 None,
810 )),
811 Default::default()
812 )
813 );
814 }
815}