1use crate::ast;
7use crate::ir;
8use crate::UtxoRef;
9
10#[derive(Debug, thiserror::Error)]
11pub enum Error {
12 #[error("missing analyze phase for {0}")]
13 MissingAnalyzePhase(String),
14
15 #[error("symbol '{0}' expected to be '{1}'")]
16 InvalidSymbol(String, &'static str),
17
18 #[error("symbol '{0}' expected to be of type '{1}'")]
19 InvalidSymbolType(String, &'static str),
20
21 #[error("invalid ast: {0}")]
22 InvalidAst(String),
23
24 #[error("invalid property {0} on type {1:?}")]
25 InvalidProperty(String, String),
26
27 #[error("missing required field {0} for {1:?}")]
28 MissingRequiredField(String, &'static str),
29
30 #[error("failed to decode hex string {0}")]
31 DecodeHexError(String),
32}
33
34#[inline]
35fn hex_decode(s: &str) -> Result<Vec<u8>, Error> {
36 hex::decode(s).map_err(|_| Error::DecodeHexError(s.to_string()))
37}
38
39fn expect_type_def(ident: &ast::Identifier) -> Result<&ast::TypeDef, Error> {
40 let symbol = ident
41 .symbol
42 .as_ref()
43 .ok_or(Error::MissingAnalyzePhase(ident.value.clone()))?;
44
45 symbol
46 .as_type_def()
47 .ok_or(Error::InvalidSymbol(ident.value.clone(), "TypeDef"))
48}
49
50fn expect_case_def(ident: &ast::Identifier) -> Result<&ast::VariantCase, Error> {
51 let symbol = ident
52 .symbol
53 .as_ref()
54 .ok_or(Error::MissingAnalyzePhase(ident.value.clone()))?;
55
56 symbol
57 .as_variant_case()
58 .ok_or(Error::InvalidSymbol(ident.value.clone(), "VariantCase"))
59}
60
61#[allow(dead_code)]
62fn expect_field_def(ident: &ast::Identifier) -> Result<&ast::RecordField, Error> {
63 let symbol = ident
64 .symbol
65 .as_ref()
66 .ok_or(Error::MissingAnalyzePhase(ident.value.clone()))?;
67
68 symbol
69 .as_field_def()
70 .ok_or(Error::InvalidSymbol(ident.value.clone(), "FieldDef"))
71}
72
73fn coerce_identifier_into_asset_def(identifier: &ast::Identifier) -> Result<ast::AssetDef, Error> {
74 match identifier.try_symbol()? {
75 ast::Symbol::AssetDef(x) => Ok(x.as_ref().clone()),
76 _ => Err(Error::InvalidSymbol(identifier.value.clone(), "AssetDef")),
77 }
78}
79
80#[derive(Debug, Default)]
81pub(crate) struct Context {
82 is_asset_expr: bool,
83 is_datum_expr: bool,
84 is_address_expr: bool,
85}
86
87impl Context {
88 pub fn enter_asset_expr(&self) -> Self {
89 Self {
90 is_asset_expr: true,
91 is_datum_expr: false,
92 is_address_expr: false,
93 }
94 }
95
96 pub fn enter_datum_expr(&self) -> Self {
97 Self {
98 is_asset_expr: false,
99 is_datum_expr: true,
100 is_address_expr: false,
101 }
102 }
103
104 pub fn enter_address_expr(&self) -> Self {
105 Self {
106 is_asset_expr: false,
107 is_datum_expr: false,
108 is_address_expr: true,
109 }
110 }
111
112 pub fn is_address_expr(&self) -> bool {
113 self.is_address_expr
114 }
115
116 pub fn is_asset_expr(&self) -> bool {
117 self.is_asset_expr
118 }
119
120 pub fn is_datum_expr(&self) -> bool {
121 self.is_datum_expr
122 }
123}
124
125pub(crate) trait IntoLower {
126 type Output;
127
128 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error>;
129}
130
131impl<T> IntoLower for Option<&T>
132where
133 T: IntoLower,
134{
135 type Output = Option<T::Output>;
136
137 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
138 self.map(|x| x.into_lower(ctx)).transpose()
139 }
140}
141
142impl<T> IntoLower for Box<T>
143where
144 T: IntoLower,
145{
146 type Output = T::Output;
147
148 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
149 self.as_ref().into_lower(ctx)
150 }
151}
152
153impl IntoLower for ast::Identifier {
154 type Output = ir::Expression;
155
156 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
157 let symbol = self
158 .symbol
159 .as_ref()
160 .ok_or(Error::MissingAnalyzePhase(self.value.clone()))?;
161
162 match symbol {
163 ast::Symbol::ParamVar(n, ty) => {
164 Ok(ir::Param::ExpectValue(n.to_lowercase().clone(), ty.into_lower(ctx)?).into())
165 }
166 ast::Symbol::LocalExpr(expr) => Ok(expr.into_lower(ctx)?),
167 ast::Symbol::PartyDef(x) => Ok(ir::Param::ExpectValue(
168 x.name.value.to_lowercase().clone(),
169 ir::Type::Address,
170 )
171 .into()),
172 ast::Symbol::Input(def) => {
173 let inner = def.into_lower(ctx)?.utxos;
174
175 let out = if ctx.is_asset_expr() {
176 ir::Coerce::IntoAssets(inner).into()
177 } else if ctx.is_datum_expr() {
178 ir::Coerce::IntoDatum(inner).into()
179 } else {
180 inner
181 };
182
183 Ok(out)
184 }
185 ast::Symbol::Fees => Ok(ir::Param::ExpectFees.into()),
186 ast::Symbol::EnvVar(n, ty) => {
187 Ok(ir::Param::ExpectValue(n.to_lowercase().clone(), ty.into_lower(ctx)?).into())
188 }
189 ast::Symbol::PolicyDef(x) => {
190 let policy = x.into_lower(ctx)?;
191
192 if ctx.is_address_expr() {
193 Ok(ir::CompilerOp::BuildScriptAddress(policy.hash).into())
194 } else {
195 Ok(policy.hash)
196 }
197 }
198 _ => {
199 dbg!(&self);
200 todo!();
201 }
202 }
203 }
204}
205
206impl IntoLower for ast::UtxoRef {
207 type Output = ir::Expression;
208
209 fn into_lower(&self, _: &Context) -> Result<Self::Output, Error> {
210 let x = ir::Expression::UtxoRefs(vec![UtxoRef {
211 txid: self.txid.clone(),
212 index: self.index as u32,
213 }]);
214
215 Ok(x)
216 }
217}
218
219impl IntoLower for ast::StructConstructor {
220 type Output = ir::StructExpr;
221
222 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
223 let type_def = expect_type_def(&self.r#type)?;
224
225 let constructor = type_def
226 .find_case_index(&self.case.name.value)
227 .ok_or(Error::InvalidAst("case not found".to_string()))?;
228
229 let case_def = expect_case_def(&self.case.name)?;
230
231 let mut fields = vec![];
232
233 for (index, field_def) in case_def.fields.iter().enumerate() {
234 let value = self.case.find_field_value(&field_def.name.value);
235
236 if let Some(value) = value {
237 fields.push(value.into_lower(ctx)?);
238 } else {
239 let spread_target = self
240 .case
241 .spread
242 .as_ref()
243 .expect("spread must be set for missing explicit field")
244 .into_lower(ctx)?;
245
246 fields.push(ir::Expression::EvalBuiltIn(Box::new(
247 ir::BuiltInOp::Property(spread_target, index),
248 )));
249 }
250 }
251
252 Ok(ir::StructExpr {
253 constructor,
254 fields,
255 })
256 }
257}
258
259impl IntoLower for ast::PolicyField {
260 type Output = ir::Expression;
261
262 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
263 match self {
264 ast::PolicyField::Hash(x) => x.into_lower(ctx),
265 ast::PolicyField::Script(x) => x.into_lower(ctx),
266 ast::PolicyField::Ref(x) => x.into_lower(ctx),
267 }
268 }
269}
270
271impl IntoLower for ast::PolicyDef {
272 type Output = ir::PolicyExpr;
273
274 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
275 match &self.value {
276 ast::PolicyValue::Assign(x) => {
277 let out = ir::PolicyExpr {
278 name: self.name.value.clone(),
279 hash: ir::Expression::Hash(hex_decode(&x.value)?),
280 script: ir::ScriptSource::expect_parameter(self.name.value.clone()),
281 };
282
283 Ok(out)
284 }
285 ast::PolicyValue::Constructor(x) => {
286 let hash = x
287 .find_field("hash")
288 .ok_or(Error::InvalidAst("Missing policy hash".to_string()))?
289 .into_lower(ctx)?;
290
291 let rf = x.find_field("ref").map(|x| x.into_lower(ctx)).transpose()?;
292
293 let script = x
294 .find_field("script")
295 .map(|x| x.into_lower(ctx))
296 .transpose()?;
297
298 let script = match (rf, script) {
299 (Some(rf), Some(script)) => ir::ScriptSource::new_ref(rf, script),
300 (Some(rf), None) => {
301 ir::ScriptSource::expect_ref_input(self.name.value.clone(), rf)
302 }
303 (None, Some(script)) => ir::ScriptSource::new_embedded(script),
304 (None, None) => ir::ScriptSource::expect_parameter(self.name.value.clone()),
305 };
306
307 Ok(ir::PolicyExpr {
308 name: self.name.value.clone(),
309 hash,
310 script,
311 })
312 }
313 }
314 }
315}
316
317impl IntoLower for ast::Type {
318 type Output = ir::Type;
319
320 fn into_lower(&self, _: &Context) -> Result<Self::Output, Error> {
321 match self {
322 ast::Type::Undefined => Ok(ir::Type::Undefined),
323 ast::Type::Unit => Ok(ir::Type::Unit),
324 ast::Type::Int => Ok(ir::Type::Int),
325 ast::Type::Bool => Ok(ir::Type::Bool),
326 ast::Type::Bytes => Ok(ir::Type::Bytes),
327 ast::Type::Address => Ok(ir::Type::Address),
328 ast::Type::Utxo => Ok(ir::Type::Utxo),
329 ast::Type::UtxoRef => Ok(ir::Type::UtxoRef),
330 ast::Type::AnyAsset => Ok(ir::Type::AnyAsset),
331 ast::Type::List(_) => Ok(ir::Type::List),
332 ast::Type::Custom(x) => Ok(ir::Type::Custom(x.value.clone())),
333 }
334 }
335}
336
337impl IntoLower for ast::AddOp {
338 type Output = ir::Expression;
339
340 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
341 let left = self.lhs.into_lower(ctx)?;
342 let right = self.rhs.into_lower(ctx)?;
343
344 Ok(ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
345 left, right,
346 ))))
347 }
348}
349
350impl IntoLower for ast::SubOp {
351 type Output = ir::Expression;
352
353 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
354 let left = self.lhs.into_lower(ctx)?;
355 let right = self.rhs.into_lower(ctx)?;
356
357 Ok(ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Sub(
358 left, right,
359 ))))
360 }
361}
362
363impl IntoLower for ast::NegateOp {
364 type Output = ir::Expression;
365
366 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
367 let operand = self.operand.into_lower(ctx)?;
368
369 Ok(ir::Expression::EvalBuiltIn(Box::new(
370 ir::BuiltInOp::Negate(operand),
371 )))
372 }
373}
374
375impl IntoLower for ast::PropertyOp {
376 type Output = ir::Expression;
377
378 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
379 let object = self.operand.into_lower(ctx)?;
380
381 let ty = self
382 .operand
383 .target_type()
384 .ok_or(Error::MissingAnalyzePhase(format!("{0:?}", self.operand)))?;
385
386 let prop_index = ty
387 .property_index(&self.property.value)
388 .ok_or(Error::InvalidProperty(
389 self.property.value.clone(),
390 ty.to_string(),
391 ))?;
392
393 Ok(ir::Expression::EvalBuiltIn(Box::new(
394 ir::BuiltInOp::Property(object, prop_index),
395 )))
396 }
397}
398
399impl IntoLower for ast::ListConstructor {
400 type Output = Vec<ir::Expression>;
401
402 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
403 let elements = self
404 .elements
405 .iter()
406 .map(|x| x.into_lower(ctx))
407 .collect::<Result<Vec<_>, _>>()?;
408
409 Ok(elements)
410 }
411}
412
413impl IntoLower for ast::DataExpr {
414 type Output = ir::Expression;
415
416 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
417 let out = match self {
418 ast::DataExpr::None => ir::Expression::None,
419 ast::DataExpr::Number(x) => Self::Output::Number(*x as i128),
420 ast::DataExpr::Bool(x) => ir::Expression::Bool(*x),
421 ast::DataExpr::String(x) => ir::Expression::String(x.value.clone()),
422 ast::DataExpr::HexString(x) => ir::Expression::Bytes(hex_decode(&x.value)?),
423 ast::DataExpr::StructConstructor(x) => ir::Expression::Struct(x.into_lower(ctx)?),
424 ast::DataExpr::ListConstructor(x) => ir::Expression::List(x.into_lower(ctx)?),
425 ast::DataExpr::StaticAssetConstructor(x) => x.into_lower(ctx)?,
426 ast::DataExpr::AnyAssetConstructor(x) => x.into_lower(ctx)?,
427 ast::DataExpr::Unit => ir::Expression::Struct(ir::StructExpr::unit()),
428 ast::DataExpr::Identifier(x) => x.into_lower(ctx)?,
429 ast::DataExpr::AddOp(x) => x.into_lower(ctx)?,
430 ast::DataExpr::SubOp(x) => x.into_lower(ctx)?,
431 ast::DataExpr::NegateOp(x) => x.into_lower(ctx)?,
432 ast::DataExpr::PropertyOp(x) => x.into_lower(ctx)?,
433 ast::DataExpr::UtxoRef(x) => x.into_lower(ctx)?,
434 };
435
436 Ok(out)
437 }
438}
439
440impl IntoLower for ast::StaticAssetConstructor {
441 type Output = ir::Expression;
442
443 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
444 let asset_def = coerce_identifier_into_asset_def(&self.r#type)?;
445
446 let policy = asset_def.policy.into_lower(ctx)?;
447 let asset_name = asset_def.asset_name.into_lower(ctx)?;
448
449 let amount = self.amount.into_lower(ctx)?;
450
451 Ok(ir::Expression::Assets(vec![ir::AssetExpr {
452 policy,
453 asset_name,
454 amount,
455 }]))
456 }
457}
458
459impl IntoLower for ast::AnyAssetConstructor {
460 type Output = ir::Expression;
461
462 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
463 let policy = self.policy.into_lower(ctx)?;
464 let asset_name = self.asset_name.into_lower(ctx)?;
465 let amount = self.amount.into_lower(ctx)?;
466
467 Ok(ir::Expression::Assets(vec![ir::AssetExpr {
468 policy,
469 asset_name,
470 amount,
471 }]))
472 }
473}
474
475impl IntoLower for ast::InputBlockField {
476 type Output = ir::Expression;
477
478 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
479 match self {
480 ast::InputBlockField::From(x) => {
481 let ctx = ctx.enter_address_expr();
482 x.into_lower(&ctx)
483 }
484 ast::InputBlockField::DatumIs(_) => todo!(),
485 ast::InputBlockField::MinAmount(x) => {
486 let ctx = ctx.enter_asset_expr();
487 x.into_lower(&ctx)
488 }
489 ast::InputBlockField::Redeemer(x) => {
490 let ctx = ctx.enter_datum_expr();
491 x.into_lower(&ctx)
492 }
493 ast::InputBlockField::Ref(x) => x.into_lower(ctx),
494 }
495 }
496}
497
498impl IntoLower for ast::InputBlock {
499 type Output = ir::Input;
500
501 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
502 let from_field = self.find("from");
503
504 let address = from_field.map(|x| x.into_lower(ctx)).transpose()?;
505
506 let min_amount = self
507 .find("min_amount")
508 .map(|x| x.into_lower(ctx))
509 .transpose()?;
510
511 let r#ref = self.find("ref").map(|x| x.into_lower(ctx)).transpose()?;
512
513 let redeemer = self
514 .find("redeemer")
515 .map(|x| x.into_lower(ctx))
516 .transpose()?
517 .unwrap_or(ir::Expression::None);
518
519 let query = ir::InputQuery {
520 address: address.unwrap_or(ir::Expression::None),
521 min_amount: min_amount.unwrap_or(ir::Expression::None),
522 r#ref: r#ref.unwrap_or(ir::Expression::None),
523 };
524
525 let param = ir::Param::ExpectInput(self.name.to_lowercase().clone(), query);
526
527 let input = ir::Input {
528 name: self.name.to_lowercase().clone(),
529 utxos: param.into(),
530 redeemer,
531 };
532
533 Ok(input)
534 }
535}
536
537impl IntoLower for ast::OutputBlockField {
538 type Output = ir::Expression;
539
540 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
541 match self {
542 ast::OutputBlockField::To(x) => {
543 let ctx = ctx.enter_address_expr();
544 x.into_lower(&ctx)
545 }
546 ast::OutputBlockField::Amount(x) => {
547 let ctx = ctx.enter_asset_expr();
548 x.into_lower(&ctx)
549 }
550 ast::OutputBlockField::Datum(x) => {
551 let ctx = ctx.enter_datum_expr();
552 x.into_lower(&ctx)
553 }
554 }
555 }
556}
557
558impl IntoLower for ast::OutputBlock {
559 type Output = ir::Output;
560
561 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
562 let address = self.find("to").into_lower(ctx)?.unwrap_or_default();
563 let datum = self.find("datum").into_lower(ctx)?.unwrap_or_default();
564 let amount = self.find("amount").into_lower(ctx)?.unwrap_or_default();
565
566 Ok(ir::Output {
567 address,
568 datum,
569 amount,
570 })
571 }
572}
573
574impl IntoLower for ast::ValidityBlockField {
575 type Output = ir::Expression;
576
577 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
578 match self {
579 ast::ValidityBlockField::SinceSlot(x) => x.into_lower(ctx),
580 ast::ValidityBlockField::UntilSlot(x) => x.into_lower(ctx),
581 }
582 }
583}
584
585impl IntoLower for ast::ValidityBlock {
586 type Output = ir::Validity;
587
588 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
589 let since = self.find("since_slot").into_lower(ctx)?.unwrap_or_default();
590 let until = self.find("until_slot").into_lower(ctx)?.unwrap_or_default();
591
592 Ok(ir::Validity { since, until })
593 }
594}
595
596impl IntoLower for ast::MintBlockField {
597 type Output = ir::Expression;
598
599 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
600 match self {
601 ast::MintBlockField::Amount(x) => x.into_lower(ctx),
602 ast::MintBlockField::Redeemer(x) => x.into_lower(ctx),
603 }
604 }
605}
606
607impl IntoLower for ast::MintBlock {
608 type Output = ir::Mint;
609
610 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
611 let amount = self.find("amount").into_lower(ctx)?.unwrap_or_default();
612 let redeemer = self.find("redeemer").into_lower(ctx)?.unwrap_or_default();
613
614 Ok(ir::Mint { amount, redeemer })
615 }
616}
617impl IntoLower for ast::MetadataBlockField {
618 type Output = ir::Metadata;
619 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
620 Ok(ir::Metadata {
621 key: self.key.into_lower(ctx)?,
622 value: self.value.into_lower(ctx)?,
623 })
624 }
625}
626
627impl IntoLower for ast::MetadataBlock {
628 type Output = Vec<ir::Metadata>;
629
630 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
631 let fields = self
632 .fields
633 .iter()
634 .map(|metadata_field| metadata_field.into_lower(ctx))
635 .collect::<Result<Vec<_>, _>>()?;
636
637 Ok(fields)
638 }
639}
640
641impl IntoLower for ast::ChainSpecificBlock {
642 type Output = ir::AdHocDirective;
643
644 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
645 match self {
646 ast::ChainSpecificBlock::Cardano(x) => x.into_lower(ctx),
647 }
648 }
649}
650
651impl IntoLower for ast::ReferenceBlock {
652 type Output = ir::Expression;
653
654 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
655 self.r#ref.into_lower(ctx)
656 }
657}
658
659impl IntoLower for ast::CollateralBlockField {
660 type Output = ir::Expression;
661
662 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
663 match self {
664 ast::CollateralBlockField::From(x) => x.into_lower(ctx),
665 ast::CollateralBlockField::MinAmount(x) => x.into_lower(ctx),
666 ast::CollateralBlockField::Ref(x) => x.into_lower(ctx),
667 }
668 }
669}
670
671impl IntoLower for ast::CollateralBlock {
672 type Output = ir::Collateral;
673
674 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
675 let from = self.find("from").map(|x| x.into_lower(ctx)).transpose()?;
676
677 let min_amount = self
678 .find("min_amount")
679 .map(|x| x.into_lower(ctx))
680 .transpose()?;
681
682 let r#ref = self.find("ref").map(|x| x.into_lower(ctx)).transpose()?;
683
684 let query = ir::InputQuery {
685 address: from.unwrap_or(ir::Expression::None),
686 min_amount: min_amount.unwrap_or(ir::Expression::None),
687 r#ref: r#ref.unwrap_or(ir::Expression::None),
688 };
689
690 let param = ir::Param::ExpectInput("collateral".to_string(), query);
691
692 let collateral = ir::Collateral {
693 utxos: param.into(),
694 };
695
696 Ok(collateral)
697 }
698}
699
700impl IntoLower for ast::SignersBlock {
701 type Output = ir::Signers;
702
703 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
704 Ok(ir::Signers {
705 signers: self
706 .signers
707 .iter()
708 .map(|x| x.into_lower(ctx))
709 .collect::<Result<Vec<_>, _>>()?,
710 })
711 }
712}
713
714impl IntoLower for ast::TxDef {
715 type Output = ir::Tx;
716
717 fn into_lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
718 let ir = ir::Tx {
719 references: self
720 .references
721 .iter()
722 .map(|x| x.into_lower(ctx))
723 .collect::<Result<Vec<_>, _>>()?,
724 inputs: self
725 .inputs
726 .iter()
727 .map(|x| x.into_lower(ctx))
728 .collect::<Result<Vec<_>, _>>()?,
729 outputs: self
730 .outputs
731 .iter()
732 .map(|x| x.into_lower(ctx))
733 .collect::<Result<Vec<_>, _>>()?,
734 validity: self
735 .validity
736 .as_ref()
737 .map(|x| x.into_lower(ctx))
738 .transpose()?,
739 mints: self
740 .mints
741 .iter()
742 .map(|x| x.into_lower(ctx))
743 .collect::<Result<Vec<_>, _>>()?,
744 adhoc: self
745 .adhoc
746 .iter()
747 .map(|x| x.into_lower(ctx))
748 .collect::<Result<Vec<_>, _>>()?,
749 fees: ir::Param::ExpectFees.into(),
750 collateral: self
751 .collateral
752 .iter()
753 .map(|x| x.into_lower(ctx))
754 .collect::<Result<Vec<_>, _>>()?,
755 signers: self
756 .signers
757 .as_ref()
758 .map(|x| x.into_lower(ctx))
759 .transpose()?,
760 metadata: self
761 .metadata
762 .as_ref()
763 .map(|x| x.into_lower(ctx))
764 .transpose()?
765 .unwrap_or(vec![]),
766 };
767
768 Ok(ir)
769 }
770}
771
772pub fn lower_tx(ast: &ast::TxDef) -> Result<ir::Tx, Error> {
773 let ctx = &Context::default();
774
775 let tx = ast.into_lower(ctx)?;
776
777 Ok(tx)
778}
779
780pub fn lower(ast: &ast::Program, template: &str) -> Result<ir::Tx, Error> {
793 let tx = ast
794 .txs
795 .iter()
796 .find(|x| x.name.value == template)
797 .ok_or(Error::InvalidAst("tx not found".to_string()))?;
798
799 lower_tx(tx)
800}
801
802#[cfg(test)]
803mod tests {
804 use assert_json_diff::assert_json_eq;
805 use paste::paste;
806
807 use super::*;
808 use crate::parsing::{self};
809
810 fn make_snapshot_if_missing(example: &str, name: &str, tx: &ir::Tx) {
811 let manifest_dir = env!("CARGO_MANIFEST_DIR");
812
813 let path = format!("{}/../../examples/{}.{}.tir", manifest_dir, example, name);
814
815 if !std::fs::exists(&path).unwrap() {
816 let ir = serde_json::to_string_pretty(tx).unwrap();
817 std::fs::write(&path, ir).unwrap();
818 }
819 }
820
821 fn test_lowering_example(example: &str) {
822 let manifest_dir = env!("CARGO_MANIFEST_DIR");
823 let mut program = parsing::parse_well_known_example(example);
824
825 crate::analyzing::analyze(&mut program).ok().unwrap();
826
827 for tx in program.txs.iter() {
828 let tir = lower(&program, &tx.name.value).unwrap();
829
830 make_snapshot_if_missing(example, &tx.name.value, &tir);
831
832 let tir_file = format!(
833 "{}/../../examples/{}.{}.tir",
834 manifest_dir, example, tx.name.value
835 );
836
837 let expected = std::fs::read_to_string(tir_file).unwrap();
838 let expected: ir::Tx = serde_json::from_str(&expected).unwrap();
839
840 assert_json_eq!(tir, expected);
841 }
842 }
843
844 #[macro_export]
845 macro_rules! test_lowering {
846 ($name:ident) => {
847 paste! {
848 #[test]
849 fn [<test_example_ $name>]() {
850 test_lowering_example(stringify!($name));
851 }
852 }
853 };
854 }
855
856 test_lowering!(lang_tour);
857
858 test_lowering!(transfer);
859
860 test_lowering!(swap);
861
862 test_lowering!(asteria);
863
864 test_lowering!(vesting);
865
866 test_lowering!(faucet);
867
868 test_lowering!(input_datum);
869
870 test_lowering!(env_vars);
871
872 test_lowering!(local_vars);
873
874 test_lowering!(cardano_witness);
875}