Skip to content

Commit bf66073

Browse files
committed
[wip]
1 parent 9274576 commit bf66073

File tree

6 files changed

+374
-0
lines changed

6 files changed

+374
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ result
22
.direnv
33
.pre-commit-config.yaml
44
.DS_Store
5+
target

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
./hercules-ci.nix
2424

2525
./plutus-ledger-api/build.nix
26+
./is-plutus-data-derive/build.nix
2627
];
2728
debug = true;
2829
systems = [ "x86_64-linux" "x86_64-darwin" ];

is-plutus-data-derive/Cargo.lock

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

is-plutus-data-derive/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "plutus-data-derive"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
proc-macro2 = "1.0.87"
8+
quote = "1.0.37"
9+
syn = { version = "2.0.79", features = ["full"]}
10+
thiserror = "1.0.64"
11+
12+
[lib]
13+
proc-macro = true

is-plutus-data-derive/build.nix

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{ inputs, ... }: {
2+
perSystem = { config, system, ... }:
3+
let
4+
rustFlake =
5+
inputs.flake-lang.lib.${system}.rustFlake {
6+
src = ./.;
7+
version = "0";
8+
crateName = "is-plutus-data-derive";
9+
devShellHook = config.settings.shell.hook;
10+
cargoNextestExtraArgs = "--all-features";
11+
};
12+
13+
in
14+
{
15+
inherit (rustFlake) packages checks devShells;
16+
};
17+
}

is-plutus-data-derive/src/lib.rs

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
use std::str::FromStr;
2+
3+
use quote::ToTokens;
4+
use syn::{
5+
ext::IdentExt,
6+
parse::{Parse, ParseStream},
7+
parse_macro_input, parse_quote,
8+
punctuated::Punctuated,
9+
spanned::Spanned,
10+
token::Impl,
11+
Attribute, Block, Data, DataEnum, DeriveInput, Error, Expr, Fields, FieldsNamed, FieldsUnnamed,
12+
Ident, ImplGenerics, LitStr, Meta, Result, Stmt, TypeGenerics, WhereClause,
13+
};
14+
15+
#[proc_macro_derive(IsPlutusData, attributes(plutus_data_derive_strategy))]
16+
pub fn derive_is_plutus_data(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
17+
let input = parse_macro_input!(input as DeriveInput);
18+
get_is_plutus_data_instance(input)
19+
.unwrap()
20+
.into_token_stream()
21+
.into()
22+
}
23+
24+
fn get_is_plutus_data_instance(input: DeriveInput) -> Result<Impl> {
25+
let type_name = &input.ident;
26+
27+
let strategy = get_derive_strategy(&input)?;
28+
29+
let (encoder, decoder) = match strategy {
30+
DeriveStrategy::Newtype => get_newtype_encoder_decoder(&input),
31+
DeriveStrategy::List => get_list_encoder_decoder(&input),
32+
DeriveStrategy::Constr => get_constr_encoder_decoder(&input),
33+
}?;
34+
35+
let mut generics = input.generics;
36+
37+
// TODO(chfanghr): Do we care about type role?
38+
generics.type_params_mut().for_each(|param| {
39+
param
40+
.bounds
41+
.push(parse_quote!(plutus_ledger_api::plutus_Data::IsPlutusData));
42+
});
43+
44+
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
45+
46+
Ok(parse_quote!(
47+
impl #impl_generics plutus_ledger_api::plutus_data::IsPlutusData for #type_name #type_generics #where_clause {
48+
fn to_plutus_data(&self) -> plutus_ledger_api::plutus_data::PlutusData {
49+
#encoder
50+
}
51+
52+
fn from_plutus_data(plutus_data: &plutus_ledger_api::plutus_data::PlutusData) -> Result<Self, plutus_ledger_api::plutus_data::PlutusDataError>
53+
where Self: Sized; {
54+
#decoder
55+
}
56+
}
57+
))
58+
}
59+
60+
#[derive(Debug)]
61+
enum DeriveStrategy {
62+
Newtype,
63+
List,
64+
Constr,
65+
}
66+
67+
#[derive(Debug, thiserror::Error)]
68+
enum DeriveStrategyError {
69+
#[error("Unknown strategy {0}. Should be one of Newtype, List and Constr.")]
70+
UnknownStrategy(String),
71+
#[error("Unable to parse strategy. Should be an Ident.")]
72+
UnexpectedToken,
73+
#[error("More than one strategies specified.")]
74+
MoreThanOneSpecified,
75+
}
76+
77+
impl Default for DeriveStrategy {
78+
fn default() -> Self {
79+
Self::Constr
80+
}
81+
}
82+
83+
impl FromStr for DeriveStrategy {
84+
type Err = DeriveStrategyError;
85+
86+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
87+
match s {
88+
"Newtype" => Ok(Self::Newtype),
89+
"List" => Ok(Self::List),
90+
"Constr" => Ok(Self::Constr),
91+
_ => Err(DeriveStrategyError::UnknownStrategy(s.into())),
92+
}
93+
}
94+
}
95+
impl Parse for DeriveStrategy {
96+
fn parse(input: ParseStream) -> Result<Self> {
97+
let ident = input.call(Ident::parse)?;
98+
Self::from_str(&ident.to_string()).map_err(|unknown_strategy| {
99+
Error::new(
100+
ident.span(),
101+
format!("unknown strategy: {}", unknown_strategy),
102+
)
103+
})
104+
}
105+
}
106+
107+
fn try_parse_derive_strategy(attr: &Attribute) -> Option<Result<DeriveStrategy>> {
108+
let value = match &attr.meta {
109+
Meta::NameValue(name_value) => name_value
110+
.path
111+
.is_ident("plutus_data_derive_strategy")
112+
.then_some(&name_value.value),
113+
_ => None,
114+
}?;
115+
116+
Some(match &value {
117+
Expr::Path(path) => (|| -> Result<DeriveStrategy> {
118+
let ident = path.path.require_ident()?;
119+
DeriveStrategy::from_str(&ident.to_string())
120+
.map_err(|err| Error::new(ident.span(), err))
121+
})(),
122+
_ => Err(Error::new(
123+
value.span(),
124+
DeriveStrategyError::UnexpectedToken,
125+
)),
126+
})
127+
}
128+
129+
fn get_derive_strategy(input: &DeriveInput) -> Result<DeriveStrategy> {
130+
let mut derive_strategy_results: Vec<_> = input
131+
.attrs
132+
.iter()
133+
.map(try_parse_derive_strategy)
134+
.flatten()
135+
.collect();
136+
137+
match derive_strategy_results.len() {
138+
0 => Ok(DeriveStrategy::default()),
139+
1 => derive_strategy_results.remove(0),
140+
_ => Err(Error::new(
141+
input.span(),
142+
DeriveStrategyError::MoreThanOneSpecified,
143+
)),
144+
}
145+
}
146+
147+
#[derive(Debug, thiserror::Error)]
148+
enum NewtypeStrategyError {
149+
#[error("Only struct types are supported by newtype strategy")]
150+
UnexpectedDataVariant,
151+
#[error("Newtype derivation expects exactly one filed")]
152+
NotSingleField,
153+
}
154+
155+
fn get_newtype_encoder_decoder(input: &DeriveInput) -> Result<(Block, Block)> {
156+
let s = match &input.data {
157+
Data::Struct(s) => Ok(s),
158+
_ => Err(Error::new(
159+
input.span(),
160+
NewtypeStrategyError::UnexpectedDataVariant,
161+
)),
162+
}?;
163+
164+
if s.fields.len() != 1 {
165+
Err(Error::new(
166+
input.span(),
167+
NewtypeStrategyError::NotSingleField,
168+
))?
169+
}
170+
171+
let field = s.fields.iter().next().unwrap();
172+
173+
let encoder = parse_quote!({
174+
self.#field.to_plutus_data()
175+
});
176+
177+
let decoder = match &field.ident {
178+
Some(field_name) => {
179+
parse_quote!({
180+
Ok(Self {
181+
#field_name: plutus_ledger_api::plutus_data::IsPlutusData::from_plutus_data(plutus_data)?
182+
})
183+
})
184+
}
185+
None => {
186+
parse_quote!({
187+
Ok(Self(
188+
plutus_ledger_api::plutus_data::IsPlutusData::from_plutus_data(plutus_data)?,
189+
))
190+
})
191+
}
192+
};
193+
194+
Ok((encoder, decoder))
195+
}
196+
197+
#[derive(Debug, thiserror::Error)]
198+
enum ListStrategyError {
199+
#[error("Only struct types are supported by list strategy")]
200+
UnexpectedDataVariant,
201+
}
202+
203+
fn get_list_encoder_decoder(input: &DeriveInput) -> Result<(Block, Block)> {
204+
match &input.data {
205+
Data::Struct(s) => {
206+
let fields = &s.fields;
207+
208+
let encode_field_stmts = fields
209+
.iter()
210+
.map(|f| -> Stmt { parse_quote!(self.#f.to_plutus_data()) });
211+
212+
let encoder: Block = parse_quote!({
213+
plutus_ledger_api::plutus_data::PlutusData::List(vec![ #(#encode_field_stmts),*])
214+
});
215+
216+
let decoder: Block = match &fields {
217+
Fields::Named(named_fields) => get_list_decoder_for_named_struct(named_fields),
218+
Fields::Unnamed(unnamed_fields) => {
219+
get_list_decoder_for_unnamed_struct(unnamed_fields)
220+
}
221+
Fields::Unit => parse_quote!({ Self }),
222+
};
223+
224+
Ok((encoder, decoder))
225+
}
226+
_ => Err(Error::new(
227+
input.span(),
228+
ListStrategyError::UnexpectedDataVariant,
229+
)),
230+
}
231+
}
232+
233+
fn get_list_decoder_for_named_struct(fields: &FieldsNamed) -> Block {
234+
let len = fields.named.len();
235+
236+
let decode_field_stmts = fields.named.iter().enumerate().map(|(i, f)| -> Stmt {
237+
parse_quote!(
238+
#f: plutus_ledger_api::plutus_data::IsPlutusData::from_plutus_data(fields[#i])?
239+
)
240+
});
241+
242+
parse_quote!({
243+
let fields = plutus_ledger_api::plutus_data::parse_list(plutus_data)?; // TODO(chfanghr): implement parse_list
244+
let fields = plutus_ledger_api::plutus_data::parse_constr_with_tag::parse_fixed_len_plutus_data_list::<#len>(fields.as_slice())?;
245+
246+
Ok(Self {
247+
#(#decode_field_stmts),*
248+
})
249+
})
250+
}
251+
252+
fn get_list_decoder_for_unnamed_struct(fields: &FieldsUnnamed) -> Block {
253+
let len = fields.unnamed.len();
254+
let indexes = 0..len;
255+
256+
parse_quote!({
257+
let fields = plutus_ledger_api::plutus_data::parse_list(plutus_data)?;
258+
let fields = plutus_ledger_api::plutus_data::parse_constr_with_tag::parse_fixed_len_plutus_data_list::<#len>(fields.as_slice())?;
259+
260+
Ok(Self (
261+
#(plutus_ledger_api::plutus_data::IsPlutusData::from_plutus_data(fields[#indexes])?),*
262+
))
263+
})
264+
}
265+
266+
#[derive(Debug, thiserror::Error)]
267+
enum ConstrStrategyError {
268+
#[error("Union types are supported by constr strategy")]
269+
UnexpectedDataVariant,
270+
}
271+
272+
fn get_constr_encoder_decoder(input: &DeriveInput) -> Result<(Block, Block)> {
273+
todo!()
274+
}

0 commit comments

Comments
 (0)