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}