1use std::cell::RefCell;
7use std::rc::Rc;
8
9use crate::ast;
10use tx3_tir::model::core::{Type, UtxoRef};
11use tx3_tir::model::v1beta0 as ir;
12
13#[derive(Debug, thiserror::Error)]
14pub enum Error {
15 #[error("missing analyze phase for {0}")]
16 MissingAnalyzePhase(String),
17
18 #[error("symbol '{0}' expected to be '{1}'")]
19 InvalidSymbol(String, &'static str),
20
21 #[error("symbol '{0}' expected to be of type '{1}'")]
22 InvalidSymbolType(String, &'static str),
23
24 #[error("invalid ast: {0}")]
25 InvalidAst(String),
26
27 #[error("invalid property {0} on type {1:?}")]
28 InvalidProperty(String, String),
29
30 #[error("missing required field {0} for {1:?}")]
31 MissingRequiredField(String, &'static str),
32
33 #[error("failed to decode hex string {0}")]
34 DecodeHexError(String),
35}
36
37#[inline]
38fn hex_decode(s: &str) -> Result<Vec<u8>, Error> {
39 hex::decode(s).map_err(|_| Error::DecodeHexError(s.to_string()))
40}
41
42fn expect_type_def(ident: &ast::Identifier) -> Result<&ast::TypeDef, Error> {
43 let symbol = ident
44 .symbol
45 .as_ref()
46 .ok_or(Error::MissingAnalyzePhase(ident.value.clone()))?;
47
48 symbol
49 .as_type_def()
50 .ok_or(Error::InvalidSymbol(ident.value.clone(), "TypeDef"))
51}
52
53fn expect_alias_def(ident: &ast::Identifier) -> Result<&ast::AliasDef, Error> {
54 let symbol = ident
55 .symbol
56 .as_ref()
57 .ok_or(Error::MissingAnalyzePhase(ident.value.clone()))?;
58
59 symbol
60 .as_alias_def()
61 .ok_or(Error::InvalidSymbol(ident.value.clone(), "AliasDef"))
62}
63
64fn expect_case_def(ident: &ast::Identifier) -> Result<&ast::VariantCase, Error> {
65 let symbol = ident
66 .symbol
67 .as_ref()
68 .ok_or(Error::MissingAnalyzePhase(ident.value.clone()))?;
69
70 symbol
71 .as_variant_case()
72 .ok_or(Error::InvalidSymbol(ident.value.clone(), "VariantCase"))
73}
74
75#[allow(dead_code)]
76fn expect_field_def(ident: &ast::Identifier) -> Result<&ast::RecordField, Error> {
77 let symbol = ident
78 .symbol
79 .as_ref()
80 .ok_or(Error::MissingAnalyzePhase(ident.value.clone()))?;
81
82 symbol
83 .as_field_def()
84 .ok_or(Error::InvalidSymbol(ident.value.clone(), "FieldDef"))
85}
86
87fn coerce_identifier_into_asset_def(identifier: &ast::Identifier) -> Result<ast::AssetDef, Error> {
88 match identifier.try_symbol()? {
89 ast::Symbol::AssetDef(x) => Ok(x.as_ref().clone()),
90 _ => Err(Error::InvalidSymbol(identifier.value.clone(), "AssetDef")),
91 }
92}
93
94#[derive(Debug, Default)]
97struct RefAccumulator {
98 refs: Vec<ir::Expression>,
99}
100
101impl RefAccumulator {
102 fn record(&mut self, r#ref: ir::Expression) {
103 if !self.refs.contains(&r#ref) {
104 self.refs.push(r#ref);
105 }
106 }
107}
108
109#[derive(Debug, Default, Clone)]
110pub(crate) struct Context {
111 is_asset_expr: bool,
112 is_datum_expr: bool,
113 is_address_expr: bool,
114 capture_policy_ref: bool,
117 script_refs: Rc<RefCell<RefAccumulator>>,
119}
120
121impl Context {
122 pub fn enter_asset_expr(&self) -> Self {
123 Self {
124 is_asset_expr: true,
125 is_datum_expr: false,
126 is_address_expr: false,
127 capture_policy_ref: self.capture_policy_ref,
128 script_refs: self.script_refs.clone(),
129 }
130 }
131
132 pub fn enter_datum_expr(&self) -> Self {
133 Self {
134 is_asset_expr: false,
135 is_datum_expr: true,
136 is_address_expr: false,
137 capture_policy_ref: self.capture_policy_ref,
138 script_refs: self.script_refs.clone(),
139 }
140 }
141
142 pub fn enter_address_expr(&self) -> Self {
143 Self {
144 is_asset_expr: false,
145 is_datum_expr: false,
146 is_address_expr: true,
147 capture_policy_ref: self.capture_policy_ref,
148 script_refs: self.script_refs.clone(),
149 }
150 }
151
152 pub fn capturing_policy_refs(&self) -> Self {
155 Self {
156 capture_policy_ref: true,
157 ..self.clone()
158 }
159 }
160
161 pub fn is_address_expr(&self) -> bool {
162 self.is_address_expr
163 }
164
165 pub fn is_asset_expr(&self) -> bool {
166 self.is_asset_expr
167 }
168
169 pub fn is_datum_expr(&self) -> bool {
170 self.is_datum_expr
171 }
172
173 pub fn captures_policy_refs(&self) -> bool {
174 self.capture_policy_ref
175 }
176
177 pub fn record_script_ref(&self, r#ref: ir::Expression) {
179 self.script_refs.borrow_mut().record(r#ref);
180 }
181
182 pub fn drain_script_refs(&self) -> Vec<ir::Expression> {
184 std::mem::take(&mut self.script_refs.borrow_mut().refs)
185 }
186}
187
188pub(crate) trait IntoLower {
189 type Output;
190
191 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error>;
192}
193
194impl<T> IntoLower for Option<&T>
195where
196 T: IntoLower,
197{
198 type Output = Option<T::Output>;
199
200 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
201 self.map(|x| x.lower(ctx)).transpose()
202 }
203}
204
205impl<T> IntoLower for Box<T>
206where
207 T: IntoLower,
208{
209 type Output = T::Output;
210
211 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
212 self.as_ref().lower(ctx)
213 }
214}
215
216impl IntoLower for ast::Identifier {
217 type Output = ir::Expression;
218
219 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
220 let symbol = self
221 .symbol
222 .as_ref()
223 .ok_or(Error::MissingAnalyzePhase(self.value.clone()))?;
224
225 match symbol {
226 ast::Symbol::ParamVar(n, ty) => {
227 Ok(ir::Param::ExpectValue(n.to_lowercase().clone(), ty.lower(ctx)?).into())
228 }
229 ast::Symbol::LocalExpr(expr) => Ok(expr.lower(ctx)?),
230 ast::Symbol::PartyDef(x) => Ok(ir::Param::ExpectValue(
231 x.name.value.to_lowercase().clone(),
232 Type::Address,
233 )
234 .into()),
235 ast::Symbol::Input(def) => {
236 let inner = def.lower(ctx)?.utxos;
237
238 let out = if ctx.is_asset_expr() {
239 ir::Coerce::IntoAssets(inner).into()
240 } else if ctx.is_datum_expr() {
241 ir::Coerce::IntoDatum(inner).into()
242 } else {
243 inner
244 };
245
246 Ok(out)
247 }
248 ast::Symbol::Reference(def) => def.lower(ctx),
249 ast::Symbol::Fees => Ok(ir::Param::ExpectFees.into()),
250 ast::Symbol::EnvVar(n, ty) => {
251 Ok(ir::Param::ExpectValue(n.to_lowercase().clone(), ty.lower(ctx)?).into())
252 }
253 ast::Symbol::PolicyDef(x) => {
254 let policy = x.lower(ctx)?;
255
256 if ctx.captures_policy_refs() {
260 if let Some(r#ref) = policy.script.as_utxo_ref() {
261 ctx.record_script_ref(r#ref);
262 }
263 }
264
265 if ctx.is_address_expr() {
266 Ok(ir::CompilerOp::BuildScriptAddress(policy.hash).into())
267 } else {
268 Ok(policy.hash)
269 }
270 }
271 ast::Symbol::Output(index) => Ok(ir::Expression::Number(*index as i128)),
272 _ => {
273 dbg!(&self);
274 todo!();
275 }
276 }
277 }
278}
279
280impl IntoLower for ast::UtxoRef {
281 type Output = ir::Expression;
282
283 fn lower(&self, _: &Context) -> Result<Self::Output, Error> {
284 let x = ir::Expression::UtxoRefs(vec![UtxoRef {
285 txid: self.txid.clone(),
286 index: self.index as u32,
287 }]);
288
289 Ok(x)
290 }
291}
292
293impl IntoLower for ast::StructConstructor {
294 type Output = ir::StructExpr;
295
296 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
297 let type_def = expect_type_def(&self.r#type)
298 .or_else(|_| {
299 expect_alias_def(&self.r#type).and_then(|alias_def| {
300 alias_def.resolve_alias_chain().ok_or_else(|| {
301 Error::InvalidAst("Alias does not resolve to a TypeDef".to_string())
302 })
303 })
304 })
305 .map_err(|_| Error::InvalidSymbol(self.r#type.value.clone(), "TypeDef or AliasDef"))?;
306
307 let constructor = type_def
308 .find_case_index(&self.case.name.value)
309 .ok_or(Error::InvalidAst("case not found".to_string()))?;
310
311 let case_def = expect_case_def(&self.case.name)?;
312
313 let mut fields = vec![];
314
315 for (index, field_def) in case_def.fields.iter().enumerate() {
316 let value = self.case.find_field_value(&field_def.name.value);
317
318 if let Some(value) = value {
319 fields.push(value.lower(ctx)?);
320 } else {
321 let spread_target = self
322 .case
323 .spread
324 .as_ref()
325 .expect("spread must be set for missing explicit field")
326 .lower(ctx)?;
327
328 fields.push(ir::Expression::EvalBuiltIn(Box::new(
329 ir::BuiltInOp::Property(spread_target, ir::Expression::Number(index as i128)),
330 )));
331 }
332 }
333
334 Ok(ir::StructExpr {
335 constructor,
336 fields,
337 })
338 }
339}
340
341impl IntoLower for ast::PolicyField {
342 type Output = ir::Expression;
343
344 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
345 match self {
346 ast::PolicyField::Hash(x) => x.lower(ctx),
347 ast::PolicyField::Script(x) => x.lower(ctx),
348 ast::PolicyField::Ref(x) => x.lower(ctx),
349 }
350 }
351}
352
353impl IntoLower for ast::PolicyDef {
354 type Output = ir::PolicyExpr;
355
356 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
357 match &self.value {
358 ast::PolicyValue::Assign(x) => {
359 let out = ir::PolicyExpr {
360 name: self.name.value.clone(),
361 hash: ir::Expression::Hash(hex_decode(&x.value)?),
362 script: ir::ScriptSource::expect_parameter(self.name.value.clone()),
363 };
364
365 Ok(out)
366 }
367 ast::PolicyValue::Constructor(x) => {
368 let hash = x
369 .find_field("hash")
370 .ok_or(Error::InvalidAst("Missing policy hash".to_string()))?
371 .lower(ctx)?;
372
373 let rf = x.find_field("ref").map(|x| x.lower(ctx)).transpose()?;
374
375 let script = x.find_field("script").map(|x| x.lower(ctx)).transpose()?;
376
377 let script = match (rf, script) {
378 (Some(rf), Some(script)) => ir::ScriptSource::new_ref(rf, script),
379 (Some(rf), None) => {
380 ir::ScriptSource::expect_ref_input(self.name.value.clone(), rf)
381 }
382 (None, Some(script)) => ir::ScriptSource::new_embedded(script),
383 (None, None) => ir::ScriptSource::expect_parameter(self.name.value.clone()),
384 };
385
386 Ok(ir::PolicyExpr {
387 name: self.name.value.clone(),
388 hash,
389 script,
390 })
391 }
392 }
393 }
394}
395
396impl IntoLower for ast::Type {
397 type Output = Type;
398
399 fn lower(&self, _: &Context) -> Result<Self::Output, Error> {
400 match self {
401 ast::Type::Undefined => Ok(Type::Undefined),
402 ast::Type::Unit => Ok(Type::Unit),
403 ast::Type::Int => Ok(Type::Int),
404 ast::Type::Bool => Ok(Type::Bool),
405 ast::Type::Bytes => Ok(Type::Bytes),
406 ast::Type::Address => Ok(Type::Address),
407 ast::Type::Utxo => Ok(Type::Utxo),
408 ast::Type::UtxoRef => Ok(Type::UtxoRef),
409 ast::Type::AnyAsset => Ok(Type::AnyAsset),
410 ast::Type::List(_) => Ok(Type::List),
411 ast::Type::Map(_, _) => Ok(Type::Map),
412 ast::Type::Tuple(_) => Ok(Type::Tuple),
413 ast::Type::Custom(x) => Ok(Type::Custom(x.value.clone())),
414 }
415 }
416}
417
418impl IntoLower for ast::AddOp {
419 type Output = ir::Expression;
420
421 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
422 let left = self.lhs.lower(ctx)?;
423 let right = self.rhs.lower(ctx)?;
424
425 Ok(ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
426 left, right,
427 ))))
428 }
429}
430
431impl IntoLower for ast::SubOp {
432 type Output = ir::Expression;
433
434 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
435 let left = self.lhs.lower(ctx)?;
436 let right = self.rhs.lower(ctx)?;
437
438 Ok(ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Sub(
439 left, right,
440 ))))
441 }
442}
443
444impl IntoLower for ast::MulOp {
445 type Output = ir::Expression;
446
447 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
448 let left = self.lhs.lower(ctx)?;
449 let right = self.rhs.lower(ctx)?;
450
451 Ok(ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Mul(
452 left, right,
453 ))))
454 }
455}
456
457impl IntoLower for ast::DivOp {
458 type Output = ir::Expression;
459
460 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
461 let left = self.lhs.lower(ctx)?;
462 let right = self.rhs.lower(ctx)?;
463
464 Ok(ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Div(
465 left, right,
466 ))))
467 }
468}
469
470impl IntoLower for ast::ConcatOp {
471 type Output = ir::Expression;
472
473 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
474 let left = self.lhs.lower(ctx)?;
475 let right = self.rhs.lower(ctx)?;
476
477 Ok(ir::Expression::EvalBuiltIn(Box::new(
478 ir::BuiltInOp::Concat(left, right),
479 )))
480 }
481}
482
483impl IntoLower for ast::NegateOp {
484 type Output = ir::Expression;
485
486 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
487 let operand = self.operand.lower(ctx)?;
488
489 Ok(ir::Expression::EvalBuiltIn(Box::new(
490 ir::BuiltInOp::Negate(operand),
491 )))
492 }
493}
494
495impl IntoLower for ast::FnCall {
496 type Output = ir::Expression;
497
498 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
499 if let Some(fn_def) = self.callee.symbol.as_ref().and_then(|s| s.as_fn_def()) {
503 if let Some(builtin) = fn_def.builtin {
504 return crate::builtins::resolve(builtin).lower_call(&self.args, ctx);
505 }
506
507 let body = fn_def.body.as_ref().ok_or_else(|| {
510 Error::InvalidAst(format!(
511 "function '{}' has neither a body nor a built-in kind",
512 fn_def.name.value
513 ))
514 })?;
515
516 let lowered_body = body.result.lower(ctx)?;
517
518 let mut subs = std::collections::HashMap::new();
519 for (param, arg) in fn_def.parameters.parameters.iter().zip(&self.args) {
520 subs.insert(param.name.value.to_lowercase(), arg.lower(ctx)?);
521 }
522
523 use tx3_tir::Node;
524 let mut visitor = ParamSubstituter { subs: &subs };
525 return lowered_body
526 .apply(&mut visitor)
527 .map_err(|e| Error::InvalidAst(e.to_string()));
528 }
529
530 match coerce_identifier_into_asset_def(&self.callee) {
533 Ok(asset_def) => {
534 let policy = asset_def.policy.lower(ctx)?;
535 let asset_name = asset_def.asset_name.lower(ctx)?;
536 let amount = self.args[0].lower(ctx)?;
537
538 Ok(ir::Expression::Assets(vec![ir::AssetExpr {
539 policy,
540 asset_name,
541 amount,
542 }]))
543 }
544 Err(_) => Err(Error::InvalidAst(format!(
545 "unknown function: {}",
546 self.callee.value
547 ))),
548 }
549 }
550}
551
552struct ParamSubstituter<'a> {
555 subs: &'a std::collections::HashMap<String, ir::Expression>,
556}
557
558impl tx3_tir::Visitor for ParamSubstituter<'_> {
559 fn reduce(&mut self, expr: ir::Expression) -> Result<ir::Expression, tx3_tir::reduce::Error> {
560 if let ir::Expression::EvalParam(ref param) = expr {
561 if let ir::Param::ExpectValue(name, _) = param.as_ref() {
562 if let Some(replacement) = self.subs.get(name) {
563 return Ok(replacement.clone());
564 }
565 }
566 }
567 Ok(expr)
568 }
569}
570
571impl IntoLower for ast::PropertyOp {
572 type Output = ir::Expression;
573
574 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
575 let ty = self
581 .operand
582 .target_type()
583 .ok_or_else(|| Error::MissingAnalyzePhase(format!("{0:?}", self.operand)))?;
584
585 let object = self.operand.lower(&ctx.enter_datum_expr())?;
591
592 let prop_index = ty.property_index(*self.property.clone()).ok_or_else(|| {
593 Error::InvalidProperty(format!("{:?}", self.property), ty.to_string())
594 })?;
595
596 Ok(ir::Expression::EvalBuiltIn(Box::new(
597 ir::BuiltInOp::Property(object, prop_index.lower(ctx)?),
598 )))
599 }
600}
601
602impl IntoLower for ast::ListConstructor {
603 type Output = Vec<ir::Expression>;
604
605 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
606 let elements = self
607 .elements
608 .iter()
609 .map(|x| x.lower(ctx))
610 .collect::<Result<Vec<_>, _>>()?;
611
612 Ok(elements)
613 }
614}
615
616impl IntoLower for ast::TupleConstructor {
617 type Output = ir::Expression;
618
619 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
620 let elements = self
621 .elements
622 .iter()
623 .map(|x| x.lower(ctx))
624 .collect::<Result<Vec<_>, _>>()?;
625
626 Ok(ir::Expression::Tuple(elements))
627 }
628}
629
630impl IntoLower for ast::MapConstructor {
631 type Output = ir::Expression;
632
633 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
634 let pairs = self
635 .fields
636 .iter()
637 .map(|field| {
638 let key = field.key.lower(ctx)?;
639 let value = field.value.lower(ctx)?;
640 Ok((key, value))
641 })
642 .collect::<Result<Vec<_>, _>>()?;
643
644 Ok(ir::Expression::Map(pairs))
645 }
646}
647
648impl IntoLower for ast::DataExpr {
649 type Output = ir::Expression;
650
651 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
652 let out = match self {
653 ast::DataExpr::None => ir::Expression::None,
654 ast::DataExpr::Number(x) => Self::Output::Number(*x as i128),
655 ast::DataExpr::Bool(x) => ir::Expression::Bool(*x),
656 ast::DataExpr::String(x) => ir::Expression::String(x.value.clone()),
657 ast::DataExpr::HexString(x) => ir::Expression::Bytes(hex_decode(&x.value)?),
658 ast::DataExpr::StructConstructor(x) => ir::Expression::Struct(x.lower(ctx)?),
659 ast::DataExpr::ListConstructor(x) => ir::Expression::List(x.lower(ctx)?),
660 ast::DataExpr::MapConstructor(x) => x.lower(ctx)?,
661 ast::DataExpr::TupleConstructor(x) => x.lower(ctx)?,
662 ast::DataExpr::AnyAssetConstructor(x) => x.lower(ctx)?,
663 ast::DataExpr::Unit => ir::Expression::Struct(ir::StructExpr::unit()),
664 ast::DataExpr::Identifier(x) => x.lower(ctx)?,
665 ast::DataExpr::AddOp(x) => x.lower(ctx)?,
666 ast::DataExpr::SubOp(x) => x.lower(ctx)?,
667 ast::DataExpr::MulOp(x) => x.lower(ctx)?,
668 ast::DataExpr::DivOp(x) => x.lower(ctx)?,
669 ast::DataExpr::ConcatOp(x) => x.lower(ctx)?,
670 ast::DataExpr::NegateOp(x) => x.lower(ctx)?,
671 ast::DataExpr::PropertyOp(x) => x.lower(ctx)?,
672 ast::DataExpr::UtxoRef(x) => x.lower(ctx)?,
673 ast::DataExpr::FnCall(x) => x.lower(ctx)?,
674 };
675
676 Ok(out)
677 }
678}
679
680impl IntoLower for ast::AnyAssetConstructor {
681 type Output = ir::Expression;
682
683 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
684 let ctx = &ctx.enter_datum_expr();
685 let policy = self.policy.lower(ctx)?;
686
687 let ctx = &ctx.enter_datum_expr();
688 let asset_name = self.asset_name.lower(ctx)?;
689
690 let ctx = &ctx.enter_datum_expr();
691 let amount = self.amount.lower(ctx)?;
692
693 Ok(ir::Expression::Assets(vec![ir::AssetExpr {
694 policy,
695 asset_name,
696 amount,
697 }]))
698 }
699}
700
701impl IntoLower for ast::InputBlockField {
702 type Output = ir::Expression;
703
704 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
705 match self {
706 ast::InputBlockField::From(x) => {
707 let ctx = ctx.enter_address_expr().capturing_policy_refs();
709 x.lower(&ctx)
710 }
711 ast::InputBlockField::DatumIs(_) => Ok(ir::Expression::None),
712 ast::InputBlockField::MinAmount(x) => {
713 let ctx = ctx.enter_asset_expr();
714 x.lower(&ctx)
715 }
716 ast::InputBlockField::Redeemer(x) => {
717 let ctx = ctx.enter_datum_expr();
718 x.lower(&ctx)
719 }
720 ast::InputBlockField::Ref(x) => x.lower(ctx),
721 }
722 }
723}
724
725impl IntoLower for ast::InputBlock {
726 type Output = ir::Input;
727
728 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
729 let from_field = self.find("from");
730
731 let address = from_field.map(|x| x.lower(ctx)).transpose()?;
732
733 let min_amount = self.find("min_amount").map(|x| x.lower(ctx)).transpose()?;
734
735 let r#ref = self.find("ref").map(|x| x.lower(ctx)).transpose()?;
736
737 let redeemer = self
738 .find("redeemer")
739 .map(|x| x.lower(ctx))
740 .transpose()?
741 .unwrap_or(ir::Expression::None);
742
743 let query = ir::InputQuery {
744 address: address.unwrap_or(ir::Expression::None),
745 min_amount: min_amount.unwrap_or(ir::Expression::None),
746 r#ref: r#ref.unwrap_or(ir::Expression::None),
747 many: self.many,
748 collateral: false,
749 };
750
751 let param = ir::Param::ExpectInput(self.name.to_lowercase().clone(), query);
752
753 let input = ir::Input {
754 name: self.name.to_lowercase().clone(),
755 utxos: param.into(),
756 redeemer,
757 };
758
759 Ok(input)
760 }
761}
762
763impl IntoLower for ast::OutputBlockField {
764 type Output = ir::Expression;
765
766 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
767 match self {
768 ast::OutputBlockField::To(x) => {
769 let ctx = ctx.enter_address_expr();
770 x.lower(&ctx)
771 }
772 ast::OutputBlockField::Amount(x) => {
773 let ctx = ctx.enter_asset_expr();
774 x.lower(&ctx)
775 }
776 ast::OutputBlockField::Datum(x) => {
777 let ctx = ctx.enter_datum_expr();
778 x.lower(&ctx)
779 }
780 }
781 }
782}
783
784impl IntoLower for ast::OutputBlock {
785 type Output = ir::Output;
786
787 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
788 let address = self.find("to").lower(ctx)?.unwrap_or_default();
789 let datum = self.find("datum").lower(ctx)?.unwrap_or_default();
790 let amount = self.find("amount").lower(ctx)?.unwrap_or_default();
791
792 Ok(ir::Output {
793 address,
794 datum,
795 amount,
796 optional: self.optional,
797 })
798 }
799}
800
801impl IntoLower for ast::ValidityBlockField {
802 type Output = ir::Expression;
803
804 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
805 match self {
806 ast::ValidityBlockField::SinceSlot(x) => x.lower(ctx),
807 ast::ValidityBlockField::UntilSlot(x) => x.lower(ctx),
808 }
809 }
810}
811
812impl IntoLower for ast::ValidityBlock {
813 type Output = ir::Validity;
814
815 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
816 let since = self.find("since_slot").lower(ctx)?.unwrap_or_default();
817 let until = self.find("until_slot").lower(ctx)?.unwrap_or_default();
818
819 Ok(ir::Validity { since, until })
820 }
821}
822
823impl IntoLower for ast::MintBlockField {
824 type Output = ir::Expression;
825
826 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
827 match self {
828 ast::MintBlockField::Amount(x) => x.lower(&ctx.capturing_policy_refs()),
830 ast::MintBlockField::Redeemer(x) => x.lower(ctx),
831 }
832 }
833}
834
835impl IntoLower for ast::MintBlock {
836 type Output = ir::Mint;
837
838 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
839 let amount = self.find("amount").lower(ctx)?.unwrap_or_default();
840 let redeemer = self.find("redeemer").lower(ctx)?.unwrap_or_default();
841
842 Ok(ir::Mint { amount, redeemer })
843 }
844}
845
846impl IntoLower for ast::MetadataBlockField {
847 type Output = ir::Metadata;
848 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
849 Ok(ir::Metadata {
850 key: self.key.lower(ctx)?,
851 value: self.value.lower(ctx)?,
852 })
853 }
854}
855
856impl IntoLower for ast::MetadataBlock {
857 type Output = Vec<ir::Metadata>;
858
859 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
860 let fields = self
861 .fields
862 .iter()
863 .map(|metadata_field| metadata_field.lower(ctx))
864 .collect::<Result<Vec<_>, _>>()?;
865
866 Ok(fields)
867 }
868}
869
870impl IntoLower for ast::ChainSpecificBlock {
871 type Output = ir::AdHocDirective;
872
873 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
874 match self {
875 ast::ChainSpecificBlock::Cardano(x) => x.lower(ctx),
876 }
877 }
878}
879
880impl IntoLower for ast::ReferenceBlock {
881 type Output = ir::Expression;
882
883 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
884 let r#ref = self.r#ref.lower(ctx)?;
885
886 let query = ir::InputQuery {
887 address: ir::Expression::None,
888 min_amount: ir::Expression::None,
889 r#ref,
890 many: false,
891 collateral: false,
892 };
893
894 let inner = ir::Param::ExpectInput(self.name.to_lowercase(), query).into();
895
896 let out = if ctx.is_asset_expr() {
897 ir::Coerce::IntoAssets(inner).into()
898 } else if ctx.is_datum_expr() {
899 ir::Coerce::IntoDatum(inner).into()
900 } else {
901 inner
902 };
903
904 Ok(out)
905 }
906}
907
908impl IntoLower for ast::CollateralBlockField {
909 type Output = ir::Expression;
910
911 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
912 match self {
913 ast::CollateralBlockField::From(x) => x.lower(ctx),
914 ast::CollateralBlockField::MinAmount(x) => x.lower(ctx),
915 ast::CollateralBlockField::Ref(x) => x.lower(ctx),
916 }
917 }
918}
919
920impl IntoLower for ast::CollateralBlock {
921 type Output = ir::Collateral;
922
923 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
924 let from = self.find("from").map(|x| x.lower(ctx)).transpose()?;
925
926 let min_amount = self.find("min_amount").map(|x| x.lower(ctx)).transpose()?;
927
928 let r#ref = self.find("ref").map(|x| x.lower(ctx)).transpose()?;
929
930 let query = ir::InputQuery {
931 address: from.unwrap_or(ir::Expression::None),
932 min_amount: min_amount.unwrap_or(ir::Expression::None),
933 r#ref: r#ref.unwrap_or(ir::Expression::None),
934 many: false,
935 collateral: true,
936 };
937
938 let param = ir::Param::ExpectInput("collateral".to_string(), query);
939
940 let collateral = ir::Collateral {
941 utxos: param.into(),
942 };
943
944 Ok(collateral)
945 }
946}
947
948impl IntoLower for ast::SignersBlock {
949 type Output = ir::Signers;
950
951 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
952 Ok(ir::Signers {
953 signers: self
954 .signers
955 .iter()
956 .map(|x| x.lower(ctx))
957 .collect::<Result<Vec<_>, _>>()?,
958 })
959 }
960}
961
962impl IntoLower for ast::TxDef {
963 type Output = ir::Tx;
964
965 fn lower(&self, ctx: &Context) -> Result<Self::Output, Error> {
966 for reference in self.references.iter() {
969 let r#ref = reference.r#ref.lower(ctx)?;
970 ctx.record_script_ref(r#ref);
971 }
972
973 let inputs = self
974 .inputs
975 .iter()
976 .map(|x| x.lower(ctx))
977 .collect::<Result<Vec<_>, _>>()?;
978 let outputs = self
979 .outputs
980 .iter()
981 .map(|x| x.lower(ctx))
982 .collect::<Result<Vec<_>, _>>()?;
983 let validity = self.validity.as_ref().map(|x| x.lower(ctx)).transpose()?;
984 let mints = self
985 .mints
986 .iter()
987 .map(|x| x.lower(ctx))
988 .collect::<Result<Vec<_>, _>>()?;
989 let burns = self
990 .burns
991 .iter()
992 .map(|x| x.lower(ctx))
993 .collect::<Result<Vec<_>, _>>()?;
994 let adhoc = self
995 .adhoc
996 .iter()
997 .map(|x| x.lower(ctx))
998 .collect::<Result<Vec<_>, _>>()?;
999 let collateral = self
1000 .collateral
1001 .iter()
1002 .map(|x| x.lower(ctx))
1003 .collect::<Result<Vec<_>, _>>()?;
1004 let signers = self.signers.as_ref().map(|x| x.lower(ctx)).transpose()?;
1005 let metadata = self
1006 .metadata
1007 .as_ref()
1008 .map(|x| x.lower(ctx))
1009 .transpose()?
1010 .unwrap_or(vec![]);
1011
1012 let ir = ir::Tx {
1013 references: ctx.drain_script_refs(),
1014 inputs,
1015 outputs,
1016 validity,
1017 mints,
1018 burns,
1019 adhoc,
1020 fees: ir::Param::ExpectFees.into(),
1021 collateral,
1022 signers,
1023 metadata,
1024 };
1025
1026 Ok(ir)
1027 }
1028}
1029
1030pub fn lower_tx(ast: &ast::TxDef) -> Result<ir::Tx, Error> {
1031 let ctx = &Context::default();
1032
1033 let tx = ast.lower(ctx)?;
1034
1035 Ok(tx)
1036}
1037
1038pub fn lower(ast: &ast::Program, template: &str) -> Result<ir::Tx, Error> {
1051 let tx = ast
1052 .txs
1053 .iter()
1054 .find(|x| x.name.value == template)
1055 .ok_or(Error::InvalidAst("tx not found".to_string()))?;
1056
1057 lower_tx(tx)
1058}
1059
1060#[cfg(test)]
1061mod tests {
1062 use assert_json_diff::assert_json_eq;
1063 use paste::paste;
1064
1065 use super::*;
1066 use crate::parsing::{self};
1067
1068 fn make_snapshot_if_missing(example: &str, name: &str, tx: &ir::Tx) {
1069 let manifest_dir = env!("CARGO_MANIFEST_DIR");
1070
1071 let path = format!("{}/../../examples/{}.{}.tir", manifest_dir, example, name);
1072
1073 if !std::fs::exists(&path).unwrap() {
1074 let ir = serde_json::to_string_pretty(tx).unwrap();
1075 std::fs::write(&path, ir).unwrap();
1076 }
1077 }
1078
1079 fn test_lowering_example(example: &str) -> std::collections::BTreeMap<String, ir::Tx> {
1082 let manifest_dir = env!("CARGO_MANIFEST_DIR");
1083 let mut program = parsing::parse_well_known_example(example);
1084
1085 crate::analyzing::analyze(&mut program).ok().unwrap();
1086
1087 let mut lowered = std::collections::BTreeMap::new();
1088
1089 for tx in program.txs.iter() {
1090 let tir = lower(&program, &tx.name.value).unwrap();
1091
1092 make_snapshot_if_missing(example, &tx.name.value, &tir);
1093
1094 let tir_file = format!(
1095 "{}/../../examples/{}.{}.tir",
1096 manifest_dir, example, tx.name.value
1097 );
1098
1099 let expected = std::fs::read_to_string(tir_file).unwrap();
1100 let expected: ir::Tx = serde_json::from_str(&expected).unwrap();
1101
1102 assert_json_eq!(tir, expected);
1103
1104 lowered.insert(tx.name.value.clone(), tir);
1105 }
1106
1107 lowered
1108 }
1109
1110 #[macro_export]
1111 macro_rules! test_lowering {
1112 ($name:ident) => {
1113 paste! {
1114 #[test]
1115 fn [<test_example_ $name>]() {
1116 test_lowering_example(stringify!($name));
1117 }
1118 }
1119 };
1120 ($name:ident, |$txs:ident| $checks:block) => {
1123 paste! {
1124 #[test]
1125 fn [<test_example_ $name>]() {
1126 let $txs = test_lowering_example(stringify!($name));
1127 $checks
1128 }
1129 }
1130 };
1131 }
1132
1133 test_lowering!(lang_tour);
1134
1135 test_lowering!(tuples);
1136
1137 test_lowering!(transfer);
1138
1139 test_lowering!(swap);
1140
1141 test_lowering!(asteria);
1142
1143 test_lowering!(vesting);
1144
1145 test_lowering!(faucet);
1146
1147 test_lowering!(input_datum);
1148
1149 test_lowering!(env_vars);
1150
1151 test_lowering!(local_vars);
1152
1153 test_lowering!(cardano_witness);
1154
1155 test_lowering!(reference_script);
1156
1157 test_lowering!(policy_reference_script, |txs| {
1158 assert_eq!(txs["spend"].references.len(), 1);
1160 assert_eq!(txs["spend_two"].references.len(), 1);
1162 assert!(txs["spend_hash_only"].references.is_empty());
1164 assert_eq!(txs["mint_token"].references.len(), 1);
1166 assert_eq!(txs["burn_token"].references.len(), 1);
1168 assert!(txs["mint_hash_only"].references.is_empty());
1170 assert!(txs["send_to_policy"].references.is_empty());
1172 assert_eq!(txs["withdraw"].references.len(), 1);
1174 });
1175
1176 test_lowering!(withdrawal);
1177
1178 test_lowering!(map);
1179
1180 test_lowering!(burn);
1181
1182 test_lowering!(min_utxo);
1183
1184 test_lowering!(tip_slot);
1185
1186 test_lowering!(posix_time);
1187
1188 test_lowering!(donation);
1189
1190 test_lowering!(list_concat);
1191
1192 test_lowering!(buidler_fest_2026);
1193
1194 test_lowering!(functions);
1195
1196 test_lowering!(nested_functions);
1197
1198 test_lowering!(param_field_shadow);
1199
1200 test_lowering!(oracle_reference_datum);
1201
1202 test_lowering!(reference_datum);
1203}