1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use quote::ToTokens;
use std::borrow::Borrow;
use std::borrow::BorrowMut;
use syn::{parse, parse_str, FnArg, ItemFn, Pat, Stmt};

fn gen_libsql_type(ty: &syn::Type) -> Box<syn::Type> {
    match ty.to_owned().into_token_stream().to_string().as_str() {
        "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "bool" | "char" => {
            Box::new(syn::Type::from(parse_str::<syn::TypePath>("i64").unwrap()))
        }
        "f32" | "f64" => Box::new(syn::Type::from(parse_str::<syn::TypePath>("f64").unwrap())),
        _ => Box::new(syn::Type::from(parse_str::<syn::TypePath>("i32").unwrap())),
    }
}

fn gen_from_expr(ty: &syn::Type, id: &str) -> String {
    let type_str = ty.to_owned().into_token_stream().to_string();
    match type_str.as_str() {
        "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "bool" | "char" => {
            format!("{} as i64", id)
        }
        "f32" | "f64" => format!("{} as f64", id),
        _ => format!("<{}>::from_libsql_type({})", type_str, id),
    }
}

fn gen_into_expr(return_type_str: &str, expr: syn::Expr) -> syn::Expr {
    let expr_str = expr.into_token_stream().to_string();
    parse_str::<syn::Expr>(&match return_type_str {
        "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "bool" | "char" => {
            format!("{} as i64", expr_str)
        }
        "f32" | "f64" => format!("{} as f64", expr_str),
        _ => format!("{}.into_libsql_type()", expr_str),
    })
    .unwrap()
}

/* libSQL bindgen transforms a native Rust function into a form compilable to Wasm
** and immediately usable as a libSQL user-defined function.
** It follows the following ABI rules for libSQL types (INTEGER, FLOAT, STRING, BLOB, NULL):
**  1. integers are passed as i64
**  2. floats are passed as f64
**  3. Strings, blobs and nulls are pointers,
**     with the first byte indicating the type - SQLITE_TEXT, SQLITE_BLOB or SQLITE_NULL
**     and then:
**       - strings are encoded as null-terminated strings
**       - blobs are encoded as [4 bytes of size information][data]
**       - nulls are encoded as nothing, because the type byte already indicates it's null
*/    
#[proc_macro_attribute]
pub fn libsql_bindgen(_attrs: TokenStream, item: TokenStream) -> TokenStream {
    let input = match parse::<ItemFn>(item) {
        Ok(i) => i,
        Err(_) => {
            return TokenStream::from(
                syn::Error::new(
                    Span::call_site(),
                    "libsql_bindgen operates on function definitions only",
                )
                .to_compile_error(),
            )
        }
    };

    let mut native_sig = input.sig.clone();
    let mut generated_sig = input.sig.clone();
    native_sig.ident = syn::Ident::new(
        format!("__libsql_native_{}", generated_sig.ident).as_str(),
        Span::call_site(),
    );
    // Translate parameter types
    for raw_param in &mut generated_sig.inputs {
        if let &mut FnArg::Typed(ref mut param) = raw_param {
            param.ty = gen_libsql_type(&param.ty);
            if let &mut Pat::Ident(ref mut id) = param.pat.borrow_mut() {
                id.mutability = Option::None;
            }
        }
    }
    // Translate the return type
    let mut return_type_str = String::new();
    if let &syn::ReturnType::Type(_, ref ty) = &generated_sig.output {
        return_type_str = ty.to_owned().into_token_stream().to_string();
        generated_sig.output =
            syn::ReturnType::Type(syn::token::RArrow::default(), gen_libsql_type(ty));
    }

    // Copy the native function body
    let native_block = &input.block;
    let mut generated_block = syn::Block {
        brace_token: input.block.brace_token,
        stmts: Vec::<Stmt>::new(),
    };

    // Generate the exported function body
    let mut raw_ret_expr =
        parse_str::<syn::Expr>(&format!("{}()", &native_sig.ident.to_string())).unwrap();
    if let &mut syn::Expr::Call(ref mut call) = &mut raw_ret_expr {
        for raw_param in &native_sig.inputs {
            if let &FnArg::Typed(ref param) = raw_param {
                if let &Pat::Ident(ref id) = param.pat.borrow() {
                    let from_expr =
                        parse_str::<syn::Expr>(&gen_from_expr(&param.ty, &id.ident.to_string()))
                            .unwrap();
                    call.args.push(from_expr);
                }
            }
        }
    }
    let ret_expr = gen_into_expr(&return_type_str, raw_ret_expr);
    generated_block.stmts.push(Stmt::Expr(ret_expr));

    let ts = TokenStream::from(quote! {
        extern crate libsql_wasm_abi;
        use crate::libsql_wasm_abi::*;

        #native_sig
        #native_block

        #[no_mangle]
        pub #generated_sig
        #generated_block
    });
    println!("Generated binding:\n{}", ts);
    ts
}