diff --git a/Cargo.lock b/Cargo.lock index db83054..358bf6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,12 +203,12 @@ name = "gml_fmt" version = "0.1.0" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gml_fmt_lib 1.0.1", + "gml_fmt_lib 1.0.2", ] [[package]] name = "gml_fmt_lib" -version = "1.0.1" +version = "1.0.2" dependencies = [ "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/gml_fmt_lib/Cargo.toml b/gml_fmt_lib/Cargo.toml index 56d0c61..a74ebd4 100644 --- a/gml_fmt_lib/Cargo.toml +++ b/gml_fmt_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gml_fmt_lib" -version = "1.0.1" +version = "1.0.2" authors = ["Jack Spira "] edition = "2018" diff --git a/gml_fmt_lib/src/expressions.rs b/gml_fmt_lib/src/expressions.rs index d5ed3b6..3beb9f7 100644 --- a/gml_fmt_lib/src/expressions.rs +++ b/gml_fmt_lib/src/expressions.rs @@ -17,6 +17,17 @@ pub enum Expr<'a> { comments_and_newlines_after_lparen: CommentsAndNewlines<'a>, arguments: DelimitedLines<'a, ExprBox<'a>>, }, + Function { + comments_after_control_word: CommentsAndNewlines<'a>, + call: ExprBox<'a>, + comments_after_rparen: CommentsAndNewlines<'a>, + is_constructor: bool, + }, + StructOperator { + token: Token<'a>, + comments_before_expression: CommentsAndNewlines<'a>, + expression: ExprBox<'a>, + }, Binary { left: ExprBox<'a>, operator: Token<'a>, diff --git a/gml_fmt_lib/src/lex_token.rs b/gml_fmt_lib/src/lex_token.rs index ca5658a..04dd11f 100644 --- a/gml_fmt_lib/src/lex_token.rs +++ b/gml_fmt_lib/src/lex_token.rs @@ -65,6 +65,10 @@ pub enum TokenType<'a> { GlobalVar, If, Else, + Function, + Constructor, + New, + Delete, Return, For, Repeat, diff --git a/gml_fmt_lib/src/parser.rs b/gml_fmt_lib/src/parser.rs index 5ebd3ff..2c5c7ff 100644 --- a/gml_fmt_lib/src/parser.rs +++ b/gml_fmt_lib/src/parser.rs @@ -481,7 +481,10 @@ impl<'a> Parser<'a> { }; let has_semicolon = self.check_next_consume(TokenType::Semicolon); - Ok(StatementWrapper::new(Statement::Return { expression }, has_semicolon)) + Ok(StatementWrapper::new( + Statement::Return { expression }, + has_semicolon, + )) } fn break_statement(&mut self) -> AnyResult> { @@ -534,9 +537,46 @@ impl<'a> Parser<'a> { Ok(ret) } + fn function_declaration(&mut self) -> AnyResult> { + let comments_after_control_word = self.get_newlines_and_comments(); + let call = self.expression()?; + let comments_after_rparen = self.get_newlines_and_comments(); + let is_constructor = self.check_next_consume(TokenType::Constructor); + + Ok(self.create_comment_expr_box(Expr::Function { + comments_after_control_word, + call, + comments_after_rparen, + is_constructor, + })) + } + + fn struct_operation(&mut self, token: Token<'a>) -> AnyResult> { + let comments_before_expression = self.get_newlines_and_comments(); + let expression = self.expression()?; + + Ok(self.create_comment_expr_box(Expr::StructOperator { + token, + comments_before_expression, + expression, + })) + } + fn assignment(&mut self) -> AnyResult> { let mut expr = self.ternary()?; + if let Expr::UnidentifiedAsLiteral { literal_token } = expr.expr { + match literal_token.token_type { + TokenType::Function => { + expr = self.function_declaration()?; + } + TokenType::New | TokenType::Delete => { + expr = self.struct_operation(literal_token)?; + } + _ => {} + } + } + if self.can_pair { if let Some(token) = self.scanner.peek() { match token.token_type { @@ -551,6 +591,7 @@ impl<'a> Parser<'a> { | TokenType::ModEquals => { let operator = self.scanner.next().unwrap(); let comments_and_newlines_between_op_and_r = self.get_newlines_and_comments(); + let assignment_expr = self.assignment()?; expr = self.create_expr_box_no_comment(Expr::Assign { @@ -560,7 +601,6 @@ impl<'a> Parser<'a> { right: assignment_expr, }); } - _ => {} } } @@ -1032,7 +1072,10 @@ impl<'a> Parser<'a> { let trailing_comment = self.get_newlines_and_comments(); - arguments.push(DelimitedLine { expr, trailing_comment }); + arguments.push(DelimitedLine { + expr, + trailing_comment, + }); if do_break { end_delimiter = false; diff --git a/gml_fmt_lib/src/printer.rs b/gml_fmt_lib/src/printer.rs index ed8e510..6619a9e 100644 --- a/gml_fmt_lib/src/printer.rs +++ b/gml_fmt_lib/src/printer.rs @@ -105,6 +105,7 @@ impl<'a> Printer<'a> { // let mut forced_semicolon_already = false; let mut indented_vars = already_started_indent; let mut iter = var_decl_list.lines.iter().peekable(); + let mut interrupt_eol_formatting = false; while let Some(delimited_var) = iter.next() { if let Some(var_token) = &delimited_var.expr.say_var { self.print_token(&var_token, true); @@ -140,7 +141,11 @@ impl<'a> Printer<'a> { if var_decl_list.has_end_delimiter { self.print(COMMA, true); } else { - self.print(SEMICOLON, true); + interrupt_eol_formatting = self.do_not_need_semicolon.len() > 0; + + if interrupt_eol_formatting == false { + self.print(SEMICOLON, true); + } } }; @@ -175,7 +180,7 @@ impl<'a> Printer<'a> { } } - if self.on_whitespace_line() == false && self.in_a_for_loop.is_empty() { + if !interrupt_eol_formatting && self.on_whitespace_line() == false && self.in_a_for_loop.is_empty() { self.print_newline(IndentationMove::Stay); self.do_not_print_single_newline_statement = true; } @@ -284,15 +289,20 @@ impl<'a> Printer<'a> { if must_indent && did_move == false { self.print_newline(IndentationMove::Right); } - let did_newline = did_move || must_indent; if did_newline == false { self.ensure_space(); } + // don't worry about semicolon or newline if only one statement + if statements.len() == 1 { + self.do_not_need_semicolon.push(()); + } + for stmt in statements { self.print_statement(stmt); - if did_newline { + + if did_newline & stmt.has_semicolon { if self.on_whitespace_line() == false { self.print_newline(IndentationMove::Stay); self.do_not_print_single_newline_statement = true; @@ -511,6 +521,7 @@ impl<'a> Printer<'a> { if let Some(expression) = expression { self.print(SPACE, false); + self.print_expr(expression); } self.print_semicolon_and_newline(stmt.has_semicolon, IndentationMove::Stay); @@ -670,6 +681,13 @@ impl<'a> Printer<'a> { comments_and_newlines_after_lparen, arguments, } => { + // For variable functions + if let Expr::UnidentifiedAsLiteral { literal_token } = procedure_name.expr { + if literal_token.token_type == TokenType::Function { + self.do_not_need_semicolon.push(()); + } + } + self.print_expr(procedure_name); self.backspace(); @@ -679,12 +697,72 @@ impl<'a> Printer<'a> { CommentAndNewlinesInstruction::new_respect_users(IndentationMove::Right, LeadingNewlines::One), ); + // for passing lambdas as arguments + let mut is_function = false; + let mut iter = arguments.lines.iter().peekable(); + + while let Some(delimited_line) = iter.next() { + if let Expr::Call { procedure_name, .. } = &delimited_line.expr.expr { + if let Expr::UnidentifiedAsLiteral { literal_token } = procedure_name.expr { + is_function = literal_token.token_type == TokenType::Function; + } + } + } + self.print_delimited_lines(arguments, COMMA, false, false); self.backspace_whitespace(); + if did_move { self.print_newline(IndentationMove::Left); } - self.print(RPAREN, true); + + if !is_function { + self.print(RPAREN, true); + } else { + self.block_instructions.push(BlockInstruction::NO_NEWLINE_AFTER_BLOCK); + } + } + + Expr::Function { + comments_after_control_word, + call, + comments_after_rparen, + is_constructor, + } => { + self.print("function", true); + self.print_comments_and_newlines( + comments_after_control_word, + CommentAndNewlinesInstruction::new(IndentationMove::Stay, LeadingNewlines::One), + ); + self.print_expr(call); + if !*is_constructor { + self.backspace_whitespace(); + } + + self.print_comments_and_newlines( + comments_after_rparen, + CommentAndNewlinesInstruction::new(IndentationMove::Stay, LeadingNewlines::One), + ); + + if *is_constructor { + self.print("constructor", true); + } + + self.do_not_need_semicolon.push(()); + } + + Expr::StructOperator { + token, + comments_before_expression, + expression, + } => { + self.print_token(token, true); + self.print_comments_and_newlines( + comments_before_expression, + CommentAndNewlinesInstruction::new(IndentationMove::Stay, LeadingNewlines::One), + ); + self.print_expr(expression); + self.backspace_whitespace(); } Expr::Binary { @@ -831,6 +909,7 @@ impl<'a> Printer<'a> { } => { self.print_expr(left); self.print_token(&operator, true); + self.print_comments_and_newlines( comments_and_newlines_between_op_and_r, CommentAndNewlinesInstruction::new(IndentationMove::Stay, LeadingNewlines::All), @@ -957,6 +1036,14 @@ impl<'a> Printer<'a> { Expr::UnidentifiedAsLiteral { literal_token } => { self.print_token(&literal_token, true); + + match literal_token.token_type { + TokenType::Constructor => { + self.do_not_need_semicolon.push(()); + } + _ => {} + } + } } @@ -1338,6 +1425,10 @@ impl<'a> Printer<'a> { TokenType::Else => "else", TokenType::Return => "return", TokenType::For => "for", + TokenType::Function => "function", + TokenType::Constructor => "constructor", + TokenType::New => "new", + TokenType::Delete => "delete", TokenType::Repeat => "repeat", TokenType::While => "while", TokenType::With => "with", diff --git a/gml_fmt_lib/src/scanner.rs b/gml_fmt_lib/src/scanner.rs index cf03cac..d46f0f5 100644 --- a/gml_fmt_lib/src/scanner.rs +++ b/gml_fmt_lib/src/scanner.rs @@ -12,6 +12,10 @@ static KEYWORD_MAP: Lazy> = Lazy::new(|| { map.insert("not", TokenType::NotAlias); map.insert("if", TokenType::If); map.insert("else", TokenType::Else); + map.insert("function", TokenType::Function); + map.insert("constructor", TokenType::Constructor); + map.insert("new", TokenType::New); + map.insert("delete", TokenType::Delete); map.insert("return", TokenType::Return); map.insert("for", TokenType::For); map.insert("repeat", TokenType::Repeat); @@ -585,7 +589,12 @@ impl<'a> Scanner<'a> { }; } - self.column_number += (current - last_column_break) as u32; + // TODO: Figure out what the bleeding hecc is going on here. Broke with function formatting. + if current > last_column_break { // TODO: Find good test for this edge case. + self.column_number += (current - last_column_break) as u32; + } else { + self.column_number = current as u32; + } Token::new( TokenType::MultilineComment(&self.input[start..current]), start_line, @@ -940,7 +949,7 @@ testCase"; #[test] fn lex_reserved_keywords<'a>() { - let input_string = "var and or if else return for repeat while do until switch case default div break enum"; + let input_string = "var and or if else return for repeat while do until switch case default div break enum function constructor new"; let scanner = Scanner::new(input_string); let vec: Vec> = scanner.collect(); @@ -964,6 +973,9 @@ testCase"; Token::new(TokenType::Div, 0, 72), Token::new(TokenType::Break, 0, 76), Token::new(TokenType::Enum, 0, 82), + Token::new(TokenType::Function, 0, 87), + Token::new(TokenType::Constructor, 0, 96), + Token::new(TokenType::New, 0, 108), ] ) } diff --git a/gml_fmt_lib/src/statements.rs b/gml_fmt_lib/src/statements.rs index b313ed7..6434903 100644 --- a/gml_fmt_lib/src/statements.rs +++ b/gml_fmt_lib/src/statements.rs @@ -6,6 +6,7 @@ pub type StmtBox<'a> = Box>; pub struct DelimitedLines<'a, T> { pub lines: Vec>, pub has_end_delimiter: bool, + } #[derive(Debug)] diff --git a/gml_fmt_lib/tests/integration_tests.rs b/gml_fmt_lib/tests/integration_tests.rs index 5ccbc94..4183561 100644 --- a/gml_fmt_lib/tests/integration_tests.rs +++ b/gml_fmt_lib/tests/integration_tests.rs @@ -53,9 +53,9 @@ else if (xx < (2 / 2.75)) { #[test] fn series_of_declarations() { - let input = "var function, xx, xx2, xxm1; + let input = "var fn, xx, xx2, xxm1; var x = 2, y, var q"; - let format = "var function, xx, xx2, xxm1; + let format = "var fn, xx, xx2, xxm1; var x = 2, y, var q; "; @@ -124,6 +124,141 @@ for (var i;;) { assert_eq!(run_test(input), format); } +#[test] +fn function_definition() { + let input = "function fn_name(arg1,arg2){ +show_debug_message(0); +} +"; + let format = "function fn_name(arg1, arg2) { + show_debug_message(0); +} +"; + + assert_eq!(run_test(input), format); +} + +#[test] +fn function_var_assignment() { + let input = "fn_name=function(arg1,arg2){ +show_debug_message(0); +} +var fn_var=function(arg1,arg2){ +show_debug_message(0); +} +"; + let format = "fn_name = function(arg1, arg2) { + show_debug_message(0); +} +var fn_var = function(arg1, arg2) { + show_debug_message(0); +} +"; + + assert_eq!(run_test(input), format); +} + +#[test] +fn function_constructor() { + let input = "function fn_name(arg1,arg2)constructor{ +fn_debug=function(arg1,arg2){ +show_debug_message(0); +} +} + +fn_var=function(arg1,arg2)constructor{ +show_debug_message(0); +} +"; + let format = "function fn_name(arg1, arg2) constructor { + fn_debug = function(arg1, arg2) { + show_debug_message(0); + } +} + +fn_var = function(arg1, arg2) constructor { + show_debug_message(0); +} +"; + + assert_eq!(run_test(input), format); +} + +#[test] +fn function_constructor_call() { + let input = "_structObj=new fn_name(0,0); +_varObj=new fn_var(0,0); +"; + + let format = "_structObj = new fn_name(0, 0); +_varObj = new fn_var(0, 0); +"; + assert_eq!(run_test(input), format); +} + +#[test] +fn function_lambda() { + let input = "fn(arg1,function(i) { show_debug_message(0) }) + +fn(arg1,function(i) { show_debug_message(0); show_debug_message(1); }) + +function fn_name(arg1, arg2) constructor { +fn(arg1,function(i) { show_debug_message(0) }) + +fn(arg1,function(i) { show_debug_message(0); show_debug_message(1); }) +} +"; + let format = "fn(arg1, function(i) { show_debug_message(0) }); + +fn(arg1, function(i) { + show_debug_message(0); + show_debug_message(1); +}); + +function fn_name(arg1, arg2) constructor { + fn(arg1, function(i) { show_debug_message(0) }); + + fn(arg1, function(i) { + show_debug_message(0); + show_debug_message(1); + }); +} +"; + + assert_eq!(run_test(input), format); +} + +#[test] +fn struct_new_argument() { + let input = "fn(argument,new _struct(property1,property2))"; + let format = "fn(argument, new _struct(property1, property2)); +"; + assert_eq!(run_test(input), format); +} + +#[test] +fn struct_new_return() { + let input = "fn_debug = function(arg1,arg2) { +show_debug_message(0) +return new _struct +} +"; + let format = "fn_debug = function(arg1, arg2) { + show_debug_message(0); + return new _struct; +} +"; + assert_eq!(run_test(input), format); +} + +#[test] +fn struct_delete() { + let input = "delete _struct"; + let format = "delete _struct; +"; + assert_eq!(run_test(input), format); +} + #[test] fn decimal_number() { let input = "var x = .3; var z = 3.;";