use syn::ItemStruct;
use translate::type_name::{self, parse_path};
extern crate syn;
pub mod common;
pub mod errors;
pub mod instruction;
pub mod translate;
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
pub fn parse_to_dtr(rust_code: &str) -> Result<String, errors::NotTranslatableError> {
let parsed_ast = syn::parse_file(rust_code).unwrap();
let mut user_defined_types: Vec<ItemStruct> = Vec::new();
let mut dtr_code = String::new();
for item in parsed_ast.items {
match item {
syn::Item::Struct(item_struct) => {
item_struct.attrs.iter().for_each(|attr| {
if parse_path(attr.meta.path()) == "contract" {
dtr_code.push_str(&format!("[Contract]: {}\n\n", item_struct.ident));
} else if parse_path(attr.meta.path()) == "contracttype" {
user_defined_types.push(item_struct.clone());
}
});
}
syn::Item::Impl(item_impl) => {
let mut is_a_contract_impl = false;
if item_impl.attrs.len() > 0 {
item_impl.attrs.iter().for_each(|attr| {
if parse_path(attr.meta.path()) == "contractimpl" {
is_a_contract_impl = true;
}
});
}
if !is_a_contract_impl {
continue;
}
dtr_code.push_str("[Functions]:\n");
item_impl.items.iter().for_each(|item_impl_item| {
if let syn::ImplItem::Fn(method) = item_impl_item {
let method_name = method.sig.ident.to_string();
dtr_code.push_str(&format!("-() [{}]\n", method_name));
dtr_code.push_str("\t* Inputs:\n");
dtr_code.push_str("\t{ \n");
method.sig.inputs.iter().for_each(|input| {
if let syn::FnArg::Typed(pat_type) = input {
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
if pat_ident.ident != "env" {
match translate::type_name::figure_out_type(&pat_type.ty) {
Ok(type_name) => {
dtr_code.push_str(&format!(
"\t\t{}: {}\n",
pat_ident.ident, type_name
));
}
Err(e) => {
dtr_code.push_str(&format!("Error: {:?}", e));
}
}
}
}
}
});
dtr_code.push_str("\t}\n");
if let syn::ReturnType::Type(_, ty) = &method.sig.output {
dtr_code.push_str(translate::parse_return_type(ty).as_str());
}
dtr_code.push_str("\t* Instructions:\n");
dtr_code.push_str("\t\t$\n");
let block = &method.block;
let mut index = 1;
let total_block_stmts = block.stmts.len();
block.stmts.iter().for_each(|stmt| {
if index != 1 {
dtr_code.push_str("\n\t\t\t");
} else {
dtr_code.push_str("\t\t\t");
}
let assignment:Option<String> = if index == total_block_stmts { Some("Thing_to_return".to_string()) } else { None };
match translate::expression::supported::block_expression::parse_block_stmt(&stmt, assignment) {
Ok(block_str) => {
let mut instructions_as_strings: Vec<String> = Vec::new();
block_str.iter().for_each(|instr|instructions_as_strings.push(instr.as_str()));
if index == total_block_stmts {
instructions_as_strings.push("{ instruction: Return, input: (Thing_to_return) }".to_string());
}
dtr_code.push_str(&instructions_as_strings.join("\n\t\t\t"));
}
Err(e) => {
dtr_code.push_str(&format!("Error: {:?}", e));
}
}
index += 1;
});
dtr_code.push_str("\n\t\t$\n");
}
});
dtr_code.push_str(":[Functions]\n");
}
_ => {} }
}
dtr_code.push_str("\n\n[User Defined Types]:");
user_defined_types.iter().for_each(|item_struct| {
dtr_code.push_str(&format!("\n\n\t* ({})\n", item_struct.ident));
dtr_code.push_str("\t{\n");
item_struct.fields.iter().for_each(|field| {
if let syn::Type::Path(type_path) = &field.ty {
dtr_code.push_str(&format!(
"\t\t{}: {}\n",
field.ident.as_ref().unwrap(),
type_name::parse_path(&type_path.path)
));
}
});
dtr_code.push_str("\t}\n");
});
dtr_code.push_str("\n:[User Defined Types]\n");
Ok(dtr_code)
}
#[cfg(test)]
mod tests {
use super::*;
const ANSWER_TO_LIFE_CONTRACT: &str = r#"
#![no_std]
use soroban_sdk::{contract, contractimpl, Env};
#[contract]
pub struct AnswerToLifeContract;
#[contractimpl]
impl AnswerToLifeContract {
pub fn fourty_two(env: Env) -> u32 {
42
}
}
"#;
const INCREMENT_ANSWER_TO_LIFE_CONTRACT: &str = r#"
#![no_std]
use soroban_sdk::{contract, contractimpl, Env};
#[contract]
pub struct IncrementAnswerToLifeContract;
#[contractimpl]
impl IncrementAnswerToLifeContract {
pub fn fourty_two_and_then_some(env: Env, and_then_some: u32) -> u32 {
42 + and_then_some
}
}
"#;
const CUSTOM_TYPES_CONTRACT: &str = r#"
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Env, Symbol};
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct State {
pub count: u32,
pub last_incr: u32,
}
const STATE: Symbol = symbol_short!("STATE");
#[contract]
pub struct IncrementContract;
#[contractimpl]
impl IncrementContract {
/// Increment increments an internal counter, and returns the value.
pub fn increment(env: Env, incr: u32) -> u32 {
// Get the current count.
let mut state = Self::get_state(env.clone());
// Increment the count.
state.count += incr;
state.last_incr = incr;
// Save the count.
env.storage().instance().set(&STATE, &state);
// Return the count to the caller.
state.count
}
/// Return the current state.
pub fn get_state(env: Env) -> State {
unwrap_or(State {
count: 0,
last_incr: 0,
}) // If no value set, assume 0.
}
}
"#;
#[test]
fn test_parse_answer_to_life_contract() {
let expected_dtr_code = r#"[Contract]: AnswerToLifeContract
[Functions]:
-() [fourty_two]* Inputs:{ }* Output: u32* Instructions:${ instruction: assign, input: (42), assign: Thing_to_return }{ instruction: Return, input: (Thing_to_return) }$:[Functions][User Defined Types]::[User Defined Types]"#;
let actual_dtr_code = parse_to_dtr(ANSWER_TO_LIFE_CONTRACT);
match actual_dtr_code {
Ok(dtr_code) => {
assert_eq!(
dtr_code.replace("\t", "").replace("\n", ""),
expected_dtr_code.replace("\t", "").replace("\n", "")
);
}
Err(err) => {
panic!("Error: {:?}", err);
}
}
}
#[test]
fn test_parse_increment_answer_to_life_contract() {
let expected_dtr_code = r#"
[Contract]: IncrementAnswerToLifeContract
[Functions]:
-() [fourty_two_and_then_some]* Inputs:{ and_then_some: u32}* Output: u32* Instructions:${ instruction: assign, input: (42), assign: BINARY_EXPRESSION_LEFT }{ instruction: assign, input: (and_then_some), assign: BINARY_EXPRESSION_RIGHT }{ instruction: add, input: (BINARY_EXPRESSION_LEFT, BINARY_EXPRESSION_RIGHT), assign: Thing_to_return }{ instruction: Return, input: (Thing_to_return) }$:[Functions][User Defined Types]::[User Defined Types]"#;
let actual_dtr_code = parse_to_dtr(INCREMENT_ANSWER_TO_LIFE_CONTRACT);
match actual_dtr_code {
Ok(dtr_code) => {
assert_eq!(
dtr_code.replace("\t", "").replace("\n", ""),
expected_dtr_code.replace("\t", "").replace("\n", "")
);
}
Err(err) => {
panic!("Error: {:?}", err);
}
}
}
#[test]
fn test_parse_custom_types_contract() {
let expected_dtr_code = r#"
[Contract]: IncrementContract
[Functions]:
-() [increment]
* Inputs:
{
incr: u32
}
* Output: u32
* Instructions:
$
{ instruction: assign, input: (get_state), assign: CALL_EXPRESSION_FUNCTION }
{ instruction: assign, input: (env), assign: METHOD_CALL_EXPRESSION }
{ instruction: evaluate, input: (clone, METHOD_CALL_EXPRESSION), assign: 1_CALL_EXPRESSION_ARG }
{ instruction: evaluate, input: (CALL_EXPRESSION_FUNCTION, 1_CALL_EXPRESSION_ARG), assign: state }
{ instruction: assign, input: (state), assign: FIELD_BASE }
{ instruction: field, input: (FIELD_BASE, count), assign: BINARY_EXPRESSION_LEFT }
{ instruction: assign, input: (incr), assign: BINARY_EXPRESSION_RIGHT }
{ instruction: add_and_assign, input: (BINARY_EXPRESSION_LEFT, BINARY_EXPRESSION_RIGHT) }
{ instruction: assign, input: (state), assign: FIELD_BASE }
{ instruction: field, input: (FIELD_BASE, last_incr), assign: ASSIGN_EXPRESSION_LEFT }
{ instruction: assign, input: (incr), assign: ASSIGN_EXPRESSION_RIGHT }
{ instruction: assign, input: (ASSIGN_EXPRESSION_LEFT, ASSIGN_EXPRESSION_RIGHT) }
{ instruction: assign, input: (env), assign: METHOD_CALL_EXPRESSION }
{ instruction: evaluate, input: (storage, METHOD_CALL_EXPRESSION), assign: METHOD_CALL_EXPRESSION }
{ instruction: evaluate, input: (instance, METHOD_CALL_EXPRESSION), assign: METHOD_CALL_EXPRESSION }
{ instruction: assign, input: (STATE), assign: 1_METHOD_CALL_ARG }
{ instruction: assign, input: (state), assign: 2_METHOD_CALL_ARG }
{ instruction: evaluate, input: (set, METHOD_CALL_EXPRESSION, 1_METHOD_CALL_ARG, 2_METHOD_CALL_ARG), assign: METHOD_CALL_RESULT }
{ instruction: assign, input: (state), assign: FIELD_BASE }
{ instruction: field, input: (FIELD_BASE, count), assign: Thing_to_return }
{ instruction: Return, input: (Thing_to_return) }
$
-() [get_state]
* Inputs:
{
}
* Output: State
* Instructions:
$
{ instruction: assign, input: (unwrap_or), assign: CALL_EXPRESSION_FUNCTION }
{ instruction: assign, input: (0), assign: count }
{ instruction: assign, input: (0), assign: last_incr }
{ instruction: initialize_udt, input: (State, count, last_incr), assign: 1_CALL_EXPRESSION_ARG }
{ instruction: evaluate, input: (CALL_EXPRESSION_FUNCTION, 1_CALL_EXPRESSION_ARG), assign: Thing_to_return }
{ instruction: Return, input: (Thing_to_return) }
$
:[Functions]
[User Defined Types]:
* (State)
{
count: u32
last_incr: u32
}
:[User Defined Types]
"#;
let actual_dtr_code = parse_to_dtr(CUSTOM_TYPES_CONTRACT);
match actual_dtr_code {
Ok(dtr_code) => {
println!("dtr_code: {:}\n", dtr_code);
assert_eq!(
dtr_code.replace("\t", "").replace("\n", ""),
expected_dtr_code.replace("\t", "").replace("\n", "")
);
}
Err(err) => {
panic!("Error: {:?}", err);
}
}
}
}