Commit ee179388 authored by Robin Freyler's avatar Robin Freyler
Browse files

[lang] Initial commit for the actual eDSL

parent b1e0e720
[package]
name = "pdsl_lang"
version = "0.1.0"
authors = ["Herobird <robbepop@web.de>"]
edition = "2018"
license = "MIT/Apache-2.0"
readme = "README.md"
# repository = "https://github.com/robbepop/substrate-contract"
# homepage = "https://github.com/robbepop/substrate-contract"
# documentation = "https://robbepop.github.io/pwasm-abi/substrate-contract/"
description = "[pDSL: Parity eDSL] Rust based eDSL for writing smart contracts for Substrate"
keywords = ["wasm", "parity", "webassembly", "blockchain", "edsl"]
categories = ["no-std", "embedded"]
include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
[dependencies]
pdsl_core = { path = "../core/" }
parity-codec = { version = "3.1", default-features = false, features = ["derive"] }
quote = "0.6"
syn = { version = "0.15", features = ["parsing", "full", "extra-traits"] }
proc-macro2 = "0.4"
heck = "0.3"
itertools = "0.7"
either = "1.5"
[lib]
name = "pdsl_lang"
proc-macro = true
[features]
default = ["std"]
std = [
"pdsl_core/std",
"parity-codec/std",
]
test-env = [
"pdsl_core/test-env",
]
../LICENSE
\ No newline at end of file
../README.md
\ No newline at end of file
use crate::parser::keywords;
use proc_macro2::Ident;
// use quote::ToTokens;
use syn::{
token,
punctuated::Punctuated,
ReturnType,
Type,
};
#[derive(Debug)]
pub struct Contract {
pub items: Vec<Item>,
}
impl Contract {
pub fn states<'a>(&'a self) -> impl Iterator<Item = &'a ItemState> + 'a {
self.items.iter().filter_map(|item| match *item {
Item::State(ref c) => Some(c),
_ => None,
})
}
pub fn impl_blocks<'a>(&'a self) -> impl Iterator<Item = &'a ItemImpl> + 'a {
self.items.iter().filter_map(|item| match *item {
Item::Impl(ref i) => Some(i),
_ => None,
})
}
}
#[derive(Debug)]
pub enum Item {
State(ItemState),
Impl(ItemImpl),
}
#[derive(Debug)]
pub struct ItemState {
pub attrs: Vec<syn::Attribute>,
pub struct_tok: token::Struct,
pub ident: Ident,
pub fields: syn::FieldsNamed,
}
#[derive(Debug)]
pub struct ItemImpl {
pub attrs: Vec<syn::Attribute>,
pub impl_tok: token::Impl,
pub self_ty: Ident,
pub brace_tok: token::Brace,
pub items: Vec<ItemImplMethod>,
}
#[derive(Debug)]
pub struct ItemImplMethod {
pub attrs: Vec<syn::Attribute>,
pub vis: MethodVisibility,
pub sig: MethodSig,
pub block: syn::Block,
}
#[derive(Debug, Clone)]
pub enum MethodVisibility {
External(ExternalVisibility),
Inherited,
}
impl MethodVisibility {
/// Returns `true` if this is an external visibility.
///
/// # Note
///
/// The `pub(external)` visibility is only used for contract messages.
pub fn is_external(&self) -> bool {
match self {
MethodVisibility::External(_) => true,
_ => false,
}
}
}
#[derive(Debug, Clone)]
pub struct ExternalVisibility {
pub pub_tok: token::Pub,
pub paren_tok: token::Paren,
pub external_tok: keywords::external,
}
#[derive(Debug, Clone)]
pub struct MethodSig {
pub ident: Ident,
pub decl: FnDecl,
}
#[derive(Debug, Clone)]
pub struct FnDecl {
pub fn_tok: token::Fn,
pub paren_tok: token::Paren,
pub inputs: Punctuated<FnArg, token::Comma>,
pub output: ReturnType,
}
#[derive(Debug, Clone)]
pub enum FnArg {
SelfRef(syn::ArgSelfRef),
SelfValue(syn::ArgSelf),
Captured(syn::ArgCaptured),
}
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::result::Result as StdResult;
pub use syn::parse::Error as SynError;
macro_rules! bail {
($($args:tt)*) => {
return Err(format_err!($($args)*).into())
}
}
macro_rules! format_err {
($tokens:expr, $($msg:tt)*) => {
match &$tokens {
t => {
syn::parse::Error::new_spanned(t, format_args!($($msg)*))
}
}
}
}
/// A collection of errors.
///
/// # Note
///
/// This is used to allow for reporting multiple errors at the same time.
#[derive(Debug)]
pub struct Errors {
errors: Vec<SynError>,
}
impl From<SynError> for Errors {
fn from(err: SynError) -> Errors {
Errors{
errors: vec![err],
}
}
}
impl From<Vec<Errors>> for Errors {
fn from(err: Vec<Errors>) -> Errors {
let result = err
.into_iter()
.flat_map(|v| v.errors)
.collect::<Vec<_>>();
assert!(result.len() > 0);
Errors{ errors: result }
}
}
/// Used to create a TokenStream from a list of errors
impl ToTokens for Errors {
fn to_tokens(&self, tokens: &mut TokenStream) {
for item in self.errors.iter() {
item.to_compile_error().to_tokens(tokens);
}
}
}
/// Result type alias for an error type which allows for accumulating errors.
pub type Result<T> = StdResult<T, Errors>;
use crate::{
ast,
errors::{Result, SynError}
};
use syn::token;
use proc_macro2::{Ident, Span};
/// A smart contract.
#[derive(Debug)]
pub struct Contract {
/// The name of the smart contract.
name: Ident,
/// The storage state fields.
state: State,
/// The deploy handler of the smart contract.
on_deploy: OnDeployHandler,
/// The messages of the smart contract.
messages: Vec<Message>,
/// Methods of the smart contract.
methods: Vec<Method>,
}
impl Contract {
/// Extracts the contract state from the contract items
/// and performs some integrity checks on it.
fn extract_state(contract: &ast::Contract) -> Result<(&Ident, State)> {
let states = contract.states().collect::<Vec<_>>();
if states.is_empty() {
return Err(
SynError::new(
Span::call_site(),
"requires exactly one contract state `struct`; found none"
).into()
)
}
if states.len() > 1 {
return Err(
SynError::new(
Span::call_site(),
format!(
"requires exactly one contract state `struct`; found {:?}",
states.len()
)
).into()
)
}
let state = states[0];
Ok((&state.ident, State::from(state)))
}
fn unpack_impl_blocks(contract_ident: &Ident, contract: &ast::Contract)
-> Result<(OnDeployHandler, Vec<Message>, Vec<Method>)>
{
let impl_blocks = contract.impl_blocks().collect::<Vec<_>>();
if impl_blocks.is_empty() {
return Err(
SynError::new(
Span::call_site(),
"requires at least one contract impl block `struct`; found none"
).into()
)
}
if impl_blocks.iter().any(|impl_block| impl_block.self_ty != *contract_ident) {
return Err(
SynError::new(
Span::call_site(),
format!(
"contract impl blocks must implement for the contract type: {}",
contract_ident
)
).into()
)
}
use itertools::Itertools as _;
let (mut messages, methods): (Vec<_>, Vec<_>) = impl_blocks
.into_iter()
.flat_map(|impl_block| impl_block.items.iter())
.partition_map(|msg_or_method| {
use either::Either;
if msg_or_method.vis.is_external() {
Either::Left(Message::from(msg_or_method))
} else {
Either::Right(Method::from(msg_or_method))
}
});
let deploy_handler_idx = messages
.iter()
.position(|msg| msg.sig.ident == "on_deploy");
let deploy_handler = match deploy_handler_idx {
Some(idx) => {
messages.swap_remove(idx).into()
}
None => {
return Err(
SynError::new(
Span::call_site(),
"could not find deploy handler (`on_deploy` message)"
).into()
)
}
};
Ok((deploy_handler, messages, methods))
}
pub fn from_ast(contract: &ast::Contract) -> Result<Self> {
let (ident, state) = Self::extract_state(contract)?;
let (deploy_handler, messages, methods) = Self::unpack_impl_blocks(ident, contract)?;
Ok(
Self {
name: ident.clone(),
state,
on_deploy: deploy_handler,
messages,
methods,
}
)
}
}
#[derive(Debug)]
pub struct State {
/// The attributes.
///
/// # Note
///
/// Also used for documentation.
pub attrs: Vec<syn::Attribute>,
/// The state fields.
///
/// # Note
///
/// These are the fields that are going to
/// be stored in the contract storage.
pub fields: syn::FieldsNamed,
}
impl From<&ast::ItemState> for State {
fn from(state: &ast::ItemState) -> Self {
Self {
attrs: state.attrs.clone(),
fields: state.fields.clone(),
}
}
}
/// The deploy handler of a smart contract.
///
/// # Note
///
/// This is what is getting called upon deploying a smart contract.
/// Normally this is used to initialize storage values.
#[derive(Debug)]
pub struct OnDeployHandler {
/// The attributes.
///
/// # Note
///
/// Also used for documentation.
pub attrs: Vec<syn::Attribute>,
/// The function declaration.
pub decl: ast::FnDecl,
/// The actual implementation.
pub block: syn::Block,
}
impl From<Message> for OnDeployHandler {
fn from(msg: Message) -> Self {
Self {
attrs: msg.attrs,
decl: msg.sig.decl,
block: msg.block,
}
}
}
/// A message that is handled by the smart contract.
///
/// # Note
///
/// Messages of a smart contract are only callable externally.
/// They are used to communicate with other smart contracts.
#[derive(Debug)]
pub struct Message {
/// The attributes.
///
/// # Note
///
/// Also used for documentation.
pub attrs: Vec<syn::Attribute>,
/// The message signature.
///
/// # Note
///
/// This also holds the name of the message.
pub sig: ast::MethodSig,
/// The actual implementation.
pub block: syn::Block,
}
impl From<&ast::ItemImplMethod> for Message {
fn from(impl_method: &ast::ItemImplMethod) -> Self {
Self {
attrs: impl_method.attrs.clone(),
sig: impl_method.sig.clone(),
block: impl_method.block.clone(),
}
}
}
/// A method defined on the smart contract.
#[derive(Debug)]
pub struct Method {
/// The attributes.
///
/// # Note
///
/// Also used for documentation.
pub attrs: Vec<syn::Attribute>,
/// The method visibility.
///
/// # Note
///
/// Currently only inherent visibility (private) is
/// available for methods.
pub vis: ast::MethodVisibility,
/// The method signature.
///
/// # Note
///
/// This also holds the name of the method.
pub sig: ast::MethodSig,
/// The actual implementation.
pub block: syn::Block,
}
impl From<&ast::ItemImplMethod> for Method {
fn from(impl_method: &ast::ItemImplMethod) -> Self {
Self {
attrs: impl_method.attrs.clone(),
sig: impl_method.sig.clone(),
vis: impl_method.vis.clone(),
block: impl_method.block.clone(),
}
}
}
use proc_macro2::{Ident, Span};
use std::fmt::Display;
/// Utilities for operating on `Ident` instances.
pub trait IdentExt: Display {
/// Creates a string out of the ident's name.
fn to_owned_string(&self) -> String {
format!("{}", self)
}
/// Creates a new Ident from the given `str`.
fn from_str<T: AsRef<str>>(s: T) -> Ident {
Ident::new(s.as_ref(), Span::call_site())
}
}
impl IdentExt for Ident {}
#![recursion_limit = "128"]
extern crate proc_macro;
use quote::{
// quote,
ToTokens,
};
#[proc_macro]
pub fn contract(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
match contract_gen_inner(input) {
Ok(tokens) => tokens,
Err(err) => { err.into_token_stream().into() },
}
}
#[macro_use]
mod errors;
mod ast;
mod hir;
mod parser;
mod ident_ext;
use errors::Result;
fn contract_gen_inner(input: proc_macro::TokenStream) -> Result<proc_macro::TokenStream> {
let ast_contract = parser::parse_contract(input.clone())?;
let _hir_contract = hir::Contract::from_ast(&ast_contract)?;
// gen::gir::generate(&hir_program)?;
// let tokens = gen::codegen(&hir_program);
// Ok(tokens.into())
Ok(proc_macro::TokenStream::new())
}
use crate::proc_macro;
// use proc_macro2::TokenStream;
use syn::{
self,
Token,
parse::{
Parse,
ParseStream,
Result,
},
};
use crate::{
ast,
};
pub mod keywords {
use syn::custom_keyword;
custom_keyword!(external);
custom_keyword!(env);
}
pub fn parse_contract(token_stream: proc_macro::TokenStream) -> Result<ast::Contract> {
syn::parse(token_stream).map_err(|e| e.into())
}
impl Parse for ast::Contract {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Ok(ast::Contract {
items: ast::Item::parse_outer(input)?,
})
}
}
impl ast::Item {
fn parse_outer(input: ParseStream<'_>) -> Result<Vec<Self>> {
let mut res = Vec::new();
while !input.is_empty() {
res.push(input.parse()?);
}
Ok(res)
}
}
impl Parse for ast::Item {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let attrs = syn::Attribute::parse_outer(input)?;
let lookahead = input.lookahead1();
if lookahead.peek(Token![struct]) {
input.parse()
.map(|mut state: ast::ItemState| {
state.attrs = attrs;
ast::Item::State(state)
})
} else if lookahead.peek(Token![impl]) {
input.parse()
.map(|mut block: ast::ItemImpl| {
block.attrs = attrs;
ast::Item::Impl(block)
})
} else {
Err(lookahead.error())
}
}
}
impl Parse for ast::ItemState {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let struct_tok = input.parse()?;
let ident = input.parse()?;
let fields = input.parse()?;
Ok(Self {
attrs: vec![],
struct_tok,
ident,
fields,
})
}
}
impl Parse for ast::ItemImpl {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let impl_tok = input.parse()?;
let self_ty = input.parse()?;
let (brace_tok, inner_attrs, items) = {
let content;
let brace_tok = syn::braced!(content in input);
let inner_attrs = content.call(syn::Attribute::parse_inner)?;
let mut items = Vec::new();
while !content.is_empty() {
items.push(content.parse()?);
}
(brace_tok, inner_attrs, items)
};
Ok(Self {
attrs: inner_attrs,
impl_tok,
self_ty,
brace_tok,
items,
})
}
}