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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#![warn(missing_docs)]
//!# Tealr_derive
//!The derive macro used by [tealr](https://github.com/lenscas/tealr/tree/master/tealr).
//!
//!Tealr is a crate that can generate `.d.tl` files for types that are exposed to `lua`/`teal` through [rlua](https://crates.io/crates/rlua)
//!
//!Read the [README.md](https://github.com/lenscas/tealr/tree/master/tealr/README.md) in [tealr](https://github.com/lenscas/tealr/tree/master/tealr) for more information.

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

#[cfg(any(
    feature = "embed_compiler_from_local",
    feature = "embed_compiler_from_download"
))]
mod embed_compiler;

use std::{
    ffi::OsStr,
    fs::{read_to_string, File},
    io::Write,
    path::PathBuf,
    process::Command,
};

#[cfg(any(
    feature = "embed_compiler_from_local",
    feature = "embed_compiler_from_download"
))]
use embed_compiler::EmbedOptions;
use proc_macro::TokenStream;
use syn::{parse::Parse, parse_macro_input, LitStr, Token};

///Implements UserData and TypeBody
///
///It wraps the UserDataMethods into tealr::UserDataWrapper
///and then passes it to tealr::TealData::add_methods.
///
///Type body is implemented in a similar way, where it uses the TealData implementation to get the type
#[cfg(feature = "derive")]
#[proc_macro_derive(UserData)]
pub fn user_data_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_user_data_derive(&ast).into()
}

fn impl_user_data_derive(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl UserData for #name {
            fn add_methods<'lua, T: ::rlua::UserDataMethods<'lua, Self>>(methods: &mut T) {
                let mut x = ::tealr::rlu::UserDataWrapper::from_user_data_methods(methods);
                <Self as TealData>::add_methods(&mut x);
            }
        }
        impl ::tealr::TypeBody for #name {
            fn get_type_body(_: ::tealr::Direction, gen: &mut ::tealr::TypeGenerator) {
                gen.is_user_data = true;
                <Self as ::tealr::rlu::TealData>::add_methods(gen);
            }
        }
    };
    gen
}

///Implements TypeName.
///
///TypeName::get_type_name will return the name of the type.
#[cfg(feature = "derive")]
#[proc_macro_derive(TypeName)]
pub fn type_representation_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();

    impl_type_representation_derive(&ast).into()
}
fn impl_type_representation_derive(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl TypeName for #name {
            fn get_type_name(_: ::tealr::Direction) -> ::std::borrow::Cow<'static, str> {
                ::std::borrow::Cow::from(stringify!(#name))
            }
        }
    };
    gen
}

///Implement both UserData and TypeName.
///
///Look at tealr_derive::UserData and tealr_derive::TypeName
///for more information on how the implemented traits will behave.
#[cfg(feature = "derive")]
#[proc_macro_derive(TealDerive)]
pub fn teal_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    let mut stream = impl_type_representation_derive(&ast);
    stream.extend(impl_user_data_derive(&ast));
    stream.into()
}

struct CompileInput {
    code: String,
    path: PathBuf,
}
impl Parse for CompileInput {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let code: String = input.parse::<LitStr>()?.value();
        let has_comma = input.parse::<Option<Token![,]>>()?.is_some();
        let mut path: PathBuf = std::env::var("CARGO_MANIFEST_DIR")
            .expect("Could not get the crate directory")
            .into();

        if has_comma {
            let extra: LitStr = input.parse()?;

            path = path.join(extra.value());
        }

        Ok(Self { code, path })
    }
}

///Compiles the given teal code at compile time to lua.
///
///The macro tries it best to pass the correct `--include-dir` to tl using `CARGO_MANIFEST_DIR`.
///However, this isn't always where you want it to be. In that case you can add an extra argument that will be joined with `CARGO_MANIFEST_DIR` using [std::path::PathBuf::join](std::path::PathBuf#method.join)
///
///## Compile time requirement!
///At this point in time this requires you to have the teal compiler installed and accessible as `tl`.
///
///## Example
///```
///# use tealr_derive::compile_inline_teal;
///assert_eq!(compile_inline_teal!("local a : number = 1\n"),"local a = 1\n")
///```

#[cfg(feature = "compile")]
#[proc_macro]
pub fn compile_inline_teal(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as CompileInput);

    let code = input.code;
    let path = input.path;

    let dir = tempfile::tempdir().expect("Could not create a temporary directory");
    let temp_path = dir.path();
    let mut input_file =
        File::create(temp_path.join("input.tl")).expect("Could not create teal source file");
    input_file
        .write_all(code.as_bytes())
        .expect("Could not write teal source file");

    println!("{:?}", path);

    let mut command = Command::new("tl")
        .args(&[
            OsStr::new("check"),
            OsStr::new("-I"),
            path.as_os_str(),
            OsStr::new("input.tl"),
        ])
        .current_dir(temp_path)
        .spawn()
        .expect("Could not run `tl check`. Make sure it is available in the path");

    if !command
        .wait()
        .expect("Something has gone wrong while running `tl check`")
        .success()
    {
        panic!("There was an error while typechecking your teal code.")
    }

    let mut command = Command::new("tl")
        .args(&[
            OsStr::new("gen"),
            OsStr::new("-o"),
            OsStr::new("output.lua"),
            OsStr::new("-I"),
            path.as_os_str(),
            OsStr::new("input.tl"),
        ])
        .current_dir(temp_path)
        .spawn()
        .expect("Could not run `tl gen`. Make sure it is available in the path");

    if !command
        .wait()
        .expect("Something has gone wrong while running `tl gen`")
        .success()
    {
        panic!("Could not compile teal code.");
    }
    let contents =
        read_to_string(temp_path.join("output.lua")).expect("Could not read generated lua");

    let stream = quote! {#contents};
    stream.into()
}
///Embeds the teal compiler, making it easy to load teal files directly.
///
///It can either download the given version from Github (default), luarocks or uses the compiler already installed on your system
///Compiling it without the lua5.3 compatibility library and embedding it into your application.
///
///It returns a closure that takes the file that needs to run
///and returns valid lua code that both prepares the lua vm so it can run teal files and
///loads the given file using `require`, returning the result of the file that got loaded.
///## NOTE!
///Due to how the teal files are being loaded, they won't be typed checked.
///More info on: [https://github.com/teal-language/tl/blob/master/docs/tutorial.md](https://github.com/teal-language/tl/blob/master/docs/tutorial.md) (Search for "loader")
///
///## Compile time requirement!
///This needs to be able to run `lua` at compile time to compile the teal compiler.
///
///If a local teal compiler is used, then `tl` needs to run at compile time instead.
///
///## Example
///Downloads:
///```rust
///# use tealr_derive::embed_compiler;
/// //This downloads from github
/// let compiler = embed_compiler!("v0.9.0");
///
/// let compiler = embed_compiler!(Github(version = "v0.9.0"));
/// let compiler = embed_compiler!(Luarocks(version = "v0.9.0"));
/// let lua_code = compiler("your_teal_file.tl");
///```
///From filesystem
//Not tested so it can have a nice path and also to not depend on having the teal compiler at a static place.
///```ignore
/// let compiler = embed_compiler!(Local(path = "some/path/to/tl.tl"));
/// //This tries to find the teal compiler on its own
/// let compiler = embed_compiler!(Local());
///```
#[cfg(any(
    feature = "embed_compiler_from_local",
    feature = "embed_compiler_from_download"
))]
#[proc_macro]
pub fn embed_compiler(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as EmbedOptions);
    let compiler = embed_compiler::get_teal(input);
    let primed_vm_string = format!(
        "local tl = (function()\n{}\nend)()\ntl.loader()\n",
        compiler
    );
    let stream = quote! {
        |require:&str| {
            format!("{}\n return require('{}')",#primed_vm_string,require)
        }
    };
    stream.into()
}