patternrustMinor
Rust Console RPN Calculator
Viewed 0 times
rustconsolecalculatorrpn
Problem
I've been trying to teach myself Rust, and I decided that a simple console calculator would be a good project to learn from.
``
``
use std::io;
extern crate regex;
#[macro_use] extern crate lazy_static;
use regex::Regex;
fn main() {
loop {
println!("Enter input:");
let mut input = String::new();
io::stdin().read_line(&mut input)
.expect("Failed to read line");
let tokens = tokenize(input);
let stack = shunt(tokens);
let res = calculate(stack);
println!("{}", res);
}
}
#[derive(Debug)]
#[derive(PartialEq)]
enum Token {
Number (i64),
Plus,
Sub,
Mul,
Div,
LeftParen,
RightParen,
}
/// Tokenizes the input string into a Vec of Tokens.
fn tokenize(mut input: String) -> Vec {
lazy_static! {
static ref NUMBER_RE: Regex = Regex::new(r"^[0-9]+").unwrap();
}
let mut res = vec![];
while !(input.trim_left().is_empty()) {
input = input.trim_left().to_string();
input = if let Some((_, end)) = NUMBER_RE.find(&input) {
let (num, rest) = input.split_at_mut(end);
res.push(Token::Number(num.parse::().unwrap()));
rest.to_string()
} else {
res.push(match input.chars().nth(0) {
Some('+') => Token::Plus,
Some('-') => Token::Sub,
Some('*') => Token::Mul,
Some('/') => Token::Div,
Some('(') => Token::LeftParen,
Some(')') => Token::RightParen,
_ => panic!("Unknown character!")
});
input.trim_left_matches(|c| c == '+' ||
c == '-' ||
c == '*' ||
c == '/' ||
c == '(' ||
c == ')').to_string()
}
}
res
}
/// Transforms the tokens created by tokenize` intSolution
-
There's no space before the parenthesis on an enum variant
-
-
Make
-
Inside of
-
There's no need for
-
There's no need for the large amount of string allocation. Take in a
-
Instead of trimming the input by the operator characters, skip by the number of bytes the first character was.
-
There's no need to specify the type of
-
The code trims the left multiple times; just do it once.
-
There's no need to specify type of
-
There's no need to use the
Larger ideas:
-
Create a newtype around
-
Create multiple types of enums; one with parens and one without. Then there's one less place to have an
-
The error handling is pretty rough for the end user. Mismatched parenthesis kill the program instead of explaining the error and letting the user continue. There's no (obvious) way to exit the program other than by killing it or closing stdin (which produces another error message).
``
fn calculate
There's no space before the parenthesis on an enum variant
-
derives are combined into one line.-
Make
precedence an inherent method on Token.-
Inside of
precedence, match on the dereference of the value. This avoids the spread of &.-
There's no need for
lazy_static; just create a structure and put the regex in it, the reuse it in the loop.-
There's no need for the large amount of string allocation. Take in a
&str instead of a String and simply slice it up.-
Instead of trimming the input by the operator characters, skip by the number of bytes the first character was.
-
There's no need to specify the type of
parse.-
The code trims the left multiple times; just do it once.
-
There's no need to specify type of
queue as it's inferrable.-
There's no need to use the
@ pattern binding; can just use token.Larger ideas:
-
Create a newtype around
Vec to indicate that data is in RPN order. Types avoid the need for documentation.-
Create multiple types of enums; one with parens and one without. Then there's one less place to have an
unreachable. If there's subsets, you could embed the subset in the superset. -
The error handling is pretty rough for the end user. Mismatched parenthesis kill the program instead of explaining the error and letting the user continue. There's no (obvious) way to exit the program other than by killing it or closing stdin (which produces another error message).
``
use std::io;
extern crate regex; // 0.1.80
#[macro_use]
extern crate lazy_static;
use regex::Regex;
fn main() {
let tokenizer = Tokenizer::new();
loop {
println!("Enter input:");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let tokens = tokenizer.tokenize(&input);
let stack = shunt(tokens);
let res = calculate(stack);
println!("{}", res);
}
}
#[derive(Debug, PartialEq)]
enum Token {
Number(i64),
Plus,
Sub,
Mul,
Div,
LeftParen,
RightParen,
}
impl Token {
/// Returns the precedence of op
fn precedence(&self) -> usize {
match *self {
Token::Plus | Token::Sub => 1,
Token::Mul | Token::Div => 2,
_ => 0,
}
}
}
struct Tokenizer {
number: Regex,
}
impl Tokenizer {
fn new() -> Tokenizer {
Tokenizer {
number: Regex::new(r"^[0-9]+").expect("Unable to create the regex"),
}
}
/// Tokenizes the input string into a Vec of Tokens.
fn tokenize(&self, mut input: &str) -> Vec {
let mut res = vec![];
loop {
input = input.trim_left();
if input.is_empty() { break }
let (token, rest) = match self.number.find(input) {
Some((_, end)) => {
let (num, rest) = input.split_at(end);
(Token::Number(num.parse().unwrap()), rest)
},
_ => {
match input.chars().next() {
Some(chr) => {
(match chr {
'+' => Token::Plus,
'-' => Token::Sub,
'*' => Token::Mul,
'/' => Token::Div,
'(' => Token::LeftParen,
')' => Token::RightParen,
_ => panic!("Unknown character!"),
}, &input[chr.len_utf8()..])
}
None => panic!("Ran out of input"),
}
}
};
res.push(token);
input = rest;
}
res
}
}
/// Transforms the tokens created by tokenize into RPN using the
/// Shunting-yard algorithm
fn shunt(tokens: Vec) -> Vec {
let mut queue = vec![];
let mut stack: Vec = vec![];
for token in tokens {
match token {
Token::Number(_) => queue.push(token),
Token::Plus | Token::Sub | Token::Mul | Token::Div => {
while let Some(o) = stack.pop() {
if token.precedence() stack.push(token),
Token::RightParen => {
let mut found_paren = false;
while let Some(op) = stack.pop() {
match op {
Token::LeftParen => {
found_paren = true;
break;
},
_ => queue.push(op),
}
}
assert!(found_paren)
},
}
}
while let Some(op) = stack.pop() {
queue.push(op);
}
queue
}
/// Takes a Vec of Tokens converted to RPN by shunt` and calculates the resultfn calculate
Code Snippets
use std::io;
extern crate regex; // 0.1.80
#[macro_use]
extern crate lazy_static;
use regex::Regex;
fn main() {
let tokenizer = Tokenizer::new();
loop {
println!("Enter input:");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let tokens = tokenizer.tokenize(&input);
let stack = shunt(tokens);
let res = calculate(stack);
println!("{}", res);
}
}
#[derive(Debug, PartialEq)]
enum Token {
Number(i64),
Plus,
Sub,
Mul,
Div,
LeftParen,
RightParen,
}
impl Token {
/// Returns the precedence of op
fn precedence(&self) -> usize {
match *self {
Token::Plus | Token::Sub => 1,
Token::Mul | Token::Div => 2,
_ => 0,
}
}
}
struct Tokenizer {
number: Regex,
}
impl Tokenizer {
fn new() -> Tokenizer {
Tokenizer {
number: Regex::new(r"^[0-9]+").expect("Unable to create the regex"),
}
}
/// Tokenizes the input string into a Vec of Tokens.
fn tokenize(&self, mut input: &str) -> Vec<Token> {
let mut res = vec![];
loop {
input = input.trim_left();
if input.is_empty() { break }
let (token, rest) = match self.number.find(input) {
Some((_, end)) => {
let (num, rest) = input.split_at(end);
(Token::Number(num.parse().unwrap()), rest)
},
_ => {
match input.chars().next() {
Some(chr) => {
(match chr {
'+' => Token::Plus,
'-' => Token::Sub,
'*' => Token::Mul,
'/' => Token::Div,
'(' => Token::LeftParen,
')' => Token::RightParen,
_ => panic!("Unknown character!"),
}, &input[chr.len_utf8()..])
}
None => panic!("Ran out of input"),
}
}
};
res.push(token);
input = rest;
}
res
}
}
/// Transforms the tokens created by `tokenize` into RPN using the
/// [Shunting-yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm)
fn shunt(tokens: Vec<Token>) -> Vec<Token> {
let mut queue = vec![];
let mut stack: Vec<Token> = vec![];
for token in tokens {
match token {
Token::Number(_) => queue.push(token),
Token::Plus | Token::Sub | Token::Mul | Token::Div => {
while let Some(o) = stack.pop() {
if token.precedence() <= o.precedence() {
queue.push(o);
} else {
let mut chars = input.chars();
match chars.next() {
Some(chr) => {
(match chr {
'+' => Token::Plus,
'-' => Token::Sub,
'*' => Token::Mul,
'/' => Token::Div,
'(' => Token::LeftParen,
')' => Token::RightParen,
_ => panic!("Unknown character!"),
}, chars.as_str())Context
StackExchange Code Review Q#140982, answer score: 2
Revisions (0)
No revisions yet.