tx3_lang/
cardano.rs

1use std::{collections::HashMap, rc::Rc};
2
3use pest::iterators::Pair;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    analyzing::{Analyzable, AnalyzeReport},
8    ast::{DataExpr, Scope, Span, Type},
9    ir,
10    lowering::IntoLower,
11    parsing::{AstNode, Error, Rule},
12};
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15pub enum WithdrawalField {
16    From(Box<DataExpr>),
17    Amount(Box<DataExpr>),
18    Redeemer(Box<DataExpr>),
19}
20
21impl WithdrawalField {
22    fn key(&self) -> &str {
23        match self {
24            WithdrawalField::From(_) => "from",
25            WithdrawalField::Amount(_) => "amount",
26            WithdrawalField::Redeemer(_) => "redeemer",
27        }
28    }
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
32pub struct WithdrawalBlock {
33    pub fields: Vec<WithdrawalField>,
34    pub span: Span,
35}
36
37impl WithdrawalBlock {
38    pub(crate) fn find(&self, key: &str) -> Option<&WithdrawalField> {
39        self.fields.iter().find(|x| x.key() == key)
40    }
41}
42
43impl AstNode for WithdrawalField {
44    const RULE: Rule = Rule::cardano_withdrawal_field;
45
46    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
47        match pair.as_rule() {
48            Rule::cardano_withdrawal_from => {
49                let pair = pair.into_inner().next().unwrap();
50                Ok(WithdrawalField::From(DataExpr::parse(pair)?.into()))
51            }
52            Rule::cardano_withdrawal_amount => {
53                let pair = pair.into_inner().next().unwrap();
54                Ok(WithdrawalField::Amount(DataExpr::parse(pair)?.into()))
55            }
56            Rule::cardano_withdrawal_redeemer => {
57                let pair = pair.into_inner().next().unwrap();
58                Ok(WithdrawalField::Redeemer(DataExpr::parse(pair)?.into()))
59            }
60            x => unreachable!("Unexpected rule in cardano_withdrawal_field: {:?}", x),
61        }
62    }
63
64    fn span(&self) -> &Span {
65        match self {
66            Self::From(x) => x.span(),
67            Self::Amount(x) => x.span(),
68            Self::Redeemer(x) => x.span(),
69        }
70    }
71}
72
73impl AstNode for WithdrawalBlock {
74    const RULE: Rule = Rule::cardano_withdrawal_block;
75
76    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
77        let span = pair.as_span().into();
78        let inner = pair.into_inner();
79
80        let fields = inner
81            .map(|x| WithdrawalField::parse(x))
82            .collect::<Result<Vec<_>, _>>()?;
83
84        Ok(WithdrawalBlock { fields, span })
85    }
86
87    fn span(&self) -> &Span {
88        &self.span
89    }
90}
91
92impl Analyzable for WithdrawalField {
93    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
94        match self {
95            WithdrawalField::From(x) => x.analyze(parent),
96            WithdrawalField::Amount(x) => {
97                let amount = x.analyze(parent.clone());
98                let amount_type = AnalyzeReport::expect_data_expr_type(x, &Type::Int);
99                amount + amount_type
100            }
101            WithdrawalField::Redeemer(x) => x.analyze(parent),
102        }
103    }
104
105    fn is_resolved(&self) -> bool {
106        match self {
107            WithdrawalField::From(x) => x.is_resolved(),
108            WithdrawalField::Amount(x) => x.is_resolved(),
109            WithdrawalField::Redeemer(x) => x.is_resolved(),
110        }
111    }
112}
113
114impl Analyzable for WithdrawalBlock {
115    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
116        self.fields.analyze(parent)
117    }
118
119    fn is_resolved(&self) -> bool {
120        self.fields.is_resolved()
121    }
122}
123
124impl IntoLower for WithdrawalField {
125    type Output = ir::Expression;
126
127    fn into_lower(
128        &self,
129        ctx: &crate::lowering::Context,
130    ) -> Result<Self::Output, crate::lowering::Error> {
131        match self {
132            WithdrawalField::From(x) => x.into_lower(ctx),
133            WithdrawalField::Amount(x) => x.into_lower(ctx),
134            WithdrawalField::Redeemer(x) => x.into_lower(ctx),
135        }
136    }
137}
138
139impl IntoLower for WithdrawalBlock {
140    type Output = ir::AdHocDirective;
141
142    fn into_lower(
143        &self,
144        ctx: &crate::lowering::Context,
145    ) -> Result<Self::Output, crate::lowering::Error> {
146        let credential = self
147            .find("from")
148            .ok_or_else(|| {
149                crate::lowering::Error::MissingRequiredField("from".to_string(), "WithdrawalBlock")
150            })?
151            .into_lower(ctx)?;
152
153        let amount = self
154            .find("amount")
155            .ok_or_else(|| {
156                crate::lowering::Error::MissingRequiredField(
157                    "amount".to_string(),
158                    "WithdrawalBlock",
159                )
160            })?
161            .into_lower(ctx)?;
162
163        let redeemer = self
164            .find("redeemer")
165            .map(|r| r.into_lower(ctx))
166            .transpose()?
167            .unwrap_or(ir::Expression::None);
168
169        Ok(ir::AdHocDirective {
170            name: "withdrawal".to_string(),
171            data: std::collections::HashMap::from([
172                ("credential".to_string(), credential),
173                ("amount".to_string(), amount),
174                ("redeemer".to_string(), redeemer),
175            ]),
176        })
177    }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
181pub struct VoteDelegationCertificate {
182    pub drep: DataExpr,
183    pub stake: DataExpr,
184    pub span: Span,
185}
186
187impl AstNode for VoteDelegationCertificate {
188    const RULE: Rule = Rule::cardano_vote_delegation_certificate;
189
190    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
191        let span = pair.as_span().into();
192        let mut inner = pair.into_inner();
193
194        Ok(VoteDelegationCertificate {
195            drep: DataExpr::parse(inner.next().unwrap())?,
196            stake: DataExpr::parse(inner.next().unwrap())?,
197            span,
198        })
199    }
200
201    fn span(&self) -> &Span {
202        &self.span
203    }
204}
205
206impl Analyzable for VoteDelegationCertificate {
207    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
208        let drep = self.drep.analyze(parent.clone());
209        let stake = self.stake.analyze(parent.clone());
210
211        drep + stake
212    }
213
214    fn is_resolved(&self) -> bool {
215        self.drep.is_resolved() && self.stake.is_resolved()
216    }
217}
218
219impl IntoLower for VoteDelegationCertificate {
220    type Output = ir::AdHocDirective;
221
222    fn into_lower(
223        &self,
224        ctx: &crate::lowering::Context,
225    ) -> Result<Self::Output, crate::lowering::Error> {
226        Ok(ir::AdHocDirective {
227            name: "vote_delegation_certificate".to_string(),
228            data: HashMap::from([
229                ("drep".to_string(), self.drep.into_lower(ctx)?),
230                ("stake".to_string(), self.stake.into_lower(ctx)?),
231            ]),
232        })
233    }
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
237pub struct StakeDelegationCertificate {
238    pub pool: DataExpr,
239    pub stake: DataExpr,
240    pub span: Span,
241}
242
243impl AstNode for StakeDelegationCertificate {
244    const RULE: Rule = Rule::cardano_stake_delegation_certificate;
245
246    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
247        let span = pair.as_span().into();
248        let mut inner = pair.into_inner();
249
250        Ok(StakeDelegationCertificate {
251            pool: DataExpr::parse(inner.next().unwrap())?,
252            stake: DataExpr::parse(inner.next().unwrap())?,
253            span,
254        })
255    }
256
257    fn span(&self) -> &Span {
258        &self.span
259    }
260}
261
262impl Analyzable for StakeDelegationCertificate {
263    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
264        let pool = self.pool.analyze(parent.clone());
265        let stake = self.stake.analyze(parent.clone());
266
267        pool + stake
268    }
269
270    fn is_resolved(&self) -> bool {
271        self.pool.is_resolved() && self.stake.is_resolved()
272    }
273}
274
275impl IntoLower for StakeDelegationCertificate {
276    type Output = ir::AdHocDirective;
277
278    fn into_lower(
279        &self,
280        _ctx: &crate::lowering::Context,
281    ) -> Result<Self::Output, crate::lowering::Error> {
282        todo!("StakeDelegationCertificate lowering not implemented")
283    }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
287pub enum PlutusWitnessField {
288    Version(DataExpr, Span),
289    Script(DataExpr, Span),
290}
291
292impl IntoLower for PlutusWitnessField {
293    type Output = (String, ir::Expression);
294
295    fn into_lower(
296        &self,
297        ctx: &crate::lowering::Context,
298    ) -> Result<Self::Output, crate::lowering::Error> {
299        match self {
300            PlutusWitnessField::Version(x, _) => Ok(("version".to_string(), x.into_lower(ctx)?)),
301            PlutusWitnessField::Script(x, _) => Ok(("script".to_string(), x.into_lower(ctx)?)),
302        }
303    }
304}
305
306impl AstNode for PlutusWitnessField {
307    const RULE: Rule = Rule::cardano_plutus_witness_field;
308
309    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
310        let span = pair.as_span().into();
311
312        match pair.as_rule() {
313            Rule::cardano_plutus_witness_version => {
314                Ok(PlutusWitnessField::Version(DataExpr::parse(pair)?, span))
315            }
316            Rule::cardano_plutus_witness_script => {
317                Ok(PlutusWitnessField::Script(DataExpr::parse(pair)?, span))
318            }
319            x => unreachable!("Unexpected rule in cardano_plutus_witness_field: {:?}", x),
320        }
321    }
322
323    fn span(&self) -> &Span {
324        match self {
325            PlutusWitnessField::Version(_, span) => span,
326            PlutusWitnessField::Script(_, span) => span,
327        }
328    }
329}
330
331impl Analyzable for PlutusWitnessField {
332    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
333        match self {
334            PlutusWitnessField::Version(x, _) => x.analyze(parent),
335            PlutusWitnessField::Script(x, _) => x.analyze(parent),
336        }
337    }
338
339    fn is_resolved(&self) -> bool {
340        match self {
341            PlutusWitnessField::Version(x, _) => x.is_resolved(),
342            PlutusWitnessField::Script(x, _) => x.is_resolved(),
343        }
344    }
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
348pub struct PlutusWitnessBlock {
349    pub fields: Vec<PlutusWitnessField>,
350    pub span: Span,
351}
352
353impl AstNode for PlutusWitnessBlock {
354    const RULE: Rule = Rule::cardano_plutus_witness_block;
355
356    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
357        let span = pair.as_span().into();
358        let inner = pair.into_inner();
359
360        let fields = inner
361            .map(|x| PlutusWitnessField::parse(x))
362            .collect::<Result<Vec<_>, _>>()?;
363
364        Ok(PlutusWitnessBlock { fields, span })
365    }
366
367    fn span(&self) -> &Span {
368        &self.span
369    }
370}
371
372impl Analyzable for PlutusWitnessBlock {
373    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
374        self.fields.analyze(parent)
375    }
376
377    fn is_resolved(&self) -> bool {
378        self.fields.is_resolved()
379    }
380}
381
382impl IntoLower for PlutusWitnessBlock {
383    type Output = ir::AdHocDirective;
384
385    fn into_lower(
386        &self,
387        ctx: &crate::lowering::Context,
388    ) -> Result<Self::Output, crate::lowering::Error> {
389        let data = self
390            .fields
391            .iter()
392            .map(|x| x.into_lower(ctx))
393            .collect::<Result<_, _>>()?;
394
395        Ok(ir::AdHocDirective {
396            name: "plutus_witness".to_string(),
397            data,
398        })
399    }
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
403pub enum CardanoBlock {
404    VoteDelegationCertificate(VoteDelegationCertificate),
405    StakeDelegationCertificate(StakeDelegationCertificate),
406    Withdrawal(WithdrawalBlock),
407    PlutusWitness(PlutusWitnessBlock),
408}
409
410impl AstNode for CardanoBlock {
411    const RULE: Rule = Rule::cardano_block;
412
413    fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
414        let mut inner = pair.into_inner();
415        let item = inner.next().unwrap();
416
417        match item.as_rule() {
418            Rule::cardano_vote_delegation_certificate => Ok(
419                CardanoBlock::VoteDelegationCertificate(VoteDelegationCertificate::parse(item)?),
420            ),
421            Rule::cardano_stake_delegation_certificate => Ok(
422                CardanoBlock::StakeDelegationCertificate(StakeDelegationCertificate::parse(item)?),
423            ),
424            Rule::cardano_withdrawal_block => {
425                Ok(CardanoBlock::Withdrawal(WithdrawalBlock::parse(item)?))
426            }
427            Rule::cardano_plutus_witness_block => Ok(CardanoBlock::PlutusWitness(
428                PlutusWitnessBlock::parse(item)?,
429            )),
430            x => unreachable!("Unexpected rule in cardano_block: {:?}", x),
431        }
432    }
433
434    fn span(&self) -> &Span {
435        match self {
436            CardanoBlock::VoteDelegationCertificate(x) => x.span(),
437            CardanoBlock::StakeDelegationCertificate(x) => x.span(),
438            CardanoBlock::Withdrawal(x) => x.span(),
439            CardanoBlock::PlutusWitness(x) => x.span(),
440        }
441    }
442}
443
444impl Analyzable for CardanoBlock {
445    fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
446        match self {
447            CardanoBlock::VoteDelegationCertificate(x) => x.analyze(parent),
448            CardanoBlock::StakeDelegationCertificate(x) => x.analyze(parent),
449            CardanoBlock::Withdrawal(x) => x.analyze(parent),
450            CardanoBlock::PlutusWitness(x) => x.analyze(parent),
451        }
452    }
453
454    fn is_resolved(&self) -> bool {
455        match self {
456            CardanoBlock::VoteDelegationCertificate(x) => x.is_resolved(),
457            CardanoBlock::StakeDelegationCertificate(x) => x.is_resolved(),
458            CardanoBlock::Withdrawal(x) => x.is_resolved(),
459            CardanoBlock::PlutusWitness(x) => x.is_resolved(),
460        }
461    }
462}
463
464impl IntoLower for CardanoBlock {
465    type Output = ir::AdHocDirective;
466
467    fn into_lower(
468        &self,
469        ctx: &crate::lowering::Context,
470    ) -> Result<Self::Output, crate::lowering::Error> {
471        match self {
472            CardanoBlock::VoteDelegationCertificate(x) => x.into_lower(ctx),
473            CardanoBlock::StakeDelegationCertificate(x) => x.into_lower(ctx),
474            CardanoBlock::Withdrawal(x) => x.into_lower(ctx),
475            CardanoBlock::PlutusWitness(x) => x.into_lower(ctx),
476        }
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483    use crate::ast::*;
484    use pest::Parser;
485
486    macro_rules! input_to_ast_check {
487        ($ast:ty, $name:expr, $input:expr, $expected:expr) => {
488            paste::paste! {
489                #[test]
490                fn [<test_parse_ $ast:snake _ $name>]() {
491                    let pairs = crate::parsing::Tx3Grammar::parse(<$ast>::RULE, $input).unwrap();
492                    let single_match = pairs.into_iter().next().unwrap();
493                    let result = <$ast>::parse(single_match).unwrap();
494
495                    assert_eq!(result, $expected);
496                }
497            }
498        };
499    }
500
501    input_to_ast_check!(
502        PlutusWitnessBlock,
503        "basic",
504        "plutus_witness {
505            version: 3,
506            script: 0xABCDEF,
507        }",
508        PlutusWitnessBlock {
509            fields: vec![
510                PlutusWitnessField::Version(DataExpr::Number(3), Span::DUMMY),
511                PlutusWitnessField::Script(
512                    DataExpr::HexString(HexStringLiteral::new("ABCDEF".to_string())),
513                    Span::DUMMY
514                )
515            ],
516            span: Span::DUMMY,
517        }
518    );
519}