tx3_lang/
lowering.rs

1//! Lowers the Tx3 language to the intermediate representation.
2//!
3//! This module takes an AST and performs lowering on it. It converts the AST
4//! into the intermediate representation (IR) of the Tx3 language.
5
6use 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
780/// Lowers the Tx3 language to the intermediate representation.
781///
782/// This function takes an AST and converts it into the intermediate
783/// representation (IR) of the Tx3 language.
784///
785/// # Arguments
786///
787/// * `ast` - The AST to lower
788///
789/// # Returns
790///
791/// * `Result<ir::Program, Error>` - The lowered intermediate representation
792pub 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}