This commit is contained in:
minish 2025-06-27 03:03:28 -04:00
commit 74095ffaf9
Signed by: min
SSH Key Fingerprint: SHA256:h4k7JNrfe1dzv1WE3oGVeAY9DPSZXIu3/j89+6DtHWE
7 changed files with 569 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "leaf"
version = "0.1.0"

6
Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "leaf"
version = "0.1.0"
edition = "2024"
[dependencies]

273
src/lexer.rs Normal file
View File

@ -0,0 +1,273 @@
use std::iter::Peekable;
#[derive(Debug)]
pub struct Ident(String);
#[derive(Debug)]
pub enum Literal {
String(String),
Integer(i64),
Boolean(bool),
Nil,
}
#[derive(Debug)]
pub enum Token {
Equals,
Add,
Multiply,
Divide,
Minus,
CurlyOpen,
CurlyClose,
ParenOpen,
ParenClose,
Comma,
Return,
Not,
EqualTo,
NotEqualTo,
And,
Or,
LessThan,
LessThanOrEqualTo,
GreaterThan,
GreaterThanOrEqualTo,
Ident(Ident),
Literal(Literal),
}
#[derive(Debug)]
pub enum LexError {
InvalidEscape(char),
UnexpectedCharacter(char),
UnexpectedEnd,
}
trait Check {
fn check(self) -> Result<char>;
}
impl Check for Option<char> {
fn check(self) -> Result<char> {
self.ok_or(LexError::UnexpectedEnd)
}
}
pub type Result<T> = std::result::Result<T, LexError>;
pub struct Lexer<I>
where
I: Iterator<Item = char>,
{
chars: Peekable<I>,
}
impl<I> Lexer<I>
where
I: Iterator<Item = char>,
{
pub fn new(chars: I) -> Self {
let chars = chars.peekable();
Self { chars }
}
fn peek(&mut self) -> Option<char> {
self.chars.peek().copied()
}
fn next(&mut self) -> Option<char> {
self.chars.next()
}
fn next_unwrap(&mut self) -> char {
match self.next() {
Some(c) => c,
None => unreachable!("called next_unwrap with nothing ahead"),
}
}
fn eat(&mut self) {
self.next();
}
fn eat_to(&mut self, tk: Token) -> Option<Result<Token>> {
self.eat();
Some(Ok(tk))
}
fn eat_peek(&mut self) -> Option<char> {
self.eat();
self.peek()
}
fn lex_whitespace(&mut self) -> Option<char> {
loop {
match self.peek()? {
' ' | '\t' | '\n' | '\r' => self.eat(),
_ => break self.peek(),
}
}
}
fn lex_word(&mut self) -> Token {
let mut word = String::new();
while let Some('a'..='z' | 'A'..='Z' | '0'..='9' | '_') = self.peek() {
word.push(self.next_unwrap());
}
match word.as_str() {
"return" => Token::Return,
"true" => Token::Literal(Literal::Boolean(true)),
"false" => Token::Literal(Literal::Boolean(false)),
"nil" => Token::Literal(Literal::Nil),
_ => Token::Ident(Ident(word)),
}
}
fn lex_integer(&mut self) -> 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() {
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().unwrap();
Token::Literal(Literal::Integer(n))
}
fn lex_string(&mut self) -> Result<Token> {
let delim = self.next_unwrap();
let mut str = String::new();
loop {
match self.peek().check()? {
'\\' => match self.eat_peek().check()? {
'n' => {
self.eat();
str.push('\n')
}
c => {
break Err(LexError::InvalidEscape(c));
}
},
c if c == delim => {
self.eat();
break Ok(Token::Literal(Literal::String(str)));
}
_ => str.push(self.next_unwrap()),
}
}
}
fn lex_comment(&mut self) -> Option<Result<Token>> {
while self.peek() != Some('\n') {
self.eat();
}
self.lex()
}
fn lex(&mut self) -> Option<Result<Token>> {
match self.lex_whitespace()? {
// { and } start/end of code block
'{' => self.eat_to(Token::CurlyOpen),
'}' => self.eat_to(Token::CurlyClose),
// ( and ) start/end of parens (idk)
'(' => self.eat_to(Token::ParenOpen),
')' => self.eat_to(Token::ParenClose),
// + add
'+' => self.eat_to(Token::Add),
// - subtract
'-' => self.eat_to(Token::Minus),
// * multiply
'*' => self.eat_to(Token::Multiply),
// / divide
'/' => self.eat_to(Token::Divide),
// , comma
',' => self.eat_to(Token::Comma),
// = equals
// or == equal to
'=' => match self.eat_peek() {
Some('=') => self.eat_to(Token::EqualTo),
_ => Some(Ok(Token::Equals)),
},
// ! not
// or != not equal to
'!' => match self.eat_peek() {
Some('=') => self.eat_to(Token::NotEqualTo),
_ => Some(Ok(Token::Not)),
},
// && and
'&' if matches!(self.eat_peek(), Some('&')) => self.eat_to(Token::And),
// || or
'|' if matches!(self.eat_peek(), Some('|')) => self.eat_to(Token::Or),
// > greater than
// or >= greater than/equal to
'>' => match self.eat_peek() {
Some('=') => self.eat_to(Token::GreaterThanOrEqualTo),
_ => Some(Ok(Token::GreaterThan)),
},
// < less than
// or <= less than/equal to
'<' => match self.eat_peek() {
Some('=') => self.eat_to(Token::LessThanOrEqualTo),
_ => Some(Ok(Token::LessThan)),
},
// a-zA-Z_ start of word
'a'..='z' | 'A'..='Z' | '_' => Some(Ok(self.lex_word())),
// 0-9 integer
'0'..='9' => Some(Ok(self.lex_integer())),
// " strings
'"' => Some(self.lex_string()),
// # comments
'#' => self.lex_comment(),
// unexpected character
c => Some(Err(LexError::UnexpectedCharacter(c))),
}
}
}
impl<T> Iterator for Lexer<T>
where
T: Iterator<Item = char>,
{
type Item = Result<Token>;
fn next(&mut self) -> Option<Self::Item> {
self.lex()
}
}

13
src/main.rs Normal file
View File

@ -0,0 +1,13 @@
use crate::{lexer::Lexer, parser::Parser};
mod lexer;
mod parser;
mod runtime;
fn main() {
let script = std::fs::read_to_string("./start.leaf").unwrap();
let lexer = Lexer::new(script.chars());
let mut parser = Parser::new(lexer.map(Result::unwrap));
let block = parser.parse_root().unwrap();
println!("{block:?}");
}

251
src/parser.rs Normal file
View File

@ -0,0 +1,251 @@
use std::iter::Peekable;
use crate::lexer::{Ident, LexError, Literal, Token};
#[derive(Debug)]
pub enum Expr {
// Data and variables
Assignment(Ident, Box<Expr>),
Literal(Literal),
Variable(Ident),
// Control flow
Call(Ident, Vec<Expr>),
Return(Box<Expr>),
// Runtime datatypes
Block(Block),
// Unary operations
Negate(Box<Expr>),
Not(Box<Expr>),
// Binary operations: logical
EqualTo(Box<Expr>, Box<Expr>),
NotEqualTo(Box<Expr>, Box<Expr>),
And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>),
// Binary operations: comparison
LessThan(Box<Expr>, Box<Expr>),
LessThanOrEqualTo(Box<Expr>, Box<Expr>),
GreaterThan(Box<Expr>, Box<Expr>),
GreaterThanOrEqualTo(Box<Expr>, Box<Expr>),
// Binary operations: arithmetic
Add(Box<Expr>, Box<Expr>),
Subtract(Box<Expr>, Box<Expr>),
Multiply(Box<Expr>, Box<Expr>),
Divide(Box<Expr>, Box<Expr>),
}
#[derive(Debug, Default)]
pub struct Block {
exprs: Vec<Expr>,
}
#[derive(Debug)]
pub enum ParseError {
UnexpectedToken(Token),
UnexpectedEnd,
LexError(LexError),
}
impl From<LexError> for ParseError {
fn from(err: LexError) -> Self {
Self::LexError(err)
}
}
pub type Result<T> = std::result::Result<T, ParseError>;
pub struct Parser<I: Iterator<Item = Token>> {
tokens: Peekable<I>,
}
impl<I> Parser<I>
where
I: Iterator<Item = Token>,
{
pub fn new(tokens: I) -> Self {
let tokens = tokens.peekable();
Self { tokens }
}
fn eat(&mut self) {
self.next_unwrap();
}
fn next_unwrap(&mut self) -> Token {
self.try_next().unwrap()
}
fn try_peek(&mut self) -> Result<&Token> {
self.tokens.peek().ok_or(ParseError::UnexpectedEnd)
}
fn try_next(&mut self) -> Result<Token> {
self.tokens
.next()
.inspect(|t| println!("next= {t:?}"))
.ok_or(ParseError::UnexpectedEnd)
}
fn parse_expr(&mut self) -> Result<Expr> {
Ok(match self.try_next()? {
// Literal
Token::Literal(lit) => match self.try_peek() {
// Binary Op: equal to (lit, expr)
Ok(Token::EqualTo) => {
self.eat();
Expr::EqualTo(Box::new(Expr::Literal(lit)), Box::new(self.parse_expr()?))
}
// Binary Op: not equal to (lit, expr)
Ok(Token::NotEqualTo) => {
self.eat();
Expr::NotEqualTo(Box::new(Expr::Literal(lit)), Box::new(self.parse_expr()?))
}
// Binary Op: less than (lit, expr)
Ok(Token::LessThan) => {
self.eat();
Expr::LessThan(Box::new(Expr::Literal(lit)), Box::new(self.parse_expr()?))
}
// Binary Op: less than or equal to (lit, expr)
Ok(Token::LessThanOrEqualTo) => {
self.eat();
Expr::LessThanOrEqualTo(
Box::new(Expr::Literal(lit)),
Box::new(self.parse_expr()?),
)
}
// Binary Op: greater than (lit, expr)
Ok(Token::GreaterThan) => {
Expr::GreaterThan(Box::new(Expr::Literal(lit)), Box::new(self.parse_expr()?))
}
// Binary Op: greater than or equal to (lit, expr)
Ok(Token::GreaterThanOrEqualTo) => {
self.eat();
Expr::GreaterThanOrEqualTo(
Box::new(Expr::Literal(lit)),
Box::new(self.parse_expr()?),
)
}
// Binary Op: and (lit, expr)
Ok(Token::And) => {
self.eat();
Expr::And(Box::new(Expr::Literal(lit)), Box::new(self.parse_expr()?))
}
// Binary Op: or (lit, expr)
Ok(Token::Or) => {
self.eat();
Expr::Or(Box::new(Expr::Literal(lit)), Box::new(self.parse_expr()?))
}
_ => Expr::Literal(lit),
},
// Unary Op: negate
Token::Minus => Expr::Negate(Box::new(self.parse_expr()?)),
// Unary Op: not
Token::Not => Expr::Not(Box::new(self.parse_expr()?)),
// Start of a block
Token::CurlyOpen => {
let mut exprs = Vec::new();
while !matches!(self.try_peek()?, Token::CurlyClose) {
exprs.push(self.parse_expr()?);
}
self.eat();
Expr::Block(Block { exprs })
}
// Return
Token::Return => Expr::Return(Box::new(self.parse_expr()?)),
Token::Ident(id) => {
match self.try_peek() {
// Assignment
Ok(Token::Equals) => {
self.eat();
let rhs = self.parse_expr()?;
Expr::Assignment(id, Box::new(rhs))
}
// Block call
Ok(Token::ParenOpen) => {
self.eat();
let mut args = Vec::new();
while !matches!(self.try_peek()?, Token::ParenClose) {
args.push(self.parse_expr()?);
// require comma for next arg if it's not the end
let tk = self.try_peek()?;
if matches!(tk, Token::Comma) {
self.eat();
} else if !matches!(tk, Token::ParenClose) {
// no comma OR closing paren.. bad...
return Err(ParseError::UnexpectedToken(self.next_unwrap()));
}
}
// Eat closing paren
self.eat();
Expr::Call(id, args)
}
// Binary Op: equal to (var, expr)
Ok(Token::EqualTo) => {
self.eat();
Expr::EqualTo(Box::new(Expr::Variable(id)), Box::new(self.parse_expr()?))
}
// Binary Op: not equal to (var, expr)
Ok(Token::NotEqualTo) => {
self.eat();
Expr::NotEqualTo(Box::new(Expr::Variable(id)), Box::new(self.parse_expr()?))
}
// Binary Op: less than (var, expr)
Ok(Token::LessThan) => {
self.eat();
Expr::LessThan(Box::new(Expr::Variable(id)), Box::new(self.parse_expr()?))
}
// Binary Op: less than or equal to (var, expr)
Ok(Token::LessThanOrEqualTo) => {
self.eat();
Expr::LessThanOrEqualTo(
Box::new(Expr::Variable(id)),
Box::new(self.parse_expr()?),
)
}
// Binary Op: greater than (var, expr)
Ok(Token::GreaterThan) => {
self.eat();
Expr::GreaterThan(
Box::new(Expr::Variable(id)),
Box::new(self.parse_expr()?),
)
}
// Binary Op: greater than or equal to (var, expr)
Ok(Token::GreaterThanOrEqualTo) => {
self.eat();
Expr::GreaterThanOrEqualTo(
Box::new(Expr::Variable(id)),
Box::new(self.parse_expr()?),
)
}
// Binary Op: and (var, expr)
Ok(Token::And) => {
self.eat();
Expr::And(Box::new(Expr::Variable(id)), Box::new(self.parse_expr()?))
}
// Binary Op: or (var, expr)
Ok(Token::Or) => {
self.eat();
Expr::Or(Box::new(Expr::Variable(id)), Box::new(self.parse_expr()?))
}
_ => Expr::Variable(id),
}
}
t => return Err(ParseError::UnexpectedToken(t)),
})
}
pub fn parse_root(&mut self) -> Result<Block> {
let mut exprs = Vec::new();
while self.try_peek().is_ok() {
exprs.push(self.parse_expr()?);
}
Ok(Block { exprs })
}
}

18
src/runtime.rs Normal file
View File

@ -0,0 +1,18 @@
use crate::parser::{Block, Expr};
enum Value {
String(String),
Integer(i64),
Boolean(bool),
Nil,
Block(Block),
}
fn eval(e: Expr) -> Value {
todo!()
}
/// Evaluates all expressions of a block.
pub fn exec(b: Block) -> Value {
todo!()
}