+= -= *= /= % ** float .lf

This commit is contained in:
minish 2025-07-21 01:48:25 -04:00
parent 56a07748cd
commit 12cd2296d5
Signed by: min
SSH Key Fingerprint: SHA256:h4k7JNrfe1dzv1WE3oGVeAY9DPSZXIu3/j89+6DtHWE
5 changed files with 100 additions and 36 deletions

View File

@ -3,4 +3,9 @@ name = "leaf"
version = "0.1.0"
edition = "2024"
[profile.release]
strip = true
lto = true
codegen-units = 1
[dependencies]

View File

@ -1,4 +1,8 @@
use std::{fmt, iter::Peekable, num::ParseIntError};
use std::{
fmt,
iter::Peekable,
num::{ParseFloatError, ParseIntError},
};
use crate::kinds;
@ -14,6 +18,7 @@ impl fmt::Display for Ident {
pub enum Literal {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Nil,
Ident(Ident),
@ -23,6 +28,7 @@ impl fmt::Display for Literal {
match self {
Literal::String(s) => write!(f, "\"{s}\""),
Literal::Integer(n) => write!(f, "{n}"),
Literal::Float(n) => write!(f, "{n}"),
Literal::Boolean(b) => write!(f, "{b}"),
Literal::Ident(id) => write!(f, "{id}"),
Literal::Nil => write!(f, "nil"),
@ -39,7 +45,11 @@ kinds!(
Star,
Slash,
Percent,
Caret,
StarStar,
PlusEquals,
MinusEquals,
StarEquals,
SlashEquals,
CurlyOpen,
CurlyClose,
ParenOpen,
@ -65,11 +75,12 @@ kinds!(
pub enum Precedence {
Min,
Assign,
WithAssign,
Logical,
Equality,
Relational,
AddSub,
MulDiv,
MulDivMod,
Pow,
Prefix,
}
@ -96,9 +107,14 @@ impl Token {
| Token::GreaterThanOrEqualTo => (Precedence::Relational, Associativity::Left),
Token::And | Token::Or => (Precedence::Logical, Associativity::Left),
Token::Plus | Token::Minus => (Precedence::AddSub, Associativity::Left),
Token::Star | Token::Slash => (Precedence::MulDiv, Associativity::Left),
Token::Caret => (Precedence::Pow, Associativity::Right),
Token::Star | Token::Slash | Token::Percent => {
(Precedence::MulDivMod, Associativity::Left)
}
Token::StarStar => (Precedence::Pow, Associativity::Right),
Token::Equals => (Precedence::Assign, Associativity::Right),
Token::PlusEquals | Token::MinusEquals | Token::StarEquals | Token::SlashEquals => {
(Precedence::WithAssign, Associativity::Right)
}
_ => return None,
})
}
@ -107,6 +123,7 @@ impl Token {
#[derive(Debug)]
pub enum LexError {
InvalidInteger(ParseIntError),
InvalidFloat(ParseFloatError),
InvalidEscape(char),
UnexpectedCharacter(char),
UnexpectedEnd,
@ -115,6 +132,7 @@ impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidInteger(err) => write!(f, "invalid integer: {err}"),
Self::InvalidFloat(err) => write!(f, "invalid float: {err}"),
Self::UnexpectedEnd => write!(f, "unexpected end of source"),
Self::UnexpectedCharacter(c) => write!(f, "unexpected char '{c}'"),
Self::InvalidEscape(c) => write!(f, "\"\\{c}\" is not a valid string escape"),
@ -126,6 +144,11 @@ impl From<ParseIntError> for LexError {
Self::InvalidInteger(err)
}
}
impl From<ParseFloatError> for LexError {
fn from(err: ParseFloatError) -> Self {
Self::InvalidFloat(err)
}
}
pub type Result<T> = std::result::Result<T, LexError>;
@ -210,21 +233,27 @@ where
}
}
fn lex_integer(&mut self) -> Result<Token> {
fn lex_number(&mut self) -> Result<Token> {
let mut n_str = String::new();
// we don't lex negatives. the impl for that is
// a negation of a positive number at runtime.
// maybe that's kind of stupid though, lol
while let Some('0'..='9') = self.peek() {
let mut is_float = false;
while let Some('0'..='9' | '.') = self.peek() {
if self.peek() == Some('.') {
is_float = true;
}
n_str.push(self.next_unwrap());
}
// we can only read digits 0 to 9 so this should not fail
// .. unless we overflow
let n = n_str.parse()?;
let lit = if is_float {
Literal::Float(n_str.parse()?)
} else {
Literal::Integer(n_str.parse()?)
};
Ok(Token::Literal(Literal::Integer(n)))
Ok(Token::Literal(lit))
}
fn lex_string(&mut self) -> Result<Token> {
@ -264,23 +293,47 @@ where
')' => self.eat_to(Token::ParenClose),
// + add
'+' => self.eat_to(Token::Plus),
// or += add eq
'+' => match self.eat_peek() {
Some('=') => self.eat_to(Token::PlusEquals),
_ => t(Token::Plus),
},
// - subtract
'-' => self.eat_to(Token::Minus),
// or -= sub eq
'-' => match self.eat_peek() {
Some('=') => self.eat_to(Token::MinusEquals),
_ => t(Token::Minus),
},
// * multiply
'*' => self.eat_to(Token::Star),
// or *= mult eq
// or ** pow
'*' => match self.eat_peek() {
Some('=') => self.eat_to(Token::StarEquals),
Some('*') => self.eat_to(Token::StarStar),
_ => t(Token::Star),
},
// / divide
'/' => self.eat_to(Token::Slash),
// or /= div eq
// or // comment
'/' => match self.eat_peek() {
Some('=') => self.eat_to(Token::SlashEquals),
Some('/') => {
// skip the rest of the line
// this leaves the newline btw
while !matches!(self.peek(), Some('\n') | None) {
self.eat();
}
continue;
}
_ => t(Token::Slash),
},
// % modulo
'%' => self.eat_to(Token::Percent),
// ^ pow
'^' => self.eat_to(Token::Caret),
// , comma
',' => self.eat_to(Token::Comma),
@ -322,21 +375,11 @@ where
'a'..='z' | 'A'..='Z' | '_' => Some(Ok(self.lex_word())),
// 0-9 integer
'0'..='9' => Some(self.lex_integer()),
'0'..='9' | '.' => Some(self.lex_number()),
// " strings
'"' => Some(self.lex_string()),
// # comments
'#' => {
// skip the rest of the line
// this leaves the newline btw
while !matches!(self.peek(), Some('\n') | None) {
self.eat();
}
continue;
}
// unexpected character
c => Some(Err(LexError::UnexpectedCharacter(c))),
};

View File

@ -7,7 +7,7 @@ mod lexer;
mod parser;
fn main() {
let script = std::fs::read_to_string("./start.leaf").unwrap();
let script = std::fs::read_to_string("./start.lf").unwrap();
let lexer = Lexer::new(script.chars());
let mut parser = Parser::new(lexer.map(Result::unwrap));
let start = Instant::now();

View File

@ -10,7 +10,7 @@ pub mod util;
#[derive(Debug)]
pub enum Expr {
// Data and variables
Assignment(Box<Expr>, Box<Expr>),
Assign(Box<Expr>, Box<Expr>),
Literal(Literal),
// Non-literal datatypes
Block(Block),
@ -38,6 +38,12 @@ pub enum Expr {
Multiply(Box<Expr>, Box<Expr>),
Divide(Box<Expr>, Box<Expr>),
Exponent(Box<Expr>, Box<Expr>),
Modulo(Box<Expr>, Box<Expr>),
// Binary operations: arithmetic w/ assignment
AddAssign(Box<Expr>, Box<Expr>),
SubtractAssign(Box<Expr>, Box<Expr>),
MultiplyAssign(Box<Expr>, Box<Expr>),
DivideAssign(Box<Expr>, Box<Expr>),
}
#[derive(Debug, Default)]
@ -245,13 +251,18 @@ where
// add, subtract
Token::Plus => Expr::Add(lhs, rhs),
Token::Minus => Expr::Subtract(lhs, rhs),
Token::PlusEquals => Expr::AddAssign(lhs, rhs),
Token::MinusEquals => Expr::SubtractAssign(lhs, rhs),
// multiply, divide
Token::Star => Expr::Multiply(lhs, rhs),
Token::Slash => Expr::Divide(lhs, rhs),
// exponent
Token::Caret => Expr::Exponent(lhs, rhs),
Token::StarEquals => Expr::MultiplyAssign(lhs, rhs),
Token::SlashEquals => Expr::DivideAssign(lhs, rhs),
// exponent, modulo
Token::StarStar => Expr::Exponent(lhs, rhs),
Token::Percent => Expr::Modulo(lhs, rhs),
// assignment
Token::Equals => Expr::Assignment(lhs, rhs),
Token::Equals => Expr::Assign(lhs, rhs),
// unreachable as all tokens with precedences are covered above
_ => unreachable!(),
});

View File

@ -18,7 +18,11 @@ fn fmt_binop(left: Expr, right: Expr, op: &str, depth: usize) -> String {
fn fmt_expr(e: Expr, depth: usize) -> String {
match e {
Expr::Assignment(l, r) => fmt_binop(*l, *r, "=", depth),
Expr::Assign(l, r) => fmt_binop(*l, *r, "=", depth),
Expr::AddAssign(l, r) => fmt_binop(*l, *r, "+=", depth),
Expr::SubtractAssign(l, r) => fmt_binop(*l, *r, "-=", depth),
Expr::MultiplyAssign(l, r) => fmt_binop(*l, *r, "*=", depth),
Expr::DivideAssign(l, r) => fmt_binop(*l, *r, "/=", depth),
Expr::Literal(l) => l.to_string(),
Expr::Call(l, r) => {
let mut result = fmt_expr(*l, depth);
@ -78,6 +82,7 @@ fn fmt_expr(e: Expr, depth: usize) -> String {
Expr::Subtract(l, r) => fmt_binop(*l, *r, "-", depth),
Expr::Multiply(l, r) => fmt_binop(*l, *r, "*", depth),
Expr::Divide(l, r) => fmt_binop(*l, *r, "/", depth),
Expr::Exponent(l, r) => fmt_binop(*l, *r, "^", depth),
Expr::Exponent(l, r) => fmt_binop(*l, *r, "**", depth),
Expr::Modulo(l, r) => fmt_binop(*l, *r, "%", depth),
}
}