diff --git a/src/ast/helpers.rs b/src/ast/helpers.rs index 03da907..4285ca3 100644 --- a/src/ast/helpers.rs +++ b/src/ast/helpers.rs @@ -39,3 +39,11 @@ pub fn define_expr(name: &str, expr: Expr) -> Definition { pub fn func(args: Vec, body: Expr) -> Expr { Expr::Func(Box::new(Fn { args, body })) } + +pub fn op(op: Op, a: Expr, b: Expr) -> Expr { + Expr::Op { + op, + lhs: Box::new(a), + rhs: Box::new(b), + } +} diff --git a/src/ast/types.rs b/src/ast/types.rs index c577e47..777e5e8 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; /// An expression. #[derive(PartialEq, PartialOrd, Debug, Clone)] pub enum Expr { + Not(Box), Op { lhs: Box, op: Op, @@ -41,7 +42,36 @@ pub enum Expr { #[derive(PartialEq, PartialOrd, Debug, Clone)] pub enum Op { Assign, - // Equals, + Compare(Cmp), + Calc(Calc), + Bool(BoolOp), +} + +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub enum BoolOp { + And, + Or, +} + +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub enum Cmp { + Eq, + NotEq, + Gt, + Lt, + Gte, + Lte, +} + +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub enum Calc { + Add, + Sub, + Div, + Mul, + Mod, + BinAnd, + BinOr, } /// A statement. diff --git a/src/interpret/interpret.rs b/src/interpret/interpret.rs index d715fd2..5d09773 100644 --- a/src/interpret/interpret.rs +++ b/src/interpret/interpret.rs @@ -172,6 +172,10 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result match eval_expr(&expr_env, state, e)? { + ast::Value::Boolean(b) => Ok(ast::Value::Boolean(!b)), + v => Err(Error::NotABoolean(v, Backtrace::capture())), + }, ast::Expr::Value(v) => match v { ast::Value::Closure { expr, env } => { let closure_env: Env = state.envs.get(env)?.clone(); @@ -189,13 +193,124 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result Err(Error::NotAReference(v.clone(), Backtrace::capture())), }, - /* - _ => { - let _lhs = eval_expr(expr_env, state, lhs)?; - let _rhs = eval_expr(expr_env, state, rhs)?; - todo!() + ast::Op::Calc(cop) => { + let lhs = eval_expr(expr_env, state, lhs)?; + let rhs = eval_expr(expr_env, state, rhs)?; + + match (lhs, rhs) { + (ast::Value::Int(a), ast::Value::Int(b)) => { + let cop = match cop { + ast::Calc::Add => |a, b| a + b, + ast::Calc::Sub => |a, b| a - b, + ast::Calc::Mul => |a, b| a * b, + ast::Calc::Div => |a, b| a / b, + ast::Calc::Mod => |a, b| a % b, + ast::Calc::BinAnd => |a, b| a & b, + ast::Calc::BinOr => |a, b| a | b, + }; + Ok(ast::Value::Int(cop(a, b))) + } + (ast::Value::Float(a), ast::Value::Int(b)) => { + let cop = match cop { + ast::Calc::Add => |a, b| a + b, + ast::Calc::Sub => |a, b| a - b, + ast::Calc::Mul => |a, b| a * b, + ast::Calc::Div => |a, b| a / b, + ast::Calc::Mod => |a, b| a % b, + _ => todo!(), + }; + Ok(ast::Value::Float(cop(a, b as f32))) + } + (ast::Value::Int(a), ast::Value::Float(b)) => { + let cop = match cop { + ast::Calc::Add => |a, b| a + b, + ast::Calc::Sub => |a, b| a - b, + ast::Calc::Mul => |a, b| a * b, + ast::Calc::Div => |a, b| a / b, + ast::Calc::Mod => |a, b| a % b, + _ => todo!(), + }; + Ok(ast::Value::Float(cop(a as f32, b))) + } + (ast::Value::Float(a), ast::Value::Float(b)) => { + let cop = match cop { + ast::Calc::Add => |a, b| a + b, + ast::Calc::Sub => |a, b| a - b, + ast::Calc::Mul => |a, b| a * b, + ast::Calc::Div => |a, b| a / b, + ast::Calc::Mod => |a, b| a % b, + _ => todo!(), + }; + Ok(ast::Value::Float(cop(a, b))) + } + (lhs, rhs) => Err(Error::OpError(lhs, op.clone(), rhs, Backtrace::capture())), + } + } + ast::Op::Compare(cop) => { + let lhs = eval_expr(expr_env, state, lhs)?; + let rhs = eval_expr(expr_env, state, rhs)?; + match (lhs, rhs) { + (ast::Value::Int(a), ast::Value::Int(b)) => { + let cop = match cop { + ast::Cmp::Eq => |a, b| a == b, + ast::Cmp::NotEq => |a, b| a != b, + ast::Cmp::Gt => |a, b| a > b, + ast::Cmp::Gte => |a, b| a >= b, + ast::Cmp::Lt => |a, b| a < b, + ast::Cmp::Lte => |a, b| a <= b, + }; + Ok(ast::Value::Boolean(cop(a, b))) + } + (ast::Value::Float(a), ast::Value::Float(b)) => { + let cop = match cop { + ast::Cmp::Eq => |a, b| a == b, + ast::Cmp::NotEq => |a, b| a != b, + ast::Cmp::Gt => |a, b| a > b, + ast::Cmp::Gte => |a, b| a >= b, + ast::Cmp::Lt => |a, b| a < b, + ast::Cmp::Lte => |a, b| a <= b, + }; + Ok(ast::Value::Boolean(cop(a, b))) + } + (ast::Value::Boolean(a), ast::Value::Boolean(b)) => { + let cop = match cop { + ast::Cmp::Eq => |a, b| a == b, + ast::Cmp::NotEq => |a, b| a != b, + ast::Cmp::Gt => |a, b| a > b, + ast::Cmp::Gte => |a, b| a >= b, + ast::Cmp::Lt => |a, b| a < b, + ast::Cmp::Lte => |a, b| a <= b, + }; + Ok(ast::Value::Boolean(cop(a, b))) + } + (ast::Value::String(a), ast::Value::String(b)) => { + let cop = match cop { + ast::Cmp::Eq => |a, b| a == b, + ast::Cmp::NotEq => |a, b| a != b, + ast::Cmp::Gt => |a, b| a > b, + ast::Cmp::Gte => |a, b| a >= b, + ast::Cmp::Lt => |a, b| a < b, + ast::Cmp::Lte => |a, b| a <= b, + }; + Ok(ast::Value::Boolean(cop(a, b))) + } + (lhs, rhs) => Err(Error::OpError(lhs, op.clone(), rhs, Backtrace::capture())), + } + } + ast::Op::Bool(bop) => { + let lhs = eval_expr(expr_env, state, lhs)?; + let rhs = eval_expr(expr_env, state, rhs)?; + match (lhs, rhs) { + (ast::Value::Boolean(a), ast::Value::Boolean(b)) => { + let bop = match bop { + ast::BoolOp::And => |a, b| a && b, + ast::BoolOp::Or => |a, b| a || b, + }; + Ok(ast::Value::Boolean(bop(a, b))) + } + (lhs, rhs) => Err(Error::OpError(lhs, op.clone(), rhs, Backtrace::capture())), + } } - */ }, ast::Expr::Record(record) => { let mut map = BTreeMap::new(); @@ -316,6 +431,55 @@ mod tests { assert_eq!(result, Ok(0.into())); } #[test] + fn main_not() { + let program = vec![helpers::define_expr( + "main", + helpers::func(vec![], ast::Expr::Not(Box::new(false.into()))), + )] + .into(); + let result = run(program, "main".into(), vec![]); + assert_eq!(result, Ok(true.into())); + } + #[test] + fn main_or() { + let program = vec![helpers::define_expr( + "main", + helpers::func( + vec![], + helpers::op(ast::Op::Bool(ast::BoolOp::Or), false.into(), true.into()), + ), + )] + .into(); + let result = run(program, "main".into(), vec![]); + assert_eq!(result, Ok(true.into())); + } + #[test] + fn main_and() { + let program = vec![helpers::define_expr( + "main", + helpers::func( + vec![], + helpers::op(ast::Op::Bool(ast::BoolOp::And), false.into(), true.into()), + ), + )] + .into(); + let result = run(program, "main".into(), vec![]); + assert_eq!(result, Ok(false.into())); + } + #[test] + fn main_add() { + let program = vec![helpers::define_expr( + "main", + helpers::func( + vec![], + helpers::op(ast::Op::Calc(ast::Calc::Add), 7.into(), 8.into()), + ), + )] + .into(); + let result = run(program, "main".into(), vec![]); + assert_eq!(result, Ok(15.into())); + } + #[test] fn var_lookup() { let program = vec![ helpers::define_expr("main", helpers::func(vec![], "lit".into())), diff --git a/src/interpret/types.rs b/src/interpret/types.rs index 57e52c2..3a6c629 100644 --- a/src/interpret/types.rs +++ b/src/interpret/types.rs @@ -17,6 +17,7 @@ pub enum Error { NotARecord(ast::Value, Backtrace), NotAReference(ast::Value, Backtrace), NotABoolean(ast::Value, Backtrace), + OpError(ast::Value, ast::Op, ast::Value, Backtrace), ArgumentsMismatch(Backtrace), } impl PartialEq for Error { @@ -60,6 +61,7 @@ impl std::fmt::Display for Error { Error::NotAReference(a, b) => write!(f, "Not a reference {a:?}\n{b}"), Error::NotABoolean(a, b) => write!(f, "Not a boolean {a:?}\n{b}"), Error::ArgumentsMismatch(a) => write!(f, "Arguments mismatch\n{a}"), + Error::OpError(a, b, c, d) => write!(f, "Op Error {a:?} {b:?} {c:?}\n{d}"), } } } diff --git a/src/parser/parser.rs b/src/parser/parser.rs index f022d06..4a6f722 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -22,6 +22,25 @@ fn parse_definitions(tokens: &mut Tokens) -> ParseResult> { tokens.many(parse_definition) } +fn parse_op(tokens: &mut Tokens) -> ParseResult<(ast::Expr, Op)> { + if let Some(LocatedToken { + token: Token::Op(op), + .. + }) = tokens.next_if(&Token::Op(Op::Add)) + { + if let Some(expr) = parse_expr(tokens)? { + Ok(Some((expr, op))) + } else { + Err(Error::UnexpectedTokenForParser( + "parse_op".into(), + tokens.next(), + )) + } + } else { + Ok(None) + } +} + fn parse_set(tokens: &mut Tokens) -> ParseResult { if let Some(_eq) = tokens.next_if(&Token::Equals) { if let Some(expr) = parse_expr(tokens)? { @@ -97,6 +116,29 @@ fn parse_expr(tokens: &mut Tokens) -> ParseResult { func: Box::new(expr1), args, })) + } else if let Some((expr2, op)) = parse_op(tokens)? { + let op = match op { + Op::Eq => ast::Op::Compare(ast::Cmp::Eq), + Op::NotEq => ast::Op::Compare(ast::Cmp::NotEq), + Op::Gt => ast::Op::Compare(ast::Cmp::Gt), + Op::Gte => ast::Op::Compare(ast::Cmp::Gte), + Op::Lt => ast::Op::Compare(ast::Cmp::Lt), + Op::Lte => ast::Op::Compare(ast::Cmp::Lte), + Op::Add => ast::Op::Calc(ast::Calc::Add), + Op::Sub => ast::Op::Calc(ast::Calc::Sub), + Op::Mul => ast::Op::Calc(ast::Calc::Mul), + Op::Div => ast::Op::Calc(ast::Calc::Div), + Op::Mod => ast::Op::Calc(ast::Calc::Mod), + Op::BinAnd => ast::Op::Calc(ast::Calc::BinAnd), + Op::BinOr => ast::Op::Calc(ast::Calc::BinOr), + Op::And => ast::Op::Bool(ast::BoolOp::And), + Op::Or => ast::Op::Bool(ast::BoolOp::Or), + }; + Ok(Some(ast::Expr::Op { + op, + lhs: Box::new(expr1.clone()), + rhs: Box::new(expr2), + })) } else { Ok(parse_set(tokens)? .map(|expr2| ast::Expr::Op { @@ -113,6 +155,13 @@ fn parse_expr(tokens: &mut Tokens) -> ParseResult { fn parse_simple_expr(tokens: &mut Tokens) -> ParseResult { tokens.one_of(vec![ + |t| { + if let Some(_) = t.next_if(&Token::Not) { + Ok(parse_expr(t)?.map(|expr| ast::Expr::Not(Box::new(expr)))) + } else { + Ok(None) + } + }, parse_number, parse_identifier, parse_string, @@ -334,6 +383,12 @@ mod tests { insta::assert_debug_snapshot!(result); } #[test] + fn negative_number() { + let program = "-108".to_string(); + let result = parse(&mut scan(program), parse_expr); + insta::assert_debug_snapshot!(result); + } + #[test] fn paren_identifier() { let program = "(hello)".to_string(); let result = parse(&mut scan(program), parse_expr); diff --git a/src/parser/scanner.rs b/src/parser/scanner.rs index 86421ec..cac6638 100644 --- a/src/parser/scanner.rs +++ b/src/parser/scanner.rs @@ -93,11 +93,135 @@ pub fn scan(source: String) -> Tokens { end: scanner.cursor(), token: Token::CloseBracket, }), - '=' => tokens.push(LocatedToken { + '+' => tokens.push(LocatedToken { start, end: scanner.cursor(), - token: Token::Equals, + token: Token::Op(Op::Add), }), + '*' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::Op(Op::Mul), + }), + '/' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::Op(Op::Div), + }), + '%' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::Op(Op::Mod), + }), + '-' => { + let c = c.clone(); + let is_number = scanner.peek().map(|ch| ch.is_numeric()) == Some(true); + if is_number { + tokens.push(lex_number(&mut scanner, start, &c)); + } else { + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::Op(Op::Sub), + }); + } + } + '<' => { + let token = if let Some(ch) = scanner.peek() + && *ch == '=' + { + scanner.pop(); + Token::Op(Op::Lte) + } else { + Token::Op(Op::Lt) + }; + + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token, + }) + } + '>' => { + let token = if let Some(ch) = scanner.peek() + && *ch == '=' + { + scanner.pop(); + Token::Op(Op::Gte) + } else { + Token::Op(Op::Gt) + }; + + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token, + }) + } + '=' => { + let token = if let Some(ch) = scanner.peek() + && *ch == '=' + { + scanner.pop(); + Token::Op(Op::Eq) + } else { + Token::Equals + }; + + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token, + }) + } + '|' => { + let token = if let Some(ch) = scanner.peek() + && *ch == '|' + { + scanner.pop(); + Token::Op(Op::Or) + } else { + Token::Op(Op::BinOr) + }; + + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token, + }) + } + '&' => { + let token = if let Some(ch) = scanner.peek() + && *ch == '&' + { + scanner.pop(); + Token::Op(Op::And) + } else { + Token::Op(Op::BinAnd) + }; + + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token, + }) + } + '!' => { + let token = if let Some(ch) = scanner.peek() + && *ch == '=' + { + scanner.pop(); + Token::Op(Op::NotEq) + } else { + Token::Not + }; + + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token, + }) + } // labels '.' => { let mut str = "".to_string(); @@ -152,25 +276,8 @@ pub fn scan(source: String) -> Tokens { _ if c.is_whitespace() => {} // numbers _ if c.is_numeric() => { - let mut str = c.to_string(); - loop { - if let Some(ch) = scanner.peek() - && !ch.is_numeric() - { - break; - } - if let Some(ch) = scanner.pop() { - str.push(*ch); - } else { - break; - } - } - let i = str.parse::().unwrap(); - tokens.push(LocatedToken { - start, - end: scanner.cursor(), - token: Token::Number(i), - }); + let c = c.clone(); + tokens.push(lex_number(&mut scanner, start, &c)); } // identifiers and keywords _ if c.is_alphabetic() || *c == '_' => { @@ -216,6 +323,28 @@ pub fn scan(source: String) -> Tokens { tokens.into() } +fn lex_number(scanner: &mut Scanner, start: Pos, c: &char) -> LocatedToken { + let mut str = c.to_string(); + loop { + if let Some(ch) = scanner.peek() + && !ch.is_numeric() + { + break; + } + if let Some(ch) = scanner.pop() { + str.push(*ch); + } else { + break; + } + } + let i = str.parse::().unwrap(); + LocatedToken { + start, + end: scanner.cursor(), + token: Token::Number(i), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/parser/snapshots/ayin__parser__parser__tests__negative_number.snap b/src/parser/snapshots/ayin__parser__parser__tests__negative_number.snap new file mode 100644 index 0000000..9cc793f --- /dev/null +++ b/src/parser/snapshots/ayin__parser__parser__tests__negative_number.snap @@ -0,0 +1,11 @@ +--- +source: src/parser/parser.rs +expression: result +--- +Ok( + Value( + Int( + -108, + ), + ), +) diff --git a/src/parser/types.rs b/src/parser/types.rs index 4bf18ea..dfc6823 100644 --- a/src/parser/types.rs +++ b/src/parser/types.rs @@ -22,7 +22,6 @@ pub enum Token { Fn, Mut, Return, - Equals, OpenParen, CloseParen, OpenBracket, @@ -34,12 +33,34 @@ pub enum Token { Colon, True, False, - Number(u16), + Equals, + Not, + Op(Op), + Number(i32), String(String), Identifier(String), Label(String), } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Op { + Eq, + NotEq, + Gt, + Lt, + Gte, + Lte, + Add, + Sub, + Div, + Mul, + Mod, + And, + Or, + BinAnd, + BinOr, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Tokens(pub Vec); @@ -65,6 +86,7 @@ impl Tokens { (Token::Number(_), Token::Number(_)) => true, (Token::String(_), Token::String(_)) => true, (Token::Label(_), Token::Label(_)) => true, + (Token::Op(_), Token::Op(_)) => true, _ => false, }) }