Макрос ржавчины с точками в деревьях токенов

Я пытаюсь создать макрос Rust, который генерирует конечные точки, такие как "/api/v2/stats/<token>".

Я подумал, что было бы круто сделать так, как это делает warp path: warp::path!("sum" / u32 / u32). Но в варпе им не нужно поддерживать деревья токенов с точками, то есть выражениями, и извлекать их значения...

То, что я получил до сих пор, это:

macro_rules! path {
    () => {};
    ($next:tt $($tail:tt)*) => {{
        println!(stringify!($next));
        path!($($tail)*);
    }};
}

fn main() {
    struct Data {
        event: String,
        token: String,
    }
    let data = Data {
        event: String::from("stats"),
        token: String::from("a1b2c3d4"),
    };
    path!("/api/v2" / data.event / data.token)
}

Это показывает, что видит макрос:

"/api/v2"
/
data
.
event
/
data
.
token

Я знаю, что деревья токенов могут быть переинтерпретированы как выражения позже, поэтому должен быть способ сохранить tt в хвосте, отделить косые черты от «чего-либо еще» и получить их как выражения для получения их значений, но я не вижу, как это сделать. . Как мне заставить его вернуть строку "/api/v2/stats/a1b2c3d4"?

Дополнительные примеры входных данных и ожидаемых результатов:

struct Conf<'a> {env: &'a str};
let conf = Conf { env: "dev" };
let subsystem = "stats";

path!("/"); // root: "/"
path!("/api/v1" / data.event / "results"); // "/api/v1/stats/results"
path!("/api/v2/errors" / conf.env / subsystem); // "/api/v2/errors/dev/stats"

Обновлено: я как бы сделал это с выражениями, которые не такие выразительные, скорее обходной путь, но он работает:

macro_rules! path {
    ($($path:expr),+) => {{
        let mut s = [$($path),+].into_iter().flat_map(|p| [p, "/"]).collect::<String>();
        s.pop();
        s
    }}
}

let result_url = path!("/api/v2", &data.event, &data.token);

Спасибо!

Можете ли вы привести еще несколько примеров использования с вводом и ожидаемым выводом?

PitaJ 18.11.2022 17:31

Я нашел возможный обходной путь, используя запятые и выражения, но это еще не то, что мне хотелось бы...

rsalmei 18.11.2022 18:07

Вы также можете потребовать круглые скобки для нелитералов во входных данных макроса: path!("/api/v2" / (data.event) / (data.token))

Peter Hall 18.11.2022 18:08

На самом деле вам нужно будет сделать что-то подобное, поскольку вам придется анализировать data.event как выражение, за которым грамматически не может следовать /. Это потребуется для поддержки таких вещей, как data.token.to_uppercase().

Peter Hall 18.11.2022 18:11

Хорошая идея, спасибо @PeterHall! Но я все же думаю, что должен быть способ интерпретировать их как выражения только на самом последнем шаге, как в ТТ-мунчере...

rsalmei 18.11.2022 18:23

Возможно, было бы проще использовать пользовательскую структуру, которая реализует Div<Rhs: Display> для добавления элементов пути, подобно pathlib Python.

EvilTak 18.11.2022 18:58

Проблема в том, что произвольное выражение может содержать /, поэтому вам нужно устранить неоднозначность, если вы не хотите ограничиваться только цепочкой методов доступа к полям с . .

Peter Hall 18.11.2022 18:59
[JS за 1 час] - 9. Асинхронный
[JS за 1 час] - 9. Асинхронный
JavaScript является однопоточным, то есть он может обрабатывать только одну задачу за раз. Для обработки длительных задач, таких как сетевые запросы,...
Топ-10 компаний-разработчиков PHP
Топ-10 компаний-разработчиков PHP
Если вы ищете надежных разработчиков PHP рядом с вами, вот список лучших компаний по разработке PHP.
Скраппинг поиска Apple App Store с помощью Python
Скраппинг поиска Apple App Store с помощью Python
📌Примечание: В этой статье я покажу вам, как скрапировать поиск Apple App Store и получить точно такой же результат, как на Apple iMac, потому что...
Редкие достижения на Github ✨
Редкие достижения на Github ✨
Редкая коллекция доступна в профиле на GitHub ✨
Подъем в javascript
Подъем в javascript
Hoisting - это поведение в JavaScript, при котором переменные и объявления функций автоматически "перемещаются" в верхнюю часть соответствующих...
Улучшение генерации файлов Angular
Улучшение генерации файлов Angular
Angular - это фреймворк. Вы можете создать практически любое приложение без использования сторонних библиотек.
0
7
143
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Для этого вы можете использовать tt muncher:

macro_rules! path {
    (@munch / ) => {
        String::from("/")
    };
    (@munch / $part:literal $(/)* ) => {
        format!("/{}", $part)
    };
    (@munch / $part:literal / $($tail:tt)* ) => {
        format!("/{}{}", $part, path!(@munch / $($tail)*))
    };
    (@munch / $($parts:ident).+ $(/)* ) => {
        format!("/{}", & $($parts).+)
    };
    (@munch / $($parts:ident).+ / $($tail:tt)* ) => {
        format!("/{}{}", & $($parts).+, path!(@munch / $($tail)*))
    };
    (/ $($input:tt)*) => {
        path!(@munch / $($input)*)
    };
}

Детская площадка

В настоящее время это производит вложенные вызовы format!. Чтобы избежать этого, вам, вероятно, также потребуется использовать аккумулятор. Меня это интересует, поэтому я работаю над версией с аккумулятором.

Редактировать: А вот версия аккумулятора

macro_rules! path {
    (/) => {
        String::from("/")
    };
    (/ $($input:tt)*) => {
        path!(@munch { / $($input)* } => ())
    };

    (@munch { / $part:literal $(/)* } => ($($accum:expr),*)) => {
        path!(@done ($( $accum, )* $part))
    };
    (@munch { / $part:literal / $($tail:tt)* } => ($($accum:expr),*)) => {
        path!(@munch { / $($tail)* } => ($( $accum, )* $part ))
    };
    
    (@munch { / $($parts:ident).+ $(/)* } => ($($accum:expr),*)) => {
        path!(@done ($( $accum, )* & $($parts).+ ))
    };
    (@munch { / $($parts:ident).+ / $($tail:tt)* } => ($($accum:expr),*)) => {
        path!(@munch { / $($tail)* } => ($( $accum, )* & $($parts).+ ))
    };

    (@replace_expr $_t:tt => $sub:expr) => { $sub };
    (@done ($($accum:expr),*)) => {
        format!(
            concat!($( path!(@replace_expr ($accum) => "/{}"), )*),
            $( $accum, )*
        )
    };
}

Детская площадка

Edit2: по вашему запросу другая версия, которая использует два аккумулятора для поддержки ведущего литерала

macro_rules! path {
    (/) => {
        String::from("/")
    };
    (/ $($input:tt)*) => {
        path!(@munch { / $($input)* } -> () : ())
    };
    ($part:literal $(/)*) => {
        String::from($part)
    };
    ($part:literal $($input:tt)*) => {
        path!(@munch { $($input)* } -> ("{}") : ($part))
    };

    (@munch { / $part:literal $(/)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        path!(@done ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* $part))
    };
    (@munch { / $part:literal / $($tail:tt)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        path!(@munch { / $($tail)* } -> ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* $part ))
    };
    
    (@munch { / $($parts:ident).+ $(/)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        path!(@done ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* & $($parts).+ ))
    };
    (@munch { / $($parts:ident).+ / $($tail:tt)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        path!(@munch { / $($tail)* } -> ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* & $($parts).+ ))
    };

    (@done ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
        format!(
            concat!($( $fmt_accum, )*),
            $( $args_accum, )*
        )
    };
}

Детская площадка

Вау, потрясающе @PitaJ!! Вложенные format! можно просто заменить на format_args! вызовы с format!("{}", path!()) на основном уровне. Я просто считаю, что мнемоника не всегда начинается с одной косой черты, но я попытался удалить это и не смог...

rsalmei 18.11.2022 18:50

Интересно, что вам даже не нужно было приводить части к выражениям, чтобы получить их значения! ДО этого я не знал, что можно "собирать" идентификаторы с точками, и код будет работать, очень круто!!

rsalmei 18.11.2022 18:54

@rsalmei Проверьте редактирование с накопительной версией

PitaJ 18.11.2022 19:01

Просто потрясающе @PitaJ! Я должен изучить это досконально, потому что, признаюсь, я не могу понять это, хотя... И вишенка на торте, можно ли иметь path!("/api... вместо path!(/ "api...?

rsalmei 18.11.2022 19:06

Должно быть возможно. Дай мне попробовать

PitaJ 18.11.2022 19:23

Вероятно, сложнее следовать, но меньше правил, поэтому немного меньше рекурсии: play.rust-lang.org/…

eggyal 18.11.2022 19:26

PitaJ 18.11.2022 19:32

ВАУ, это потрясающе @eggyal! Я также хотел бы тщательно изучить вашу версию, потому что я не могу ее понять.... Довольно круто!

rsalmei 18.11.2022 19:34

eggyal 18.11.2022 19:37

Макросы Rust - это сам по себе язык, на самом деле огромный, и я рад, что здесь есть два мастера макросов!! Спасибо PitaJ и Эггьял.

rsalmei 18.11.2022 19:37

@rsalmei см. редактирование с последней версией. Если он вас удовлетворит, я добавлю несколько комментариев, чтобы объяснить, что происходит.

PitaJ 18.11.2022 19:55

Да! Это даже больше, чем я ожидал @PitaJ! Вы сделали так, чтобы он поддерживал ОБА ведущую косую черту и литерал.... Это действительно здорово, я очень благодарен!

rsalmei 18.11.2022 20:09

О, @PitaJ, пожалуйста, связанное с этим сомнение: в другой части моего кода мне нужно включить суффиксы к этим путям, что-то вроде «?err=invalid_key». Я пытаюсь использовать макрос path! в другом макросе, который вызывает его только с одной частью: concat!("/failure", $suffix), но не получается с no rules expected the token concat!("/failure", "?err=invalid_key")... Могу ли я что-то сделать, чтобы сначала разрешить этот литерал?

rsalmei 18.11.2022 20:30

Может быть полезно кинуть ссылку на неофициальную ссылку на tt munchers.

Jmb 18.11.2022 20:30

Спасибо @Jmb, но несколько часов назад я бросил эту точную ссылку в комментариях к вопросу.

rsalmei 18.11.2022 20:33

@rsalmei, к сожалению, именно так работают макросы в Rust, но вы можете справиться с этим вручную, как показано здесь

PitaJ 18.11.2022 23:15

На самом деле вот более простая версия, которая поддерживает все виды выражений в качестве заключительной части: площадка. Он не поддерживает конечные косые черты, но я думаю, что это не так уж и важно.

PitaJ 18.11.2022 23:33

Другие вопросы по теме