Skip to content

Instantly share code, notes, and snippets.

@NoraCodes
Created July 8, 2016 15:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NoraCodes/34fb1a452f2e9a1bc29ff61587c2ecac to your computer and use it in GitHub Desktop.
Save NoraCodes/34fb1a452f2e9a1bc29ff61587c2ecac to your computer and use it in GitHub Desktop.
// 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