Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a good way to match strings against patterns and extract values?

I am trying get something like this (doesn't work):

match input {
    "next" => current_question_number += 1,
    "prev" => current_question_number -= 1,
    "goto {x}" => current_question_number = x,
    // ...
    _ => status = "Unknown Command".to_owned()
}

I tried two different versions of Regex:

go_match = regex::Regex::new(r"goto (\d+)?").unwrap();
// ...
match input {
    ...
    x if go_match.is_match(x) => current_question_number = go_match.captures(x).unwrap().get(1).unwrap().as_str().parse().unwrap(),
    _ => status = "Unknown Command".to_owned()
}

and

let cmd_match = regex::Regex::new(r"([a-zA-Z]+) (\d+)?").unwrap();
// ...
if let Some(captures) = cmd_match.captures(input.as_ref()) {
        let cmd = captures.get(1).unwrap().as_str().to_lowercase();
        if let Some(param) = captures.get(2) {
            let param = param.as_str().parse().unwrap();
            match cmd.as_ref() {
                "goto" => current_question_number = param,
            }
        } else {
            match cmd.as_ref() {
                "next" => current_question_number += 1,
                "prev" => current_question_number -= 1,
            }
        }
    } else {
        status = "Unknown Command".to_owned();
    }

Both seem like a ridiculously long and and complicated way to do something pretty common, am I missing something?

like image 690
8176135 Avatar asked May 24 '18 09:05

8176135


People also ask

How do you match a string with a pattern?

To match a character in the string expression against a range of characters. Put brackets ( [ ] ) in the pattern string, and inside the brackets put the lowest and highest characters in the range, separated by a hyphen ( – ). Any single character within the range makes a successful match.

Which operator compare a string against a pattern?

Example. This example uses the Like operator to compare strings to various patterns.

What is text pattern matching?

Pattern matching is the process of checking whether a specific sequence of characters/tokens/data exists among the given data. Regular programming languages make use of regular expressions (regex) for pattern matching.


3 Answers

You can create a master Regex that captures all the interesting components then build a Vec of all the captured pieces. This Vec can then be matched against:

extern crate regex;

use regex::Regex;

fn main() {
    let input = "goto 4";
    let mut current_question_number = 0;

    // Create a regex that matches on the union of all commands
    // Each command and argument is captured
    // Using the "extended mode" flag to write a nicer Regex
    let input_re = Regex::new(
        r#"(?x)
        (next) |
        (prev) |
        (goto)\s+(\d+)
        "#
    ).unwrap();

    // Execute the Regex
    let captures = input_re.captures(input).map(|captures| {
        captures
            .iter() // All the captured groups
            .skip(1) // Skipping the complete match
            .flat_map(|c| c) // Ignoring all empty optional matches
            .map(|c| c.as_str()) // Grab the original strings
            .collect::<Vec<_>>() // Create a vector
    });

    // Match against the captured values as a slice
    match captures.as_ref().map(|c| c.as_slice()) {
        Some(["next"]) => current_question_number += 1,
        Some(["prev"]) => current_question_number -= 1,
        Some(["goto", x]) => {
            let x = x.parse().expect("can't parse number");
            current_question_number = x;
        }
        _ => panic!("Unknown Command: {}", input),
    }

    println!("Now at question {}", current_question_number);
}
like image 78
Shepmaster Avatar answered Oct 19 '22 06:10

Shepmaster


You have a mini language for picking questions:

  • pick the next question
  • pick the prev question
  • goto a specific question

If your requirements end here a Regex based solution fits perfectly.

If your DSL may evolve a parser based solution is worth considering.

The parser combinator nom is a powerful tool to build a grammar starting from basic elements.

Your language has these characteristics:

  • it has three alternatives statements (alt!): next, prev, goto \d+

  • the most complex statement "goto {number}" is composed of the keyword (tag!) goto in front of (preceded!) a number (digit!).

  • any numbers of whitespaces (ws!) has to be ignored

These requirements translate in this implementation:

#[macro_use]
extern crate nom;

use nom::{IResult, digit};
use nom::types::CompleteStr;

// we have for now two types of outcome: absolute or relative cursor move
pub enum QMove {
    Abs(i32),
    Rel(i32)
}

pub fn question_picker(input: CompleteStr) -> IResult<CompleteStr, QMove> {
    ws!(input,
        alt!(
            map!(
                tag!("next"),
                |_| QMove::Rel(1)
            ) |
            map!(
                tag!("prev"),
                |_| QMove::Rel(-1)
            ) |
            preceded!(
                tag!("goto"),
                map!(
                    digit,
                    |s| QMove::Abs(std::str::FromStr::from_str(s.0).unwrap())
                )
            )
        )
    )
}

fn main() {
    let mut current_question_number = 60;
    let first_line = "goto 5";

    let outcome = question_picker(CompleteStr(first_line));

    match outcome {
        Ok((_, QMove::Abs(n))) => current_question_number = n,
        Ok((_, QMove::Rel(n))) => current_question_number += n,
        Err(err) => {panic!("error: {:?}", err)}
    }

    println!("Now at question {}", current_question_number);
}
like image 34
attdona Avatar answered Oct 19 '22 05:10

attdona


You can use str::split for this (playground)

fn run(input: &str) {
    let mut toks = input.split(' ').fuse();
    let first = toks.next();
    let second = toks.next();

    match first {
        Some("next") => println!("next found"),
        Some("prev") => println!("prev found"),
        Some("goto") => match second {
            Some(num) => println!("found goto with number {}", num),
            _ => println!("goto with no parameter"),
        },
        _ => println!("invalid input {:?}", input),
    }
}

fn main() {
    run("next");
    run("prev");
    run("goto 10");
    run("this is not valid");
    run("goto"); // also not valid but for a different reason
}

will output

next found
prev found
found goto with number 10
invalid input "this is not valid"
goto with no parameter
like image 44
user25064 Avatar answered Oct 19 '22 06:10

user25064