logos calculator example

This commit is contained in:
LunarAkai 2025-08-05 17:17:11 +02:00
commit 0c30f0022d
9 changed files with 414 additions and 22 deletions

18
src/ast/evaluator.rs Normal file
View file

@ -0,0 +1,18 @@
use crate::ast::Expression;
impl Expression {
pub fn eval(&self) -> isize {
match self {
Expression::Integer(n) => *n,
Expression::Negate(rhs) => -rhs.eval(),
Expression::Add(lhs, rhs) => lhs.eval() + rhs.eval(),
Expression::Substract(lhs, rhs) => lhs.eval() - rhs.eval(),
Expression::Multiply(lhs, rhs) => lhs.eval() * rhs.eval(),
Expression::Divide(lhs, rhs) => lhs.eval() / rhs.eval(),
}
}
}

12
src/ast/mod.rs Normal file
View file

@ -0,0 +1,12 @@
pub mod evaluator;
#[derive(Debug)]
pub enum Expression {
Integer(isize),
Negate(Box<Expression>),
// Binary operators,
Add(Box<Expression>, Box<Expression>),
Substract(Box<Expression>, Box<Expression>),
Multiply(Box<Expression>, Box<Expression>),
Divide(Box<Expression>, Box<Expression>),
}

View file

@ -1,16 +0,0 @@
mod lexer;
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

36
src/main.rs Normal file
View file

@ -0,0 +1,36 @@
use chumsky::Parser;
use logos::Logos;
use crate::{parser::parser, tokens::Token};
mod tokens;
mod ast;
mod parser;
fn main() {
let lexer = Token::lexer("(1 + 1) * 3");
let mut tokens = vec![];
for (token, span) in lexer.spanned() {
match token {
Ok(token) => tokens.push(token),
Err(e) => {
println!("lexer error at {:?}: {:?}", span, e);
return;
}
}
}
let ast = match parser().parse(&tokens).into_result() {
Ok(expr) => {
println!("[AST]\n{:#?}", expr);
expr
}
Err(e) => {
println!("parse error: {:#?}", e);
return;
}
};
println!("\n[result]\n{}", ast.eval());
}

56
src/parser.rs Normal file
View file

@ -0,0 +1,56 @@
use chumsky::{prelude::{just, recursive}, recursive, select, IterParser, Parser};
use crate::{ast::Expression, tokens::Token};
#[allow(clippy::let_and_return)]
/* ANCHOR: parser */
pub fn parser<'src>(
) -> impl Parser<'src, &'src [Token<'src>], Expression, chumsky::extra::Err<chumsky::error::Simple<'src, Token<'src>>>>
{
recursive(
|p|
{
let atom = {
let parenthesized = p
.clone()
.delimited_by(just(Token::ParenBegin), just(Token::ParenEnd));
let integer = select! {
Token::Integer(n) => Expression::Integer(n),
};
parenthesized.or(integer)
};
let unary = just(Token::Substract)
.repeated()
.foldr(atom, |_op, rhs| Expression::Negate(Box::new(rhs)));
let binary_1 = unary.clone().foldl(
just(Token::Multiply)
.or(just(Token::Divide))
.then(unary)
.repeated(),
|lhs, (op, rhs)| match op {
Token::Multiply => Expression::Multiply(Box::new(lhs), Box::new(rhs)),
Token::Divide => Expression::Divide(Box::new(lhs), Box::new(rhs)),
_ => unreachable!(),
},
);
let binary_2 = binary_1.clone().foldl(
just(Token::Add)
.or(just(Token::Substract))
.then(binary_1)
.repeated(),
|lhs, (op, rhs)| match op {
Token::Add => Expression::Add(Box::new(lhs), Box::new(rhs)),
Token::Substract => Expression::Substract(Box::new(lhs), Box::new(rhs)),
_ => unreachable!(),
},
);
binary_2
})
}

View file

@ -2,7 +2,7 @@ use logos::{Lexer, Logos};
#[derive(Logos, Debug, Clone, PartialEq)]
#[logos(skip r"[ \t\r\n\f]+")] // Skips whitespace
enum Token<'source> {
pub enum Token<'source> {
#[token("false", |_| false)]
#[token("true", |_| true)]
Bool(bool),
@ -18,16 +18,40 @@ enum Token<'source> {
#[token("/")]
Divide,
#[token("=")]
Equals,
#[token(":")]
Colon,
#[token("(")]
ParenBegin,
#[token(")")]
ParenEnd,
#[token("{")]
BraceBegin,
#[token("}")]
BraceEnd,
#[regex("[0-9]+", |lex| lex.slice().parse::<isize>().unwrap())]
Integer(isize),
#[regex(r"[_a-zA-Z][_0-9a-zA-Z]*")]
Ident(&'source str),
#[regex(r#""([^"\\\x00-\x1F]|\\(["\\bnfrt/]|u[a-fA-F0-9]{4}))*""#, |lex| lex.slice().to_owned())]
String(String),
}
fn float<'a>(lex: &mut Lexer<'a, Token<'a>>) -> Result<f64, ()> {
lex.slice().parse().map_err(|_| ())
#[token("class")]
#[token("fun")]
#[token("var")]
#[token("if")]
#[token("else")]
Keyword(&'source str),
}