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