macro_ruby/
lib.rs

1mod exec;
2mod filesystem;
3mod parser;
4
5use crate::{exec::*, parser::parse_str};
6use proc_macro::{TokenStream, TokenTree};
7
8const USE_MSG_STR_CODE: &str =
9    "Wrong usage:\nUse ruby_code_str!(\"literal\")\nExample: ruby_code_str!(\"print 'something'\")";
10
11const USE_MSG_TYPE_CODE: &str =
12    "Wrong usage:\nUse ruby_code_to!(type \"literal\")\nExample: ruby_code_to!(i32 \"print 2+2\")";
13
14const USE_MSG_AST_CODE: &str =
15    "Wrong usage:\nUse ruby_code_ast!(\"literal\")\nExample: ruby_code_ast!(\"puts 'let a = 1;'\")";
16
17const USE_MSG_STR_FILE: &str =
18    "Wrong usage:\nUse ruby_code_str!(\"filepath\")\nExample: ruby_file_str!(\"print 'something'\")";
19
20const USE_MSG_TYPE_FILE: &str =
21    "Wrong usage:\nUse ruby_code_to!(type \"filepath\")\nExample: ruby_file_to!(i32 \"print 2+2\")";
22
23const USE_MSG_AST_FILE: &str =
24    "Wrong usage:\nUse ruby_code_ast!(\"filepath\")\nExample: ruby_file_ast!(\"puts 'let a = 1;'\")";
25
26
27/// Execute Ruby code and return a string
28///
29/// # Arguments
30///
31/// * `input` - A &str literal containing ruby code 
32///
33/// # Example
34/// ```
35/// use macro_ruby::ruby_code_str;
36///
37/// let my_str = ruby_code_str!("puts hi");
38/// assert_eq!(my_str, "hi\n");
39///
40/// ```
41/// # Note
42/// `puts` adds a trailing '\n', use `print` if you don't want that
43#[proc_macro]
44pub fn ruby_code_str(input: TokenStream) -> TokenStream {
45    let input: Vec<proc_macro::TokenTree> = input.into_iter().collect();
46    let code = input.get(0).expect(USE_MSG_STR_CODE);
47
48    let code = match code {
49        TokenTree::Literal(literal) => parse_str(&literal.to_string()),
50        _ => panic!("{}", USE_MSG_STR_CODE),
51    };
52
53    let mut result = String::from("r#\"");
54    result.push_str(&execute_code(&code));
55    result.push_str("\"#");
56
57    result.parse::<TokenStream>().expect(
58        "Couldn't parse ruby output, perhaps check for presence of comments in your ruby code",
59    )
60}
61
62/// Execute Ruby code and return a different type
63///
64/// # Arguments
65/// * `type` - The return type of the ruby code
66/// * `input` - A `&str` literal containing ruby code 
67///
68/// # Example
69/// ```
70/// use macro_ruby::ruby_code_to;
71///
72/// let my_int = ruby_code_to!(i32, "print 500+500");
73/// assert_eq!(my_int, 1000);
74///
75/// ```
76///
77/// # List of supported types
78/// `i8`, `i16`, `i32`, `i64`, `i128`, `u8`, `u16`, `u32`
79/// `u64`, `u128`, `usize`, `f32`, `f64`, `char`, `bool`
80///
81/// # Note
82/// If your type isn't supported you may use `ruby_code_ast!`;
83#[proc_macro]
84pub fn ruby_code_to(input: TokenStream) -> TokenStream {
85    let input: Vec<proc_macro::TokenTree> = input.into_iter().collect();
86    let return_type = input.get(0).expect(USE_MSG_TYPE_CODE);
87    let code = input.get(1).expect(USE_MSG_TYPE_CODE);
88
89    let return_type = match return_type {
90        TokenTree::Ident(t) => t.to_string(),
91        _ => panic!(
92            "Should be type but found '{}': {}",
93            return_type, USE_MSG_TYPE_CODE
94        ),
95    };
96
97    let code = match code {
98        TokenTree::Literal(literal) => parse_str(&literal.to_string()),
99        TokenTree::Punct(_) => match input.get(2).expect(USE_MSG_TYPE_CODE) {
100            TokenTree::Literal(literal) => parse_str(&literal.to_string()),
101            _ => panic!(
102                "Should be string literal but found '{}':\n{}",
103                code, USE_MSG_TYPE_CODE
104            ),
105        },
106        _ => panic!(
107            "Should be string literal but found '{}':\n{}",
108            code, USE_MSG_TYPE_CODE
109        ),
110    };
111
112    macro_rules! eval_type {
113        ( $($type:ty),* ) => {
114            match return_type.as_ref() {
115                $(stringify!($type) => {
116
117                    let result = execute_code(&code)
118                        .trim_end()
119                        .parse::<$type>();
120
121                    let result = match result {
122                        Ok(v) => v,
123                        Err(e) => {
124                                panic!("Couldn't convert to type: {}{}",
125                                    stringify!($type),
126                                    match e.kind() {
127                                        &std::num::IntErrorKind::Empty => "\nPerhaps you forgot to print the result?",
128                                        _ => ""
129                                    }
130                                );
131                        }
132                    };
133
134                    let mut result = result.to_string();
135                    result.push_str(concat!(" as ", stringify!($type)));
136
137                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
138
139                }),*
140
141                "f32" => {
142                    let result = execute_code(&code)
143                        .trim_end()
144                        .parse::<f32>();
145
146                    let result = match result {
147                        Ok(v) => v,
148                        Err(e) => {
149                            panic!(
150                                "Couldn't convert to type: f32{}",
151                                if e == "".parse::<f32>().unwrap_err() { // weird hack
152                                    "\nPerhaps you forgot to print the result?"
153                                } else {
154                                    ""
155                                }
156                            )
157                        }
158                    };
159
160                    let mut result = result.to_string();
161                    result.push_str(" as f32");
162
163                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
164                },
165
166                "f64" => {
167                    let result = execute_code(&code)
168                        .trim_end()
169                        .parse::<f64>();
170
171                    let result = match result {
172                        Ok(v) => v,
173                        Err(e) => {
174                            panic!(
175                                "Couldn't convert to type: f64{}",
176                                if e == "".parse::<f64>().unwrap_err() { // weird hack
177                                    "\nPerhaps you forgot to print the result?"
178                                } else {
179                                    ""
180                                }
181                            )
182                        }
183                    };
184
185                    let mut result = result.to_string();
186                    result.push_str(" as f64");
187
188                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
189                },
190
191                "char" => {
192                    let result = execute_code(&code)
193                        .trim_end()
194                        .parse::<char>();
195
196                    let result = match result {
197                        Ok(v) => v,
198                        Err(e) => {
199                            panic!(
200                                "Couldn't convert to type: char{}",
201                                if e == "".parse::<char>().unwrap_err() { // weird hack
202                                    "\nPerhaps you forgot to print the result?"
203                                } else {
204                                    ""
205                                }
206                            )
207                        }
208                    };
209
210                    let mut result = result.to_string();
211                    result.push_str(" as char");
212
213                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
214
215                }
216                _ => panic!("Unsupported type")
217            }
218        }
219    }
220
221    eval_type!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize)
222}
223
224/// Execute Ruby code and generate Rust code
225///
226/// # Arguments
227/// * `input` - A `&str` literal containing ruby code 
228///
229/// # Example
230/// ```
231/// use macro_ruby::ruby_code_ast;
232///
233/// ruby_code_ast!(i32, r#"
234///
235/// 3.times do |x|
236///     puts "let var#{x} = #{x};"
237/// end
238///
239/// "#);
240/// assert_eq!(var0, 0);
241/// assert_eq!(var1, 1);
242/// assert_eq!(var2, 2);
243/// ```
244#[proc_macro]
245pub fn ruby_code_ast(input: TokenStream) -> TokenStream {
246    let input: Vec<proc_macro::TokenTree> = input.into_iter().collect();
247    let code = input.get(0).expect(USE_MSG_AST_CODE);
248
249    let code = match code {
250        TokenTree::Literal(literal) => parse_str(&literal.to_string()),
251        _ => panic!("{}", USE_MSG_AST_CODE),
252    };
253
254    execute_code(&code)
255        .parse::<TokenStream>()
256        .expect("Couldn't parse ruby output")
257}
258
259/// Execute Ruby from a file and return a string
260///
261/// # Arguments
262///
263/// * `filepath` - A &str literal containing the file path
264///
265/// # Example
266/// Contents of ./src/main.rs
267/// ```
268/// use macro_ruby::ruby_file_str;
269///
270/// let my_str = ruby_file_str!("src/file.rb");
271/// assert_eq!(my_str, "hi\n");
272/// ```
273/// ---
274/// Contents of ./src/file.rb:
275/// ```
276/// puts "hi"
277///
278/// ```
279/// # Note
280/// `puts` adds a trailing '\n', use `print` if you don't want that
281#[proc_macro]
282pub fn ruby_file_str(input: TokenStream) -> TokenStream {
283    let input: Vec<proc_macro::TokenTree> = input.into_iter().collect();
284    let file = input.get(0).expect(USE_MSG_STR_FILE);
285
286    let file = match file {
287        TokenTree::Literal(literal) => parse_str(&literal.to_string()),
288        _ => panic!("{}", USE_MSG_STR_FILE),
289    };
290
291    let mut result = String::from("r#\"");
292    result.push_str(&execute_file(&file));
293    result.push_str("\"#");
294
295    result.parse::<TokenStream>().expect(
296        "Couldn't parse ruby output, perhaps check for presence of comments in your ruby code",
297    )
298}
299
300/// Execute Ruby from a file and return a different type
301///
302/// # Arguments
303/// * `type` - The return type of the ruby code
304/// * `input` - A `&str` literal containing the file path
305///
306/// # Example
307/// Contents of ./src/main.rs
308/// ```
309/// use macro_ruby::ruby_file_to;
310///
311/// let my_int = ruby_file_to!("src/file.rb");
312/// assert_eq!(my_str, 1000);
313/// ```
314/// ---
315/// Contents of ./src/file.rb:
316/// ```
317/// puts 500+500
318///
319/// ```
320///
321/// # List of supported types
322/// `i8`, `i16`, `i32`, `i64`, `i128`, `u8`, `u16`, `u32`
323/// `u64`, `u128`, `usize`, `f32`, `f64`, `char`, `bool`
324///
325/// # Note
326/// If your type isn't supported you may use `ruby_file_ast!`;
327#[proc_macro]
328pub fn ruby_file_to(input: TokenStream) -> TokenStream {
329    let input: Vec<proc_macro::TokenTree> = input.into_iter().collect();
330    let return_type = input.get(0).expect(USE_MSG_TYPE_FILE);
331    let file = input.get(1).expect(USE_MSG_TYPE_FILE);
332
333    let return_type = match return_type {
334        TokenTree::Ident(t) => t.to_string(),
335        _ => panic!(
336            "Should be type but found '{}': {}",
337            return_type, USE_MSG_TYPE_FILE
338        ),
339    };
340
341    let file = match file {
342        TokenTree::Literal(literal) => parse_str(&literal.to_string()),
343        TokenTree::Punct(_) => match input.get(2).expect(USE_MSG_TYPE_FILE) {
344            TokenTree::Literal(literal) => parse_str(&literal.to_string()),
345            _ => panic!(
346                "Should be string literal but found '{}':\n{}",
347                file, USE_MSG_TYPE_FILE
348            ),
349        },
350        _ => panic!(
351            "Should be string literal but found '{}':\n{}",
352            file, USE_MSG_TYPE_FILE
353        ),
354    };
355
356    macro_rules! eval_type {
357        ( $($type:ty),* ) => {
358            match return_type.as_ref() {
359                $(stringify!($type) => {
360
361                    let result = execute_file(&file)
362                        .trim_end()
363                        .parse::<$type>();
364
365                    let result = match result {
366                        Ok(v) => v,
367                        Err(e) => {
368                                panic!("Couldn't convert to type: {}{}",
369                                    stringify!($type),
370                                    match e.kind() {
371                                        &std::num::IntErrorKind::Empty => "\nPerhaps you forgot to print the result?",
372                                        _ => ""
373                                    }
374                                );
375                        }
376                    };
377
378                    let mut result = result.to_string();
379                    result.push_str(concat!(" as ", stringify!($type)));
380
381                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
382
383                }),*
384
385                "f32" => {
386                    let result = execute_file(&file)
387                        .trim_end()
388                        .parse::<f32>();
389
390                    let result = match result {
391                        Ok(v) => v,
392                        Err(e) => {
393                            panic!(
394                                "Couldn't convert to type: f32{}",
395                                if e == "".parse::<f32>().unwrap_err() { // weird hack
396                                    "\nPerhaps you forgot to print the result?"
397                                } else {
398                                    ""
399                                }
400                            )
401                        }
402                    };
403
404                    let mut result = result.to_string();
405                    result.push_str(" as f32");
406
407                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
408                },
409
410                "f64" => {
411                    let result = execute_file(&file)
412                        .trim_end()
413                        .parse::<f64>();
414
415                    let result = match result {
416                        Ok(v) => v,
417                        Err(e) => {
418                            panic!(
419                                "Couldn't convert to type: f64{}",
420                                if e == "".parse::<f64>().unwrap_err() { // weird hack
421                                    "\nPerhaps you forgot to print the result?"
422                                } else {
423                                    ""
424                                }
425                            )
426                        }
427                    };
428
429                    let mut result = result.to_string();
430                    result.push_str(" as f64");
431
432                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
433                },
434
435                "bool" => {
436
437                    let result = execute_file(&file)
438                        .trim_end()
439                        .parse::<bool>()
440                        .expect("Couldn't convert to type: bool.\nPerhaps you forgot to print the result?");
441
442                    let result = result.to_string();
443
444                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
445                },
446
447                "char" => {
448                    let result = execute_file(&file)
449                        .trim_end()
450                        .parse::<char>();
451
452                    let result = match result {
453                        Ok(v) => v,
454                        Err(e) => {
455                            panic!(
456                                "Couldn't convert to type: char{}",
457                                if e == "".parse::<char>().unwrap_err() { // weird hack
458                                    "\nPerhaps you forgot to print the result?"
459                                } else {
460                                    ""
461                                }
462                            )
463                        }
464                    };
465
466                    let mut result = result.to_string();
467                    result.push_str(" as char");
468
469                    result.parse::<TokenStream>().expect("Couldn't parse token stream")
470
471                }
472                _ => panic!("Unsupported type")
473            }
474        }
475    }
476
477    eval_type!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize)
478}
479
480/// Execute Ruby from a file and generate Rust code
481///
482/// # Arguments
483/// * `input` - A `&str` literal containing ruby code 
484///
485/// # Example
486/// Contents of ./src/main.rs
487/// ```
488/// use macro_ruby::ruby_file_ast;
489///
490/// ruby_file_ast!("src/file.rb");
491///
492/// assert_eq!(var0, 0);
493/// assert_eq!(var1, 1);
494/// assert_eq!(var2, 2);
495/// ```
496/// ---
497/// Contents of ./src/file.rb:
498/// ```
499/// 3.times do |x|
500///     puts "let var#{x} = #{x};"
501/// end
502/// ```
503#[proc_macro]
504pub fn ruby_file_ast(input: TokenStream) -> TokenStream {
505    let input: Vec<proc_macro::TokenTree> = input.into_iter().collect();
506    let file = input.get(0).expect(USE_MSG_AST_FILE);
507
508    let file = match file {
509        TokenTree::Literal(literal) => parse_str(&literal.to_string()),
510        _ => panic!("{}", USE_MSG_AST_FILE),
511    };
512
513    execute_file(&file)
514        .parse::<TokenStream>()
515        .expect("Couldn't parse ruby output")
516}