compile_ops/
lib.rs

1#![doc(html_root_url = "https://docs.rs/compile_ops/0.1.3/")]
2#![no_std]
3/*!
4This crate provides macros that expand to the result of operations like addition,substraction,division,multiplication or power and a
5macro for join them all.
6
7All the macros support exclusion of expressions thought '!' before they,useful when you cannot prevent code
8to expand inside the tokens passed;nowadays only possible thought the MBE expansion bucles.
9
10This crate is **no_std**.
11
12# Examples
13
14```
15#![feature(proc_macro_hygiene)]
16
17use compile_ops::*;
18
19assert_eq!(2, add!(1!5, !5, 1)); // five it is not invited...
20assert_eq!(2, sub!(3, 1));
21assert_eq!(2, mul!(2, 1));
22assert_eq!(2, div!(4, 2));
23assert_eq!(2, rem!(11, 3));
24
25assert_eq!(2, ops!(2 % 2 + 2 * 2 ^ 1 / 1 - 2)); 
26```
27*/
28
29extern crate proc_macro;
30extern crate alloc;
31
32use proc_macro::TokenStream;
33use core::hint::unreachable_unchecked;
34use alloc::{ string::{String, ToString}, vec::Vec, format };
35
36/// Performs a compile-time addition between comma-separated values,excluding that is prefixed with ! from they.
37#[proc_macro]
38pub fn add(input: TokenStream) -> TokenStream {
39    let input = input.to_string();
40
41    let mut err = String::with_capacity(64);
42
43    let mut it: Vec<String> = input.split(',').map(|e| {
44        let mut trigger = false;
45        let mut string = String::with_capacity(e.len()+1);
46
47        string.push('0');
48
49        string.extend(e.chars().filter(|c| {
50            if *c == '!' {
51                trigger = true;
52            }
53
54            c.is_numeric() && !trigger
55        }));
56
57        string
58    }).collect();
59
60    let mut result: usize = it[0].parse().unwrap_or_else(|_| {
61        err.push_str("compile_error!(\"Error at parsing first operand.\")");
62        0
63    });
64
65    if err != "" {
66        return err.parse().unwrap();
67    }
68
69    for (i, e) in it.drain(1..).enumerate() {
70        let e: usize = e.parse().unwrap_or_else(|_| {
71            err.push_str("compile_error!(\"Error at parsing operand number ");
72            err.push_str(i.to_string().as_ref());
73            err.push_str(".\")");
74            0
75        });
76
77        if err != "" {
78            return err.parse().unwrap();
79        }
80
81        result += e;        
82    }
83
84    result.to_string().parse().unwrap()
85}
86
87/// Performs a compile-time substraction between comma-separated values,excluding that is prefixed with ! from they.
88#[proc_macro]
89pub fn sub(input: TokenStream) -> TokenStream {
90    let input = input.to_string();
91    let mut err = String::with_capacity(64);
92
93    let mut it: Vec<String> = input.split(',').map(|e| {
94        let mut trigger = false;
95        let mut string = String::with_capacity(e.len()+1);
96
97        string.push('0');
98
99        string.extend(e.chars().filter(|c| {
100            if *c == '!' {
101                trigger = true;
102            }
103
104            c.is_numeric() && !trigger
105        }));
106
107        string
108    }).collect();
109
110    let mut result: isize = it[0].parse().unwrap_or_else(|_| {
111        err.push_str("compile_error!(\"Error at parsing first operand.\")");
112        0
113    });
114
115    if err != "" {
116        return err.parse().unwrap();
117    }
118
119    for (i, e) in it.drain(1..).enumerate() {
120
121        if e == "" {
122            return ("compile_error!(\"Error at parsing operand number ".to_string() + 
123            i.to_string().as_ref() + 
124            ".\")").parse().unwrap()
125        }
126
127        let e: isize = e.parse().unwrap_or_else(|_| unsafe { unreachable_unchecked() });
128
129        if err != "" {
130            return err.parse().unwrap();
131        }
132
133        result -= e;        
134    }
135
136    result.to_string().parse().unwrap()
137}
138
139/// Performs a compile-time product between comma-separated values,excluding that is prefixed with ! from they.
140#[proc_macro]
141pub fn mul(input: TokenStream) -> TokenStream {
142    let input = input.to_string();
143    let mut err = String::with_capacity(64);
144
145    let mut it: Vec<String> = input.split(',').map(|e| {
146        let mut trigger = false;
147        let mut string = String::with_capacity(e.len()+1);
148
149        string.push('0');
150
151        string.extend(e.chars().filter(|c| {
152            if *c == '!' {
153                trigger = true;
154            }
155
156            c.is_numeric() && !trigger
157        }));
158
159        string
160    }).collect();
161
162    let mut result: usize = it[0].parse().unwrap_or_else(|_| {
163        err.push_str("compile_error!(\"Error at parsing first operand.\")");
164        0
165    });
166
167    if err != "" {
168        return err.parse().unwrap();
169    }
170
171    for (i, e) in it.drain(1..).enumerate() {
172        if e == "" {
173            return ("compile_error!(\"Error at parsing operand number ".to_string() + 
174            i.to_string().as_ref() + 
175            ".\")").parse().unwrap()
176        }
177
178        let e: usize = e.parse().unwrap_or_else(|_| unsafe { unreachable_unchecked() });
179
180        if err != "" {
181            return err.parse().unwrap();
182        }
183
184        result *= e;        
185    }
186
187    result.to_string().parse().unwrap()
188}
189
190/// Performs a compile-time division between comma-separated values,excluding that is prefixed with ! from they.
191#[proc_macro]
192pub fn div(input: TokenStream) -> TokenStream {
193    let input = input.to_string();
194    let mut err = String::with_capacity(64);
195
196    let mut it: Vec<String> = input.split(',').map(|e| {
197        let mut trigger = false;
198        let mut string = String::with_capacity(e.len()+1);
199
200        string.push('0');
201
202        string.extend(e.chars().filter(|c| {
203            if *c == '!' {
204                trigger = true;
205            }
206
207            c.is_numeric() && !trigger
208        }));
209
210        string
211    }).collect();
212
213    let mut result: usize = it[0].parse().unwrap_or_else(|_| {
214        err.push_str("compile_error!(\"Error at parsing first operand.\")");
215        0
216    });
217
218    if err != "" {
219        return err.parse().unwrap();
220    }
221
222    for (i, e) in it.drain(1..).enumerate() {
223        if e == "" {
224            return ("compile_error!(\"Error at parsing operand number ".to_string() + 
225            i.to_string().as_ref() + 
226            ".\")").parse().unwrap()
227        }
228
229        let e: usize = e.parse().unwrap_or_else(|_| unsafe { unreachable_unchecked() });
230
231        result /= e;        
232    }
233
234    result.to_string().parse().unwrap()
235}
236
237/// Performs a compile-time remainder between comma-separated values,excluding that is prefixed with ! from they.
238#[proc_macro]
239pub fn rem(input: TokenStream) -> TokenStream {
240    let input = input.to_string();
241    let mut err = String::with_capacity(64);
242
243    let mut it: Vec<String> = input.split(',').map(|e| {
244        let mut trigger = false;
245        let mut string = String::with_capacity(e.len()+1);
246
247        string.push('0');
248
249        string.extend(e.chars().filter(|c| {
250            if *c == '!' {
251                trigger = true;
252            }
253
254            c.is_numeric() && !trigger
255        }));
256
257        string
258    }).collect();
259
260    let mut result: usize = it[0].parse().unwrap_or_else(|_| {
261        err.push_str("compile_error!(\"Error at parsing first operand.\")");
262        0
263    });
264
265    if err != "" {
266        return err.parse().unwrap();
267    }
268
269    for (i, e) in it.drain(1..).enumerate() {
270        if e == "" {
271            return ("compile_error!(\"Error at parsing operand number ".to_string() + 
272            i.to_string().as_ref() + 
273            ".\")").parse().unwrap()
274        }
275
276        let e: usize = e.parse().unwrap_or_else(|_| unsafe { unreachable_unchecked() });
277
278        result %= e;        
279    }
280
281    result.to_string().parse().unwrap()
282}
283
284/// Performs a compile-time power between comma-separated values,excluding that is prefixed with ! from they.
285#[proc_macro]
286pub fn pow(input: TokenStream) -> TokenStream {
287    let input = input.to_string();
288    let mut err = String::with_capacity(64);
289
290    let mut it: Vec<String> = input.split(',').map(|e| {
291        let mut trigger = false;
292        let mut string = String::with_capacity(e.len()+1);
293
294        string.push('0');
295
296        string.extend(e.chars().filter(|c| {
297            if *c == '!' {
298                trigger = true;
299            }
300
301            c.is_numeric() && !trigger
302        }));
303
304        string
305    }).collect();
306
307    let mut result: usize = it[0].parse().unwrap_or_else(|_| {
308        err.push_str("compile_error!(\"Error at parsing first operand.\")");
309        0
310    });
311
312    if err != "" {
313        return err.parse().unwrap();
314    }
315
316    for (i, e) in it.drain(1..).enumerate() {
317        if e == "" {
318            return ("compile_error!(\"Error at parsing operand number ".to_string() + 
319            i.to_string().as_ref() + 
320            ".\")").parse().unwrap()
321        }
322
323        let e: u32 = e.parse().unwrap_or_else(|_| unsafe { unreachable_unchecked() });
324
325        result = result.pow(e);        
326    }
327
328    result.to_string().parse().unwrap()
329}
330
331/// Performs any of the mathematical operations in Rust with their respective operator,`^` for power.
332/// This macro also excludes anything that is prefixed with ! from the values.
333/// 
334/// The precedence is not applied,meaning that the operations are evaluated left to rigth.
335#[proc_macro]
336pub fn ops(input: TokenStream) -> TokenStream {
337    let input = input.to_string();
338
339    let mut operators = String::with_capacity(input.len());
340    let mut err = String::with_capacity(64);
341
342    let mut it: Vec<String> = input.split(|c: char| {
343        match c {
344            '+' => {operators.push('+'); true},
345            '-' => {operators.push('-'); true},
346            '*' => {operators.push('*'); true},
347            '/' => {operators.push('/'); true},
348            '%' => {operators.push('%'); true},
349            '^' => {operators.push('^'); true},
350            _ => false   
351        }
352    }).map(|e| {
353        let mut trigger = false;
354        let mut string = String::with_capacity(e.len()+1);
355
356        string.push('0');
357
358        string.extend(e.chars().filter(|c| {
359            if *c == '!' {
360                trigger = true;
361            }
362
363            c.is_numeric() && !trigger
364        }));
365
366        string
367    }).collect();
368
369    let mut result: isize = it[0].parse().unwrap_or_else(|_| {
370        err.push_str("compile_error!(\"Error at parsing first operand.\")");
371        0
372    });
373
374    if err != "" {
375        return err.parse().unwrap();
376    }
377
378    for (i, (e, op)) in it.drain(1..).zip(operators.chars()).enumerate() {
379
380        if e == "" {
381            return format!("compile_error!(\"Error at parsing operand number {}.\")", i+1).parse().unwrap()
382        }
383
384        let e: isize = match e.parse() {
385            Ok(i) => i,
386            Err(_) => return format!("compile_error!(\"Error operand number {} overflows an isize.\")", i+1).parse().unwrap(),
387        };
388
389        match op {
390            '+' => result = match result.checked_add(e) {
391                Some(i) => i,
392                None => return format!("compile_error!(\"operation number {} failed.\")", i+1).parse().unwrap(),
393            },
394
395            '-' => result = match result.checked_sub(e) {
396                Some(i) => i,
397                None => return format!("compile_error!(\"operation number {} failed.\")", i+1).parse().unwrap(),
398            },
399
400            '*' => result = match result.checked_mul(e) {
401                Some(i) => i,
402                None => return format!("compile_error!(\"operation number {} failed.\")", i+1).parse().unwrap(),
403            },
404
405            '/' => result = match result.checked_div(e) {
406                Some(i) => i,
407                None => return format!("compile_error!(\"operation number {} failed.\")", i+1).parse().unwrap(),
408            },
409
410            '%' => result = match result.checked_rem(e) {
411                Some(i) => i,
412                None => return format!("compile_error!(\"operation number {} failed.\")", i+1).parse().unwrap(),
413            },
414
415            '^' => result = match result.checked_pow(e as u32) {
416                Some(i) => i,
417                None => return format!("compile_error!(\"operation number {} failed.\")", i+1).parse().unwrap(),
418            },
419
420            _ => ()
421        }        
422    }
423
424    result.to_string().parse().unwrap()
425}
426
427/// Ternary operations with `$bool:expr ? $code:expr $(! else_code)?` syntax for shorter and more legible conditionals.
428/// 
429/// This macro expand to `if bool_expr { code } else { else_code }` so does not prevent you for declaring
430/// new scopes,take it count in position-sensitive code,because inner blocks can access outer ones this is not
431/// typically a problem for safe code.
432/// 
433/// Because procedural macros does not expand if them are inside the arguments of another,you can't nest
434/// ternary as you can do with normal `if` statements.
435/// 
436/// # Examples
437/// 
438/// ```
439/// #![feature(proc_macro_hygiene)] // needed here due to the use in let
440///                                 // statements but generally unneccesary
441/// use compile_ops::ternary;
442/// 
443/// let mut one = 1;
444/// let mut two = ternary!(one == 1 ? 2 ! 3);
445/// 
446/// assert_eq!(two, 2);
447/// 
448/// one = 2;
449/// two = ternary!(one == 1 ? 2 ! 3);
450/// 
451/// ternary!(two == 3 ? one = 1);
452/// 
453/// assert_eq!(two, 3);
454/// assert_eq!(one, 1);
455/// ```
456#[proc_macro]
457pub fn ternary(input: TokenStream) -> TokenStream {
458    let input = input.to_string();
459    let mut expansion = String::with_capacity(input.len()+16);
460
461    let mut index = input.find('?').unwrap_or_else(|| { expansion.push_str("compile_error!(\"Question mark character(?) not found in ternary operation.\")"); 0});
462
463    if expansion != "" {
464        return expansion.parse().unwrap();
465    }
466
467    if index == 0 {
468        return "compile_error!(\"Missing boolean expresion behind the question mark character(?).\")".to_string().parse().unwrap();
469    }
470
471    let mut else_case = true;
472    let (bool_expr, mut other) = input.split_at(index);
473    
474    other = &other[1..];
475
476    index = other.rfind('!').unwrap_or_else(|| {else_case = false; 0});
477
478    let (code, mut else_code) = if else_case { other.split_at(index) } else { (other, "") };
479
480    if else_case {
481        else_code = &else_code[1..];
482    }
483
484    expansion.push_str("if ");
485    expansion.push_str(bool_expr);
486    expansion.push_str(" {");
487    expansion.push_str(code);
488    expansion.push_str(" }");
489
490    if else_case {
491        expansion.push_str(" else { ");
492        expansion.push_str(else_code);
493        expansion.push_str(" }");
494    }
495
496    expansion.parse().unwrap()
497}