Возможно ли иметь макрос для литерала хэш-карты с синтаксисом «ключ: значение»?

Этот крейт маплит позволяет использовать литералы хэш-карт с => в качестве разделителя. Я считаю, что невозможно использовать macro_rules!, чтобы разрешить разделитель :, но возможно ли это с помощью макроса, который обрабатывает поток токенов?

Нельзя ли добавить в язык макросы стиля { key: value }, даже в компиляторе?

Я подозреваю, что проблема в конфликте с оператором типа :, но я не могу построить неоднозначный пример, в котором компилятор не может принять решение о том, как интерпретировать :.

Скорее всего, вы сможете добиться того, чего хотите, с помощью функционально-подобный процедурный макрос.

EvilTak 19.03.2022 20:14
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
1
103
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если бы вы заменили => на : в макросе maplit!, вы бы получили эту ошибку:

error: `$key:expr` is followed by `:`, which is not allowed for `expr` fragments
 --> src/lib.rs:6:17
  |
6 |     ($($key:expr: $value:expr),*) => {
  |                 ^ not allowed after `expr` fragments
  |
  = note: allowed there are: `=>`, `,` or `;`

Конкретная проблема заключается в том, что макрос принимает любое произвольное выражение в качестве типа ключа. Вы все еще можете использовать синтаксис { key: value }, но только если key является ident (например) вместо expr.

Вы можете прочитать раздел Последующая установка ограничений неоднозначности макросов на примерах в Rust Reference:

The parser used by the macro system is reasonably powerful, but it is limited in order to prevent ambiguity in current or future versions of the language.

[...]

expr and stmt may only be followed by one of: =>, ,, or ;.

Это не означает, что это обязательно неоднозначно, хотя может возникнуть путаница при анализе примеров с большим количеством :, таких как MyEnum::Variant: ::std::collections::Vec::new(). Он разработан таким образом, потому что только документированные токены гарантировано указывают на конец выражения, а другие токены может стать неоднозначны в будущем.

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


Я попытался создать простой процедурный макрос, используя syn, чтобы помочь в анализе выражений, но он задыхается от синтаксиса Expr : Expr, потому что он пытается жадно проанализировать первое выражение как выражение приписывания типа, которое является еще не завершенным характерная черта. Это своего рода будущее расширение, от которого защищает macro_rules!.

Использование дерева токенов и использование скобок для двусмысленности в Ответ Эйден4 — это то, что нужно, если вы действительно хотите : вместо =>. Это по-прежнему можно сделать с помощью процедурного макроса, но он будет не так легко доступен и будет неоднозначным, если в язык будут добавлены выражения приписывания типов.

Мой вопрос был о процедурных макросах. Это определенно возможно с процедурными макросами?

Test 19.03.2022 20:40

@Test Я отредактировал свой ответ, но вывод немного неудовлетворителен.

kmdreko 19.03.2022 21:30
Ответ принят как подходящий

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

macro_rules! hashmap{
    ( $($key:tt : $val:expr),* $(,)? ) =>{{
        #[allow(unused_mut)]
        let mut map = ::std::collections::HashMap::with_capacity(hashmap!(@count $($key),* ));
        $(
            #[allow(unused_parens)]
            let _ = map.insert($key, $val);
        )*
        map
    }};
    (@replace $_t:tt $e:expr ) => { $e };
    (@count $($t:tt)*) => { <[()]>::len(&[$( hashmap!(@replace $t ()) ),*]) }
}

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

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

let map = hashmap!{ 2 + 2 : "foo" }; 

не сработает, но

let map = hashmap!{ (2 + 2) : "foo" };

будем.

Удивительно. Я предложил добавить это в maplit.

Test 19.03.2022 21:00

Это действительно невероятно. Я думаю, что это так же, как, например, макрос json!() в serde-json, но все же круто видеть написанное объяснение.

rv.kvetch 19.03.2022 23:22

@rv.kvetch макрос json!() использует гораздо более сложный tt muncher, который может анализировать expr : expr и сразу же ломается всякий раз, когда присваивание типов для выражений становится вещью.

Aiden4 20.03.2022 01:24

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