Каков хороший способ сопоставить строки с шаблонами и извлечь значения? - PullRequest
0 голосов
/ 24 мая 2018

Я пытаюсь получить что-то вроде этого (не работает):

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

Я пробовал две разные версии 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()
}

и

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();
    }

И то, и другое кажется невероятно длинным и сложным способом сделать что-то довольно распространенное, я что-то упустил?

Ответы [ 3 ]

0 голосов
/ 25 мая 2018

Вы можете создать мастер Regex, который будет содержать все интересные компоненты, а затем собрать Vec из всех захваченных фрагментов.Это Vec может быть сопоставлено с:

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);
}
0 голосов
/ 25 мая 2018

У вас есть мини-язык для выбора вопросов:

  • выберите следующий вопрос
  • выберите пред вопрос
  • Перейти конкретный вопрос

Если ваши требования заканчиваются здесь, решение на основе Regex идеально подходит.

Если ваш DSL может развиться на основе синтаксического анализаторарешение стоит рассмотреть.

Комбинатор синтаксического анализа nom - это мощный инструмент для построения грамматики, основанной на базовых элементах.

Ваш язык имеет следующие характеристики:

  • имеет три альтернативных оператора (alt!): следующий , пред , goto \ d +

  • самый сложный оператор "goto {number}" состоит из ключевого слова (tag!) goto перед (preceded!) a number (digit!).

  • любое количество пробелов (ws!) должно игнорироваться

Эти требования приводятся в данном описанииn:

#[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);
}
0 голосов
/ 24 мая 2018

Вы можете использовать str::split для этого ( детская площадка )

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
}

выведет

next found
prev found
found goto with number 10
invalid input "this is not valid"
goto with no parameter
...