mirror of
https://github.com/TheM1Stery/izanami.git
synced 2025-05-11 23:50:11 +00:00
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:
parent
04e8d5b8cb
commit
4bb2ce1af4
15
src/ast.rs
15
src/ast.rs
@ -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
105
src/callable.rs
Normal 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(¶m.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>>>,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ use scanner::Scanner;
|
||||
use token::TokenType;
|
||||
|
||||
mod ast;
|
||||
mod callable;
|
||||
mod environment;
|
||||
mod interpreter;
|
||||
mod parser;
|
||||
|
@ -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:
|
||||
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -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}>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user