Created
May 5, 2021 17:57
-
-
Save iK4tsu/4a627dc57ed2bac8d0322ec3fc75d591 to your computer and use it in GitHub Desktop.
Simple arithmetic parser and interpreter
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
#[derive(Debug, Clone)] | |
enum Token { | |
PRODUCT, | |
SUM, | |
NUMBER(i64), | |
} | |
#[derive(Debug, Clone)] | |
struct Node { | |
pub children: Vec<Node>, | |
pub token: Token, | |
} | |
impl Node { | |
pub fn new(t: Token) -> Node { | |
Node { | |
children: Vec::new(), | |
token: t, | |
} | |
} | |
} | |
#[derive(Debug, Clone)] | |
enum Item { | |
Paren(char), | |
Op(char), | |
Num(i64), | |
} | |
fn lex(input: &String) -> Result<Vec<Item>, String> { | |
let mut res = Vec::new(); | |
let mut iter = input.chars().peekable(); | |
while let Some(&c) = iter.peek() { | |
match c { | |
'0' ..= '9' => { | |
use itertools::Itertools; | |
let content = iter.by_ref() | |
.take_while_ref(|c| c.is_ascii_digit()) | |
.collect::<String>(); | |
let num = content.parse::<i64>().expect(format!("Failed to parse '{}' to number!", content).as_ref()); | |
res.push(Item::Num(num)); | |
}, | |
'+' | '*' => res.push(Item::Op(iter.by_ref().take(1).next().unwrap())), | |
'(' | ')' => res.push(Item::Paren(iter.by_ref().take(1).next().unwrap())), | |
' ' => { iter.next(); }, | |
_ => return Err(format!("Unexpected char ({})!", c)) | |
} | |
} | |
Ok(res) | |
} | |
fn parse(input: &String) -> Result<Node, String> { | |
let mut items = lex(&input)?; | |
items.reverse(); | |
parse_exp(&mut items) | |
} | |
// lowest priority: + | - | |
fn parse_exp(items: &mut Vec<Item>) -> Result<Node, String> { | |
let left_node = parse_exp1(items)?; | |
let item = items.last(); | |
match item { | |
Some(&Item::Op('+')) => { | |
items.pop(); | |
let mut node = Node::new(Token::SUM); | |
node.children.push(left_node); | |
node.children.push(parse_exp(items)?); | |
Ok(node) | |
}, | |
_ => Ok(left_node) | |
} | |
} | |
// * | / | |
fn parse_exp1(items: &mut Vec<Item>) -> Result<Node, String> { | |
let left_node = parse_exp2(items)?; | |
let item = items.last(); | |
match item { | |
Some(&Item::Op('*')) => { | |
items.pop(); | |
let mut node = Node::new(Token::PRODUCT); | |
node.children.push(left_node); | |
node.children.push(parse_exp1(items)?); | |
Ok(node) | |
}, | |
_ => Ok(left_node) | |
} | |
} | |
// numbers and parens | |
fn parse_exp2(items: &mut Vec<Item>) -> Result<Node, String> { | |
let item = items.pop().ok_or(format!("Expected number or parenteses, found (None)!"))?; | |
match item { | |
Item::Num(n) => Ok(Node::new(Token::NUMBER(n))), | |
Item::Paren(_) => parse_exp(items).and_then(|node| { | |
let item = items.pop().ok_or(format!("Expected number or parenteses, found (None)!"))?; | |
match item { | |
Item::Paren(_) => Ok(node), | |
item => Err(format!("Expected matching parenteses, found ({:?})!", item)), | |
} | |
}), | |
Item::Op(item) => Err(format!("Expected number or parenteses, found ({})!", item)) | |
} | |
} | |
fn interp(node: &mut Node) -> i64 { | |
match node.token { | |
Token::NUMBER(n) => n, | |
Token::SUM => interp(&mut node.children.pop().unwrap())+interp(&mut node.children.pop().unwrap()), | |
Token::PRODUCT => interp(&mut node.children.pop().unwrap())*interp(&mut node.children.pop().unwrap()) | |
} | |
} | |
fn main() -> std::io::Result<()> { | |
println!("Write an arithmetic expression! Write 'exit' when you're done."); | |
println!("Disclamer: This is only a program for educational purposes, only sums and products are available."); | |
loop { | |
let mut buf = String::new(); | |
print!(">> "); | |
std::io::Write::flush(&mut std::io::stdout())?; | |
std::io::BufRead::read_line(&mut std::io::BufReader::new(std::io::stdin()),&mut buf)?; | |
match buf.trim_end() { | |
"exit" => return Ok(()), | |
s => match &mut parse(&s.to_owned()) { | |
Ok(n) => println!("{} = {}\n", s, interp(n)), | |
Err(e) => println!("{:?}", e), | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment