From bf0007a83c5745dcf9b6bc289b2736d26bd383ee Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Mon, 3 Oct 2022 00:04:47 +1100 Subject: [PATCH 01/20] `ergotree-proc-macro` first commit --- Cargo.toml | 1 + ergotree-interpreter/Cargo.toml | 1 + ergotree-ir/Cargo.toml | 4 ++ ergotree-ir/src/lib.rs | 2 +- ergotree-ir/src/mir/bool_to_sigma.rs | 22 ++++++ ergotree-ir/src/mir/coll_append.rs | 28 ++++++++ ergotree-ir/src/mir/expr.rs | 102 +++++++++++++++++++++++++++ ergotree-ir/src/mir/func_value.rs | 64 +++++++++++++++++ ergotree-ir/src/mir/val_def.rs | 17 +++++ ergotree-ir/src/mir/val_use.rs | 26 +++++++ ergotree-ir/src/types/stype.rs | 44 ++++++++++++ ergotree-macro/Cargo.toml | 25 +++++++ ergotree-macro/README.md | 3 + ergotree-macro/src/lib.rs | 13 ++++ ergotree-macro/tests/01-initial.rs | 31 ++++++++ ergotree-macro/tests/progress.rs | 5 ++ 16 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 ergotree-macro/Cargo.toml create mode 100644 ergotree-macro/README.md create mode 100644 ergotree-macro/src/lib.rs create mode 100644 ergotree-macro/tests/01-initial.rs create mode 100644 ergotree-macro/tests/progress.rs 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..edb1624e5 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.19", 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/lib.rs b/ergotree-ir/src/lib.rs index a4607f643..d639de4a4 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)] 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/expr.rs b/ergotree-ir/src/mir/expr.rs index 85f84cd06..730670670 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -370,6 +370,108 @@ 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()?)) + } + "BoolToSigmaProp" => Ok(Expr::BoolToSigmaProp(input.parse()?)), + "ValUse" => Ok(Expr::ValUse(input.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(_) => todo!(), + Expr::ConstPlaceholder(_) => todo!(), + Expr::SubstConstants(_) => todo!(), + Expr::ByteArrayToLong(_) => todo!(), + Expr::ByteArrayToBigInt(_) => todo!(), + Expr::LongToByteArray(_) => todo!(), + Expr::Collection(_) => todo!(), + Expr::Tuple(_) => todo!(), + 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(_) => todo!(), + Expr::ProperyCall(_) => todo!(), + Expr::BlockValue(_) => todo!(), + Expr::ValDef(_) => todo!(), + Expr::ValUse(v) => { + quote! { ergotree_ir::mir::expr::Expr::ValUse(#v) } + } + Expr::If(_) => todo!(), + Expr::BinOp(_) => todo!(), + Expr::And(_) => todo!(), + Expr::Or(_) => todo!(), + Expr::Xor(_) => todo!(), + Expr::Atleast(_) => todo!(), + Expr::LogicalNot(_) => todo!(), + 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(_) => todo!(), + 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(_) => todo!(), + Expr::BoolToSigmaProp(b) => { + quote! { ergotree_ir::mir::expr::Expr::BoolToSigmaProp(#b) } + } + Expr::Upcast(_) => todo!(), + Expr::Downcast(_) => todo!(), + 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/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/val_def.rs b/ergotree-ir/src/mir/val_def.rs index 4858c5ad0..ec2af542f 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. 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/stype.rs b/ergotree-ir/src/types/stype.rs index eba90fea1..fec5f19a0 100644 --- a/ergotree-ir/src/types/stype.rs +++ b/ergotree-ir/src/types/stype.rs @@ -126,6 +126,50 @@ 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), + _ => 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(_) => todo!(), + SType::SAny => todo!(), + SType::SUnit => todo!(), + SType::SBoolean => quote! { ergotree_ir::types::stype::SType::SBoolean }, + SType::SByte => todo!(), + SType::SShort => todo!(), + SType::SInt => todo!(), + SType::SLong => todo!(), + SType::SBigInt => todo!(), + SType::SGroupElement => todo!(), + SType::SSigmaProp => todo!(), + SType::SBox => todo!(), + SType::SAvlTree => todo!(), + SType::SOption(_) => todo!(), + SType::SColl(_) => todo!(), + SType::STuple(_) => todo!(), + SType::SFunc(_) => todo!(), + SType::SContext => todo!(), + SType::SHeader => todo!(), + SType::SPreHeader => todo!(), + SType::SGlobal => todo!(), + }) + } +} + /// Conversion to SType pub trait LiftIntoSType { /// get SType diff --git a/ergotree-macro/Cargo.toml b/ergotree-macro/Cargo.toml new file mode 100644 index 000000000..77ad1ec2f --- /dev/null +++ b/ergotree-macro/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ergotree-macro" +version = "0.19.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 + +[[test]] +name = "tests" +path = "tests/progress.rs" + +[dev-dependencies] +trybuild = { version = "1.0.49", features = ["diff"] } + +[dependencies] +proc-macro-error = "1" +proc-macro2 = "1" +quote = "1" +syn = { version = "1", features = ["full", "extra-traits", "visit-mut"] } +ergotree-ir = { version = "^0.19.0", path = "../ergotree-ir", features = ["arbitrary", "ergotree-proc-macro"] } \ 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/01-initial.rs b/ergotree-macro/tests/01-initial.rs new file mode 100644 index 000000000..8f40d041e --- /dev/null +++ b/ergotree-macro/tests/01-initial.rs @@ -0,0 +1,31 @@ +use ergotree_ir::{ + mir::{ + bool_to_sigma::BoolToSigmaProp, + expr::Expr, + func_value::{FuncArg, FuncValue}, + val_def::ValId, + val_use::ValUse, + }, + types::stype::SType, +}; +use ergotree_macro::ergo_tree; + +fn main() { + 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); +} diff --git a/ergotree-macro/tests/progress.rs b/ergotree-macro/tests/progress.rs new file mode 100644 index 000000000..42d86a7a6 --- /dev/null +++ b/ergotree-macro/tests/progress.rs @@ -0,0 +1,5 @@ +#[test] +fn tests() { + let t = trybuild::TestCases::new(); + t.pass("tests/01-initial.rs"); +} From 80a07d814f426e4d7d8a2bd7f36e539ae3f94842 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Mon, 3 Oct 2022 00:14:18 +1100 Subject: [PATCH 02/20] Fix dependencies after rebase --- ergotree-interpreter/Cargo.toml | 2 +- ergotree-macro/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ergotree-interpreter/Cargo.toml b/ergotree-interpreter/Cargo.toml index edb1624e5..41b13e5d1 100644 --- a/ergotree-interpreter/Cargo.toml +++ b/ergotree-interpreter/Cargo.toml @@ -62,5 +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.19", path = "../ergotree-macro"} +ergotree-macro = { version = "0.20", path = "../ergotree-macro"} diff --git a/ergotree-macro/Cargo.toml b/ergotree-macro/Cargo.toml index 77ad1ec2f..0c2f2397a 100644 --- a/ergotree-macro/Cargo.toml +++ b/ergotree-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ergotree-macro" -version = "0.19.0" +version = "0.20.0" license = "CC0-1.0" authors = ["Timothy Ling (@kettlebell)"] description = "Procedural macro to generate ErgoTree expressions" @@ -22,4 +22,4 @@ proc-macro-error = "1" proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "extra-traits", "visit-mut"] } -ergotree-ir = { version = "^0.19.0", path = "../ergotree-ir", features = ["arbitrary", "ergotree-proc-macro"] } \ No newline at end of file +ergotree-ir = { version = "^0.20.0", path = "../ergotree-ir", features = ["arbitrary", "ergotree-proc-macro"] } \ No newline at end of file From 83b310ea825e62cfaa607e1c69fc88e51229d694 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Fri, 7 Oct 2022 00:54:28 +1100 Subject: [PATCH 03/20] Split up test suite by crates #639 --- .github/workflows/ci.yml | 65 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf92ec181..8b7d1b593 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,16 +53,71 @@ jobs: uses: actions-rs/cargo@v1 with: command: fetch - - name: Build tests + - name: Run ergo-chain-generation tests uses: actions-rs/cargo@v1 with: - command: build - args: --verbose --release --tests - - name: Run tests + command: test + args: --verbose --release --all-features --manifest-path ergo-chain-generation/Cargo.toml + - name: Run ergo-chain-types tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path ergo-chain-types/Cargo.toml + - name: Run ergo-lib tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path ergo-lib/Cargo.toml + - name: Run ergo-merkle-tree tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path ergo-merkle-tree/Cargo.toml + - name: Run ergo-nipopow tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path ergo-nipopow/Cargo.toml + - name: Run ergo-p2p tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path ergo-p2p/Cargo.toml + - name: Run ergo-rest tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path ergo-rest/Cargo.toml + - name: Run ergoscript-compiler tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path ergoscript-compiler/Cargo.toml + - name: Run ergotree-interpreter tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --all-features --manifest-path ergotree-interpreter/Cargo.toml + - name: Run ergotree-ir tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path ergotree-ir/Cargo.toml + - name: Run ergotree-macro tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --all-features --manifest-path ergotree-macro/Cargo.toml + - name: Run gf2_192 tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all-features --manifest-path gf2_192/Cargo.toml + - name: Run sigma-ser tests uses: actions-rs/cargo@v1 with: command: test - args: --verbose --release + args: --verbose --release --all-features --manifest-path sigma-ser/Cargo.toml test_coverage: name: Code coverage in tests From da0505f9fcfbbe24728e331f7d85663ce73fb2d9 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Fri, 7 Oct 2022 07:55:27 +1100 Subject: [PATCH 04/20] Revert last commit, but remove release mode on tests --- .github/workflows/ci.yml | 65 ++++------------------------------------ 1 file changed, 5 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b7d1b593..5d6e923e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,71 +53,16 @@ jobs: uses: actions-rs/cargo@v1 with: command: fetch - - name: Run ergo-chain-generation tests + - name: Build tests uses: actions-rs/cargo@v1 with: - command: test - args: --verbose --release --all-features --manifest-path ergo-chain-generation/Cargo.toml - - name: Run ergo-chain-types tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path ergo-chain-types/Cargo.toml - - name: Run ergo-lib tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path ergo-lib/Cargo.toml - - name: Run ergo-merkle-tree tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path ergo-merkle-tree/Cargo.toml - - name: Run ergo-nipopow tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path ergo-nipopow/Cargo.toml - - name: Run ergo-p2p tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path ergo-p2p/Cargo.toml - - name: Run ergo-rest tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path ergo-rest/Cargo.toml - - name: Run ergoscript-compiler tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path ergoscript-compiler/Cargo.toml - - name: Run ergotree-interpreter tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --all-features --manifest-path ergotree-interpreter/Cargo.toml - - name: Run ergotree-ir tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path ergotree-ir/Cargo.toml - - name: Run ergotree-macro tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --all-features --manifest-path ergotree-macro/Cargo.toml - - name: Run gf2_192 tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release --all-features --manifest-path gf2_192/Cargo.toml - - name: Run sigma-ser tests + command: build + args: --verbose --tests + - name: Run tests uses: actions-rs/cargo@v1 with: command: test - args: --verbose --release --all-features --manifest-path sigma-ser/Cargo.toml + args: --verbose test_coverage: name: Code coverage in tests From 6f6345e9a0330c4ef0714c3c289f0a79b4d7e0e9 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:47:58 +1100 Subject: [PATCH 05/20] Process `Tuple/STuple` in `ergo_tree!` --- ergotree-ir/src/mir/expr.rs | 10 +++++++- ergotree-ir/src/mir/tuple.rs | 30 ++++++++++++++++++++++ ergotree-ir/src/types/stuple.rs | 40 ++++++++++++++++++++++++++++++ ergotree-ir/src/types/stype.rs | 7 +++++- ergotree-macro/tests/01-initial.rs | 25 ++++++++++++++++++- 5 files changed, 109 insertions(+), 3 deletions(-) diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 730670670..66e81ff54 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -381,6 +381,12 @@ impl syn::parse::Parse for Expr { Ok(Expr::FuncValue(content.parse()?)) } + "Tuple" => { + let content; + let _paren = syn::parenthesized!(content in input); + + Ok(Expr::Tuple(content.parse()?)) + } "BoolToSigmaProp" => Ok(Expr::BoolToSigmaProp(input.parse()?)), "ValUse" => Ok(Expr::ValUse(input.parse()?)), _ => Err(syn::Error::new_spanned(name, "Unknown `Expr` variant name")), @@ -404,7 +410,9 @@ impl quote::ToTokens for Expr { Expr::ByteArrayToBigInt(_) => todo!(), Expr::LongToByteArray(_) => todo!(), Expr::Collection(_) => todo!(), - Expr::Tuple(_) => todo!(), + Expr::Tuple(t) => { + quote! { ergotree_ir::mir::expr::Expr::Tuple(#t) } + } Expr::CalcBlake2b256(_) => todo!(), Expr::CalcSha256(_) => todo!(), Expr::Context => todo!(), diff --git a/ergotree-ir/src/mir/tuple.rs b/ergotree-ir/src/mir/tuple.rs index 5eeb627d0..0afeb0088 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),*], + )}) + } +} + #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] /// Arbitrary impl 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 fec5f19a0..3168d092e 100644 --- a/ergotree-ir/src/types/stype.rs +++ b/ergotree-ir/src/types/stype.rs @@ -132,6 +132,11 @@ impl syn::parse::Parse for SType { let name: syn::Ident = input.parse()?; match name.to_string().as_str() { "SBoolean" => Ok(SType::SBoolean), + "STuple" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(SType::STuple(content.parse()?)) + } _ => Err(syn::Error::new_spanned( name, "Unknown `SType` variant name", @@ -160,7 +165,7 @@ impl quote::ToTokens for SType { SType::SAvlTree => todo!(), SType::SOption(_) => todo!(), SType::SColl(_) => todo!(), - SType::STuple(_) => todo!(), + SType::STuple(s) => quote! { ergotree_ir::types::stype::SType::STuple(#s) }, SType::SFunc(_) => todo!(), SType::SContext => todo!(), SType::SHeader => todo!(), diff --git a/ergotree-macro/tests/01-initial.rs b/ergotree-macro/tests/01-initial.rs index 8f40d041e..fd5d05935 100644 --- a/ergotree-macro/tests/01-initial.rs +++ b/ergotree-macro/tests/01-initial.rs @@ -6,11 +6,16 @@ use ergotree_ir::{ val_def::ValId, val_use::ValUse, }, - types::stype::SType, + types::{stuple::STuple, stype::SType}, }; use ergotree_macro::ergo_tree; fn main() { + example_0(); + example_tuple(); +} + +fn example_0() { let e = ergo_tree!(FuncValue( Vector((1, SBoolean)), BoolToSigmaProp(ValUse(1, SBoolean)) @@ -29,3 +34,21 @@ fn main() { let expected = Expr::FuncValue(FuncValue::new(args, body)); assert_eq!(e, expected); } + +fn example_tuple() { + 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); +} From 924710b8da3b08cde677b15c7acca600450f95da Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Fri, 7 Oct 2022 18:03:39 +1100 Subject: [PATCH 06/20] Remove `trybuild` dependency for `ergotree-macro` --- ergotree-macro/Cargo.toml | 7 ---- ergotree-macro/tests/progress.rs | 5 --- .../tests/{01-initial.rs => tests.rs} | 39 +++++++++---------- 3 files changed, 18 insertions(+), 33 deletions(-) delete mode 100644 ergotree-macro/tests/progress.rs rename ergotree-macro/tests/{01-initial.rs => tests.rs} (93%) diff --git a/ergotree-macro/Cargo.toml b/ergotree-macro/Cargo.toml index 0c2f2397a..afcf52c3d 100644 --- a/ergotree-macro/Cargo.toml +++ b/ergotree-macro/Cargo.toml @@ -10,13 +10,6 @@ edition = "2021" [lib] proc-macro = true -[[test]] -name = "tests" -path = "tests/progress.rs" - -[dev-dependencies] -trybuild = { version = "1.0.49", features = ["diff"] } - [dependencies] proc-macro-error = "1" proc-macro2 = "1" diff --git a/ergotree-macro/tests/progress.rs b/ergotree-macro/tests/progress.rs deleted file mode 100644 index 42d86a7a6..000000000 --- a/ergotree-macro/tests/progress.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[test] -fn tests() { - let t = trybuild::TestCases::new(); - t.pass("tests/01-initial.rs"); -} diff --git a/ergotree-macro/tests/01-initial.rs b/ergotree-macro/tests/tests.rs similarity index 93% rename from ergotree-macro/tests/01-initial.rs rename to ergotree-macro/tests/tests.rs index fd5d05935..45babb117 100644 --- a/ergotree-macro/tests/01-initial.rs +++ b/ergotree-macro/tests/tests.rs @@ -10,45 +10,42 @@ use ergotree_ir::{ }; use ergotree_macro::ergo_tree; -fn main() { - example_0(); - example_tuple(); -} - -fn example_0() { +#[test] +fn test_stuple() { let e = ergo_tree!(FuncValue( - Vector((1, SBoolean)), - BoolToSigmaProp(ValUse(1, SBoolean)) + Vector((1, STuple(Vector(SBoolean, SBoolean)))), + ValUse(1, STuple(Vector(SBoolean, SBoolean))) )); - let input = Expr::ValUse(ValUse { + let body = Expr::ValUse(ValUse { val_id: ValId(1), - tpe: SType::SBoolean, - }) - .into(); + tpe: SType::STuple(STuple::pair(SType::SBoolean, SType::SBoolean)), + }); let args = vec![FuncArg { idx: ValId(1), - tpe: SType::SBoolean, + tpe: SType::STuple(STuple::pair(SType::SBoolean, SType::SBoolean)), }]; - let body = Expr::BoolToSigmaProp(BoolToSigmaProp { input }); let expected = Expr::FuncValue(FuncValue::new(args, body)); assert_eq!(e, expected); } -fn example_tuple() { +#[test] +fn test_lambda_0() { let e = ergo_tree!(FuncValue( - Vector((1, STuple(Vector(SBoolean, SBoolean)))), - ValUse(1, STuple(Vector(SBoolean, SBoolean))) + Vector((1, SBoolean)), + BoolToSigmaProp(ValUse(1, SBoolean)) )); - let body = Expr::ValUse(ValUse { + let input = Expr::ValUse(ValUse { val_id: ValId(1), - tpe: SType::STuple(STuple::pair(SType::SBoolean, SType::SBoolean)), - }); + tpe: SType::SBoolean, + }) + .into(); let args = vec![FuncArg { idx: ValId(1), - tpe: SType::STuple(STuple::pair(SType::SBoolean, SType::SBoolean)), + tpe: SType::SBoolean, }]; + let body = Expr::BoolToSigmaProp(BoolToSigmaProp { input }); let expected = Expr::FuncValue(FuncValue::new(args, body)); assert_eq!(e, expected); } From d1db47cbcd96932993a7419bf2ec746941f5e982 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Mon, 10 Oct 2022 13:57:35 +1100 Subject: [PATCH 07/20] Start impl of `SelectField` for `ergo_tree!` --- ergotree-ir/src/mir/expr.rs | 5 +- ergotree-ir/src/mir/select_field.rs | 73 +++++++++++++++++++++++++++++ ergotree-macro/tests/tests.rs | 23 +++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 66e81ff54..838b5a2d8 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -389,6 +389,7 @@ impl syn::parse::Parse for Expr { } "BoolToSigmaProp" => Ok(Expr::BoolToSigmaProp(input.parse()?)), "ValUse" => Ok(Expr::ValUse(input.parse()?)), + "SelectField" => Ok(Expr::SelectField(input.parse()?)), _ => Err(syn::Error::new_spanned(name, "Unknown `Expr` variant name")), } } @@ -456,7 +457,9 @@ impl quote::ToTokens for Expr { Expr::Filter(_) => todo!(), Expr::Exists(_) => todo!(), Expr::ForAll(_) => todo!(), - Expr::SelectField(_) => todo!(), + Expr::SelectField(s) => { + quote! { ergotree_ir::mir::expr::Expr::SelectField(#s) } + } Expr::BoolToSigmaProp(b) => { quote! { ergotree_ir::mir::expr::Expr::BoolToSigmaProp(#b) } } diff --git a/ergotree-ir/src/mir/select_field.rs b/ergotree-ir/src/mir/select_field.rs index c84dc42b1..066e220fb 100644 --- a/ergotree-ir/src/mir/select_field.rs +++ b/ergotree-ir/src/mir/select_field.rs @@ -57,6 +57,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 +136,62 @@ 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 type_name: syn::Ident = content.parse()?; + let field_tpe = match &*type_name.to_string() { + "BoolValue" => SType::SBoolean, + _ => unreachable!(), + }; + 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)) + })?; + if sf.field_tpe != field_tpe { + return Err(syn::Error::new_spanned( + name, + format!( + "Expected tuple field of type {:?}, got {:?} ", + field_tpe, sf.field_tpe + ), + )); + } else { + Ok(sf) + } + } 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-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index 45babb117..2b7c3dea2 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -3,6 +3,7 @@ use ergotree_ir::{ bool_to_sigma::BoolToSigmaProp, expr::Expr, func_value::{FuncArg, FuncValue}, + select_field::{SelectField, TupleFieldIndex}, val_def::ValId, val_use::ValUse, }, @@ -29,6 +30,28 @@ fn test_stuple() { assert_eq!(e, expected); } +#[test] +fn test_tuple_select_field() { + 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_lambda_0() { let e = ergo_tree!(FuncValue( From 7f6e43a85a08e0296c90c4ce6a2e4bf58c3405f0 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Mon, 10 Oct 2022 20:41:26 +1100 Subject: [PATCH 08/20] Test `ergo_tree!` with simple `SType::*` variants --- ergotree-ir/src/types/stype.rs | 45 ++++++++++++++++++++++------------ ergotree-macro/Cargo.toml | 5 +++- ergotree-macro/tests/tests.rs | 43 ++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 16 deletions(-) diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs index 3168d092e..793e78075 100644 --- a/ergotree-ir/src/types/stype.rs +++ b/ergotree-ir/src/types/stype.rs @@ -132,6 +132,21 @@ impl syn::parse::Parse for SType { 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); @@ -151,26 +166,26 @@ impl quote::ToTokens for SType { use quote::quote; tokens.extend(match self { SType::STypeVar(_) => todo!(), - SType::SAny => todo!(), - SType::SUnit => todo!(), + 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 => todo!(), - SType::SShort => todo!(), - SType::SInt => todo!(), - SType::SLong => todo!(), - SType::SBigInt => todo!(), - SType::SGroupElement => todo!(), - SType::SSigmaProp => todo!(), - SType::SBox => todo!(), - SType::SAvlTree => todo!(), + 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(_) => todo!(), SType::SColl(_) => todo!(), SType::STuple(s) => quote! { ergotree_ir::types::stype::SType::STuple(#s) }, SType::SFunc(_) => todo!(), - SType::SContext => todo!(), - SType::SHeader => todo!(), - SType::SPreHeader => todo!(), - SType::SGlobal => todo!(), + 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 }, }) } } diff --git a/ergotree-macro/Cargo.toml b/ergotree-macro/Cargo.toml index afcf52c3d..fa5c68621 100644 --- a/ergotree-macro/Cargo.toml +++ b/ergotree-macro/Cargo.toml @@ -15,4 +15,7 @@ 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"] } \ No newline at end of file +ergotree-ir = { version = "^0.20.0", path = "../ergotree-ir", features = ["arbitrary", "ergotree-proc-macro"] } + +[dev-dependencies] +paste = "^1.0" \ No newline at end of file diff --git a/ergotree-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index 2b7c3dea2..f58db4f79 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -10,6 +10,7 @@ use ergotree_ir::{ types::{stuple::STuple, stype::SType}, }; use ergotree_macro::ergo_tree; +use paste::paste; #[test] fn test_stuple() { @@ -72,3 +73,45 @@ fn test_lambda_0() { 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); From d9044e3d51fd1c0e34d3e8dc1cdefd3ef8e8cea9 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Thu, 13 Oct 2022 00:07:37 +1100 Subject: [PATCH 09/20] Handle `[SelectField|MethodCall].typed[..]` in `ergo_tree!` --- ergotree-ir/src/ergotree_proc_macro.rs | 115 +++++++++++++++++++++++++ ergotree-ir/src/lib.rs | 3 + ergotree-ir/src/mir/select_field.rs | 46 ++++++---- ergotree-macro/tests/tests.rs | 33 +++++++ 4 files changed, 182 insertions(+), 15 deletions(-) create mode 100644 ergotree-ir/src/ergotree_proc_macro.rs diff --git a/ergotree-ir/src/ergotree_proc_macro.rs b/ergotree-ir/src/ergotree_proc_macro.rs new file mode 100644 index 000000000..8fd5c3bb3 --- /dev/null +++ b/ergotree-ir/src/ergotree_proc_macro.rs @@ -0,0 +1,115 @@ +//! Utility code to support `ergo_tree!` procedural-macro + +use syn::parse::ParseBuffer; + +use crate::types::stype::SType; + +/// aa +#[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: ParseBuffer) -> 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()), + "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; + let _bracketed = syn::bracketed!(content in buf); + Ok(ExtractedType::SOption(Box::new( + extract_tpe_from_dot_typed(content)?, + ))) + } + "SCollection" => { + let content; + let _bracketed = syn::bracketed!(content in buf); + Ok(ExtractedType::SCollection(Box::new( + extract_tpe_from_dot_typed(content)?, + ))) + } + _ => { + unreachable!("unknown ident T in _.typed[Value[T]]") + } + } + } + _ => unreachable!("unknown ident T in _.typed[T]"), + } +} + +/// Parses `.type` from the buffered token stream +pub fn handle_dot_type(buf: ParseBuffer) -> Result { + let _dot: syn::Token![.] = buf.parse()?; + let ident: syn::Ident = buf.parse()?; + if ident != "type" { + return Err(syn::Error::new_spanned(ident, "")); + } + Ok(buf) +} diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs index d639de4a4..5c2aa07b1 100644 --- a/ergotree-ir/src/lib.rs +++ b/ergotree-ir/src/lib.rs @@ -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/select_field.rs b/ergotree-ir/src/mir/select_field.rs index 066e220fb..162983524 100644 --- a/ergotree-ir/src/mir/select_field.rs +++ b/ergotree-ir/src/mir/select_field.rs @@ -1,5 +1,6 @@ use std::convert::TryFrom; +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; @@ -144,11 +145,7 @@ impl syn::parse::Parse for SelectField { if name == "typed" { let mut content; let _bracketed = syn::bracketed!(content in input); - let type_name: syn::Ident = content.parse()?; - let field_tpe = match &*type_name.to_string() { - "BoolValue" => SType::SBoolean, - _ => unreachable!(), - }; + 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()?; @@ -162,16 +159,35 @@ impl syn::parse::Parse for SelectField { let sf = Self::new(input, field_index).map_err(|e| { syn::Error::new_spanned(name.clone(), format!("SelectField::new error: {}", e)) })?; - if sf.field_tpe != field_tpe { - return Err(syn::Error::new_spanned( - name, - format!( - "Expected tuple field of type {:?}, got {:?} ", - field_tpe, sf.field_tpe - ), - )); - } else { - Ok(sf) + match extracted_type { + crate::ergotree_proc_macro::ExtractedType::FullySpecified(field_tpe) => { + if sf.field_tpe != field_tpe { + return 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 { + return Err(syn::Error::new_spanned( + name, + format!( + "Expected tuple field of type STuple(_), got {:?}", + sf.field_tpe + ), + )); + } + } + crate::ergotree_proc_macro::ExtractedType::SCollection(_) => todo!(), + crate::ergotree_proc_macro::ExtractedType::SOption(_) => todo!(), } } else { Err(syn::Error::new_spanned(name, "Expected `typed` keyword")) diff --git a/ergotree-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index f58db4f79..6ccd3addb 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -74,6 +74,39 @@ fn test_lambda_0() { 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 { From ef5f04a66cb9114fa82cd118ea91702666260c2c Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Thu, 13 Oct 2022 00:20:24 +1100 Subject: [PATCH 10/20] Fix some clippy issues --- ergotree-ir/src/mir/select_field.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ergotree-ir/src/mir/select_field.rs b/ergotree-ir/src/mir/select_field.rs index 162983524..c3716c3c9 100644 --- a/ergotree-ir/src/mir/select_field.rs +++ b/ergotree-ir/src/mir/select_field.rs @@ -1,5 +1,6 @@ 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; @@ -162,13 +163,13 @@ impl syn::parse::Parse for SelectField { match extracted_type { crate::ergotree_proc_macro::ExtractedType::FullySpecified(field_tpe) => { if sf.field_tpe != field_tpe { - return Err(syn::Error::new_spanned( + Err(syn::Error::new_spanned( name, format!( "Expected tuple field of type {:?}, got {:?} ", field_tpe, sf.field_tpe ), - )); + )) } else { Ok(sf) } @@ -177,13 +178,13 @@ impl syn::parse::Parse for SelectField { if let SType::STuple(_) = sf.field_tpe { Ok(sf) } else { - return Err(syn::Error::new_spanned( + Err(syn::Error::new_spanned( name, format!( "Expected tuple field of type STuple(_), got {:?}", sf.field_tpe ), - )); + )) } } crate::ergotree_proc_macro::ExtractedType::SCollection(_) => todo!(), From 6883f87c538e7e0c0eb0204a4a348f01ce7f105c Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Thu, 13 Oct 2022 19:09:18 +1100 Subject: [PATCH 11/20] Handle `SelectField.typed[Value[SCollection[_]]]` --- ergotree-ir/src/ergotree_proc_macro.rs | 31 +++++++---- ergotree-ir/src/mir/select_field.rs | 16 +++++- ergotree-ir/src/types/stype.rs | 10 +++- ergotree-macro/tests/tests.rs | 77 +++++++++++++++++++++++++- 4 files changed, 119 insertions(+), 15 deletions(-) diff --git a/ergotree-ir/src/ergotree_proc_macro.rs b/ergotree-ir/src/ergotree_proc_macro.rs index 8fd5c3bb3..a2a5dc2bf 100644 --- a/ergotree-ir/src/ergotree_proc_macro.rs +++ b/ergotree-ir/src/ergotree_proc_macro.rs @@ -1,6 +1,6 @@ //! Utility code to support `ergo_tree!` procedural-macro -use syn::parse::ParseBuffer; +use syn::{ext::IdentExt, Ident}; use crate::types::stype::SType; @@ -25,7 +25,9 @@ impl From for ExtractedType { /// 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: ParseBuffer) -> Result { +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()), @@ -35,6 +37,10 @@ pub fn extract_tpe_from_dot_typed(buf: ParseBuffer) -> Result 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); @@ -82,17 +88,20 @@ pub fn extract_tpe_from_dot_typed(buf: ParseBuffer) -> Result { - let content; - let _bracketed = syn::bracketed!(content in buf); + let content_nested; + let _bracketed = syn::bracketed!(content_nested in content); Ok(ExtractedType::SOption(Box::new( - extract_tpe_from_dot_typed(content)?, + extract_tpe_from_dot_typed(&content_nested)?, ))) } "SCollection" => { - let content; - let _bracketed = syn::bracketed!(content in buf); + 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)?, + ExtractedType::FullySpecified(SType::SByte), //extract_tpe_from_dot_typed(content)?, ))) } _ => { @@ -105,11 +114,11 @@ pub fn extract_tpe_from_dot_typed(buf: ParseBuffer) -> Result Result { +pub fn handle_dot_type(buf: syn::parse::ParseStream) -> Result<(), syn::Error> { let _dot: syn::Token![.] = buf.parse()?; - let ident: syn::Ident = buf.parse()?; + let ident: syn::Ident = buf.call(Ident::parse_any)?; //buf.parse()?; if ident != "type" { return Err(syn::Error::new_spanned(ident, "")); } - Ok(buf) + Ok(()) } diff --git a/ergotree-ir/src/mir/select_field.rs b/ergotree-ir/src/mir/select_field.rs index c3716c3c9..be8bef1ca 100644 --- a/ergotree-ir/src/mir/select_field.rs +++ b/ergotree-ir/src/mir/select_field.rs @@ -146,7 +146,7 @@ impl syn::parse::Parse for SelectField { if name == "typed" { let mut content; let _bracketed = syn::bracketed!(content in input); - let extracted_type = extract_tpe_from_dot_typed(content)?; + 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()?; @@ -187,7 +187,19 @@ impl syn::parse::Parse for SelectField { )) } } - crate::ergotree_proc_macro::ExtractedType::SCollection(_) => todo!(), + 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(_) => todo!(), } } else { diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs index 793e78075..cfffc1c05 100644 --- a/ergotree-ir/src/types/stype.rs +++ b/ergotree-ir/src/types/stype.rs @@ -152,6 +152,11 @@ impl syn::parse::Parse for SType { 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()?)) + } _ => Err(syn::Error::new_spanned( name, "Unknown `SType` variant name", @@ -179,7 +184,10 @@ impl quote::ToTokens for SType { SType::SBox => quote! { ergotree_ir::types::stype::SType::SBox }, SType::SAvlTree => quote! { ergotree_ir::types::stype::SType::SAvlTree }, SType::SOption(_) => todo!(), - SType::SColl(_) => todo!(), + 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(_) => todo!(), SType::SContext => quote! { ergotree_ir::types::stype::SType::SContext }, diff --git a/ergotree-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index 6ccd3addb..e63a2b87c 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -32,7 +32,7 @@ fn test_stuple() { } #[test] -fn test_tuple_select_field() { +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) @@ -53,6 +53,39 @@ fn test_tuple_select_field() { 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( @@ -148,3 +181,45 @@ 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); From 43acc674c57f231dff3a6ae94285d630eb053fe5 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Sat, 15 Oct 2022 15:38:50 +1100 Subject: [PATCH 12/20] Handle arithmetics in block --- ergotree-ir/src/mir/bin_op.rs | 92 ++++++++++++++++++ ergotree-ir/src/mir/block.rs | 45 +++++++++ ergotree-ir/src/mir/constant.rs | 24 +++++ ergotree-ir/src/mir/expr.rs | 36 ++++++- ergotree-ir/src/mir/select_field.rs | 14 ++- ergotree-ir/src/mir/tuple.rs | 2 +- ergotree-ir/src/mir/val_def.rs | 32 +++++++ ergotree-ir/src/types/stype.rs | 10 +- ergotree-macro/tests/tests.rs | 139 +++++++++++++++++++++++++++- 9 files changed, 385 insertions(+), 9 deletions(-) diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs index 102cf9b25..a6123a952 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 { @@ -174,6 +190,82 @@ 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 { + match op_name.to_string().as_str() { + "ArithOp" => { + let left: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let right: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let kind = extract_arithmetic_bin_op_kind(input)?; + 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), + } + } + } + _ => todo!(), + }); + } +} +#[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/constant.rs b/ergotree-ir/src/mir/constant.rs index 58e9f21d2..6dc958023 100644 --- a/ergotree-ir/src/mir/constant.rs +++ b/ergotree-ir/src/mir/constant.rs @@ -829,6 +829,30 @@ 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()) + } + _ => 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) }, + _ => todo!(), + }); + } +} + #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] #[allow(clippy::todo)] diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 838b5a2d8..6962cbd11 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -387,9 +387,31 @@ impl syn::parse::Parse for Expr { 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" => { + let content; + let _paren = syn::parenthesized!(content in input); + Ok(Expr::BinOp(super::bin_op::parse_bin_op(&name, &content)?)) + } + "IntConstant" => { + 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()?)) + } _ => Err(syn::Error::new_spanned(name, "Unknown `Expr` variant name")), } } @@ -404,7 +426,7 @@ impl quote::ToTokens for Expr { quote! { ergotree_ir::mir::expr::Expr::Append { #a } } } - Expr::Const(_) => todo!(), + Expr::Const(c) => quote! { ergotree_ir::mir::expr::Expr::Const(#c) }, Expr::ConstPlaceholder(_) => todo!(), Expr::SubstConstants(_) => todo!(), Expr::ByteArrayToLong(_) => todo!(), @@ -425,13 +447,19 @@ impl quote::ToTokens for Expr { Expr::Apply(_) => todo!(), Expr::MethodCall(_) => todo!(), Expr::ProperyCall(_) => todo!(), - Expr::BlockValue(_) => todo!(), - Expr::ValDef(_) => 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(_) => todo!(), + Expr::BinOp(b) => { + quote! { ergotree_ir::mir::expr::Expr::BinOp(#b) } + } Expr::And(_) => todo!(), Expr::Or(_) => todo!(), Expr::Xor(_) => todo!(), diff --git a/ergotree-ir/src/mir/select_field.rs b/ergotree-ir/src/mir/select_field.rs index be8bef1ca..a2fa4beee 100644 --- a/ergotree-ir/src/mir/select_field.rs +++ b/ergotree-ir/src/mir/select_field.rs @@ -200,7 +200,19 @@ impl syn::parse::Parse for SelectField { )) } } - crate::ergotree_proc_macro::ExtractedType::SOption(_) => todo!(), + 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")) diff --git a/ergotree-ir/src/mir/tuple.rs b/ergotree-ir/src/mir/tuple.rs index 0afeb0088..37003e223 100644 --- a/ergotree-ir/src/mir/tuple.rs +++ b/ergotree-ir/src/mir/tuple.rs @@ -85,7 +85,7 @@ impl quote::ToTokens for Tuple { let items = self.items.clone().to_vec(); tokens.extend(quote::quote! { ergotree_ir::mir::tuple::Tuple::new( vec![#( #items),*], - )}) + ).unwrap()}) } } diff --git a/ergotree-ir/src/mir/val_def.rs b/ergotree-ir/src/mir/val_def.rs index ec2af542f..9739575a8 100644 --- a/ergotree-ir/src/mir/val_def.rs +++ b/ergotree-ir/src/mir/val_def.rs @@ -89,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/types/stype.rs b/ergotree-ir/src/types/stype.rs index cfffc1c05..3c5bab7c0 100644 --- a/ergotree-ir/src/types/stype.rs +++ b/ergotree-ir/src/types/stype.rs @@ -157,6 +157,11 @@ impl syn::parse::Parse for SType { 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", @@ -183,7 +188,10 @@ impl quote::ToTokens for SType { 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(_) => todo!(), + 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)) } diff --git a/ergotree-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index e63a2b87c..d82700652 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -4,8 +4,8 @@ use ergotree_ir::{ expr::Expr, func_value::{FuncArg, FuncValue}, select_field::{SelectField, TupleFieldIndex}, - val_def::ValId, - val_use::ValUse, + val_def::{ValId, ValDef}, + val_use::ValUse, bin_op::{BinOp, ArithOp}, constant::Constant, tuple::Tuple, block::BlockValue, }, types::{stuple::STuple, stype::SType}, }; @@ -107,6 +107,141 @@ fn test_lambda_0() { 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_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: ergotree_ir::mir::bin_op::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: From 31825178629e3659ff9732d8050eafb03d250181 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Sat, 15 Oct 2022 16:59:38 +1100 Subject: [PATCH 13/20] Handle `[Byte|Short|Long|BigInt]Constant` --- ergotree-ir/src/mir/constant.rs | 60 ++++++++++ ergotree-ir/src/mir/expr.rs | 3 +- ergotree-macro/Cargo.toml | 1 + ergotree-macro/tests/tests.rs | 188 ++++++++++++++++++++++++++++---- 4 files changed, 230 insertions(+), 22 deletions(-) diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs index 6dc958023..301732105 100644 --- a/ergotree-ir/src/mir/constant.rs +++ b/ergotree-ir/src/mir/constant.rs @@ -838,6 +838,59 @@ pub fn parse_constant(name: &syn::Ident, input: syn::parse::ParseStream) -> syn: 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!(), } } @@ -848,6 +901,13 @@ impl quote::ToTokens for Constant { 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!(), }); } diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 6962cbd11..511c51317 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -400,7 +400,8 @@ impl syn::parse::Parse for Expr { let _paren = syn::parenthesized!(content in input); Ok(Expr::BinOp(super::bin_op::parse_bin_op(&name, &content)?)) } - "IntConstant" => { + "IntConstant" | "LongConstant" | "ByteConstant" | "ShortConstant" + | "BigIntConstant" => { let content; let _paren = syn::parenthesized!(content in input); Ok(Expr::Const(super::constant::parse_constant( diff --git a/ergotree-macro/Cargo.toml b/ergotree-macro/Cargo.toml index fa5c68621..ef4e8218b 100644 --- a/ergotree-macro/Cargo.toml +++ b/ergotree-macro/Cargo.toml @@ -16,6 +16,7 @@ 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/tests/tests.rs b/ergotree-macro/tests/tests.rs index d82700652..64f929d8d 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -1,15 +1,22 @@ use ergotree_ir::{ + bigint256::BigInt256, mir::{ + bin_op::{ArithOp, BinOp}, + block::BlockValue, bool_to_sigma::BoolToSigmaProp, + constant::Constant, expr::Expr, func_value::{FuncArg, FuncValue}, select_field::{SelectField, TupleFieldIndex}, - val_def::{ValId, ValDef}, - val_use::ValUse, bin_op::{BinOp, ArithOp}, constant::Constant, tuple::Tuple, block::BlockValue, + tuple::Tuple, + 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] @@ -110,7 +117,7 @@ fn test_lambda_0() { #[test] fn test_simple_arithmetic() { let e = ergo_tree!(FuncValue( - Vector((1, SInt)), + Vector((1, SInt)), ArithOp(ValUse(1, SInt), IntConstant(-345), OpCode @@ (-102.toByte)) )); @@ -133,6 +140,143 @@ fn test_simple_arithmetic() { 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_arithmetic_in_block() { // This example comes from the scala JIT test suite: @@ -187,37 +331,39 @@ fn test_arithmetic_in_block() { ); let items = vec![ - ValDef { + 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)) }), + 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() + ).unwrap()).into() }.into(), - ValDef { + 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)) }), + 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() + ).unwrap()).into() }.into(), ]; let val_use3: Box = Expr::ValUse(ValUse { val_id: ValId(3), tpe: SType::SByte, - }).into(); + }) + .into(); let val_use4: Box = Expr::ValUse(ValUse { val_id: ValId(4), tpe: SType::SByte, - }).into(); + }) + .into(); let make_def = |op| { - Expr::BinOp(BinOp { + Expr::BinOp(BinOp { kind: ergotree_ir::mir::bin_op::BinOpKind::Arith(op), - left: val_use3.clone(), + left: val_use3.clone(), right: val_use4.clone(), }) }; @@ -228,16 +374,16 @@ fn test_arithmetic_in_block() { 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 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 body = Expr::BlockValue(BlockValue { items, result }); let expected = Expr::FuncValue(FuncValue::new(args, body)); assert_eq!(e, expected); } From 6cabd1bc713e34942149f2a9611eb92fbed31cc3 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Sat, 15 Oct 2022 17:02:46 +1100 Subject: [PATCH 14/20] Document `ExtractedType` --- ergotree-ir/src/ergotree_proc_macro.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ergotree-ir/src/ergotree_proc_macro.rs b/ergotree-ir/src/ergotree_proc_macro.rs index a2a5dc2bf..9e06f93dc 100644 --- a/ergotree-ir/src/ergotree_proc_macro.rs +++ b/ergotree-ir/src/ergotree_proc_macro.rs @@ -4,7 +4,7 @@ use syn::{ext::IdentExt, Ident}; use crate::types::stype::SType; -/// aa +/// A representation of the type T extracted from `_.typed[T]` on scala side. #[derive(Debug)] pub enum ExtractedType { /// Fully specified `SType` From 034fc01310dfbe9ca4e633776393f7e8c28f65bd Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Sat, 15 Oct 2022 23:52:36 +1100 Subject: [PATCH 15/20] Handle relation operators --- ergotree-ir/src/mir/bin_op.rs | 66 +++++++++++++++++++++++++++++ ergotree-ir/src/mir/expr.rs | 2 +- ergotree-macro/tests/tests.rs | 78 +++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs index a6123a952..893cf13e1 100644 --- a/ergotree-ir/src/mir/bin_op.rs +++ b/ergotree-ir/src/mir/bin_op.rs @@ -79,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 { @@ -202,6 +217,48 @@ pub fn parse_bin_op(op_name: &syn::Ident, input: syn::parse::ParseStream) -> syn let kind = extract_arithmetic_bin_op_kind(input)?; Ok(BinOp { kind, left, right }) } + "EQ" => { + let left: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let right: Box = input.parse()?; + let kind = BinOpKind::Relation(RelationOp::Eq); + Ok(BinOp { kind, left, right }) + } + "NEQ" => { + let left: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let right: Box = input.parse()?; + let kind = BinOpKind::Relation(RelationOp::NEq); + Ok(BinOp { kind, left, right }) + } + "GE" => { + let left: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let right: Box = input.parse()?; + let kind = BinOpKind::Relation(RelationOp::Ge); + Ok(BinOp { kind, left, right }) + } + "LE" => { + let left: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let right: Box = input.parse()?; + let kind = BinOpKind::Relation(RelationOp::Le); + Ok(BinOp { kind, left, right }) + } + "GT" => { + let left: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let right: Box = input.parse()?; + let kind = BinOpKind::Relation(RelationOp::Gt); + Ok(BinOp { kind, left, right }) + } + "LT" => { + let left: Box = input.parse()?; + let _comma: syn::Token![,] = input.parse()?; + let right: Box = input.parse()?; + let kind = BinOpKind::Relation(RelationOp::Lt); + Ok(BinOp { kind, left, right }) + } _ => Err(syn::Error::new_spanned( op_name.clone(), "Unknown `BinOp` variant name", @@ -225,6 +282,15 @@ impl quote::ToTokens for BinOp { } } } + 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), + } + } + } _ => todo!(), }); } diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 511c51317..d81d727be 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -395,7 +395,7 @@ impl syn::parse::Parse for Expr { "BoolToSigmaProp" => Ok(Expr::BoolToSigmaProp(input.parse()?)), "ValUse" => Ok(Expr::ValUse(input.parse()?)), "SelectField" => Ok(Expr::SelectField(input.parse()?)), - "ArithOp" => { + "ArithOp" | "EQ" | "NEQ" | "GE" | "LE" | "GT" | "LT" => { let content; let _paren = syn::parenthesized!(content in input); Ok(Expr::BinOp(super::bin_op::parse_bin_op(&name, &content)?)) diff --git a/ergotree-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index 64f929d8d..749dc758c 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -277,6 +277,84 @@ fn test_integer_constants() { } } +#[test] +fn test_eq_relation() { + let e = ergo_tree!(EQ(IntConstant(33), IntConstant(44))); + let expected = Expr::BinOp(BinOp { + kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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_arithmetic_in_block() { // This example comes from the scala JIT test suite: From 48473a4afc75f1db8e343585ddb014efa8e513cc Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Sun, 16 Oct 2022 00:15:20 +1100 Subject: [PATCH 16/20] Handle logical operators --- ergotree-ir/src/mir/bin_op.rs | 66 +++++++++++++---------- ergotree-ir/src/mir/expr.rs | 3 +- ergotree-macro/tests/tests.rs | 98 ++++++++++++++++++++++++++++++++++- 3 files changed, 139 insertions(+), 28 deletions(-) diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs index 893cf13e1..3a36b46ac 100644 --- a/ergotree-ir/src/mir/bin_op.rs +++ b/ergotree-ir/src/mir/bin_op.rs @@ -129,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))] @@ -208,57 +219,51 @@ 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 left: Box = input.parse()?; - let _comma: syn::Token![,] = input.parse()?; - let right: Box = input.parse()?; let _comma: syn::Token![,] = input.parse()?; let kind = extract_arithmetic_bin_op_kind(input)?; Ok(BinOp { kind, left, right }) } "EQ" => { - let left: Box = input.parse()?; - let _comma: syn::Token![,] = input.parse()?; - let right: Box = input.parse()?; let kind = BinOpKind::Relation(RelationOp::Eq); Ok(BinOp { kind, left, right }) } "NEQ" => { - let left: Box = input.parse()?; - let _comma: syn::Token![,] = input.parse()?; - let right: Box = input.parse()?; let kind = BinOpKind::Relation(RelationOp::NEq); Ok(BinOp { kind, left, right }) } "GE" => { - let left: Box = input.parse()?; - let _comma: syn::Token![,] = input.parse()?; - let right: Box = input.parse()?; let kind = BinOpKind::Relation(RelationOp::Ge); Ok(BinOp { kind, left, right }) } "LE" => { - let left: Box = input.parse()?; - let _comma: syn::Token![,] = input.parse()?; - let right: Box = input.parse()?; let kind = BinOpKind::Relation(RelationOp::Le); Ok(BinOp { kind, left, right }) } "GT" => { - let left: Box = input.parse()?; - let _comma: syn::Token![,] = input.parse()?; - let right: Box = input.parse()?; let kind = BinOpKind::Relation(RelationOp::Gt); Ok(BinOp { kind, left, right }) } "LT" => { - let left: Box = input.parse()?; - let _comma: syn::Token![,] = input.parse()?; - let right: Box = input.parse()?; 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 }) + } _ => Err(syn::Error::new_spanned( op_name.clone(), "Unknown `BinOp` variant name", @@ -284,11 +289,20 @@ impl quote::ToTokens for BinOp { } 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), - } + 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), + } } } _ => todo!(), diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index d81d727be..815ca0551 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -395,7 +395,8 @@ impl syn::parse::Parse for Expr { "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" => { + "ArithOp" | "EQ" | "NEQ" | "GE" | "LE" | "GT" | "LT" | "BinAnd" | "BinOr" + | "BinXor" => { let content; let _paren = syn::parenthesized!(content in input); Ok(Expr::BinOp(super::bin_op::parse_bin_op(&name, &content)?)) diff --git a/ergotree-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index 749dc758c..0a777a0f9 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -1,7 +1,7 @@ use ergotree_ir::{ bigint256::BigInt256, mir::{ - bin_op::{ArithOp, BinOp}, + bin_op::{ArithOp, BinOp, LogicalOp}, block::BlockValue, bool_to_sigma::BoolToSigmaProp, constant::Constant, @@ -355,6 +355,102 @@ fn test_lt_relation() { 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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Relation( + ergotree_ir::mir::bin_op::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: ergotree_ir::mir::bin_op::BinOpKind::Logical(LogicalOp::Xor), + left, + right, + }); + assert_eq!(e, and_expr); +} + #[test] fn test_arithmetic_in_block() { // This example comes from the scala JIT test suite: From 2ba5d17ff118f267895c7f4fcdde7b19d2ee7082 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Sun, 16 Oct 2022 00:33:45 +1100 Subject: [PATCH 17/20] Handle bitwise operations --- ergotree-ir/src/mir/bin_op.rs | 35 +++++++++++++- ergotree-ir/src/mir/expr.rs | 2 +- ergotree-macro/tests/tests.rs | 91 +++++++++++++++++++---------------- 3 files changed, 85 insertions(+), 43 deletions(-) diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs index 3a36b46ac..8184063cd 100644 --- a/ergotree-ir/src/mir/bin_op.rs +++ b/ergotree-ir/src/mir/bin_op.rs @@ -162,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))] @@ -264,6 +276,18 @@ pub fn parse_bin_op(op_name: &syn::Ident, input: syn::parse::ParseStream) -> syn 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", @@ -305,10 +329,19 @@ impl quote::ToTokens for BinOp { } } } - _ => todo!(), + 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 { diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 815ca0551..e4b0b3225 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -396,7 +396,7 @@ impl syn::parse::Parse for Expr { "ValUse" => Ok(Expr::ValUse(input.parse()?)), "SelectField" => Ok(Expr::SelectField(input.parse()?)), "ArithOp" | "EQ" | "NEQ" | "GE" | "LE" | "GT" | "LT" | "BinAnd" | "BinOr" - | "BinXor" => { + | "BinXor" | "BitAnd" | "BitOr" | "BitXor" => { let content; let _paren = syn::parenthesized!(content in input); Ok(Expr::BinOp(super::bin_op::parse_bin_op(&name, &content)?)) diff --git a/ergotree-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index 0a777a0f9..623fbcc53 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -1,7 +1,7 @@ use ergotree_ir::{ bigint256::BigInt256, mir::{ - bin_op::{ArithOp, BinOp, LogicalOp}, + bin_op::{ArithOp, BinOp, BinOpKind, BitOp, LogicalOp, RelationOp}, block::BlockValue, bool_to_sigma::BoolToSigmaProp, constant::Constant, @@ -281,9 +281,7 @@ fn test_integer_constants() { fn test_eq_relation() { let e = ergo_tree!(EQ(IntConstant(33), IntConstant(44))); let expected = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Eq, - ), + kind: BinOpKind::Relation(RelationOp::Eq), left: Expr::from(Constant::from(33_i32)).into(), right: Expr::from(Constant::from(44_i32)).into(), }); @@ -294,9 +292,7 @@ fn test_eq_relation() { fn test_neq_relation() { let e = ergo_tree!(NEQ(IntConstant(33), IntConstant(44))); let expected = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::NEq, - ), + kind: BinOpKind::Relation(RelationOp::NEq), left: Expr::from(Constant::from(33_i32)).into(), right: Expr::from(Constant::from(44_i32)).into(), }); @@ -307,9 +303,7 @@ fn test_neq_relation() { fn test_ge_relation() { let e = ergo_tree!(GE(IntConstant(33), IntConstant(44))); let expected = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Ge, - ), + kind: BinOpKind::Relation(RelationOp::Ge), left: Expr::from(Constant::from(33_i32)).into(), right: Expr::from(Constant::from(44_i32)).into(), }); @@ -320,9 +314,7 @@ fn test_ge_relation() { fn test_le_relation() { let e = ergo_tree!(LE(IntConstant(33), IntConstant(44))); let expected = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Le, - ), + kind: BinOpKind::Relation(RelationOp::Le), left: Expr::from(Constant::from(33_i32)).into(), right: Expr::from(Constant::from(44_i32)).into(), }); @@ -333,9 +325,7 @@ fn test_le_relation() { fn test_gt_relation() { let e = ergo_tree!(GT(IntConstant(33), IntConstant(44))); let expected = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Gt, - ), + kind: BinOpKind::Relation(RelationOp::Gt), left: Expr::from(Constant::from(33_i32)).into(), right: Expr::from(Constant::from(44_i32)).into(), }); @@ -346,9 +336,7 @@ fn test_gt_relation() { fn test_lt_relation() { let e = ergo_tree!(LT(IntConstant(33), IntConstant(44))); let expected = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Lt, - ), + kind: BinOpKind::Relation(RelationOp::Lt), left: Expr::from(Constant::from(33_i32)).into(), right: Expr::from(Constant::from(44_i32)).into(), }); @@ -363,24 +351,20 @@ fn test_bin_and_op() { )); let left = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Lt, - ), + 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: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Gt, - ), + 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: ergotree_ir::mir::bin_op::BinOpKind::Logical(LogicalOp::And), + kind: BinOpKind::Logical(LogicalOp::And), left, right, }); @@ -395,24 +379,20 @@ fn test_bin_or_op() { )); let left = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Lt, - ), + 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: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Gt, - ), + 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: ergotree_ir::mir::bin_op::BinOpKind::Logical(LogicalOp::Or), + kind: BinOpKind::Logical(LogicalOp::Or), left, right, }); @@ -427,30 +407,59 @@ fn test_bin_xor_op() { )); let left = Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Lt, - ), + 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: ergotree_ir::mir::bin_op::BinOpKind::Relation( - ergotree_ir::mir::bin_op::RelationOp::Gt, - ), + 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: ergotree_ir::mir::bin_op::BinOpKind::Logical(LogicalOp::Xor), + kind: BinOpKind::Logical(LogicalOp::Xor), left, right, }); assert_eq!(e, and_expr); } +#[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_arithmetic_in_block() { // This example comes from the scala JIT test suite: @@ -536,7 +545,7 @@ fn test_arithmetic_in_block() { let make_def = |op| { Expr::BinOp(BinOp { - kind: ergotree_ir::mir::bin_op::BinOpKind::Arith(op), + kind: BinOpKind::Arith(op), left: val_use3.clone(), right: val_use4.clone(), }) From 2d85550be8d64b20765bc085acc1ff54b27f0154 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Sun, 16 Oct 2022 01:22:58 +1100 Subject: [PATCH 18/20] Handle `LogicalNot` --- ergotree-ir/src/mir/expr.rs | 9 ++++++++- ergotree-ir/src/mir/logical_not.rs | 18 ++++++++++++++++++ ergotree-macro/tests/tests.rs | 14 ++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index e4b0b3225..baa582554 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -414,6 +414,11 @@ impl syn::parse::Parse for Expr { 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()?)) + } _ => Err(syn::Error::new_spanned(name, "Unknown `Expr` variant name")), } } @@ -466,7 +471,9 @@ impl quote::ToTokens for Expr { Expr::Or(_) => todo!(), Expr::Xor(_) => todo!(), Expr::Atleast(_) => todo!(), - Expr::LogicalNot(_) => todo!(), + Expr::LogicalNot(l) => { + quote! { ergotree_ir::mir::expr::Expr::LogicalNot(#l) } + } Expr::Negation(_) => todo!(), Expr::BitInversion(_) => todo!(), Expr::OptionGet(_) => todo!(), 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-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index 623fbcc53..02e3eb25b 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -7,6 +7,7 @@ use ergotree_ir::{ constant::Constant, expr::Expr, func_value::{FuncArg, FuncValue}, + logical_not::LogicalNot, select_field::{SelectField, TupleFieldIndex}, tuple::Tuple, val_def::{ValDef, ValId}, @@ -427,6 +428,19 @@ fn test_bin_xor_op() { 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))); From 84df5d402e3400886aefdd5837bfb56eecc784d3 Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Sun, 16 Oct 2022 02:00:12 +1100 Subject: [PATCH 19/20] Handle `Downcast` and `Upcast` --- ergotree-ir/src/mir/downcast.rs | 22 ++++++++++++++++++++++ ergotree-ir/src/mir/expr.rs | 18 ++++++++++++++++-- ergotree-ir/src/mir/upcast.rs | 22 ++++++++++++++++++++++ ergotree-macro/tests/tests.rs | 22 ++++++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) 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 baa582554..138cdd92f 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -419,6 +419,16 @@ impl syn::parse::Parse for Expr { 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()?)) + } _ => Err(syn::Error::new_spanned(name, "Unknown `Expr` variant name")), } } @@ -500,8 +510,12 @@ impl quote::ToTokens for Expr { Expr::BoolToSigmaProp(b) => { quote! { ergotree_ir::mir::expr::Expr::BoolToSigmaProp(#b) } } - Expr::Upcast(_) => todo!(), - Expr::Downcast(_) => todo!(), + 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!(), 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-macro/tests/tests.rs b/ergotree-macro/tests/tests.rs index 02e3eb25b..6b26f629b 100644 --- a/ergotree-macro/tests/tests.rs +++ b/ergotree-macro/tests/tests.rs @@ -5,11 +5,13 @@ use ergotree_ir::{ 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, }, @@ -474,6 +476,26 @@ fn test_bit_xor_op() { 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: From f9f91e11e60d7b8aee62da92d7ac6cf51a11d0bd Mon Sep 17 00:00:00 2001 From: Tim Ling <791016+kettlebell@users.noreply.github.com> Date: Mon, 17 Oct 2022 01:38:33 +1100 Subject: [PATCH 20/20] Initial `MethodCall` code --- ergotree-ir/src/ergotree_proc_macro.rs | 152 +++++++++++++- ergotree-ir/src/mir/expr.rs | 14 +- ergotree-ir/src/mir/extract_script_bytes.rs | 20 ++ ergotree-ir/src/mir/method_call.rs | 186 ++++++++++++++++++ ergotree-ir/src/types/sfunc.rs | 11 ++ ergotree-ir/src/types/smethod.rs | 44 +++++ ergotree-ir/src/types/stype.rs | 4 +- ergotree-ir/src/types/stype_companion.rs | 35 ++++ ergotree-ir/src/types/stype_param.rs | 11 ++ .../tests/method_call_smoke_tests.rs | 101 ++++++++++ 10 files changed, 568 insertions(+), 10 deletions(-) create mode 100644 ergotree-macro/tests/method_call_smoke_tests.rs diff --git a/ergotree-ir/src/ergotree_proc_macro.rs b/ergotree-ir/src/ergotree_proc_macro.rs index 9e06f93dc..357273f8d 100644 --- a/ergotree-ir/src/ergotree_proc_macro.rs +++ b/ergotree-ir/src/ergotree_proc_macro.rs @@ -37,10 +37,64 @@ pub fn extract_tpe_from_dot_typed( "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); @@ -98,21 +152,107 @@ pub fn extract_tpe_from_dot_typed( 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( - ExtractedType::FullySpecified(SType::SByte), //extract_tpe_from_dot_typed(content)?, + extract_tpe_from_dot_typed(&content_nested)?, ))) } - _ => { - unreachable!("unknown ident T in _.typed[Value[T]]") + t => { + unreachable!("unknown ident T in _.typed[Value[T]]: T = {}", t) } } } - _ => unreachable!("unknown ident T in _.typed[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()?; diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index 138cdd92f..14da722a3 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -429,6 +429,12 @@ impl syn::parse::Parse for Expr { 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")), } } @@ -462,7 +468,9 @@ impl quote::ToTokens for Expr { quote! { ergotree_ir::mir::expr::Expr::FuncValue(#f) } } Expr::Apply(_) => todo!(), - Expr::MethodCall(_) => 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) } @@ -493,7 +501,9 @@ impl quote::ToTokens for Expr { Expr::ExtractRegisterAs(_) => todo!(), Expr::ExtractBytes(_) => todo!(), Expr::ExtractBytesWithNoRef(_) => todo!(), - Expr::ExtractScriptBytes(_) => todo!(), + Expr::ExtractScriptBytes(e) => { + quote! { ergotree_ir::mir::expr::Expr::ExtractScriptBytes(#e) } + } Expr::ExtractCreationInfo(_) => todo!(), Expr::ExtractId(_) => todo!(), Expr::ByIndex(_) => todo!(), 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/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/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/stype.rs b/ergotree-ir/src/types/stype.rs index 3c5bab7c0..faf697d16 100644 --- a/ergotree-ir/src/types/stype.rs +++ b/ergotree-ir/src/types/stype.rs @@ -175,7 +175,7 @@ impl quote::ToTokens for SType { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { use quote::quote; tokens.extend(match self { - SType::STypeVar(_) => todo!(), + 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 }, @@ -197,7 +197,7 @@ impl quote::ToTokens for SType { quote! { ergotree_ir::types::stype::SType::SColl(Box::new(#tpe)) } } SType::STuple(s) => quote! { ergotree_ir::types::stype::SType::STuple(#s) }, - SType::SFunc(_) => todo!(), + 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 }, 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/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() + ) + ) + ); +}