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::Unit => Ok(ir::Type::Unit),
249 ast::Type::Int => Ok(ir::Type::Int),
250 ast::Type::Bool => Ok(ir::Type::Bool),
251 ast::Type::Bytes => Ok(ir::Type::Bytes),
252 ast::Type::Address => Ok(ir::Type::Address),
253 ast::Type::UtxoRef => Ok(ir::Type::UtxoRef),
254 ast::Type::AnyAsset => Ok(ir::Type::AnyAsset),
255 ast::Type::Custom(x) => Ok(ir::Type::Custom(x.value.clone())),
256 }
257 }
258}
259
260impl IntoLower for ast::DataBinaryOp {
261 type Output = ir::BinaryOp;
262
263 fn into_lower(&self) -> Result<Self::Output, Error> {
264 let left = self.left.into_lower()?;
265 let right = self.right.into_lower()?;
266
267 Ok(ir::BinaryOp {
268 left,
269 right,
270 op: match self.operator {
271 ast::BinaryOperator::Add => ir::BinaryOpKind::Add,
272 ast::BinaryOperator::Subtract => ir::BinaryOpKind::Sub,
273 },
274 })
275 }
276}
277
278impl IntoLower for ast::PropertyAccess {
279 type Output = ir::Expression;
280
281 fn into_lower(&self) -> Result<Self::Output, Error> {
282 let mut object = self.object.into_lower()?;
283
284 for field in self.path.iter() {
285 object = ir::Expression::EvalProperty(Box::new(ir::PropertyAccess {
286 object: Box::new(object),
287 field: field.value.clone(),
288 }));
289 }
290
291 Ok(object)
292 }
293}
294
295impl IntoLower for ast::DataExpr {
296 type Output = ir::Expression;
297
298 fn into_lower(&self) -> Result<Self::Output, Error> {
299 let out = match self {
300 ast::DataExpr::None => ir::Expression::None,
301 ast::DataExpr::Number(x) => Self::Output::Number(*x as i128),
302 ast::DataExpr::Bool(x) => ir::Expression::Bool(*x),
303 ast::DataExpr::String(x) => ir::Expression::Bytes(x.value.as_bytes().to_vec()),
304 ast::DataExpr::HexString(x) => ir::Expression::Bytes(hex::decode(&x.value)?),
305 ast::DataExpr::Constructor(x) => ir::Expression::Struct(x.into_lower()?),
306 ast::DataExpr::Unit => ir::Expression::Struct(ir::StructExpr::unit()),
307 ast::DataExpr::Identifier(x) => x.into_lower()?,
308 ast::DataExpr::BinaryOp(x) => ir::Expression::EvalCustom(Box::new(x.into_lower()?)),
309 ast::DataExpr::PropertyAccess(x) => x.into_lower()?,
310 };
311
312 Ok(out)
313 }
314}
315
316impl IntoLower for ast::AssetConstructor {
317 type Output = ir::Expression;
318
319 fn into_lower(&self) -> Result<Self::Output, Error> {
320 let asset_def = coerce_identifier_into_asset_def(&self.r#type)?;
321
322 let asset_name = ir::Expression::Bytes(asset_def.asset_name.as_bytes().to_vec());
323
324 let amount = self.amount.into_lower()?;
325
326 Ok(ir::Expression::Assets(vec![ir::AssetExpr {
327 policy: hex::decode(&asset_def.policy.value)?,
328 asset_name,
329 amount,
330 }]))
331 }
332}
333
334impl IntoLower for ast::AssetBinaryOp {
335 type Output = ir::BinaryOp;
336
337 fn into_lower(&self) -> Result<Self::Output, Error> {
338 let left = self.left.into_lower()?;
339 let right = self.right.into_lower()?;
340
341 Ok(ir::BinaryOp {
342 left,
343 right,
344 op: match self.operator {
345 ast::BinaryOperator::Add => ir::BinaryOpKind::Add,
346 ast::BinaryOperator::Subtract => ir::BinaryOpKind::Sub,
347 },
348 })
349 }
350}
351
352impl IntoLower for ast::AssetExpr {
353 type Output = ir::Expression;
354
355 fn into_lower(&self) -> Result<Self::Output, Error> {
356 match self {
357 ast::AssetExpr::Constructor(x) => x.into_lower(),
358 ast::AssetExpr::BinaryOp(x) => {
359 Ok(ir::Expression::EvalCustom(Box::new(x.into_lower()?)))
360 }
361 ast::AssetExpr::Identifier(x) => coerce_identifier_into_asset_expr(x),
362 ast::AssetExpr::PropertyAccess(_x) => todo!(),
363 }
364 }
365}
366
367impl IntoLower for ast::AddressExpr {
368 type Output = ir::Expression;
369
370 fn into_lower(&self) -> Result<Self::Output, Error> {
371 match self {
372 ast::AddressExpr::String(x) => Ok(ir::Expression::String(x.value.clone())),
373 ast::AddressExpr::HexString(x) => Ok(ir::Expression::Bytes(hex::decode(&x.value)?)),
374 ast::AddressExpr::Identifier(x) => lower_into_address_expr(x),
375 }
376 }
377}
378
379impl IntoLower for ast::InputBlockField {
380 type Output = ir::Expression;
381
382 fn into_lower(&self) -> Result<Self::Output, Error> {
383 match self {
384 ast::InputBlockField::From(x) => x.into_lower(),
385 ast::InputBlockField::DatumIs(_) => todo!(),
386 ast::InputBlockField::MinAmount(x) => x.into_lower(),
387 ast::InputBlockField::Redeemer(x) => x.into_lower(),
388 ast::InputBlockField::Ref(x) => x.into_lower(),
389 }
390 }
391}
392
393impl IntoLower for ast::InputBlock {
394 type Output = ir::Input;
395
396 fn into_lower(&self) -> Result<Self::Output, Error> {
397 let from = self.find("from");
398 let min_amount = self.find("min_amount");
399 let r#ref = self.find("ref");
400
401 let policy = from
402 .and_then(ast::InputBlockField::as_address_expr)
403 .and_then(ast::AddressExpr::as_identifier)
404 .and_then(|x| x.symbol.as_ref())
405 .and_then(|x| x.as_policy_def())
406 .map(|x| x.into_lower())
407 .transpose()?;
408
409 let input = ir::Input {
410 name: self.name.to_lowercase().clone(),
411 query: ir::InputQuery {
412 address: from.into_lower()?,
413 min_amount: min_amount.into_lower()?,
414 r#ref: r#ref.into_lower()?,
415 }
416 .into(),
417 refs: HashSet::new(),
418 redeemer: None,
419 policy,
420 };
421
422 Ok(input)
423 }
424}
425
426impl IntoLower for ast::OutputBlockField {
427 type Output = ir::Expression;
428
429 fn into_lower(&self) -> Result<Self::Output, Error> {
430 match self {
431 ast::OutputBlockField::To(x) => x.into_lower(),
432 ast::OutputBlockField::Amount(x) => x.into_lower(),
433 ast::OutputBlockField::Datum(x) => x.into_lower(),
434 }
435 }
436}
437
438impl IntoLower for ast::OutputBlock {
439 type Output = ir::Output;
440
441 fn into_lower(&self) -> Result<Self::Output, Error> {
442 Ok(ir::Output {
443 address: self.find("to").into_lower()?,
444 datum: self.find("datum").into_lower()?,
445 amount: self.find("amount").into_lower()?,
446 })
447 }
448}
449
450impl IntoLower for ast::MintBlockField {
451 type Output = ir::Expression;
452
453 fn into_lower(&self) -> Result<Self::Output, Error> {
454 match self {
455 ast::MintBlockField::Amount(x) => x.into_lower(),
456 ast::MintBlockField::Redeemer(x) => x.into_lower(),
457 }
458 }
459}
460
461impl IntoLower for ast::MintBlock {
462 type Output = ir::Mint;
463
464 fn into_lower(&self) -> Result<Self::Output, Error> {
465 Ok(ir::Mint {
466 amount: self.find("amount").into_lower()?,
467 redeemer: self.find("redeemer").into_lower()?,
468 })
469 }
470}
471
472impl IntoLower for ast::ChainSpecificBlock {
473 type Output = ir::AdHocDirective;
474
475 fn into_lower(&self) -> Result<Self::Output, Error> {
476 match self {
477 ast::ChainSpecificBlock::Cardano(x) => x.into_lower(),
478 }
479 }
480}
481
482pub fn lower_tx(ast: &ast::TxDef) -> Result<ir::Tx, Error> {
483 let ir = ir::Tx {
484 inputs: ast
485 .inputs
486 .iter()
487 .map(|x| x.into_lower())
488 .collect::<Result<Vec<_>, _>>()?,
489 outputs: ast
490 .outputs
491 .iter()
492 .map(|x| x.into_lower())
493 .collect::<Result<Vec<_>, _>>()?,
494 mint: ast.mint.as_ref().map(|x| x.into_lower()).transpose()?,
495 adhoc: ast
496 .adhoc
497 .iter()
498 .map(|x| x.into_lower())
499 .collect::<Result<Vec<_>, _>>()?,
500 fees: ir::Expression::FeeQuery,
501 };
502
503 Ok(ir)
504}
505
506pub fn lower(ast: &ast::Program, template: &str) -> Result<ir::Tx, Error> {
519 let tx = ast
520 .txs
521 .iter()
522 .find(|x| x.name == template)
523 .ok_or(Error::InvalidAst("tx not found".to_string()))?;
524
525 lower_tx(tx)
526}
527
528#[cfg(test)]
529mod tests {
530 use assert_json_diff::assert_json_eq;
531 use paste::paste;
532
533 use super::*;
534 use crate::parsing;
535
536 fn make_snapshot_if_missing(example: &str, name: &str, tx: &ir::Tx) {
537 let manifest_dir = env!("CARGO_MANIFEST_DIR");
538
539 let path = format!("{}/../../examples/{}.{}.tir", manifest_dir, example, name);
540
541 if !std::fs::exists(&path).unwrap() {
542 let ir = serde_json::to_string_pretty(tx).unwrap();
543 std::fs::write(&path, ir).unwrap();
544 }
545 }
546
547 fn test_lowering_example(example: &str) {
548 let manifest_dir = env!("CARGO_MANIFEST_DIR");
549 let mut program = parsing::parse_well_known_example(example);
550
551 crate::analyzing::analyze(&mut program).ok().unwrap();
552
553 for tx in program.txs.iter() {
554 let tir = lower(&program, &tx.name).unwrap();
555
556 make_snapshot_if_missing(example, &tx.name, &tir);
557
558 let tir_file = format!(
559 "{}/../../examples/{}.{}.tir",
560 manifest_dir, example, tx.name
561 );
562
563 let expected = std::fs::read_to_string(tir_file).unwrap();
564 let expected: ir::Tx = serde_json::from_str(&expected).unwrap();
565
566 assert_json_eq!(tir, expected);
567 }
568 }
569
570 #[macro_export]
571 macro_rules! test_lowering {
572 ($name:ident) => {
573 paste! {
574 #[test]
575 fn [<test_example_ $name>]() {
576 test_lowering_example(stringify!($name));
577 }
578 }
579 };
580 }
581
582 test_lowering!(lang_tour);
583
584 test_lowering!(transfer);
585
586 test_lowering!(swap);
587
588 test_lowering!(asteria);
589
590 test_lowering!(vesting);
591
592 test_lowering!(faucet);
593}