14 releases
0.4.0 | Jan 3, 2023 |
---|---|
0.3.3 | Mar 28, 2022 |
0.2.3 | Mar 8, 2022 |
0.1.4 | Mar 5, 2022 |
#1526 in Development tools
5,475 downloads per month
Used in profi
26KB
282 lines
akin
A zero-dependency crate for writing repetitive code easier and faster
Check Syntax for information on how to use it.
Why?
I've found myself having to write a lot of repetitive code (mostly when matching against enums in parsing).
The fantastic duplicate had an unintuitive syntax for me, so I decided to make my own tool.
Example
trait Sqrt {
fn dumb_sqrt(self) -> Result<f64, &'static str>;
}
akin! {
let &int_type = [i64, u64];
let &negative_check = [
{
if self < 0 {
return Err("Sqrt of negative number")
}
},
NONE
];
let &num = [1, 2, 3, 4];
let &res = [1., 1.41, 1.73, 2.];
let &branch = {
*num => Ok(*res),
};
impl Sqrt for *int_type {
fn dumb_sqrt(self) -> Result<f64, &'static str> {
*negative_check
match self {
*branch
_ => Err("Sqrt of num not in [1, 4]")
}
}
}
}
Is expanded to:
trait Sqrt {
fn dumb_sqrt(self) -> Result<f64, &'static str>;
}
impl Sqrt for i64 {
fn dumb_sqrt(self) -> Result<f64, &'static str> {
if self < 0 {
return Err("Sqrt of negative number")
}
match self {
1 => Ok(1.),
2 => Ok(1.41),
3 => Ok(1.73),
4 => Ok(2.),
_ => Err("Sqrt of num not in [1, 4]")
}
}
}
impl Sqrt for u64 {
fn dumb_sqrt(self) -> Result<f64, &'static str> {
match self {
1 => Ok(1.),
2 => Ok(1.41),
3 => Ok(1.73),
4 => Ok(2.),
_ => Err("Sqrt of num not in [1, 4]")
}
}
}
The good thing about akin is that it detects automatically the number of values of each variable for each scope, so for example "branch" will get copied 4 times (as "num" and "res" both have 4 values), but the main function will only be duplicated once, as all the variables it has have 2 values.
Check the tests/ folder of the repository for more examples.
Syntax
The crate only provides one macro, akin!
.
The syntax is as follows:
First, you declare the variables you'll use.
A variable name must start with &
, as it's the only way it can differentiate between macro or real declarations.
Also notice that variables end with ;
let &variable = [v1, v2, v3, ...];
let &variable2 = [...];
let &code = {
...
};
Then, when all variables have been declared, you can write the code snippet you want to duplicate.
The amount of times it will be duplicated depends on the variables that are used.
let &lhs = [1, 2, 3];
let &rhs = [4, 5, 6];
println!("*lhs + *rhs = {}", *lhs + *rhs);
Will get expanded to:
println!("1 + 4 = {}", 1 + 4);
println!("2 + 5 = {}", 2 + 5);
println!("3 + 6 = {}", 3 + 6);
Because the variables &lhs
and &rhs
both have 3 values.
As you can see, &
is used to declare variables and *
is used to "dereference" them to the current value.
As a matter of convenience, the range syntax is also accepted, when declaring a variable,
e.g. 0..3
and 0..=3
, which are equivalent to [0,1,2]
and [0,1,2,3]
respectively.
So the above example could also be written like
let &lhs = 1..=3;
let &rhs = 4..=6;
println!("*lhs + *rhs = {}", *lhs + *rhs);
Presently, only unsigned integers that can fit in u64
are supported in ranges, i.e. ranges
like -10..-1
or 'a'..'c'
, which are fine in regular Rust, aren't accepted by akin
.
If a used variable has less values than another, the last one will be used.
akin! {
let &v1 = [c];
let &v2 = [a, b];
println!("*v1*v2");
}
Expands to
println!("ca");
println!("cb");
All code in variables must be enclosed in brackets {...}
.
akin! {
let &var = [-1, 1];
let &code = [
{
println!("true");
},
{
println!("false");
}
];
if *var < 0 {
*code
}
}
Will expand to
if -1 < 0 {
println!("true");
}
if 1 < 0 {
println!("false")
}
Check the tests/ folder of the repository for more examples.
NONE
NONE
is the way you can tell akin
to simply skip that value and not write anything.
It is useful for when you want to have elements in a duplication that do not have to be in the others.
akin! {
let &num = [1, 2, 3];
let &code = [
NONE,
{
.pow(2)
}
];
println!("*num^2 = {}", *num~u32*code);
// *num~u32 is necessary to ensure the type is written correctly (it would be "1 u32" otherwise)
}
Joint modifier
By default, akin
places a space between all identifiers
let name = 5; // 'name' is an identifier
^^^^
Sometimes, this is not desirable, for example, if trying to interpolate between a function name
let &name = [1];
fn _*name()...
// Will get wrongly expanded because '_' is an identifier
fn _ 1()
To avoid it, use the joint modifier ~
, making the next identifier not to be separated.
let &name = [1];
fn _~*name()... // *name is affected by the modifier
// Will get correctly expanded to
fn _1()
Inside string literals "..."
it is not necessary to use the modifier, as Rust does not count them as identifiers.
This is a limitation on proc-macro parsing, so I doubt it'll be fixed soon.
Zero dependencies? Really?
Yes, this crate does not use syn
nor quote
, as parsing the syntax is pretty simple and both add a lot of overhead.
For this reason, akin
should not impact compile times as much as most proc-macros, try using it and see it by yourself!