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?
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.
Example. This example uses the Like operator to compare strings to various patterns.
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.
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);
}
You have a mini language for picking questions:
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);
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With