diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0c544ba..bca4103 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -57,4 +57,3 @@ jobs: with: reporter: 'github-pr-check' github_token: ${{ secrets.GITHUB_TOKEN }} - \ No newline at end of file diff --git a/.gitignore b/.gitignore index f81d7f6..d6686bf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ target/ *.pdb -reports/ \ No newline at end of file +reports/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..9a1e08b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: local + hooks: + - id: cargo-fmt + name: cargo fmt + entry: cargo fmt -- --check + language: system + files: \.rs$ + args: [] + - id: lint + name: lint + entry: just lint + language: system + pass_filenames: false + files: \.rs$ + args: [] + - id: test + name: test + entry: just test + language: system + pass_filenames: false + files: \.rs$ + args: [] + stages: [pre-push] diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..449d964 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,18 @@ +https://craftinginterpreters.com/functions.html#function-objects + +Implementing custom functions. + +i.e +```lox +fun sayHi(first, last) { + print "Hi, " + first + " " + last + "!"; +} +sayHi("Dear", "Reader"); +``` + +The parsing for the func definition and the func call are implemented. + +Work to do: +- Store the `IDENTIFIER:sayHi -> code to run` in interpreter +- Lookup this code +- Run this code diff --git a/justfile b/justfile index eb73ac2..d891611 100644 --- a/justfile +++ b/justfile @@ -8,10 +8,14 @@ lint: @cargo clippy --version cargo clippy -- -D warnings -W clippy::pedantic -W clippy::nursery cargo doc +lint_fix: + @cargo clippy --version + cargo clippy --fix -- -D warnings -W clippy::pedantic -W clippy::nursery + test: cargo nextest run --all-targets --no-fail-fast t:test lox_run:build - cargo run run test.lox \ No newline at end of file + cargo run run test.lox diff --git a/src/builtins.rs b/src/builtins.rs new file mode 100644 index 0000000..147a4ec --- /dev/null +++ b/src/builtins.rs @@ -0,0 +1,16 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use crate::interpreter; +use crate::value::EvaluatedValue; + +pub fn clock( + _interpreter: &mut interpreter::Interpreter, + _args: &[EvaluatedValue], +) -> Result { + let start = SystemTime::now(); + #[allow(clippy::cast_precision_loss)] + start.duration_since(UNIX_EPOCH).map_or_else( + |_| Err("Unable to calculate time since UNIX_EPOC".to_string()), + |since_the_epoch| Ok(EvaluatedValue::Number(since_the_epoch.as_millis() as f64)), + ) +} diff --git a/src/eval.rs b/src/eval.rs index 6065b84..e845da5 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,59 +1,14 @@ //! Eval module //! -//! Responsible for evlauting the AST and returning the computed values -//! - -use std::fmt::Display; +//! Responsible for evalulating the AST and returning the computed values +//! Only supports simple expressions use crate::{ eval_parser::{Expr, LiteralAtom, Parser}, lexer::TokenType, + value::EvaluatedValue, }; -/// The value that an expression has evaluated too, this can be a literal. -#[derive(Clone, Debug)] -pub enum EvaluatedValue { - /// String value `"hello"` - String(String), - /// Number value. Note Lox only supports double precision floating point - Number(f64), - /// nil, the unset/null value - Nil, - /// Boolean value `true`/`false` - Bool(bool), -} - -impl EvaluatedValue { - pub(crate) const fn is_truthy(&self) -> bool { - match self { - Self::String(_) | Self::Number(_) => true, - Self::Nil => false, - Self::Bool(b) => *b, - } - } -} - -impl From for bool { - fn from(val: EvaluatedValue) -> Self { - match val { - EvaluatedValue::String(_) | EvaluatedValue::Number(_) => true, - EvaluatedValue::Nil => false, - EvaluatedValue::Bool(b) => b, - } - } -} - -impl Display for EvaluatedValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::String(s) => write!(f, "{s}"), - Self::Number(n) => write!(f, "{n}"), - Self::Nil => write!(f, "nil"), - Self::Bool(b) => write!(f, "{b:}"), - } - } -} - /// `Eval` /// an iterator that consumes expressions from the parser and tries to evaluate them. pub struct Eval<'de> { @@ -177,15 +132,14 @@ fn evaluate_expression(expr: Expr) -> Result { true => Ok(EvaluatedValue::Bool(false)), false => Ok(EvaluatedValue::Bool(true)), }, + _ => todo!(), }, ), TokenType::Minus => r.as_ref().map_or_else( |_| todo!(), |v| match v { - EvaluatedValue::String(_) => todo!(), EvaluatedValue::Number(n) => Ok(EvaluatedValue::Number(-n)), - EvaluatedValue::Nil => todo!(), - EvaluatedValue::Bool(_) => todo!(), + _ => todo!(), }, ), // TODO: Make unrepresentable by narrowing `operator` to `UnaryOperator:Not|Negate` diff --git a/src/run.rs b/src/interpreter.rs similarity index 59% rename from src/run.rs rename to src/interpreter.rs index 7f65368..c25d853 100644 --- a/src/run.rs +++ b/src/interpreter.rs @@ -3,19 +3,84 @@ //! Responsible for running the AST and returning the computed values //! -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; use crate::{ - eval::EvaluatedValue, - lexer::TokenType, + builtins, + lexer::{Token, TokenType}, parser::{Expr, LiteralAtom, Parser, Stmt}, + value::EvaluatedValue, }; -/// `Run` -/// an iterator that consumes expressions from the parser and tries to evaluate them. -pub struct Run<'de> { +trait Callable { + fn arity(&self, interpreter: &Interpreter) -> u8; + fn call( + &self, + interpreter: &mut Interpreter, + args: &[EvaluatedValue], + ) -> Result; +} + +/// `NativeFunction` is used to represent builtin native functions +#[derive(Clone)] +pub struct NativeFunction { + /// `name` of the native function + pub name: String, + /// Numbers of arguments that should be passed to `callable` + pub arity: u8, + /// A function to be run + pub callable: fn(&mut Interpreter, &[EvaluatedValue]) -> Result, +} + +impl fmt::Debug for NativeFunction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "NativeFunction({})", self.name) + } +} + +impl Callable for NativeFunction { + fn arity(&self, _interpreter: &Interpreter) -> u8 { + self.arity + } + fn call( + &self, + interpreter: &mut Interpreter, + args: &[EvaluatedValue], + ) -> Result { + (self.callable)(interpreter, args) + } +} + +#[derive(Debug)] +pub struct LoxFunction<'de> { + pub name: Token<'de>, + pub parameters: Vec>, + pub body: Vec>, +} + +impl Callable for LoxFunction<'_> { + fn arity(&self, _interpreter: &Interpreter) -> u8 { + self.parameters.len() as u8 + } + + fn call( + &self, + interpreter: &mut Interpreter, + args: &[EvaluatedValue], + ) -> Result { + todo!() + } +} + +/// `Interpreter` +/// responsible for iterating over the rusults of parser +/// and evaluating the statements and expressions encountered +pub struct Interpreter<'de> { parser: Parser<'de>, environment: Environment<'de>, + globals: Environment<'de>, + lox_functions: HashMap>, + counter: u64, } #[derive(Debug, Clone)] @@ -75,88 +140,138 @@ impl<'de> Environment<'de> { } } -impl<'de> Run<'de> { - /// Create a new `Run` to process a given input source code +impl<'de> Interpreter<'de> { + /// Create a new `Interpreter` to process a given input source code + /// # Panics + /// Will panic if `SystemTime::now()` returns value before `UNIX_EPOCH` #[must_use] pub fn new(input: &'de str) -> Self { - Run { + let mut global_data = HashMap::new(); + global_data.insert( + "clock", + EvaluatedValue::NativeFunction(NativeFunction { + name: "clock".to_string(), + arity: 0, + callable: builtins::clock, + }), + ); + let globals = Environment { + data: global_data, + enclosing: None, + }; + Self { parser: Parser::new(input), environment: Environment::new(), + globals, + lox_functions: Default::default(), + counter: 0, } } } -impl Iterator for Run<'_> { +impl Iterator for Interpreter<'_> { type Item = Result<(), u8>; fn next(&mut self) -> Option { let stmt = self.parser.next()?; - match stmt { - Ok(s) => { - let eval_stmt = evaluate_statement(&s, &mut self.environment); - match eval_stmt { - Ok(()) => Some(Ok(())), - Err(_) => Some(Err(70)), - } + stmt.map_or(Some(Err(65)), |s| { + let eval_stmt = evaluate_statement(&s, self); + match eval_stmt { + Ok(()) => Some(Ok(())), + Err(_) => Some(Err(70)), } - Err(_) => Some(Err(65)), - } + }) + } +} + +impl Interpreter<'_> { + fn alloc_id(&mut self) -> u64 { + self.counter += 1; + self.counter } } fn evaluate_statement<'de>( stmt: &Stmt<'de>, - environment: &mut Environment<'de>, + interpreter: &mut Interpreter<'de>, ) -> Result<(), String> { match stmt { Stmt::Print(expr) => { - let val = evaluate_expression(expr, environment)?; + let val = evaluate_expression(expr, interpreter)?; println!("{val}"); } Stmt::If(cond, then_branch, else_branch) => { - let eval_cond = evaluate_expression(cond, environment)?; + let eval_cond = evaluate_expression(cond, interpreter)?; if eval_cond.into() { - evaluate_statement(then_branch, environment)?; + evaluate_statement(then_branch, interpreter)?; } else if let Some(e) = else_branch { - evaluate_statement(e, environment)?; + evaluate_statement(e, interpreter)?; } } Stmt::ExpressionStatement(expr) => { - evaluate_expression(expr, environment)?; + evaluate_expression(expr, interpreter)?; } Stmt::Var(name, expr) => match expr { Some(v) => { - let evalutated_val = evaluate_expression(v, environment)?; - environment.var_assign(name, &evalutated_val); + let evalutated_val = evaluate_expression(v, interpreter)?; + interpreter.environment.var_assign(name, &evalutated_val); } None => { - environment.var_assign(name, &EvaluatedValue::Nil); + interpreter + .environment + .var_assign(name, &EvaluatedValue::Nil); } }, Stmt::Block(stmts) => { - let mut new_env = Environment::from_parent(environment.clone()); + let new_env = Environment::from_parent(interpreter.environment.clone()); + interpreter.environment = new_env; for stmt in stmts { - evaluate_statement(stmt, &mut new_env)?; + evaluate_statement(stmt, interpreter)?; } - *environment = new_env + // # TODO: Remove this clone + interpreter.environment = interpreter + .environment + .clone() .enclosing .expect("`new_env` declared above will always have `enclosing` set") .borrow() .clone(); } Stmt::While { condition, body } => loop { - if !(evaluate_expression(condition, environment)?.is_truthy()) { + if !(evaluate_expression(condition, interpreter)?.is_truthy()) { break; } - evaluate_statement(body, environment)?; + evaluate_statement(body, interpreter)?; }, + Stmt::Function { + name, + parameters, + body, + } => { + let func_id = interpreter.alloc_id(); + // interpreter.environment.assign( + // &'de key, + // &EvaluatedValue::LoxFunction { + // name: name.to_string(), + // binding: None, + // }, + // ); + let lox_fun = LoxFunction { + name: name.clone(), + parameters: parameters.clone(), + body: body.clone(), + }; + interpreter.lox_functions.insert(func_id, lox_fun); + interpreter.globals.insert(name, lox_fun); + () + } } Ok(()) } fn evaluate_expression<'de>( expr: &Expr<'de>, - environment: &mut Environment<'de>, + interpreter: &mut Interpreter<'de>, ) -> Result { match expr { Expr::Binary { @@ -164,8 +279,8 @@ fn evaluate_expression<'de>( operator, right, } => { - let l_expr = evaluate_expression(left, environment)?; - let r_expr = evaluate_expression(right, environment)?; + let l_expr = evaluate_expression(left, interpreter)?; + let r_expr = evaluate_expression(right, interpreter)?; match operator.token_type { TokenType::Minus | TokenType::Star @@ -242,7 +357,7 @@ fn evaluate_expression<'de>( } } Expr::Unary { operator, right } => { - let r = evaluate_expression(right, environment); + let r = evaluate_expression(right, interpreter); if let (TokenType::Minus, Ok(e)) = (operator.token_type, &r) { if let EvaluatedValue::Number(_) = e { } else { @@ -263,6 +378,8 @@ fn evaluate_expression<'de>( true => Ok(EvaluatedValue::Bool(false)), false => Ok(EvaluatedValue::Bool(true)), }, + EvaluatedValue::NativeFunction(_f) => todo!(), + EvaluatedValue::LoxFunction { name, binding } => todo!(), }, ), TokenType::Minus => r.as_ref().map_or_else( @@ -272,6 +389,8 @@ fn evaluate_expression<'de>( EvaluatedValue::Number(n) => Ok(EvaluatedValue::Number(-n)), EvaluatedValue::Nil => todo!(), EvaluatedValue::Bool(_) => todo!(), + EvaluatedValue::NativeFunction(_f) => todo!(), + EvaluatedValue::LoxFunction { name, binding } => todo!(), }, ), // TODO: Make unrepresentable by narrowing `operator` to `UnaryOperator:Not|Negate` @@ -286,19 +405,22 @@ fn evaluate_expression<'de>( LiteralAtom::Nil => Ok(EvaluatedValue::Nil), LiteralAtom::Bool(b) => Ok(EvaluatedValue::Bool(*b)), }, - Expr::Grouping(expr) => evaluate_expression(expr, environment), - Expr::Variable(token) => environment.get(token.origin).map_or_else( - || { - eprintln!("Undefined variable '{}'.", token.origin); - eprintln!("[line {}]", token.line); - Err("Undefined var".to_string()) - }, - Ok, - ), + Expr::Grouping(expr) => evaluate_expression(expr, interpreter), + Expr::Variable(token) => match interpreter.environment.get(token.origin) { + Some(v) => Ok(v), + None => interpreter.globals.get(token.origin).map_or_else( + || { + eprintln!("Undefined variable '{}'.", token.origin); + eprintln!("[line {}]", token.line); + Err("Undefined var".to_string()) + }, + Ok, + ), + }, Expr::Assign(name, expr) => { - if environment.get(name).is_some() { - let eval_expr = evaluate_expression(expr, environment)?; - environment.assign(name, &eval_expr)?; + if interpreter.environment.get(name).is_some() { + let eval_expr = evaluate_expression(expr, interpreter)?; + interpreter.environment.assign(name, &eval_expr)?; Ok(eval_expr) } else { eprintln!("Undefined variable '{name}'."); @@ -310,7 +432,7 @@ fn evaluate_expression<'de>( operator, right, } => { - let left_val = evaluate_expression(left, environment)?; + let left_val = evaluate_expression(left, interpreter)?; let left_truth: bool = left_val.is_truthy(); if operator.token_type == TokenType::Or { if left_truth { @@ -319,7 +441,32 @@ fn evaluate_expression<'de>( } else if !left_truth { return Ok(left_val); } - Ok(evaluate_expression(right, environment)?) + Ok(evaluate_expression(right, interpreter)?) + } + Expr::Call { + callee, + paren: _, + arguments, + } => { + let callee_fn = evaluate_expression(callee, interpreter)?; + let mut args: Vec = Vec::new(); + for arg in arguments { + args.push(evaluate_expression(arg, interpreter)?); + } + if let EvaluatedValue::NativeFunction(native_function) = callee_fn { + if native_function.arity(interpreter) as usize != args.len() { + eprintln!( + "Expected {} arguments but got {}.", + native_function.arity, + args.len() + ); + return Err("Incorrect arity".to_string()); + } + native_function.call(interpreter, &args) + } else { + eprintln!("Can only call function and classes."); + Err("Can only call functions and classes.".to_string()) + } } } } diff --git a/src/lexer.rs b/src/lexer.rs index 8df6ca2..66089e6 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -236,7 +236,7 @@ impl<'de> Iterator for Lexer<'de> { }, _ => { return Some(Err( - miette! {labels = vec![LabeledSpan::at(self.byte-c.len_utf8()..self.byte, "this character")], + miette! {labels = vec![LabeledSpan::at(self.byte-c.len_utf8()..self.byte, "this character")], "[line {}] Error: Unexpected character: {c}", self.line_num} .with_source_code(self.whole.to_string()), )) diff --git a/src/lib.rs b/src/lib.rs index d7bbd84..d57be5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,10 @@ #![allow(clippy::float_cmp)] #![allow(clippy::too_many_lines)] #![warn(missing_docs)] +mod builtins; pub mod eval; pub mod eval_parser; +pub mod interpreter; pub mod lexer; -pub mod parser; -pub mod run; +mod parser; +mod value; diff --git a/src/main.rs b/src/main.rs index 5e4cd0f..03e9637 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ use clap::Subcommand; use loxide::eval::Eval; use loxide::eval_parser::Parser; +use loxide::interpreter::Interpreter; use loxide::lexer::Lexer; -use loxide::run::Run; use miette::{IntoDiagnostic, Result, WrapErr}; use std::fs; use std::path::PathBuf; @@ -57,7 +57,7 @@ fn main() -> Result { .into_diagnostic() .wrap_err_with(|| "reading file".to_string())?; let mut exit_code = 0; - for res in Run::new(&input) { + for res in Interpreter::new(&input) { match res { Ok(()) => {} Err(err_code) => { diff --git a/src/parser.rs b/src/parser.rs index 824ff24..184fb60 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,7 +4,10 @@ //! //! Uses a recursive desecent parser. To transform the token stream into //! `Expr` -use crate::lexer::{Lexer, Token, TokenType}; +use crate::{ + interpreter, + lexer::{Lexer, Token, TokenType}, +}; /// `Parser` is responsible for iterating over the token stream from `Lexer` /// and converting the lexed `Token` into `Expr` which represent an Abstract Syntax Tree (AST) @@ -27,7 +30,7 @@ pub enum LiteralAtom<'de> { Bool(bool), } -#[derive(Debug)] +#[derive(Debug, Clone)] /// `Expr` represents a unit of an AST pub enum Expr<'de> { /// `Binary` is a binary expression such as `1 * 2` @@ -63,9 +66,18 @@ pub enum Expr<'de> { /// The right expression of a Logical expression right: Box>, }, + /// Function `Call` + Call { + /// function to be called + callee: Box>, + /// paren token + paren: Token<'de>, + /// arguments to be passed to function call + arguments: Vec>, + }, } -#[derive(Debug)] +#[derive(Debug, Clone)] /// `Stmt` represents the possible statements supported pub enum Stmt<'de> { /// A print statement @@ -86,6 +98,12 @@ pub enum Stmt<'de> { /// The statements that will be executed repreatedly if `condition` body: Box>, }, + /// Func statement + Function { + name: Token<'de>, + parameters: Vec>, + body: Vec>, + }, } impl<'de> Iterator for Parser<'de> { @@ -121,12 +139,49 @@ impl<'de> Parser<'de> { } fn declaration(&mut self) -> Result, String> { + if self.match_tokens(&[TokenType::Fun]) { + return self.function("function"); + } if self.match_tokens(&[TokenType::Var]) { return self.var_declaration(); } self.statement() } + fn function(&mut self, kind: &str) -> Result, String> { + let name = self + .consume(&TokenType::Identifier, &format!("Expect {} name.", kind))? + .clone(); + self.consume( + &TokenType::LeftParen, + &format!("Expect '(' after {} name.", kind), + )?; + let mut parameters = Vec::new(); + if !self.check(&TokenType::RightParen) { + loop { + if parameters.len() > 255 { + return Err("Can't have more than 255 parameters.".to_string()); + } + // TODO: Figure out right way to avoid this clone + let param = self + .consume(&TokenType::Identifier, "Expect parameter name")? + .clone(); + parameters.push(param); + if !self.match_tokens(&[TokenType::Comma]) { + break; + } + } + } + self.consume(&TokenType::RightParen, "Expect ')' after parameters")?; + self.consume(&TokenType::LeftBrace, "Expect '{' after parameters")?; + let body = self.block()?; + Ok(Stmt::Function { + name, + parameters, + body, + }) + } + fn var_declaration(&mut self) -> Result, String> { let name = self .consume(&TokenType::Identifier, "Expect variable name.")? @@ -229,20 +284,19 @@ impl<'de> Parser<'de> { while !self.check(&TokenType::RightBrace) && !self.is_at_end() { stmts.push(self.declaration()?); } - - self.consume(&TokenType::RightBrace, "Expect '}' after block.")?; + self.consume(&TokenType::RightBrace, "Expect '}' after block")?; Ok(stmts) } fn expression_statement(&mut self) -> Result, String> { let expr = self.expression()?; - self.consume(&TokenType::Semicolon, "Expect ';' after value.")?; + self.consume(&TokenType::Semicolon, "Expect ';' after value")?; Ok(Stmt::ExpressionStatement(expr)) } fn print_statement(&mut self) -> Result, String> { let expr = self.expression()?; - self.consume(&TokenType::Semicolon, "Expect ';' after value.")?; + self.consume(&TokenType::Semicolon, "Expect ';' after value")?; Ok(Stmt::Print(expr)) } @@ -382,7 +436,44 @@ impl<'de> Parser<'de> { }); } - self.primary() + self.call() + } + + fn call(&mut self) -> Result, String> { + let mut expr = self.primary()?; + loop { + if self.match_tokens(&[TokenType::LeftParen]) { + expr = self.finish_call(expr)?; + } else { + break; + } + } + Ok(expr) + } + + fn finish_call(&mut self, callee: Expr<'de>) -> Result, String> { + let mut arguments: Vec> = vec![]; + + if !self.check(&TokenType::RightParen) { + loop { + arguments.push(self.expression()?); + // https://users.rust-lang.org/t/how-many-arguments-can-i-pass-to-a-function/84250 + if arguments.len() >= 65535 { + return Err("Can't have more than 65535 arguments.".to_string()); + } + if !self.match_tokens(&[TokenType::Comma]) { + break; + } + } + } + let paren = self + .consume(&TokenType::RightParen, "Expect ')' after arguments.")? + .clone(); + Ok(Expr::Call { + callee: Box::new(callee), + paren, + arguments, + }) } fn primary(&mut self) -> Result, String> { diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..00774d1 --- /dev/null +++ b/src/value.rs @@ -0,0 +1,60 @@ +use std::fmt::{Display, write}; + +use crate::interpreter::NativeFunction; + +/// The value that an expression has evaluated too, this can be a literal. +#[derive(Clone, Debug)] +pub enum EvaluatedValue { + /// String value `"hello"` + String(String), + /// Number value. Note Lox only supports double precision floating point + Number(f64), + /// nil, the unset/null value + Nil, + /// Boolean value `true`/`false` + Bool(bool), + /// builtin fn + NativeFunction(NativeFunction), + /// fn + LoxFunction { + name: String, + binding: Option>, + }, +} + +impl EvaluatedValue { + pub(crate) const fn is_truthy(&self) -> bool { + match self { + Self::String(_) | Self::Number(_) => true, + Self::Nil => false, + Self::Bool(b) => *b, + Self::NativeFunction(_f) => true, + Self::LoxFunction { name, binding } => true, + } + } +} + +impl From for bool { + fn from(val: EvaluatedValue) -> Self { + match val { + EvaluatedValue::String(_) | EvaluatedValue::Number(_) => true, + EvaluatedValue::Nil => false, + EvaluatedValue::Bool(b) => b, + EvaluatedValue::NativeFunction(_f) => true, + EvaluatedValue::LoxFunction { name, binding } => true, + } + } +} + +impl Display for EvaluatedValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::String(s) => write!(f, "{s}"), + Self::Number(n) => write!(f, "{n}"), + Self::Nil => write!(f, "nil"), + Self::Bool(b) => write!(f, "{b:}"), + Self::NativeFunction(native_fn) => write!(f, "{native_fn:?}"), + Self::LoxFunction { name, binding } => write!(f, "{name:?}"), + } + } +} diff --git a/test.lox b/test.lox index d88ec81..12fff54 100644 --- a/test.lox +++ b/test.lox @@ -1 +1,4 @@ -if (true) print "bar"; // "bar" +fun sayHi(first, last) { + print "Hi, " + first + " " + last + "!"; +} +sayHi("Dear", "Reader"); diff --git a/tests/eval_parser_test.rs b/tests/eval_parser_test.rs index e9dd5e3..02b9c6f 100644 --- a/tests/eval_parser_test.rs +++ b/tests/eval_parser_test.rs @@ -22,7 +22,7 @@ fn test_parser_literals(#[case] input: &str) { .take_while(std::result::Result::is_ok) .map(|e| match e { Ok(exp) => format!("{exp}"), - Err(_) => format!(""), + Err(_) => String::new(), }) .collect(); assert_snapshot!(exprs.join("\n")); diff --git a/tests/fixtures/evaluate.lox b/tests/fixtures/evaluate.lox index 44ea417..406583b 100644 --- a/tests/fixtures/evaluate.lox +++ b/tests/fixtures/evaluate.lox @@ -1,3 +1,3 @@ false true -nil \ No newline at end of file +nil diff --git a/tests/fixtures/evaluate_error.lox b/tests/fixtures/evaluate_error.lox index a1fa585..3216e5a 100644 --- a/tests/fixtures/evaluate_error.lox +++ b/tests/fixtures/evaluate_error.lox @@ -1,2 +1,2 @@ -"hello" --true \ No newline at end of file +-true diff --git a/tests/fixtures/lexer.lox b/tests/fixtures/lexer.lox index 8ecf4bf..2cea7df 100644 --- a/tests/fixtures/lexer.lox +++ b/tests/fixtures/lexer.lox @@ -1 +1 @@ -"hello" 123 false nil \ No newline at end of file +"hello" 123 false nil diff --git a/tests/fixtures/lexer_error.lox b/tests/fixtures/lexer_error.lox index f1ee89c..bf82c85 100644 --- a/tests/fixtures/lexer_error.lox +++ b/tests/fixtures/lexer_error.lox @@ -1 +1 @@ -#"// \ No newline at end of file +#"// diff --git a/tests/fixtures/parser.lox b/tests/fixtures/parser.lox index 75dc39d..93c6c7d 100644 --- a/tests/fixtures/parser.lox +++ b/tests/fixtures/parser.lox @@ -1,2 +1,2 @@ 52 + 80 - 94 -83 < 99 > 115 \ No newline at end of file +83 < 99 > 115 diff --git a/tests/fixtures/parser_error.lox b/tests/fixtures/parser_error.lox index f41d69e..02d56b5 100644 --- a/tests/fixtures/parser_error.lox +++ b/tests/fixtures/parser_error.lox @@ -1,2 +1,2 @@ (72 +) -"foo \ No newline at end of file +"foo diff --git a/tests/fixtures/run.lox b/tests/fixtures/run.lox index 189d6f3..5e39b9b 100644 --- a/tests/fixtures/run.lox +++ b/tests/fixtures/run.lox @@ -61,4 +61,5 @@ var quz = "global quz"; } print world; // "global world" print baz; // "global baz" -print quz; // "global_quz" \ No newline at end of file +print quz; // "global_quz" +clock(); diff --git a/tests/fixtures/run_error.lox b/tests/fixtures/run_error.lox index 1d23839..beb534a 100644 --- a/tests/fixtures/run_error.lox +++ b/tests/fixtures/run_error.lox @@ -1,4 +1,4 @@ 49 + "baz"; print "this should not be printed"; print "79" + "baz"; -print false * (18 + 84); \ No newline at end of file +print false * (18 + 84); diff --git a/tests/fixtures/run_error_illegal_call.lox b/tests/fixtures/run_error_illegal_call.lox new file mode 100644 index 0000000..f5e13cc --- /dev/null +++ b/tests/fixtures/run_error_illegal_call.lox @@ -0,0 +1,4 @@ +true(); +false(); +1(); +"string"(); diff --git a/tests/fixtures/run_error_incorrect_arity.lox b/tests/fixtures/run_error_incorrect_arity.lox new file mode 100644 index 0000000..cd27a8c --- /dev/null +++ b/tests/fixtures/run_error_incorrect_arity.lox @@ -0,0 +1 @@ +clock(1, 2, 3); diff --git a/tests/fixtures/run_error_must_be_a_number.lox b/tests/fixtures/run_error_must_be_a_number.lox index 2c3f14a..77b413e 100644 --- a/tests/fixtures/run_error_must_be_a_number.lox +++ b/tests/fixtures/run_error_must_be_a_number.lox @@ -1,3 +1,3 @@ if ("x" > 1) { print "panic!"; -} \ No newline at end of file +} diff --git a/tests/fixtures/run_error_undefined_var.lox b/tests/fixtures/run_error_undefined_var.lox index 92c08d2..dfc4801 100644 --- a/tests/fixtures/run_error_undefined_var.lox +++ b/tests/fixtures/run_error_undefined_var.lox @@ -14,4 +14,4 @@ print foo; // "modified foo" print quz; // "outer quz" } -print quz; \ No newline at end of file +print quz; diff --git a/tests/fixtures/run_error_var.lox b/tests/fixtures/run_error_var.lox index d145095..96bb531 100644 --- a/tests/fixtures/run_error_var.lox +++ b/tests/fixtures/run_error_var.lox @@ -1,2 +1,2 @@ x = 10; -print x; \ No newline at end of file +print x; diff --git a/tests/fixtures/run_error_var_assign.lox b/tests/fixtures/run_error_var_assign.lox index 806d369..97cdd5e 100644 --- a/tests/fixtures/run_error_var_assign.lox +++ b/tests/fixtures/run_error_var_assign.lox @@ -1 +1 @@ -var x = ; \ No newline at end of file +var x = ; diff --git a/tests/fixtures/run_for.lox b/tests/fixtures/run_for.lox index 7487b92..3db3a10 100644 --- a/tests/fixtures/run_for.lox +++ b/tests/fixtures/run_for.lox @@ -32,4 +32,4 @@ var quz = "after"; for (quz = 0; quz < 1; quz = quz + 1) { print quz; } -} \ No newline at end of file +} diff --git a/tests/fixtures/run_if.lox b/tests/fixtures/run_if.lox index 0057f25..5b77064 100644 --- a/tests/fixtures/run_if.lox +++ b/tests/fixtures/run_if.lox @@ -12,7 +12,7 @@ if (a = true) { var stage = "unknown"; var age = 50; if (age < 18) { stage = "child"; } -if (age >= 18) { stage = "adult"; } +if (age >= 18) { stage = "adult"; } print stage; // "adult" var isAdult = age >= 18; @@ -23,4 +23,4 @@ if (1 > 2 and 1 != 2 or 1 < 3) { } else { print "else"; } -if (1>2) {print"if";} else {print"else";} \ No newline at end of file +if (1>2) {print"if";} else {print"else";} diff --git a/tests/fixtures/run_logical.lox b/tests/fixtures/run_logical.lox index a37de9f..e8437b8 100644 --- a/tests/fixtures/run_logical.lox +++ b/tests/fixtures/run_logical.lox @@ -19,4 +19,4 @@ var a = "hello"; var b = "hello"; (a = false) or (b = true) or (a = "hello"); print a; -print b; \ No newline at end of file +print b; diff --git a/tests/main_test.rs b/tests/main_test.rs index 30b12a0..69ade05 100644 --- a/tests/main_test.rs +++ b/tests/main_test.rs @@ -193,6 +193,18 @@ fn test_run_with_file(#[case] file_name: &str, #[case] expected_stdout: &str) { "", 70 )] +#[case( + "run_error_illegal_call", + "Can only call function and classes.\n", + "", + 70 +)] +#[case( + "run_error_incorrect_arity", + "Expected 0 arguments but got 3.\n", + "", + 70 +)] fn test_run_with_file_error( #[case] file_name: &str, #[case] expected_err: &str,