2020-12-31 15:48:06 -08:00
|
|
|
use crate::idl::*;
|
2021-01-06 11:36:33 -08:00
|
|
|
use crate::parser::accounts;
|
2020-12-31 15:48:06 -08:00
|
|
|
use crate::parser::program;
|
|
|
|
use crate::AccountsStruct;
|
|
|
|
use anyhow::Result;
|
2021-01-02 22:40:17 -08:00
|
|
|
use heck::MixedCase;
|
2020-12-31 15:48:06 -08:00
|
|
|
use quote::ToTokens;
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Read;
|
2021-01-02 22:40:17 -08:00
|
|
|
use std::path::Path;
|
2020-12-31 15:48:06 -08:00
|
|
|
|
|
|
|
static DERIVE_NAME: &'static str = "Accounts";
|
|
|
|
|
|
|
|
// Parse an entire interface file.
|
2021-01-02 22:40:17 -08:00
|
|
|
pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
|
2020-12-31 15:48:06 -08:00
|
|
|
let mut file = File::open(&filename)?;
|
|
|
|
|
|
|
|
let mut src = String::new();
|
|
|
|
file.read_to_string(&mut src).expect("Unable to read file");
|
|
|
|
|
|
|
|
let f = syn::parse_file(&src).expect("Unable to parse file");
|
|
|
|
|
|
|
|
let p = program::parse(parse_program_mod(&f));
|
|
|
|
|
|
|
|
let accs = parse_accounts(&f);
|
|
|
|
|
|
|
|
let acc_names = {
|
|
|
|
let mut acc_names = HashSet::new();
|
|
|
|
|
|
|
|
for accs_strct in accs.values() {
|
2021-01-14 22:35:50 -08:00
|
|
|
for a in accs_strct.account_tys(&accs)? {
|
2020-12-31 15:48:06 -08:00
|
|
|
acc_names.insert(a);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
acc_names
|
|
|
|
};
|
|
|
|
|
2021-01-01 13:58:20 -08:00
|
|
|
let instructions = p
|
2020-12-31 15:48:06 -08:00
|
|
|
.rpcs
|
|
|
|
.iter()
|
|
|
|
.map(|rpc| {
|
|
|
|
let args = rpc
|
|
|
|
.args
|
|
|
|
.iter()
|
|
|
|
.map(|arg| {
|
|
|
|
let mut tts = proc_macro2::TokenStream::new();
|
|
|
|
arg.raw_arg.ty.to_tokens(&mut tts);
|
|
|
|
let ty = tts.to_string().parse().unwrap();
|
|
|
|
IdlField {
|
2021-01-02 16:24:35 -08:00
|
|
|
name: arg.name.to_string().to_mixed_case(),
|
2020-12-31 15:48:06 -08:00
|
|
|
ty,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
// todo: don't unwrap
|
|
|
|
let accounts_strct = accs.get(&rpc.anchor_ident.to_string()).unwrap();
|
2021-01-14 22:35:50 -08:00
|
|
|
let accounts = accounts_strct.idl_accounts(&accs);
|
2021-01-01 13:58:20 -08:00
|
|
|
IdlInstruction {
|
2021-01-02 16:24:35 -08:00
|
|
|
name: rpc.ident.to_string().to_mixed_case(),
|
2020-12-31 15:48:06 -08:00
|
|
|
accounts,
|
|
|
|
args,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
// All user defined types.
|
|
|
|
let mut accounts = vec![];
|
|
|
|
let mut types = vec![];
|
|
|
|
let ty_defs = parse_ty_defs(&f)?;
|
|
|
|
for ty_def in ty_defs {
|
2021-01-01 13:58:20 -08:00
|
|
|
if acc_names.contains(&ty_def.name) {
|
2020-12-31 15:48:06 -08:00
|
|
|
accounts.push(ty_def);
|
|
|
|
} else {
|
|
|
|
types.push(ty_def);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Idl {
|
|
|
|
version: "0.0.0".to_string(),
|
|
|
|
name: p.name.to_string(),
|
2021-01-01 13:58:20 -08:00
|
|
|
instructions,
|
2020-12-31 15:48:06 -08:00
|
|
|
types,
|
|
|
|
accounts,
|
2021-01-02 22:40:17 -08:00
|
|
|
metadata: None,
|
2020-12-31 15:48:06 -08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the main program mod.
|
|
|
|
fn parse_program_mod(f: &syn::File) -> syn::ItemMod {
|
|
|
|
let mods = f
|
|
|
|
.items
|
|
|
|
.iter()
|
|
|
|
.filter_map(|i| match i {
|
|
|
|
syn::Item::Mod(item_mod) => {
|
|
|
|
let mods = item_mod
|
|
|
|
.attrs
|
|
|
|
.iter()
|
|
|
|
.filter_map(|attr| {
|
|
|
|
let segment = attr.path.segments.last().unwrap();
|
|
|
|
if segment.ident.to_string() == "program" {
|
|
|
|
return Some(attr);
|
|
|
|
}
|
|
|
|
None
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
if mods.len() != 1 {
|
|
|
|
panic!("invalid program attribute");
|
|
|
|
}
|
|
|
|
Some(item_mod)
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
assert!(mods.len() == 1);
|
|
|
|
mods[0].clone()
|
|
|
|
}
|
|
|
|
|
2021-01-14 22:35:50 -08:00
|
|
|
// Parse all structs implementing the `Accounts` trait.
|
2020-12-31 15:48:06 -08:00
|
|
|
fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
|
|
|
|
f.items
|
|
|
|
.iter()
|
|
|
|
.filter_map(|i| match i {
|
|
|
|
syn::Item::Struct(i_strct) => {
|
|
|
|
for attr in &i_strct.attrs {
|
|
|
|
if attr.tokens.to_string().contains(DERIVE_NAME) {
|
2021-01-06 11:36:33 -08:00
|
|
|
let strct = accounts::parse(i_strct);
|
2020-12-31 15:48:06 -08:00
|
|
|
return Some((strct.ident.to_string(), strct));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
2021-01-14 22:35:50 -08:00
|
|
|
// TODO: parse manual implementations. Currently we only look
|
|
|
|
// for derives.
|
2020-12-31 15:48:06 -08:00
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse all user defined types in the file.
|
|
|
|
fn parse_ty_defs(f: &syn::File) -> Result<Vec<IdlTypeDef>> {
|
|
|
|
f.items
|
|
|
|
.iter()
|
|
|
|
.filter_map(|i| match i {
|
|
|
|
syn::Item::Struct(item_strct) => {
|
|
|
|
for attr in &item_strct.attrs {
|
|
|
|
if attr.tokens.to_string().contains(DERIVE_NAME) {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let syn::Visibility::Public(_) = &item_strct.vis {
|
|
|
|
let name = item_strct.ident.to_string();
|
|
|
|
let fields = match &item_strct.fields {
|
|
|
|
syn::Fields::Named(fields) => fields
|
|
|
|
.named
|
|
|
|
.iter()
|
|
|
|
.map(|f| {
|
|
|
|
let mut tts = proc_macro2::TokenStream::new();
|
|
|
|
f.ty.to_tokens(&mut tts);
|
|
|
|
Ok(IdlField {
|
2021-01-02 22:40:17 -08:00
|
|
|
name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
|
2020-12-31 15:48:06 -08:00
|
|
|
ty: tts.to_string().parse()?,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<IdlField>>>(),
|
|
|
|
_ => panic!("Only named structs are allowed."),
|
|
|
|
};
|
|
|
|
|
2021-01-01 13:58:20 -08:00
|
|
|
return Some(fields.map(|fields| IdlTypeDef {
|
|
|
|
name,
|
|
|
|
ty: IdlTypeDefTy::Struct { fields },
|
|
|
|
}));
|
2020-12-31 15:48:06 -08:00
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|