[][src]Macro genco::quote

quote!() { /* proc-macro */ }

Language neutral whitespace sensitive quasi-quoting.


quote!(hello world)

quote! {
    hello...
    world!
}

String Quoting

Literal strings like quote!("hello") are automatically quoted for the target language according to its Lang::quote_string implementation.

To avoid this, you would have to produce a string literal. This can be done through interpolation as shown below in the third example.

use genco::prelude::*;

let tokens: rust::Tokens = quote! {
    "hello world"
    #(quoted("hello world"))
    #("\"hello world\"")
    #_(hello world)
};

assert_eq!(
    vec![
        "\"hello world\"",
        "\"hello world\"",
        "\"hello world\"",
        "\"hello world\"",
    ],
    tokens.to_file_vec()?,
);

The items produced in the token stream above are however subtly different.

The first one is a static quoted string. The second one is a boxed quoted string, who's content is stored on the heap. And the third one is a static literal which bypasses language quoting entirely.

use genco::tokens::{Item, ItemStr};

assert_eq!(
    vec![
        Item::OpenQuote(false),
        Item::Literal(ItemStr::Static("hello world")),
        Item::CloseQuote,
        Item::Push,
        Item::OpenQuote(false),
        Item::Literal(ItemStr::Box("hello world".into())),
        Item::CloseQuote,
        Item::Push,
        Item::Literal(ItemStr::Static("\"hello world\"")),
        Item::Push,
        Item::OpenQuote(false),
        Item::Literal(ItemStr::Static("hello world")),
        Item::CloseQuote
    ],
    tokens,
);

Quoted String Interpolation

Some languages support interpolating values into strings.

Examples of this is:

  • JavaScript - `Hello ${a}` (note the backticks).
  • Dart - "Hello $a" or "Hello ${a + b}".

The quote! macro supports this through a special form of string quoting through the form: #_(<string>). This will produce literal strings but with the appropriate language-specific string interpolation method used.

Interpolated values are specified with $(<quoted>). And $ itself is escaped by repeating it twice with $$. The <quoted> section is interpreted the same as in the quote! macro, but is whitespace sensitive. So $(foo) is not the same as $(foo ) (note the space at the end).

use genco::prelude::*;

let t: dart::Tokens = quote!(#_(Hello $(world)));
assert_eq!("\"Hello $world\"", t.to_string()?);

let t: dart::Tokens = quote!(#_(Hello $(a + b)));
assert_eq!("\"Hello ${a + b}\"", t.to_string()?);

let t: js::Tokens = quote!(#_(Hello $(world)));
assert_eq!("`Hello ${world}`", t.to_string()?);

Interpolation

Elements are interpolated using #, so to include the variable test, you could write #test. Returned elements must implement FormatInto.

Note: # can be escaped by repeating it twice. So ## would produce a single # token.

use genco::prelude::*;

let map = rust::import("std::collections", "HashMap");

let tokens: rust::Tokens = quote! {
    struct Quoted {
        field: #map<u32, u32>,
    }
};

assert_eq!(
    vec![
        "use std::collections::HashMap;",
        "",
        "struct Quoted {",
        "    field: HashMap<u32, u32>,",
        "}",
    ],
    tokens.to_file_vec()?,
);

Inline code can be evaluated through #(<expr>).

use genco::prelude::*;

let world = "world";

let tokens: genco::Tokens = quote!(hello #(world.to_uppercase()));

assert_eq!("hello WORLD", tokens.to_string()?);

Interpolations are evaluated in the same scope as where the macro is invoked, so you can make use of keywords like ? (try) when appropriate.

use std::error::Error;

use genco::prelude::*;

fn age_fn(age: &str) -> Result<rust::Tokens, Box<dyn Error>> {
    Ok(quote! {
        fn age() {
            println!("You are {} years old!", #(str::parse::<u32>(age)?));
        }
    })
}

Escape Sequences

Because this macro is whitespace sensitive, it might sometimes be necessary to provide hints of where they should be inserted.

quote! trims any trailing and leading whitespace that it sees. So quote!(Hello ) is the same as quote!(Hello). To include a space at the end, we can use the special #<space> escape sequence: quote!(Hello#<space>).

The available escape sequences are:

  • #<space> — Inserts a space between tokens. This corresponds to the Tokens::space function.

  • #<push> — Inserts a push operation. Push operations makes sure that any following tokens are on their own dedicated line. This corresponds to the Tokens::push function.

  • #<line> — Inserts a forced line. Line operations makes sure that any following tokens have an empty line separating them. This corresponds to the Tokens::line function.

use genco::prelude::*;

let numbers = 3..=5;

let tokens: Tokens<()> = quote!(foo#<push>bar#<line>baz#<space>biz);

assert_eq!("foo\nbar\n\nbaz biz", tokens.to_string()?);

Loops

To repeat a pattern you can use #(for <bindings> in <expr> { <quoted> }), where is an iterator.

It is also possible to use the more compact #(for <bindings> in <expr> => <quoted>) (note the arrow).

<quoted> will be treated as a quoted expression, so anything which works during regular quoting will work here as well, with the addition that anything defined in <bindings> will be made available to the statement.

use genco::prelude::*;

let numbers = 3..=5;

let tokens: Tokens<()> = quote! {
    Your numbers are: #(for n in numbers => #n#<space>)
};

assert_eq!("Your numbers are: 3 4 5", tokens.to_string()?);

Joining Loops

You can add join (<quoted>) to the end of a repitition specification.

The expression specified in join (<quoted>) is added between each element produced by the loop.

Note: The argument to join us whitespace sensitive, so leading and trailing is preserved. join (,) and join (, ) would therefore produce different results.

use genco::prelude::*;

let numbers = 3..=5;

let tokens: Tokens<()> = quote! {
    Your numbers are: #(for n in numbers join (, ) => #n).
};

assert_eq!("Your numbers are: 3, 4, 5.", tokens.to_string()?);

Conditionals

You can specify a conditional with #(if <condition> => <then>) where is an expression evaluating to a bool, and <then> and <else> are quoted expressions.

It's also possible to specify a condition with an else branch, by using #(if <condition> { <then> } else { <else> }). In this instance, <else> is also a quoted expression.

use genco::prelude::*;

fn greeting(hello: bool, name: &str) -> Tokens<()> {
    quote!(Custom Greeting: #(if hello {
        Hello #name
    } else {
        Goodbye #name
    }))
}

let tokens = greeting(true, "John");
assert_eq!("Custom Greeting: Hello John", tokens.to_string()?);

let tokens = greeting(false, "John");
assert_eq!("Custom Greeting: Goodbye John", tokens.to_string()?);

The <else> branch is optional, so the following is a valid expression that if false, won't result in any tokens:

use genco::prelude::*;

fn greeting(hello: bool, name: &str) -> Tokens<()> {
    quote!(Custom Greeting:#(if hello {
        #<space>Hello #name
    }))
}

let tokens = greeting(true, "John");
assert_eq!("Custom Greeting: Hello John", tokens.to_string()?);

let tokens = greeting(false, "John");
assert_eq!("Custom Greeting:", tokens.to_string()?);

Match Statements

You can specify a match statement with #(match <condition> { [<pattern> => <quoted>,]* }, where is an evaluated expression that is match against each subsequent . If a pattern matches, the arm with the matching <quoted> block is evaluated.

use genco::prelude::*;

enum Greeting {
    Hello,
    Goodbye,
}

fn greeting(greeting: Greeting, name: &str) -> Tokens<()> {
    quote!(Custom Greeting: #(match greeting {
        Greeting::Hello => Hello #name,
        Greeting::Goodbye => Goodbye #name,
    }))
}

let tokens = greeting(Greeting::Hello, "John");
assert_eq!("Custom Greeting: Hello John", tokens.to_string()?);

let tokens = greeting(Greeting::Goodbye, "John");
assert_eq!("Custom Greeting: Goodbye John", tokens.to_string()?);

Scopes

You can use #(ref <binding> { <quoted> }) to gain mutable access to the current token stream. This is an alternative to existing control flow operators if you want to execute more complex logic during evaluation.

For a more compact version, you can also omit the braces by doing #(ref <binding> => <quoted>).

use genco::prelude::*;

fn quote_greeting(surname: &str, lastname: Option<&str>) -> rust::Tokens {
    quote! {
        Hello #surname#(ref toks {
            if let Some(lastname) = lastname {
                toks.space();
                toks.append(lastname);
            }
        })
    }
}

assert_eq!("Hello John", quote_greeting("John", None).to_string()?);
assert_eq!("Hello John Doe", quote_greeting("John", Some("Doe")).to_string()?);

Whitespace Detection

The quote! macro has the following rules for dealing with indentation and spacing.

Spaces — Two tokens that are separated, are spaced. Regardless of how many spaces there are between them. This can also be controlled manually by inserting the #<space> escape in the token stream.

use genco::prelude::*;

let tokens: rust::Tokens = quote! {
    fn     test()     {
        println!("Hello... ");

        println!("World!");
    }
};

assert_eq!(
    vec![
        "fn test() {",
        "    println!(\"Hello... \");",
        "",
        "    println!(\"World!\");",
        "}",
    ],
    tokens.to_file_vec()?,
);

Line breaking — Line breaks are detected by leaving two empty lines between two tokens. This can also be controlled manually by inserting the #<line> escape in the token stream.

use genco::prelude::*;

let tokens: rust::Tokens = quote! {
    fn test() {
        println!("Hello... ");



        println!("World!");
    }
};

assert_eq!(
    vec![
        "fn test() {",
        "    println!(\"Hello... \");",
        "",
        "    println!(\"World!\");",
        "}",
    ],
    tokens.to_file_vec()?,
);

Indentation — Indentation is determined on a row-by-row basis. If a column is further in than the one on the preceeding row, it is indented one level deeper.

If a column starts shallower than a previous row, it will be matched against previously known indentation levels.

All indentations inserted during the macro will be unrolled at the end of it. So any trailing indentations will be matched by unindentations.

use genco::prelude::*;

let tokens: rust::Tokens = quote! {
    fn test() {
            println!("Hello... ");

            println!("World!");
    }
};

assert_eq!(
    vec![
        "fn test() {",
        "    println!(\"Hello... \");",
        "",
        "    println!(\"World!\");",
        "}",
    ],
    tokens.to_file_vec()?,
);

A mismatched indentation would result in an error:

This example deliberately fails to compile
use genco::prelude::*;

let tokens: rust::Tokens = quote! {
    fn test() {
            println!("Hello... ");

        println!("World!");
    }
};
---- src\lib.rs -  (line 150) stdout ----
error: expected 4 less spaces of indentation
--> src\lib.rs:157:9
   |
10 |         println!("World!");
   |         ^^^^^^^