Matching an i32 value with character constants

Viewed 207

I'm writing a parser in Rust, which needs at various points to match the current token against candidate values. Some of the candidate values are characters, others are integer constants, so the token is declared as i32, which would be plenty to accommodate both. (All the characters to be matched against are ASCII.)

The problem is that when I supply a character constant like '(' to be matched against, the compiler complains that it expected i32 and is getting char.

I tried writing e.g. '(' as i32 but an as expression is not allowed as a match candidate.

Obviously I could look up the ASCII values and provide them as numbers, but it seems there should be a more readable solution. Declaring the token as char doesn't really seem correct, as it sometimes needs to hold integers that are not actually characters.

What's the recommended way to solve this problem?

3 Answers
  • It’s a bit verbose, but your match arms could be of the form c if c == i32::from(b'(').

  • Another alternative would be to match on u8::try_from(some_i32) (branch arms Some(b'(') and then either None if some_i32 == … or None => { match some_i32 { … } }).

  • Yet another would be to change the type from i32 to your own enum, which is probably the cleanest option but might require some convincing of the Rust compiler to get an i32-like representation if you need that for some reason.

  • Finally, you could define const PAREN_OPEN: i32 = b'(' as i32; and use PAREN_OPEN as the pattern.

Since as expressions are allowed in constants, and matching is allowed against constants, you can use a constant:

const LPAREN: i32 = '(' as i32;
match v {
    LPAREN => { ... }
    // ...
}

If you can use nightly, you can use the inline_const_pat feature to reduce the boilerplate:

#![feature(inline_const_pat)]

match v {
    const { '(' as i32 } => { ... }
    // ...
}

Another way: here's a small proc macro that will replace the characters with their numerical value (it does not work with nested char patterns):

use proc_macro::TokenStream;

use quote::ToTokens;

#[proc_macro]
pub fn i32_match(input: TokenStream) -> TokenStream {
    let mut input = syn::parse_macro_input!(input as syn::ExprMatch);
    for arm in &mut input.arms {
        if let syn::Pat::Lit(lit) = &mut arm.pat {
            if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = &mut *lit.expr {
                if let syn::Lit::Char(ch) = lit {
                    *lit = syn::Lit::Int(syn::LitInt::new(
                        &(ch.value() as i32).to_string(),
                        ch.span(),
                    ));
                }
            }
        }
    }
    input.into_token_stream().into()
}
i32_match! {
    match v {
        '(' => { ... }
        // ...
    }
}
Related