Initial commit

This commit is contained in:
Ryan Levick 2019-07-02 00:30:39 +02:00
commit 59c0285103
6 changed files with 145 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
.vscode

20
Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "overflow"
version = "0.0.1"
edition = "2018"
autotests = false
[lib]
proc-macro = true
[[test]]
name = "tests"
path = "tests/progress.rs"
[dev-dependencies]
trybuild = "1.0"
[dependencies]
syn = { version = "0.15.39", features = ["full", "extra-traits"] }
quote = "0.6"
proc-macro2 = "0.4.30"

34
README.md Normal file
View File

@ -0,0 +1,34 @@
# Overflow
A convenience macro for changing the overflow properties of math expressions without having to change those expressions (mostly).
## Example
By default the following will fail in dev builds at runtime:
```rust
(2.pow(20) << 20) + 2 * 2
```
In order to make this wrap in dev and release builds you would need to write it this way:
```rust
(2.wrapping_pow(20).wrapping_shl(20)).wrapping_add(2.wrapping_mul(2))
```
Or you could use Overflow and write the following:
```rust
overflow::wrapping { (2.pow(20) << 20) + 2 * 2 }
```
The above converts the normal meth expression syntax directly into the `wrapping` variant from above.
## Limitations
Overflow is currently limited in the following:
* The crate currently requires nightly because proc macros in expressions are not currently stable.
* Because math operations can more easily propogate type inference information than method calls, you may have to add type information when using the macros that were not neceesary before.
* Overflow behaivor is only affected at the top level (or within parenthesis) meaning that if you have math expressions inside of method invocations or inside of other macros, those expressions will not be converted.
* Conversion of `pow` is extremely naive so if you call a `pow` method on some type, this will be converted to `wrapping_pow` even if that makes no sense for that type.

72
src/lib.rs Normal file
View File

@ -0,0 +1,72 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{spanned::Spanned, BinOp, Expr, ExprBinary, ExprUnary, Ident, UnOp};
#[proc_macro]
pub fn wrapping(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::Expr);
let expanded = parse_expr(input);
TokenStream::from(expanded)
}
fn parse_expr(mut expr: syn::Expr) -> proc_macro2::TokenStream {
match expr {
Expr::Unary(unary) => parse_unary(unary),
Expr::Binary(binary) => parse_binary(binary),
Expr::MethodCall(ref mut mc) if mc.method == "pow" => {
mc.method = syn::Ident::new("wrapping_pow", mc.method.span());
quote! { #mc }
}
Expr::Paren(p) => {
let expr = parse_expr(*p.expr);
quote! {
(#expr)
}
}
_ => quote! { #expr },
}
}
fn parse_unary(unary: ExprUnary) -> proc_macro2::TokenStream {
let expr = parse_expr(*unary.expr);
let op = unary.op;
match op {
UnOp::Neg(_) => {
quote! {
#expr.wrapping_neg()
}
}
_ => quote! { #expr },
}
}
fn parse_binary(binary: ExprBinary) -> proc_macro2::TokenStream {
let left = parse_expr(*binary.left);
let right = parse_expr(*binary.right);
let op = binary.op;
let method_name = match op {
BinOp::Add(_) => Some("wrapping_add"),
BinOp::Sub(_) => Some("wrapping_sub"),
BinOp::Mul(_) => Some("wrapping_mul"),
BinOp::Div(_) => Some("wrapping_div"),
BinOp::Rem(_) => Some("wrapping_rem"),
BinOp::Shl(_) => Some("wrapping_shl"),
BinOp::Shr(_) => Some("wrapping_shr"),
_ => None,
};
method_name
.map(|method_name| {
let method_name = Ident::new(method_name, op.span());
quote! {
#left.#method_name(#right)
}
})
.unwrap_or_else(|| {
quote! { #left #op #right }
})
}

10
tests/01-success.rs Normal file
View File

@ -0,0 +1,10 @@
#![feature(proc_macro_hygiene)]
use overflow::wrapping;
fn main() {
let num = 2u8;
let result = wrapping!{ ((num.pow(20) << 20) + 255) + 2u8 * 2u8 };
assert!(result == 3);
let result = wrapping!{ -std::i8::MIN };
assert!(result == -128);
}

5
tests/progress.rs Normal file
View File

@ -0,0 +1,5 @@
#[test]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/01-success.rs");
}