Skip to content

Instantly share code, notes, and snippets.

@iK4tsu
Created May 5, 2021 17:57
Show Gist options
  • Save iK4tsu/4a627dc57ed2bac8d0322ec3fc75d591 to your computer and use it in GitHub Desktop.
Save iK4tsu/4a627dc57ed2bac8d0322ec3fc75d591 to your computer and use it in GitHub Desktop.
Simple arithmetic parser and interpreter
#[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