Created
July 8, 2016 15:16
-
-
Save NoraCodes/34fb1a452f2e9a1bc29ff61587c2ecac to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Hello! I'm Leo Tindall, the SilverWingedSeraph, and this is a follow-up to my tutorial on | |
// using match expressions in Rust. | |
// In my last video, we created a finite state machine to parse and modify some simple markup. | |
// In this video, we'll make the machine more abstract and more concise. | |
// We'll start out the same way: defining the four states of the machine. | |
// However, we'll use a neat trick that Rust | |
// provides to make things easier later on. We'll ask the compiler to derive the Copy and Clone | |
// traits on MachineState, so we don't have to worry about borrowing and ownership. | |
#[derive(Copy, Clone)] | |
enum MachineState { | |
Normal, | |
Comment, | |
Upper, | |
Lower, | |
} | |
// Now we'll define a function to represent one step of the FSM. It will take a state and a char, | |
// and return a tuple of (Option<char> and MachineState), just as before. | |
fn machine_cycle(state: MachineState, character: char) -> (Option<char>, MachineState) { | |
// This line brings in the ASCII upper and lowercase functions from the standard library. | |
use std::ascii::AsciiExt; | |
// This line lets us refer to MachineState::Normal, for example, as simply Normal | |
use MachineState::*; | |
// Here is where our match statement comes into play. We can execute different | |
// code depending on the machine's state. | |
// | |
// The syntax is match, name of variable, brackets. In this case, instead of nested match | |
// expressions, we'll use tuple matching: | |
match (state, character) { | |
// Branches are denoted by the value followed by an arrow (equals-greater-than) | |
// | |
// Here are all our state transitions. First, from normal to other states: | |
(Normal, '#') => (None, Comment), | |
(Normal, '^') => (None, Upper), | |
(Normal, '_') => (None, Lower), | |
// Then, for each of the other states to Normal: | |
(Comment, '#') => (None, Normal), | |
(Upper, '^') => (None, Normal), | |
(Lower, '_') => (None, Normal), | |
// Finally, these are the cases in which the machine returns characters, transformed or not | |
(Normal, _) => (Some(character), Normal), | |
(Upper, _) => (Some(character.to_ascii_uppercase()), Upper), | |
(Lower, _) => (Some(character.to_ascii_lowercase()), Lower), | |
// The "transformation" done by the comment state is simply deletion | |
(Comment, _) => (None, Comment), | |
} | |
// We don't even need a return statement. That match expression will evaluate to the value we | |
// want to return, and since it's the last executed and has no semicolon, its value will be | |
// returned. | |
} | |
// Now, we can deploy the state machine. Instead of deploying directly to the main() function, | |
// we'll abstract the loop out into its own function. | |
// This function will take an &str as input and return a String. | |
fn run_machine(input: &str) -> String { | |
// This function is very similar to the main() function we wrote in the last video. | |
// First, we set the default state: | |
let mut state = MachineState::Normal; | |
let mut processed_string = String::new(); | |
// As before, we'll loop through the input string's chars | |
for character in input.chars() { | |
// Now that MachineState is Copy, we no longer have to borrow state out to machine_cycle | |
let (output, new_state) = machine_cycle(state, character); | |
// Since we don't have to do anything for the None case of output's Option type, we can | |
// use if let syntax: | |
if let Some(c) = output { | |
processed_string.push(c); | |
} | |
// The state transition happens just as before. | |
state = new_state; | |
} | |
// And finally, our returnless return: | |
processed_string | |
} | |
// Now that we've abstracted away the internals of running the machine, we can simply call | |
// run_machine in our main function. | |
fn main() { | |
let input_string = "This text is _LOWERCaSe_, this is ^capiTaL^. #this is a comment# "; | |
let output_string = run_machine(input_string); | |
println!("Input:\t{}\nOutput:\t{}", input_string, output_string); | |
} | |
// A quick shoutout to /u/notriddle, /u/handle0174, /u/SimonWoodburyForget, and /u/Archaeanimus for | |
// pointing out improvements to the example code in the original video. | |
// Thanks also to Plasticcaz for pointing out the opportunity for a more concise unwrapping of | |
// output. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment