pub(crate) use crate::{ ast::{ self, Annotation, ArgBy, ArgName, AssignmentKind, AssignmentPattern, BinOp, Bls12_381Point, ByteArrayFormatPreference, CallArg, Curve, DataType, DataTypeKey, DefinitionLocation, Located, LogicalOpChainKind, ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypedArg, TypedAssignmentKind, TypedClause, TypedDataType, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause, UntypedIfBranch, UntypedRecordUpdateArg, }, parser::token::Base, tipo::{ check_replaceable_opaque_type, convert_opaque_type, lookup_data_type_by_tipo, ModuleValueConstructor, PatternConstructor, Type, TypeVar, ValueConstructor, ValueConstructorVariant, }, }; use indexmap::IndexMap; use pallas_primitives::alonzo::{Constr, PlutusData}; use std::{fmt::Debug, rc::Rc}; use uplc::{ ast::Data, machine::{runtime::convert_tag_to_constr, value::from_pallas_bigint}, KeyValuePairs, }; use vec1::Vec1; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum TypedExpr { UInt { location: Span, tipo: Rc, value: String, base: Base, }, String { location: Span, tipo: Rc, value: String, }, ByteArray { location: Span, tipo: Rc, bytes: Vec, preferred_format: ByteArrayFormatPreference, }, CurvePoint { location: Span, tipo: Rc, point: Box, preferred_format: ByteArrayFormatPreference, }, Sequence { location: Span, expressions: Vec, }, /// A chain of pipe expressions. /// By this point the type checker has expanded it into a series of /// assignments and function calls, but we still have a Pipeline AST node as /// even though it is identical to `Sequence` we want to use different /// locations when showing it in error messages, etc. Pipeline { location: Span, expressions: Vec, }, Var { location: Span, constructor: ValueConstructor, name: String, }, Fn { location: Span, tipo: Rc, is_capture: bool, args: Vec, body: Box, return_annotation: Option, }, List { location: Span, tipo: Rc, elements: Vec, tail: Option>, }, Call { location: Span, tipo: Rc, fun: Box, args: Vec>, }, BinOp { location: Span, tipo: Rc, name: BinOp, left: Box, right: Box, }, Assignment { location: Span, tipo: Rc, value: Box, pattern: Pattern>, kind: TypedAssignmentKind, }, Trace { location: Span, tipo: Rc, then: Box, text: Box, }, When { location: Span, tipo: Rc, subject: Box, clauses: Vec, }, If { location: Span, #[serde(with = "Vec1Ref")] branches: Vec1, final_else: Box, tipo: Rc, }, RecordAccess { location: Span, tipo: Rc, label: String, index: u64, record: Box, }, ModuleSelect { location: Span, tipo: Rc, label: String, module_name: String, module_alias: String, constructor: ModuleValueConstructor, }, Tuple { location: Span, tipo: Rc, elems: Vec, }, Pair { location: Span, tipo: Rc, fst: Box, snd: Box, }, TupleIndex { location: Span, tipo: Rc, index: usize, tuple: Box, }, ErrorTerm { location: Span, tipo: Rc, }, RecordUpdate { location: Span, tipo: Rc, spread: Box, args: Vec, }, UnOp { location: Span, value: Box, tipo: Rc, op: UnOp, }, } #[derive(serde::Serialize, serde::Deserialize)] #[serde(remote = "Vec1")] struct Vec1Ref(#[serde(getter = "Vec1::as_vec")] Vec); impl From> for Vec1 { fn from(v: Vec1Ref) -> Self { Vec1::try_from_vec(v.0).unwrap() } } impl TypedExpr { pub fn is_simple_expr_to_format(&self) -> bool { match self { Self::String { .. } | Self::UInt { .. } | Self::ByteArray { .. } | Self::Var { .. } => { true } Self::Pair { fst, snd, .. } => { fst.is_simple_expr_to_format() && snd.is_simple_expr_to_format() } Self::Tuple { elems, .. } => elems.iter().all(|e| e.is_simple_expr_to_format()), Self::List { elements, .. } if elements.len() <= 3 => { elements.iter().all(|e| e.is_simple_expr_to_format()) } _ => false, } } pub fn and_then(self, next: Self) -> Self { if let TypedExpr::Trace { tipo, location, then, text, } = self { return TypedExpr::Trace { tipo, location, then: Box::new(then.and_then(next)), text, }; } TypedExpr::Sequence { location: self.location(), expressions: vec![self, next], } } pub fn sequence(exprs: &[TypedExpr]) -> Self { TypedExpr::Sequence { location: Span::empty(), expressions: exprs.to_vec(), } } pub fn let_(value: Self, pattern: TypedPattern, tipo: Rc, location: Span) -> Self { TypedExpr::Assignment { tipo: tipo.clone(), value: value.into(), pattern, kind: AssignmentKind::let_(), location, } } // Create an expect assignment, unless the target type is `Data`; then fallback to a let. pub fn flexible_expect( value: Self, pattern: TypedPattern, tipo: Rc, location: Span, ) -> Self { TypedExpr::Assignment { tipo: tipo.clone(), value: value.into(), pattern, kind: if tipo.is_data() { AssignmentKind::let_() } else { AssignmentKind::expect() }, location, } } pub fn local_var(name: &str, tipo: Rc, location: Span) -> Self { TypedExpr::Var { constructor: ValueConstructor { public: true, variant: ValueConstructorVariant::LocalVariable { location: Span::empty(), }, tipo: tipo.clone(), }, name: name.to_string(), location, } } pub fn tipo(&self) -> Rc { match self { Self::Var { constructor, .. } => constructor.tipo.clone(), Self::Trace { then, .. } => then.tipo(), Self::Fn { tipo, .. } | Self::UInt { tipo, .. } | Self::ErrorTerm { tipo, .. } | Self::When { tipo, .. } | Self::List { tipo, .. } | Self::Call { tipo, .. } | Self::If { tipo, .. } | Self::UnOp { tipo, .. } | Self::BinOp { tipo, .. } | Self::Tuple { tipo, .. } | Self::Pair { tipo, .. } | Self::String { tipo, .. } | Self::ByteArray { tipo, .. } | Self::TupleIndex { tipo, .. } | Self::Assignment { tipo, .. } | Self::ModuleSelect { tipo, .. } | Self::RecordAccess { tipo, .. } | Self::RecordUpdate { tipo, .. } | Self::CurvePoint { tipo, .. } => tipo.clone(), Self::Pipeline { expressions, .. } | Self::Sequence { expressions, .. } => expressions .last() .map(TypedExpr::tipo) .unwrap_or_else(Type::void), } } pub fn is_literal(&self) -> bool { matches!( self, Self::UInt { .. } | Self::List { .. } | Self::Tuple { .. } | Self::String { .. } | Self::ByteArray { .. } ) } pub fn is_error_term(&self) -> bool { matches!(self, Self::ErrorTerm { .. }) } /// Returns `true` if the typed expr is [`Assignment`]. pub fn is_assignment(&self) -> bool { matches!(self, Self::Assignment { .. }) } pub fn definition_location(&self) -> Option> { match self { TypedExpr::Fn { .. } | TypedExpr::UInt { .. } | TypedExpr::Trace { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::When { .. } | TypedExpr::ErrorTerm { .. } | TypedExpr::BinOp { .. } | TypedExpr::Tuple { .. } | TypedExpr::Pair { .. } | TypedExpr::UnOp { .. } | TypedExpr::String { .. } | TypedExpr::Sequence { .. } | TypedExpr::Pipeline { .. } | TypedExpr::ByteArray { .. } | TypedExpr::Assignment { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::CurvePoint { .. } => None, TypedExpr::If { .. } => None, // TODO: test // TODO: definition TypedExpr::RecordUpdate { .. } => None, // TODO: test TypedExpr::ModuleSelect { module_name, constructor, .. } => Some(DefinitionLocation { module: Some(module_name.as_str()), span: constructor.location(), }), // TODO: test TypedExpr::Var { constructor, .. } => Some(constructor.definition_location()), } } pub fn type_defining_location(&self) -> Span { match self { Self::Fn { location, .. } | Self::UInt { location, .. } | Self::Var { location, .. } | Self::Trace { location, .. } | Self::ErrorTerm { location, .. } | Self::When { location, .. } | Self::Call { location, .. } | Self::List { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } | Self::Pair { location, .. } | Self::String { location, .. } | Self::UnOp { location, .. } | Self::Pipeline { location, .. } | Self::ByteArray { location, .. } | Self::Assignment { location, .. } | Self::TupleIndex { location, .. } | Self::ModuleSelect { location, .. } | Self::RecordAccess { location, .. } | Self::RecordUpdate { location, .. } | Self::CurvePoint { location, .. } => *location, Self::If { branches, .. } => branches.first().body.type_defining_location(), Self::Sequence { expressions, location, .. } => expressions .last() .map(TypedExpr::location) .unwrap_or(*location), } } pub fn location(&self) -> Span { match self { Self::Fn { location, .. } | Self::UInt { location, .. } | Self::Trace { location, .. } | Self::Var { location, .. } | Self::ErrorTerm { location, .. } | Self::When { location, .. } | Self::Call { location, .. } | Self::If { location, .. } | Self::List { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } | Self::Pair { location, .. } | Self::String { location, .. } | Self::UnOp { location, .. } | Self::Sequence { location, .. } | Self::Pipeline { location, .. } | Self::ByteArray { location, .. } | Self::Assignment { location, .. } | Self::TupleIndex { location, .. } | Self::ModuleSelect { location, .. } | Self::RecordAccess { location, .. } | Self::RecordUpdate { location, .. } | Self::CurvePoint { location, .. } => *location, } } // This could be optimised in places to exit early if the first of a series // of expressions is after the byte index. pub fn find_node(&self, byte_index: usize) -> Option> { if !self.location().contains(byte_index) { return None; } match self { TypedExpr::ErrorTerm { .. } | TypedExpr::Var { .. } | TypedExpr::UInt { .. } | TypedExpr::String { .. } | TypedExpr::ByteArray { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::CurvePoint { .. } => Some(Located::Expression(self)), TypedExpr::Trace { text, then, .. } => text .find_node(byte_index) .or_else(|| then.find_node(byte_index)) .or(Some(Located::Expression(self))), TypedExpr::Pipeline { expressions, .. } | TypedExpr::Sequence { expressions, .. } => { expressions.iter().find_map(|e| e.find_node(byte_index)) } TypedExpr::Fn { body, args, return_annotation, .. } => args .iter() .find_map(|arg| arg.find_node(byte_index)) .or_else(|| body.find_node(byte_index)) .or_else(|| { return_annotation .as_ref() .and_then(|a| a.find_node(byte_index)) }) .or(Some(Located::Expression(self))), TypedExpr::Tuple { elems: elements, .. } => elements .iter() .find_map(|e| e.find_node(byte_index)) .or(Some(Located::Expression(self))), TypedExpr::Pair { fst, snd, .. } => [fst, snd] .iter() .find_map(|e| e.find_node(byte_index)) .or(Some(Located::Expression(self))), TypedExpr::List { elements, tail, .. } => elements .iter() .find_map(|e| e.find_node(byte_index)) .or_else(|| tail.as_ref().and_then(|t| t.find_node(byte_index))) .or(Some(Located::Expression(self))), TypedExpr::Call { fun, args, .. } => args .iter() .find_map(|arg| arg.find_node(byte_index)) .or_else(|| fun.find_node(byte_index)) .or(Some(Located::Expression(self))), TypedExpr::BinOp { left, right, .. } => left .find_node(byte_index) .or_else(|| right.find_node(byte_index)) .or(Some(Located::Expression(self))), TypedExpr::Assignment { value, pattern, .. } => pattern .find_node(byte_index, &value.tipo()) .or_else(|| value.find_node(byte_index)), TypedExpr::When { subject, clauses, .. } => subject .find_node(byte_index) .or_else(|| { clauses .iter() .find_map(|clause| clause.find_node(byte_index, &subject.tipo())) }) .or(Some(Located::Expression(self))), TypedExpr::RecordAccess { record: expression, .. } | TypedExpr::TupleIndex { tuple: expression, .. } => expression .find_node(byte_index) .or(Some(Located::Expression(self))), TypedExpr::RecordUpdate { spread, args, .. } => args .iter() .find_map(|arg| arg.find_node(byte_index)) .or_else(|| spread.find_node(byte_index)) .or(Some(Located::Expression(self))), TypedExpr::If { branches, final_else, .. } => branches .iter() .find_map(|branch| { branch .condition .find_node(byte_index) .or_else(|| branch.body.find_node(byte_index)) }) .or_else(|| final_else.find_node(byte_index)) .or(Some(Located::Expression(self))), TypedExpr::UnOp { value, .. } => value .find_node(byte_index) .or(Some(Located::Expression(self))), } } pub fn void(location: Span) -> Self { TypedExpr::Var { name: "Void".to_string(), constructor: ValueConstructor { public: true, variant: ValueConstructorVariant::Record { name: "Void".to_string(), arity: 0, field_map: None, location: Span::empty(), module: String::new(), constructors_count: 1, }, tipo: Type::void(), }, location, } } } // Represent how a function was written so that we can format it back. #[derive(Debug, Clone, PartialEq, Copy)] pub enum FnStyle { Plain, Capture, BinOp(BinOp), } #[derive(Debug, Clone, PartialEq)] pub enum UntypedExpr { UInt { location: Span, value: String, base: Base, }, String { location: Span, value: String, }, Sequence { location: Span, expressions: Vec, }, Var { location: Span, name: String, }, Fn { location: Span, fn_style: FnStyle, arguments: Vec, body: Box, return_annotation: Option, }, List { location: Span, elements: Vec, tail: Option>, }, Call { arguments: Vec>, fun: Box, location: Span, }, BinOp { location: Span, name: BinOp, left: Box, right: Box, }, ByteArray { location: Span, bytes: Vec, preferred_format: ByteArrayFormatPreference, }, CurvePoint { location: Span, point: Box, preferred_format: ByteArrayFormatPreference, }, PipeLine { expressions: Vec1, one_liner: bool, }, Assignment { location: Span, value: Box, patterns: Vec1, kind: UntypedAssignmentKind, }, Trace { kind: TraceKind, location: Span, then: Box, label: Box, arguments: Vec, }, TraceIfFalse { location: Span, value: Box, }, When { location: Span, subject: Box, clauses: Vec, }, If { location: Span, branches: Vec1, final_else: Box, }, FieldAccess { location: Span, label: String, container: Box, }, Tuple { location: Span, elems: Vec, }, Pair { location: Span, fst: Box, snd: Box, }, TupleIndex { location: Span, index: usize, tuple: Box, }, ErrorTerm { location: Span, }, RecordUpdate { location: Span, constructor: Box, spread: RecordUpdateSpread, arguments: Vec, }, UnOp { op: UnOp, location: Span, value: Box, }, LogicalOpChain { kind: LogicalOpChainKind, expressions: Vec, location: Span, }, } pub const DEFAULT_TODO_STR: &str = "aiken::todo"; pub const DEFAULT_ERROR_STR: &str = "aiken::error"; impl UntypedExpr { // Reify some opaque 'Constant' into an 'UntypedExpr', using a Type annotation. We also need // an extra map to lookup record & enum constructor's names as they're completely erased when // in their PlutusData form, and the Type annotation only contains type name. // // The function performs some sanity check to ensure that the type does indeed somewhat // correspond to the data being given. pub fn reify_constant( data_types: &IndexMap<&DataTypeKey, &TypedDataType>, cst: uplc::ast::Constant, tipo: &Type, ) -> Result { UntypedExpr::do_reify_constant(&mut IndexMap::new(), data_types, cst, tipo) } // Reify some opaque 'PlutusData' into an 'UntypedExpr', using a Type annotation. We also need // an extra map to lookup record & enum constructor's names as they're completely erased when // in their PlutusData form, and the Type annotation only contains type name. // // The function performs some sanity check to ensure that the type does indeed somewhat // correspond to the data being given. pub fn reify_data( data_types: &IndexMap<&DataTypeKey, &TypedDataType>, data: PlutusData, tipo: &Type, ) -> Result { UntypedExpr::do_reify_data(&mut IndexMap::new(), data_types, data, tipo) } fn reify_with( generics: &mut IndexMap>, data_types: &IndexMap<&DataTypeKey, &TypedDataType>, t: T, tipo: &Type, with: F, ) -> Result where T: Debug, F: Fn( &mut IndexMap>, &IndexMap<&DataTypeKey, &TypedDataType>, T, &Type, ) -> Result, { if let Type::Var { tipo: var_tipo, .. } = tipo { match &*var_tipo.borrow() { TypeVar::Link { tipo } => { return Self::reify_with(generics, data_types, t, tipo, with); } TypeVar::Generic { id } => { if let Some(tipo) = generics.get(id) { return Self::reify_with(generics, data_types, t, &tipo.clone(), with); } } _ => unreachable!("unbound type during reification {tipo:?} -> {t:?}"), } } // NOTE: Opaque types are tricky. We can't tell from a type only if it is // opaque or not. We have to lookup its datatype definition. // // Also, we can't -- in theory -- peak into an opaque type. More so, if it // has a single constructor with a single argument, it is an zero-cost // wrapper. That means the underlying PlutusData has no residue of that // wrapper. So we have to manually reconstruct it before crawling further // down the type tree. if check_replaceable_opaque_type(tipo, data_types) { let DataType { name, .. } = lookup_data_type_by_tipo(data_types, tipo) .expect("Type just disappeared from known types? {tipo:?}"); let inner_type = convert_opaque_type(&tipo.clone().into(), data_types, false); let value = Self::reify_with(generics, data_types, t, &inner_type, with)?; return Ok(UntypedExpr::Call { location: Span::empty(), arguments: vec![CallArg { label: None, location: Span::empty(), value, }], fun: Box::new(UntypedExpr::Var { name, location: Span::empty(), }), }); } with(generics, data_types, t, tipo) } fn do_reify_constant( generics: &mut IndexMap>, data_types: &IndexMap<&DataTypeKey, &TypedDataType>, cst: uplc::ast::Constant, tipo: &Type, ) -> Result { Self::reify_with( generics, data_types, cst, tipo, |generics, data_types, cst, tipo| match cst { uplc::ast::Constant::Data(data) => { UntypedExpr::do_reify_data(generics, data_types, data, tipo) } uplc::ast::Constant::Integer(i) => { UntypedExpr::do_reify_data(generics, data_types, Data::integer(i), tipo) } uplc::ast::Constant::ByteString(bytes) => { UntypedExpr::do_reify_data(generics, data_types, Data::bytestring(bytes), tipo) } uplc::ast::Constant::ProtoList(_, args) => match tipo { Type::App { module, name, args: type_args, .. } if module.is_empty() && name.as_str() == "List" => { if let [inner] = &type_args[..] { Ok(UntypedExpr::List { location: Span::empty(), elements: args .into_iter() .map(|arg| { UntypedExpr::do_reify_constant( generics, data_types, arg, inner, ) }) .collect::, _>>()?, tail: None, }) } else { Err( "invalid List type annotation: the list has multiple type-parameters." .to_string(), ) } } Type::Tuple { elems, .. } => Ok(UntypedExpr::Tuple { location: Span::empty(), elems: args .into_iter() .zip(elems) .map(|(arg, arg_type)| { UntypedExpr::do_reify_constant(generics, data_types, arg, arg_type) }) .collect::, _>>()?, }), _ => Err(format!( "invalid type annotation. expected List but got: {tipo:?}" )), }, uplc::ast::Constant::ProtoPair(_, _, left, right) => match tipo { Type::Pair { fst, snd, .. } => { let elems = [left.as_ref(), right.as_ref()] .into_iter() .zip([fst, snd]) .map(|(arg, arg_type)| { UntypedExpr::do_reify_constant( generics, data_types, arg.to_owned(), arg_type, ) }) .collect::, _>>()?; Ok(UntypedExpr::Pair { location: Span::empty(), fst: elems.first().unwrap().to_owned().into(), snd: elems.last().unwrap().to_owned().into(), }) } _ => Err(format!( "invalid type annotation. expected Pair but got: {tipo:?}" )), }, uplc::ast::Constant::Unit => Ok(UntypedExpr::Var { location: Span::empty(), name: "Void".to_string(), }), uplc::ast::Constant::Bool(is_true) => Ok(UntypedExpr::Var { location: Span::empty(), name: if is_true { "True" } else { "False" }.to_string(), }), uplc::ast::Constant::String(value) => Ok(UntypedExpr::String { location: Span::empty(), value, }), uplc::ast::Constant::Bls12_381G1Element(pt) => Ok(UntypedExpr::CurvePoint { location: Span::empty(), point: Curve::Bls12_381(Bls12_381Point::G1(*pt)).into(), preferred_format: ByteArrayFormatPreference::HexadecimalString, }), uplc::ast::Constant::Bls12_381G2Element(pt) => Ok(UntypedExpr::CurvePoint { location: Span::empty(), point: Curve::Bls12_381(Bls12_381Point::G2(*pt)).into(), preferred_format: ByteArrayFormatPreference::HexadecimalString, }), uplc::ast::Constant::Bls12_381MlResult(ml) => { let mut bytes = Vec::new(); bytes.extend((*ml).to_bendian()); // NOTE: We don't actually have a syntax for representing MillerLoop results, so we // just fake it as a constructor with a bytearray. Note also that the bytearray is // *large*. Ok(UntypedExpr::Call { location: Span::empty(), arguments: vec![CallArg { label: None, location: Span::empty(), value: UntypedExpr::ByteArray { location: Span::empty(), bytes, preferred_format: ByteArrayFormatPreference::HexadecimalString, }, }], fun: Box::new(UntypedExpr::Var { name: "MillerLoopResult".to_string(), location: Span::empty(), }), }) } }, ) } fn reify_blind(data: PlutusData) -> Self { match data { PlutusData::BigInt(ref i) => UntypedExpr::UInt { location: Span::empty(), base: Base::Decimal { numeric_underscore: false, }, value: from_pallas_bigint(i).to_string(), }, PlutusData::BoundedBytes(bytes) => UntypedExpr::ByteArray { location: Span::empty(), bytes: bytes.into(), preferred_format: ByteArrayFormatPreference::HexadecimalString, }, PlutusData::Array(elems) => UntypedExpr::List { location: Span::empty(), elements: elems .to_vec() .into_iter() .map(UntypedExpr::reify_blind) .collect::>(), tail: None, }, PlutusData::Map(indef_or_def) => { let kvs = match indef_or_def { KeyValuePairs::Def(kvs) => kvs, KeyValuePairs::Indef(kvs) => kvs, }; UntypedExpr::List { location: Span::empty(), elements: kvs .into_iter() .map(|(k, v)| UntypedExpr::Pair { location: Span::empty(), fst: UntypedExpr::reify_blind(k).into(), snd: UntypedExpr::reify_blind(v).into(), }) .collect::>(), tail: None, } } PlutusData::Constr(Constr { tag, any_constructor, fields, }) => { let ix = convert_tag_to_constr(tag).or(any_constructor).unwrap() as usize; let fields = fields .to_vec() .into_iter() .map(|field| CallArg { location: Span::empty(), label: None, value: UntypedExpr::reify_blind(field), }) .collect::>(); let mut arguments = vec![CallArg { location: Span::empty(), label: None, value: UntypedExpr::UInt { location: Span::empty(), value: ix.to_string(), base: Base::Decimal { numeric_underscore: false, }, }, }]; arguments.extend(fields); UntypedExpr::Call { location: Span::empty(), arguments, fun: UntypedExpr::Var { name: "Constr".to_string(), location: Span::empty(), } .into(), } } } } fn do_reify_data( generics: &mut IndexMap>, data_types: &IndexMap<&DataTypeKey, &TypedDataType>, data: PlutusData, tipo: &Type, ) -> Result { if let Type::App { name, module, .. } = tipo { if module.is_empty() && name == "Data" { return Ok(Self::reify_blind(data)); } } Self::reify_with( generics, data_types, data, tipo, |generics, data_types, data, tipo| match data { PlutusData::BigInt(ref i) => Ok(UntypedExpr::UInt { location: Span::empty(), base: Base::Decimal { numeric_underscore: false, }, value: from_pallas_bigint(i).to_string(), }), PlutusData::BoundedBytes(bytes) => { if tipo.is_string() { Ok(UntypedExpr::String { location: Span::empty(), value: String::from_utf8(bytes.to_vec()).expect("invalid UTF-8 string"), }) } else { Ok(UntypedExpr::ByteArray { location: Span::empty(), bytes: bytes.into(), preferred_format: ByteArrayFormatPreference::HexadecimalString, }) } } PlutusData::Array(args) => match tipo { Type::App { module, name, args: type_args, .. } if module.is_empty() && name.as_str() == "List" => { if let [inner] = &type_args[..] { Ok(UntypedExpr::List { location: Span::empty(), elements: args .to_vec() .into_iter() .map(|arg| { UntypedExpr::do_reify_data(generics, data_types, arg, inner) }) .collect::, _>>()?, tail: None, }) } else { Err( "invalid List type annotation: the list has multiple type-parameters." .to_string(), ) } } Type::Tuple { elems, .. } => Ok(UntypedExpr::Tuple { location: Span::empty(), elems: args .to_vec() .into_iter() .zip(elems) .map(|(arg, arg_type)| { UntypedExpr::do_reify_data(generics, data_types, arg, arg_type) }) .collect::, _>>()?, }), Type::Pair { fst, snd, .. } => { let mut elems = args .to_vec() .into_iter() .zip([fst, snd]) .map(|(arg, arg_type)| { UntypedExpr::do_reify_data(generics, data_types, arg, arg_type) }) .collect::, _>>()?; Ok(UntypedExpr::Pair { location: Span::empty(), fst: elems.remove(0).into(), snd: elems.remove(0).into(), }) } _ => Err(format!( "invalid type annotation. expected List but got: {tipo:?}" )), }, PlutusData::Constr(Constr { tag, any_constructor, fields, }) => { let ix = convert_tag_to_constr(tag).or(any_constructor).unwrap() as usize; if let Type::App { args, .. } = tipo { if let Some(DataType { constructors, typed_parameters, .. }) = lookup_data_type_by_tipo(data_types, tipo) { if constructors.is_empty() { return Ok(UntypedExpr::Var { location: Span::empty(), name: "Data".to_string(), }); } let constructor = &constructors[ix]; typed_parameters .iter() .zip(args) .for_each(|(generic, arg)| { if let Some(ix) = generic.get_generic() { if !generics.contains_key(&ix) { generics.insert(ix, arg.clone()); } } }); return if fields.is_empty() { Ok(UntypedExpr::Var { location: Span::empty(), name: constructor.name.to_string(), }) } else { let arguments = fields .to_vec() .into_iter() .zip(constructor.arguments.iter()) .map( |( field, RecordConstructorArg { ref label, ref tipo, .. }, )| { UntypedExpr::do_reify_data( generics, data_types, field, tipo, ) .map(|value| CallArg { label: label.clone(), location: Span::empty(), value, }) }, ) .collect::, _>>()?; Ok(UntypedExpr::Call { location: Span::empty(), arguments, fun: Box::new(UntypedExpr::Var { name: constructor.name.to_string(), location: Span::empty(), }), }) }; } } Err(format!( "invalid type annotation {tipo:?} for {}{} constructor with fields: {fields:?}", ix + 1, ordinal::Ordinal::(ix + 1).suffix(), )) } PlutusData::Map(indef_or_def) => { let kvs = match indef_or_def { KeyValuePairs::Def(kvs) => kvs, KeyValuePairs::Indef(kvs) => kvs, }; UntypedExpr::do_reify_data( generics, data_types, Data::list( kvs.into_iter() .map(|(k, v)| Data::list(vec![k, v])) .collect(), ), tipo, ) } }, ) } pub fn todo(reason: Option, location: Span) -> Self { UntypedExpr::Trace { location, kind: TraceKind::Todo, then: Box::new(UntypedExpr::ErrorTerm { location }), label: Box::new(reason.unwrap_or_else(|| UntypedExpr::String { location, value: DEFAULT_TODO_STR.to_string(), })), arguments: Vec::new(), } } pub fn fail(reason: Option, location: Span) -> Self { if let Some(reason) = reason { UntypedExpr::Trace { location, kind: TraceKind::Error, then: Box::new(UntypedExpr::ErrorTerm { location }), label: Box::new(reason), arguments: Vec::new(), } } else { UntypedExpr::ErrorTerm { location } } } pub fn tuple_index(self, index: usize, location: Span) -> Self { UntypedExpr::TupleIndex { location: self.location().union(location), index, tuple: Box::new(self), } } pub fn field_access(self, label: String, location: Span) -> Self { UntypedExpr::FieldAccess { location: self.location().union(location), label, container: Box::new(self), } } pub fn call(self, args: Vec, location: Span) -> Self { let mut holes = Vec::new(); let args = args .into_iter() .enumerate() .map(|(index, a)| match a { CallArg { value: Some(value), label, location, } => CallArg { value, label, location, }, CallArg { value: None, label, location, } => { let name = format!("{}__{index}", ast::CAPTURE_VARIABLE); holes.push(ast::UntypedArg { location: Span::empty(), annotation: None, doc: None, by: ArgBy::ByName(ast::ArgName::Named { label: name.clone(), name, location: Span::empty(), }), is_validator_param: false, }); ast::CallArg { label, location, value: UntypedExpr::Var { location, name: format!("{}__{index}", ast::CAPTURE_VARIABLE), }, } } }) .collect(); let call = UntypedExpr::Call { location: self.location().union(location), fun: Box::new(self), arguments: args, }; if holes.is_empty() { call } else { UntypedExpr::Fn { location: call.location(), fn_style: FnStyle::Capture, arguments: holes, body: Box::new(call), return_annotation: None, } } } pub fn append_in_sequence(self, next: Self) -> Self { let location = Span { start: self.location().start, end: next.location().end, }; match (self.clone(), next.clone()) { ( Self::Sequence { expressions: mut current_expressions, .. }, Self::Sequence { expressions: mut next_expressions, .. }, ) => { current_expressions.append(&mut next_expressions); Self::Sequence { location, expressions: current_expressions, } } ( _, Self::Sequence { expressions: mut next_expressions, .. }, ) => { let mut current_expressions = vec![self]; current_expressions.append(&mut next_expressions); Self::Sequence { location, expressions: current_expressions, } } (_, _) => Self::Sequence { location, expressions: vec![self, next], }, } } pub fn location(&self) -> Span { match self { Self::PipeLine { expressions, .. } => expressions.last().location(), Self::Trace { then, .. } => then.location(), Self::TraceIfFalse { location, .. } | Self::Fn { location, .. } | Self::Var { location, .. } | Self::UInt { location, .. } | Self::ErrorTerm { location, .. } | Self::When { location, .. } | Self::Call { location, .. } | Self::List { location, .. } | Self::ByteArray { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } | Self::Pair { location, .. } | Self::String { location, .. } | Self::Assignment { location, .. } | Self::TupleIndex { location, .. } | Self::FieldAccess { location, .. } | Self::RecordUpdate { location, .. } | Self::UnOp { location, .. } | Self::LogicalOpChain { location, .. } | Self::If { location, .. } | Self::CurvePoint { location, .. } => *location, Self::Sequence { location, expressions, .. } => expressions.last().map(Self::location).unwrap_or(*location), } } pub fn start_byte_index(&self) -> usize { match self { Self::Sequence { expressions, location, .. } => expressions .first() .map(|e| e.start_byte_index()) .unwrap_or(location.start), Self::PipeLine { expressions, .. } => expressions.first().start_byte_index(), Self::Trace { location, .. } | Self::Assignment { location, .. } => location.start, _ => self.location().start, } } pub fn binop_precedence(&self) -> u8 { match self { Self::BinOp { name, .. } => name.precedence(), Self::PipeLine { .. } => 0, _ => u8::MAX, } } /// Returns true when an UntypedExpr can be displayed in a flex-break manner (i.e. tries to fit as /// much as possible on a single line). When false, long lines with several of those patterns /// will be broken down to one expr per line. pub fn is_simple_expr_to_format(&self) -> bool { match self { Self::String { .. } | Self::UInt { .. } | Self::ByteArray { .. } | Self::Var { .. } => { true } Self::Pair { fst, snd, .. } => { fst.is_simple_expr_to_format() && snd.is_simple_expr_to_format() } Self::Tuple { elems, .. } => elems.iter().all(|e| e.is_simple_expr_to_format()), Self::List { elements, .. } if elements.len() <= 3 => { elements.iter().all(|e| e.is_simple_expr_to_format()) } _ => false, } } pub fn lambda( names: Vec<(ArgName, Span, Option)>, expressions: Vec, location: Span, ) -> Self { Self::Fn { location, fn_style: FnStyle::Plain, arguments: names .into_iter() .map(|(arg_name, location, annotation)| UntypedArg { location, doc: None, annotation, is_validator_param: false, by: ArgBy::ByName(arg_name), }) .collect(), body: Self::Sequence { location, expressions, } .into(), return_annotation: None, } } }