From 05aaf41e1540470434398bb2bcdeb512f7b610bb Mon Sep 17 00:00:00 2001 From: Yago Date: Thu, 3 Aug 2023 21:37:43 +0200 Subject: [PATCH 01/18] Initial implementation, some work needs to be done. For now it passes the est suite, but it needs to be tested with more complex examples. --- Cargo.lock | 8 + crates/formatter/Cargo.toml | 11 + crates/formatter/src/formatter.rs | 405 ++++++++++++++++++++++++++++++ crates/formatter/src/lib.rs | 1 + crates/parser/src/ast.rs | 2 +- 5 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 crates/formatter/Cargo.toml create mode 100644 crates/formatter/src/formatter.rs create mode 100644 crates/formatter/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fb3d059..8a60200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,6 +335,14 @@ dependencies = [ "libc", ] +[[package]] +name = "formatter" +version = "0.1.0" +dependencies = [ + "lexer", + "parser", +] + [[package]] name = "half" version = "1.8.2" diff --git a/crates/formatter/Cargo.toml b/crates/formatter/Cargo.toml new file mode 100644 index 0000000..9727d9a --- /dev/null +++ b/crates/formatter/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "formatter" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +lexer = { path = "../lexer" } +parser = { path = "../parser" } diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs new file mode 100644 index 0000000..178dda2 --- /dev/null +++ b/crates/formatter/src/formatter.rs @@ -0,0 +1,405 @@ +use parser::ast::{precedence_of, BlockStatement, Expression, Precedence, Program, Statement}; + +pub struct Formatter { + /// The current indentation level. + indent: usize, + + /// Current precedence. + preference: Precedence, + + /// Indicates if the current expression is nested. + is_nested: bool, + + last_expression: Option, + + /// The output buffer. + output: String, + + /// Program to format. + program: Program, +} + +impl Formatter { + pub fn new(program: Program) -> Self { + Self { + indent: 0, + preference: Precedence::Lowest, + is_nested: false, + last_expression: None, + output: String::new(), + program, + } + } + + pub fn format(&mut self) -> String { + self.visit_program(self.program.clone()); + self.output.clone() + } + + fn visit_program(&mut self, program: Program) { + for stmt in program.statements { + self.visit_statement(&stmt); + } + } + + fn visit_statement(&mut self, stmt: &Statement) { + self.push_indent(); + self.is_nested = false; + match stmt { + Statement::Let(let_stmt) => { + self.push("let "); + self.push(let_stmt.name.value.as_str()); + self.push(" = "); + self.visit_expression(&let_stmt.value); + self.push(";"); + } + Statement::Return(return_stmt) => { + self.push("return "); + self.visit_expression(&return_stmt.return_value); + self.push(";"); + } + Statement::Expression(exp_stmt) => { + self.visit_expression(&exp_stmt); + if let Some(Expression::Conditional(_)) = self.last_expression { + } else { + self.push(";"); + } + } + } + self.push("\n"); + self.last_expression = None; + } + + fn visit_expression(&mut self, exp: &Expression) { + match exp { + Expression::Identifier(ident) => { + self.push(ident.value.as_str()); + } + Expression::Primitive(primitive) => { + self.push(primitive.to_string().as_str()); + } + Expression::Prefix(prefix) => { + self.push(prefix.token.to_string().as_str()); + + self.last_expression = Some(exp.clone()); + self.visit_expression(&prefix.right); + } + Expression::Infix(infix) => { + let mut needs_parenthesis = false; + if let Some(last) = &self.last_expression { + match &last { + Expression::Prefix(_) => { + self.push("("); + needs_parenthesis = true; + } + Expression::Infix(last_infix) => { + if precedence_of(&last_infix.token) > precedence_of(&infix.token) { + self.push("("); + needs_parenthesis = true; + } + } + _ => {} + } + } + + self.last_expression = Some(exp.clone()); + self.visit_expression(&infix.left); + self.is_nested = true; + self.push(" "); + self.push(infix.token.to_string().as_str()); + self.push(" "); + + self.last_expression = Some(exp.clone()); + self.visit_expression(&infix.right); + + if needs_parenthesis { + self.push(")"); + } + } + Expression::Conditional(if_exp) => { + self.push("if ("); + + self.last_expression = Some(exp.clone()); + self.visit_expression(&if_exp.condition); + self.push(") {"); + self.push("\n"); + self.indent += 1; + + self.last_expression = Some(exp.clone()); + self.visit_block_statement(&if_exp.consequence); + self.indent -= 1; + self.push_indent(); + self.push("}"); + if let Some(alternative) = &if_exp.alternative { + self.push(" else {\n"); + self.indent += 1; + + self.last_expression = Some(exp.clone()); + self.visit_block_statement(alternative); + self.indent -= 1; + self.push_indent(); + self.push("}"); + } + } + Expression::FunctionLiteral(func) => { + self.push("fn ("); + let parameters = func + .parameters + .iter() + .map(ToString::to_string) + .collect::>(); + self.push(parameters.join(", ").as_str()); + self.push(") {"); + self.push("\n"); + + self.indent += 1; + self.last_expression = Some(exp.clone()); + self.visit_block_statement(&func.body); + self.indent -= 1; + + self.push_indent(); + self.push("}"); + } + Expression::FunctionCall(call) => { + self.last_expression = Some(exp.clone()); + self.visit_expression(&call.function); + self.push("("); + for (i, arg) in call.arguments.iter().enumerate() { + self.last_expression = Some(exp.clone()); + self.visit_expression(arg); + if i < call.arguments.len() - 1 { + self.push(", "); + } + } + self.push(")"); + } + Expression::ArrayLiteral(array) => { + self.push(array.to_string().as_str()); + } + Expression::HashMapLiteral(hash) => { + self.push(hash.to_string().as_str()); + } + Expression::IndexExpression(index) => { + self.last_expression = Some(exp.clone()); + self.visit_expression(&index.left); + self.push("["); + + self.last_expression = Some(exp.clone()); + self.visit_expression(&index.index); + self.push("]"); + } + } + + self.last_expression = Some(exp.clone()); + + self.preference = self.get_precedence(exp); + } + + fn visit_block_statement(&mut self, block: &BlockStatement) { + for stmt in &block.statements { + self.visit_statement(&stmt); + } + } + + fn get_precedence(&self, exp: &Expression) -> Precedence { + match exp { + Expression::Infix(infix) => precedence_of(&infix.token), + Expression::Prefix(prefix) => precedence_of(&prefix.token), + _ => Precedence::Lowest, + } + } + + fn push(&mut self, s: &str) { + self.output.push_str(s); + } + + fn push_indent(&mut self) { + for _ in 0..self.indent { + self.push(" "); + } + } +} + +#[cfg(test)] +mod tests { + use lexer::lexer::Lexer; + use parser::parser::Parser; + + use super::*; + + #[test] + fn test_basic_format() { + let input = r#" + let x = 5; + let y = 10; + let foobar = 838383; + let add = fn(x, y) { + x + y; + }; + let result = add(x, y); + if (5 < 10) { + return true; + } else {return false; + } + "#; + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut formatter = Formatter::new(program); + let formatted = formatter.format(); + let expected = r#"let x = 5; +let y = 10; +let foobar = 838383; +let add = fn (x, y) { + x + y; +}; +let result = add(x, y); +if (5 < 10) { + return true; +} else { + return false; +} +"#; + println!("{}", formatted); + assert_eq!(formatted, expected); + } + + #[test] + fn test_format_arithmetic() { + let input = r#" + let x = 5 * 9 + 10; + let z = 5 * (9 + 10); + let y = 10 / 5 - 2; + let yy = 10 / (5 - 2); + let a = 5 * (9 + 10) / 2; + let b = 5 * (9 + 10) / (2 + 3); + let c = (5 * (9 + 10) / (2 + 3)) * 4; + let d = (5 * (9 + 10) / 2 + 3) * 4; + let e = [1, 2, 3, 4, 5][1] * 2 + 3; + let f = {"one": 1, "two": 2}["one"] * 2 + 3; + "#; + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut formatter = Formatter::new(program); + let formatted = formatter.format(); + let expected = r#"let x = 5 * 9 + 10; +let z = 5 * (9 + 10); +let y = 10 / 5 - 2; +let yy = 10 / (5 - 2); +let a = 5 * (9 + 10) / 2; +let b = 5 * (9 + 10) / (2 + 3); +let c = 5 * (9 + 10) / (2 + 3) * 4; +let d = (5 * (9 + 10) / 2 + 3) * 4; +let e = [1, 2, 3, 4, 5][1] * 2 + 3; +let f = {"one": 1, "two": 2}["one"] * 2 + 3; +"#; + println!("{}", formatted); + assert_eq!(formatted, expected); + } + + #[test] + fn test_prefix_formatting() { + let input = r#"let x = -5; + let y = !true; + let a = -5 + 10; + let b = !(true == false); + let b = !(true ); + let c = -(5 + 10); + let c = -(-5 + 10); + let c = --(5 + 10); + let c = -(-(5 + 10)); + let c = ---(5 + 10); + let d = !!true; + let d = !(!true); +"#; + + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut formatter = Formatter::new(program); + let formatted = formatter.format(); + let expected = r#"let x = -5; +let y = !true; +let a = -5 + 10; +let b = !(true == false); +let b = !true; +let c = -(5 + 10); +let c = -(-5 + 10); +let c = --(5 + 10); +let c = --(5 + 10); +let c = ---(5 + 10); +let d = !!true; +let d = !!true; +"#; + println!("{}", formatted); + assert_eq!(formatted, expected); + } + + #[test] + fn test_string_has_quotes() { + let input = r#"let x = "hello"; +"#; + + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut formatter = Formatter::new(program); + let formatted = formatter.format(); + let expected = r#"let x = "hello"; +"#; + + println!("{}", formatted); + assert_eq!(formatted, expected); + } + + #[test] + fn test_fibonacci_it_formatting() { + let input = r#" +let fibonacci_it= fn(x) { +if (x < 2){ + return x; +} + let iter = fn (i, table) { + if (i > x) { + return table[x]; + } else { + let table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, table); + } + }; + return iter(2, [0,1]); +}; + +let fib = fibonacci_it(20); + +puts(fib);"#; + + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut formatter = Formatter::new(program); + let formatted = formatter.format(); + + let expected = r#"let fibonacci_it = fn (x) { + if (x < 2) { + return x; + } + let iter = fn (i, table) { + if (i > x) { + return table[x]; + } else { + let table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, table); + } + }; + return iter(2, [0, 1]); +}; +let fib = fibonacci_it(20); +puts(fib); +"#; + println!("{}", formatted); + + assert_eq!(formatted, expected); + } +} diff --git a/crates/formatter/src/lib.rs b/crates/formatter/src/lib.rs new file mode 100644 index 0000000..96dc2d9 --- /dev/null +++ b/crates/formatter/src/lib.rs @@ -0,0 +1 @@ +pub mod formatter; diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index 67b0a73..35677ca 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -164,7 +164,7 @@ impl Display for Primitive { match self { Primitive::IntegerLiteral(x) => write!(f, "{x}"), Primitive::BooleanLiteral(x) => write!(f, "{x}"), - Primitive::StringLiteral(x) => write!(f, "{x}"), + Primitive::StringLiteral(x) => write!(f, "\"{x}\""), } } } From 38daecee202814985b64235dad7fa9493f954385 Mon Sep 17 00:00:00 2001 From: Yago Date: Thu, 3 Aug 2023 23:28:00 +0200 Subject: [PATCH 02/18] Feat: Formatter now handles implicit returns --- crates/formatter/src/formatter.rs | 48 +++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs index 178dda2..2cef750 100644 --- a/crates/formatter/src/formatter.rs +++ b/crates/formatter/src/formatter.rs @@ -8,7 +8,7 @@ pub struct Formatter { preference: Precedence, /// Indicates if the current expression is nested. - is_nested: bool, + is_inside_function: bool, last_expression: Option, @@ -24,7 +24,7 @@ impl Formatter { Self { indent: 0, preference: Precedence::Lowest, - is_nested: false, + is_inside_function: false, last_expression: None, output: String::new(), program, @@ -44,7 +44,6 @@ impl Formatter { fn visit_statement(&mut self, stmt: &Statement) { self.push_indent(); - self.is_nested = false; match stmt { Statement::Let(let_stmt) => { self.push("let "); @@ -61,7 +60,7 @@ impl Formatter { Statement::Expression(exp_stmt) => { self.visit_expression(&exp_stmt); if let Some(Expression::Conditional(_)) = self.last_expression { - } else { + } else if !self.is_inside_function { self.push(";"); } } @@ -104,7 +103,6 @@ impl Formatter { self.last_expression = Some(exp.clone()); self.visit_expression(&infix.left); - self.is_nested = true; self.push(" "); self.push(infix.token.to_string().as_str()); self.push(" "); @@ -152,10 +150,12 @@ impl Formatter { self.push(") {"); self.push("\n"); + self.is_inside_function = true; self.indent += 1; self.last_expression = Some(exp.clone()); self.visit_block_statement(&func.body); self.indent -= 1; + self.is_inside_function = false; self.push_indent(); self.push("}"); @@ -251,7 +251,7 @@ mod tests { let y = 10; let foobar = 838383; let add = fn (x, y) { - x + y; + x + y }; let result = add(x, y); if (5 < 10) { @@ -402,4 +402,40 @@ puts(fib); assert_eq!(formatted, expected); } + + #[test] + fn format_implicit_return() { + let input = r#" +let fibonacci = fn(x) { + if (x < 2) { + x + } + else{ + fibonacci(x - 1) + fibonacci(x - 2) + } +} + + +puts(fibonacci(30)); + "#; + + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut formatter = Formatter::new(program); + let formatted = formatter.format(); + + let expected = r#"let fibonacci = fn (x) { + if (x < 2) { + x + } else { + fibonacci(x - 1) + fibonacci(x - 2) + } +}; +puts(fibonacci(30)); +"#; + println!("{}", formatted); + + assert_eq!(formatted, expected); + } } From 60390bc35c0aba0aea2611102157c516e4e6e676 Mon Sep 17 00:00:00 2001 From: Yago Date: Thu, 3 Aug 2023 23:31:58 +0200 Subject: [PATCH 03/18] Test refactoring --- crates/formatter/src/formatter.rs | 123 +++++++++++++----------------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs index 2cef750..9238807 100644 --- a/crates/formatter/src/formatter.rs +++ b/crates/formatter/src/formatter.rs @@ -10,6 +10,7 @@ pub struct Formatter { /// Indicates if the current expression is nested. is_inside_function: bool, + /// Previos expression on the ast last_expression: Option, /// The output buffer. @@ -242,11 +243,8 @@ mod tests { } else {return false; } "#; - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - let mut formatter = Formatter::new(program); - let formatted = formatter.format(); + + let formatted = format(input); let expected = r#"let x = 5; let y = 10; let foobar = 838383; @@ -278,11 +276,7 @@ if (5 < 10) { let e = [1, 2, 3, 4, 5][1] * 2 + 3; let f = {"one": 1, "two": 2}["one"] * 2 + 3; "#; - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - let mut formatter = Formatter::new(program); - let formatted = formatter.format(); + let formatted = format(input); let expected = r#"let x = 5 * 9 + 10; let z = 5 * (9 + 10); let y = 10 / 5 - 2; @@ -301,24 +295,20 @@ let f = {"one": 1, "two": 2}["one"] * 2 + 3; #[test] fn test_prefix_formatting() { let input = r#"let x = -5; - let y = !true; - let a = -5 + 10; - let b = !(true == false); - let b = !(true ); - let c = -(5 + 10); - let c = -(-5 + 10); - let c = --(5 + 10); - let c = -(-(5 + 10)); - let c = ---(5 + 10); - let d = !!true; - let d = !(!true); -"#; + let y = !true; + let a = -5 + 10; + let b = !(true == false); + let b = !(true ); + let c = -(5 + 10); + let c = -(-5 + 10); + let c = --(5 + 10); + let c = -(-(5 + 10)); + let c = ---(5 + 10); + let d = !!true; + let d = !(!true); + "#; - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - let mut formatter = Formatter::new(program); - let formatted = formatter.format(); + let formatted = format(input); let expected = r#"let x = -5; let y = !true; let a = -5 + 10; @@ -341,11 +331,7 @@ let d = !!true; let input = r#"let x = "hello"; "#; - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - let mut formatter = Formatter::new(program); - let formatted = formatter.format(); + let formatted = format(input); let expected = r#"let x = "hello"; "#; @@ -356,30 +342,26 @@ let d = !!true; #[test] fn test_fibonacci_it_formatting() { let input = r#" -let fibonacci_it= fn(x) { -if (x < 2){ - return x; -} - let iter = fn (i, table) { - if (i > x) { - return table[x]; - } else { - let table = push(table, table[i - 1] + table[i - 2]); - return iter(i + 1, table); - } - }; - return iter(2, [0,1]); -}; + let fibonacci_it= fn(x) { + if (x < 2){ + return x; + } + let iter = fn (i, table) { + if (i > x) { + return table[x]; + } else { + let table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, table); + } + }; + return iter(2, [0,1]); + }; -let fib = fibonacci_it(20); + let fib = fibonacci_it(20); -puts(fib);"#; + puts(fib);"#; - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - let mut formatter = Formatter::new(program); - let formatted = formatter.format(); + let formatted = format(input); let expected = r#"let fibonacci_it = fn (x) { if (x < 2) { @@ -406,25 +388,20 @@ puts(fib); #[test] fn format_implicit_return() { let input = r#" -let fibonacci = fn(x) { - if (x < 2) { - x - } - else{ - fibonacci(x - 1) + fibonacci(x - 2) - } -} - + let fibonacci = fn(x) { + if (x < 2) { + x + } + else{ + fibonacci(x - 1) + fibonacci(x - 2) + } + } -puts(fibonacci(30)); - "#; - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - let mut formatter = Formatter::new(program); - let formatted = formatter.format(); + puts(fibonacci(30)); + "#; + let formatted = format(input); let expected = r#"let fibonacci = fn (x) { if (x < 2) { x @@ -438,4 +415,12 @@ puts(fibonacci(30)); assert_eq!(formatted, expected); } + + fn format(input: &str) -> String { + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + let mut formatter = Formatter::new(program); + formatter.format() + } } From 9f0ebea860c02c8d0e25955e1bf32218e1cc07b7 Mon Sep 17 00:00:00 2001 From: Yago Date: Thu, 3 Aug 2023 23:38:48 +0200 Subject: [PATCH 04/18] Refactor precedence_of and update parser tests --- crates/formatter/src/formatter.rs | 8 ++++---- crates/parser/src/ast.rs | 20 +++++++++++--------- crates/parser/src/parser.rs | 17 ++++++++--------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs index 9238807..5c748c0 100644 --- a/crates/formatter/src/formatter.rs +++ b/crates/formatter/src/formatter.rs @@ -1,4 +1,4 @@ -use parser::ast::{precedence_of, BlockStatement, Expression, Precedence, Program, Statement}; +use parser::ast::{ BlockStatement, Expression, Precedence, Program, Statement}; pub struct Formatter { /// The current indentation level. @@ -93,7 +93,7 @@ impl Formatter { needs_parenthesis = true; } Expression::Infix(last_infix) => { - if precedence_of(&last_infix.token) > precedence_of(&infix.token) { + if Precedence::from(&last_infix.token) > Precedence::from(&infix.token) { self.push("("); needs_parenthesis = true; } @@ -204,8 +204,8 @@ impl Formatter { fn get_precedence(&self, exp: &Expression) -> Precedence { match exp { - Expression::Infix(infix) => precedence_of(&infix.token), - Expression::Prefix(prefix) => precedence_of(&prefix.token), + Expression::Infix(infix) => Precedence::from(&infix.token), + Expression::Prefix(prefix) => Precedence::from(&prefix.token), _ => Precedence::Lowest, } } diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index 35677ca..040aa51 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -585,15 +585,17 @@ pub enum Precedence { Index = 7, // array[index] } -pub fn precedence_of(token: &Token) -> Precedence { - match token { - Token::Equal | Token::NotEqual => Precedence::Equals, - Token::LT | Token::GT | Token::LTE | Token::GTE => Precedence::LessGreater, - Token::Plus | Token::Minus | Token::Or => Precedence::Sum, - Token::Slash | Token::Asterisk | Token::And => Precedence::Product, - Token::LParen => Precedence::Call, - Token::LSquare => Precedence::Index, - _ => Precedence::Lowest, +impl From<&Token> for Precedence { + fn from(value: &Token) -> Self { + match value { + Token::Equal | Token::NotEqual => Precedence::Equals, + Token::LT | Token::GT | Token::LTE | Token::GTE => Precedence::LessGreater, + Token::Plus | Token::Minus | Token::Or => Precedence::Sum, + Token::Slash | Token::Asterisk | Token::And => Precedence::Product, + Token::LParen => Precedence::Call, + Token::LSquare => Precedence::Index, + _ => Precedence::Lowest, + } } } diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs index be55d5b..bba99f5 100644 --- a/crates/parser/src/parser.rs +++ b/crates/parser/src/parser.rs @@ -1,6 +1,5 @@ use crate::ast::{ - precedence_of, Expression, Identifier, LetStatement, Precedence, Program, ReturnStatement, - Statement, + Expression, Identifier, LetStatement, Precedence, Program, ReturnStatement, Statement, }; use lexer::{lexer::Lexer, token::Token}; use std::{ @@ -210,11 +209,11 @@ impl Parser { } pub fn peek_precedence(&mut self) -> Precedence { - precedence_of(&self.peek_token) + Precedence::from(&self.peek_token) } pub fn current_precedence(&mut self) -> Precedence { - precedence_of(&self.current_token) + Precedence::from(&self.current_token) } fn push_error(&mut self, message: String) { @@ -653,7 +652,7 @@ mod tests { fn test_parsing_index_expression_string_conversion() { let tests = vec![ ("myArray[1]", "myArray", "1"), - ("myArray[\"hello\"]", "myArray", "hello"), + ("myArray[\"hello\"]", "myArray", "\"hello\""), ("[1,2,3,4][2]", "[1, 2, 3, 4]", "2"), ("test()[call()]", "test()", "call()"), ]; @@ -723,9 +722,9 @@ mod tests { Expression::HashMapLiteral(h) => { assert_eq!(h.pairs.len(), 3); let expected = vec![ - ("one", "(1 + 34)"), - ("two", "(2 / 5)"), - ("three", "(3 - 1)"), + ("\"one\"", "(1 + 34)"), + ("\"two\"", "(2 / 5)"), + ("\"three\"", "(3 - 1)"), ]; for (i, (key, value)) in expected.iter().enumerate() { let pair = h.pairs.get(i).unwrap(); @@ -750,7 +749,7 @@ mod tests { Statement::Expression(exp) => match exp { Expression::HashMapLiteral(h) => { assert_eq!(h.pairs.len(), 3); - let expected = vec![("1", "true"), ("2", "Hi"), ("three", "(3 - 1)")]; + let expected = vec![("1", "true"), ("2", "\"Hi\""), ("\"three\"", "(3 - 1)")]; for (i, (key, value)) in expected.iter().enumerate() { let pair = h.pairs.get(i).unwrap(); assert_eq!(pair.0.to_string(), **key); From 98f69cb5ef091528fdd0b520c5ed18d64579b547 Mon Sep 17 00:00:00 2001 From: Yago Date: Fri, 4 Aug 2023 00:14:28 +0200 Subject: [PATCH 05/18] Small refactor formatter struct --- crates/formatter/src/formatter.rs | 43 ++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs index 5c748c0..83e131c 100644 --- a/crates/formatter/src/formatter.rs +++ b/crates/formatter/src/formatter.rs @@ -1,4 +1,4 @@ -use parser::ast::{ BlockStatement, Expression, Precedence, Program, Statement}; +use parser::ast::{BlockStatement, Expression, Precedence, Program, Statement}; pub struct Formatter { /// The current indentation level. @@ -7,7 +7,7 @@ pub struct Formatter { /// Current precedence. preference: Precedence, - /// Indicates if the current expression is nested. + /// Indicates if the current expression is inside a function definition. is_inside_function: bool, /// Previos expression on the ast @@ -15,26 +15,24 @@ pub struct Formatter { /// The output buffer. output: String, - - /// Program to format. - program: Program, } impl Formatter { - pub fn new(program: Program) -> Self { + fn new() -> Self { Self { indent: 0, preference: Precedence::Lowest, is_inside_function: false, last_expression: None, output: String::new(), - program, } } - pub fn format(&mut self) -> String { - self.visit_program(self.program.clone()); - self.output.clone() + pub fn format(program: Program) -> String { + let mut formatter = Self::new(); + + formatter.visit_program(program); + formatter.output.clone() } fn visit_program(&mut self, program: Program) { @@ -93,7 +91,8 @@ impl Formatter { needs_parenthesis = true; } Expression::Infix(last_infix) => { - if Precedence::from(&last_infix.token) > Precedence::from(&infix.token) { + if Precedence::from(&last_infix.token) > Precedence::from(&infix.token) + { self.push("("); needs_parenthesis = true; } @@ -122,11 +121,12 @@ impl Formatter { self.visit_expression(&if_exp.condition); self.push(") {"); self.push("\n"); - self.indent += 1; + self.indent += 1; self.last_expression = Some(exp.clone()); self.visit_block_statement(&if_exp.consequence); self.indent -= 1; + self.push_indent(); self.push("}"); if let Some(alternative) = &if_exp.alternative { @@ -151,12 +151,10 @@ impl Formatter { self.push(") {"); self.push("\n"); - self.is_inside_function = true; - self.indent += 1; + self.enter_function(); self.last_expression = Some(exp.clone()); self.visit_block_statement(&func.body); - self.indent -= 1; - self.is_inside_function = false; + self.leave_function(); self.push_indent(); self.push("}"); @@ -210,6 +208,16 @@ impl Formatter { } } + fn enter_function(&mut self) { + self.is_inside_function = true; + self.indent += 1; + } + + fn leave_function(&mut self) { + self.indent -= 1; + self.is_inside_function = false; + } + fn push(&mut self, s: &str) { self.output.push_str(s); } @@ -420,7 +428,6 @@ puts(fibonacci(30)); let lexer = Lexer::new(input); let mut parser = Parser::new(lexer); let program = parser.parse_program(); - let mut formatter = Formatter::new(program); - formatter.format() + Formatter::format(program) } } From e48924030eed6b702c67407c2ca8d07211c2bd2d Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 8 Aug 2023 12:49:24 +0200 Subject: [PATCH 06/18] Introduction of formatter function scopes. This technique is used to allow the formatter to correctly handle the semicolons inside functions. In particular, the formatter will now correctly put a semicolon or not at the end of a function due to implicit return. --- crates/formatter/src/formatter.rs | 204 +++++++++++++++++++++++++----- 1 file changed, 169 insertions(+), 35 deletions(-) diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs index 83e131c..e5d6d04 100644 --- a/crates/formatter/src/formatter.rs +++ b/crates/formatter/src/formatter.rs @@ -1,4 +1,38 @@ -use parser::ast::{BlockStatement, Expression, Precedence, Program, Statement}; +use parser::ast::{BlockStatement, Expression, FunctionLiteral, Precedence, Program, Statement}; + +/// A formatter function scope. +/// +/// This is used to keep track of the current function being formatted. +/// In particular it is used to determine if a semicolon should be added +/// to the end of a statement, due to implicit return rules. +#[derive(Debug, Clone)] +struct FormatterFunctionScope { + outer: Option>, + block_statement_length: usize, + current_position: usize, +} + +impl FormatterFunctionScope { + fn new(outer: Option>, block_statement_length: usize) -> Self { + Self { + outer, + block_statement_length, + current_position: 0, + } + } + + fn leave_scope(&mut self) -> Option> { + self.outer.take() + } + + fn next(&mut self) { + self.current_position += 1; + } + + fn is_end(&self) -> bool { + self.current_position == self.block_statement_length - 1 + } +} pub struct Formatter { /// The current indentation level. @@ -7,12 +41,12 @@ pub struct Formatter { /// Current precedence. preference: Precedence, - /// Indicates if the current expression is inside a function definition. - is_inside_function: bool, - - /// Previos expression on the ast + /// Previous expression on the ast last_expression: Option, + /// The current formatter function scope. + formatter_function_scope: Option>, + /// The output buffer. output: String, } @@ -22,8 +56,8 @@ impl Formatter { Self { indent: 0, preference: Precedence::Lowest, - is_inside_function: false, last_expression: None, + formatter_function_scope: None, output: String::new(), } } @@ -59,7 +93,11 @@ impl Formatter { Statement::Expression(exp_stmt) => { self.visit_expression(&exp_stmt); if let Some(Expression::Conditional(_)) = self.last_expression { - } else if !self.is_inside_function { + } else if self.formatter_function_scope.is_some() { + if !self.formatter_function_scope.clone().unwrap().is_end() { + self.push(";"); + } + } else { self.push(";"); } } @@ -140,25 +178,7 @@ impl Formatter { self.push("}"); } } - Expression::FunctionLiteral(func) => { - self.push("fn ("); - let parameters = func - .parameters - .iter() - .map(ToString::to_string) - .collect::>(); - self.push(parameters.join(", ").as_str()); - self.push(") {"); - self.push("\n"); - - self.enter_function(); - self.last_expression = Some(exp.clone()); - self.visit_block_statement(&func.body); - self.leave_function(); - - self.push_indent(); - self.push("}"); - } + Expression::FunctionLiteral(func) => self.visit_function_literal(func), Expression::FunctionCall(call) => { self.last_expression = Some(exp.clone()); self.visit_expression(&call.function); @@ -194,6 +214,28 @@ impl Formatter { self.preference = self.get_precedence(exp); } + fn visit_function_literal(&mut self, func: &FunctionLiteral) { + self.push("fn ("); + let parameters = func + .parameters + .iter() + .map(ToString::to_string) + .collect::>(); + self.push(parameters.join(", ").as_str()); + self.push(") {"); + self.push("\n"); + + self.enter_function(func); + for stmt in &func.body.statements { + self.visit_statement(&stmt); + self.formatter_function_scope.as_mut().unwrap().next(); + } + self.leave_function(); + + self.push_indent(); + self.push("}"); + } + fn visit_block_statement(&mut self, block: &BlockStatement) { for stmt in &block.statements { self.visit_statement(&stmt); @@ -208,14 +250,21 @@ impl Formatter { } } - fn enter_function(&mut self) { - self.is_inside_function = true; + fn enter_function(&mut self, function: &FunctionLiteral) { + self.formatter_function_scope = Some(Box::new(FormatterFunctionScope::new( + self.formatter_function_scope.clone(), + function.body.statements.len(), + ))); + self.indent += 1; } fn leave_function(&mut self) { self.indent -= 1; - self.is_inside_function = false; + match self.formatter_function_scope.clone() { + Some(ref mut scope) => self.formatter_function_scope = scope.leave_scope(), + None => {} + } } fn push(&mut self, s: &str) { @@ -388,14 +437,15 @@ let d = !!true; let fib = fibonacci_it(20); puts(fib); "#; - println!("{}", formatted); + println!("{formatted}"); assert_eq!(formatted, expected); } #[test] fn format_implicit_return() { - let input = r#" + let inputs = vec![ + r#" let fibonacci = fn(x) { if (x < 2) { x @@ -407,10 +457,25 @@ puts(fib); puts(fibonacci(30)); - "#; + "#, + r#" + let fibonacci = fn(x) { + puts(x); + if (x < 2) { + x + } + else{ + fibonacci(x - 1) + fibonacci(x - 2) + } + } - let formatted = format(input); - let expected = r#"let fibonacci = fn (x) { + + puts(fibonacci(30)); + "#, + ]; + + let expected_values = vec![ + r#"let fibonacci = fn (x) { if (x < 2) { x } else { @@ -418,8 +483,77 @@ puts(fib); } }; puts(fibonacci(30)); +"#, + r#"let fibonacci = fn (x) { + puts(x); + if (x < 2) { + x + } else { + fibonacci(x - 1) + fibonacci(x - 2) + } +}; +puts(fibonacci(30)); +"#, + ]; + for (input, expected) in inputs.iter().zip(expected_values) { + let formatted = format(input); + println!("{formatted}"); + + assert_eq!(formatted, expected); + } + } + + #[test] + fn format_nested_functions() { + let input = r#" + let counter = fn(x) { + puts(x); + let count = fn(y) { + puts(x + y); + x + y + }; + puts(count(1)); + return count; + }; + let second_counter = fn (x) { + puts(x); + let count = fn(y) { + puts(x + y); + x + y + }; + puts(count(1)); + count + }; + let c = counter(1); + let d = second_counter(2); + puts(c(2)); + "#; + + let formatted = format(input); + + let expected = r#"let counter = fn (x) { + puts(x); + let count = fn (y) { + puts(x + y); + x + y + }; + puts(count(1)); + return count; +}; +let second_counter = fn (x) { + puts(x); + let count = fn (y) { + puts(x + y); + x + y + }; + puts(count(1)); + count +}; +let c = counter(1); +let d = second_counter(2); +puts(c(2)); "#; - println!("{}", formatted); + println!("{formatted}"); assert_eq!(formatted, expected); } From 90dedda9dfb8d4b7d208ca17b7ef63fd331c7973 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 8 Aug 2023 12:54:15 +0200 Subject: [PATCH 07/18] Improve nested functions test --- crates/formatter/src/formatter.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs index e5d6d04..5b8d892 100644 --- a/crates/formatter/src/formatter.rs +++ b/crates/formatter/src/formatter.rs @@ -524,6 +524,17 @@ puts(fibonacci(30)); puts(count(1)); count }; + let third = fn (x) { + let max = fn(y) { + if (x < y) { + y + } + else{ + x + } + }; + count(max(1,x)) + }; let c = counter(1); let d = second_counter(2); puts(c(2)); @@ -549,6 +560,16 @@ let second_counter = fn (x) { puts(count(1)); count }; +let third = fn (x) { + let max = fn (y) { + if (x < y) { + y + } else { + x + } + }; + count(max(1, x)) +}; let c = counter(1); let d = second_counter(2); puts(c(2)); From 124e0cb83cd3235219d203d67a3acf400939aa6f Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 8 Aug 2023 17:02:05 +0200 Subject: [PATCH 08/18] Add integration tests for the formatter These tests make sure that the formatter does not change the semantics of the code. --- Cargo.lock | 1 + Cargo.toml | 1 + crates/formatter/src/formatter.rs | 321 +------------- crates/formatter/src/formatter_tests.rs | 546 ++++++++++++++++++++++++ crates/formatter/src/lib.rs | 1 + lib/lib.rs | 10 +- monkey_examples/fibonacci_it.monkey | 6 +- tests/formatting_integrity.rs | 184 ++++++++ 8 files changed, 755 insertions(+), 315 deletions(-) create mode 100644 crates/formatter/src/formatter_tests.rs create mode 100644 tests/formatting_integrity.rs diff --git a/Cargo.lock b/Cargo.lock index 8a60200..738945d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,6 +474,7 @@ dependencies = [ "clap_derive", "compiler", "criterion", + "formatter", "interpreter", "lexer", "object", diff --git a/Cargo.toml b/Cargo.toml index 055ab47..d7ccb9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ compiler = { path = "./crates/compiler" } vm = { path = "./crates/vm" } repl = { path = "./crates/repl" } object = { path = "./crates/object" } +formatter = { path = "./crates/formatter" } # external crates clap = "4.3.11" diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs index 5b8d892..c00945e 100644 --- a/crates/formatter/src/formatter.rs +++ b/crates/formatter/src/formatter.rs @@ -1,4 +1,7 @@ -use parser::ast::{BlockStatement, Expression, FunctionLiteral, Precedence, Program, Statement}; +use parser::{ + ast::{BlockStatement, Expression, FunctionLiteral, Precedence, Program, Statement}, + parser::parse, +}; /// A formatter function scope. /// @@ -62,7 +65,12 @@ impl Formatter { } } - pub fn format(program: Program) -> String { + pub fn format(input: &str) -> String { + let program = parse(input); + Self::format_program(program) + } + + pub fn format_program(program: Program) -> String { let mut formatter = Self::new(); formatter.visit_program(program); @@ -277,312 +285,3 @@ impl Formatter { } } } - -#[cfg(test)] -mod tests { - use lexer::lexer::Lexer; - use parser::parser::Parser; - - use super::*; - - #[test] - fn test_basic_format() { - let input = r#" - let x = 5; - let y = 10; - let foobar = 838383; - let add = fn(x, y) { - x + y; - }; - let result = add(x, y); - if (5 < 10) { - return true; - } else {return false; - } - "#; - - let formatted = format(input); - let expected = r#"let x = 5; -let y = 10; -let foobar = 838383; -let add = fn (x, y) { - x + y -}; -let result = add(x, y); -if (5 < 10) { - return true; -} else { - return false; -} -"#; - println!("{}", formatted); - assert_eq!(formatted, expected); - } - - #[test] - fn test_format_arithmetic() { - let input = r#" - let x = 5 * 9 + 10; - let z = 5 * (9 + 10); - let y = 10 / 5 - 2; - let yy = 10 / (5 - 2); - let a = 5 * (9 + 10) / 2; - let b = 5 * (9 + 10) / (2 + 3); - let c = (5 * (9 + 10) / (2 + 3)) * 4; - let d = (5 * (9 + 10) / 2 + 3) * 4; - let e = [1, 2, 3, 4, 5][1] * 2 + 3; - let f = {"one": 1, "two": 2}["one"] * 2 + 3; - "#; - let formatted = format(input); - let expected = r#"let x = 5 * 9 + 10; -let z = 5 * (9 + 10); -let y = 10 / 5 - 2; -let yy = 10 / (5 - 2); -let a = 5 * (9 + 10) / 2; -let b = 5 * (9 + 10) / (2 + 3); -let c = 5 * (9 + 10) / (2 + 3) * 4; -let d = (5 * (9 + 10) / 2 + 3) * 4; -let e = [1, 2, 3, 4, 5][1] * 2 + 3; -let f = {"one": 1, "two": 2}["one"] * 2 + 3; -"#; - println!("{}", formatted); - assert_eq!(formatted, expected); - } - - #[test] - fn test_prefix_formatting() { - let input = r#"let x = -5; - let y = !true; - let a = -5 + 10; - let b = !(true == false); - let b = !(true ); - let c = -(5 + 10); - let c = -(-5 + 10); - let c = --(5 + 10); - let c = -(-(5 + 10)); - let c = ---(5 + 10); - let d = !!true; - let d = !(!true); - "#; - - let formatted = format(input); - let expected = r#"let x = -5; -let y = !true; -let a = -5 + 10; -let b = !(true == false); -let b = !true; -let c = -(5 + 10); -let c = -(-5 + 10); -let c = --(5 + 10); -let c = --(5 + 10); -let c = ---(5 + 10); -let d = !!true; -let d = !!true; -"#; - println!("{}", formatted); - assert_eq!(formatted, expected); - } - - #[test] - fn test_string_has_quotes() { - let input = r#"let x = "hello"; -"#; - - let formatted = format(input); - let expected = r#"let x = "hello"; -"#; - - println!("{}", formatted); - assert_eq!(formatted, expected); - } - - #[test] - fn test_fibonacci_it_formatting() { - let input = r#" - let fibonacci_it= fn(x) { - if (x < 2){ - return x; - } - let iter = fn (i, table) { - if (i > x) { - return table[x]; - } else { - let table = push(table, table[i - 1] + table[i - 2]); - return iter(i + 1, table); - } - }; - return iter(2, [0,1]); - }; - - let fib = fibonacci_it(20); - - puts(fib);"#; - - let formatted = format(input); - - let expected = r#"let fibonacci_it = fn (x) { - if (x < 2) { - return x; - } - let iter = fn (i, table) { - if (i > x) { - return table[x]; - } else { - let table = push(table, table[i - 1] + table[i - 2]); - return iter(i + 1, table); - } - }; - return iter(2, [0, 1]); -}; -let fib = fibonacci_it(20); -puts(fib); -"#; - println!("{formatted}"); - - assert_eq!(formatted, expected); - } - - #[test] - fn format_implicit_return() { - let inputs = vec![ - r#" - let fibonacci = fn(x) { - if (x < 2) { - x - } - else{ - fibonacci(x - 1) + fibonacci(x - 2) - } - } - - - puts(fibonacci(30)); - "#, - r#" - let fibonacci = fn(x) { - puts(x); - if (x < 2) { - x - } - else{ - fibonacci(x - 1) + fibonacci(x - 2) - } - } - - - puts(fibonacci(30)); - "#, - ]; - - let expected_values = vec![ - r#"let fibonacci = fn (x) { - if (x < 2) { - x - } else { - fibonacci(x - 1) + fibonacci(x - 2) - } -}; -puts(fibonacci(30)); -"#, - r#"let fibonacci = fn (x) { - puts(x); - if (x < 2) { - x - } else { - fibonacci(x - 1) + fibonacci(x - 2) - } -}; -puts(fibonacci(30)); -"#, - ]; - for (input, expected) in inputs.iter().zip(expected_values) { - let formatted = format(input); - println!("{formatted}"); - - assert_eq!(formatted, expected); - } - } - - #[test] - fn format_nested_functions() { - let input = r#" - let counter = fn(x) { - puts(x); - let count = fn(y) { - puts(x + y); - x + y - }; - puts(count(1)); - return count; - }; - let second_counter = fn (x) { - puts(x); - let count = fn(y) { - puts(x + y); - x + y - }; - puts(count(1)); - count - }; - let third = fn (x) { - let max = fn(y) { - if (x < y) { - y - } - else{ - x - } - }; - count(max(1,x)) - }; - let c = counter(1); - let d = second_counter(2); - puts(c(2)); - "#; - - let formatted = format(input); - - let expected = r#"let counter = fn (x) { - puts(x); - let count = fn (y) { - puts(x + y); - x + y - }; - puts(count(1)); - return count; -}; -let second_counter = fn (x) { - puts(x); - let count = fn (y) { - puts(x + y); - x + y - }; - puts(count(1)); - count -}; -let third = fn (x) { - let max = fn (y) { - if (x < y) { - y - } else { - x - } - }; - count(max(1, x)) -}; -let c = counter(1); -let d = second_counter(2); -puts(c(2)); -"#; - println!("{formatted}"); - - assert_eq!(formatted, expected); - } - - fn format(input: &str) -> String { - let lexer = Lexer::new(input); - let mut parser = Parser::new(lexer); - let program = parser.parse_program(); - Formatter::format(program) - } -} diff --git a/crates/formatter/src/formatter_tests.rs b/crates/formatter/src/formatter_tests.rs new file mode 100644 index 0000000..66ebc55 --- /dev/null +++ b/crates/formatter/src/formatter_tests.rs @@ -0,0 +1,546 @@ +use lexer::lexer::Lexer; +use parser::parser::Parser; + +use crate::formatter::Formatter; + +#[allow(dead_code)] +fn format(input: &str) -> String { + let lexer = Lexer::new(input); + let mut parser = Parser::new(lexer); + let program = parser.parse_program(); + Formatter::format_program(program) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_basic_format() { + let input = r#" + let x = 5; + let y = 10; + let foobar = 838383; + let add = fn(x, y) { + x + y; + }; + let result = add(x, y); + if (5 < 10) { + return true; + } else {return false; + } + "#; + + let formatted = format(input); + let expected = r#"let x = 5; +let y = 10; +let foobar = 838383; +let add = fn (x, y) { + x + y +}; +let result = add(x, y); +if (5 < 10) { + return true; +} else { + return false; +} +"#; + println!("{}", formatted); + assert_eq!(formatted, expected); + } + + #[test] + fn test_format_arithmetic() { + let input = r#" + let x = 5 * 9 + 10; + let z = 5 * (9 + 10); + let y = 10 / 5 - 2; + let yy = 10 / (5 - 2); + let a = 5 * (9 + 10) / 2; + let b = 5 * (9 + 10) / (2 + 3); + let c = (5 * (9 + 10) / (2 + 3)) * 4; + let d = (5 * (9 + 10) / 2 + 3) * 4; + let e = [1, 2, 3, 4, 5][1] * 2 + 3; + let f = {"one": 1, "two": 2}["one"] * 2 + 3; + "#; + let formatted = format(input); + let expected = r#"let x = 5 * 9 + 10; +let z = 5 * (9 + 10); +let y = 10 / 5 - 2; +let yy = 10 / (5 - 2); +let a = 5 * (9 + 10) / 2; +let b = 5 * (9 + 10) / (2 + 3); +let c = 5 * (9 + 10) / (2 + 3) * 4; +let d = (5 * (9 + 10) / 2 + 3) * 4; +let e = [1, 2, 3, 4, 5][1] * 2 + 3; +let f = {"one": 1, "two": 2}["one"] * 2 + 3; +"#; + println!("{}", formatted); + assert_eq!(formatted, expected); + } + + #[test] + fn test_prefix_formatting() { + let input = r#"let x = -5; + let y = !true; + let a = -5 + 10; + let b = !(true == false); + let b = !(true ); + let c = -(5 + 10); + let c = -(-5 + 10); + let c = --(5 + 10); + let c = -(-(5 + 10)); + let c = ---(5 + 10); + let d = !!true; + let d = !(!true); + "#; + + let formatted = format(input); + let expected = r#"let x = -5; +let y = !true; +let a = -5 + 10; +let b = !(true == false); +let b = !true; +let c = -(5 + 10); +let c = -(-5 + 10); +let c = --(5 + 10); +let c = --(5 + 10); +let c = ---(5 + 10); +let d = !!true; +let d = !!true; +"#; + println!("{}", formatted); + assert_eq!(formatted, expected); + } + + #[test] + fn test_string_has_quotes() { + let input = r#"let x = "hello"; +"#; + + let formatted = format(input); + let expected = r#"let x = "hello"; +"#; + + println!("{}", formatted); + assert_eq!(formatted, expected); + } + + #[test] + fn test_fibonacci_it_formatting() { + let input = r#" + let fibonacci_it= fn(x) { + if (x < 2){ + return x; + } + let iter = fn (i, table) { + if (i > x) { + return table[x]; + } else { + let table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, table); + } + }; + return iter(2, [0,1]); + }; + + let fib = fibonacci_it(20); + + puts(fib);"#; + + let formatted = format(input); + + let expected = r#"let fibonacci_it = fn (x) { + if (x < 2) { + return x; + } + let iter = fn (i, table) { + if (i > x) { + return table[x]; + } else { + let table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, table); + } + }; + return iter(2, [0, 1]); +}; +let fib = fibonacci_it(20); +puts(fib); +"#; + println!("{formatted}"); + + assert_eq!(formatted, expected); + } + + #[test] + fn format_implicit_return() { + let inputs = vec![ + r#" + let fibonacci = fn(x) { + if (x < 2) { + x + } + else{ + fibonacci(x - 1) + fibonacci(x - 2) + } + } + + + puts(fibonacci(30)); + "#, + r#" + let fibonacci = fn(x) { + puts(x); + if (x < 2) { + x + } + else{ + fibonacci(x - 1) + fibonacci(x - 2) + } + } + + + puts(fibonacci(30)); + "#, + r#" + let fibonacci = fn(x) { + if (x < 2) { + x + } + else{ + return fibonacci(x - 1) + fibonacci(x - 2); + } + }"#, + ]; + + let expected_values = vec![ + r#"let fibonacci = fn (x) { + if (x < 2) { + x + } else { + fibonacci(x - 1) + fibonacci(x - 2) + } +}; +puts(fibonacci(30)); +"#, + r#"let fibonacci = fn (x) { + puts(x); + if (x < 2) { + x + } else { + fibonacci(x - 1) + fibonacci(x - 2) + } +}; +puts(fibonacci(30)); +"#, + r#"let fibonacci = fn (x) { + if (x < 2) { + x + } else { + return fibonacci(x - 1) + fibonacci(x - 2); + } +}; +"#, + ]; + for (input, expected) in inputs.iter().zip(expected_values) { + let formatted = format(input); + println!("{formatted}"); + + assert_eq!(formatted, expected); + } + } + + #[test] + fn format_nested_functions() { + let input = r#" + let counter = fn(x) { + puts(x); + let count = fn(y) { + puts(x + y); + x + y + }; + puts(count(1)); + return count; + }; + let second_counter = fn (x) { + puts(x); + let count = fn(y) { + puts(x + y); + x + y + }; + puts(count(1)); + count + }; + let third = fn (x) { + let max = fn(y) { + if (x < y) { + y + } + else{ + x + } + }; + count(max(1,x)) + }; + let fourth = fn (x) { + if (x < 2) { + x + } + else{ + let h = fn (y) { + if (x < y) { + y + } + else{ + x + } + }; + h(x - 1) + h(x - 2) + } + }; + let c = counter(1); + let d = second_counter(2); + puts(c(2)); + "#; + + let formatted = format(input); + + let expected = r#"let counter = fn (x) { + puts(x); + let count = fn (y) { + puts(x + y); + x + y + }; + puts(count(1)); + return count; +}; +let second_counter = fn (x) { + puts(x); + let count = fn (y) { + puts(x + y); + x + y + }; + puts(count(1)); + count +}; +let third = fn (x) { + let max = fn (y) { + if (x < y) { + y + } else { + x + } + }; + count(max(1, x)) +}; +let fourth = fn (x) { + if (x < 2) { + x + } else { + let h = fn (y) { + if (x < y) { + y + } else { + x + } + }; + h(x - 1) + h(x - 2) + } +}; +let c = counter(1); +let d = second_counter(2); +puts(c(2)); +"#; + println!("{formatted}"); + + assert_eq!(formatted, expected); + } + + #[test] + fn test_integer_and_variable_declaration() { + let input = r#" + let a = 10; + let b = 20; + "#; + + let expected = r#"let a = 10; +let b = 20; +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_basic_operators() { + let input = r#" + let result = a + b; + let result = a - b; + let result = a * b; + let result = a / b; + let result = a == b; + let result = a != b; + let result = a < b; + let result = a > b; + let result = a <= b; + let result = a >= b; + "#; + + let expected = r#"let result = a + b; +let result = a - b; +let result = a * b; +let result = a / b; +let result = a == b; +let result = a != b; +let result = a < b; +let result = a > b; +let result = a <= b; +let result = a >= b; +"#; + + println!("{}", format(input)); + + assert_eq!(format(input), expected); + } + + #[test] + fn test_array_declaration_and_indexing() { + let input = r#" + let arr = [1, 2, 3]; + let firstElement = arr[0]; + "#; + + let expected = r#"let arr = [1, 2, 3]; +let firstElement = arr[0]; +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_array_builtin_functions() { + let input = r#" let arr = [1, 2, 3]; + let length = len(arr); + let first = first(arr); + let last = last(arr); + let restArr = rest(arr); + let newArr = push(arr, 4); + "#; + + let expected = r#"let arr = [1, 2, 3]; +let length = len(arr); +let first = first(arr); +let last = last(arr); +let restArr = rest(arr); +let newArr = push(arr, 4); +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_hash_declaration_and_indexing() { + let input = r#" + let hash = {"key1": "value1", "key2": 42}; + let value = hash["key1"]; + "#; + + let expected = r#"let hash = {"key1": "value1", "key2": 42}; +let value = hash["key1"]; +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_function_declaration_and_invocation() { + let input = r#" + let add = fn(a, b) { + return a + b; + }; + + let result = add(5, 10); + "#; + + let expected = r#"let add = fn (a, b) { + return a + b; +}; +let result = add(5, 10); +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_if_else_statement() { + let input = r#" + let num = 7; + if (num < 10) { + return "Less than 10"; + } else { + return "10 or greater"; + } + "#; + + let expected = r#"let num = 7; +if (num < 10) { + return "Less than 10"; +} else { + return "10 or greater"; +} +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_function_as_first_class_citizen() { + let input = r#" + let double = fn(x) { + return x * 2; + }; + + let arr = [1, 2, 3, 4]; + let mappedArr = map(arr, double); + "#; + + let expected = r#"let double = fn (x) { + return x * 2; +}; +let arr = [1, 2, 3, 4]; +let mappedArr = map(arr, double); +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_shadowing() { + let input = r#" + let a = 5; + let a = 10; + "#; + + let expected = r#"let a = 5; +let a = 10; +"#; + + assert_eq!(format(input), expected); + } + + #[test] + fn test_built_in_function() { + let input = r#" + puts("Hello, Monkey!"); + let arr = [1, 2, 3]; + let length = len(arr); + "#; + + let expected = r#"puts("Hello, Monkey!"); +let arr = [1, 2, 3]; +let length = len(arr); +"#; + + assert_eq!(format(input), expected); + } +} diff --git a/crates/formatter/src/lib.rs b/crates/formatter/src/lib.rs index 96dc2d9..f73a5e0 100644 --- a/crates/formatter/src/lib.rs +++ b/crates/formatter/src/lib.rs @@ -1 +1,2 @@ pub mod formatter; +mod formatter_tests; diff --git a/lib/lib.rs b/lib/lib.rs index 1deb4d4..f9d00fa 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -1,6 +1,7 @@ use compiler::compiler::Compiler; use interpreter::evaluator::Evaluator; use lexer::lexer::Lexer; +use object::object::Object; use parser::ast::Program; use parser::parser::Parser; use vm::vm::VM; @@ -17,10 +18,11 @@ pub fn compile_program(program: Program) -> Compiler { compiler } -pub fn execute_vm(compiler: &Compiler) { +pub fn execute_vm(compiler: &Compiler) -> Object { let bytecode = compiler.bytecode(); let mut vm = VM::new(bytecode); vm.run().unwrap(); + vm.last_popped_stack_element().unwrap().as_ref().clone() } pub fn execute_interpreter(program: &Program) { @@ -28,3 +30,9 @@ pub fn execute_interpreter(program: &Program) { interpreter.eval(program.clone()); } + +pub fn run_input(input: &str) -> Object { + let program = parse_program(input); + let compiler = compile_program(program.clone()); + execute_vm(&compiler) +} diff --git a/monkey_examples/fibonacci_it.monkey b/monkey_examples/fibonacci_it.monkey index 620ec9e..931b3a7 100644 --- a/monkey_examples/fibonacci_it.monkey +++ b/monkey_examples/fibonacci_it.monkey @@ -4,10 +4,10 @@ let fibonacci_it= fn(x) { } let iter = fn (i, table) { if (i > x) { - return table[x]; + return last(table); } else { - let table = push(table, table[i - 1] + table[i - 2]); - return iter(i + 1, table); + let new_table = push(table, table[i-1] + table[i - 2]); + return iter(i + 1, new_table); } }; return iter(2, [0,1]); diff --git a/tests/formatting_integrity.rs b/tests/formatting_integrity.rs new file mode 100644 index 0000000..d65403f --- /dev/null +++ b/tests/formatting_integrity.rs @@ -0,0 +1,184 @@ +// Test suite to assert that the formatting of the codebase is consistent with +// the source code and that the evaluation of a formatted code is the same as +// the evaluation of the source code. + +use formatter::formatter::Formatter; +use monkey::run_input; + +fn run_test(input: &str) { + let input_evaluation = run_input(input); + + let formatted_input = Formatter::format(input); + + println!("{}", formatted_input); + + let formatted_evaluation = run_input(formatted_input.as_str()); + + println!("{}", formatted_evaluation); + + assert_eq!(input_evaluation, formatted_evaluation); +} + +#[test] +fn test_fibonacci_rec_integrity() { + let input = r#" + let fibonacci = fn(x) { + if (x == 0) { + 0 + } else { + if (x == 1) { + return 1; + } else { + fibonacci(x - 1) + fibonacci(x - 2); + } + } + }; + fibonacci(10); + "#; + + run_test(input); +} + +#[test] +fn test_fibonacci_iter_integrity() { + let input = r#" +let fibonacci_it= fn(x) { + if (x < 2){ + return x; + } + let iter = fn (i, table) { + if (i > x) { + return table[x]; + } else { + let new_table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, new_table); + } + }; + return iter(2, [0,1]); +}; + +let fib = fibonacci_it(20); + +puts(fib); + "#; + + run_test(input); +} + +#[test] +fn test_map_integrity() { + let input = r#" + let map = fn(arr, f) { + let iter = fn(arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + iter(rest(arr), push(accumulated, f(first(arr)))); + } + }; + iter(arr, []); + }; + let a = [1, 2, 3, 4]; + let double = fn(x) { x * 2 }; + map(a, double); + "#; + + run_test(input); +} + +#[test] +fn test_fold_left_integrity() { + let input = r#" + let foldl = fn(arr, initial, f) { + let iter = fn(arr, result) { + if (len(arr) == 0) { + result + } else { + iter(rest(arr), f(result, first(arr))); + } + }; + iter(arr, initial); + }; + let a = [1, 2, 3, 4]; + let sum = fn(x, y) { x + y }; + foldl(a, 0, sum); + "#; + + run_test(input); +} + +#[test] +fn test_fold_right_integrity() { + let input = r#" + let foldr = fn(arr, initial, f) { + let iter = fn(arr, result) { + if (len(arr) == 0) { + result + } else { + iter(rest(arr), f(first(arr), result)); + } + }; + iter(arr, initial); + }; + let a = [1, 2, 3, 4]; + let sum = fn(x, y) { x + y }; + foldr(a, 0, sum); + "#; + + run_test(input); +} + +#[test] +fn test_filter_integrity() { + let input = r#" + let filter = fn(arr, f) { + let iter = fn(arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + let head = first(arr); + let tail = rest(arr); + if (f(head)) { + iter(tail, push(accumulated, head)); + } else { + iter(tail, accumulated); + } + } + }; + iter(arr, []); + }; + let a = [1, 2, 3, 4,5,6,7,8,9,11,100]; + let is_even = fn(x) { (x/2)*2 == x }; + filter(a, is_even); + "#; + + // We use an obscure is_even function because Monkey does not yet support + // the modulo operator. + + run_test(input); +} + +#[test] +fn test_closure_integrity() { + let input = r#" + let new_adder = fn(x) { + fn(y) { x + y }; + }; + let add_two = new_adder(2); + add_two(2); + "#; + + run_test(input); +} + +#[test] +fn test_complex_arithmetic_integrity() { + let input = r#" + let x = (1 + 2) * 3 - 4 / 5 * ((6 + 7) * 8 + 9) - 10; + let y = 1 + 2 * 3 - 4 / 5 * 6 + 7 * 8 + 9 - 10; + let z = 434 - ((((1 + 2) * 3 - 4 / 5 * ((6 + 7) * 8 + 9) - 10) + 1 + 2 * 3 - 4 / 5 * 6 + 7 * 8 + 9 - 10) * 2); + x * (y - z); + "#; + + run_test(input); +} From 4f87792bcd153429e0d734c31e0cd3c84e097287 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 8 Aug 2023 20:50:59 +0200 Subject: [PATCH 09/18] Add binary to format monkey code --- Cargo.lock | 2 + crates/formatter/Cargo.toml | 13 +++ .../ressources/test_formatting.monkey | 19 ++++ crates/formatter/src/bin.rs | 7 ++ crates/formatter/src/formatter/cli.rs | 95 +++++++++++++++++++ .../src/{ => formatter}/formatter.rs | 0 .../src/{ => formatter}/formatter_tests.rs | 2 +- crates/formatter/src/formatter/mod.rs | 3 + crates/formatter/src/lib.rs | 2 +- tests/formatting_integrity.rs | 2 +- 10 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 crates/formatter/ressources/test_formatting.monkey create mode 100644 crates/formatter/src/bin.rs create mode 100644 crates/formatter/src/formatter/cli.rs rename crates/formatter/src/{ => formatter}/formatter.rs (100%) rename crates/formatter/src/{ => formatter}/formatter_tests.rs (99%) create mode 100644 crates/formatter/src/formatter/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 738945d..9c4e578 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,6 +339,8 @@ dependencies = [ name = "formatter" version = "0.1.0" dependencies = [ + "clap", + "clap_derive", "lexer", "parser", ] diff --git a/crates/formatter/Cargo.toml b/crates/formatter/Cargo.toml index 9727d9a..a0e9124 100644 --- a/crates/formatter/Cargo.toml +++ b/crates/formatter/Cargo.toml @@ -5,7 +5,20 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "formatter" +path = "src/lib.rs" + +[[bin]] +name = "formatter" +path = "src/bin.rs" + [dependencies] lexer = { path = "../lexer" } parser = { path = "../parser" } + + +# External crates +clap = "4.3.11" +clap_derive = "4.3.2" diff --git a/crates/formatter/ressources/test_formatting.monkey b/crates/formatter/ressources/test_formatting.monkey new file mode 100644 index 0000000..c675995 --- /dev/null +++ b/crates/formatter/ressources/test_formatting.monkey @@ -0,0 +1,19 @@ +let filter = fn(arr, f) { + let iter = fn(arr, accumulated) { +if (len(arr)==0) { + accumulated + } else { + let head = first(arr); +let tail =rest(arr); + if (f(head)) { + iter(tail, push(accumulated, head)); + } else { + iter(tail, accumulated); + } + } + }; + iter(arr, []); +}; +let a = [1, 2, 3, 4,5,6,7,8,9,11,100]; +let is_even = fn(x) { (x/2)*2 == x }; +filter(a, is_even); diff --git a/crates/formatter/src/bin.rs b/crates/formatter/src/bin.rs new file mode 100644 index 0000000..5bf8462 --- /dev/null +++ b/crates/formatter/src/bin.rs @@ -0,0 +1,7 @@ +use clap::Parser; +use formatter::formatter::cli::Cli; + +fn main() -> Result<(), Box> { + let args = Cli::parse(); + args.run() +} diff --git a/crates/formatter/src/formatter/cli.rs b/crates/formatter/src/formatter/cli.rs new file mode 100644 index 0000000..7405c9f --- /dev/null +++ b/crates/formatter/src/formatter/cli.rs @@ -0,0 +1,95 @@ +use clap_derive::Parser; + +use crate::formatter::formatter::Formatter; + +trait Logger { + fn log(&mut self, msg: &str) -> Result<(), Box>; +} + +struct StdoutLogger; + +impl Logger for StdoutLogger { + fn log(&mut self, msg: &str) -> Result<(), Box> { + println!("{}", msg); + Ok(()) + } +} + +struct FileLogger { + filename: String, +} + +impl Logger for FileLogger { + fn log(&mut self, msg: &str) -> Result<(), Box> { + std::fs::write(&self.filename, msg)?; + Ok(()) + } +} + +#[derive(Parser)] +pub struct Cli { + /// Input file + filename: String, + + /// Indicates if you want to replace the input file + /// with the formatted output + #[clap(short, long, value_name = "replace")] + replace: bool, +} + +impl Cli { + fn get_logger(&self) -> Box { + if self.replace { + Box::new(FileLogger { + filename: self.filename.clone(), + }) + } else { + Box::new(StdoutLogger) + } + } + pub fn run(&self) -> Result<(), Box> { + let mut logger = self.get_logger(); + self.run_with_logger(logger.as_mut()) + } + fn run_with_logger(&self, logger: &mut dyn Logger) -> Result<(), Box> { + let input = std::fs::read_to_string(&self.filename)?; + let output = Formatter::format(&input); + logger.log(&output)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct TestLogger { + pub msg: String, + } + + impl Logger for TestLogger { + fn log(&mut self, msg: &str) -> Result<(), Box> { + self.msg = msg.to_string(); + Ok(()) + } + } + + #[test] + fn test_cli() { + let filename = "ressources/test_formatting.monkey".to_string(); + let input = std::fs::read_to_string(&filename).unwrap(); + + let cli = Cli { + filename, + replace: false, + }; + + let mut logger = TestLogger { + msg: "".to_string(), + }; + + cli.run_with_logger(&mut logger).unwrap(); + + assert_eq!(logger.msg, Formatter::format(&input)); + } +} diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter/formatter.rs similarity index 100% rename from crates/formatter/src/formatter.rs rename to crates/formatter/src/formatter/formatter.rs diff --git a/crates/formatter/src/formatter_tests.rs b/crates/formatter/src/formatter/formatter_tests.rs similarity index 99% rename from crates/formatter/src/formatter_tests.rs rename to crates/formatter/src/formatter/formatter_tests.rs index 66ebc55..f45c05f 100644 --- a/crates/formatter/src/formatter_tests.rs +++ b/crates/formatter/src/formatter/formatter_tests.rs @@ -1,7 +1,7 @@ use lexer::lexer::Lexer; use parser::parser::Parser; -use crate::formatter::Formatter; +use crate::formatter::formatter::Formatter; #[allow(dead_code)] fn format(input: &str) -> String { diff --git a/crates/formatter/src/formatter/mod.rs b/crates/formatter/src/formatter/mod.rs new file mode 100644 index 0000000..9056389 --- /dev/null +++ b/crates/formatter/src/formatter/mod.rs @@ -0,0 +1,3 @@ +pub mod formatter; +mod formatter_tests; +pub mod cli; diff --git a/crates/formatter/src/lib.rs b/crates/formatter/src/lib.rs index f73a5e0..9562899 100644 --- a/crates/formatter/src/lib.rs +++ b/crates/formatter/src/lib.rs @@ -1,2 +1,2 @@ pub mod formatter; -mod formatter_tests; + diff --git a/tests/formatting_integrity.rs b/tests/formatting_integrity.rs index d65403f..52c45a8 100644 --- a/tests/formatting_integrity.rs +++ b/tests/formatting_integrity.rs @@ -2,7 +2,7 @@ // the source code and that the evaluation of a formatted code is the same as // the evaluation of the source code. -use formatter::formatter::Formatter; +use formatter::formatter::formatter::Formatter; use monkey::run_input; fn run_test(input: &str) { From 7034d28d05b1ec3b2ab3437df1e273aab6a9f656 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Tue, 8 Aug 2023 20:52:34 +0200 Subject: [PATCH 10/18] Format examples --- monkey_examples/fibonacci.monkey | 18 +++++++----------- monkey_examples/fibonacci_it.monkey | 28 +++++++++++++--------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/monkey_examples/fibonacci.monkey b/monkey_examples/fibonacci.monkey index 40d4313..a446a63 100644 --- a/monkey_examples/fibonacci.monkey +++ b/monkey_examples/fibonacci.monkey @@ -1,12 +1,8 @@ -let fibonacci = fn(x) { - if (x < 2) { - x - } - else{ - fibonacci(x - 1) + fibonacci(x - 2) - } -} - - +let fibonacci = fn (x) { + if (x < 2) { + x + } else { + fibonacci(x - 1) + fibonacci(x - 2) + } +}; puts(fibonacci(30)); - diff --git a/monkey_examples/fibonacci_it.monkey b/monkey_examples/fibonacci_it.monkey index 931b3a7..2e0814c 100644 --- a/monkey_examples/fibonacci_it.monkey +++ b/monkey_examples/fibonacci_it.monkey @@ -1,18 +1,16 @@ -let fibonacci_it= fn(x) { - if (x < 2){ - return x; - } - let iter = fn (i, table) { - if (i > x) { - return last(table); - } else { - let new_table = push(table, table[i-1] + table[i - 2]); - return iter(i + 1, new_table); - } - }; - return iter(2, [0,1]); +let fibonacci_it = fn (x) { + if (x < 2) { + return x; + } + let iter = fn (i, table) { + if (i > x) { + return last(table); + } else { + let new_table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, new_table); + } + }; + return iter(2, [0, 1]); }; - let fib = fibonacci_it(20); - puts(fib); From c900631359939197593a800fa8093266b16bf510 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Wed, 9 Aug 2023 00:08:09 +0200 Subject: [PATCH 11/18] Bugfix: Array append bench monkey code not working The monkey code for the append bench was just incorrect, I made two mistakes and it did just not parse, which was the reason why the results were more favorable to the interpreter. With the new code whe can see an improvement with the compiler. --- benches/array_bench.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/array_bench.rs b/benches/array_bench.rs index fd8c8b7..d47611c 100644 --- a/benches/array_bench.rs +++ b/benches/array_bench.rs @@ -7,13 +7,13 @@ let push_n = fn(arr, n) { if (n < 0) { arr } else{ - let new_arr = arr.push(n): - push_n(new_arr, n - 1); + let new_arr = push(arr,n); + push_n(new_arr, n - 1) } }; let a = []; -push_n(a, 100); +push_n(a, 500) "#; pub fn array_append_compiler_benchmark(c: &mut Criterion) { From b717bbbc96c701bf60d3e4c43c4f4ffd882201c0 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Wed, 9 Aug 2023 00:14:03 +0200 Subject: [PATCH 12/18] Reorganize formatted crate and binaries. I have now split the binaries in two in the monkey-rs crate. We have `monkey` for the standard monkey REPL and file reader and the `monkeyfmt` that handles formatting. --- README.md | 21 ++++++++++++++---- crates/formatter/src/bin.rs | 7 ------ crates/formatter/src/{formatter => }/cli.rs | 8 +++---- .../src/{formatter => }/formatter.rs | 0 crates/formatter/src/formatter/mod.rs | 3 --- .../src/{formatter => }/formatter_tests.rs | 2 +- crates/formatter/src/lib.rs | 3 ++- crates/repl/src/repl.rs | 22 +++++++++---------- src/{main.rs => bin/monkey.rs} | 4 ++-- src/bin/monkeyfmt.rs | 7 ++++++ tests/formatting_integrity.rs | 2 +- 11 files changed, 45 insertions(+), 34 deletions(-) rename crates/formatter/src/{formatter => }/cli.rs (95%) rename crates/formatter/src/{formatter => }/formatter.rs (100%) delete mode 100644 crates/formatter/src/formatter/mod.rs rename crates/formatter/src/{formatter => }/formatter_tests.rs (99%) rename src/{main.rs => bin/monkey.rs} (63%) create mode 100644 src/bin/monkeyfmt.rs diff --git a/README.md b/README.md index 30b4374..01a61b7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ There are some issues that I want to fix before I can call this implementation c To start the REPL, run the following command: ```bash -cargo run --release +cargo run --release --bin monkey ``` ### File interpreter @@ -24,7 +24,7 @@ cargo run --release To run a Monkey file, run the following command: ```bash -cargo run --release -- +cargo run --release --bin monkey -- ``` ### Other modes @@ -40,7 +40,7 @@ Where `` can be `compiler`, `parser`, `lexer` or `interpreter`. Example: ```bash -cargo run --release -- --mode compiler +cargo run --release --bin monkey -- --mode compiler ``` ### Help @@ -48,9 +48,22 @@ cargo run --release -- --mode compiler To see the help, run the following command: ```bash -cargo run --release -- --help +cargo run --release --bin monkey -- --help ``` +### Formatter + +A monkey formatter is also available, with the binary `monkeyfmt`. I will format any correct piece of monkey code. +To use it you only need to run the following command: + +```bash +cargo run --release --bin monkeyfmt -- +``` + +Adding the `-r` flag after the file name will replace the contents of the file with the +formatted code. If the flag is not activated, the formatted code will be printed to +`stdout`. + ## Monkey syntax ### Types diff --git a/crates/formatter/src/bin.rs b/crates/formatter/src/bin.rs index 5bf8462..e69de29 100644 --- a/crates/formatter/src/bin.rs +++ b/crates/formatter/src/bin.rs @@ -1,7 +0,0 @@ -use clap::Parser; -use formatter::formatter::cli::Cli; - -fn main() -> Result<(), Box> { - let args = Cli::parse(); - args.run() -} diff --git a/crates/formatter/src/formatter/cli.rs b/crates/formatter/src/cli.rs similarity index 95% rename from crates/formatter/src/formatter/cli.rs rename to crates/formatter/src/cli.rs index 7405c9f..15fb105 100644 --- a/crates/formatter/src/formatter/cli.rs +++ b/crates/formatter/src/cli.rs @@ -1,6 +1,6 @@ use clap_derive::Parser; -use crate::formatter::formatter::Formatter; +use crate::formatter::Formatter; trait Logger { fn log(&mut self, msg: &str) -> Result<(), Box>; @@ -27,7 +27,7 @@ impl Logger for FileLogger { } #[derive(Parser)] -pub struct Cli { +pub struct FormatterCli { /// Input file filename: String, @@ -37,7 +37,7 @@ pub struct Cli { replace: bool, } -impl Cli { +impl FormatterCli { fn get_logger(&self) -> Box { if self.replace { Box::new(FileLogger { @@ -79,7 +79,7 @@ mod tests { let filename = "ressources/test_formatting.monkey".to_string(); let input = std::fs::read_to_string(&filename).unwrap(); - let cli = Cli { + let cli = FormatterCli { filename, replace: false, }; diff --git a/crates/formatter/src/formatter/formatter.rs b/crates/formatter/src/formatter.rs similarity index 100% rename from crates/formatter/src/formatter/formatter.rs rename to crates/formatter/src/formatter.rs diff --git a/crates/formatter/src/formatter/mod.rs b/crates/formatter/src/formatter/mod.rs deleted file mode 100644 index 9056389..0000000 --- a/crates/formatter/src/formatter/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod formatter; -mod formatter_tests; -pub mod cli; diff --git a/crates/formatter/src/formatter/formatter_tests.rs b/crates/formatter/src/formatter_tests.rs similarity index 99% rename from crates/formatter/src/formatter/formatter_tests.rs rename to crates/formatter/src/formatter_tests.rs index f45c05f..66ebc55 100644 --- a/crates/formatter/src/formatter/formatter_tests.rs +++ b/crates/formatter/src/formatter_tests.rs @@ -1,7 +1,7 @@ use lexer::lexer::Lexer; use parser::parser::Parser; -use crate::formatter::formatter::Formatter; +use crate::formatter::Formatter; #[allow(dead_code)] fn format(input: &str) -> String { diff --git a/crates/formatter/src/lib.rs b/crates/formatter/src/lib.rs index 9562899..a432c89 100644 --- a/crates/formatter/src/lib.rs +++ b/crates/formatter/src/lib.rs @@ -1,2 +1,3 @@ pub mod formatter; - +pub mod cli; +mod formatter_tests; diff --git a/crates/repl/src/repl.rs b/crates/repl/src/repl.rs index a05802a..d550788 100644 --- a/crates/repl/src/repl.rs +++ b/crates/repl/src/repl.rs @@ -28,7 +28,7 @@ enum Mode { } #[derive(Parser)] -pub struct Cli { +pub struct ReplCli { /// Input file, if not specified, the REPL will be launched filename: Option, @@ -41,7 +41,7 @@ pub struct Cli { logo: bool, } -impl Cli { +impl ReplCli { fn get_input_type(&self) -> InputType { match &self.filename { Some(filename) => InputType::File(filename.to_string()), @@ -69,7 +69,7 @@ impl Cli { fn rlpl(&self) -> Result<(), LexerErrors> { self.greeting_message(); - Cli::print_entry_header(); + ReplCli::print_entry_header(); let mut errors = LexerErrors::new(); std::io::stdin().lines().for_each(|line| { if let Ok(line) = line { @@ -78,7 +78,7 @@ impl Cli { errors.add_errors(err); } } - Cli::print_entry_header(); + ReplCli::print_entry_header(); }); if errors.is_empty() { Ok(()) @@ -89,7 +89,7 @@ impl Cli { pub fn rppl(&self) -> Result<(), ParserErrors> { self.greeting_message(); - Cli::print_entry_header(); + ReplCli::print_entry_header(); let mut errors = ParserErrors::new(); std::io::stdin().lines().for_each(|line| { if let Ok(line) = line { @@ -98,7 +98,7 @@ impl Cli { errors.add_errors(err.errors); } } - Cli::print_entry_header(); + ReplCli::print_entry_header(); }); if errors.is_empty() { @@ -110,7 +110,7 @@ impl Cli { pub fn interpreter(&self) -> Result<(), Box> { self.greeting_message(); - Cli::print_entry_header(); + ReplCli::print_entry_header(); let mut evaluator = Evaluator::new(); for line in std::io::stdin().lines().flatten() { match interpret(&mut evaluator, &line) { @@ -121,14 +121,14 @@ impl Cli { } Err(err) => eprintln!("{err}",), } - Cli::print_entry_header(); + ReplCli::print_entry_header(); } Ok(()) } pub fn compiler(&self) -> Result<(), Box> { self.greeting_message(); - Cli::print_entry_header(); + ReplCli::print_entry_header(); let mut symbol_table = SymbolTable::new(); for (i, builtin) in BuiltinFunction::get_builtins_names().iter().enumerate() { symbol_table.define_builtin(i, builtin.clone()); @@ -179,7 +179,7 @@ impl Cli { Err(err) => eprintln!("{err}",), } - Cli::print_entry_header(); + ReplCli::print_entry_header(); } Ok(()) } @@ -239,7 +239,7 @@ impl Cli { } fn run_file(&self, file_path: &str) -> Result<(), Box> { - let contents = Cli::read_file_contents(file_path)?; + let contents = ReplCli::read_file_contents(file_path)?; match self.get_mode() { Mode::Lexer => lex(&contents)?, diff --git a/src/main.rs b/src/bin/monkey.rs similarity index 63% rename from src/main.rs rename to src/bin/monkey.rs index ac444c6..11c7d3c 100644 --- a/src/main.rs +++ b/src/bin/monkey.rs @@ -1,8 +1,8 @@ use clap::Parser; -use repl::repl::Cli; +use repl::repl::ReplCli; use std::error::Error; fn main() -> Result<(), Box> { - let args = Cli::parse(); + let args = ReplCli::parse(); args.run() } diff --git a/src/bin/monkeyfmt.rs b/src/bin/monkeyfmt.rs new file mode 100644 index 0000000..cc34d45 --- /dev/null +++ b/src/bin/monkeyfmt.rs @@ -0,0 +1,7 @@ +use clap::Parser; +use formatter::cli::FormatterCli; + +fn main() -> Result<(), Box> { + let args = FormatterCli::parse(); + args.run() +} diff --git a/tests/formatting_integrity.rs b/tests/formatting_integrity.rs index 52c45a8..d65403f 100644 --- a/tests/formatting_integrity.rs +++ b/tests/formatting_integrity.rs @@ -2,7 +2,7 @@ // the source code and that the evaluation of a formatted code is the same as // the evaluation of the source code. -use formatter::formatter::formatter::Formatter; +use formatter::formatter::Formatter; use monkey::run_input; fn run_test(input: &str) { From f643da4eb753677ad65ae51b16a9908a746ab4b8 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Wed, 9 Aug 2023 23:56:12 +0200 Subject: [PATCH 13/18] Add more monkey examples (the ones from the tests) --- monkey_examples/filter.monkey | 21 +++++++++++++++++++++ monkey_examples/fold_left.monkey | 15 +++++++++++++++ monkey_examples/fold_right.monkey | 14 ++++++++++++++ monkey_examples/map.monkey | 16 ++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 monkey_examples/filter.monkey create mode 100644 monkey_examples/fold_left.monkey create mode 100644 monkey_examples/fold_right.monkey create mode 100644 monkey_examples/map.monkey diff --git a/monkey_examples/filter.monkey b/monkey_examples/filter.monkey new file mode 100644 index 0000000..ce15105 --- /dev/null +++ b/monkey_examples/filter.monkey @@ -0,0 +1,21 @@ +let filter = fn (arr, f) { + let iter = fn (arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + let head = first(arr); + let tail = rest(arr); + if (f(head)) { + iter(tail, push(accumulated, head)) + } else { + iter(tail, accumulated) + } + } + }; + iter(arr, []) +}; +let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 100]; +let is_even = fn (x) { + x / 2 * 2 == x +}; +filter(a, is_even); diff --git a/monkey_examples/fold_left.monkey b/monkey_examples/fold_left.monkey new file mode 100644 index 0000000..51f9dfb --- /dev/null +++ b/monkey_examples/fold_left.monkey @@ -0,0 +1,15 @@ +let foldl = fn (arr, initial, f) { + let iter = fn (arr, result) { + if (len(arr) == 0) { + result + } else { + iter(rest(arr), f(result, first(arr))) + } + }; + iter(arr, initial) +}; +let a = [1, 2, 3, 4]; +let sum = fn (x, y) { + x + y +}; +foldl(a, 0, sum); diff --git a/monkey_examples/fold_right.monkey b/monkey_examples/fold_right.monkey new file mode 100644 index 0000000..3d26593 --- /dev/null +++ b/monkey_examples/fold_right.monkey @@ -0,0 +1,14 @@ + + let foldr = fn(arr, initial, f) { + let iter = fn(arr, result) { + if (len(arr) == 0) { + result + } else { + iter(rest(arr), f(first(arr), result)); + } + }; + iter(arr, initial); + }; + let a = [1, 2, 3, 4]; + let sum = fn(x, y) { x + y }; + foldr(a, 0, sum); diff --git a/monkey_examples/map.monkey b/monkey_examples/map.monkey new file mode 100644 index 0000000..e4c9298 --- /dev/null +++ b/monkey_examples/map.monkey @@ -0,0 +1,16 @@ +let input = r; +" + let map = fn(arr, f) { + let iter = fn(arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + iter(rest(arr), push(accumulated, f(first(arr)))); + } + }; + iter(arr, []); + }; + let a = [1, 2, 3, 4]; + let double = fn(x) { x * 2 }; + map(a, double); +"; From 630d3b312b5c4b151da801c656fadae48333d81f Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Thu, 10 Aug 2023 00:03:17 +0200 Subject: [PATCH 14/18] Simple lint resolutions --- crates/formatter/Cargo.toml | 8 -------- crates/formatter/src/cli.rs | 4 ++-- crates/formatter/src/formatter.rs | 11 +++++------ crates/formatter/src/formatter_tests.rs | 8 ++++---- run_linter.sh | 2 +- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/crates/formatter/Cargo.toml b/crates/formatter/Cargo.toml index a0e9124..67986c9 100644 --- a/crates/formatter/Cargo.toml +++ b/crates/formatter/Cargo.toml @@ -5,14 +5,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = "formatter" -path = "src/lib.rs" - -[[bin]] -name = "formatter" -path = "src/bin.rs" - [dependencies] lexer = { path = "../lexer" } diff --git a/crates/formatter/src/cli.rs b/crates/formatter/src/cli.rs index 15fb105..eb5be0b 100644 --- a/crates/formatter/src/cli.rs +++ b/crates/formatter/src/cli.rs @@ -10,7 +10,7 @@ struct StdoutLogger; impl Logger for StdoutLogger { fn log(&mut self, msg: &str) -> Result<(), Box> { - println!("{}", msg); + println!("{msg}"); Ok(()) } } @@ -85,7 +85,7 @@ mod tests { }; let mut logger = TestLogger { - msg: "".to_string(), + msg: String::new(), }; cli.run_with_logger(&mut logger).unwrap(); diff --git a/crates/formatter/src/formatter.rs b/crates/formatter/src/formatter.rs index c00945e..32b61b0 100644 --- a/crates/formatter/src/formatter.rs +++ b/crates/formatter/src/formatter.rs @@ -99,7 +99,7 @@ impl Formatter { self.push(";"); } Statement::Expression(exp_stmt) => { - self.visit_expression(&exp_stmt); + self.visit_expression(exp_stmt); if let Some(Expression::Conditional(_)) = self.last_expression { } else if self.formatter_function_scope.is_some() { if !self.formatter_function_scope.clone().unwrap().is_end() { @@ -235,7 +235,7 @@ impl Formatter { self.enter_function(func); for stmt in &func.body.statements { - self.visit_statement(&stmt); + self.visit_statement(stmt); self.formatter_function_scope.as_mut().unwrap().next(); } self.leave_function(); @@ -246,7 +246,7 @@ impl Formatter { fn visit_block_statement(&mut self, block: &BlockStatement) { for stmt in &block.statements { - self.visit_statement(&stmt); + self.visit_statement(stmt); } } @@ -269,9 +269,8 @@ impl Formatter { fn leave_function(&mut self) { self.indent -= 1; - match self.formatter_function_scope.clone() { - Some(ref mut scope) => self.formatter_function_scope = scope.leave_scope(), - None => {} + if let Some(ref mut scope) = self.formatter_function_scope { + self.formatter_function_scope = scope.leave_scope(); } } diff --git a/crates/formatter/src/formatter_tests.rs b/crates/formatter/src/formatter_tests.rs index 66ebc55..58d5ee3 100644 --- a/crates/formatter/src/formatter_tests.rs +++ b/crates/formatter/src/formatter_tests.rs @@ -46,7 +46,7 @@ if (5 < 10) { return false; } "#; - println!("{}", formatted); + println!("{formatted}"); assert_eq!(formatted, expected); } @@ -76,7 +76,7 @@ let d = (5 * (9 + 10) / 2 + 3) * 4; let e = [1, 2, 3, 4, 5][1] * 2 + 3; let f = {"one": 1, "two": 2}["one"] * 2 + 3; "#; - println!("{}", formatted); + println!("{formatted}"); assert_eq!(formatted, expected); } @@ -110,7 +110,7 @@ let c = ---(5 + 10); let d = !!true; let d = !!true; "#; - println!("{}", formatted); + println!("{formatted}"); assert_eq!(formatted, expected); } @@ -123,7 +123,7 @@ let d = !!true; let expected = r#"let x = "hello"; "#; - println!("{}", formatted); + println!("{formatted}"); assert_eq!(formatted, expected); } diff --git a/run_linter.sh b/run_linter.sh index 319d7ed..a424720 100755 --- a/run_linter.sh +++ b/run_linter.sh @@ -1,6 +1,6 @@ #!/bin/bash -ALLOWED_LINTS=("must_use_candidate" "missing-errors-doc" "cast_possible_truncation" "cast_possible_wrap" "missing_panics_doc" "cast_sign_loss" "unused_self") +ALLOWED_LINTS=("must_use_candidate" "missing-errors-doc" "cast_possible_truncation" "cast_possible_wrap" "missing_panics_doc" "cast_sign_loss" "unused_self" "module-name-repetitions") format_lints(){ From aed3aa0b4bdf9d57c70c94045eca832225e51613 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Thu, 10 Aug 2023 15:18:40 +0200 Subject: [PATCH 15/18] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 01a61b7..addb1bc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The Monkey language is a language created by Thorsten Ball for his book [Writing Monkey-rs is an implementation of the Monkey language in Rust. It is based on the books [Writing an Interpreter in Go](https://interpreterbook.com/) and [Writing a Compiler in Go](https://compilerbook.com/). -This implemenattion is still in development. For now an interpreter and a compiler are fully implemented, allowing to run a REPL and to run Monkey files (`.monkey` extension). +This implementation is still in development. For now an interpreter and a compiler are fully implemented, allowing to run a REPL and to run Monkey files (`.monkey` extension). There are some issues that I want to fix before I can call this implementation complete. ### REPL @@ -43,14 +43,6 @@ Example: cargo run --release --bin monkey -- --mode compiler ``` -### Help - -To see the help, run the following command: - -```bash -cargo run --release --bin monkey -- --help -``` - ### Formatter A monkey formatter is also available, with the binary `monkeyfmt`. I will format any correct piece of monkey code. @@ -64,6 +56,14 @@ Adding the `-r` flag after the file name will replace the contents of the file w formatted code. If the flag is not activated, the formatted code will be printed to `stdout`. +### Help + +To see the help, run the following command: + +```bash +cargo run --release --bin monkey -- --help +``` + ## Monkey syntax ### Types From 93aad71fdd8b4271fa1ba6da89df8bd1773da0e2 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Thu, 10 Aug 2023 17:05:51 +0200 Subject: [PATCH 16/18] fix: format forgotten examples --- monkey_examples/fold_right.monkey | 29 +++++++++++++++-------------- monkey_examples/map.monkey | 31 +++++++++++++++---------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/monkey_examples/fold_right.monkey b/monkey_examples/fold_right.monkey index 3d26593..5a7a6e5 100644 --- a/monkey_examples/fold_right.monkey +++ b/monkey_examples/fold_right.monkey @@ -1,14 +1,15 @@ - - let foldr = fn(arr, initial, f) { - let iter = fn(arr, result) { - if (len(arr) == 0) { - result - } else { - iter(rest(arr), f(first(arr), result)); - } - }; - iter(arr, initial); - }; - let a = [1, 2, 3, 4]; - let sum = fn(x, y) { x + y }; - foldr(a, 0, sum); +let foldr = fn (arr, initial, f) { + let iter = fn (arr, result) { + if (len(arr) == 0) { + result + } else { + iter(rest(arr), f(first(arr), result)) + } + }; + iter(arr, initial) +}; +let a = [1, 2, 3, 4]; +let sum = fn (x, y) { + x + y +}; +foldr(a, 0, sum); diff --git a/monkey_examples/map.monkey b/monkey_examples/map.monkey index e4c9298..da5123f 100644 --- a/monkey_examples/map.monkey +++ b/monkey_examples/map.monkey @@ -1,16 +1,15 @@ -let input = r; -" - let map = fn(arr, f) { - let iter = fn(arr, accumulated) { - if (len(arr) == 0) { - accumulated - } else { - iter(rest(arr), push(accumulated, f(first(arr)))); - } - }; - iter(arr, []); - }; - let a = [1, 2, 3, 4]; - let double = fn(x) { x * 2 }; - map(a, double); -"; +let map = fn (arr, f) { + let iter = fn (arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + iter(rest(arr), push(accumulated, f(first(arr)))) + } + }; + iter(arr, []) +}; +let a = [1, 2, 3, 4]; +let double = fn (x) { + x * 2 +}; +map(a, double); From ddc1b7c04a7fac6a407b09fa922a73a558afbe1c Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Thu, 10 Aug 2023 17:13:49 +0200 Subject: [PATCH 17/18] feat: add formatter bench --- Cargo.toml | 7 ++ benches/formatter_bench.rs | 201 ++++++++++++++++++++++++++++++++++++ crates/formatter/src/bin.rs | 0 3 files changed, 208 insertions(+) create mode 100644 benches/formatter_bench.rs delete mode 100644 crates/formatter/src/bin.rs diff --git a/Cargo.toml b/Cargo.toml index d7ccb9a..295aae8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,11 @@ clap_derive = "4.3.2" criterion = "0.5.1" chrono = "0.4.26" + +[[bench]] +name = "formatter_bench" +harness = false + [[bench]] name = "fibonacci_bench" harness = false @@ -43,3 +48,5 @@ harness = false name = "array_bench" harness = false + + diff --git a/benches/formatter_bench.rs b/benches/formatter_bench.rs new file mode 100644 index 0000000..b6e2b70 --- /dev/null +++ b/benches/formatter_bench.rs @@ -0,0 +1,201 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +use formatter::formatter::Formatter; + +pub fn long_formatter_benchmark(c: &mut Criterion) { + let input = r#" +let fibonacci_it = fn (x) { + if (x < 2) { + return x; + + + + } + let iter = fn (i, table) { + + if (i > x) { + return last(table); + } else { + let new_table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, new_table); + } + }; + return iter(2, [0, 1]); +}; + let fib = fibonacci_it(20); +puts (fib); +let fibonacci = fn (x) { + if (x < 2) { + x + } else { + fibonacci(x - 1) + fibonacci(x - 2) + } +}; + + + puts(fibonacci(3)); +let filter = fn (arr, f) { + let iter = fn (arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + let head = first(arr); + let tail = rest(arr); + if (f(head)) { + iter(tail, push(accumulated, head)) + } else { + iter(tail, accumulated) + } + } + }; + iter(arr, []) +}; +let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 100]; +let is_even = fn (x) { + x / 2 * 2 == x +}; +filter(a, is_even); +let foldl = fn (arr, initial , f) { + let iter = fn (arr, result) { + if (len(arr) == 0) { + + + + result; + } else { + iter(rest(arr), f(result, first(arr))) + } + }; + iter(arr, initial) +}; +let a = [1, 2, 3, 4]; +let sum = fn (x, y) { + x + y +}; +foldl(a, 0, sum); + + let foldr = fn(arr, initial, f) { + let iter = fn(arr, result) { + if (len(arr) == 0) { + result + } else { + iter(rest(arr), f(first(arr), result)); + } + }; + iter(arr, initial); + }; + let a = [1, 2, 3, 4]; + let sum = fn(x, y) { x + y }; + foldr(a, 0, sum); +let input = r; +" + let map = fn(arr, f) { + let iter = fn(arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + iter(rest(arr), push(accumulated, f(first(arr)))); + } + }; + iter(arr, []); + }; + let a = [1, 2, 3, 4]; + let double = fn(x) { x * 2 }; + map(a, double); +"; +~/Projects/monkey-rs 3-Formatter *2 !1 ?1 ❯ cat monkey_examples/* 17:00:36 +let fibonacci_it = fn (x) { + if (x < 2) { + return x; + } + let iter = fn (i, table) { + if (i > x) { + return last(table); + } else { + let new_table = push(table, table[i - 1] + table[i - 2]); + return iter(i + 1, new_table); + } + }; + return iter(2, [0, 1]); +}; +let fib = fibonacci_it(20); +puts(fib); +let fibonacci = fn (x) { + if (x < 2) { + x + } else { + fibonacci(x - 1) + fibonacci(x - 2) + } +}; +puts(fibonacci(30)); +let filter = fn (arr, f) { + let iter = fn (arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + let head = first(arr); + let tail = rest(arr); + if (f(head)) { + iter(tail, push(accumulated, head)) + } else { + iter(tail, accumulated) + } + } + }; + iter(arr, []) +}; +let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 100]; +let is_even = fn (x) { + x / 2 * 2 == x +}; +filter(a, is_even); +let foldl = fn (arr, initial, f) { + let iter = fn (arr, result) { + if (len(arr) == 0) { + result + } else { + iter(rest(arr), f(result, first(arr))) + } + }; + iter(arr, initial) +}; +let a = [1, 2, 3, 4]; +let sum = fn (x, y) { +x + y +}; +foldl(a, 0, sum); +let foldr = fn(arr, initial, f) { +let iter = fn(arr, result) { +if (len(arr) == 0) { +result +} else { +iter(rest(arr), f(first(arr), result)); +} +}; +iter(arr, initial); +}; +let a = [1, 2, 3, 4]; +let sum = fn(x, y) { x + y }; +foldr(a, 0, sum); +let map = fn(arr, f) {let iter = fn(arr, accumulated) { + if (len(arr) == 0) { + accumulated + } else { + iter(rest(arr), push(accumulated, f(first(arr)))); + } + }; + iter(arr, []); + }; + let a = [1, 2, 3, 4]; + let double = fn(x) { x * 2 }; + map(a, double); + + "#; + + c.bench_function("Long format", |b| { + b.iter(|| Formatter::format(black_box(input))) + }); +} + +criterion_group!(benches, long_formatter_benchmark); +criterion_main!(benches); diff --git a/crates/formatter/src/bin.rs b/crates/formatter/src/bin.rs deleted file mode 100644 index e69de29..0000000 From 8ae10aefb7dc764b17bb67094f2324cca89eff82 Mon Sep 17 00:00:00 2001 From: Yago Iglesias Date: Thu, 10 Aug 2023 17:22:16 +0200 Subject: [PATCH 18/18] fix: format monkey code on array bench --- benches/array_bench.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/benches/array_bench.rs b/benches/array_bench.rs index d47611c..e7409e9 100644 --- a/benches/array_bench.rs +++ b/benches/array_bench.rs @@ -3,17 +3,16 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use monkey::{compile_program, execute_interpreter, execute_vm, parse_program}; const ARRAY_APPEND: &str = r#" -let push_n = fn(arr, n) { +let push_n = fn (arr, n) { if (n < 0) { arr - } else{ - let new_arr = push(arr,n); + } else { + let new_arr = push(arr, n); push_n(new_arr, n - 1) } }; - let a = []; -push_n(a, 500) +push_n(a, 500); "#; pub fn array_append_compiler_benchmark(c: &mut Criterion) {