feat: finish chapter 10 (functions)

this was a big chapter, but the most fun i had!

I plan to add some native functions in the future and do the challenges
of course
This commit is contained in:
Seymur Bagirov 2025-02-06 20:52:09 +04:00
parent 04e8d5b8cb
commit 4bb2ce1af4
9 changed files with 388 additions and 44 deletions

View File

@ -12,6 +12,11 @@ pub enum Expr {
op: Token,
right: Box<Expr>,
},
Call {
callee: Box<Expr>,
paren: Token,
args: Vec<Expr>,
},
Grouping {
expression: Box<Expr>,
},
@ -36,6 +41,7 @@ pub enum Expr {
},
}
#[derive(Debug, Clone)]
pub enum Stmt {
Block {
statements: Vec<Stmt>,
@ -44,6 +50,11 @@ pub enum Stmt {
Expression {
expression: Expr,
},
Function {
name: Token,
params: Vec<Token>,
body: Vec<Stmt>,
},
If {
condition: Expr,
then_branch: Box<Stmt>,
@ -52,6 +63,10 @@ pub enum Stmt {
Print {
expression: Expr,
},
Return {
keyword: Token,
value: Option<Expr>,
},
Var {
name: Token,
initializer: Option<Expr>,

105
src/callable.rs Normal file
View File

@ -0,0 +1,105 @@
use std::{cell::RefCell, fmt::Display, rc::Rc};
use crate::{
ast::Stmt,
environment::Environment,
interpreter::{execute_block, InterpreterEnvironment, InterpreterSignal, RuntimeError},
token::{LiteralType, Token},
};
pub trait CallableTrait {
fn arity(&self) -> u8;
fn call(
&self,
args: &[LiteralType],
env: &InterpreterEnvironment,
) -> Result<LiteralType, InterpreterSignal>;
}
#[derive(Debug, Clone)]
pub enum Callable {
Function {
name: Box<Token>,
params: Vec<Token>,
body: Vec<Stmt>,
closure: Rc<RefCell<Environment>>,
},
NativeFunction(NativeFunction),
}
impl CallableTrait for Callable {
fn arity(&self) -> u8 {
match self {
Callable::Function { params, .. } => params.len() as u8,
Callable::NativeFunction(native_function) => native_function.arity,
}
}
fn call(
&self,
args: &[LiteralType],
env: &InterpreterEnvironment,
) -> Result<LiteralType, InterpreterSignal> {
match self {
Callable::Function {
name: _,
params,
body,
closure,
} => {
let environment = Rc::new(RefCell::new(Environment::with_enclosing(closure)));
for (param, arg) in params.iter().zip(args) {
environment
.borrow_mut()
.define(&param.lexeme, Some(arg.clone()));
}
let environment = InterpreterEnvironment {
globals: Rc::clone(&env.globals),
environment,
};
match execute_block(body, &environment) {
Err(InterpreterSignal::Return(v)) => Ok(v),
v => v.map(|_| LiteralType::Nil),
}
}
Callable::NativeFunction(native_function) => (native_function.call_impl)(args),
}
}
}
#[derive(Debug, Clone)]
pub struct NativeFunction {
name: String,
arity: u8,
call_impl: fn(&[LiteralType]) -> Result<LiteralType, InterpreterSignal>,
}
impl NativeFunction {
pub fn new(
name: String,
arity: u8,
call_impl: fn(&[LiteralType]) -> Result<LiteralType, InterpreterSignal>,
) -> Self {
Self {
name,
arity,
call_impl,
}
}
}
impl Display for Callable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Callable::Function { name, .. } => {
write!(f, "{}", name.lexeme)
}
Callable::NativeFunction(native_function) => {
write!(f, "{}", native_function.name)
}
}
}
}

View File

@ -1,7 +1,11 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::token::{LiteralType, Token};
use crate::{
callable::Callable,
token::{LiteralType, Token},
};
#[derive(Debug, Clone)]
pub struct Environment {
values: HashMap<String, Option<LiteralType>>,
enclosing: Option<Rc<RefCell<Environment>>>,

View File

@ -1,18 +1,30 @@
use core::panic;
use std::{cell::RefCell, rc::Rc};
use std::{
cell::RefCell,
rc::Rc,
time::{SystemTime, UNIX_EPOCH},
};
use crate::{
ast::{Expr, Stmt},
callable::{Callable, CallableTrait, NativeFunction},
environment::Environment,
token::{LiteralType, Token, TokenType},
};
type InterpreterResult = Result<LiteralType, InterpreterSignal>;
#[derive(Debug)]
pub struct RuntimeError {
pub token: Token,
pub message: String,
}
pub struct InterpreterEnvironment {
pub globals: Rc<RefCell<Environment>>,
pub environment: Rc<RefCell<Environment>>,
}
impl RuntimeError {
pub fn new(token: &Token, message: &str) -> RuntimeError {
RuntimeError {
@ -22,22 +34,29 @@ impl RuntimeError {
}
}
pub enum InterpreterError {
pub enum InterpreterSignal {
RuntimeError(RuntimeError),
BreakSignal,
Break,
Return(LiteralType),
}
/*
This two impl blocks are for the ? operator. I'm too lazy to write the wrapping code for the enums and it also looks ugly,
so i just abuse the ? operator lol
Instead of InterpreterError::RuntimeError(RuntimeError {...} ) i can just RuntimeError {...}? to turn it into a InterpreterError
*/
impl From<RuntimeError> for InterpreterError {
impl From<RuntimeError> for InterpreterSignal {
fn from(value: RuntimeError) -> Self {
Self::RuntimeError(value)
}
}
impl From<InterpreterError> for RuntimeError {
fn from(value: InterpreterError) -> Self {
impl From<InterpreterSignal> for RuntimeError {
fn from(value: InterpreterSignal) -> Self {
match value {
InterpreterError::RuntimeError(runtime_error) => runtime_error,
InterpreterError::BreakSignal => panic!("Not a runtime error"),
InterpreterSignal::RuntimeError(runtime_error) => runtime_error,
InterpreterSignal::Break => panic!("Not a runtime error"),
InterpreterSignal::Return(_) => panic!("Not a runtime error"),
}
}
}
@ -45,9 +64,30 @@ impl From<InterpreterError> for RuntimeError {
pub fn interpret(
statements: &Vec<Stmt>,
environment: &Rc<RefCell<Environment>>,
) -> Result<(), InterpreterError> {
) -> Result<(), InterpreterSignal> {
let clock = |_arg: &[LiteralType]| {
Ok(LiteralType::Number(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs_f64()
/ 1000.0,
))
};
let clock_function = NativeFunction::new("clock".to_string(), 0, clock);
let environment = InterpreterEnvironment {
globals: Rc::clone(environment),
environment: Rc::clone(environment),
};
environment.globals.borrow_mut().define(
"clock",
Some(LiteralType::Callable(Callable::NativeFunction(
clock_function,
))),
);
for statement in statements {
execute(statement, environment)?;
execute(statement, &environment)?
}
Ok(())
@ -55,23 +95,24 @@ pub fn interpret(
fn execute(
statement: &Stmt,
environment: &Rc<RefCell<Environment>>,
) -> Result<(), InterpreterError> {
environment: &InterpreterEnvironment,
) -> Result<(), InterpreterSignal> {
let curr_environment = &environment.environment;
match statement {
Stmt::Expression { expression } => {
evaluate(expression, &mut environment.borrow_mut())?;
evaluate(expression, environment)?;
}
Stmt::Print { expression } => {
let expr = evaluate(expression, &mut environment.borrow_mut())?;
let expr = evaluate(expression, environment)?;
println!("{expr}");
}
Stmt::Var { name, initializer } => {
let value = if let Some(initializer) = initializer {
Some(evaluate(initializer, &mut environment.borrow_mut())?)
Some(evaluate(initializer, environment)?)
} else {
None
};
environment.borrow_mut().define(&name.lexeme, value);
curr_environment.borrow_mut().define(&name.lexeme, value);
}
Stmt::Block { statements } => {
execute_block(statements, environment)?;
@ -81,39 +122,69 @@ fn execute(
then_branch,
else_branch,
} => {
if is_truthy(&evaluate(condition, &mut environment.borrow_mut())?) {
if is_truthy(&evaluate(condition, environment)?) {
execute(then_branch, environment)?;
} else if let Some(else_branch) = else_branch {
execute(else_branch, environment)?;
}
}
Stmt::While { condition, body } => {
while is_truthy(&evaluate(condition, &mut environment.borrow_mut())?) {
while is_truthy(&evaluate(condition, environment)?) {
let result = execute(body, environment);
if result.is_err() {
break;
}
}
}
Stmt::Break => Err(InterpreterError::BreakSignal)?,
Stmt::Break => Err(InterpreterSignal::Break)?,
Stmt::Function { name, params, body } => {
let function = Callable::Function {
name: Box::new(name.clone()),
body: body.to_vec(),
params: params.to_vec(),
closure: Rc::clone(curr_environment),
};
environment
.globals
.borrow_mut()
.define(&name.lexeme, Some(LiteralType::Callable(function)));
}
Stmt::Return { value, .. } => {
let value = if let Some(v) = value {
evaluate(v, environment)?
} else {
LiteralType::Nil
};
return Err(InterpreterSignal::Return(value));
}
}
Ok(())
}
fn execute_block(
pub fn execute_block(
statements: &Vec<Stmt>,
environment: &Rc<RefCell<Environment>>,
) -> Result<(), InterpreterError> {
let block_enviroment = Rc::new(RefCell::new(Environment::with_enclosing(environment)));
environment: &InterpreterEnvironment,
) -> Result<(), InterpreterSignal> {
let block_enviroment = Rc::new(RefCell::new(Environment::with_enclosing(
&environment.environment,
)));
// we just move the block_enviroment to a new InterpreterEnvironment and clone the reference to
// globals, bcs outer environments might have the globals reference
let environment = InterpreterEnvironment {
globals: Rc::clone(&environment.globals),
environment: block_enviroment,
};
for stmt in statements {
execute(stmt, &block_enviroment)?;
execute(stmt, &environment)?;
}
Ok(())
}
fn evaluate(expr: &Expr, environment: &mut Environment) -> Result<LiteralType, InterpreterError> {
fn evaluate(expr: &Expr, environment: &InterpreterEnvironment) -> InterpreterResult {
let curr_environment = &environment.environment;
match expr {
Expr::Ternary {
first,
@ -129,7 +200,8 @@ fn evaluate(expr: &Expr, environment: &mut Environment) -> Result<LiteralType, I
Expr::Grouping { expression } => evaluate(expression, environment),
Expr::Literal { value } => Ok(value.clone()),
Expr::Unary { op, right } => Ok(unary(&evaluate(right, environment)?, op)),
Expr::Variable { name } => environment
Expr::Variable { name } => curr_environment
.borrow()
.get(name)
.ok_or_else(|| RuntimeError {
token: name.clone(),
@ -141,10 +213,11 @@ fn evaluate(expr: &Expr, environment: &mut Environment) -> Result<LiteralType, I
message: format!("Uninitialized variable {}.", name.lexeme),
})
})
.map_err(InterpreterError::RuntimeError),
.map_err(InterpreterSignal::RuntimeError),
Expr::Assign { name, value } => {
let value = evaluate(value, environment)?;
environment
curr_environment
.borrow_mut()
.assign(name, value.clone())
.map_err(|_| RuntimeError {
token: name.clone(),
@ -166,6 +239,38 @@ fn evaluate(expr: &Expr, environment: &mut Environment) -> Result<LiteralType, I
evaluate(right, environment)
}
Expr::Call {
callee,
paren,
args,
} => {
let callee_result = evaluate(callee, environment)?;
let mut arguments = Vec::new();
for arg in args {
arguments.push(evaluate(arg, environment)?);
}
match callee_result {
LiteralType::Callable(function) => {
if arguments.len() as u8 != function.arity() {
Err(RuntimeError {
token: paren.clone(),
message: format!(
"Expected {} arguments but got {}.",
function.arity(),
args.len()
),
})?
}
Ok(function.call(&arguments, environment)?)
}
_ => Err(RuntimeError {
token: paren.clone(),
message: "Can only call functions and classes".to_string(),
})?,
}
}
}
}
@ -173,8 +278,8 @@ fn ternary(
first: &Expr,
second: &Expr,
third: &Expr,
environment: &mut Environment,
) -> Result<LiteralType, InterpreterError> {
environment: &InterpreterEnvironment,
) -> InterpreterResult {
let first = evaluate(first, environment)?;
if is_truthy(&first) {
return evaluate(second, environment);
@ -182,11 +287,7 @@ fn ternary(
evaluate(third, environment)
}
fn binary(
left: &LiteralType,
right: &LiteralType,
op: &Token,
) -> Result<LiteralType, InterpreterError> {
fn binary(left: &LiteralType, right: &LiteralType, op: &Token) -> InterpreterResult {
use LiteralType::{Bool, Number, String};
use TokenType::{
BangEqual, Comma, EqualEqual, Greater, GreaterEqual, Less, LessEqual, Minus, Plus, Slash,
@ -232,10 +333,14 @@ fn is_truthy(literal: &LiteralType) -> bool {
}
}
fn is_equal(left: &LiteralType, right: &LiteralType) -> bool {
pub fn is_equal(left: &LiteralType, right: &LiteralType) -> bool {
match (left, right) {
(LiteralType::Nil, LiteralType::Nil) => true,
(LiteralType::Nil, _) => false,
_ => left == right,
// i could've implemeneted PartialEq but it doesn't make sense for every LiteralType
(LiteralType::String(s), LiteralType::String(s2)) => s == s2,
(LiteralType::Number(n1), LiteralType::Number(n2)) => n1 == n2,
(LiteralType::Bool(t1), LiteralType::Bool(t2)) => t1 == t2,
_ => false,
}
}

View File

@ -13,6 +13,7 @@ use scanner::Scanner;
use token::TokenType;
mod ast;
mod callable;
mod environment;
mod interpreter;
mod parser;

View File

@ -55,7 +55,9 @@ impl Parser<'_> {
}
fn declaration(&mut self) -> Result<Stmt, ParseError> {
let stmt = if self.match_token(&[TokenType::Var]) {
let stmt = if self.match_token(&[TokenType::Fun]) {
self.function("function")
} else if self.match_token(&[TokenType::Var]) {
self.var_declaration()
} else {
self.statement()
@ -64,6 +66,42 @@ impl Parser<'_> {
stmt.inspect_err(|_| self.synchronize())
}
fn function(&mut self, kind: &str) -> Result<Stmt, ParseError> {
let name = self.consume(TokenType::Identifier, &format!("Expect {} name.", kind))?;
self.consume(
TokenType::LeftParen,
&format!("Expect '(' after {kind} name."),
)?;
let mut params = Vec::new();
if !self.check(TokenType::RightParen) {
loop {
if params.len() >= 255 {
return Err(ParseError {
token: self.peek().clone(),
msg: "Can't have more than 255 parameters".to_string(),
});
}
params.push(self.consume(TokenType::Identifier, "Expect parameter name.")?);
if !self.match_token(&[TokenType::Comma]) {
break;
}
}
}
self.consume(TokenType::RightParen, "Expect ')' after parameters")?;
self.consume(
TokenType::LeftBrace,
&format!("Expect '{{' before {} body.", kind),
)?;
let body = self.block()?;
Ok(Stmt::Function { name, params, body })
}
fn var_declaration(&mut self) -> Result<Stmt, ParseError> {
let name = self.consume(TokenType::Identifier, "Expect variable name")?;
let initializer = if self.match_token(&[TokenType::Equal]) {
@ -90,6 +128,9 @@ impl Parser<'_> {
if self.match_token(&[TokenType::Print]) {
return self.print_statement();
}
if self.match_token(&[TokenType::Return]) {
return self.return_statement();
}
if self.match_token(&[TokenType::While]) {
return self.while_statement();
}
@ -227,6 +268,19 @@ impl Parser<'_> {
Ok(Stmt::Print { expression })
}
fn return_statement(&mut self) -> Result<Stmt, ParseError> {
let keyword = self.previous().clone();
let value = if !self.check(TokenType::Semicolon) {
Some(self.expression()?)
} else {
None
};
self.consume(TokenType::Semicolon, "Expect ';' after return value.");
Ok(Stmt::Return { keyword, value })
}
fn expression_statement(&mut self) -> Result<Stmt, ParseError> {
let expression = self.expression()?;
self.consume(TokenType::Semicolon, "Expect ';' after expression.")?;
@ -348,7 +402,46 @@ impl Parser<'_> {
});
}
self.primary()
self.call()
}
fn call(&mut self) -> Result<Expr, ParseError> {
let mut expr = self.primary()?;
loop {
if self.match_token(&[TokenType::LeftParen]) {
expr = self.finish_call(expr)?;
} else {
break;
}
}
Ok(expr)
}
fn finish_call(&mut self, callee: Expr) -> Result<Expr, ParseError> {
let mut args = Vec::new();
if !self.check(TokenType::RightParen) {
loop {
if args.len() >= 255 {
return Err(ParseError {
token: self.peek().clone(),
msg: "Can't have more than 255 arguments".to_string(),
});
}
args.push(self.equality()?);
if !self.match_token(&[TokenType::Comma]) {
break;
}
}
}
let paren = self.consume(TokenType::RightParen, "Expect ')' after arguments")?;
Ok(Expr::Call {
callee: Box::new(callee),
paren,
args,
})
}
/* error boundaries:

View File

@ -9,6 +9,7 @@ pub fn pretty_print(expr: &Expr) -> String {
LiteralType::Number(v) => v.to_string(),
LiteralType::Bool(v) => v.to_string(),
LiteralType::Nil => "Nil".to_string(),
LiteralType::Callable(_) => todo!(),
},
Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]),
Expr::Ternary {
@ -19,6 +20,11 @@ pub fn pretty_print(expr: &Expr) -> String {
Expr::Variable { name } => name.lexeme.clone(),
Expr::Assign { name, value } => parenthesize(&name.lexeme, &[value]),
Expr::Logical { left, op, right } => parenthesize(&op.lexeme, &[left, right]),
Expr::Call {
callee: _,
paren: _,
args: _,
} => todo!(),
}
}

View File

@ -273,6 +273,8 @@ fn get_identified_keyword(identifier: &str) -> Option<TokenType> {
#[cfg(test)]
mod tests {
use crate::interpreter::is_equal;
use super::*;
use TokenType::*;
@ -325,7 +327,10 @@ mod tests {
let expected = LiteralType::String("salam!".to_string());
assert_eq!(expected, actual.literal.as_ref().unwrap().clone());
assert!(is_equal(
&expected,
&actual.literal.as_ref().unwrap().clone(),
))
}
#[test]
@ -375,7 +380,10 @@ mod tests {
let actual_value = &token.literal;
assert_eq!(expected_value, actual_value.as_ref().unwrap().clone())
assert!(is_equal(
&expected_value,
&actual_value.as_ref().unwrap().clone()
))
}
#[test]
@ -398,6 +406,9 @@ mod tests {
let actual_value = &token.literal;
assert_eq!(expected_value, actual_value.as_ref().unwrap().clone())
assert!(is_equal(
&expected_value,
&actual_value.as_ref().unwrap().clone()
))
}
}

View File

@ -1,5 +1,7 @@
use std::fmt::Display;
use crate::callable::Callable;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum TokenType {
LeftParen,
@ -51,12 +53,13 @@ pub enum TokenType {
}
// i've seen this implementation in the wild
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub enum LiteralType {
String(String),
Number(f64),
Bool(bool),
Nil,
Callable(Callable),
}
impl LiteralType {
@ -76,6 +79,7 @@ impl Display for LiteralType {
LiteralType::Number(v) => write!(f, "{v:.2}"),
LiteralType::Bool(v) => write!(f, "{v}"),
LiteralType::Nil => write!(f, "nil"),
LiteralType::Callable(c) => write!(f, "<fn {c}>"),
}
}
}