diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf92ec181..5d6e923e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,12 +57,12 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --verbose --release --tests + args: --verbose --tests - name: Run tests uses: actions-rs/cargo@v1 with: command: test - args: --verbose --release + args: --verbose test_coverage: name: Code coverage in tests diff --git a/Cargo.toml b/Cargo.toml index b6d893762..c2400f8eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "ergoscript-compiler", "ergotree-ir", "ergotree-interpreter", + "ergotree-macro", "ergo-lib", "ergo-p2p", "ergo-chain-generation", diff --git a/ergotree-interpreter/Cargo.toml b/ergotree-interpreter/Cargo.toml index 643478ae3..41b13e5d1 100644 --- a/ergotree-interpreter/Cargo.toml +++ b/ergotree-interpreter/Cargo.toml @@ -62,4 +62,5 @@ ergotree-ir = { version = "^0.20.0", path = "../ergotree-ir", features = ["arbit ergoscript-compiler = { version = "^0.16.0", path = "../ergoscript-compiler" } proptest = "1.0.0" sigma-test-util = { version = "^0.3.0", path = "../sigma-test-util" } +ergotree-macro = { version = "0.20", path = "../ergotree-macro"} diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml index a4c937091..b728985d3 100644 --- a/ergotree-ir/Cargo.toml +++ b/ergotree-ir/Cargo.toml @@ -39,6 +39,9 @@ strum_macros = "0.21" indexmap = "1.3.2" serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0", features = ["arbitrary_precision"], optional = true } +syn = { version = "1", features = ["parsing"], optional = true } +quote = { version = "1", optional = true } +proc-macro2 = { version = "1", optional = true } [dependencies.proptest] # wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html @@ -59,6 +62,7 @@ optional = true default = ["json"] arbitrary = ["proptest", "proptest-derive", "ergo-chain-types/arbitrary"] json = ["serde", "serde_json", "serde_with", "bounded-vec/serde"] +ergotree-proc-macro = ["syn", "quote", "proc-macro2"] [dev-dependencies] sigma-test-util = { version = "^0.3.0", path = "../sigma-test-util" } diff --git a/ergotree-ir/src/ergotree_proc_macro.rs b/ergotree-ir/src/ergotree_proc_macro.rs new file mode 100644 index 000000000..357273f8d --- /dev/null +++ b/ergotree-ir/src/ergotree_proc_macro.rs @@ -0,0 +1,264 @@ +//! Utility code to support `ergo_tree!` procedural-macro + +use syn::{ext::IdentExt, Ident}; + +use crate::types::stype::SType; + +/// A representation of the type T extracted from `_.typed[T]` on scala side. +#[derive(Debug)] +pub enum ExtractedType { + /// Fully specified `SType` + FullySpecified(SType), + /// `SCollection[_]]` in scala representation. + SCollection(Box), + /// `SOption[_]]` in scala representation. + SOption(Box), + /// `STuple` in scala representation + STuple, +} + +impl From for ExtractedType { + fn from(s: SType) -> Self { + ExtractedType::FullySpecified(s) + } +} + +/// Extracts T within `_.typed[T]`. +/// Note that scala uses some type aliases: e.g. `BoolValue` is short for `Value[SBoolean.type]` +pub fn extract_tpe_from_dot_typed( + buf: syn::parse::ParseStream, +) -> Result { + let ident: syn::Ident = buf.parse()?; + match &*ident.to_string() { + "BoolValue" => Ok(SType::SBoolean.into()), + "IntValue" => Ok(SType::SInt.into()), + "ShortValue" => Ok(SType::SShort.into()), + "LongValue" => Ok(SType::SLong.into()), + "BigIntValue" => Ok(SType::SBigInt.into()), + "ByteValue" => Ok(SType::SByte.into()), + "SigmaPropValue" => Ok(SType::SSigmaProp.into()), + "STuple" => Ok(ExtractedType::STuple), + "SByte" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SByte)) + } + "SGroupElement" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SGroupElement)) + } + "SInt" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SInt)) + } + "SLong" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SLong)) + } + "SBigInt" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SBigInt)) + } + "SBoolean" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SBoolean)) + } + "SAvlTree" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SAvlTree)) + } + "SBox" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SBox)) + } + "SSigmaProp" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SSigmaProp)) + } + "SHeader" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SHeader)) + } + "SOption" => { + let content_nested; + let _bracketed = syn::bracketed!(content_nested in buf); + Ok(ExtractedType::SOption(Box::new( + extract_tpe_from_dot_typed(&content_nested)?, + ))) + } + "SCollection" => { + let content_nested; + let _bracketed = syn::bracketed!(content_nested in buf); + + //let _ident: syn::Ident = content_nested.parse()?; + //handle_dot_type(&content_nested)?; + Ok(ExtractedType::SCollection(Box::new( + extract_tpe_from_dot_typed(&content_nested)?, + ))) + } + "Value" => { + let content; + let _bracketed = syn::bracketed!(content in buf); + let next_ident: syn::Ident = content.parse()?; + match &*next_ident.to_string() { + "STuple" => Ok(ExtractedType::STuple), + "SByte" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SByte)) + } + "SGroupElement" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SGroupElement)) + } + "SInt" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SInt)) + } + "SLong" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SLong)) + } + "SBigInt" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SBigInt)) + } + "SBoolean" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SBoolean)) + } + "SAvlTree" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SAvlTree)) + } + "SBox" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SBox)) + } + "SSigmaProp" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SSigmaProp)) + } + "SHeader" => { + handle_dot_type(buf)?; + Ok(ExtractedType::FullySpecified(SType::SHeader)) + } + "SOption" => { + let content_nested; + let _bracketed = syn::bracketed!(content_nested in content); + Ok(ExtractedType::SOption(Box::new( + extract_tpe_from_dot_typed(&content_nested)?, + ))) + } + "SCollection" => { + let content_nested; + let _bracketed = syn::bracketed!(content_nested in content); + + Ok(ExtractedType::SCollection(Box::new( + extract_tpe_from_dot_typed(&content_nested)?, + ))) + } + t => { + unreachable!("unknown ident T in _.typed[Value[T]]: T = {}", t) + } + } + } + t => unreachable!("unknown ident T in _.typed[T]: T = {}", t), + } +} + +//pub fn extract_tpe_from_dot_typed( +// buf: syn::parse::ParseStream, +//) -> Result { +// let ident: syn::Ident = buf.parse()?; +// match &*ident.to_string() { +// "BoolValue" => Ok(SType::SBoolean.into()), +// "IntValue" => Ok(SType::SInt.into()), +// "ShortValue" => Ok(SType::SShort.into()), +// "LongValue" => Ok(SType::SLong.into()), +// "BigIntValue" => Ok(SType::SBigInt.into()), +// "ByteValue" => Ok(SType::SByte.into()), +// "SigmaPropValue" => Ok(SType::SSigmaProp.into()), +// "SByte" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SByte)) +// } +// "Value" => { +// let content; +// let _bracketed = syn::bracketed!(content in buf); +// let next_ident: syn::Ident = content.parse()?; +// match &*next_ident.to_string() { +// "STuple" => Ok(ExtractedType::STuple), +// "SByte" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SByte)) +// } +// "SGroupElement" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SGroupElement)) +// } +// "SInt" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SInt)) +// } +// "SLong" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SLong)) +// } +// "SBigInt" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SBigInt)) +// } +// "SBoolean" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SBoolean)) +// } +// "SAvlTree" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SAvlTree)) +// } +// "SBox" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SBox)) +// } +// "SSigmaProp" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SSigmaProp)) +// } +// "SHeader" => { +// handle_dot_type(buf)?; +// Ok(ExtractedType::FullySpecified(SType::SHeader)) +// } +// "SOption" => { +// let content_nested; +// let _bracketed = syn::bracketed!(content_nested in content); +// Ok(ExtractedType::SOption(Box::new( +// extract_tpe_from_dot_typed(&content_nested)?, +// ))) +// } +// "SCollection" => { +// let content_nested; +// let _bracketed = syn::bracketed!(content_nested in content); +// +// //let _ident: syn::Ident = content_nested.parse()?; +// //handle_dot_type(&content_nested)?; +// Ok(ExtractedType::SCollection(Box::new( +// extract_tpe_from_dot_typed(&content_nested)?, +// ))) +// } +// t => { +// unreachable!("unknown ident T in _.typed[Value[T]]: T = {}", t) +// } +// } +// } +// t => unreachable!("unknown ident T in _.typed[T]: T = {}", t), +// } +//} + +/// Parses `.type` from the buffered token stream +pub fn handle_dot_type(buf: syn::parse::ParseStream) -> Result<(), syn::Error> { + let _dot: syn::Token![.] = buf.parse()?; + let ident: syn::Ident = buf.call(Ident::parse_any)?; //buf.parse()?; + if ident != "type" { + return Err(syn::Error::new_spanned(ident, "")); + } + Ok(()) +} diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs index a4607f643..5c2aa07b1 100644 --- a/ergotree-ir/src/lib.rs +++ b/ergotree-ir/src/lib.rs @@ -14,7 +14,7 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(clippy::unwrap_used)] #![deny(clippy::expect_used)] -#![deny(clippy::todo)] +//#![deny(clippy::todo)] #![deny(clippy::unimplemented)] #![deny(clippy::panic)] @@ -30,3 +30,6 @@ pub mod sigma_protocol; pub mod type_check; pub mod types; pub mod util; + +#[cfg(feature = "ergotree-proc-macro")] +pub mod ergotree_proc_macro; diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs index 102cf9b25..8184063cd 100644 --- a/ergotree-ir/src/mir/bin_op.rs +++ b/ergotree-ir/src/mir/bin_op.rs @@ -31,6 +31,22 @@ pub enum ArithOp { Modulo, } +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for ArithOp { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + tokens.extend(match self { + ArithOp::Plus => quote! { ergotree_ir::mir::bin_op::ArithOp::Plus }, + ArithOp::Minus => quote! { ergotree_ir::mir::bin_op::ArithOp::Minus }, + ArithOp::Multiply => quote! { ergotree_ir::mir::bin_op::ArithOp::Multiply }, + ArithOp::Divide => quote! { ergotree_ir::mir::bin_op::ArithOp::Divide }, + ArithOp::Max => quote! { ergotree_ir::mir::bin_op::ArithOp::Max }, + ArithOp::Min => quote! { ergotree_ir::mir::bin_op::ArithOp::Min }, + ArithOp::Modulo => quote! { ergotree_ir::mir::bin_op::ArithOp::Modulo }, + }); + } +} + impl From for OpCode { fn from(op: ArithOp) -> Self { match op { @@ -63,6 +79,21 @@ pub enum RelationOp { Lt, } +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for RelationOp { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + tokens.extend(match self { + RelationOp::Eq => quote! { ergotree_ir::mir::bin_op::RelationOp::Eq }, + RelationOp::NEq => quote! { ergotree_ir::mir::bin_op::RelationOp::NEq }, + RelationOp::Ge => quote! { ergotree_ir::mir::bin_op::RelationOp::Ge }, + RelationOp::Gt => quote! { ergotree_ir::mir::bin_op::RelationOp::Gt }, + RelationOp::Le => quote! { ergotree_ir::mir::bin_op::RelationOp::Le }, + RelationOp::Lt => quote! { ergotree_ir::mir::bin_op::RelationOp::Lt }, + }) + } +} + impl From for OpCode { fn from(op: RelationOp) -> Self { match op { @@ -98,6 +129,17 @@ impl From for OpCode { } } +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for LogicalOp { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + tokens.extend(match self { + LogicalOp::And => quote! { ergotree_ir::mir::bin_op::LogicalOp::And }, + LogicalOp::Or => quote! { ergotree_ir::mir::bin_op::LogicalOp::Or }, + LogicalOp::Xor => quote! { ergotree_ir::mir::bin_op::LogicalOp::Xor }, + }); + } +} /// Bitwise operations #[derive(PartialEq, Eq, Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -120,6 +162,18 @@ impl From for OpCode { } } +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for BitOp { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + tokens.extend(match self { + BitOp::BitOr => quote! { ergotree_ir::mir::bin_op::BitOp::BitOr }, + BitOp::BitAnd => quote! { ergotree_ir::mir::bin_op::BitOp::BitAnd}, + BitOp::BitXor => quote! { ergotree_ir::mir::bin_op::BitOp::BitXor}, + }); + } +} + /// Binary operations #[derive(PartialEq, Eq, Debug, Clone, Copy, From)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -174,6 +228,157 @@ impl HasOpCode for BinOp { } } +#[cfg(feature = "ergotree-proc-macro")] +/// Given name of a binary op, parse an instance of `BinOp` +pub fn parse_bin_op(op_name: &syn::Ident, input: syn::parse::ParseStream) -> syn::Result { + let left: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let right: Box = input.parse()?; + match op_name.to_string().as_str() { + "ArithOp" => { + let _comma: syn::Token![,] = input.parse()?; + let kind = extract_arithmetic_bin_op_kind(input)?; + Ok(BinOp { kind, left, right }) + } + "EQ" => { + let kind = BinOpKind::Relation(RelationOp::Eq); + Ok(BinOp { kind, left, right }) + } + "NEQ" => { + let kind = BinOpKind::Relation(RelationOp::NEq); + Ok(BinOp { kind, left, right }) + } + "GE" => { + let kind = BinOpKind::Relation(RelationOp::Ge); + Ok(BinOp { kind, left, right }) + } + "LE" => { + let kind = BinOpKind::Relation(RelationOp::Le); + Ok(BinOp { kind, left, right }) + } + "GT" => { + let kind = BinOpKind::Relation(RelationOp::Gt); + Ok(BinOp { kind, left, right }) + } + "LT" => { + let kind = BinOpKind::Relation(RelationOp::Lt); + Ok(BinOp { kind, left, right }) + } + "BinAnd" => { + let kind = BinOpKind::Logical(LogicalOp::And); + Ok(BinOp { kind, left, right }) + } + "BinOr" => { + let kind = BinOpKind::Logical(LogicalOp::Or); + Ok(BinOp { kind, left, right }) + } + "BinXor" => { + let kind = BinOpKind::Logical(LogicalOp::Xor); + Ok(BinOp { kind, left, right }) + } + "BitAnd" => { + let kind = BinOpKind::Bit(BitOp::BitAnd); + Ok(BinOp { kind, left, right }) + } + "BitOr" => { + let kind = BinOpKind::Bit(BitOp::BitOr); + Ok(BinOp { kind, left, right }) + } + "BitXor" => { + let kind = BinOpKind::Bit(BitOp::BitXor); + Ok(BinOp { kind, left, right }) + } + _ => Err(syn::Error::new_spanned( + op_name.clone(), + "Unknown `BinOp` variant name", + )), + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for BinOp { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + let left = *self.left.clone(); + let right = *self.right.clone(); + tokens.extend(match self.kind { + BinOpKind::Arith(a) => { + quote! { + ergotree_ir::mir::bin_op::BinOp { + left: Box::new(#left), + right: Box::new(#right), + kind: ergotree_ir::mir::bin_op::BinOpKind::Arith(#a), + } + } + } + BinOpKind::Relation(r) => { + quote! { + ergotree_ir::mir::bin_op::BinOp { + left: Box::new(#left), + right: Box::new(#right), + kind: ergotree_ir::mir::bin_op::BinOpKind::Relation(#r), + } + } + } + BinOpKind::Logical(l) => { + quote! { + ergotree_ir::mir::bin_op::BinOp { + left: Box::new(#left), + right: Box::new(#right), + kind: ergotree_ir::mir::bin_op::BinOpKind::Logical(#l), + } + } + } + BinOpKind::Bit(b) => { + quote! { + ergotree_ir::mir::bin_op::BinOp { + left: Box::new(#left), + right: Box::new(#right), + kind: ergotree_ir::mir::bin_op::BinOpKind::Bit(#b), + } + } + } + }); + } +} + +#[cfg(feature = "ergotree-proc-macro")] +/// Converts `OpCode @@ x` into an instance of `BinOpKind::Arith`. +fn extract_arithmetic_bin_op_kind(buf: syn::parse::ParseStream) -> Result { + let ident: syn::Ident = buf.parse()?; + if ident == "OpCode" { + let _at: syn::Token![@] = buf.parse()?; + let _at: syn::Token![@] = buf.parse()?; + let content; + let _paren = syn::parenthesized!(content in buf); + let id: syn::LitInt = content.parse()?; + let scala_op_code = id.base10_parse::()?; + let _dot: syn::Token![.] = content.parse()?; + let as_byte_ident: syn::Ident = content.parse()?; + if as_byte_ident != "toByte" { + return Err(syn::Error::new_spanned( + as_byte_ident.clone(), + format!("Expected `asByte` Ident, got {}", as_byte_ident), + )); + } + match OpCode::parse(scala_op_code as u8) { + OpCode::PLUS => Ok(ArithOp::Plus.into()), + OpCode::MINUS => Ok(ArithOp::Minus.into()), + OpCode::MULTIPLY => Ok(ArithOp::Multiply.into()), + OpCode::DIVISION => Ok(ArithOp::Divide.into()), + OpCode::MAX => Ok(ArithOp::Max.into()), + OpCode::MIN => Ok(ArithOp::Min.into()), + OpCode::MODULO => Ok(ArithOp::Modulo.into()), + _ => Err(syn::Error::new_spanned(ident, "Expected arithmetic opcode")), + } + } else { + Err(syn::Error::new_spanned( + ident.clone(), + format!("Expected `OpCode` ident, got {} ", ident), + )) + } +} + #[cfg(feature = "arbitrary")] /// Arbitrary impl mod arbitrary { diff --git a/ergotree-ir/src/mir/block.rs b/ergotree-ir/src/mir/block.rs index e7870f8a6..6ddae22b1 100644 --- a/ergotree-ir/src/mir/block.rs +++ b/ergotree-ir/src/mir/block.rs @@ -53,6 +53,51 @@ impl SigmaSerializable for BlockValue { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for BlockValue { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut items = vec![]; + let ident: syn::Ident = input.parse()?; + if ident != "Vector" && ident != "Array" { + return Err(syn::Error::new_spanned( + ident.clone(), + format!( + "BlockValue(..): expected `Vector` or `Array` Ident, got {:?}", + ident + ), + )); + } + let content; + let _paren = syn::parenthesized!(content in input); + + loop { + let expr: Expr = content.parse()?; + items.push(expr); + if !content.peek(syn::Token![,]) { + break; + } + let _comma: syn::Token![,] = content.parse()?; + } + let _comma: syn::Token![,] = input.parse()?; + let result = input.parse()?; + Ok(BlockValue { items, result }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for BlockValue { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let items = self.items.clone(); + let result = *self.result.clone(); + tokens.extend(quote::quote! { + ergotree_ir::mir::block::BlockValue { + items: vec![#( #items),*], + result: Box::new(#result), + } + }) + } +} + /// Arbitrary impl #[cfg(feature = "arbitrary")] mod arbitrary { diff --git a/ergotree-ir/src/mir/bool_to_sigma.rs b/ergotree-ir/src/mir/bool_to_sigma.rs index 851e85008..74e2a43ae 100644 --- a/ergotree-ir/src/mir/bool_to_sigma.rs +++ b/ergotree-ir/src/mir/bool_to_sigma.rs @@ -44,6 +44,28 @@ impl OneArgOpTryBuild for BoolToSigmaProp { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for BoolToSigmaProp { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + let _paren = syn::parenthesized!(content in input); + let input = content.parse()?; + Ok(Self { + input: Box::new(input), + }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for BoolToSigmaProp { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let input = &*self.input; + tokens.extend(quote::quote! { + ergotree_ir::mir::bool_to_sigma::BoolToSigmaProp { input: Box::new(#input) } + }) + } +} + /// Arbitrary impl #[cfg(feature = "arbitrary")] mod arbitrary { diff --git a/ergotree-ir/src/mir/coll_append.rs b/ergotree-ir/src/mir/coll_append.rs index 390ca5373..2b2754793 100644 --- a/ergotree-ir/src/mir/coll_append.rs +++ b/ergotree-ir/src/mir/coll_append.rs @@ -75,6 +75,34 @@ impl SigmaSerializable for Append { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for Append { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name: syn::Ident = input.parse()?; + if name == "Append" { + let content; + let _paren = syn::parenthesized!(content in input); + let input = Box::new(content.parse()?); + let col_2 = Box::new(content.parse()?); + + Ok(Append { input, col_2 }) + } else { + Err(syn::Error::new_spanned(name, "Expected `Append`")) + } + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for Append { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let input = &*self.input; + let col_2 = &*self.col_2; + tokens.extend(quote::quote! { + ergotree_ir::mir::coll_append::Append { input: Box::new(#input), col_2: Box::new(#col_2) } + }) + } +} + #[cfg(test)] #[cfg(feature = "arbitrary")] #[allow(clippy::panic)] diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs index 58e9f21d2..301732105 100644 --- a/ergotree-ir/src/mir/constant.rs +++ b/ergotree-ir/src/mir/constant.rs @@ -829,6 +829,90 @@ impl TryFrom for Constant { } } +#[cfg(feature = "ergotree-proc-macro")] +/// Given name of a constant, parse an instance of `Constant` +pub fn parse_constant(name: &syn::Ident, input: syn::parse::ParseStream) -> syn::Result { + match name.to_string().as_str() { + "IntConstant" => { + let c: syn::LitInt = input.parse()?; + let int_const = c.base10_parse::()?; + Ok(int_const.into()) + } + "ByteConstant" => { + let c: syn::LitInt = input.parse()?; + let byte_const = c.base10_parse::()?; + let _dot: syn::Token![.] = input.parse()?; + let to_byte_ident: syn::Ident = input.parse()?; + if to_byte_ident != "toByte" { + return Err(syn::Error::new_spanned( + to_byte_ident.clone(), + format!("Expected `toByte` Ident, got {}", to_byte_ident), + )); + } + Ok(byte_const.into()) + } + "ShortConstant" => { + let c: syn::LitInt = input.parse()?; + let short_const = c.base10_parse::()?; + if input.peek(syn::Token![.]) { + let _dot: syn::Token![.] = input.parse()?; + let to_short_ident: syn::Ident = input.parse()?; + if to_short_ident != "toShort" { + return Err(syn::Error::new_spanned( + to_short_ident.clone(), + format!("Expected `toShort` Ident, got {}", to_short_ident), + )); + } + } + Ok(short_const.into()) + } + "LongConstant" => { + let c: syn::LitInt = input.parse()?; + let long_const = c.base10_parse::()?; + if input.peek(syn::Token![.]) { + let _dot: syn::Token![.] = input.parse()?; + let to_long_ident: syn::Ident = input.parse()?; + if to_long_ident != "toLong" { + return Err(syn::Error::new_spanned( + to_long_ident.clone(), + format!("Expected `toLong` Ident, got {}", to_long_ident), + )); + } + } else if c.suffix() != "L" { + return Err(syn::Error::new_spanned(c, "Expected `L` suffix")); + } + Ok(long_const.into()) + } + "BigIntConstant" => { + let c: syn::LitInt = input.parse()?; + let long_const = c.base10_parse::()?; + if c.suffix() != "L" { + return Err(syn::Error::new_spanned(c, "Expected `L` suffix")); + } + Ok(BigInt256::from(long_const).into()) + } + _ => todo!(), + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for Constant { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + tokens.extend(match self.v { + Literal::Int(i) => quote! { ergotree_ir::mir::constant::Constant::from(#i) }, + Literal::Long(l) => quote! { ergotree_ir::mir::constant::Constant::from(#l) }, + Literal::Byte(b) => quote! { ergotree_ir::mir::constant::Constant::from(#b) }, + Literal::Short(s) => quote! { ergotree_ir::mir::constant::Constant::from(#s) }, + Literal::BigInt(ref b) => { + let string_rep = b.to_str_radix(10); + quote! { ergotree_ir::mir::constant::Constant::from(ergotree_ir::bigint256::BigInt256::from_str_radix(#string_rep, 10).unwrap()) } + } + _ => todo!(), + }); + } +} + #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] #[allow(clippy::todo)] diff --git a/ergotree-ir/src/mir/downcast.rs b/ergotree-ir/src/mir/downcast.rs index f3f3ec582..488d40038 100644 --- a/ergotree-ir/src/mir/downcast.rs +++ b/ergotree-ir/src/mir/downcast.rs @@ -67,6 +67,28 @@ impl SigmaSerializable for Downcast { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for Downcast { + fn parse(buf: syn::parse::ParseStream) -> syn::Result { + let input: Box = buf.parse()?; + let _comma: syn::Token![,] = buf.parse()?; + let tpe: SType = buf.parse()?; + Ok(Self { input, tpe }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for Downcast { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let input = *self.input.clone(); + let tpe = self.tpe.clone(); + tokens.extend(quote::quote! { ergotree_ir::mir::downcast::Downcast{ + input: Box::new(#input), + tpe: #tpe, + }}) + } +} + #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] /// Arbitrary impl diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 85f84cd06..14da722a3 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -370,6 +370,180 @@ impl> TryExtractFrom for T { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for Expr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name: syn::Ident = input.parse()?; + match name.to_string().as_str() { + "FuncValue" => { + let content; + let _paren = syn::parenthesized!(content in input); + + Ok(Expr::FuncValue(content.parse()?)) + } + "Tuple" => { + let content; + let _paren = syn::parenthesized!(content in input); + + Ok(Expr::Tuple(content.parse()?)) + } + "BlockValue" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::BlockValue(content.parse()?)) + } + "BoolToSigmaProp" => Ok(Expr::BoolToSigmaProp(input.parse()?)), + "ValUse" => Ok(Expr::ValUse(input.parse()?)), + "SelectField" => Ok(Expr::SelectField(input.parse()?)), + "ArithOp" | "EQ" | "NEQ" | "GE" | "LE" | "GT" | "LT" | "BinAnd" | "BinOr" + | "BinXor" | "BitAnd" | "BitOr" | "BitXor" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::BinOp(super::bin_op::parse_bin_op(&name, &content)?)) + } + "IntConstant" | "LongConstant" | "ByteConstant" | "ShortConstant" + | "BigIntConstant" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::Const(super::constant::parse_constant( + &name, &content, + )?)) + } + "ValDef" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::ValDef(content.parse()?)) + } + "LogicalNot" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::LogicalNot(content.parse()?)) + } + "Downcast" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::Downcast(content.parse()?)) + } + "Upcast" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::Upcast(content.parse()?)) + } + "MethodCall" => Ok(Expr::MethodCall(input.parse()?)), + "ExtractScriptBytes" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::ExtractScriptBytes(content.parse()?)) + } + _ => Err(syn::Error::new_spanned(name, "Unknown `Expr` variant name")), + } + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for Expr { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + tokens.extend(match self { + Expr::Append(a) => { + quote! { ergotree_ir::mir::expr::Expr::Append { #a } } + } + + Expr::Const(c) => quote! { ergotree_ir::mir::expr::Expr::Const(#c) }, + Expr::ConstPlaceholder(_) => todo!(), + Expr::SubstConstants(_) => todo!(), + Expr::ByteArrayToLong(_) => todo!(), + Expr::ByteArrayToBigInt(_) => todo!(), + Expr::LongToByteArray(_) => todo!(), + Expr::Collection(_) => todo!(), + Expr::Tuple(t) => { + quote! { ergotree_ir::mir::expr::Expr::Tuple(#t) } + } + Expr::CalcBlake2b256(_) => todo!(), + Expr::CalcSha256(_) => todo!(), + Expr::Context => todo!(), + Expr::Global => todo!(), + Expr::GlobalVars(_) => todo!(), + Expr::FuncValue(f) => { + quote! { ergotree_ir::mir::expr::Expr::FuncValue(#f) } + } + Expr::Apply(_) => todo!(), + Expr::MethodCall(m) => { + quote! { ergotree_ir::mir::expr::Expr::MethodCall(#m) } + } + Expr::ProperyCall(_) => todo!(), + Expr::BlockValue(b) => { + quote! { ergotree_ir::mir::expr::Expr::BlockValue(#b) } + } + Expr::ValDef(v) => { + quote! { ergotree_ir::mir::expr::Expr::ValDef(#v) } + } + Expr::ValUse(v) => { + quote! { ergotree_ir::mir::expr::Expr::ValUse(#v) } + } + Expr::If(_) => todo!(), + Expr::BinOp(b) => { + quote! { ergotree_ir::mir::expr::Expr::BinOp(#b) } + } + Expr::And(_) => todo!(), + Expr::Or(_) => todo!(), + Expr::Xor(_) => todo!(), + Expr::Atleast(_) => todo!(), + Expr::LogicalNot(l) => { + quote! { ergotree_ir::mir::expr::Expr::LogicalNot(#l) } + } + Expr::Negation(_) => todo!(), + Expr::BitInversion(_) => todo!(), + Expr::OptionGet(_) => todo!(), + Expr::OptionIsDefined(_) => todo!(), + Expr::OptionGetOrElse(_) => todo!(), + Expr::ExtractAmount(_) => todo!(), + Expr::ExtractRegisterAs(_) => todo!(), + Expr::ExtractBytes(_) => todo!(), + Expr::ExtractBytesWithNoRef(_) => todo!(), + Expr::ExtractScriptBytes(e) => { + quote! { ergotree_ir::mir::expr::Expr::ExtractScriptBytes(#e) } + } + Expr::ExtractCreationInfo(_) => todo!(), + Expr::ExtractId(_) => todo!(), + Expr::ByIndex(_) => todo!(), + Expr::SizeOf(_) => todo!(), + Expr::Slice(_) => todo!(), + Expr::Fold(_) => todo!(), + Expr::Map(_) => todo!(), + Expr::Filter(_) => todo!(), + Expr::Exists(_) => todo!(), + Expr::ForAll(_) => todo!(), + Expr::SelectField(s) => { + quote! { ergotree_ir::mir::expr::Expr::SelectField(#s) } + } + Expr::BoolToSigmaProp(b) => { + quote! { ergotree_ir::mir::expr::Expr::BoolToSigmaProp(#b) } + } + Expr::Upcast(u) => { + quote! { ergotree_ir::mir::expr::Expr::Upcast(#u) } + } + Expr::Downcast(d) => { + quote! { ergotree_ir::mir::expr::Expr::Downcast(#d) } + } + Expr::CreateProveDlog(_) => todo!(), + Expr::CreateProveDhTuple(_) => todo!(), + Expr::SigmaPropBytes(_) => todo!(), + Expr::DecodePoint(_) => todo!(), + Expr::SigmaAnd(_) => todo!(), + Expr::SigmaOr(_) => todo!(), + Expr::GetVar(_) => todo!(), + Expr::DeserializeRegister(_) => todo!(), + Expr::DeserializeContext(_) => todo!(), + Expr::MultiplyGroup(_) => todo!(), + Expr::Exponentiate(_) => todo!(), + Expr::XorOf(_) => todo!(), + Expr::TreeLookup(_) => todo!(), + Expr::CreateAvlTree(_) => todo!(), + }); + } +} + #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] #[allow(clippy::panic)] diff --git a/ergotree-ir/src/mir/extract_script_bytes.rs b/ergotree-ir/src/mir/extract_script_bytes.rs index 5912c2def..f90ce19e4 100644 --- a/ergotree-ir/src/mir/extract_script_bytes.rs +++ b/ergotree-ir/src/mir/extract_script_bytes.rs @@ -40,6 +40,26 @@ impl OneArgOpTryBuild for ExtractScriptBytes { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for ExtractScriptBytes { + fn parse(buf: syn::parse::ParseStream) -> syn::Result { + let input: Box = buf.parse()?; + Ok(Self { input }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for ExtractScriptBytes { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let input = *self.input.clone(); + tokens.extend( + quote::quote! { ergotree_ir::mir::extract_script_bytes::ExtractScriptBytes{ + input: Box::new(#input), + }}, + ) + } +} + #[cfg(test)] #[cfg(feature = "arbitrary")] mod tests { diff --git a/ergotree-ir/src/mir/func_value.rs b/ergotree-ir/src/mir/func_value.rs index e313a35e0..a97273c4b 100644 --- a/ergotree-ir/src/mir/func_value.rs +++ b/ergotree-ir/src/mir/func_value.rs @@ -24,6 +24,34 @@ pub struct FuncArg { pub tpe: SType, } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for FuncArg { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + let _paren = syn::parenthesized!(content in input); + let id: syn::LitInt = content.parse()?; + let value = id.base10_parse::()?; + let idx = ValId(value); + let _comma: syn::Token![,] = content.parse()?; + let tpe = content.parse()?; + Ok(FuncArg { idx, tpe }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for FuncArg { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let idx = &self.idx; + let tpe = &self.tpe; + tokens.extend(quote::quote! { + ergotree_ir::mir::func_value::FuncArg { + idx: #idx, + tpe: #tpe, + } + }) + } +} + impl SigmaSerializable for FuncArg { fn sigma_serialize(&self, w: &mut W) -> SigmaSerializeResult { self.idx.sigma_serialize(w)?; @@ -97,6 +125,42 @@ impl SigmaSerializable for FuncValue { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for FuncValue { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let args = { + let name: syn::Ident = input.parse()?; + if name == "Vector" { + let content; + let _paren = syn::parenthesized!(content in input); + let punctuated: syn::punctuated::Punctuated = + content.parse_terminated(FuncArg::parse)?; + punctuated.into_iter().collect() + } else { + return Err(syn::Error::new_spanned(name, "Expected `Vector`")); + } + }; + let _comma: syn::Token![,] = input.parse()?; + let body: Expr = input.parse()?; + Ok(FuncValue::new(args, body)) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for FuncValue { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let args = &self.args; + let body = &*self.body; + //let tpe = &self.tpe; + tokens.extend( + quote::quote! { ergotree_ir::mir::func_value::FuncValue::new( + vec![#( #args),*], + #body, + )}, + ) + } +} + #[cfg(test)] #[cfg(feature = "arbitrary")] #[allow(clippy::panic)] diff --git a/ergotree-ir/src/mir/logical_not.rs b/ergotree-ir/src/mir/logical_not.rs index 7418d8d95..a7898a9c7 100644 --- a/ergotree-ir/src/mir/logical_not.rs +++ b/ergotree-ir/src/mir/logical_not.rs @@ -39,6 +39,24 @@ impl OneArgOpTryBuild for LogicalNot { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for LogicalNot { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let input: Box = input.parse()?; + Ok(Self { input }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for LogicalNot { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let input = *self.input.clone(); + tokens.extend(quote::quote! { ergotree_ir::mir::logical_not::LogicalNot{ + input: Box::new(#input), + }}) + } +} + #[cfg(feature = "arbitrary")] /// Arbitrary impl mod arbitrary { diff --git a/ergotree-ir/src/mir/method_call.rs b/ergotree-ir/src/mir/method_call.rs index f9a8787e0..73f477226 100644 --- a/ergotree-ir/src/mir/method_call.rs +++ b/ergotree-ir/src/mir/method_call.rs @@ -66,3 +66,189 @@ impl MethodCall { impl HasStaticOpCode for MethodCall { const OP_CODE: OpCode = OpCode::METHOD_CALL; } + +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for MethodCall { + fn parse(buf: syn::parse::ParseStream) -> syn::Result { + use crate::ergotree_proc_macro::extract_tpe_from_dot_typed; + let _extracted_type = if buf.peek(syn::Token![.]) { + let _dot: syn::Token![.] = buf.parse()?; + let name: syn::Ident = buf.parse()?; + if name != "typed" { + return Err(syn::Error::new_spanned( + name.clone(), + format!("Expected `typed` keyword, got {}", name), + )); + } + let content; + let _bracketed = syn::bracketed!(content in buf); + Some(extract_tpe_from_dot_typed(&content)?) + } else { + None + }; + + let content; + let _paren = syn::parenthesized!(content in buf); + let obj: Box = content.parse()?; + let _comma: syn::Token![,] = content.parse()?; + let method = extract_smethod(&content)?; + let _comma: syn::Token![,] = content.parse()?; + + // Extract method args + let vector: syn::Ident = content.parse()?; + if vector != "Vector" { + return Err(syn::Error::new_spanned( + vector.clone(), + format!("Expected `Vector` keyword, got {}", vector), + )); + } + let content_nested; + let _paren = syn::parenthesized!(content_nested in content); + let mut args = vec![]; + + while let Ok(arg) = content_nested.parse::() { + args.push(arg); + if content_nested.peek(syn::Token![,]) { + let _comma: syn::Token![,] = content_nested.parse()?; + } + } + let _comma: syn::Token![,] = content.parse()?; + let map_ident: syn::Ident = content.parse()?; + if map_ident != "Map" { + return Err(syn::Error::new_spanned( + map_ident.clone(), + format!("Expected `Map` keyword, got {}", map_ident), + )); + } + let _content_nested; + let _paren = syn::parenthesized!(_content_nested in content); + Ok(Self { obj, method, args }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for MethodCall { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let obj = *self.obj.clone(); + let method = self.method.clone(); + let args = self.args.clone(); + tokens.extend( + quote::quote! { ergotree_ir::mir::method_call::MethodCall::new(#obj, #method, vec![#(#args),*]).unwrap()} + ); + } +} + +#[cfg(feature = "ergotree-proc-macro")] +fn extract_smethod(buf: syn::parse::ParseStream) -> syn::Result { + use crate::types::{scoll, sgroup_elem}; + + let object_type: syn::Ident = buf.parse()?; + match object_type.to_string().as_str() { + "SCollection" => match extract_method_name(buf)?.as_str() { + "flatMap" => { + let subst = extract_concrete_types(buf)?; + Ok(scoll::FLATMAP_METHOD.clone().with_concrete_types(&subst)) + } + "indices" => { + let subst = extract_concrete_types(buf)?; + Ok(scoll::INDICES_METHOD.clone().with_concrete_types(&subst)) + } + "zip" => { + let subst = extract_concrete_types(buf)?; + Ok(scoll::ZIP_METHOD.clone().with_concrete_types(&subst)) + } + _ => todo!(), + }, + "SGroupElement" => match extract_method_name(buf)?.as_str() { + "getEncoded" => Ok(sgroup_elem::GET_ENCODED_METHOD.clone()), + _ => todo!(), + }, + _ => Err(syn::Error::new_spanned( + object_type.clone(), + format!("Unknown `object_type`, got {}", object_type), + )), + } +} + +#[cfg(feature = "ergotree-proc-macro")] +fn extract_method_name(buf: syn::parse::ParseStream) -> syn::Result { + let _dot: syn::Token![.] = buf.parse()?; + let ident: syn::Ident = buf.parse()?; + if ident == "getMethodByName" { + let content; + let _paren = syn::parenthesized!(content in buf); + let method_name: syn::LitStr = content.parse()?; + Ok(method_name.value()) + } else { + Err(syn::Error::new_spanned( + ident.clone(), + format!("Expected `getMethodByName`, got {}", ident), + )) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +fn extract_concrete_types( + buf: syn::parse::ParseStream, +) -> syn::Result> { + use crate::types::stype_param::STypeVar; + + let _dot: syn::Token![.] = buf.parse()?; + let with_concrete_types_ident: syn::Ident = buf.parse()?; + if with_concrete_types_ident != "withConcreteTypes" { + return Err(syn::Error::new_spanned( + with_concrete_types_ident.clone(), + format!( + "Expected `withConcreteTypes` keyword, got {}", + with_concrete_types_ident + ), + )); + } + let content; + let _paren = syn::parenthesized!(content in buf); + let mut res = std::collections::HashMap::new(); + let map_ident: syn::Ident = content.parse()?; + if map_ident == "Map" { + let content_nested; + let _paren = syn::parenthesized!(content_nested in content); + loop { + let s_type_var: syn::Ident = content_nested.parse()?; + if s_type_var != "STypeVar" { + return Err(syn::Error::new_spanned( + s_type_var.clone(), + format!("Expected `STypeVar` Ident, got {}", s_type_var), + )); + } + let content_nested1; + let _paren = syn::parenthesized!(content_nested1 in content_nested); + let type_var_lit: syn::LitStr = content_nested1.parse()?; + let type_var = match type_var_lit.value().as_str() { + "T" => STypeVar::t(), + "IV" => STypeVar::iv(), + "OV" => STypeVar::ov(), + _ => { + return Err(syn::Error::new_spanned( + type_var_lit.clone(), + format!( + "Unknown type variable for `STypeVar`, got {:?}", + type_var_lit + ), + )); + } + }; + let _arrow: syn::Token![->] = content_nested.parse()?; + let stype: SType = content_nested.parse()?; + res.insert(type_var, stype); + if !content_nested.peek(syn::Token![,]) { + break; + } + let _comma: syn::Token![,] = content_nested.parse()?; + } + Ok(res) + } else { + Err(syn::Error::new_spanned( + map_ident.clone(), + format!("Expected `Map` Ident, got {}", map_ident), + )) + } +} diff --git a/ergotree-ir/src/mir/select_field.rs b/ergotree-ir/src/mir/select_field.rs index c84dc42b1..a2fa4beee 100644 --- a/ergotree-ir/src/mir/select_field.rs +++ b/ergotree-ir/src/mir/select_field.rs @@ -1,5 +1,7 @@ use std::convert::TryFrom; +#[cfg(feature = "ergotree-proc-macro")] +use crate::ergotree_proc_macro::extract_tpe_from_dot_typed; use crate::serialization::op_code::OpCode; use crate::serialization::sigma_byte_reader::SigmaByteRead; use crate::serialization::sigma_byte_writer::SigmaByteWrite; @@ -57,6 +59,23 @@ impl SigmaSerializable for TupleFieldIndex { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for TupleFieldIndex { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let id: syn::LitInt = input.parse()?; + let value = id.base10_parse::()?; + Ok(Self(value)) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for TupleFieldIndex { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let field_index = self.0; + tokens.extend(quote::quote! { ergotree_ir::mir::select_field::TupleFieldIndex::try_from(#field_index).unwrap() }) + } +} + /// Select a field of the tuple value #[derive(PartialEq, Eq, Debug, Clone)] pub struct SelectField { @@ -119,6 +138,101 @@ impl SigmaSerializable for SelectField { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for SelectField { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let _dot: syn::Token![.] = input.parse()?; + let name: syn::Ident = input.parse()?; + if name == "typed" { + let mut content; + let _bracketed = syn::bracketed!(content in input); + let extracted_type = extract_tpe_from_dot_typed(&content)?; + let _paren = syn::parenthesized!(content in input); + let input: Expr = content.parse()?; + let _comma: syn::Token![,] = content.parse()?; + let id: syn::LitInt = content.parse()?; + let value = id.base10_parse::()?; + let field_index = TupleFieldIndex::try_from(value).map_err(|_| { + syn::Error::new_spanned(name.clone(), "Expected `field_index` >= 1") + })?; + let _dot: syn::Token![.] = content.parse()?; + let _to_byte_ident: syn::Ident = content.parse()?; + let sf = Self::new(input, field_index).map_err(|e| { + syn::Error::new_spanned(name.clone(), format!("SelectField::new error: {}", e)) + })?; + match extracted_type { + crate::ergotree_proc_macro::ExtractedType::FullySpecified(field_tpe) => { + if sf.field_tpe != field_tpe { + Err(syn::Error::new_spanned( + name, + format!( + "Expected tuple field of type {:?}, got {:?} ", + field_tpe, sf.field_tpe + ), + )) + } else { + Ok(sf) + } + } + crate::ergotree_proc_macro::ExtractedType::STuple => { + if let SType::STuple(_) = sf.field_tpe { + Ok(sf) + } else { + Err(syn::Error::new_spanned( + name, + format!( + "Expected tuple field of type STuple(_), got {:?}", + sf.field_tpe + ), + )) + } + } + crate::ergotree_proc_macro::ExtractedType::SCollection(_) => { + if let SType::SColl(_) = sf.field_tpe { + Ok(sf) + } else { + Err(syn::Error::new_spanned( + name, + format!( + "Expected tuple field of type SCollection(_), got {:?}", + sf.field_tpe + ), + )) + } + } + crate::ergotree_proc_macro::ExtractedType::SOption(_) => { + if let SType::SOption(_) = sf.field_tpe { + Ok(sf) + } else { + Err(syn::Error::new_spanned( + name, + format!( + "Expected tuple field of type SOption(_), got {:?}", + sf.field_tpe + ), + )) + } + } + } + } else { + Err(syn::Error::new_spanned(name, "Expected `typed` keyword")) + } + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for SelectField { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let field_index = self.field_index; + let input = *self.input.clone(); + tokens.extend( + quote::quote! { ergotree_ir::mir::select_field::SelectField::new( + #input, #field_index, + ).unwrap()}, + ) + } +} + #[cfg(test)] #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] diff --git a/ergotree-ir/src/mir/tuple.rs b/ergotree-ir/src/mir/tuple.rs index 5eeb627d0..37003e223 100644 --- a/ergotree-ir/src/mir/tuple.rs +++ b/ergotree-ir/src/mir/tuple.rs @@ -59,6 +59,36 @@ impl SigmaSerializable for Tuple { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for Tuple { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let (items, name) = { + let name: syn::Ident = input.parse()?; + if name == "Vector" { + let content; + let _paren = syn::parenthesized!(content in input); + let punctuated: syn::punctuated::Punctuated = + content.parse_terminated(Expr::parse)?; + (punctuated.into_iter().collect(), name) + } else { + return Err(syn::Error::new_spanned(name, "Expected `Vector`")); + } + }; + Tuple::new(items) + .map_err(|_| syn::Error::new_spanned(name, "Tuple must have at least 2 elements")) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for Tuple { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let items = self.items.clone().to_vec(); + tokens.extend(quote::quote! { ergotree_ir::mir::tuple::Tuple::new( + vec![#( #items),*], + ).unwrap()}) + } +} + #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] /// Arbitrary impl diff --git a/ergotree-ir/src/mir/upcast.rs b/ergotree-ir/src/mir/upcast.rs index 364f143c1..9d61e9e6c 100644 --- a/ergotree-ir/src/mir/upcast.rs +++ b/ergotree-ir/src/mir/upcast.rs @@ -67,6 +67,28 @@ impl SigmaSerializable for Upcast { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for Upcast { + fn parse(buf: syn::parse::ParseStream) -> syn::Result { + let input: Box = buf.parse()?; + let _comma: syn::Token![,] = buf.parse()?; + let tpe: SType = buf.parse()?; + Ok(Self { input, tpe }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for Upcast { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let input = *self.input.clone(); + let tpe = self.tpe.clone(); + tokens.extend(quote::quote! { ergotree_ir::mir::upcast::Upcast{ + input: Box::new(#input), + tpe: #tpe, + }}) + } +} + #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] /// Arbitrary impl diff --git a/ergotree-ir/src/mir/val_def.rs b/ergotree-ir/src/mir/val_def.rs index 4858c5ad0..9739575a8 100644 --- a/ergotree-ir/src/mir/val_def.rs +++ b/ergotree-ir/src/mir/val_def.rs @@ -31,6 +31,23 @@ impl ValId { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for ValId { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let id: syn::LitInt = input.parse()?; + let value = id.base10_parse::()?; + Ok(ValId(value)) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for ValId { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let val_id = self.0; + tokens.extend(quote::quote! { ergotree_ir::mir::val_def::ValId(#val_id) }) + } +} + /** IR node for let-bound expressions `let x = rhs` which is ValDef. * These nodes are used to represent ErgoTrees after common sub-expression elimination. * This representation is more compact in serialized form. @@ -72,6 +89,38 @@ impl SigmaSerializable for ValDef { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for ValDef { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let id = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let list_ident: syn::Ident = input.parse()?; + if list_ident != "List" { + return Err(syn::Error::new_spanned( + list_ident.clone(), + format!("Expected `List` ident, got {}", list_ident), + )); + } + let _content; + let _paren = syn::parenthesized!(_content in input); + let _comma: syn::Token![,] = input.parse()?; + let rhs = input.parse()?; + Ok(Self { id, rhs }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for ValDef { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + let id = self.id; + let rhs = *self.rhs.clone(); + tokens.extend(quote! { + ergotree_ir::mir::val_def::ValDef { id: #id, rhs: Box::new(#rhs)} + }); + } +} + #[cfg(test)] #[cfg(feature = "arbitrary")] #[allow(clippy::panic)] diff --git a/ergotree-ir/src/mir/val_use.rs b/ergotree-ir/src/mir/val_use.rs index 1a4ae5607..954246efa 100644 --- a/ergotree-ir/src/mir/val_use.rs +++ b/ergotree-ir/src/mir/val_use.rs @@ -38,6 +38,32 @@ impl SigmaSerializable for ValUse { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for ValUse { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + let _paren = syn::parenthesized!(content in input); + let id: syn::LitInt = content.parse()?; + let value = id.base10_parse::()?; + let val_id = ValId(value); + let _comma: syn::Token![,] = content.parse()?; + let tpe = content.parse()?; + + Ok(ValUse { val_id, tpe }) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for ValUse { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let val_id = &self.val_id; + let tpe = &self.tpe; + tokens.extend( + quote::quote! { ergotree_ir::mir::val_use::ValUse { val_id: #val_id, tpe: #tpe }}, + ) + } +} + #[cfg(test)] #[cfg(feature = "arbitrary")] #[allow(clippy::panic)] diff --git a/ergotree-ir/src/types/sfunc.rs b/ergotree-ir/src/types/sfunc.rs index f838a3304..526aab2f3 100644 --- a/ergotree-ir/src/types/sfunc.rs +++ b/ergotree-ir/src/types/sfunc.rs @@ -49,3 +49,14 @@ impl SFunc { res } } + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for SFunc { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let t_dom = self.t_dom.clone(); + let t_range = *self.t_range.clone(); + tokens.extend( + quote::quote! { ergotree_ir::types::sfunc::SFunc::new(vec![#(#t_dom),*], #t_range) }, + ); + } +} diff --git a/ergotree-ir/src/types/smethod.rs b/ergotree-ir/src/types/smethod.rs index 7e050fc1f..5051cb063 100644 --- a/ergotree-ir/src/types/smethod.rs +++ b/ergotree-ir/src/types/smethod.rs @@ -103,6 +103,14 @@ pub struct SMethodDesc { } impl SMethodDesc { + /// Create new + pub fn new(name: &'static str, method_id: MethodId, tpe: SFunc) -> Self { + SMethodDesc { + name, + method_id, + tpe, + } + } /// Initialize property method description pub fn property( obj_tpe: SType, @@ -131,3 +139,39 @@ impl SMethodDesc { Self { tpe, ..self } } } + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for SMethodDesc { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let name = self.name; + let method_id = self.method_id.0; + let tpe = self.tpe.clone(); + tokens.extend( + quote::quote! { ergotree_ir::types::smethod::SMethodDesc::new( + #name, + ergotree_ir::types::smethod::MethodId(#method_id), + #tpe, + ) + + }, + ); + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for SMethod { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let obj_type = self.obj_type; + let name = self.name(); + let method_id = self.method_id(); + let tpe = self.tpe().clone(); + let method_raw = SMethodDesc { + name, + method_id, + tpe, + }; + tokens.extend( + quote::quote! { ergotree_ir::types::smethod::SMethod::new(#obj_type, #method_raw) }, + ); + } +} diff --git a/ergotree-ir/src/types/stuple.rs b/ergotree-ir/src/types/stuple.rs index 95afe5947..860d82fe7 100644 --- a/ergotree-ir/src/types/stuple.rs +++ b/ergotree-ir/src/types/stuple.rs @@ -5,6 +5,8 @@ use std::convert::TryInto; use bounded_vec::BoundedVec; use bounded_vec::BoundedVecOutOfBounds; +use crate::mir::expr::InvalidArgumentError; + use super::stype::SType; use super::stype_param::STypeVar; @@ -35,6 +37,13 @@ impl std::fmt::Debug for STuple { } impl STuple { + /// Create new STuple + pub fn new(items: Vec) -> Result { + Ok(STuple { + items: items.try_into()?, + }) + } + /// Create a tuple type for a given type pair pub fn pair(t1: SType, t2: SType) -> Self { STuple { @@ -64,3 +73,34 @@ impl STuple { } } } + +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for STuple { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let (items, name) = { + let name: syn::Ident = input.parse()?; + if name == "Vector" { + let content; + let _paren = syn::parenthesized!(content in input); + let punctuated: syn::punctuated::Punctuated = + content.parse_terminated(SType::parse)?; + (punctuated.into_iter().collect(), name) + } else { + return Err(syn::Error::new_spanned(name, "Expected `Vector`")); + } + }; + STuple::new(items) + .map_err(|_| syn::Error::new_spanned(name, "Tuple must have at least 2 elements")) + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for STuple { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let items = self.items.clone().to_vec(); + tokens.extend(quote::quote! { ergotree_ir::types::stuple::STuple::new( + vec![#( #items),*], + ).unwrap() + }) + } +} diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs index eba90fea1..faf697d16 100644 --- a/ergotree-ir/src/types/stype.rs +++ b/ergotree-ir/src/types/stype.rs @@ -126,6 +126,86 @@ impl From for SType { } } +#[cfg(feature = "ergotree-proc-macro")] +impl syn::parse::Parse for SType { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name: syn::Ident = input.parse()?; + match name.to_string().as_str() { + "SBoolean" => Ok(SType::SBoolean), + "SAny" => Ok(SType::SAny), + "SUnit" => Ok(SType::SUnit), + "SByte" => Ok(SType::SByte), + "SShort" => Ok(SType::SShort), + "SInt" => Ok(SType::SInt), + "SLong" => Ok(SType::SLong), + "SBigInt" => Ok(SType::SBigInt), + "SGroupElement" => Ok(SType::SGroupElement), + "SSigmaProp" => Ok(SType::SSigmaProp), + "SBox" => Ok(SType::SBox), + "SAvlTree" => Ok(SType::SAvlTree), + "SContext" => Ok(SType::SContext), + "SHeader" => Ok(SType::SHeader), + "SPreHeader" => Ok(SType::SPreHeader), + "SGlobal" => Ok(SType::SGlobal), + "STuple" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(SType::STuple(content.parse()?)) + } + "SCollectionType" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(SType::SColl(content.parse()?)) + } + "SOption" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(SType::SOption(content.parse()?)) + } + _ => Err(syn::Error::new_spanned( + name, + "Unknown `SType` variant name", + )), + } + } +} + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for SType { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::quote; + tokens.extend(match self { + SType::STypeVar(s) => quote! { ergotree_ir::types::stype::SType::STypeVar(#s) }, + SType::SAny => quote! { ergotree_ir::types::stype::SType::SAny }, + SType::SUnit => quote! { ergotree_ir::types::stype::SType::SUnit }, + SType::SBoolean => quote! { ergotree_ir::types::stype::SType::SBoolean }, + SType::SByte => quote! { ergotree_ir::types::stype::SType::SByte }, + SType::SShort => quote! { ergotree_ir::types::stype::SType::SShort }, + SType::SInt => quote! { ergotree_ir::types::stype::SType::SInt }, + SType::SLong => quote! { ergotree_ir::types::stype::SType::SLong }, + SType::SBigInt => quote! { ergotree_ir::types::stype::SType::SBigInt }, + SType::SGroupElement => quote! { ergotree_ir::types::stype::SType::SGroupElement }, + SType::SSigmaProp => quote! { ergotree_ir::types::stype::SType::SSigmaProp }, + SType::SBox => quote! { ergotree_ir::types::stype::SType::SBox }, + SType::SAvlTree => quote! { ergotree_ir::types::stype::SType::SAvlTree }, + SType::SOption(o) => { + let tpe = *o.clone(); + quote! { ergotree_ir::types::stype::SType::SOption(Box::new(#tpe)) } + } + SType::SColl(c) => { + let tpe = *c.clone(); + quote! { ergotree_ir::types::stype::SType::SColl(Box::new(#tpe)) } + } + SType::STuple(s) => quote! { ergotree_ir::types::stype::SType::STuple(#s) }, + SType::SFunc(f) => quote! { ergotree_ir::types::stype::SType::SFunc(#f) }, + SType::SContext => quote! { ergotree_ir::types::stype::SType::SContext }, + SType::SHeader => quote! { ergotree_ir::types::stype::SType::SHeader }, + SType::SPreHeader => quote! { ergotree_ir::types::stype::SType::SPreHeader }, + SType::SGlobal => quote! { ergotree_ir::types::stype::SType::SGlobal }, + }) + } +} + /// Conversion to SType pub trait LiftIntoSType { /// get SType diff --git a/ergotree-ir/src/types/stype_companion.rs b/ergotree-ir/src/types/stype_companion.rs index f07411b0a..464a595d5 100644 --- a/ergotree-ir/src/types/stype_companion.rs +++ b/ergotree-ir/src/types/stype_companion.rs @@ -118,3 +118,38 @@ impl TryFrom for STypeCompanion { ))) } } + +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for STypeCompanion { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(match self { + STypeCompanion::Context => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::Context} + } + STypeCompanion::Box => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::Box} + } + STypeCompanion::Coll => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::Coll} + } + STypeCompanion::GroupElem => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::GroupElem} + } + STypeCompanion::Global => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::Global} + } + STypeCompanion::Header => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::Header} + } + STypeCompanion::PreHeader => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::PreHeader} + } + STypeCompanion::Option => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::Option} + } + STypeCompanion::AvlTree => { + quote::quote! { ergotree_ir::types::stype_companion::STypeCompanion::AvlTree} + } + }); + } +} diff --git a/ergotree-ir/src/types/stype_param.rs b/ergotree-ir/src/types/stype_param.rs index 39e7c2f59..adc791887 100644 --- a/ergotree-ir/src/types/stype_param.rs +++ b/ergotree-ir/src/types/stype_param.rs @@ -89,6 +89,17 @@ impl SigmaSerializable for STypeVar { } } +#[cfg(feature = "ergotree-proc-macro")] +impl quote::ToTokens for STypeVar { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let s = self.as_string(); + let bytes = s.as_bytes().to_vec(); + tokens.extend(quote::quote! { + ergotree_ir::types::stype_param::STypeVar::new_from_bytes(vec![#(#bytes),*]).unwrap() + }); + } +} + /// Type parameter #[derive(PartialEq, Eq, Debug, Clone)] pub struct STypeParam { diff --git a/ergotree-macro/Cargo.toml b/ergotree-macro/Cargo.toml new file mode 100644 index 000000000..ef4e8218b --- /dev/null +++ b/ergotree-macro/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ergotree-macro" +version = "0.20.0" +license = "CC0-1.0" +authors = ["Timothy Ling (@kettlebell)"] +description = "Procedural macro to generate ErgoTree expressions" +repository = "https://github.com/ergoplatform/sigma-rust" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro-error = "1" +proc-macro2 = "1" +quote = "1" +syn = { version = "1", features = ["full", "extra-traits", "visit-mut"] } +ergotree-ir = { version = "^0.20.0", path = "../ergotree-ir", features = ["arbitrary", "ergotree-proc-macro"] } +num-traits = "0.2.14" + +[dev-dependencies] +paste = "^1.0" \ No newline at end of file diff --git a/ergotree-macro/README.md b/ergotree-macro/README.md new file mode 100644 index 000000000..7edfa148d --- /dev/null +++ b/ergotree-macro/README.md @@ -0,0 +1,3 @@ +# ergotree-macro + +This crate defines a proc-macro `ergo_tree` that converts a pretty-printed representation of an ergo tree expression into an instance of `ergotree::ir::mir::expr::Expr`. This macro exists for the purpose of checking the correctness of the JIT v5 costing method. \ No newline at end of file diff --git a/ergotree-macro/src/lib.rs b/ergotree-macro/src/lib.rs new file mode 100644 index 000000000..99927ca3b --- /dev/null +++ b/ergotree-macro/src/lib.rs @@ -0,0 +1,13 @@ +//! Procedural macro to generate ergotree instances +//! + +use ergotree_ir::mir::expr::Expr; +use proc_macro::TokenStream; +use quote::quote; +use syn::parse_macro_input; + +#[proc_macro] +pub fn ergo_tree(input: TokenStream) -> TokenStream { + let expr = parse_macro_input!(input as Expr); + TokenStream::from(quote! { #expr }) +} diff --git a/ergotree-macro/tests/method_call_smoke_tests.rs b/ergotree-macro/tests/method_call_smoke_tests.rs new file mode 100644 index 000000000..b827a497d --- /dev/null +++ b/ergotree-macro/tests/method_call_smoke_tests.rs @@ -0,0 +1,101 @@ +use ergotree_macro::ergo_tree; + +#[test] +fn test_method_call_flatmap_0() { + // For ergoscript: + // { (x: Coll[Box]) => x.flatMap({(b: Box) => b.propositionBytes }) } + let _ = ergo_tree!( + FuncValue( + Vector((1, SCollectionType(SBox))), + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SCollectionType(SBox)), + SCollection.getMethodByName("flatMap").withConcreteTypes( + Map(STypeVar("IV") -> SBox, STypeVar("OV") -> SByte) + ), + Vector(FuncValue(Vector((3, SBox)), ExtractScriptBytes(ValUse(3, SBox)))), + Map() + ) + ) + ); +} + +#[test] +fn test_method_call_flatmap_1() { + // For ergoscript: + // { (x: Coll[GroupElement]) => x.flatMap({ (b: GroupElement) => b.getEncoded }) } + let _ = ergo_tree!( + FuncValue( + Vector((1, SCollectionType(SGroupElement))), + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SCollectionType(SGroupElement)), + SCollection.getMethodByName("flatMap").withConcreteTypes( + Map(STypeVar("IV") -> SGroupElement, STypeVar("OV") -> SByte) + ), + Vector( + FuncValue( + Vector((3, SGroupElement)), + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(3, SGroupElement), + SGroupElement.getMethodByName("getEncoded"), + Vector(), + Map() + ) + ) + ), + Map() + ) + ) + ); +} + +#[test] +fn test_method_call_flatmap_2() { + let _ = ergo_tree!( + FuncValue( + Vector((1, SCollectionType(SGroupElement))), + MethodCall.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SCollectionType(SGroupElement)), + SCollection.getMethodByName("flatMap").withConcreteTypes( + Map(STypeVar("IV") -> SGroupElement, STypeVar("OV") -> SInt) + ), + Vector( + FuncValue( + Vector((3, SGroupElement)), + MethodCall.typed[Value[SCollection[SInt.type]]]( + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(3, SGroupElement), + SGroupElement.getMethodByName("getEncoded"), + Vector(), + Map() + ), + SCollection.getMethodByName("indices").withConcreteTypes( + Map(STypeVar("IV") -> SByte) + ), + Vector(), + Map() + ) + ) + ), + Map() + ) + ) + ); +} + +#[test] +fn test_method_call_zip_0() { + // { (x: Coll[Box]) => x.zip(x) } + let _ = ergo_tree!( + FuncValue( + Vector((1, SCollectionType(SBox))), + MethodCall.typed[Value[SCollection[STuple]]]( + ValUse(1, SCollectionType(SBox)), + SCollection.getMethodByName("zip").withConcreteTypes( + Map(STypeVar("IV") -> SBox, STypeVar("OV") -> SBox) + ), + Vector(ValUse(1, SCollectionType(SBox))), + Map() + ) + ) + ); +} diff --git a/ergotree-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs new file mode 100644 index 000000000..6b26f629b --- /dev/null +++ b/ergotree-macro/tests/tests.rs @@ -0,0 +1,725 @@ +use ergotree_ir::{ + bigint256::BigInt256, + mir::{ + bin_op::{ArithOp, BinOp, BinOpKind, BitOp, LogicalOp, RelationOp}, + block::BlockValue, + bool_to_sigma::BoolToSigmaProp, + constant::Constant, + downcast::Downcast, + expr::Expr, + func_value::{FuncArg, FuncValue}, + logical_not::LogicalNot, + select_field::{SelectField, TupleFieldIndex}, + tuple::Tuple, + upcast::Upcast, + val_def::{ValDef, ValId}, + val_use::ValUse, + }, + types::{stuple::STuple, stype::SType}, +}; +use ergotree_macro::ergo_tree; +// Note that this trait MUST be imported to local scope to handle `BigIntConstant(..)`! +use num_traits::Num; +use paste::paste; + +#[test] +fn test_stuple() { + let e = ergo_tree!(FuncValue( + Vector((1, STuple(Vector(SBoolean, SBoolean)))), + ValUse(1, STuple(Vector(SBoolean, SBoolean))) + )); + + let body = Expr::ValUse(ValUse { + val_id: ValId(1), + tpe: SType::STuple(STuple::pair(SType::SBoolean, SType::SBoolean)), + }); + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::STuple(STuple::pair(SType::SBoolean, SType::SBoolean)), + }]; + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); +} + +#[test] +fn test_tuple_select_field_simple() { + let e = ergo_tree!(FuncValue( + Vector((1, STuple(Vector(SBoolean, SBoolean)))), + SelectField.typed[BoolValue](ValUse(1, STuple(Vector(SBoolean, SBoolean))), 1.toByte) + )); + + let input = Expr::ValUse(ValUse { + val_id: ValId(1), + tpe: SType::STuple(STuple::pair(SType::SBoolean, SType::SBoolean)), + }); + let body = Expr::SelectField( + SelectField::new(input, TupleFieldIndex::try_from(1_u8).unwrap()).unwrap(), + ); + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::STuple(STuple::pair(SType::SBoolean, SType::SBoolean)), + }]; + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); +} + +#[test] +fn test_tuple_select_field_with_coll() { + let e = ergo_tree!(FuncValue( + Vector((1, STuple(Vector(SCollectionType(SByte), SBoolean)))), + SelectField.typed[Value[ + SCollection[ SInt.type ] + ]]( + ValUse(1, STuple(Vector(SCollectionType(SByte), SBoolean))), + 1.toByte + ) + )); + + let input = Expr::ValUse(ValUse { + val_id: ValId(1), + tpe: SType::STuple(STuple::pair( + SType::SColl(SType::SByte.into()), + SType::SBoolean, + )), + }); + let body = Expr::SelectField( + SelectField::new(input, TupleFieldIndex::try_from(1_u8).unwrap()).unwrap(), + ); + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::STuple(STuple::pair( + SType::SColl(SType::SByte.into()), + SType::SBoolean, + )), + }]; + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); +} + +#[test] +fn test_lambda_0() { + let e = ergo_tree!(FuncValue( + Vector((1, SBoolean)), + BoolToSigmaProp(ValUse(1, SBoolean)) + )); + + let input = Expr::ValUse(ValUse { + val_id: ValId(1), + tpe: SType::SBoolean, + }) + .into(); + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::SBoolean, + }]; + let body = Expr::BoolToSigmaProp(BoolToSigmaProp { input }); + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); +} + +#[test] +fn test_simple_arithmetic() { + let e = ergo_tree!(FuncValue( + Vector((1, SInt)), + ArithOp(ValUse(1, SInt), IntConstant(-345), OpCode @@ (-102.toByte)) + )); + + let left = Expr::ValUse(ValUse { + val_id: ValId(1), + tpe: SType::SInt, + }) + .into(); + let right = Expr::Const(Constant::from(-345_i32)).into(); + let body = Expr::BinOp(BinOp { + kind: ergotree_ir::mir::bin_op::BinOpKind::Arith(ergotree_ir::mir::bin_op::ArithOp::Plus), + left, + right, + }); + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::SInt, + }]; + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); +} + +#[test] +fn test_integer_constants() { + let suite = vec![ + ( + ergo_tree!(ByteConstant(0.toByte)), + Expr::Const(Constant::from(0_i8)), + ), + ( + ergo_tree!(ByteConstant(-0.toByte)), + Expr::Const(Constant::from(0_i8)), + ), + ( + ergo_tree!(ByteConstant(1.toByte)), + Expr::Const(Constant::from(1_i8)), + ), + ( + ergo_tree!(ByteConstant(-1.toByte)), + Expr::Const(Constant::from(-1_i8)), + ), + ( + ergo_tree!(ByteConstant(127.toByte)), + Expr::Const(Constant::from(127_i8)), + ), + ( + ergo_tree!(ByteConstant(-128.toByte)), + Expr::Const(Constant::from(-128_i8)), + ), + ( + ergo_tree!(ShortConstant(0)), + Expr::Const(Constant::from(0_i16)), + ), + ( + ergo_tree!(ShortConstant(-0)), + Expr::Const(Constant::from(0_i16)), + ), + ( + ergo_tree!(ShortConstant(1)), + Expr::Const(Constant::from(1_i16)), + ), + ( + ergo_tree!(ShortConstant(-1)), + Expr::Const(Constant::from(-1_i16)), + ), + ( + ergo_tree!(ShortConstant(32767)), + Expr::Const(Constant::from(32767_i16)), + ), + ( + ergo_tree!(ShortConstant(-32768)), + Expr::Const(Constant::from(-32768_i16)), + ), + ( + ergo_tree!(ShortConstant(0.toShort)), + Expr::Const(Constant::from(0_i16)), + ), + ( + ergo_tree!(ShortConstant(-0.toShort)), + Expr::Const(Constant::from(0_i16)), + ), + ( + ergo_tree!(ShortConstant(1.toShort)), + Expr::Const(Constant::from(1_i16)), + ), + ( + ergo_tree!(ShortConstant(-1.toShort)), + Expr::Const(Constant::from(-1_i16)), + ), + ( + ergo_tree!(ShortConstant(32767.toShort)), + Expr::Const(Constant::from(32767_i16)), + ), + ( + ergo_tree!(ShortConstant(-32768.toShort)), + Expr::Const(Constant::from(-32768_i16)), + ), + ( + ergo_tree!(IntConstant(0)), + Expr::Const(Constant::from(0_i32)), + ), + ( + ergo_tree!(IntConstant(-0)), + Expr::Const(Constant::from(0_i32)), + ), + ( + ergo_tree!(IntConstant(1)), + Expr::Const(Constant::from(1_i32)), + ), + ( + ergo_tree!(IntConstant(-1)), + Expr::Const(Constant::from(-1_i32)), + ), + ( + ergo_tree!(IntConstant(1024)), + Expr::Const(Constant::from(1024_i32)), + ), + ( + ergo_tree!(IntConstant(-1024)), + Expr::Const(Constant::from(-1024_i32)), + ), + ( + ergo_tree!(LongConstant(0L)), + Expr::Const(Constant::from(0_i64)), + ), + ( + ergo_tree!(LongConstant(-0L)), + Expr::Const(Constant::from(0_i64)), + ), + ( + ergo_tree!(LongConstant(1L)), + Expr::Const(Constant::from(1_i64)), + ), + ( + ergo_tree!(LongConstant(-1L)), + Expr::Const(Constant::from(-1_i64)), + ), + ( + ergo_tree!(LongConstant(1024.toLong)), + Expr::Const(Constant::from(1024_i64)), + ), + ( + ergo_tree!(LongConstant(-1024.toLong)), + Expr::Const(Constant::from(-1024_i64)), + ), + ( + ergo_tree!(LongConstant(-6985752043373238161L)), + Expr::Const(Constant::from(-6985752043373238161_i64)), + ), + ( + ergo_tree!(BigIntConstant(-6985752043373238161L)), + Expr::Const(Constant::from(BigInt256::from(-6985752043373238161_i64))), + ), + ]; + for (actual, expected) in suite { + assert_eq!(actual, expected); + } +} + +#[test] +fn test_eq_relation() { + let e = ergo_tree!(EQ(IntConstant(33), IntConstant(44))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Eq), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_neq_relation() { + let e = ergo_tree!(NEQ(IntConstant(33), IntConstant(44))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::NEq), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_ge_relation() { + let e = ergo_tree!(GE(IntConstant(33), IntConstant(44))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Ge), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_le_relation() { + let e = ergo_tree!(LE(IntConstant(33), IntConstant(44))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Le), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_gt_relation() { + let e = ergo_tree!(GT(IntConstant(33), IntConstant(44))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Gt), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_lt_relation() { + let e = ergo_tree!(LT(IntConstant(33), IntConstant(44))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Lt), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_bin_and_op() { + let e = ergo_tree!(BinAnd( + LT(IntConstant(33), IntConstant(44)), + GT(IntConstant(330), IntConstant(44)) + )); + + let left = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Lt), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }) + .into(); + let right = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Gt), + left: Expr::from(Constant::from(330_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }) + .into(); + + let and_expr = Expr::BinOp(BinOp { + kind: BinOpKind::Logical(LogicalOp::And), + left, + right, + }); + assert_eq!(e, and_expr); +} + +#[test] +fn test_bin_or_op() { + let e = ergo_tree!(BinOr( + LT(IntConstant(33), IntConstant(44)), + GT(IntConstant(330), IntConstant(44)) + )); + + let left = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Lt), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }) + .into(); + let right = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Gt), + left: Expr::from(Constant::from(330_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }) + .into(); + + let and_expr = Expr::BinOp(BinOp { + kind: BinOpKind::Logical(LogicalOp::Or), + left, + right, + }); + assert_eq!(e, and_expr); +} + +#[test] +fn test_bin_xor_op() { + let e = ergo_tree!(BinXor( + LT(IntConstant(33), IntConstant(44)), + GT(IntConstant(330), IntConstant(44)) + )); + + let left = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Lt), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }) + .into(); + let right = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Gt), + left: Expr::from(Constant::from(330_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }) + .into(); + + let and_expr = Expr::BinOp(BinOp { + kind: BinOpKind::Logical(LogicalOp::Xor), + left, + right, + }); + assert_eq!(e, and_expr); +} + +#[test] +fn test_logical_not() { + let e = ergo_tree!(LogicalNot(GT(IntConstant(33), IntConstant(44)))); + let input = Expr::BinOp(BinOp { + kind: BinOpKind::Relation(RelationOp::Gt), + left: Expr::from(Constant::from(33_i32)).into(), + right: Expr::from(Constant::from(44_i32)).into(), + }) + .into(); + let expected = Expr::LogicalNot(LogicalNot { input }); + assert_eq!(e, expected); +} + +#[test] +fn test_bit_and_op() { + let e = ergo_tree!(BitAnd(ByteConstant(100.toByte), ByteConstant(82.toByte))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Bit(BitOp::BitAnd), + left: Expr::from(Constant::from(100_i8)).into(), + right: Expr::from(Constant::from(82_i8)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_bit_or_op() { + let e = ergo_tree!(BitOr(ByteConstant(100.toByte), ByteConstant(82.toByte))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Bit(BitOp::BitOr), + left: Expr::from(Constant::from(100_i8)).into(), + right: Expr::from(Constant::from(82_i8)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_bit_xor_op() { + let e = ergo_tree!(BitXor(ByteConstant(100.toByte), ByteConstant(82.toByte))); + let expected = Expr::BinOp(BinOp { + kind: BinOpKind::Bit(BitOp::BitXor), + left: Expr::from(Constant::from(100_i8)).into(), + right: Expr::from(Constant::from(82_i8)).into(), + }); + assert_eq!(e, expected); +} + +#[test] +fn test_downcast() { + let e = ergo_tree!(Downcast(IntConstant(100), SByte)); + let expected = Expr::Downcast(Downcast { + input: Expr::from(Constant::from(100_i32)).into(), + tpe: SType::SByte, + }); + assert_eq!(e, expected); +} + +#[test] +fn test_upcast() { + let e = ergo_tree!(Upcast(ByteConstant(100.toByte), SInt)); + let expected = Expr::Upcast(Upcast { + input: Expr::from(Constant::from(100_i8)).into(), + tpe: SType::SInt, + }); + assert_eq!(e, expected); +} + +#[test] +fn test_arithmetic_in_block() { + // This example comes from the scala JIT test suite: + // { (x: (Byte, Byte)) => + // val a = x._1; val b = x._2 + // val plus = a + b + // val minus = a - b + // val mul = a * b + // val div = a / b + // val mod = a % b + // (plus, (minus, (mul, (div, mod)))) + // } + let e = ergo_tree!( + FuncValue( + Vector((1, STuple(Vector(SByte, SByte)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[ByteValue](ValUse(1, STuple(Vector(SByte, SByte))), 1.toByte) + ), + ValDef( + 4, + List(), + SelectField.typed[ByteValue](ValUse(1, STuple(Vector(SByte, SByte))), 2.toByte) + ) + ), + Tuple( + Vector( + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-102.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-103.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-100.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-99.toByte)), + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-98.toByte)) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + + let items = vec![ + ValDef { + id: ValId(3), + rhs: Expr::SelectField( + SelectField::new( + Expr::ValUse(ValUse { val_id: ValId(1), tpe: SType::STuple(STuple::pair(SType::SByte, SType::SByte)) }), + TupleFieldIndex::try_from(1).unwrap() + ).unwrap()).into() + }.into(), + ValDef { + id: ValId(4), + rhs: Expr::SelectField( + SelectField::new( + Expr::ValUse(ValUse { val_id: ValId(1), tpe: SType::STuple(STuple::pair(SType::SByte, SType::SByte)) }), + TupleFieldIndex::try_from(2).unwrap() + ).unwrap()).into() + }.into(), + ]; + + let val_use3: Box = Expr::ValUse(ValUse { + val_id: ValId(3), + tpe: SType::SByte, + }) + .into(); + let val_use4: Box = Expr::ValUse(ValUse { + val_id: ValId(4), + tpe: SType::SByte, + }) + .into(); + + let make_def = |op| { + Expr::BinOp(BinOp { + kind: BinOpKind::Arith(op), + left: val_use3.clone(), + right: val_use4.clone(), + }) + }; + + let plus = make_def(ArithOp::Plus); + let minus = make_def(ArithOp::Minus); + let mul = make_def(ArithOp::Multiply); + let div = make_def(ArithOp::Divide); + let modulo = make_def(ArithOp::Modulo); + + let t3 = Expr::Tuple(Tuple::new(vec![div, modulo]).unwrap()); + let t2 = Expr::Tuple(Tuple::new(vec![mul, t3]).unwrap()); + let t1 = Expr::Tuple(Tuple::new(vec![minus, t2]).unwrap()); + let result = Expr::Tuple(Tuple::new(vec![plus, t1]).unwrap()).into(); + + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::STuple(STuple::pair(SType::SByte, SType::SByte)), + }]; + let body = Expr::BlockValue(BlockValue { items, result }); + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); +} + +#[test] +fn test_nested_tuples() { + // For the following ergoscript: + // { (t: (Boolean, (Int, Long))) => t._2 } + let e = ergo_tree!(FuncValue( + Vector((1, STuple(Vector(SBoolean, STuple(Vector(SInt, SLong)))))), + SelectField.typed[Value[STuple]]( + ValUse(1, STuple(Vector(SBoolean, STuple(Vector(SInt, SLong))))), + 2.toByte + ) + )); + + let input = Expr::ValUse(ValUse { + val_id: ValId(1), + tpe: SType::STuple(STuple::pair( + SType::SBoolean, + SType::STuple(STuple::pair(SType::SInt, SType::SLong)), + )), + }); + let body = Expr::SelectField( + SelectField::new(input, TupleFieldIndex::try_from(2_u8).unwrap()).unwrap(), + ); + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::STuple(STuple::pair( + SType::SBoolean, + SType::STuple(STuple::pair(SType::SInt, SType::SLong)), + )), + }]; + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); +} + +/// This macro creates a unit test for parsing and tokenizing the following ergoscript: +/// { (x: $type_name) -> x } +macro_rules! identity_fn { + ($type_name:ident) => { + paste! { + #[test] + fn []() { + let e = ergo_tree!(FuncValue( + Vector((1, $type_name)), + ValUse(1, $type_name) + )); + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::$type_name, + }]; + let body = Expr::ValUse(ValUse { + val_id: ValId(1), + tpe: SType::$type_name, + }); + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); + } + } + }; +} + +identity_fn!(SAny); +identity_fn!(SUnit); +identity_fn!(SBoolean); +identity_fn!(SShort); +identity_fn!(SInt); +identity_fn!(SLong); +identity_fn!(SBigInt); +identity_fn!(SGroupElement); +identity_fn!(SSigmaProp); +identity_fn!(SBox); +identity_fn!(SAvlTree); +identity_fn!(SContext); +identity_fn!(SHeader); +identity_fn!(SPreHeader); +identity_fn!(SGlobal); + +/// This macro creates a unit test for parsing and tokenizing the following ergoscript: +/// { (x: Coll[$type_name]) -> x } +macro_rules! identity_fn_coll { + ($type_name:ident) => { + paste! { + #[test] + fn []() { + let e = ergo_tree!(FuncValue( + Vector((1, SCollectionType($type_name))), + ValUse(1, SCollectionType($type_name)) + )); + let args = vec![FuncArg { + idx: ValId(1), + tpe: SType::SColl(SType::$type_name.into()), + }]; + let body = Expr::ValUse(ValUse { + val_id: ValId(1), + tpe: SType::SColl(SType::$type_name.into()), + }); + let expected = Expr::FuncValue(FuncValue::new(args, body)); + assert_eq!(e, expected); + } + } + }; +} + +identity_fn_coll!(SAny); +identity_fn_coll!(SUnit); +identity_fn_coll!(SBoolean); +identity_fn_coll!(SShort); +identity_fn_coll!(SInt); +identity_fn_coll!(SLong); +identity_fn_coll!(SBigInt); +identity_fn_coll!(SGroupElement); +identity_fn_coll!(SSigmaProp); +identity_fn_coll!(SBox); +identity_fn_coll!(SAvlTree); +identity_fn_coll!(SContext); +identity_fn_coll!(SHeader); +identity_fn_coll!(SPreHeader); +identity_fn_coll!(SGlobal);