macroonz-compiler 0.2.0

Deterministic Rust code generation for procedural macros: plan, render, close, explain, and bind one sealed expansion from declared input.
Documentation
//! Composing the Rust a renderer writes, out of the tokens that spell it.
//!
//! A renderer states what it means — a path, a call, a binding, an attribute — and never assembles punctuation by hand.
//! A path stated as segments cannot be mis-spaced and cannot lose a colon; a call stated as a path and its arguments cannot lose a parenthesis; and neither can be built out of a string somebody supplied.
//!
//! Every helper here composes and never bounds.
//! The declared magnitude bites only where a group closes, so exactly the helpers that write a group return [`Overflow`] and the rest are total.

use super::{GeneratedDelimiter, GeneratedToken};
use crate::bounded::Overflow;

/// One delimited group.
///
/// # Errors
///
/// Returns [`Overflow`] where the group carries more tokens than the declared magnitude admits.
pub fn group(
    delimiter: GeneratedDelimiter,
    tokens: Vec<GeneratedToken>,
) -> Result<GeneratedToken, Overflow> {
    GeneratedToken::group(delimiter, tokens)
}

/// One macro metavariable, as the two tokens that spell it.
///
/// The `$` is written joint, so the projection a person reads is `$name` rather than `$ name`.
#[must_use]
pub fn metavariable(name: &str) -> Vec<GeneratedToken> {
    vec![GeneratedToken::joint('$'), GeneratedToken::word(name)]
}

/// The absolute path `::a::b::c`.
#[must_use]
pub fn absolute_path(segments: &[&str]) -> Vec<GeneratedToken> {
    let mut tokens = Vec::new();
    extend_path(&mut tokens, segments);
    tokens
}

/// The path `root::a::b::c`, rooted at a crate the caller named.
///
/// The root is written as a plain word, so a caller that renamed its dependency is named the way it named itself.
#[must_use]
pub fn bound_path(root: &str, segments: &[&str]) -> Vec<GeneratedToken> {
    let mut tokens = vec![GeneratedToken::word(root)];
    extend_path(&mut tokens, segments);
    tokens
}

/// The path `$binding::a::b::c`, rooted at a metavariable a macro shell will bind.
#[must_use]
pub fn twin_path(binding: &str, segments: &[&str]) -> Vec<GeneratedToken> {
    let mut tokens = metavariable(binding);
    extend_path(&mut tokens, segments);
    tokens
}

/// The path `$root $(:: $segment)*::a::b`, rooted at the complete segmented path a macro matcher captured.
///
/// The repetition group has fixed arity, so its fit under the generated-token magnitude is settled at compile time.
#[must_use]
pub(crate) fn segmented_twin_path(
    root_binding: &str,
    segment_binding: &str,
    segments: &[&str],
) -> Vec<GeneratedToken> {
    let mut tokens = metavariable(root_binding);
    tokens.push(GeneratedToken::joint('$'));
    tokens.push(GeneratedToken::fixed_group(
        GeneratedDelimiter::Parenthesis,
        [
            GeneratedToken::joint(':'),
            GeneratedToken::alone(':'),
            GeneratedToken::joint('$'),
            GeneratedToken::word(segment_binding),
        ],
    ));
    tokens.push(GeneratedToken::alone('*'));
    extend_path(&mut tokens, segments);
    tokens
}

/// Write `::segment` for each segment onto a path being built.
fn extend_path(tokens: &mut Vec<GeneratedToken>, segments: &[&str]) {
    for segment in segments {
        tokens.push(GeneratedToken::joint(':'));
        tokens.push(GeneratedToken::alone(':'));
        tokens.push(GeneratedToken::word(segment));
    }
}

/// One call `path(arguments)`.
///
/// # Errors
///
/// Returns [`Overflow`] where the argument list outgrows the declared magnitude.
pub fn call(
    mut path: Vec<GeneratedToken>,
    arguments: Vec<GeneratedToken>,
) -> Result<Vec<GeneratedToken>, Overflow> {
    path.push(group(GeneratedDelimiter::Parenthesis, arguments)?);
    Ok(path)
}

/// One call `receiver.method(arguments)`.
///
/// # Errors
///
/// Returns [`Overflow`] where the argument list outgrows the declared magnitude.
pub fn method_call(
    mut receiver: Vec<GeneratedToken>,
    method: &str,
    arguments: Vec<GeneratedToken>,
) -> Result<Vec<GeneratedToken>, Overflow> {
    receiver.push(GeneratedToken::alone('.'));
    receiver.push(GeneratedToken::word(method));
    call(receiver, arguments)
}

/// One chain `receiver.first().second().third()`, every method taking no argument.
///
/// # Errors
///
/// Returns [`Overflow`] where a call in the chain outgrows the declared magnitude.
pub fn method_chain(
    mut receiver: Vec<GeneratedToken>,
    methods: &[&str],
) -> Result<Vec<GeneratedToken>, Overflow> {
    for method in methods {
        receiver = method_call(receiver, method, Vec::new())?;
    }
    Ok(receiver)
}

/// One statement `let name = expression;`.
///
/// A value a rendered block needs twice is bound once, which makes the agreement between its two readers structural rather than a comparison of two separately built values.
#[must_use]
pub fn bound_local(name: &str, expression: Vec<GeneratedToken>) -> Vec<GeneratedToken> {
    let mut tokens = vec![
        GeneratedToken::word("let"),
        GeneratedToken::word(name),
        GeneratedToken::alone('='),
    ];
    tokens.extend(expression);
    tokens.push(GeneratedToken::alone(';'));
    tokens
}

/// The type `::core::result::Result<ok, error>`.
#[must_use]
pub fn result_type(ok: Vec<GeneratedToken>, error: Vec<GeneratedToken>) -> Vec<GeneratedToken> {
    let mut tokens = absolute_path(&["core", "result", "Result"]);
    tokens.push(GeneratedToken::alone('<'));
    tokens.extend(ok);
    tokens.push(GeneratedToken::alone(','));
    tokens.extend(error);
    tokens.push(GeneratedToken::alone('>'));
    tokens
}

/// One item `const name: kind = value;`.
///
/// The visibility is the caller's and is written before this.
#[must_use]
pub fn constant(
    name: &str,
    kind: Vec<GeneratedToken>,
    value: Vec<GeneratedToken>,
) -> Vec<GeneratedToken> {
    let mut tokens = vec![GeneratedToken::word("const"), GeneratedToken::word(name)];
    tokens.push(GeneratedToken::alone(':'));
    tokens.extend(kind);
    tokens.push(GeneratedToken::alone('='));
    tokens.extend(value);
    tokens.push(GeneratedToken::alone(';'));
    tokens
}

/// One comparison `left == right`.
#[must_use]
pub fn equality(mut left: Vec<GeneratedToken>, right: Vec<GeneratedToken>) -> Vec<GeneratedToken> {
    left.push(GeneratedToken::joint('='));
    left.push(GeneratedToken::alone('='));
    left.extend(right);
    left
}

/// Every comparison joined by `&&`.
#[must_use]
pub fn and_all(comparisons: Vec<Vec<GeneratedToken>>) -> Vec<GeneratedToken> {
    let mut tokens = Vec::new();
    for (position, comparison) in comparisons.into_iter().enumerate() {
        if position > 0 {
            tokens.push(GeneratedToken::joint('&'));
            tokens.push(GeneratedToken::alone('&'));
        }
        tokens.extend(comparison);
    }
    tokens
}

/// Two token runs separated by a comma.
#[must_use]
pub fn comma(mut left: Vec<GeneratedToken>, right: Vec<GeneratedToken>) -> Vec<GeneratedToken> {
    left.push(GeneratedToken::alone(','));
    left.extend(right);
    left
}

/// Every token run separated by a comma, with no trailing one.
#[must_use]
pub fn comma_many(parts: Vec<Vec<GeneratedToken>>) -> Vec<GeneratedToken> {
    let mut tokens = Vec::new();
    for (position, part) in parts.into_iter().enumerate() {
        if position > 0 {
            tokens.push(GeneratedToken::alone(','));
        }
        tokens.extend(part);
    }
    tokens
}

/// Two spellings as the two comma-separated text literals a two-argument parser takes.
#[must_use]
pub fn text_pair(first: &str, second: &str) -> Vec<GeneratedToken> {
    vec![
        GeneratedToken::text(first),
        GeneratedToken::alone(','),
        GeneratedToken::text(second),
    ]
}

/// One attribute `#[body]`.
///
/// # Errors
///
/// Returns [`Overflow`] where the body outgrows the declared magnitude.
pub fn attribute(body: Vec<GeneratedToken>) -> Result<Vec<GeneratedToken>, Overflow> {
    Ok(vec![
        GeneratedToken::alone('#'),
        group(GeneratedDelimiter::Bracket, body)?,
    ])
}

/// One documentation attribute over one sentence.
///
/// # Errors
///
/// Returns [`Overflow`] where the attribute outgrows the declared magnitude.
pub fn documentation(sentence: &str) -> Result<Vec<GeneratedToken>, Overflow> {
    attribute(vec![
        GeneratedToken::word("doc"),
        GeneratedToken::alone('='),
        GeneratedToken::text(sentence),
    ])
}

/// The `::std::vec![…]` a roster-taking constructor is handed.
///
/// # Errors
///
/// Returns [`Overflow`] where the roster outgrows the declared magnitude.
pub fn roster(items: Vec<GeneratedToken>) -> Result<Vec<GeneratedToken>, Overflow> {
    let mut tokens = absolute_path(&["std", "vec"]);
    tokens.push(GeneratedToken::alone('!'));
    tokens.push(group(GeneratedDelimiter::Bracket, items)?);
    Ok(tokens)
}

/// One owned `Vec` value, using `Vec::new()` when empty and `Vec::from([members])` otherwise.
///
/// # Errors
///
/// Returns [`Overflow`] where either generated group outgrows the declared token magnitude.
pub(crate) fn vector(members: Vec<Vec<GeneratedToken>>) -> Result<Vec<GeneratedToken>, Overflow> {
    if members.is_empty() {
        return call(absolute_path(&["std", "vec", "Vec", "new"]), Vec::new());
    }
    let array = group(GeneratedDelimiter::Bracket, comma_many(members))?;
    call(absolute_path(&["std", "vec", "Vec", "from"]), vec![array])
}