ops, negative number parsing

This commit is contained in:
me 2025-12-20 12:52:58 +02:00
parent 2372f0d254
commit 8e9ac2262a
8 changed files with 451 additions and 30 deletions

View file

@ -39,3 +39,11 @@ pub fn define_expr(name: &str, expr: Expr) -> Definition {
pub fn func(args: Vec<Arg>, 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),
}
}

View file

@ -5,6 +5,7 @@ use std::collections::BTreeMap;
/// An expression.
#[derive(PartialEq, PartialOrd, Debug, Clone)]
pub enum Expr {
Not(Box<Expr>),
Op {
lhs: Box<Expr>,
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.

View file

@ -172,6 +172,10 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result<ast:
Backtrace::capture(),
)),
},
ast::Expr::Not(e) => 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<ast:
}
v => 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())),

View file

@ -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}"),
}
}
}

View file

@ -22,6 +22,25 @@ fn parse_definitions(tokens: &mut Tokens) -> ParseResult<Vec<ast::Definition>> {
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<ast::Expr> {
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<ast::Expr> {
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<ast::Expr> {
fn parse_simple_expr(tokens: &mut Tokens) -> ParseResult<ast::Expr> {
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);

View file

@ -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::<u16>().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::<i32>().unwrap();
LocatedToken {
start,
end: scanner.cursor(),
token: Token::Number(i),
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -0,0 +1,11 @@
---
source: src/parser/parser.rs
expression: result
---
Ok(
Value(
Int(
-108,
),
),
)

View file

@ -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<LocatedToken>);
@ -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,
})
}