Skip to content

Commit

Permalink
If statements (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
OchirErkhembayar committed Feb 3, 2024
1 parent 68ebe3d commit 848766c
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "qcalc"
version = "0.7.0"
version = "0.8.0"
edition = "2021"
authors = ["ochir <ochir_erkhembayar@yahoo.com>"]
description = """
Expand Down
8 changes: 8 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,4 +328,12 @@ mod tests {
input_and_evaluate(&mut app, "bar(3)(2)");
assert_output(&app, Value::Int(9));
}

#[test]
fn test_if_else() {
let mut app = new_app();

input_and_evaluate(&mut app, "if 0 then 3 % 5 else \"this is the answer\"");
assert_output(&app, Value::String("this is the answer".to_string()));
}
}
8 changes: 8 additions & 0 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,15 @@ impl Interpreter {
match expr {
Expr::Float(float) => Ok(Value::Float(*float)),
Expr::Int(int) => Ok(Value::Int(*int)),
Expr::String(string) => Ok(Value::String(string.clone())),
Expr::Bool(bool) => Ok(Value::Bool(*bool)),
Expr::If(cond, then, else_expr) => {
if self.interpret_expr(cond)?.truthy() {
self.interpret_expr(then)
} else {
self.interpret_expr(else_expr)
}
}
Expr::Binary(left, operator, right) => {
let left = self.interpret_expr(left)?;
let right = self.interpret_expr(right)?;
Expand Down
49 changes: 48 additions & 1 deletion src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,19 @@ pub enum Expr {
Fun(Vec<String>, Box<Expr>),
Var(String),
Bool(bool),
If(Box<Expr>, Box<Expr>, Box<Expr>),
String(String),
}

impl Expr {
pub fn format(&self) -> String {
match self {
Self::Float(float) => float.to_string(),
Self::If(cond, then, else_expr) => {
format!("if {} then {} else {}", cond, then, else_expr)
}
Self::Int(int) => int.to_string(),
Self::String(string) => format!("\"{}\"", string.clone()),
Self::Unary(expr, operator) => format!("{}{}", operator, expr.format()),
Self::Grouping(expr) => format!("({})", expr.format()),
Self::Var(var) => var.to_string(),
Expand Down Expand Up @@ -264,7 +270,7 @@ impl<'a> Parser<'a> {
fn expression(&mut self) -> Result<Expr, ParseErr> {
match self.peek() {
Token::Pipe => self.callable(),
_ => self.or(),
_ => self.if_expr(),
}
}

Expand Down Expand Up @@ -296,6 +302,24 @@ impl<'a> Parser<'a> {
Ok(Expr::Fun(parameters, expr))
}

fn if_expr(&mut self) -> Result<Expr, ParseErr> {
if *self.peek() == Token::If {
self.advance();
let cond = Box::new(self.expression()?);
eprintln!("Cond: {:?}", cond);
self.consume(Token::Then, "Expected then after condition")?;
let then = Box::new(self.expression()?);
eprintln!("Then: {:?}", then);
println!("Next: {:?}", self.tokenizer.peek());
self.consume(Token::Else, "Expected else after then body")?;
let else_expr = Box::new(self.expression()?);
eprintln!("Else: {:?}", else_expr);
Ok(Expr::If(cond, then, else_expr))
} else {
self.or()
}
}

fn or(&mut self) -> Result<Expr, ParseErr> {
let mut expr = self.and()?;
while *self.peek() == Token::Or {
Expand Down Expand Up @@ -436,6 +460,11 @@ impl<'a> Parser<'a> {
self.advance();
res
}
Token::String(string) => {
let res = Ok(Expr::String(string.clone()));
self.advance();
res
}
Token::True => {
self.advance();
Ok(Expr::Bool(true))
Expand Down Expand Up @@ -715,4 +744,22 @@ mod tests {
let current = tokenizer.next().unwrap();
assert_eq!(Parser::new(tokenizer, current).parse(), Ok(expected));
}

#[test]
fn test_if() {
let expected = Stmt::Expr(Expr::If(
Box::new(Expr::Bool(true)),
Box::new(Expr::Int(1)),
Box::new(Expr::String("this is the answer".to_string())),
));

let mut tokenizer = Tokenizer::new(
"if true then 1 else \"this is the answer\""
.chars()
.peekable(),
)
.peekable();
let current = tokenizer.next().unwrap();
assert_eq!(Parser::new(tokenizer, current).parse(), Ok(expected));
}
}
69 changes: 63 additions & 6 deletions src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const LET: &str = "let";
const UNDEF: &str = "undef";
const TRUE: &str = "true";
const FALSE: &str = "false";
const IF: &str = "if";
const THEN: &str = "then";
const ELSE: &str = "else";

#[derive(PartialEq, Debug, Clone)]
pub enum Token {
Expand Down Expand Up @@ -41,13 +44,18 @@ pub enum Token {
Lte,
Or,
And,
If,
Then,
Else,
String(String),
}

impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Token::Float(float) => inner_write(float, f),
Token::Int(int) => inner_write(int, f),
Token::String(string) => inner_write(format!("\"{}\"", string), f),
Token::Undef => inner_write(UNDEF, f),
Token::Comma => inner_write(',', f),
Token::Ident(ident) => inner_write(ident, f),
Expand All @@ -67,8 +75,8 @@ impl std::fmt::Display for Token {
Token::RParen => inner_write(')', f),
Token::Shr => inner_write(">>", f),
Token::Shl => inner_write("<<", f),
Token::True => inner_write("true", f),
Token::False => inner_write("false", f),
Token::True => inner_write(TRUE, f),
Token::False => inner_write(FALSE, f),
Token::Eq => inner_write("==", f),
Token::Ne => inner_write("!=", f),
Token::Gt => inner_write(">", f),
Expand All @@ -77,6 +85,9 @@ impl std::fmt::Display for Token {
Token::Lte => inner_write("<=", f),
Token::Or => inner_write("||", f),
Token::And => inner_write("&&", f),
Token::If => inner_write(IF, f),
Token::Then => inner_write(THEN, f),
Token::Else => inner_write(ELSE, f),
}
}
}
Expand Down Expand Up @@ -217,21 +228,45 @@ impl<'a> Iterator for Tokenizer<'a> {
}
}
}
'"' => {
let mut string = String::new();
while self.input.peek().is_some_and(|c| *c != '"') {
let next = self.input.next().unwrap();
if next == '\\' {
if let Some(char) = self.input.next() {
string.push('\\');
string.push(char);
} else {
return None;
}
} else {
string.push(next);
}
}
if let Some('"') = self.input.next() {
Token::String(string)
} else {
return None;
}
}
'A'..='Z' | 'a'..='z' => {
let mut func = next.to_string();
let mut ident = next.to_string();
while self
.input
.peek()
.is_some_and(|c| c.is_ascii_alphanumeric() || *c == '_')
{
func.push(self.input.next().unwrap());
ident.push(self.input.next().unwrap());
}
match func.as_str() {
match ident.as_str() {
LET => Token::Let,
UNDEF => Token::Undef,
TRUE => Token::True,
FALSE => Token::False,
_ => Token::Ident(func),
IF => Token::If,
THEN => Token::Then,
ELSE => Token::Else,
_ => Token::Ident(ident),
}
}
_ => return None, // Unknown chars just end the parsing. Not sure if good or
Expand Down Expand Up @@ -320,4 +355,26 @@ mod tests {
Some(Token::Ident("foo_bar1337".to_string()))
);
}

#[test]
fn test_if_else() {
let str = "if 1 == 3 then 3 % 3 else \"lol\"".chars().peekable();
println!("str: {:?}", str);
let tokens = Tokenizer::new(str).collect::<Vec<_>>();
assert_eq!(
tokens,
vec![
Token::If,
Token::Int(1),
Token::Eq,
Token::Int(3),
Token::Then,
Token::Int(3),
Token::Mod,
Token::Int(3),
Token::Else,
Token::String("lol".to_string()),
]
);
}
}

0 comments on commit 848766c

Please sign in to comment.