1use std::collections::HashSet;
7use std::ops::Deref;
8
9use crate::ast;
10use crate::ir;
11
12#[derive(Debug, thiserror::Error)]
13pub enum Error {
14 #[error("missing analyze phase")]
15 MissingAnalyzePhase,
16
17 #[error("symbol '{0}' expected to be '{1}'")]
18 InvalidSymbol(String, &'static str),
19
20 #[error("symbol '{0}' expected to be of type '{1}'")]
21 InvalidSymbolType(String, &'static str),
22
23 #[error("invalid ast: {0}")]
24 InvalidAst(String),
25
26 #[error("failed to decode hex string: {0}")]
27 DecodeHexError(#[from] hex::FromHexError),
28}
29
30fn expect_type_def(ident: &ast::Identifier) -> Result<&ast::TypeDef, Error> {
31 let symbol = ident.symbol.as_ref().ok_or(Error::MissingAnalyzePhase)?;
32
33 symbol
34 .as_type_def()
35 .ok_or(Error::InvalidSymbol(ident.value.clone(), "TypeDef"))
36}
37
38fn expect_case_def(ident: &ast::Identifier) -> Result<&ast::VariantCase, Error> {
39 let symbol = ident.symbol.as_ref().ok_or(Error::MissingAnalyzePhase)?;
40
41 symbol
42 .as_variant_case()
43 .ok_or(Error::InvalidSymbol(ident.value.clone(), "VariantCase"))
44}
45
46#[allow(dead_code)]
47fn expect_field_def(ident: &ast::Identifier) -> Result<&ast::RecordField, Error> {
48 let symbol = ident.symbol.as_ref().ok_or(Error::MissingAnalyzePhase)?;
49
50 symbol
51 .as_field_def()
52 .ok_or(Error::InvalidSymbol(ident.value.clone(), "FieldDef"))
53}
54
55fn coerce_identifier_into_asset_def(identifier: &ast::Identifier) -> Result<ast::AssetDef, Error> {
56 match identifier.try_symbol()? {
57 ast::Symbol::AssetDef(x) => Ok(x.as_ref().clone()),
58 _ => Err(Error::InvalidSymbol(identifier.value.clone(), "AssetDef")),
59 }
60}
61
62fn coerce_identifier_into_asset_expr(
63 identifier: &ast::Identifier,
64) -> Result<ir::Expression, Error> {
65 match identifier.try_symbol()? {
66 ast::Symbol::Input(x, _) => Ok(ir::Expression::EvalInputAssets(x.clone())),
67 ast::Symbol::Fees => Ok(ir::Expression::FeeQuery),
68 ast::Symbol::ParamVar(name, ty) => match ty.deref() {
69 ast::Type::AnyAsset => Ok(ir::Expression::EvalParameter(
70 name.to_lowercase().clone(),
71 ir::Type::AnyAsset,
72 )),
73 _ => Err(Error::InvalidSymbolType(
74 identifier.value.clone(),
75 "AnyAsset",
76 )),
77 },
78 _ => Err(Error::InvalidSymbol(identifier.value.clone(), "AssetExpr")),
79 }
80}
81
82fn lower_into_address_expr(identifier: &ast::Identifier) -> Result<ir::Expression, Error> {
83 match identifier.try_symbol()? {
84 ast::Symbol::PolicyDef(x) => Ok(x.into_lower()?.hash),
85 ast::Symbol::PartyDef(x) => Ok(ir::Expression::EvalParameter(
86 x.name.to_lowercase().clone(),
87 ir::Type::Address,
88 )),
89 _ => Err(Error::InvalidSymbol(
90 identifier.value.clone(),
91 "AddressExpr",
92 )),
93 }
94}
95
96pub(crate) trait IntoLower {
97 type Output;
98
99 fn into_lower(&self) -> Result<Self::Output, Error>;
100}
101
102impl<T> IntoLower for Option<&T>
103where
104 T: IntoLower,
105{
106 type Output = Option<T::Output>;
107
108 fn into_lower(&self) -> Result<Self::Output, Error> {
109 self.map(|x| x.into_lower()).transpose()
110 }
111}
112
113impl<T> IntoLower for Box<T>
114where
115 T: IntoLower,
116{
117 type Output = T::Output;
118
119 fn into_lower(&self) -> Result<Self::Output, Error> {
120 self.as_ref().into_lower()
121 }
122}
123
124impl IntoLower for ast::Identifier {
125 type Output = ir::Expression;
126
127 fn into_lower(&self) -> Result<Self::Output, Error> {
128 let symbol = self.symbol.as_ref().expect("analyze phase must be run");
129
130 match symbol {
131 ast::Symbol::ParamVar(n, ty) => Ok(ir::Expression::EvalParameter(
132 n.to_lowercase().clone(),
133 ty.into_lower()?,
134 )),
135 ast::Symbol::PartyDef(x) => Ok(ir::Expression::EvalParameter(
136 x.name.to_lowercase().clone(),
137 ir::Type::Address,
138 )),
139 ast::Symbol::Input(n, _) => Ok(ir::Expression::EvalInputDatum(n.clone())),
140 _ => {
141 dbg!(&self);
142 todo!();
143 }
144 }
145 }
146}
147
148impl IntoLower for ast::DatumConstructor {
149 type Output = ir::StructExpr;
150
151 fn into_lower(&self) -> Result<Self::Output, Error> {
152 let type_def = expect_type_def(&self.r#type)?;
153
154 let constructor = type_def
155 .find_case_index(&self.case.name.value)
156 .ok_or(Error::InvalidAst("case not found".to_string()))?;
157
158 let case_def = expect_case_def(&self.case.name)?;
159
160 let mut fields = vec![];
161
162 for field_def in case_def.fields.iter() {
163 let value = self.case.find_field_value(&field_def.name);
164
165 if let Some(value) = value {
166 fields.push(value.into_lower()?);
167 } else {
168 let spread_target = self
169 .case
170 .spread
171 .as_ref()
172 .expect("spread must be set for missing explicit field")
173 .into_lower()?;
174
175 fields.push(ir::Expression::EvalProperty(Box::new(ir::PropertyAccess {
176 object: Box::new(spread_target),
177 field: field_def.name.clone(),
178 })));
179 }
180 }
181
182 Ok(ir::StructExpr {
183 constructor,
184 fields,
185 })
186 }
187}
188
189impl IntoLower for ast::PolicyField {
190 type Output = ir::Expression;
191
192 fn into_lower(&self) -> Result<Self::Output, Error> {
193 match self {
194 ast::PolicyField::Hash(x) => x.into_lower(),
195 ast::PolicyField::Script(x) => x.into_lower(),
196 ast::PolicyField::Ref(x) => x.into_lower(),
197 }
198 }
199}
200
201impl IntoLower for ast::PolicyDef {
202 type Output = ir::PolicyExpr;
203
204 fn into_lower(&self) -> Result<Self::Output, Error> {
205 match &self.value {
206 ast::PolicyValue::Assign(x) => Ok(ir::PolicyExpr {
207 name: self.name.clone(),
208 hash: ir::Expression::Hash(hex::decode(&x.value)?),
209 script: None,
210 }),
211 ast::PolicyValue::Constructor(x) => {
212 let hash = x
213 .find_field("hash")
214 .ok_or(Error::InvalidAst("Missing policy hash".to_string()))?
215 .into_lower()?;
216
217 let ref_field = x.find_field("ref");
218 let script_field = x.find_field("script");
219
220 let script = match (ref_field, script_field) {
221 (Some(x), None) => Some(ir::ScriptSource::UtxoRef {
222 r#ref: x.into_lower()?,
223 source: None,
224 }),
225 (None, Some(x)) => Some(ir::ScriptSource::Embedded(x.into_lower()?)),
226 (Some(r#ref), Some(source)) => Some(ir::ScriptSource::UtxoRef {
227 r#ref: r#ref.into_lower()?,
228 source: Some(source.into_lower()?),
229 }),
230 (None, None) => None,
231 };
232
233 Ok(ir::PolicyExpr {
234 name: self.name.clone(),
235 hash,
236 script,
237 })
238 }
239 }
240 }
241}
242
243impl IntoLower for ast::Type {
244 type Output = ir::Type;
245
246 fn into_lower(&self) -> Result<Self::Output, Error> {
247 match self {
248 ast::Type::Undefined => Ok(ir::Type::Undefined),
249 ast::Type::Unit => Ok(ir::Type::Unit),
250 ast::Type::Int => Ok(ir::Type::Int),
251 ast::Type::Bool => Ok(ir::Type::Bool),
252 ast::Type::Bytes => Ok(ir::Type::Bytes),
253 ast::Type::Address => Ok(ir::Type::Address),
254 ast::Type::UtxoRef => Ok(ir::Type::UtxoRef),
255 ast::Type::AnyAsset => Ok(ir::Type::AnyAsset),
256 ast::Type::Custom(x) => Ok(ir::Type::Custom(x.value.clone())),
257 }
258 }
259}
260
261impl IntoLower for ast::DataBinaryOp {
262 type Output = ir::BinaryOp;
263
264 fn into_lower(&self) -> Result<Self::Output, Error> {
265 let left = self.left.into_lower()?;
266 let right = self.right.into_lower()?;
267
268 Ok(ir::BinaryOp {
269 left,
270 right,
271 op: match self.operator {
272 ast::BinaryOperator::Add => ir::BinaryOpKind::Add,
273 ast::BinaryOperator::Subtract => ir::BinaryOpKind::Sub,
274 },
275 })
276 }
277}
278
279impl IntoLower for ast::PropertyAccess {
280 type Output = ir::Expression;
281
282 fn into_lower(&self) -> Result<Self::Output, Error> {
283 let mut object = self.object.into_lower()?;
284
285 for field in self.path.iter() {
286 object = ir::Expression::EvalProperty(Box::new(ir::PropertyAccess {
287 object: Box::new(object),
288 field: field.value.clone(),
289 }));
290 }
291
292 Ok(object)
293 }
294}
295
296impl IntoLower for ast::DataExpr {
297 type Output = ir::Expression;
298
299 fn into_lower(&self) -> Result<Self::Output, Error> {
300 let out = match self {
301 ast::DataExpr::None => ir::Expression::None,
302 ast::DataExpr::Number(x) => Self::Output::Number(*x as i128),
303 ast::DataExpr::Bool(x) => ir::Expression::Bool(*x),
304 ast::DataExpr::String(x) => ir::Expression::Bytes(x.value.as_bytes().to_vec()),
305 ast::DataExpr::HexString(x) => ir::Expression::Bytes(hex::decode(&x.value)?),
306 ast::DataExpr::Constructor(x) => ir::Expression::Struct(x.into_lower()?),
307 ast::DataExpr::Unit => ir::Expression::Struct(ir::StructExpr::unit()),
308 ast::DataExpr::Identifier(x) => x.into_lower()?,
309 ast::DataExpr::BinaryOp(x) => ir::Expression::EvalCustom(Box::new(x.into_lower()?)),
310 ast::DataExpr::PropertyAccess(x) => x.into_lower()?,
311 };
312
313 Ok(out)
314 }
315}
316
317impl IntoLower for ast::StaticAssetConstructor {
318 type Output = ir::Expression;
319
320 fn into_lower(&self) -> Result<Self::Output, Error> {
321 let asset_def = coerce_identifier_into_asset_def(&self.r#type)?;
322
323 let policy = match asset_def.policy {
324 Some(x) => ir::Expression::Bytes(hex::decode(&x.value)?),
325 None => ir::Expression::None,
326 };
327
328 let asset_name = match asset_def.asset_name {
329 Some(x) => ir::Expression::Bytes(x.as_bytes().to_vec()),
330 None => ir::Expression::None,
331 };
332
333 let amount = self.amount.into_lower()?;
334
335 Ok(ir::Expression::Assets(vec![ir::AssetExpr {
336 policy,
337 asset_name,
338 amount,
339 }]))
340 }
341}
342
343impl IntoLower for ast::AnyAssetConstructor {
344 type Output = ir::Expression;
345
346 fn into_lower(&self) -> Result<Self::Output, Error> {
347 let policy = self.policy.into_lower()?;
348 let asset_name = self.asset_name.into_lower()?;
349 let amount = self.amount.into_lower()?;
350
351 Ok(ir::Expression::Assets(vec![ir::AssetExpr {
352 policy,
353 asset_name,
354 amount,
355 }]))
356 }
357}
358
359impl IntoLower for ast::AssetBinaryOp {
360 type Output = ir::BinaryOp;
361
362 fn into_lower(&self) -> Result<Self::Output, Error> {
363 let left = self.left.into_lower()?;
364 let right = self.right.into_lower()?;
365
366 Ok(ir::BinaryOp {
367 left,
368 right,
369 op: match self.operator {
370 ast::BinaryOperator::Add => ir::BinaryOpKind::Add,
371 ast::BinaryOperator::Subtract => ir::BinaryOpKind::Sub,
372 },
373 })
374 }
375}
376
377impl IntoLower for ast::AssetExpr {
378 type Output = ir::Expression;
379
380 fn into_lower(&self) -> Result<Self::Output, Error> {
381 match self {
382 ast::AssetExpr::StaticConstructor(x) => x.into_lower(),
383 ast::AssetExpr::AnyConstructor(x) => x.into_lower(),
384 ast::AssetExpr::BinaryOp(x) => {
385 Ok(ir::Expression::EvalCustom(Box::new(x.into_lower()?)))
386 }
387 ast::AssetExpr::Identifier(x) => coerce_identifier_into_asset_expr(x),
388 ast::AssetExpr::PropertyAccess(_x) => todo!(),
389 }
390 }
391}
392
393impl IntoLower for ast::AddressExpr {
394 type Output = ir::Expression;
395
396 fn into_lower(&self) -> Result<Self::Output, Error> {
397 match self {
398 ast::AddressExpr::String(x) => Ok(ir::Expression::String(x.value.clone())),
399 ast::AddressExpr::HexString(x) => Ok(ir::Expression::Bytes(hex::decode(&x.value)?)),
400 ast::AddressExpr::Identifier(x) => lower_into_address_expr(x),
401 }
402 }
403}
404
405impl IntoLower for ast::InputBlockField {
406 type Output = ir::Expression;
407
408 fn into_lower(&self) -> Result<Self::Output, Error> {
409 match self {
410 ast::InputBlockField::From(x) => x.into_lower(),
411 ast::InputBlockField::DatumIs(_) => todo!(),
412 ast::InputBlockField::MinAmount(x) => x.into_lower(),
413 ast::InputBlockField::Redeemer(x) => x.into_lower(),
414 ast::InputBlockField::Ref(x) => x.into_lower(),
415 }
416 }
417}
418
419impl IntoLower for ast::InputBlock {
420 type Output = ir::Input;
421
422 fn into_lower(&self) -> Result<Self::Output, Error> {
423 let from = self.find("from");
424 let min_amount = self.find("min_amount");
425 let r#ref = self.find("ref");
426 let redeemer = self.find("redeemer");
427
428 let policy = from
429 .and_then(ast::InputBlockField::as_address_expr)
430 .and_then(ast::AddressExpr::as_identifier)
431 .and_then(|x| x.symbol.as_ref())
432 .and_then(|x| x.as_policy_def())
433 .map(|x| x.into_lower())
434 .transpose()?;
435
436 let input = ir::Input {
437 name: self.name.to_lowercase().clone(),
438 query: ir::InputQuery {
439 address: from.into_lower()?,
440 min_amount: min_amount.into_lower()?,
441 r#ref: r#ref.into_lower()?,
442 }
443 .into(),
444 refs: HashSet::new(),
445 redeemer: redeemer.into_lower()?,
446 policy,
447 };
448
449 Ok(input)
450 }
451}
452
453impl IntoLower for ast::OutputBlockField {
454 type Output = ir::Expression;
455
456 fn into_lower(&self) -> Result<Self::Output, Error> {
457 match self {
458 ast::OutputBlockField::To(x) => x.into_lower(),
459 ast::OutputBlockField::Amount(x) => x.into_lower(),
460 ast::OutputBlockField::Datum(x) => x.into_lower(),
461 }
462 }
463}
464
465impl IntoLower for ast::OutputBlock {
466 type Output = ir::Output;
467
468 fn into_lower(&self) -> Result<Self::Output, Error> {
469 Ok(ir::Output {
470 address: self.find("to").into_lower()?,
471 datum: self.find("datum").into_lower()?,
472 amount: self.find("amount").into_lower()?,
473 })
474 }
475}
476
477impl IntoLower for ast::MintBlockField {
478 type Output = ir::Expression;
479
480 fn into_lower(&self) -> Result<Self::Output, Error> {
481 match self {
482 ast::MintBlockField::Amount(x) => x.into_lower(),
483 ast::MintBlockField::Redeemer(x) => x.into_lower(),
484 }
485 }
486}
487
488impl IntoLower for ast::MintBlock {
489 type Output = ir::Mint;
490
491 fn into_lower(&self) -> Result<Self::Output, Error> {
492 Ok(ir::Mint {
493 amount: self.find("amount").into_lower()?,
494 redeemer: self.find("redeemer").into_lower()?,
495 })
496 }
497}
498
499impl IntoLower for ast::ChainSpecificBlock {
500 type Output = ir::AdHocDirective;
501
502 fn into_lower(&self) -> Result<Self::Output, Error> {
503 match self {
504 ast::ChainSpecificBlock::Cardano(x) => x.into_lower(),
505 }
506 }
507}
508
509pub fn lower_tx(ast: &ast::TxDef) -> Result<ir::Tx, Error> {
510 let ir = ir::Tx {
511 inputs: ast
512 .inputs
513 .iter()
514 .map(|x| x.into_lower())
515 .collect::<Result<Vec<_>, _>>()?,
516 outputs: ast
517 .outputs
518 .iter()
519 .map(|x| x.into_lower())
520 .collect::<Result<Vec<_>, _>>()?,
521 mint: ast.mint.as_ref().map(|x| x.into_lower()).transpose()?,
522 adhoc: ast
523 .adhoc
524 .iter()
525 .map(|x| x.into_lower())
526 .collect::<Result<Vec<_>, _>>()?,
527 fees: ir::Expression::FeeQuery,
528 };
529
530 Ok(ir)
531}
532
533pub fn lower(ast: &ast::Program, template: &str) -> Result<ir::Tx, Error> {
546 let tx = ast
547 .txs
548 .iter()
549 .find(|x| x.name == template)
550 .ok_or(Error::InvalidAst("tx not found".to_string()))?;
551
552 lower_tx(tx)
553}
554
555#[cfg(test)]
556mod tests {
557 use assert_json_diff::assert_json_eq;
558 use paste::paste;
559
560 use super::*;
561 use crate::{
562 ast::Span,
563 parsing::{self, AstNode},
564 };
565
566 fn make_snapshot_if_missing(example: &str, name: &str, tx: &ir::Tx) {
567 let manifest_dir = env!("CARGO_MANIFEST_DIR");
568
569 let path = format!("{}/../../examples/{}.{}.tir", manifest_dir, example, name);
570
571 if !std::fs::exists(&path).unwrap() {
572 let ir = serde_json::to_string_pretty(tx).unwrap();
573 std::fs::write(&path, ir).unwrap();
574 }
575 }
576
577 fn test_lowering_example(example: &str) {
578 let manifest_dir = env!("CARGO_MANIFEST_DIR");
579 let mut program = parsing::parse_well_known_example(example);
580
581 crate::analyzing::analyze(&mut program).ok().unwrap();
582
583 for tx in program.txs.iter() {
584 let tir = lower(&program, &tx.name).unwrap();
585
586 make_snapshot_if_missing(example, &tx.name, &tir);
587
588 let tir_file = format!(
589 "{}/../../examples/{}.{}.tir",
590 manifest_dir, example, tx.name
591 );
592
593 let expected = std::fs::read_to_string(tir_file).unwrap();
594 let expected: ir::Tx = serde_json::from_str(&expected).unwrap();
595
596 assert_json_eq!(tir, expected);
597 }
598 }
599
600 #[macro_export]
601 macro_rules! test_lowering {
602 ($name:ident) => {
603 paste! {
604 #[test]
605 fn [<test_example_ $name>]() {
606 test_lowering_example(stringify!($name));
607 }
608 }
609 };
610 }
611
612 test_lowering!(lang_tour);
613
614 test_lowering!(transfer);
615
616 test_lowering!(swap);
617
618 test_lowering!(asteria);
619
620 test_lowering!(vesting);
621
622 test_lowering!(faucet);
623}