bobtail 0.3.0

Generate macro proxies of functions whose tails can be "bobbed" as in cut off
Documentation
# bobtail

Generate macro proxies of functions whose tails can be "bobbed" as in cut off.

<p align="center">
  <img height="300" alt="A bobtail machine" src="https://github.com/user-attachments/assets/68f58156-1308-4bb8-9700-546733d6d18b"><br>
  <em>Figure 1. A bobtail machine.</em>
</p>


This crate produces macro proxies of functions whose trailing arguments may be
omitted or provided with less boilerplate.

## Prototypes

The `define!` macro generates macro proxies for functions and method prototypes.

### Free Functions

```rust
fn f(a: u8, b: Option<u8>) -> u8 {
  b.map(|x| x + a).unwrap_or(a)
}

bobtail::define! {
    fn f(a: u8, #[tail] b: Option<u8>);
}
assert_eq!(f(1, Some(2)), 3);    // Call function.
assert_eq!(f!(1), 1);            // Call macro with omission.
#[cfg(feature = "omit-token")]
assert_eq!(f!(1, _), 1);         // Call macro with explicit omission.
assert_eq!(f!(1, 2), 3);         // Pass unwrapped second argument.
# assert_eq!(f!(1, Some(2)), 3); // Pass wrapped second argument.
```

### Generated Macro Rules
The `bobtail::define!` produces macro rules that might have looked like this code.
```rust,ignore
macro_rules! f {
    ($a:expr) => {
        f($a, None)
    };
    ($a:expr, $b:expr) => {
        f($a, Some($b))
    };
}
```

But `bobtail::define!` can handle the following case, which the above macro can
not.

``` rust,ignore
assert_eq!(f!(1, Some(2)), 3);   // Pass wrapped second argument.
```

How? Because instead of being restricted to `Option`, an ommitable parameter can
be any type that implements `Default` and `From<T>`. What `bobtail::define!`
actually produces is this:

```rust,ignore
macro_rules! f {
    ($a:expr) => {
        f($a, Default::default())
    };
    ($a:expr, $b:expr) => {
        f($a, From::from($b))
    };
}
```

`From<T>` is not only more flexible, but it permits one to use `Some(2)` above
because there is a blanket implementation for `From<T>` for all `T`, which is an
identity function.

### Methods

Methods with a `&self`, `&mut self`, or `self` expect the receiver as the first
argument to the macro.

```rust
struct A;

impl A {
    fn b(&self, a: u8, b: Option<u8>) -> u8 {
        b.map(|x| x + a).unwrap_or(a)
    }
    fn c(self, a: u8) -> u8 {
        a
    }
}

bobtail::define! {
    fn b(&self, a: u8, #[bobtail::tail] b: Option<u8>) -> u8;
    // Name the macro explicitly.
    c_macro => fn c(self, #[tail] a: u8); // Return type can be omitted.
}
let a = A;

assert_eq!(a.b(1, Some(2)), 3);   // Call function.

assert_eq!(b!(a, 1, Some(2)), 3); // Call macro.
assert_eq!(b!(a, 1, 2), 3);       // Omit `Some`.
assert_eq!(b!(a, 1), 1);          // Omit second argument.
#[cfg(feature = "omit-token")]
assert_eq!(b!(a, 1, _), 1);       // Explicitly omit second argument.
assert_eq!(c_macro!(a, 4), 4);    // Consume self.

let a = A;
assert_eq!(c_macro!(a), 0);       // Any `Default` will do.
```

## Attributes

One can also generate macro proxies with attributes.

### Free Functions

```rust
#[bobtail::bob]
fn f(a: u8, #[tail] b: Option<u8>) -> u8 {
  b.map(|x| x + a).unwrap_or(a)
}

assert_eq!(f(1, Some(2)), 3);    // Call function.
assert_eq!(f!(1), 1);            // Call macro with omission.
#[cfg(feature = "omit-token")]
assert_eq!(f!(1, _), 1);         // Call macro with explicit omission.
assert_eq!(f!(1, 2), 3);         // Pass unwrapped second argument.
# assert_eq!(f!(1, Some(2)), 3); // Pass wrapped second argument.
```

### Methods

Methods with a `&self`, `&mut self`, or `self` expect the receiver as the first
argument to the macro proxy.

```rust
struct A;

#[bobtail::block]
impl A {
    #[bobtail::bob]
    fn b(&self, a: u8, #[bobtail::tail] b: Option<u8>) -> u8 {
        b.map(|x| x + a).unwrap_or(a)
    }
    #[bob(c_macro)] // Name the macro explicitly.
    fn c(self, #[tail] a: u8) -> u8 {
        a
    }
}

let a = A;
assert_eq!(a.b(1, Some(2)), 3);   // Call function.

assert_eq!(b!(a, 1, Some(2)), 3); // Call macro.
assert_eq!(b!(a, 1, 2), 3);       // Omit `Some`.
assert_eq!(b!(a, 1), 1);          // Omit second argument.
#[cfg(feature = "omit-token")]
assert_eq!(b!(a, 1, _), 1);       // Explicitly omit second argument.
assert_eq!(c_macro!(a, 4), 4);    // Consume self.

let a = A;
assert_eq!(c_macro!(a), 0);       // Any `Default` will do.
```
## Motivation and Justification

This crate was inspired by my work on
[Nano-9](https://github.com/shanecelis/nano-9), a Pico-8 compatibility layer for
Bevy. Pico-8's Lua API has many arguments that are often omitted. Consider
Pico-8's text drawing function `print`.

``` lua
-- print(str, [x,] [y,] [color])
print("hello world")
-- No x? No y? No problem.
```

Nano-9 provides the Lua API as-is, but it also provides a Pico-8-like API in
Rust for which the above looks like this:

``` rust,ignore
// print(str, vec2, color, /* Nano-9 extensions: */ font_size, font_index)
pico8.print("hello world", None, None, None, None).unwrap();
```

The aim of this crate is to offer an API on the Rust side that is not so
verbose.

``` rust,ignore
print!(pico8, "hello world").unwrap();
```

### A Caution

The above are my reasons for creating this crate, but that does not mean I
wholeheartly endorse this kind of positional, omittable, API design. If I were
not constrained by Pico-8's initial design and wanting to bear a strong
resemblance to it, I would consider using structs expressively as named and
omittable arguments potentially using other crates like
[bon](https://crates.io/crates/bon),
[typed-builder](https://crates.io/crates/typed-builder), and
[derive_builder](https://crates.io/crates/derive_builder).

## Features

bobtail generates self-contained and straightforward to inspect macros, but one
cannot use the underscore '_' to omit values without using the `omit-token`
feature.

### `omit-token` - Enable `_` placeholder syntax

If one wants to use `_` as a placeholder for default values anywhere in the tail
arguments, enable the `omit-token` feature:

```toml
[dependencies]
bobtail = { version = "0.3", features = ["omit-token"] }
```

With this feature enabled, one can write:

```rust,ignore
assert_eq!(f!(1, _), 1);         // Explicitly use default for second arg
assert_eq!(f!(1, _, 3), ...);    // Use default for second, explicit third
```

Without the feature, one would need to pass explicit default values:

```rust,ignore
assert_eq!(f!(1, None, 0), 1);           // Explicitly pass `None`
assert_eq!(f!(1, Default::default(), 0), // Explicitly pass default
           1);
```

The trade-off is that `omit-token` uses a recursive helper macro internally,
which makes macro expansion slightly more complex. 

## Advanced Features

### Macro Visibility

By default, the generated macro has the same visibility as the function. One can
override this with explicit visibility:

For `#[bob]` attribute:

```rust,ignore
#[bobtail::bob(pub(crate) my_macro)]  // Define visibility and macro name.
#[bobtail::bob(pub(crate))]           // Define macro visibility.
#[bobtail::bob(pub(self))]            // Define private macro.
```

For `define!` macro:

```rust,ignore
bobtail::define! {
    pub(crate) my_macro => fn foo(a, #[tail] b);  // Define visibility and macro name.
    pub(crate) => fn bar(a, #[tail] b);           // Define macro visibility.
    pub(self) => fn bar(a, #[tail] b);            // Define private macro.
}
```

- `pub` visibility adds `#[macro_export]`.
- `pub(crate)` and `pub(in path)` use re-exports.
- `pub(self)` creates a private macro even for a public function.

### Macro Attributes

Add custom attributes to generated macros using `#[bobtail::macro_attrs(...)]`:

```rust,ignore
#[bobtail::macro_attrs(doc(hidden))]
#[bobtail::bob]
pub fn foo(a: u8, #[tail] b: Option<u8>) -> u8 {
    b.map(|x| x + a).unwrap_or(a)
}
```

For `define!`, use outer attributes directly:

```rust,ignore
bobtail::define! {
    #[doc(hidden)]
    fn foo(a, #[tail] b);
}
```

### Typeless Parameters in define!

Types are optional in `define!` since only parameter counts and `#[tail]`
markers matter for macro generation:

```rust,ignore
bobtail::define! {
    fn foo(a, #[tail] b);  // No types needed
}
```

This is equivalent to:

```rust,ignore
bobtail::define! {
    fn foo(a: u8, #[tail] b: Option<u8>) -> u8;
}
```

## Install

Add bobtail to a project with the following command:

``` sh
cargo add bobtail
```

Or add these lines to the Cargo.toml file:

```toml
[dependencies]
bobtail = "0.3"
```

## License

This crate is licensed under the MIT License or the Apache License 2.0.