A zero-dependency crate for writing repetitive code easier and faster.

Overview

akin

A zero-dependency crate for writing repetitive code easier and faster.
Check Syntax for information about 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

Ok(*res), }; impl Sqrt for *int_type { fn dumb_sqrt(self) -> Result { *negative_check match self { *branch _ => Err("Sqrt of num not in [1, 4]") } } } }">
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:

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 { match self { 1 => Ok(1.), 2 => Ok(1.41), 3 => Ok(1.73), 4 => Ok(2.), _ => Err("Sqrt of num not in [1, 4]") } } }">
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.

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.

Comments
  • Substitution variable 1 replace part of substitution variable 2 if var 1 is a prefix of var 2

    Substitution variable 1 replace part of substitution variable 2 if var 1 is a prefix of var 2

    Hi @LyonSyonII,

    I just discovered a bug:

    akin! {
      let &foo = [abc];
      let &foobar = [def];
      
      *foo
      *foobar
    }
    

    The foo of *foobar gets replaced with abc.

    bug 
    opened by d4h0 11
  • [New Feature] Unsigned integer ranges

    [New Feature] Unsigned integer ranges

    This PR adds the possibility to declare substitution variables with range syntax. For example, let &x = 0..10;, which is equivalent to the longer declaration of let &x = [0,1,2,3,4,5,6,7,8,9];.

    Since akin! previously rejected variable declarations that didn't involve some kind of group after the =, this new syntax doesn't conflict with anything a user could have written.

    When working with macros that accept a variable number of arguments (e.g. sqlx::query!), one often finds themselves needing to write things like macro!(arr[0], arr[1], arr[2],...).

    akin! could help with this, but the best one can do with it presently is to spell all the numbers out manually in a let &x = [0, 1, 2, ...], which winds up being not much shorter. The addition of the range syntax would eliminate some whole screens of repetitive code in some of my use-cases, so I hope for this PR to get merged.

    opened by albel727 1
  • Some code improvements and bug fixes

    Some code improvements and bug fixes

    I wonder if I could interest you in this contribution. This fixes several substitution bugs, I won't go into depth about them, as the bugs were relatively subtle. See individual commit messages and the new tests for details. Fix multiplicity calculation when one variable is prefix of another and Fix repeated substitution bug are probably the most important ones. You can also take the new tests and run them on the original code to see what was broken and how.

    Still, there's no telling if some user hasn't depended on it, and some user templates might be not accepted or produce different results now, so a major version bump might be in order.

    opened by albel727 1
  • Support for separators

    Support for separators

    Hi @LyonSyonII

    As of now I don't think there is a good way to express some repetitions:

    impl<T> Foo for T where T: Baz<A> + Baz<B> + ... {}
    
    //We could do
    akin! {
        let &Type = [B, ...];
      
        let &bound = { + Baz<*Type> };
      
        impl<T> Foo for T where T: Baz<A> *bound {}
    }
    
    //Or
    akin! {
        let &Type = [A, B, ...];
      
        let &separator = [NONE, +, ...]
    
        let &bound = { *separator Baz<*Type> };
      
        impl<T> Foo for T where T: *bound {}
    }
    

    It would be nice to be able to specify a separator. Maybe something along those lines:

    akin! {
        let &Type = [A, B, ...];
      
        let &bound = { Baz<*Type> };
      
        impl<T> Foo for T where T: *{+}bound {}
    }
    
    opened by Airtz 1
  • Idea: Support for global substitutions, and for generating items once / starting a new `akin` context

    Idea: Support for global substitutions, and for generating items once / starting a new `akin` context

    Hi @LyonSyonII,

    First of all, thanks for creating akin. I like the approach quit a lot.

    I'm wondering if global substitutions, and generating items only once can be added to akin.

    An example describes the use case best:

    Imagine we would like to create an extension trait for serde_json::Value that adds methods like into_xy methods that return owned values.

    I'd create and implement a trait like this:

    trait ValueExt {
        fn into_null(self) -> Result<(), Self>;
        fn into_bool(self) -> Result<bool, Self>;
        fn into_array(self) -> Result<Vec<Value>, Self>;
    
        // etc...
    }
    
    impl ValueExt for Value {
        fn into_null(self) -> Result<(), Self> { todo!()}
        fn into_bool(self) -> Result<bool, Self> { todo!()}
        fn into_array(self) -> Result<Vec<Value>, Self>  { todo!()}
        
        // etc...
    }
    

    Instead of writing all of this by hand, I'd like to use akin to generate the trait and the trait implementation:

    akin::akin! {
        let &ident = [ null, bool, array, /* ... */ ];
        let &Type = [
            { () },
            { bool },
            { Vec<Value> },
            // ...
        ];
    
        trait JsonValueExt {
            fn into_~*ident(self) -> Result<*Type, Self>;
        }
    
        impl ValueExt for serde_json::Value {
            fn into_~*ident(self) -> Result<*Type, Self> { todo!() }
        }
    }
    

    But of course this doesn't work (JsonValueExt is defined several times).

    I'm wondering if this use case somehow can be supported.

    For example via something like this:

    akin::akin! {
        // ...
        
        #[akin(context)]
        trait JsonValueExt {
            // A new `akin` context
            fn into_~*ident(self) -> Result<*Type, Self>;
        }
    
        // ...
    }
    

    #[akin(context)] would tell akin to generate the decorated item only once, and substitute within the item (similar to how $($ident)+ works for macro_rules macros). Something like akin_context! { /* new context */ } probably also would be useful, for cases where attributes can't be used.

    My first idea for a name of the new attribute was inner, but I think that wouldn't be ideal, because it should be possible to substitute things within the trait definition itself (e.g. for generic traits). Maybe there is a better name than context, however.

    Similarly, the duplicate crate has a nice feature to define global substitutions, which often come in handy.

    I'm wondering if this can be supported via something like:

    akin::akin! {
        // ...
        
        const &foo = { println!("Hello, world!") };
    
        *foo
        // ...
    }
    

    ...which would expand to just one println!("Hello, world!").

    Using const here would be perfect semantically, I believe.

    These features would make akin useful in even more cases.

    Currently, I have created an additional macro_rules macro to generate the trait definition (duplicating the &Type and &ident definitions), which works but is not really ideal.

    Would it be possible to add these features?

    Currently, I haven't learned how to use procedural macros, so I currently can't add these features myself. But if you don't want to add this yourself, but would accept a PR, I could do this when I learn procedural macros at some point (however, that would most likely be at least several months into the future, if not more).

    Thanks again for creating akin!

    enhancement 
    opened by d4h0 5
Owner
LyonSyonII
LyonSyonII
A truly zero-dependency crate providing quick, easy, reliable, and scalable access to the name "jordin"

jordin Finally! A truly zero-dependency crate providing quick, easy, reliable, and scalable access to the name "jordin". Additionally, this one-of-a-k

jordin 2 Aug 4, 2022
A tool that makes writing WebAssembly Text files easier.

Silly WAT Linker SWL is a tool that makes writing WebAssembly Text files easier. It is future-proof and simple because it doesn’t actually understand

Surma 27 Feb 17, 2023
A lightweight, zero-dependency struct diffing library which allows changed fields to be collected and applied

structdiff A lightweight, zero-dependency struct diffing library which allows changed fields to be collected and applied. Derive Difference on a struc

null 7 Dec 25, 2022
zero-dependency 3d math library in rust

dualquat A lightweight, zero-dependency 3d math library for use in Dual Quaternion based physics simulation. Capable of representing and transforming

null 4 Nov 11, 2022
Pure rust library for reading / writing DNG files providing access to the raw data in a zero-copy friendly way.

DNG-rs   A pure rust library for reading / writing DNG files providing access to the raw data in a zero-copy friendly way. Also containing code for re

apertus° - open source cinema 4 Dec 1, 2022
CLI tool that make it easier to perform multiple lighthouse runs towards a single target and output the result in a plotable format.

Lighthouse Aggregator CLI tool that make it easier to perform multiple lighthouse runs towards a single target and output the result in a "plotable" f

Polestar 1 Jan 12, 2022
Croc-look is a tool to make testing and debuging proc macros easier

croc-look croc-look is a tool to make testing and debuging proc macros easier by these two features Printing the implementation specific generated cod

weegee 7 Dec 2, 2022
Set of tools that make it easier for the operator to manage a TAPLE network.

⚠️ TAPLE is in early development and should not be used in production ⚠️ TAPLE Tools TAPLE (pronounced T+ ?? ['tapəl]) stands for Tracking (Autonomous

Open Canarias 5 Jan 25, 2023
A Faster(⚡) formatter, linter, bundler, and more for JavaScript, TypeScript, JSON, HTML, Markdown, and CSS Lapce Plugin

Lapce Plugin for Rome Lapce-rome is a Lapce plugin for rome, The Rome is faster ⚡ , A formatter, linter, compiler, bundler, and more for JavaScript, T

xiaoxin 7 Dec 16, 2022
Scriptable tool to read and write UEFI variables from EFI shell. View, save, edit and restore hidden UEFI (BIOS) Setup settings faster than with the OEM menu forms.

UEFI Variable Tool (UVT) UEFI Variable Tool (UVT) is a command-line application that runs from the UEFI shell. It can be launched in seconds from any

null 4 Dec 11, 2023
Faster and better alternative to Vtop written in Rust.

Rtop Faster and better alternative to Vtop written in Rust. Work in Progress Features Lightweight < 1MB Responsive UI Sort Process by Memory, CPU Usag

Squitch 7 Nov 18, 2022
Track and query Cargo dependency graphs.

cargo-guppy: track and query dependency graphs This repository contains the source code for: guppy: a library for performing queries on Cargo dependen

guppy 42 Dec 30, 2022
fcp is a significantly faster alternative to the classic Unix cp(1) command

A significantly faster alternative to the classic Unix cp(1) command, copying large files and directories in a fraction of the time.

Kevin Svetlitski 532 Jan 3, 2023
Provide CRUD CLI for Moco Activities with Jira Cloud Sync Option for faster time tracking.

Moco CLI Provide CRUD CLI for Moco Activities with Jira Cloud Sync Option for faster time tracking. Available commands Login Jira Must be called befor

Emanuel Vollmer 7 Nov 18, 2022
Technically, this does exactly what sleep does but completes much faster!

hypersleep Technically does everything that sleep does but it is "blazingly fast!" For example, $ time sleep 1 real 0m1.005s user 0m0.001s sys

Nigel 4 Oct 27, 2022
Backend service to build customer facing dashboards 10x faster. Written in Rust.

Frolic is an open source backend service (written in Rust) to build customer facing dashboards 10x faster. You can directly connect your database to t

Frolic 82 Aug 7, 2023
A reasonable web framework. Others are faster, but this is likely to be more economical.

intercity A web framework for people wanting to build microservices good and build other stuff good too. Intercity is a reasonable web framework. Othe

Tim McNamara 3 Nov 2, 2023
Recompile Rust faster. Good for your flow state.

plonk Plonk is a development-time build tool for Rust projects. cargo install cargo-plonk # fn main() { # lib::say_hello(); # } $ cargo build -p exam

Divy Srivastava 16 Dec 12, 2023
Generate a dependency list to thank them on README.

thanks-dependencies This generates list of dependencies. I think it's better to publish dependencies explicitly on documentation. Of course users can

keiya sasaki 7 Jan 30, 2023