Skip to main content

tx3_lang/
cardano.rs

1use std::{collections::HashMap, rc::Rc};
2
3use pest::iterators::Pair;
4use serde::{Deserialize, Serialize};
5
6use tx3_tir::model::v1beta0 as ir;
7
8use crate::{
9    analyzing::{Analyzable, AnalyzeReport},
10    ast::{DataExpr, Identifier, Scope, Span, Type},
11    lowering::IntoLower,
12    parsing::{AstNode, Error, Rule},
13};
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16pub enum WithdrawalField {
17    From(Box<DataExpr>),
18    Amount(Box<DataExpr>),
19    Redeemer(Box<DataExpr>),
20}
21
22impl WithdrawalField {
23    fn key(&self) -> &str {
24        match self {
25            WithdrawalField::From(_) => "from",
26            WithdrawalField::Amount(_) => "amount",
27            WithdrawalField::Redeemer(_) => "redeemer",
28        }
29    }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33pub struct WithdrawalBlock {
34    pub fields: Vec<WithdrawalField>,
35    pub span: Span,
36}
37
38impl WithdrawalBlock {
39    pub(crate) fn find(&self, key: &str) -> Option<&WithdrawalField> {
40        self.fields.iter().find(|x| x.key() == key)
41    }
42}
43
44impl AstNode for WithdrawalField {
45    const RULE: Rule = Rule::cardano_withdrawal_field;
46
47    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
48        match pair.as_rule() {
49            Rule::cardano_withdrawal_from => {
50                let pair = pair.into_inner().next().unwrap();
51                Ok(WithdrawalField::From(DataExpr::parse(pair)?.into()))
52            }
53            Rule::cardano_withdrawal_amount => {
54                let pair = pair.into_inner().next().unwrap();
55                Ok(WithdrawalField::Amount(DataExpr::parse(pair)?.into()))
56            }
57            Rule::cardano_withdrawal_redeemer => {
58                let pair = pair.into_inner().next().unwrap();
59                Ok(WithdrawalField::Redeemer(DataExpr::parse(pair)?.into()))
60            }
61            x => unreachable!("Unexpected rule in cardano_withdrawal_field: {:?}", x),
62        }
63    }
64
65    fn span(&self) -> &Span {
66        match self {
67            Self::From(x) => x.span(),
68            Self::Amount(x) => x.span(),
69            Self::Redeemer(x) => x.span(),
70        }
71    }
72}
73
74impl AstNode for WithdrawalBlock {
75    const RULE: Rule = Rule::cardano_withdrawal_block;
76
77    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
78        let span = pair.as_span().into();
79        let inner = pair.into_inner();
80
81        let fields = inner
82            .map(|x| WithdrawalField::parse(x))
83            .collect::<Result<Vec<_>, _>>()?;
84
85        Ok(WithdrawalBlock { fields, span })
86    }
87
88    fn span(&self) -> &Span {
89        &self.span
90    }
91}
92
93impl Analyzable for WithdrawalField {
94    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
95        match self {
96            WithdrawalField::From(x) => x.analyze(parent),
97            WithdrawalField::Amount(x) => {
98                let amount = x.analyze(parent.clone());
99                let amount_type = AnalyzeReport::expect_data_expr_type(x, &Type::Int);
100                amount + amount_type
101            }
102            WithdrawalField::Redeemer(x) => x.analyze(parent),
103        }
104    }
105
106    fn is_resolved(&self) -> bool {
107        match self {
108            WithdrawalField::From(x) => x.is_resolved(),
109            WithdrawalField::Amount(x) => x.is_resolved(),
110            WithdrawalField::Redeemer(x) => x.is_resolved(),
111        }
112    }
113}
114
115impl Analyzable for WithdrawalBlock {
116    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
117        self.fields.analyze(parent)
118    }
119
120    fn is_resolved(&self) -> bool {
121        self.fields.is_resolved()
122    }
123}
124
125impl IntoLower for WithdrawalField {
126    type Output = ir::Expression;
127
128    fn lower(
129        &self,
130        ctx: &crate::lowering::Context,
131    ) -> Result<Self::Output, crate::lowering::Error> {
132        match self {
133            // Withdrawing from a script stake credential runs its script.
134            WithdrawalField::From(x) => x.lower(&ctx.capturing_policy_refs()),
135            WithdrawalField::Amount(x) => x.lower(ctx),
136            WithdrawalField::Redeemer(x) => x.lower(ctx),
137        }
138    }
139}
140
141impl IntoLower for WithdrawalBlock {
142    type Output = ir::AdHocDirective;
143
144    fn lower(
145        &self,
146        ctx: &crate::lowering::Context,
147    ) -> Result<Self::Output, crate::lowering::Error> {
148        let credential = self
149            .find("from")
150            .ok_or_else(|| {
151                crate::lowering::Error::MissingRequiredField("from".to_string(), "WithdrawalBlock")
152            })?
153            .lower(ctx)?;
154
155        let amount = self
156            .find("amount")
157            .ok_or_else(|| {
158                crate::lowering::Error::MissingRequiredField(
159                    "amount".to_string(),
160                    "WithdrawalBlock",
161                )
162            })?
163            .lower(ctx)?;
164
165        let redeemer = self
166            .find("redeemer")
167            .map(|r| r.lower(ctx))
168            .transpose()?
169            .unwrap_or(ir::Expression::None);
170
171        Ok(ir::AdHocDirective {
172            name: "withdrawal".to_string(),
173            data: std::collections::HashMap::from([
174                ("credential".to_string(), credential),
175                ("amount".to_string(), amount),
176                ("redeemer".to_string(), redeemer),
177            ]),
178        })
179    }
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
183pub struct VoteDelegationCertificate {
184    pub drep: DataExpr,
185    pub stake: DataExpr,
186    pub span: Span,
187}
188
189impl AstNode for VoteDelegationCertificate {
190    const RULE: Rule = Rule::cardano_vote_delegation_certificate;
191
192    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
193        let span = pair.as_span().into();
194        let mut inner = pair.into_inner();
195
196        Ok(VoteDelegationCertificate {
197            drep: DataExpr::parse(inner.next().unwrap())?,
198            stake: DataExpr::parse(inner.next().unwrap())?,
199            span,
200        })
201    }
202
203    fn span(&self) -> &Span {
204        &self.span
205    }
206}
207
208impl Analyzable for VoteDelegationCertificate {
209    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
210        let drep = self.drep.analyze(parent.clone());
211        let stake = self.stake.analyze(parent.clone());
212
213        drep + stake
214    }
215
216    fn is_resolved(&self) -> bool {
217        self.drep.is_resolved() && self.stake.is_resolved()
218    }
219}
220
221impl IntoLower for VoteDelegationCertificate {
222    type Output = ir::AdHocDirective;
223
224    fn lower(
225        &self,
226        ctx: &crate::lowering::Context,
227    ) -> Result<Self::Output, crate::lowering::Error> {
228        Ok(ir::AdHocDirective {
229            name: "vote_delegation_certificate".to_string(),
230            data: HashMap::from([
231                ("drep".to_string(), self.drep.lower(ctx)?),
232                ("stake".to_string(), self.stake.lower(ctx)?),
233            ]),
234        })
235    }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
239pub struct StakeDelegationCertificate {
240    pub pool: DataExpr,
241    pub stake: DataExpr,
242    pub span: Span,
243}
244
245impl AstNode for StakeDelegationCertificate {
246    const RULE: Rule = Rule::cardano_stake_delegation_certificate;
247
248    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
249        let span = pair.as_span().into();
250        let mut inner = pair.into_inner();
251
252        Ok(StakeDelegationCertificate {
253            pool: DataExpr::parse(inner.next().unwrap())?,
254            stake: DataExpr::parse(inner.next().unwrap())?,
255            span,
256        })
257    }
258
259    fn span(&self) -> &Span {
260        &self.span
261    }
262}
263
264impl Analyzable for StakeDelegationCertificate {
265    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
266        let pool = self.pool.analyze(parent.clone());
267        let stake = self.stake.analyze(parent.clone());
268
269        pool + stake
270    }
271
272    fn is_resolved(&self) -> bool {
273        self.pool.is_resolved() && self.stake.is_resolved()
274    }
275}
276
277impl IntoLower for StakeDelegationCertificate {
278    type Output = ir::AdHocDirective;
279
280    fn lower(
281        &self,
282        _ctx: &crate::lowering::Context,
283    ) -> Result<Self::Output, crate::lowering::Error> {
284        todo!("StakeDelegationCertificate lowering not implemented")
285    }
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
289pub enum PlutusWitnessField {
290    Version(DataExpr, Span),
291    Script(DataExpr, Span),
292}
293
294impl IntoLower for PlutusWitnessField {
295    type Output = (String, ir::Expression);
296
297    fn lower(
298        &self,
299        ctx: &crate::lowering::Context,
300    ) -> Result<Self::Output, crate::lowering::Error> {
301        match self {
302            PlutusWitnessField::Version(x, _) => Ok(("version".to_string(), x.lower(ctx)?)),
303            PlutusWitnessField::Script(x, _) => Ok(("script".to_string(), x.lower(ctx)?)),
304        }
305    }
306}
307
308impl AstNode for PlutusWitnessField {
309    const RULE: Rule = Rule::cardano_plutus_witness_field;
310
311    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
312        let span = pair.as_span().into();
313
314        match pair.as_rule() {
315            Rule::cardano_plutus_witness_version => {
316                Ok(PlutusWitnessField::Version(DataExpr::parse(pair)?, span))
317            }
318            Rule::cardano_plutus_witness_script => {
319                Ok(PlutusWitnessField::Script(DataExpr::parse(pair)?, span))
320            }
321            x => unreachable!("Unexpected rule in cardano_plutus_witness_field: {:?}", x),
322        }
323    }
324
325    fn span(&self) -> &Span {
326        match self {
327            PlutusWitnessField::Version(_, span) => span,
328            PlutusWitnessField::Script(_, span) => span,
329        }
330    }
331}
332
333impl Analyzable for PlutusWitnessField {
334    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
335        match self {
336            PlutusWitnessField::Version(x, _) => x.analyze(parent),
337            PlutusWitnessField::Script(x, _) => x.analyze(parent),
338        }
339    }
340
341    fn is_resolved(&self) -> bool {
342        match self {
343            PlutusWitnessField::Version(x, _) => x.is_resolved(),
344            PlutusWitnessField::Script(x, _) => x.is_resolved(),
345        }
346    }
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
350pub struct PlutusWitnessBlock {
351    pub fields: Vec<PlutusWitnessField>,
352    pub span: Span,
353}
354
355impl AstNode for PlutusWitnessBlock {
356    const RULE: Rule = Rule::cardano_plutus_witness_block;
357
358    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
359        let span = pair.as_span().into();
360        let inner = pair.into_inner();
361
362        let fields = inner
363            .map(|x| PlutusWitnessField::parse(x))
364            .collect::<Result<Vec<_>, _>>()?;
365
366        Ok(PlutusWitnessBlock { fields, span })
367    }
368
369    fn span(&self) -> &Span {
370        &self.span
371    }
372}
373
374impl Analyzable for PlutusWitnessBlock {
375    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
376        self.fields.analyze(parent)
377    }
378
379    fn is_resolved(&self) -> bool {
380        self.fields.is_resolved()
381    }
382}
383
384impl IntoLower for PlutusWitnessBlock {
385    type Output = ir::AdHocDirective;
386
387    fn lower(
388        &self,
389        ctx: &crate::lowering::Context,
390    ) -> Result<Self::Output, crate::lowering::Error> {
391        let data = self
392            .fields
393            .iter()
394            .map(|x| x.lower(ctx))
395            .collect::<Result<_, _>>()?;
396
397        Ok(ir::AdHocDirective {
398            name: "plutus_witness".to_string(),
399            data,
400        })
401    }
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
405pub enum NativeWitnessField {
406    Script(DataExpr, Span),
407}
408
409impl IntoLower for NativeWitnessField {
410    type Output = (String, ir::Expression);
411
412    fn lower(
413        &self,
414        ctx: &crate::lowering::Context,
415    ) -> Result<Self::Output, crate::lowering::Error> {
416        match self {
417            NativeWitnessField::Script(x, _) => Ok(("script".to_string(), x.lower(ctx)?)),
418        }
419    }
420}
421
422impl AstNode for NativeWitnessField {
423    const RULE: Rule = Rule::cardano_native_witness_field;
424
425    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
426        let span = pair.as_span().into();
427
428        match pair.as_rule() {
429            Rule::cardano_native_witness_script => {
430                Ok(NativeWitnessField::Script(DataExpr::parse(pair)?, span))
431            }
432            x => unreachable!("Unexpected rule in cardano_native_witness_field: {:?}", x),
433        }
434    }
435
436    fn span(&self) -> &Span {
437        match self {
438            NativeWitnessField::Script(_, span) => span,
439        }
440    }
441}
442
443impl Analyzable for NativeWitnessField {
444    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
445        match self {
446            NativeWitnessField::Script(x, _) => x.analyze(parent),
447        }
448    }
449
450    fn is_resolved(&self) -> bool {
451        match self {
452            NativeWitnessField::Script(x, _) => x.is_resolved(),
453        }
454    }
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
458pub struct NativeWitnessBlock {
459    pub fields: Vec<NativeWitnessField>,
460    pub span: Span,
461}
462
463impl AstNode for NativeWitnessBlock {
464    const RULE: Rule = Rule::cardano_native_witness_block;
465
466    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
467        let span = pair.as_span().into();
468        let inner = pair.into_inner();
469
470        let fields = inner
471            .map(|x| NativeWitnessField::parse(x))
472            .collect::<Result<Vec<_>, _>>()?;
473
474        Ok(NativeWitnessBlock { fields, span })
475    }
476
477    fn span(&self) -> &Span {
478        &self.span
479    }
480}
481
482impl Analyzable for NativeWitnessBlock {
483    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
484        self.fields.analyze(parent)
485    }
486
487    fn is_resolved(&self) -> bool {
488        self.fields.is_resolved()
489    }
490}
491
492impl IntoLower for NativeWitnessBlock {
493    type Output = ir::AdHocDirective;
494
495    fn lower(
496        &self,
497        ctx: &crate::lowering::Context,
498    ) -> Result<Self::Output, crate::lowering::Error> {
499        let data = self
500            .fields
501            .iter()
502            .map(|x| x.lower(ctx))
503            .collect::<Result<_, _>>()?;
504
505        Ok(ir::AdHocDirective {
506            name: "native_witness".to_string(),
507            data,
508        })
509    }
510}
511
512#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
513pub struct TreasuryDonationBlock {
514    pub coin: DataExpr,
515    pub span: Span,
516}
517
518impl AstNode for TreasuryDonationBlock {
519    const RULE: Rule = Rule::cardano_treasury_donation_block;
520
521    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
522        let span = pair.as_span().into();
523
524        let mut inner = pair.into_inner();
525        let coin = DataExpr::parse(inner.next().unwrap())?;
526
527        Ok(TreasuryDonationBlock { coin, span })
528    }
529
530    fn span(&self) -> &Span {
531        &self.span
532    }
533}
534
535impl Analyzable for TreasuryDonationBlock {
536    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
537        let coin = self.coin.analyze(parent);
538        let coin_type = AnalyzeReport::expect_data_expr_type(&self.coin, &Type::Int);
539
540        coin + coin_type
541    }
542
543    fn is_resolved(&self) -> bool {
544        self.coin.is_resolved()
545    }
546}
547
548impl IntoLower for TreasuryDonationBlock {
549    type Output = ir::AdHocDirective;
550
551    fn lower(
552        &self,
553        ctx: &crate::lowering::Context,
554    ) -> Result<Self::Output, crate::lowering::Error> {
555        let coin = self.coin.lower(ctx)?;
556
557        Ok(ir::AdHocDirective {
558            name: "treasury_donation".to_string(),
559            data: std::collections::HashMap::from([("coin".to_string(), coin)]),
560        })
561    }
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
565pub enum CardanoPublishBlockField {
566    To(Box<DataExpr>),
567    Amount(Box<DataExpr>),
568    Datum(Box<DataExpr>),
569    Version(Box<DataExpr>),
570    Script(Box<DataExpr>),
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
574pub struct CardanoPublishBlock {
575    pub name: Option<Identifier>,
576    pub fields: Vec<CardanoPublishBlockField>,
577    pub span: Span,
578}
579
580impl AstNode for CardanoPublishBlockField {
581    const RULE: Rule = Rule::cardano_publish_block_field;
582
583    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
584        match pair.as_rule() {
585            Rule::cardano_publish_block_to => {
586                let pair = pair.into_inner().next().unwrap();
587                Ok(CardanoPublishBlockField::To(DataExpr::parse(pair)?.into()))
588            }
589            Rule::cardano_publish_block_amount => {
590                let pair = pair.into_inner().next().unwrap();
591                Ok(CardanoPublishBlockField::Amount(
592                    DataExpr::parse(pair)?.into(),
593                ))
594            }
595            Rule::cardano_publish_block_datum => {
596                let pair = pair.into_inner().next().unwrap();
597                Ok(CardanoPublishBlockField::Datum(
598                    DataExpr::parse(pair)?.into(),
599                ))
600            }
601            Rule::cardano_publish_block_version => {
602                let pair = pair.into_inner().next().unwrap();
603                Ok(CardanoPublishBlockField::Version(
604                    DataExpr::parse(pair)?.into(),
605                ))
606            }
607            Rule::cardano_publish_block_script => {
608                let pair = pair.into_inner().next().unwrap();
609                Ok(CardanoPublishBlockField::Script(
610                    DataExpr::parse(pair)?.into(),
611                ))
612            }
613            x => unreachable!("Unexpected rule in cardano_publish_block_field: {:?}", x),
614        }
615    }
616
617    fn span(&self) -> &Span {
618        match self {
619            Self::To(x) => x.span(),
620            Self::Amount(x) => x.span(),
621            Self::Datum(x) => x.span(),
622            Self::Version(x) => x.span(),
623            Self::Script(x) => x.span(),
624        }
625    }
626}
627
628impl AstNode for CardanoPublishBlock {
629    const RULE: Rule = Rule::cardano_publish_block;
630
631    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
632        let span = pair.as_span().into();
633        let mut inner = pair.into_inner();
634        let has_name = inner
635            .peek()
636            .map(|x| x.as_rule() == Rule::identifier)
637            .unwrap_or_default();
638
639        let name = if has_name {
640            Some(Identifier::parse(inner.next().unwrap())?)
641        } else {
642            None
643        };
644
645        let fields = inner
646            .map(|x| CardanoPublishBlockField::parse(x))
647            .collect::<Result<Vec<_>, _>>()?;
648
649        Ok(CardanoPublishBlock { name, fields, span })
650    }
651
652    fn span(&self) -> &Span {
653        &self.span
654    }
655}
656
657impl Analyzable for CardanoPublishBlockField {
658    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
659        match self {
660            CardanoPublishBlockField::To(x) => x.analyze(parent),
661            CardanoPublishBlockField::Amount(x) => x.analyze(parent),
662            CardanoPublishBlockField::Datum(x) => x.analyze(parent),
663            CardanoPublishBlockField::Version(x) => x.analyze(parent),
664            CardanoPublishBlockField::Script(x) => x.analyze(parent),
665        }
666    }
667
668    fn is_resolved(&self) -> bool {
669        match self {
670            CardanoPublishBlockField::To(x) => x.is_resolved(),
671            CardanoPublishBlockField::Amount(x) => x.is_resolved(),
672            CardanoPublishBlockField::Datum(x) => x.is_resolved(),
673            CardanoPublishBlockField::Version(x) => x.is_resolved(),
674            CardanoPublishBlockField::Script(x) => x.is_resolved(),
675        }
676    }
677}
678
679impl Analyzable for CardanoPublishBlock {
680    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
681        self.fields.analyze(parent)
682    }
683
684    fn is_resolved(&self) -> bool {
685        self.fields.is_resolved()
686    }
687}
688
689impl IntoLower for CardanoPublishBlockField {
690    type Output = (String, ir::Expression);
691
692    fn lower(
693        &self,
694        ctx: &crate::lowering::Context,
695    ) -> Result<Self::Output, crate::lowering::Error> {
696        match self {
697            CardanoPublishBlockField::To(x) => {
698                let ctx = ctx.enter_address_expr();
699                Ok(("to".to_string(), x.lower(&ctx)?))
700            }
701            CardanoPublishBlockField::Amount(x) => {
702                let ctx = ctx.enter_asset_expr();
703                Ok(("amount".to_string(), x.lower(&ctx)?))
704            }
705            CardanoPublishBlockField::Datum(x) => {
706                let ctx = ctx.enter_datum_expr();
707                Ok(("datum".to_string(), x.lower(&ctx)?))
708            }
709            CardanoPublishBlockField::Version(x) => Ok(("version".to_string(), x.lower(ctx)?)),
710            CardanoPublishBlockField::Script(x) => Ok(("script".to_string(), x.lower(ctx)?)),
711        }
712    }
713}
714
715impl IntoLower for CardanoPublishBlock {
716    type Output = ir::AdHocDirective;
717
718    fn lower(
719        &self,
720        ctx: &crate::lowering::Context,
721    ) -> Result<Self::Output, crate::lowering::Error> {
722        let data = self
723            .fields
724            .iter()
725            .map(|x| x.lower(ctx))
726            .collect::<Result<_, _>>()?;
727
728        Ok(ir::AdHocDirective {
729            name: "cardano_publish".to_string(),
730            data,
731        })
732    }
733}
734
735#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
736pub enum CardanoBlock {
737    VoteDelegationCertificate(VoteDelegationCertificate),
738    StakeDelegationCertificate(StakeDelegationCertificate),
739    Withdrawal(WithdrawalBlock),
740    PlutusWitness(PlutusWitnessBlock),
741    NativeWitness(NativeWitnessBlock),
742    TreasuryDonation(TreasuryDonationBlock),
743    Publish(CardanoPublishBlock),
744}
745
746impl AstNode for CardanoBlock {
747    const RULE: Rule = Rule::cardano_block;
748
749    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
750        let mut inner = pair.into_inner();
751        let item = inner.next().unwrap();
752
753        match item.as_rule() {
754            Rule::cardano_vote_delegation_certificate => Ok(
755                CardanoBlock::VoteDelegationCertificate(VoteDelegationCertificate::parse(item)?),
756            ),
757            Rule::cardano_stake_delegation_certificate => Ok(
758                CardanoBlock::StakeDelegationCertificate(StakeDelegationCertificate::parse(item)?),
759            ),
760            Rule::cardano_withdrawal_block => {
761                Ok(CardanoBlock::Withdrawal(WithdrawalBlock::parse(item)?))
762            }
763            Rule::cardano_plutus_witness_block => Ok(CardanoBlock::PlutusWitness(
764                PlutusWitnessBlock::parse(item)?,
765            )),
766            Rule::cardano_native_witness_block => Ok(CardanoBlock::NativeWitness(
767                NativeWitnessBlock::parse(item)?,
768            )),
769            Rule::cardano_treasury_donation_block => Ok(CardanoBlock::TreasuryDonation(
770                TreasuryDonationBlock::parse(item)?,
771            )),
772            Rule::cardano_publish_block => {
773                Ok(CardanoBlock::Publish(CardanoPublishBlock::parse(item)?))
774            }
775            x => unreachable!("Unexpected rule in cardano_block: {:?}", x),
776        }
777    }
778
779    fn span(&self) -> &Span {
780        match self {
781            CardanoBlock::VoteDelegationCertificate(x) => x.span(),
782            CardanoBlock::StakeDelegationCertificate(x) => x.span(),
783            CardanoBlock::Withdrawal(x) => x.span(),
784            CardanoBlock::PlutusWitness(x) => x.span(),
785            CardanoBlock::NativeWitness(x) => x.span(),
786            CardanoBlock::TreasuryDonation(x) => x.span(),
787            CardanoBlock::Publish(x) => x.span(),
788        }
789    }
790}
791
792impl Analyzable for CardanoBlock {
793    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
794        match self {
795            CardanoBlock::VoteDelegationCertificate(x) => x.analyze(parent),
796            CardanoBlock::StakeDelegationCertificate(x) => x.analyze(parent),
797            CardanoBlock::Withdrawal(x) => x.analyze(parent),
798            CardanoBlock::PlutusWitness(x) => x.analyze(parent),
799            CardanoBlock::NativeWitness(x) => x.analyze(parent),
800            CardanoBlock::TreasuryDonation(x) => x.analyze(parent),
801            CardanoBlock::Publish(x) => x.analyze(parent),
802        }
803    }
804
805    fn is_resolved(&self) -> bool {
806        match self {
807            CardanoBlock::VoteDelegationCertificate(x) => x.is_resolved(),
808            CardanoBlock::StakeDelegationCertificate(x) => x.is_resolved(),
809            CardanoBlock::Withdrawal(x) => x.is_resolved(),
810            CardanoBlock::PlutusWitness(x) => x.is_resolved(),
811            CardanoBlock::NativeWitness(x) => x.is_resolved(),
812            Self::TreasuryDonation(x) => x.is_resolved(),
813            CardanoBlock::Publish(x) => x.is_resolved(),
814        }
815    }
816}
817
818impl IntoLower for CardanoBlock {
819    type Output = ir::AdHocDirective;
820
821    fn lower(
822        &self,
823        ctx: &crate::lowering::Context,
824    ) -> Result<<CardanoBlock as IntoLower>::Output, crate::lowering::Error> {
825        match self {
826            CardanoBlock::VoteDelegationCertificate(x) => x.lower(ctx),
827            CardanoBlock::StakeDelegationCertificate(x) => x.lower(ctx),
828            CardanoBlock::Withdrawal(x) => x.lower(ctx),
829            CardanoBlock::PlutusWitness(x) => x.lower(ctx),
830            CardanoBlock::NativeWitness(x) => x.lower(ctx),
831            CardanoBlock::TreasuryDonation(x) => x.lower(ctx),
832            CardanoBlock::Publish(x) => x.lower(ctx),
833        }
834    }
835}
836
837#[cfg(test)]
838mod tests {
839    use super::*;
840    use crate::{analyzing::analyze, ast::*};
841    use pest::Parser;
842
843    macro_rules! input_to_ast_check {
844        ($ast:ty, $name:expr, $input:expr, $expected:expr) => {
845            paste::paste! {
846                #[test]
847                fn [<test_parse_ $ast:snake _ $name>]() {
848                    let pairs = crate::parsing::Tx3Grammar::parse(<$ast>::RULE, $input).unwrap();
849                    let single_match = pairs.into_iter().next().unwrap();
850                    let result = <$ast>::parse(single_match).unwrap();
851
852                    assert_eq!(result, $expected);
853                }
854            }
855        };
856    }
857
858    input_to_ast_check!(
859        PlutusWitnessBlock,
860        "basic",
861        "plutus_witness {
862            version: 3,
863            script: 0xABCDEF,
864        }",
865        PlutusWitnessBlock {
866            fields: vec![
867                PlutusWitnessField::Version(DataExpr::Number(3), Span::DUMMY),
868                PlutusWitnessField::Script(
869                    DataExpr::HexString(HexStringLiteral::new("ABCDEF".to_string())),
870                    Span::DUMMY
871                )
872            ],
873            span: Span::DUMMY,
874        }
875    );
876
877    input_to_ast_check!(
878        NativeWitnessBlock,
879        "basic",
880        "native_witness {
881            script: 0xABCDEF,
882        }",
883        NativeWitnessBlock {
884            fields: vec![NativeWitnessField::Script(
885                DataExpr::HexString(HexStringLiteral::new("ABCDEF".to_string())),
886                Span::DUMMY
887            )],
888            span: Span::DUMMY,
889        }
890    );
891
892    input_to_ast_check!(
893        TreasuryDonationBlock,
894        "basic",
895        "treasury_donation {
896            coin: 2020,
897        }",
898        TreasuryDonationBlock {
899            coin: DataExpr::Number(2020),
900            span: Span::DUMMY,
901        }
902    );
903
904    input_to_ast_check!(
905        CardanoPublishBlock,
906        "basic",
907        "publish {
908            to: Receiver,
909            amount: Ada(quantity),
910            version: 3,
911            script: 0xABCDEF,
912        }",
913        CardanoPublishBlock {
914            name: None,
915            fields: vec![
916                CardanoPublishBlockField::To(Box::new(DataExpr::Identifier(Identifier::new(
917                    "Receiver"
918                )))),
919                CardanoPublishBlockField::Amount(Box::new(DataExpr::FnCall(crate::ast::FnCall {
920                    callee: Identifier::new("Ada"),
921                    args: vec![DataExpr::Identifier(Identifier::new("quantity"))],
922                    span: Span::DUMMY,
923                }))),
924                CardanoPublishBlockField::Version(Box::new(DataExpr::Number(3))),
925                CardanoPublishBlockField::Script(Box::new(DataExpr::HexString(
926                    HexStringLiteral::new("ABCDEF".to_string())
927                ))),
928            ],
929            span: Span::DUMMY,
930        }
931    );
932
933    input_to_ast_check!(
934        CardanoPublishBlock,
935        "basic_with_name",
936        "publish test_publish {
937            to: Receiver,
938            amount: Ada(quantity),
939            version: 3,
940            script: 0xABCDEF,
941        }",
942        CardanoPublishBlock {
943            name: Some(Identifier::new("test_publish")),
944            fields: vec![
945                CardanoPublishBlockField::To(Box::new(DataExpr::Identifier(Identifier::new(
946                    "Receiver"
947                )))),
948                CardanoPublishBlockField::Amount(Box::new(DataExpr::FnCall(crate::ast::FnCall {
949                    callee: Identifier::new("Ada"),
950                    args: vec![DataExpr::Identifier(Identifier::new("quantity"))],
951                    span: Span::DUMMY,
952                }))),
953                CardanoPublishBlockField::Version(Box::new(DataExpr::Number(3))),
954                CardanoPublishBlockField::Script(Box::new(DataExpr::HexString(
955                    HexStringLiteral::new("ABCDEF".to_string())
956                ))),
957            ],
958            span: Span::DUMMY,
959        }
960    );
961
962    #[test]
963    fn test_treasury_donation_type() {
964        let mut ast = crate::parsing::parse_string(
965            r#"
966            tx test(quantity: Int) {
967                cardano::treasury_donation {
968                    coin: quantity,
969                }
970            }
971            "#,
972        )
973        .unwrap();
974
975        let result = analyze(&mut ast);
976        assert!(result.errors.is_empty());
977    }
978
979    #[test]
980    fn test_treasury_donation_type_not_ok() {
981        let mut ast = crate::parsing::parse_string(
982            r#"
983            tx test(quantity: Bytes) {
984                cardano::treasury_donation {
985                    coin: quantity,
986                }
987            }
988            "#,
989        )
990        .unwrap();
991
992        let result = analyze(&mut ast);
993        assert!(!result.errors.is_empty());
994    }
995
996    #[test]
997    fn test_publish_type_ok() {
998        let mut ast = crate::parsing::parse_string(
999            r#"
1000            party Receiver;
1001
1002            tx test(quantity: Int) {
1003                cardano::publish {
1004                    to: Receiver,
1005                    amount: Ada(quantity),
1006                    version: 3,
1007                    script: 0xABCDEF,
1008                }
1009            }
1010            "#,
1011        )
1012        .unwrap();
1013
1014        let result = analyze(&mut ast);
1015        assert!(result.errors.is_empty());
1016    }
1017
1018    #[test]
1019    fn test_publish_type_with_name_ok() {
1020        let mut ast = crate::parsing::parse_string(
1021            r#"
1022            party Receiver;
1023
1024            tx test(quantity: Int) {
1025                cardano::publish deploy {
1026                    to: Receiver,
1027                    amount: Ada(quantity),
1028                    version: 3,
1029                    script: 0xABCDEF,
1030                }
1031            }
1032            "#,
1033        )
1034        .unwrap();
1035
1036        let result = analyze(&mut ast);
1037        assert!(result.errors.is_empty());
1038    }
1039}