From 64152af65b588bae355499d2523731165943d9a7 Mon Sep 17 00:00:00 2001 From: myfreess Date: Mon, 25 May 2026 14:28:43 +0800 Subject: [PATCH] fmt init --- cmd/moonfmt/main.mbt | 92 + cmd/moonfmt/moon.pkg | 10 + cmd/moonfmt/pkg.generated.mbti | 13 + fmt/LICENSE | 202 ++ fmt/README.mbt.md | 62 + fmt/README.md | 1 + fmt/design.md | 162 + fmt/internal/comment/cursor.mbt | 81 + fmt/internal/comment/group.mbt | 49 + fmt/internal/comment/import.mbt | 14 + fmt/internal/comment/log.mbt | 11 + fmt/internal/comment/mapper.mbt | 243 ++ fmt/internal/comment/mapper_visitor.mbt | 247 ++ fmt/internal/comment/moon.pkg | 9 + fmt/internal/comment/pkg.generated.mbti | 76 + fmt/internal/comment/token_array.mbt | 25 + fmt/internal/comment/token_table.mbt | 91 + fmt/internal/comment/token_table_array.mbt | 20 + fmt/internal/format/attach_docstring.mbt | 77 + fmt/internal/format/attribute2doc.mbt | 45 + fmt/internal/format/compose.mbt | 351 +++ fmt/internal/format/context.mbt | 275 ++ fmt/internal/format/format.mbt | 55 + fmt/internal/format/import.mbt | 31 + fmt/internal/format/moon.pkg | 16 + fmt/internal/format/pkg.generated.mbti | 27 + fmt/internal/format/remove_group.mbt | 19 + fmt/internal/format/syntax2doc.mbt | 2603 +++++++++++++++++ fmt/internal/format/utils.mbt | 148 + fmt/internal/format/utils_wbtest.mbt | 62 + .../__snapshot__/expr_sequence.input | 14 + .../__snapshot__/expr_sequence.output | 153 + .../__snapshot__/expr_sequence_array.input | 20 + .../__snapshot__/expr_sequence_array.output | 210 ++ .../__snapshot__/expr_sequence_array2.input | 18 + .../__snapshot__/expr_sequence_array2.output | 223 ++ .../__snapshot__/expr_sequence_array3.input | 22 + .../__snapshot__/expr_sequence_array3.output | 305 ++ .../comment_test/__snapshot__/stmt.output | 25 + .../comment_test/__snapshot__/top_func.input | 25 + .../comment_test/__snapshot__/top_func.output | 340 +++ .../testsuite/comment_test/comment_test.mbt | 49 + fmt/internal/testsuite/comment_test/moon.pkg | 8 + .../testsuite/comment_test/pkg.generated.mbti | 13 + .../__snapshot__/arrow_function.output | 36 + .../__snapshot__/async_toplevel.output | 11 + .../style_test/__snapshot__/attribute.output | 40 + .../style_test/__snapshot__/cases.output | 104 + .../__snapshot__/chained_dot.output | 151 + .../__snapshot__/compose_arrow_rhs.output | 66 + .../__snapshot__/compose_case_rhs.output | 181 ++ .../__snapshot__/compose_let_rhs.output | 109 + .../__snapshot__/constant_literals.output | 13 + .../__snapshot__/declare_keyword.output | 10 + .../style_test/__snapshot__/docstring.output | 79 + .../style_test/__snapshot__/ffi.output | 21 + .../style_test/__snapshot__/for_where.output | 8 + .../style_test/__snapshot__/lexmatch.output | 9 + .../__snapshot__/mixed_infix.output | 24 + .../__snapshot__/struct_constructor.output | 9 + .../__snapshot__/top_function.output | 43 + .../__snapshot__/trailing_block.output | 77 + .../__snapshot__/trait_impl_decl.output | 90 + .../__snapshot__/typedecl_alias.output | 5 + .../__snapshot__/typedecl_derive.output | 19 + .../__snapshot__/typedecl_enum.output | 58 + .../__snapshot__/typedecl_struct.output | 36 + .../__snapshot__/typedecl_suberror.output | 37 + .../__snapshot__/typedecl_tuplestruct.output | 41 + .../__snapshot__/typedecl_type.output | 11 + .../style_test/__snapshot__/using_decl.output | 7 + .../style_test/fixtures/arrow_function.input | 28 + .../style_test/fixtures/async_toplevel.input | 7 + .../style_test/fixtures/attribute.input | 41 + .../testsuite/style_test/fixtures/cases.input | 89 + .../style_test/fixtures/chained_dot.input | 106 + .../fixtures/compose_arrow_rhs.input | 71 + .../fixtures/compose_case_rhs.input | 135 + .../style_test/fixtures/compose_let_rhs.input | 71 + .../fixtures/constant_literals.input | 11 + .../style_test/fixtures/declare_keyword.input | 3 + .../style_test/fixtures/docstring.input | 63 + .../testsuite/style_test/fixtures/ffi.input | 13 + .../style_test/fixtures/for_where.input | 5 + .../style_test/fixtures/lexmatch.input | 6 + .../style_test/fixtures/mixed_infix.input | 20 + .../fixtures/struct_constructor.input | 6 + .../style_test/fixtures/top_function.input | 23 + .../style_test/fixtures/trailing_block.input | 73 + .../style_test/fixtures/trait_impl_decl.input | 49 + .../style_test/fixtures/typedecl_alias.input | 1 + .../style_test/fixtures/typedecl_derive.input | 9 + .../style_test/fixtures/typedecl_enum.input | 31 + .../style_test/fixtures/typedecl_struct.input | 16 + .../fixtures/typedecl_suberror.input | 20 + .../fixtures/typedecl_tuplestruct.input | 7 + .../style_test/fixtures/typedecl_type.input | 8 + .../style_test/fixtures/using_decl.input | 2 + fmt/internal/testsuite/style_test/moon.pkg | 10 + .../testsuite/style_test/pkg.generated.mbti | 13 + .../testsuite/style_test/style_test.mbt | 225 ++ fmt/moon.pkg | 3 + fmt/pkg.generated.mbti | 21 + fmt/top.mbt | 3 + moon.mod | 1 + 105 files changed, 9027 insertions(+) create mode 100644 cmd/moonfmt/main.mbt create mode 100644 cmd/moonfmt/moon.pkg create mode 100644 cmd/moonfmt/pkg.generated.mbti create mode 100644 fmt/LICENSE create mode 100644 fmt/README.mbt.md create mode 120000 fmt/README.md create mode 100644 fmt/design.md create mode 100644 fmt/internal/comment/cursor.mbt create mode 100644 fmt/internal/comment/group.mbt create mode 100644 fmt/internal/comment/import.mbt create mode 100644 fmt/internal/comment/log.mbt create mode 100644 fmt/internal/comment/mapper.mbt create mode 100644 fmt/internal/comment/mapper_visitor.mbt create mode 100644 fmt/internal/comment/moon.pkg create mode 100644 fmt/internal/comment/pkg.generated.mbti create mode 100644 fmt/internal/comment/token_array.mbt create mode 100644 fmt/internal/comment/token_table.mbt create mode 100644 fmt/internal/comment/token_table_array.mbt create mode 100644 fmt/internal/format/attach_docstring.mbt create mode 100644 fmt/internal/format/attribute2doc.mbt create mode 100644 fmt/internal/format/compose.mbt create mode 100644 fmt/internal/format/context.mbt create mode 100644 fmt/internal/format/format.mbt create mode 100644 fmt/internal/format/import.mbt create mode 100644 fmt/internal/format/moon.pkg create mode 100644 fmt/internal/format/pkg.generated.mbti create mode 100644 fmt/internal/format/remove_group.mbt create mode 100644 fmt/internal/format/syntax2doc.mbt create mode 100644 fmt/internal/format/utils.mbt create mode 100644 fmt/internal/format/utils_wbtest.mbt create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence.input create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence.output create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array.input create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array.output create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array2.input create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array2.output create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array3.input create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array3.output create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/stmt.output create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/top_func.input create mode 100644 fmt/internal/testsuite/comment_test/__snapshot__/top_func.output create mode 100644 fmt/internal/testsuite/comment_test/comment_test.mbt create mode 100644 fmt/internal/testsuite/comment_test/moon.pkg create mode 100644 fmt/internal/testsuite/comment_test/pkg.generated.mbti create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/arrow_function.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/async_toplevel.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/attribute.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/cases.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/chained_dot.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/compose_arrow_rhs.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/compose_case_rhs.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/compose_let_rhs.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/constant_literals.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/declare_keyword.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/docstring.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/ffi.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/for_where.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/lexmatch.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/mixed_infix.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/struct_constructor.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/top_function.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/trailing_block.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/trait_impl_decl.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/typedecl_alias.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/typedecl_derive.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/typedecl_enum.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/typedecl_struct.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/typedecl_suberror.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/typedecl_tuplestruct.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/typedecl_type.output create mode 100644 fmt/internal/testsuite/style_test/__snapshot__/using_decl.output create mode 100644 fmt/internal/testsuite/style_test/fixtures/arrow_function.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/async_toplevel.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/attribute.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/cases.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/chained_dot.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/compose_arrow_rhs.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/compose_case_rhs.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/compose_let_rhs.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/constant_literals.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/declare_keyword.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/docstring.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/ffi.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/for_where.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/lexmatch.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/mixed_infix.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/struct_constructor.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/top_function.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/trailing_block.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/trait_impl_decl.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/typedecl_alias.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/typedecl_derive.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/typedecl_enum.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/typedecl_struct.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/typedecl_suberror.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/typedecl_tuplestruct.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/typedecl_type.input create mode 100644 fmt/internal/testsuite/style_test/fixtures/using_decl.input create mode 100644 fmt/internal/testsuite/style_test/moon.pkg create mode 100644 fmt/internal/testsuite/style_test/pkg.generated.mbti create mode 100644 fmt/internal/testsuite/style_test/style_test.mbt create mode 100644 fmt/moon.pkg create mode 100644 fmt/pkg.generated.mbti create mode 100644 fmt/top.mbt diff --git a/cmd/moonfmt/main.mbt b/cmd/moonfmt/main.mbt new file mode 100644 index 00000000..d8ec2d60 --- /dev/null +++ b/cmd/moonfmt/main.mbt @@ -0,0 +1,92 @@ +///| +priv struct MoonfmtOptions { + path : String + overwrite : Bool +} + +///| +fn moonfmt_command() -> @argparse.Command { + @argparse.Command( + "moonfmt", + about="Format a MoonBit source file.", + flags=[ + @argparse.FlagArg( + "overwrite", + short='w', + long="overwrite", + about="Overwrite the input file.", + ), + ], + positionals=[ + @argparse.PositionArg( + "path", + about="MoonBit source file to format.", + num_args=@argparse.ValueRange::single(), + ), + ], + ) +} + +///| +fn parse_moonfmt_args(args : ArrayView[String]) -> MoonfmtOptions raise { + let matches = moonfmt_command().parse(argv=args, env={}) + let path = match matches.values.get("path") { + Some([path]) => path + _ => panic() + } + { path, overwrite: matches.flags.get("overwrite").unwrap_or(false) } +} + +///| +fn moonfmt(args : ArrayView[String]) -> Unit { + let options = parse_moonfmt_args(args) catch { + e => { + println(e) + panic() + } + } + let source = @fs.read_file_to_string(options.path) catch { + e => { + println(e) + panic() + } + } + let result = @fmt.format(source) + if options.overwrite { + @fs.write_string_to_file(options.path, result) catch { + e => { + println(e) + panic() + } + } + } else { + println(result) + } +} + +///| +fn program_args() -> ArrayView[String] { + let args = @env.args() + if args.length() > 1 { + args[1:] + } else { + [] + } +} + +///| +fn main { + moonfmt(program_args()) +} + +///| +test "parse moonfmt args" { + let parsed = parse_moonfmt_args(["--overwrite", "input.mbt"]) catch { + _ => panic() + } + inspect(parsed.path, content="input.mbt") + inspect(parsed.overwrite, content="true") + let short = parse_moonfmt_args(["-w", "input.mbt"]) catch { _ => panic() } + inspect(short.path, content="input.mbt") + inspect(short.overwrite, content="true") +} diff --git a/cmd/moonfmt/moon.pkg b/cmd/moonfmt/moon.pkg new file mode 100644 index 00000000..72780471 --- /dev/null +++ b/cmd/moonfmt/moon.pkg @@ -0,0 +1,10 @@ +import { + "moonbitlang/core/argparse", + "moonbitlang/x/fs", + "moonbitlang/parser/fmt", + "moonbitlang/core/env", +} + +options( + "is-main": true, +) diff --git a/cmd/moonfmt/pkg.generated.mbti b/cmd/moonfmt/pkg.generated.mbti new file mode 100644 index 00000000..105e0fc1 --- /dev/null +++ b/cmd/moonfmt/pkg.generated.mbti @@ -0,0 +1,13 @@ +// Generated using `moon info`, DON'T EDIT IT +package "moonbitlang/parser/cmd/moonfmt" + +// Values + +// Errors + +// Types and methods + +// Type aliases + +// Traits + diff --git a/fmt/LICENSE b/fmt/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/fmt/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/fmt/README.mbt.md b/fmt/README.mbt.md new file mode 100644 index 00000000..26d95f73 --- /dev/null +++ b/fmt/README.mbt.md @@ -0,0 +1,62 @@ +# moonbitlang/formatter + +`moonbitlang/formatter` is a code formatter for the MoonBit. This project was +created with the following goals in mind: + +- Ensure readability and consistent code formatting. +- Provide a single "true" formatting style, with no configuration required, making it easy to adopt across teams. +- Guarantee idempotence: formatting the code multiple times yields the same result after the first application. +- Properly handle comments and merge blank lines. + +**Warning: This module is highly experimental and work in progress.** + +## Status + +* [x] Parenthesis inference +* [x] Expression layout composition +* [x] Expression formatting +* [x] Top-level declaration formatting +* [ ] Comment formatting +* [ ] Blank line awareness +* [ ] Fully test coverage + +## Usage + +```mbt nocheck +///| +test { + let code = + #|/// Tree data structure + #|pub enum Tree[T] { Empty; Node(Tree[T], T, Tree[T]) }derive(Show) + #| + #|/// Greet a person + #|pub fn hello(name:String)->Unit{ let str = "hello, \{name}!"; println(str) } + let result = @formatter.format(code) + println(result) + inspect( + result, + content=( + #|///| + #|/// Tree data structure + #|pub enum Tree[T] { + #| Empty + #| Node(Tree[T], T, Tree[T]) + #|} derive(Show) + #| + #|///| + #|/// Greet a person + #|pub fn hello(name : String) -> Unit { + #| let str = "hello, name!" + #| println(str) + #|} + #| + #| + ), + ) +} +``` +## Contributing + +At this early stage, we are not ready to accept pull requests yet. +You can contribute by using this module in your projects and providing feedback. +If you encounter any bugs or poor formatting results, please open an issue. diff --git a/fmt/README.md b/fmt/README.md new file mode 120000 index 00000000..7ae1db3e --- /dev/null +++ b/fmt/README.md @@ -0,0 +1 @@ +README.mbt.md \ No newline at end of file diff --git a/fmt/design.md b/fmt/design.md new file mode 100644 index 00000000..e063f6ac --- /dev/null +++ b/fmt/design.md @@ -0,0 +1,162 @@ +# Overview of formatter design + + +# Compose expressions + +# Reduce the indentation, newline and braces + +## Rule 1: Simple Expressions/Patterns Sequence + +## Rule 2: Trailing Block + +## Rule 3: Collapsible Expression in RHS + +To reduce the indentation level and improve readbility, we can collapse some expressions in the right hand side of certain constructs. For example: + +```moonbit +// good: space is enough after `let short_pattern =` +let short_pattern = oneline_func(arg) +// bad: extra braces, newline and indentation +let short_pattern = { + oneline_func(arg) +} +// bad: extra indentation and newline +let short_pattern = + oneline_func(arg) +``` + +```moonbit +// good: space is not enough after `long_pattern =>`, +// move to next line with indentation without extra braces +match e { + long_pattern => + oneline_func(arg) +} +// bad +match e { + long_pattern => oneline_func( + arg + ) +} +// bad: extra braces, newline and indentation +match e { + long_pattern => { + oneline_func(arg) + } +} +``` + +```moonbit +// good: space is not enough for the record, but the head `TypeName::{` fits after `long_pattern =>`. +// move the other parts to next line **without** extra indentation. +match e { + long_pattern => TypeName::{ + field1: value1, + field2: value2, + } +} +// bad +match e { + long_pattern => TypeName::{ + field1: value1, + field2: value2, + } +} +// bad +match e { + long_pattern => { + TypeName::{ + field1: value1, + field2: value2, + } + } +} +// bad +match e { + long_pattern => + TypeName::{ + field1: value1, + field2: value2, + } +} +``` + +This rule applies to the collapsible expression in the right hand side of following constructs: + +- `pattern => rhs` +- `let a = rhs`, `let mut a = rhs` +- `binder = rhs`, `arr[i] = rhs`, `x.field = rhs` + +The collapsible expression are divided into two parts, `head` and `body`. For example: + +``` + x => ... + fn(x) {... } + fcall (args) + \---/ \--/ + head body + + match cond { ... } + if cond { ... } else { ... } + \----------/\-----------------/ + head body + + [ elem1, elem2 ] + ( elem1, elem2 ) + ( expr : Annotation ) + { field1: value1, field2, value2 } + \-------------------------------/ + body (head is empty) +``` + +Consider the acc is the some thing like `pattern => ` or `let a =`: + +If the `rhs` fits in the current line, print in flatten style. + +``` + +-----+--------+--------+ + | acc | head | body | + +-----+--------+--------+ + flatten style. +``` + + +If the `rhs` fits in the next line with indentation: + +``` + +-----------+ + | acc | + +--+--------+--------+ + | head | body | + +--------+--------+ + dangling style. +``` + +If the head fits in current line, the `rhs` can be composed: + +``` + +-----+--------+ + | acc | head | + +-----+--------+ + | body | + +--------------+ + composed style. +``` + +If the head can not fits in the current line, print in normal: + +``` + +-----------+--+ + | acc | {| + +--+--------+--+-----+ + | head | body | + +--+--------+--------+ + |} | + +--+ + normal style. +``` + + + + + diff --git a/fmt/internal/comment/cursor.mbt b/fmt/internal/comment/cursor.mbt new file mode 100644 index 00000000..cf2b216a --- /dev/null +++ b/fmt/internal/comment/cursor.mbt @@ -0,0 +1,81 @@ +///| +fn Mapper::cursor_advance(self : Self, pos : Position) -> Unit { + while self.tokens.get(self.cursor) is Some((tok, start, _)) && !(tok is EOF) { + log("advance pos: \{pos}, \{tok.kind()}, \{start}") + if start < pos { + self.cursor += 1 + continue + } else { + break + } + } +} + +///| +fn Mapper::cursor_find( + self : Self, + tok : TokenKind, + before_pos? : Position, + before_token? : TokenKind, +) -> Key? { + match self.cursor_find_aux(tok, before_pos?, before_token?) { + None => None + Some(k) as r => { + self.attach(k) + r + } + } +} + +///| +fn Mapper::cursor_find_aux( + self : Self, + tok : TokenKind, + before_pos? : Position, + before_token? : TokenKind, +) -> Key? { + if before_pos is Some(pos) { + while self.tokens.get(self.cursor) is Some(triple) && + !(triple.0 is EOF) && + triple.1 < pos { + if triple.0.kind() != tok { + log("advance tok before: \{triple.0.kind()} \{triple.1} \{triple.2}") + self.cursor += 1 + continue + } else { + log("consume tok: \{triple.0.kind()} \{triple.1} \{triple.2}") + self.cursor += 1 + return Some(Token({ start: triple.1, end: triple.2 })) + } + } + None + } else if before_token is Some(before) { + while self.tokens.get(self.cursor) is Some(triple) && + !(triple.0 is EOF) && + triple.0.kind() != before { + if triple.0.kind() != tok { + log("advance tok before: \{triple.0.kind()} \{triple.1} \{triple.2}") + self.cursor += 1 + continue + } else { + log("consume tok: \{triple.0.kind()} \{triple.1} \{triple.2}") + self.cursor += 1 + return Some(Token({ start: triple.1, end: triple.2 })) + } + } + None + } else { + while self.tokens.get(self.cursor) is Some(triple) && !(triple.0 is EOF) { + if triple.0.kind() != tok { + log("advance tok: \{triple.0.kind()} \{triple.1} \{triple.2}") + self.cursor += 1 + continue + } else { + log("consume tok: \{triple.0.kind()} \{triple.1} \{triple.2}") + self.cursor += 1 + return Some(Token({ start: triple.1, end: triple.2 })) + } + } + None + } +} diff --git a/fmt/internal/comment/group.mbt b/fmt/internal/comment/group.mbt new file mode 100644 index 00000000..9dcfa225 --- /dev/null +++ b/fmt/internal/comment/group.mbt @@ -0,0 +1,49 @@ +///| +pub(all) struct Comment { + start : Position + end : Position + left : Triple? + right : Triple? + kind : CommentKind +} derive(@debug.Debug) + +///| +fn pretty_triple(x : Triple?) -> @pp.Document { + match x { + None => text("None") + Some(x) => + text(x.0.to_expect_string()) + + space + + @pp.parens(text(Location::{ start: x.1, end: x.2 }.to_string())) + } +} + +///| +// fn pretty_pos(x : Position) -> @pp.Document { +// x.to_string() |> text +// } + +///| +pub impl Pretty for Comment with fn pretty(self) { + @pp.record({ + "kind": pretty(self.kind), + "loc": text(Location::{ start: self.start, end: self.end }.to_string()), + "surround": pretty((pretty_triple(self.left), pretty_triple(self.right))), + }) +} + +///| +pub(all) enum CommentKind { + Blank + Block(String) + Line(String) +} derive(@debug.Debug) + +///| +pub impl Pretty for CommentKind with fn pretty(x) { + match x { + Blank => text("BLANK") + Block(s) => text(s.escape()) + Line(s) => text(s) + } +} diff --git a/fmt/internal/comment/import.mbt b/fmt/internal/comment/import.mbt new file mode 100644 index 00000000..5b5eae98 --- /dev/null +++ b/fmt/internal/comment/import.mbt @@ -0,0 +1,14 @@ +///| +using @basic {type Location, type Position} + +///| +using @tokens {type TokenKind} + +///| +using @pp {trait Pretty, space, text, pretty} + +///| +type Triple = (@tokens.Token, @basic.Position, @basic.Position) + +///| +using @syntax {trait IterVisitor} diff --git a/fmt/internal/comment/log.mbt b/fmt/internal/comment/log.mbt new file mode 100644 index 00000000..9bdd461a --- /dev/null +++ b/fmt/internal/comment/log.mbt @@ -0,0 +1,11 @@ +///| +#cfg(false) +fn log(s : String) -> Unit { + println(s) +} + +///| +#cfg(true) +fn log(s : String) -> Unit { + ignore(s) +} diff --git a/fmt/internal/comment/mapper.mbt b/fmt/internal/comment/mapper.mbt new file mode 100644 index 00000000..bbb3444a --- /dev/null +++ b/fmt/internal/comment/mapper.mbt @@ -0,0 +1,243 @@ +///| +struct Mapper { + map : Map[Key, Array[Comment]] + table : Map[Key, NodeTable] + queue : @queue.Queue[Comment] + stack : Array[Key] + /// for debug purpose + comments : Array[Comment] + tokens : Array[Triple] + mut cursor : Int +} + +///| +pub fn Mapper::get_table(self : Self, node_loc : Location) -> NodeTable { + self.table.get(Node(node_loc)).unwrap_or_else(fn() { NodeTable::empty() }) +} + +///| +pub fn Mapper::consume_comments( + self : Self, + key : Key?, + filter : (Comment) -> Bool, +) -> Array[Comment] { + match key { + None => [] + Some(k) => { + let arr = self.map.get(k).unwrap_or([]) + let keep_arr = [] + let move_arr = [] + for e in arr { + if filter(e) { + move_arr.push(e) + } else { + keep_arr.push(e) + } + } + self.map.set(k, keep_arr) + move_arr + } + } +} + +///| +pub fn Mapper::has_comment(self : Self, key : Key?) -> Bool { + match key { + None => false + Some(k) => self.map.contains(k) + } +} + +///| +pub fn Mapper::has_leading_comment(self : Self, key : Key?) -> Bool { + // TODO: optimize this + match key { + None => false + Some(k) => + self.map.get(k).unwrap_or([]).iter().any(x => x.end < k.loc().start) + } +} + +///| +pub fn Mapper::has_trailing_comment(self : Self, key : Key?) -> Bool { + // TODO: optimize this + match key { + None => false + Some(k) => + self.map.get(k).unwrap_or([]).iter().any(x => x.start > k.loc().start) + } +} + +///| +pub fn Mapper::new(tokens : Array[Triple], impls : @syntax.Impls) -> Self { + let queue : @queue.Queue[Comment] = Queue([]) + let mut i = 0 + let mut previous = None + while true { + match tokens[i:] { + [(COMMENT(c), spos, epos), ..] => { + i += 2 + let next = tokens.get(i) + queue.push({ + start: spos, + end: epos, + kind: Line(c.content), + left: previous, + right: next, + }) + } + [(NEWLINE, spos, epos), ..] => { + let mut epos = epos + let mut count = 0 + while tokens.get(i) is Some((NEWLINE, _, e)) { + count += 1 + epos = e + i += 1 + } + if count > 1 { + let next = tokens.get(i) + queue.push({ + start: spos, + end: epos, + kind: Blank, + left: previous, + right: next, + }) + } + } + [(SEMI(false), _, _), ..] => i += 1 + [triple, ..] => { + previous = Some(triple) + i += 1 + } + _ => break + } + } + // println(@pp.pretty(queue)) + let document_loc : Location = { + let pos : Position = { fname: "", lnum: 0, bol: 0, cnum: 0 } + if tokens.last() is Some(eof) { + { start: pos, end: eof.2 } + } else { + { start: pos, end: pos } + } + } + let self = { + queue, + comments: queue.iter().collect(), + map: {}, + stack: [], + table: {}, + tokens, + cursor: 0, + } + self.visit_Impls(impls, document_loc) + self +} + +///| +pub fn Mapper::to_debug_info(self : Self) -> String { + let dict : Map[Key, Array[Comment]] = self.map + let doc = dict.to_array() |> @sorted_map.from_array |> @pp.render(width=80) + let comments = @pp.render(width=80, self.comments) + let token_table = @pp.render(@pp.pretty(self.table), width=80) + comments + + "\n\n" + + doc + + "\n\n" + + @pp.render(self.table) + + "\n\n" + + token_table +} + +///| +fn @basic.Location::contains_pos(self : Self, pos : Position) -> Bool { + self.start <= pos && self.end > pos +} + +///| +/// Associated related comment to AST node n, if: +/// +/// 1. c starts on the same line as n ends +/// 2. c starts on the line following n, and there is +/// at least one empty line after c +/// 3. c starts before n and is not associated to the node before n +/// via the previous rules +/// +/// This algorithm was inspired by gofmt. +fn Mapper::attach(self : Self, n : Key) -> Unit { + // The stack tracks the path from root to the previous visited node. Pop the + // stack until the parent of n is at the top. The last popped node (pn) should + // be the previous sibling of n. + // + // Consider this case: The comment will be attach to previous sibling of n. + // If previous sibling doesn't exist (stack top is `[[]]`, which is the parent of n), + // the comment should be attach to n instead. + // + // ``` + // [[ + // //comment + // n + // ]] + // ``` + let mut pn = None + while self.stack.last() is Some(top) && !top.loc().contains_pos(n.loc().start) { + pn = self.stack.pop() + } + + // handle comments before current node + while self.queue.peek() is Some(c) && c.end < n.loc().start { + self.queue.pop() |> ignore + let assoc : Key = match pn { + // 1) the `c` in the same line of the `pn`, or + // 2) immediately after the `pn` and there is an empty line before the `n` + // attach `c` to `pn`. + // + // NOTE: + // There should be nothing between `pn` and `c` (except NEWLINE, COMMENT, + // and inserted SEMI). Consider this case: + // + // ``` + // //comment + // match e { + // //comment + // pat => ... + // } + // ``` + // The comment should not be associated to `e` + Some(pn) if pn.loc().contains_pos(c.left.unwrap().1) && + ( + pn.loc().end.lnum == c.start.lnum || + ( + pn.loc().end.lnum + 1 == c.start.lnum && + c.end.lnum + 1 < n.loc().start.lnum + ) + ) => pn + // The previous sibling may not exsit, attach `c` to `n` in this case. + _ => n + } + match self.map.get(assoc) { + None => self.map[assoc] = [c] + Some(arr) => arr.push(c) + } + log("attached comment \{@pp.render(c.kind)} to \{@pp.render(assoc)}") + } + // Update the stack + self.stack.push(n) +} + +///| +fn Mapper::attach_node(self : Self, n : Location) -> Unit { + self.attach(Node(n)) +} + +///| +fn Mapper::visit_Impls( + self : Self, + impls : @syntax.Impls, + _loc : Location, +) -> Unit { + impls.map(x => self.visit_Impl(x)) |> ignore + // println("eof: \{eof_loc}, delayed: \{self.delayed}") + // self.attach_after(eof_loc) +} diff --git a/fmt/internal/comment/mapper_visitor.mbt b/fmt/internal/comment/mapper_visitor.mbt new file mode 100644 index 00000000..67caa842 --- /dev/null +++ b/fmt/internal/comment/mapper_visitor.mbt @@ -0,0 +1,247 @@ +///| +impl IterVisitor for Mapper with fn visit_Expr(self, expr) { + self..attach(Node(expr.loc())).base().visit_Expr(expr) +} + +///| +impl IterVisitor for Mapper with fn visit_Pattern(self, pat) { + self..attach(Node(pat.loc())).base().visit_Pattern(pat) +} + +///| +impl IterVisitor for Mapper with fn visit_Type(self, ty) { + self..attach(Node(ty.loc())).base().visit_Type(ty) +} + +///| +impl IterVisitor for Mapper with fn visit_Impl(self, top) { + self..attach(Node(top.loc())).base().visit_Impl(top) +} + +///| +impl IterVisitor for Mapper with fn visit_Expr_Array(self, exprs~, loc~) { + self.attach(Node(loc)) + self.cursor_advance(loc.start) + let lbracket = self.cursor_find(TK_LBRACKET) + let commas = [] + for elem in exprs { + self.visit_Expr(elem) + self.cursor_advance(elem.loc().end) + let comma = self.cursor_find(TK_COMMA, before_pos=loc.end) + commas.push(comma) + } + let rbracket = self.cursor_find(TK_RBRACKET) + self.table[Node(loc)] = NodeTable::new({ + "lbracket": Token(lbracket), + "comma": TokenArray(commas), + "rbracket": Token(rbracket), + }) +} + +///| +impl IterVisitor for Mapper with fn visit_Visibility(self, vis) { + match vis { + Default => () + Pub(attr~, loc~) => { + self.cursor_advance(loc.start) + let modifier0 = self.cursor_find(TK_PUB, before_pos=loc.end) + let lparen = self.cursor_find(TK_LPAREN, before_pos=loc.end) + let (lident, rparen) = if attr is Some(_) { + let a = self.cursor_find(TK_LIDENT, before_token=TK_RPAREN) + let b = self.cursor_find(TK_RPAREN, before_pos=loc.end) + (a, b) + } else { + (None, None) + } + self.table[Node(loc)] = NodeTable::new({ + "pub": Token(modifier0), + "lparen": Token(lparen), + "lident": Token(lident), + "rparen": Token(rparen), + }) + } + Priv(loc~) => { + self.cursor_advance(loc.start) + let modifier0 = self.cursor_find(TK_PRIV, before_pos=loc.end) + self.table[Node(loc)] = NodeTable::new({ "priv": Token(modifier0) }) + } + } +} + +///| +fn Mapper::fill_parameter(self : Self, param : @syntax.Parameter) -> NodeTable { + self.attach_node(param.loc()) + match param { + DiscardPositional(ty~, loc~) => { + self.cursor_advance(loc.start) + let colon = self.cursor_find(TK_COLON) + if ty is Some(ty) { + self.visit_Type(ty) + } + NodeTable::new({ "colon": Token(colon) }) + } + Labelled(ty~, binder~) | Positional(ty~, binder~) => { + self.cursor_advance(binder.loc.start) + let colon = self.cursor_find(TK_COLON) + if ty is Some(ty) { + self.visit_Type(ty) + } + NodeTable::new({ "colon": Token(colon) }) + } + QuestionOptional(ty~, binder~) => { + self.cursor_advance(binder.loc.start) + let question = self.cursor_find(TK_QUESTION) + let colon = self.cursor_find(TK_COLON) + if ty is Some(ty) { + self.visit_Type(ty) + } + NodeTable::new({ "question": Token(question), "colon": Token(colon) }) + } + Optional(ty~, default~, binder~) => { + self.cursor_advance(binder.loc.start) + let colon = self.cursor_find(TK_COLON) + if ty is Some(ty) { + self.visit_Type(ty) + } + let equal = self.cursor_find(TK_EQUAL) + self.visit_Expr(default) + NodeTable::new({ "colon": Token(colon), "equal": Token(equal) }) + } + } +} + +///| +// TODO: patch the parser +fn @syntax.TypeVarBinder::loc(self : Self) -> Location { + match self.constraints { + More(x, ..) => self.name_loc.merge(x.loc) + Empty => self.name_loc + } +} + +///| +fn Mapper::fill_type_var_binder( + self : Self, + binder : @syntax.TypeVarBinder, +) -> NodeTable { + let loc = binder.loc() + self.cursor_advance(loc.start) + self.attach_node(loc) + self.attach_node(binder.name_loc) //name_loc + let mut colon = None + let pluses = [] + for i, constraint in binder.constraints { + if i == 0 { + colon = self.cursor_find(TK_COLON, before_pos=loc.end) + } else { + pluses.push(self.cursor_find(TK_PLUS, before_pos=loc.end)) + } + self.attach_node(constraint.loc) + } + NodeTable::new({ "colon": Token(colon), "pluses": TokenArray(pluses) }) +} + +///| +impl IterVisitor for Mapper with fn visit_Binder(self, binder) { + self.attach_node(binder.loc) +} + +///| +impl IterVisitor for Mapper with fn visit_Impl_TopFuncDef( + s, + fun_decl~, + decl_body~, + where_clause~, + loc~, +) { + s.cursor_advance(loc.start) + s.visit_Visibility(fun_decl.vis) + if fun_decl.type_name is Some(x) { + s.visit_TypeName(x) + } + let async_ = s.cursor_find(TK_ASYNC, before_token=TK_FN) + let fn_ = s.cursor_find(TK_FN, before_token=TK_LPAREN) + let commas0 = [] + let type_var_binders = [] + let (lbracket, rbracket) = if !fun_decl.quantifiers.is_empty() { + let lb = s.cursor_find(TK_LBRACKET, before_pos=loc.end) + for tv in fun_decl.quantifiers { + type_var_binders.push(s.fill_type_var_binder(tv)) + commas0.push(s.cursor_find(TK_COMMA, before_token=TK_RBRACKET)) + } + let rb = s.cursor_find(TK_RBRACKET, before_pos=loc.end) + (lb, rb) + } else { + (None, None) + } + s.visit_Binder(fun_decl.name) + let parameters = [] + let commas1 = [] + let (lparen, rparen) = if fun_decl.decl_params is Some(ps) { + let lparen = s.cursor_find(TK_LPAREN, before_pos=loc.end) + for p in ps { + parameters.push(s.fill_parameter(p)) + let comma = s.cursor_find(TK_COMMA, before_token=TK_RPAREN) + commas1.push(comma) + } + let rparen = s.cursor_find(TK_RPAREN, before_pos=loc.end) + (lparen, rparen) + } else { + (None, None) + } + let thin_arrow = s.cursor_find(TK_THIN_ARROW) + if fun_decl.return_type is Some(ty) { + s.visit_Type(ty) + } + let (raise_or_noraise, question) = match fun_decl.error_type { + MaybeError(_) => { + let a = s.cursor_find(TK_RAISE) + let b = s.cursor_find(TK_QUESTION) + (a, b) + } + ErrorType(_) | DefaultErrorType(_) => { + let a = s.cursor_find(TK_RAISE) + (a, None) + } + Noraise(_) => { + let a = s.cursor_find(TK_NORAISE) + (a, None) + } + NoErrorType => (None, None) + } + match where_clause { + None => () + Some(where_clause) => { + ignore(s.cursor_find(TK_WHERE, before_pos=where_clause.loc.end)) + ignore(s.cursor_find(TK_LBRACE, before_pos=where_clause.loc.end)) + s.visit_WhereClause(where_clause) + ignore(s.cursor_find(TK_RBRACE, before_pos=where_clause.loc.end)) + } + } + let (lbrace, rbrace) = match decl_body { + DeclBody(expr~) => { + let lb = s.cursor_find(TK_LBRACE, before_pos=loc.end) + s.visit_Expr(expr) + let rb = s.cursor_find(TK_RBRACE, before_pos=loc.end) + (lb, rb) + } + DeclStubs(_) | DeclNone => (None, None) + } + s.table[Node(loc)] = NodeTable::new({ + "async": Token(async_), + "fn": Token(fn_), + "lbracket": Token(lbracket), + "commas0": TokenArray(commas0), + "type_var_binders": TableArray(type_var_binders), + "rbracket": Token(rbracket), + "lparen": Token(lparen), + "parameters": TableArray(parameters), + "commas1": TokenArray(commas1), + "rparen": Token(rparen), + "thin_arrow": Token(thin_arrow), + "raise_or_noraise": Token(raise_or_noraise), + "question": Token(question), + "lbrace": Token(lbrace), + "rbrace": Token(rbrace), + }) +} diff --git a/fmt/internal/comment/moon.pkg b/fmt/internal/comment/moon.pkg new file mode 100644 index 00000000..a4adef14 --- /dev/null +++ b/fmt/internal/comment/moon.pkg @@ -0,0 +1,9 @@ +import { + "moonbitlang/parser/syntax", + "moonbitlang/parser/basic", + "moonbitlang/parser/tokens", + "moonbit-community/prettyprinter" @pp, + "moonbitlang/core/queue", + "moonbitlang/core/sorted_map", + "moonbitlang/core/debug", +} diff --git a/fmt/internal/comment/pkg.generated.mbti b/fmt/internal/comment/pkg.generated.mbti new file mode 100644 index 00000000..9bb9ced3 --- /dev/null +++ b/fmt/internal/comment/pkg.generated.mbti @@ -0,0 +1,76 @@ +// Generated using `moon info`, DON'T EDIT IT +package "moonbitlang/parser/fmt/internal/comment" + +import { + "moonbit-community/prettyprinter", + "moonbitlang/core/debug", + "moonbitlang/core/list", + "moonbitlang/parser/basic", + "moonbitlang/parser/syntax", + "moonbitlang/parser/tokens", +} + +// Values + +// Errors + +// Types and methods +pub(all) struct Comment { + start : @basic.Position + end : @basic.Position + left : (@tokens.Token, @basic.Position, @basic.Position)? + right : (@tokens.Token, @basic.Position, @basic.Position)? + kind : CommentKind +} derive(@debug.Debug) +pub impl @prettyprinter.Pretty for Comment + +pub(all) enum CommentKind { + Blank + Block(String) + Line(String) +} derive(@debug.Debug) +pub impl @prettyprinter.Pretty for CommentKind + +pub(all) enum Key { + Token(@basic.Location) + Node(@basic.Location) +} derive(Compare, Eq, Hash) +pub fn Key::loc(Self) -> @basic.Location +pub impl @prettyprinter.Pretty for Key + +type Mapper +pub fn Mapper::consume_comments(Self, Key?, (Comment) -> Bool) -> Array[Comment] +pub fn Mapper::get_table(Self, @basic.Location) -> NodeTable +pub fn Mapper::has_comment(Self, Key?) -> Bool +pub fn Mapper::has_leading_comment(Self, Key?) -> Bool +pub fn Mapper::has_trailing_comment(Self, Key?) -> Bool +pub fn Mapper::new(Array[(@tokens.Token, @basic.Position, @basic.Position)], @list.List[@syntax.Impl]) -> Self +pub fn Mapper::to_debug_info(Self) -> String + +type NodeArray +pub fn NodeArray::empty() -> Self +pub fn NodeArray::last(Self) -> Key? +pub fn NodeArray::op_get(Self, Int) -> Key? +pub impl @prettyprinter.Pretty for NodeArray + +type NodeTable +pub fn NodeTable::empty() -> Self +pub fn NodeTable::get_array(Self, String) -> NodeArray +pub fn NodeTable::get_table(Self, String) -> Self +pub fn NodeTable::get_table_array(Self, String) -> NodeTableArray +pub fn NodeTable::get_token(Self, String) -> Key? +pub fn NodeTable::new(Map[String, TokenGroup]) -> Self +pub impl @prettyprinter.Pretty for NodeTable + +type NodeTableArray +pub fn NodeTableArray::empty() -> Self +pub fn NodeTableArray::op_get(Self, Int) -> NodeTable +pub impl @prettyprinter.Pretty for NodeTableArray + +type TokenGroup +pub impl @prettyprinter.Pretty for TokenGroup + +// Type aliases + +// Traits + diff --git a/fmt/internal/comment/token_array.mbt b/fmt/internal/comment/token_array.mbt new file mode 100644 index 00000000..db022d98 --- /dev/null +++ b/fmt/internal/comment/token_array.mbt @@ -0,0 +1,25 @@ +///| +struct NodeArray(Array[Key?]) + +///| +let empty_node_array : NodeArray = [] + +///| +pub fn NodeArray::empty() -> NodeArray { + [] +} + +///| +pub fn NodeArray::op_get(self : Self, idx : Int) -> Key? { + self.0.get(idx).unwrap_or(None) +} + +///| +pub fn NodeArray::last(self : Self) -> Key? { + self.0.last().unwrap_or(None) +} + +///| +pub impl Pretty for NodeArray with fn pretty(self) { + @pp.pretty(self.0) +} diff --git a/fmt/internal/comment/token_table.mbt b/fmt/internal/comment/token_table.mbt new file mode 100644 index 00000000..383134f7 --- /dev/null +++ b/fmt/internal/comment/token_table.mbt @@ -0,0 +1,91 @@ +///| +struct NodeTable(Map[String, TokenGroup]) + +///| +let empty_table : NodeTable = NodeTable::empty() + +///| +pub impl Pretty for NodeTable with fn pretty(node) { + @pp.pretty(node.0) +} + +///| +pub fn NodeTable::empty() -> NodeTable { + {} +} + +///| +#warnings("-unused_constructor") +enum TokenGroup { + Token(Key?) + Table(NodeTable) + TokenArray(NodeArray) + TableArray(NodeTableArray) +} + +///| +pub impl Pretty for TokenGroup with fn pretty(group) { + match group { + Token(k) => @pp.ctor("Token", [@pp.pretty(k)]) + Table(t) => @pp.ctor("Table", [@pp.pretty(t)]) + TokenArray(a) => @pp.ctor("TokenArray", [@pp.pretty(a)]) + TableArray(a) => @pp.ctor("TableArray", [@pp.pretty(a)]) + } +} + +///| +pub fn NodeTable::new(token_keys : Map[String, TokenGroup]) -> NodeTable { + token_keys +} + +///| +pub(all) enum Key { + Token(Location) + Node(Location) +} derive(Eq, Hash, Compare) + +///| +pub impl Pretty for Key with fn pretty(self) { + match self { + Token(loc) | Node(loc) => text(loc.to_string()) + } +} + +///| +pub fn Key::loc(self : Self) -> Location { + match self { + Token(loc) | Node(loc) => loc + } +} + +///| +pub fn NodeTable::get_token(self : Self, name : String) -> Key? { + match self.0.get(name) { + Some(Token(key)) => key + _ => None + } +} + +///| +pub fn NodeTable::get_table(self : Self, name : String) -> NodeTable { + match self.0.get(name) { + Some(Table(tbl)) => tbl + _ => empty_table + } +} + +///| +pub fn NodeTable::get_table_array(self : Self, name : String) -> NodeTableArray { + match self.0.get(name) { + Some(TableArray(arr)) => arr + _ => empty_table_array + } +} + +///| +pub fn NodeTable::get_array(self : Self, name : String) -> NodeArray { + match self.0.get(name) { + Some(TokenArray(arr)) => arr + _ => empty_node_array + } +} diff --git a/fmt/internal/comment/token_table_array.mbt b/fmt/internal/comment/token_table_array.mbt new file mode 100644 index 00000000..72e68e62 --- /dev/null +++ b/fmt/internal/comment/token_table_array.mbt @@ -0,0 +1,20 @@ +///| +struct NodeTableArray(Array[NodeTable]) + +///| +let empty_table_array : NodeTableArray = NodeTableArray::empty() + +///| +pub fn NodeTableArray::empty() -> NodeTableArray { + [] +} + +///| +pub fn NodeTableArray::op_get(self : Self, idx : Int) -> NodeTable { + self.0.get(idx).unwrap_or_else(fn() { NodeTable::empty() }) +} + +///| +pub impl Pretty for NodeTableArray with fn pretty(self) { + @pp.pretty(self.0) +} diff --git a/fmt/internal/format/attach_docstring.mbt b/fmt/internal/format/attach_docstring.mbt new file mode 100644 index 00000000..6d0f205d --- /dev/null +++ b/fmt/internal/format/attach_docstring.mbt @@ -0,0 +1,77 @@ +///| +fn attach_docstrings( + docstrings : Array[List[(Location, @tokens.Comment)]], + toplevels : @syntax.Impls, +) -> Unit { + + // skip the docstring before the posisiton, return the last skipped docstring. + fn skip_docstrings_before( + pos : @basic.Position, + ) -> List[(Location, @tokens.Comment)]? { + let mut previous = None + while docstrings.last() is Some(comments) && + comments.last().unwrap().0.end <= pos { + previous = docstrings.pop() + } + previous + } + + fn make_doc( + comments : List[(@basic.Location, @tokens.Comment)], + ) -> @syntax.DocString { + { + content: comments.map(p => { + match p.1.content { + [.. "///|", .. remain] | [.. "///", .. remain] => remain.to_owned() + _ => panic() + } + }), + loc: { + start: comments.head().unwrap().0.start, + end: comments.last().unwrap().0.end, + }, + } + } + + for toplevel in toplevels { + let previous = skip_docstrings_before(toplevel.loc().start) + let doc = previous.map(make_doc).unwrap_or(@syntax.DocString::empty()) + match toplevel { + TopTypeDef(td) => { + td.doc = doc + match td.components { + // there is no docstring inside the types + Abstract | Alias(_) | Error(NoPayload) | TupleStruct(_) => () + // handle docstring before the enum/suberror constructor and struct fields + Error(EnumPayload(constrs)) + | Variant(constrs) + | ExtensibleEnum(constrs) + | ExtendEnum(constructors=constrs, ..) => + constrs.each(constr => { + let previous = skip_docstrings_before(constr.loc.start) + .map(make_doc) + .unwrap_or(@syntax.DocString::empty()) + constr.doc = previous + }) + Record(fields~, ..) => + fields.each(field => { + let previous = skip_docstrings_before(field.loc.start) + .map(make_doc) + .unwrap_or(@syntax.DocString::empty()) + field.doc = previous + }) + } + } + TopFuncDef(fun_decl~, ..) => fun_decl.doc = doc + TopLetDef(..) as ld => ld.doc = doc + TopExpr(..) => () + TopImplRelation(..) as imp => imp.doc = doc + TopTest(..) as test_ => test_.doc = doc + TopTrait(decl) => decl.doc = doc + TopUsing(..) as decl => decl.doc = doc + TopView(..) as view => view.doc = doc + TopImpl(..) as imp => imp.doc = doc + } + skip_docstrings_before(toplevel.loc().end) |> ignore + } +} diff --git a/fmt/internal/format/attribute2doc.mbt b/fmt/internal/format/attribute2doc.mbt new file mode 100644 index 00000000..57b27a4c --- /dev/null +++ b/fmt/internal/format/attribute2doc.mbt @@ -0,0 +1,45 @@ +///| +fn attr_id2doc(id : @attribute.Id) -> Doc { + let { qual, name } = id + let qual = match qual { + None => empty + Some(q) => text(q) + text(".") + } + qual + text(name) +} + +///| +fn attr_prop2doc(prop : @attribute.Prop) -> Doc { + match prop { + Labeled(label, expr) => text(label) + char('=') + attr_expr2doc(expr) + Expr(expr) => attr_expr2doc(expr) + } +} + +///| +fn attr_expr2doc(expr : @attribute.Expr) -> Doc { + match expr { + Apply(id, props) => + attr_id2doc(id) + + char('(') + + separate_map(props, char(',') + space, attr_prop2doc) + + char(')') + String(s) => @pp.string(s) + Ident(id) => attr_id2doc(id) + Bool(bool) => if bool { text("true") } else { text("false") } + } +} + +///| +fn attribute2doc(attr : Attribute) -> Doc { + let { raw, parsed, .. } = attr + match parsed { + None => text(raw) + Some(expr) => text("#") + attr_expr2doc(expr) + } +} + +///| +fn Fmt::attributes(_ : Fmt, attrs : List[Attribute]) -> Doc { + concat_map(attrs, x => attribute2doc(x) + hardline) +} diff --git a/fmt/internal/format/compose.mbt b/fmt/internal/format/compose.mbt new file mode 100644 index 00000000..66ca1a0c --- /dev/null +++ b/fmt/internal/format/compose.mbt @@ -0,0 +1,351 @@ +///| +/// Represents the Doc of an expression that cannot be directly composed with +/// other expression parts. +/// +/// For example, in `(1 + 2) * 4`, when handling the left-hand side `(1 + 2)` +/// with `Fmt::expr`, it returns a `RawExprDoc` representing `1 + 2`, without +/// parentheses or braces included. Concatenating a `RawExprDoc` directly with +/// the `* 4` part or using `Fmt::expr` would produce incorrect formatting. +/// Instead, use the `Fmt::compose` or `Fmt::compose_*` functions to combine +/// expressions properly. +/// +/// See design.md#compose-expression for more details. +priv enum RawExprDoc { + /// Any syntatic structure that better to split into two part. + /// + /// For example: + /// - f(args) + /// - if cond { a } else { b } + Collapsible(Doc, Doc) + /// { x }, [ x ], ( x ), (value : TypeAnnotation) etc. + BlockLike(Doc) + /// Other case + Normal(Doc) +} + +///| +fn RawExprDoc::to_doc(self : Self) -> Doc { + match self { + Normal(doc) | BlockLike(doc) => doc + Collapsible(doc1, doc2) => doc1 + doc2 + } +} + +///| +fn Fmt::compose(fmt : Self, expr : @syntax.Expr, ctx : Context) -> Doc { + let kind = expr.classify() + let doc = fmt.expr(expr).to_doc() + match infer_parens(ctx, kind) { + NoParen => doc + NeedBraces => braces(doc) + NeedParens => parens(doc) + GroupedParens => group(parens(doc)) + } +} + +///| +fn Fmt::compose_assign_rhs( + fmt : Self, + expr : @syntax.Expr, + ctx : Context, +) -> Doc { + let kind = expr.classify() + let raw = fmt.expr(expr) + let rhs = match infer_parens(ctx, kind) { + NoParen => + match raw { + Normal(doc) | BlockLike(doc) => doc + Collapsible(head, body) => { + let rhs_req = head.requirement() + body.requirement() + let head_req = head.requirement() + dynamic(ctx => { + let current_remain = Requirement::Space( + maximum(0, ctx.width - ctx.indent), + ) + let next_remain = Requirement::Space( + maximum(0, ctx.width - ctx.next_indent), + ) + if current_remain >= rhs_req { + // flatten style + group(head + body) + } else if current_remain >= head_req && + !(next_remain >= rhs_req + Space(2)) { + // Composed style + // + // Bad case: + // + // 1. To avoid following bad cases, if the head is not empty and + // rhs fits in next line, use dangling style + // + // ``` + // VeryLongPattern | VeryLongPattern | LongPattern | LongPattern => func( + // x, + // ) + // VeryLongPattern | VeryLongPattern | LongPattern | LongPattern => + // [a, b, c, d, e, f, g, h] + // ``` + // + // 2. To avoid following bad cases, if the rhs need more than one line, + // use normal style + // + // ``` + // match e { + // P => { + // long_expr + // } + // VeryLongPattern | VeryLongPattern | LongPattern => if condition { + // long_expr + // } else { + // long_expr + // } + // } + // ``` + group(head + body) + } else if next_remain >= head_req + Space(2) { + // dangling style + nest(hardline + group(head + body)) + } else { + // normal style + group(braces(head + body, force_newline=true)) + } + }) + } + } + NeedBraces => braces(raw.to_doc()) + NeedParens => parens(raw.to_doc()) + GroupedParens => group(parens(raw.to_doc())) + } + space + rhs +} + +///| +fn Fmt::compose_return_break_continue_rhs( + fmt : Self, + expr : @syntax.Expr, + ctx : Context, +) -> Doc { + fmt.compose(expr, ctx) +} + +///| +fn flatten_style(doc : Doc) -> Doc { + group(doc) +} + +///| +fn composed_style(doc : Doc) -> Doc { + group(doc) +} + +///| +fn dangling_style(doc : Doc) -> Doc { + nest(hardline + group(doc)) +} + +///| +fn normal_style(doc : Doc) -> Doc { + group(braces(doc, force_newline=true)) +} + +///| +fn Fmt::compose_case_rhs(fmt : Self, expr : @syntax.Expr, ctx : Context) -> Doc { + let kind = expr.classify() + let raw = fmt.expr(expr) + let rhs = match infer_parens(ctx, kind) { + NoParen => + match raw { + // For a normal RawExprDoc, we always wrap it with braces when it cannot + // compose with `=>` in the same line. + // + // Bad case: + // If the case rhs is a identifier, to avoid ambiguous with record containing + // one punning field, we should avoid the `{}`. + Normal(doc) => + if expr is Ident(_) { + nest(softline + doc) + } else { + group(braces_if_not_flatten(doc)) + } + + // For a block-like RawExprDoc, we always compose it directly with `composed_style`. + // This is because block-like structures usually already have their own paren/braces; + // adding extra braces or line wraps causes redundant indentation. + // + // Bad case: + // ``` + // match e { + // VeryLongPattern | VeryLongPattern | LongPattern | LongPattern => + // [a, b] + // VeryLongPattern | VeryLongPattern | LongPattern | LongPattern => + // [ + // long_a, + // long_b, + // ] + // } + // ``` + // + // Ideally: + // ``` + // match e { + // VeryLongPattern | VeryLongPattern | LongPattern | LongPattern => [ + // a, + // b, + // ] + // VeryLongPattern | VeryLongPattern | LongPattern | LongPattern => [ + // long_a, + // long_b, + // ] + // } + // ``` + BlockLike(doc) => composed_style(doc) + + // For a collapsible RawExprDoc, we choose different styles based on the + // remaining space in the current and next lines. + // + // Bad case: + // 1. if rhs fits in next line, use dangling style + // + // Bad format result: + // ``` + // VeryLongPattern | VeryLongPattern | LongPattern | LongPattern => func( + // x, + // ) + // ``` + // + // Ideal format result: + // ``` + // VeryLongPattern | VeryLongPattern | LongPattern | LongPattern => + // func(x) + // ``` + // + // 2. if the rhs need more than one line, use normal style + // + // This avoids confusing layouts, especially in nested control + // flow expressions, when the control flow bodies have more than one + // part that looks like a block. + // + // + // Bad format result: + // ``` + // match e { + // VeryLongPattern | VeryLongPattern => try { + // long_expr + // } catch { + // long_expr + // } noraise { + // long_expr + // } + // } + // ``` + // + // Ideal format result: + // ``` + // match e { + // VeryLongPattern | VeryLongPattern => { + // try { + // long_expr + // } catch { + // long_expr + // } noraise { + // long_expr + // } + // } + // } + // ``` + Collapsible(head, body) => { + let rhs_req = (head + body).requirement() + dynamic(ctx => { + let current_remain = ctx.current_remain() + let next_remain = ctx.next_remain() + if current_remain >= rhs_req { + flatten_style(head + body) + } else if next_remain >= rhs_req + Space(2) { + dangling_style(head + body) + } else { + normal_style(head + body) + } + }) + } + } + NeedBraces => braces(raw.to_doc()) + NeedParens => parens(raw.to_doc()) + GroupedParens => group(parens(raw.to_doc())) + } + space + rhs +} + +///| +fn Fmt::compose_arrow_rhs(fmt : Self, expr : @syntax.Expr) -> Doc { + let kind = expr.classify() + let raw = fmt.expr(expr) + let rhs = match infer_parens(ExprStmt, kind) { + NoParen => + match raw { + // Bad case: + // In some cases, the trailing callback rule applies. If the callback is an arrow + // function and the body is too long to fit on the current line, it will wrap + // inside. This looks strange if the body is a control flow expression with more + // than one `{}`, or a nested function call: + // + // ``` + // function_call(arg1, arg2, (x) => if cond { + // long_expr + // } else { + // long_expr + // }) + // + // function_call(arg1, arg2, (x) => + // callback( + // long_arg1, + // long_arg2, + // )) + // ``` + // + // We should always add `{}` if the body requires more than one line to make + // it look better: + // + // ``` + // function_call(arg1, arg2, (x) => { + // if cond { + // long_expr + // } else { + // long_expr + // } + // }) + // + // function_call(arg1, arg2, (x) => { + // callback( + // long_arg1, + // long_arg2, + // ) + // }) + // ``` + // Therefore we didn't use the composed style here. + Normal(_) | Collapsible(_, _) => { + let doc = raw.to_doc() + match expr { + // Bad case: In arrow function: the body cannot be break, continue or return + Break(_) | Continue(_) | Return(_) => + group(braces(doc, force_newline=false)) + // Bad case: ambiguous with record containing one punning field + // ``` + // function_call(arg1, arg2, (x) => { + // long_id + // }) + // ``` + // The long_id need to be surround with `()`. + Ident(_) => { + let lb = switch(empty, char('(')) + let rb = switch(empty, char(')')) + group(braces_if_not_flatten(lb + doc + rb)) + } + _ => group(braces_if_not_flatten(doc)) + } + } + BlockLike(doc) => composed_style(doc) + } + NeedBraces => braces(raw.to_doc()) + NeedParens | GroupedParens => panic() + } + space + rhs +} diff --git a/fmt/internal/format/context.mbt b/fmt/internal/format/context.mbt new file mode 100644 index 00000000..3ce3ddf4 --- /dev/null +++ b/fmt/internal/format/context.mbt @@ -0,0 +1,275 @@ +///| +priv enum OperandPosition { + LeftOperand + RightOperand +} derive(Eq) + +///| +priv enum InfixCtx { + InfixOperand(op~ : String, pos~ : OperandPosition) + Other +} + +///| +fn precedence_of_operator(op : String) -> Int { + match op { + "||" => 1 + "&&" => 2 + "|" => 3 + "^" => 4 + "&" => 5 + ">" | "<" | "==" | "!=" | "<=" | "=>" => 300 + "<<" | ">>" => 400 + "+" | "-" => 500 + "*" | "/" | "%" => 600 + "..<" | "..=" => 900 + _ => panic() + } +} + +///| +priv enum Assoc { + Left + Right +} derive(Eq) + +///| +fn assoc_of_operator(op : String) -> Assoc { + match op { + "||" | "&&" => Right + "|" | "^" | "&" => Left + ">" | "<" | "==" | "!=" | "<=" | ">=" => Left + "<<" | ">>" => Left + "+" | "-" => Left + "*" | "/" | "%" => Left + "..=" | "..<" => Left + _ => panic() + } +} + +///| +/// Context classify the formatting context of an expression, e.g., what kind +/// of expression it expects and the position of the expression in the code. +/// These contexts represent the expressions that can be parsed by +/// +/// specific grammar rules in moonbitlang/parser/yacc_parser/parser.mbty: +/// +/// | context | rules in menhir parser | +/// | --------------- | ---------------------------- | +/// | Simple | simple_expr | +/// | Prefix | prefix_expr | +/// | Range | range_expr | +/// | Infix | infix_expr | +/// | Pipeline | pipe_expr | +/// | Expression | expr | +/// | ExprStatement | expr_statement | +/// | Statement | statement | +/// | Any | all rules above | +/// +/// **Ideally, the relationship above and the context passed to Fmt::expr should +/// faithfully reflect the grammar in the Menhir parser.** +/// +/// Given code `e1 * e2`, the context of e2 is `Infix (Rhs "*")`, this means: +/// +/// - if e2 is an expression that cannot parsed by `infix_expr`, it should be +/// wrapped in braces. +/// +/// - if e2 is a infix expression, and the operator precedence is lower than `*`, +/// it should be wrapped in parentheses. +/// +priv enum Context { + Stmt + ExprStmt + Expr + Pipeline + Infix(InfixCtx) + Range + Prefix + Simple +} + +///| +/// `expr_kind` classifies the kind of expression +priv enum ExprKind { + SimpleKind + PrefixKind + PostfixKind(String) + InfixKind(String) + PipeKind + /// loop, for, foreach, while, try, if, simple_try, arrow_fn + ControlFlowAndArrowFnKind + /// local let, letrec, let mut, local fn, guard + ExprStmtKind + StmtKind +} + +///| +fn @syntax.Expr::classify(expr : Self) -> ExprKind { + match expr { + Group(_) => panic() + Apply(_) + | Array(_) + | ArraySpread(_) + | ListComprehension(_) + | ArrayGet(_) + | Constant(_) + | MultilineString(_) + | Interp(_) + | Constraint(_) + | Constr(_) + | Ident(_) + | Tuple(_) + | Record(_) + | RecordUpdate(_) + | Field(_) + | ArrayGetSlice(_) + | Method(_) + | Map(_) + | Hole(kind=Synthesized | Incomplete, ..) + | Unit(_) + | Function(func={ kind: Lambda, .. }, ..) + | DotApply(_) => SimpleKind + Unary(_) => PrefixKind + As(_) => PostfixKind("as") + Is(_) | IsLexMatch(_) => PostfixKind("is") + RegexMatch(_) => PostfixKind("=~") + Infix(op~, ..) => + match op.name { + Ident(name~) => InfixKind(name) + _ => panic() + } + Pipe(_) | RevPipe(_) => PipeKind + TemplateWriting(_) => PostfixKind("<+") + Quantifier(_) => PrefixKind + Implies(_) => PostfixKind("→") + Match(_) + | LexMatch(_) + | If(_) + | For(_) + | ForEach(_) + | While(_) + | Try(_) + | TryOperator(_) + | Function(func={ kind: Arrow, .. }, ..) => ControlFlowAndArrowFnKind + Mutate(_) + | Assign(_) + | ArraySet(_) + | ArrayAugmentedSet(_) + | Return(_) + | Break(_) + | Continue(_) + | Raise(_) + | ProofAssert(_) + | ProofLet(_) + | Hole(kind=Todo, ..) => ExprStmtKind + Guard(_) + | Defer(_) + | LetFn(_) + | LetAnd(_) + | Let(_) + | Sequence(_) + | LetMut(_) + | StaticAssert(_) => StmtKind + } +} + +///| +/// Consider a sequence of mixed infix expression `e1 op1 e2 op2 e3`, +fn is_ambiguous(op1 : String, op2 : String) -> Bool { + ignore((op1, op2)) + false //TODO +} + +///| +priv enum ParenResult { + GroupedParens + NeedBraces + NeedParens + NoParen +} + +///| +/// Infer whether this kind of expression needs to be wrapped with `()` or `{}` +/// in this context. +fn infer_parens(context : Context, kind : ExprKind) -> ParenResult { + let braces = NeedBraces + let parens = NeedParens + match (context, kind) { + (Stmt, _) => NoParen + (ExprStmt, StmtKind) => braces + ( + ExprStmt, + ExprStmtKind + | PipeKind + | ControlFlowAndArrowFnKind + | InfixKind(_) + | PostfixKind(_) + | PrefixKind + | SimpleKind, + ) => NoParen + (Expr, StmtKind | ExprStmtKind) => braces + ( + Expr, + ControlFlowAndArrowFnKind + | PipeKind + | InfixKind(_) + | PostfixKind(_) + | PrefixKind + | SimpleKind, + ) => NoParen + (Pipeline, StmtKind | ExprStmtKind) => braces + (Pipeline, ControlFlowAndArrowFnKind) => parens + (Pipeline, InfixKind(_) | PostfixKind(_)) => NoParen + (Pipeline, PipeKind | PrefixKind | SimpleKind) => NoParen + (Infix(_), StmtKind | ExprStmtKind) => braces + (Infix(_), ControlFlowAndArrowFnKind | PipeKind) => parens + (Infix(ctx), InfixKind(inner_op)) => + match ctx { + Other => NoParen + InfixOperand(op=outer_op, pos~) => { + let outer_prec = precedence_of_operator(outer_op) + let outer_assoc = assoc_of_operator(outer_op) + let inner_prec = precedence_of_operator(inner_op) + let keep_user_parens = outer_prec > inner_prec || + ( + outer_prec == inner_prec && + ( + (pos == LeftOperand && outer_assoc == Right) || + (pos == RightOperand && outer_assoc == Left) + ) + ) + if keep_user_parens || is_ambiguous(outer_op, inner_op) { + GroupedParens + } else { + NoParen + } + } + } + (Infix(ctx), PostfixKind(op2)) => + match ctx { + Other => NoParen + InfixOperand(op=op1, ..) => + if is_ambiguous(op1, op2) { + parens + } else { + NoParen + } + } + (Infix(_), PrefixKind | SimpleKind) => NoParen + (Range, StmtKind | ExprStmtKind) => braces + (Range, ControlFlowAndArrowFnKind | PipeKind | InfixKind(_) | PostfixKind(_) + ) => parens + (Range, PrefixKind | SimpleKind) => NoParen + (Prefix | Simple, StmtKind | ExprStmtKind) => braces + ( + Prefix + | Simple, + ControlFlowAndArrowFnKind + | PipeKind + | InfixKind(_) + | PostfixKind(_), + ) => parens + (Prefix | Simple, PrefixKind) => parens + (Prefix | Simple, SimpleKind) => NoParen + } +} diff --git a/fmt/internal/format/format.mbt b/fmt/internal/format/format.mbt new file mode 100644 index 00000000..c9741ddf --- /dev/null +++ b/fmt/internal/format/format.mbt @@ -0,0 +1,55 @@ +///| +pub fn impls_to_string(impls : @syntax.Impls) -> String { + impls + |> RemoveGroup::process + |> x => { Fmt::new().impls(x) } + |> @pp.render(width=80) +} + +///| +pub fn format(source : String, block_line? : Bool = true) -> String { + let { tokens, docstrings, .. } = @lexer.tokens_from_string( + comment=true, + source, + ) + let (impls, diag) = @handrolled_parser.parse(tokens) + attach_docstrings(docstrings, impls) + ignore(diag) + impls + |> RemoveGroup::process + |> x => { Fmt::new(block_line~).impls(x) } + |> @pp.render(width=80) +} + +///| +/// for testing purposes, returns also the tokens +pub fn parse( + source : String, +) -> (@syntax.Impls, Array[@basic.Report], @tokens.Triples) { + let { tokens, docstrings, .. } = @lexer.tokens_from_string( + comment=true, + source, + ) + let (impls, diag) = @handrolled_parser.parse(tokens) + attach_docstrings(docstrings, impls) + let impls = impls |> RemoveGroup::process + (impls, diag, tokens) +} + +///| +pub fn debug_comment(source : String) -> (String, String) { + let { tokens, .. } = @lexer.tokens_from_string(comment=true, source) + let (impls, _) = @handrolled_parser.parse(tokens) + let mapper = @comment.Mapper::new(tokens, impls) + let attach_info = mapper.to_debug_info() + let ast = impls + |> RemoveGroup::process + |> x => { Fmt::new(mapper~).impls(x) } + |> @pp.render(width=80) + (attach_info, ast) +} + +///| +fn init { + ignore(debug_comment) +} diff --git a/fmt/internal/format/import.mbt b/fmt/internal/format/import.mbt new file mode 100644 index 00000000..1c263504 --- /dev/null +++ b/fmt/internal/format/import.mbt @@ -0,0 +1,31 @@ +///| +using @pp { + group, + text, + char, + nest, + switch, + dynamic, + softline, + space, + type Document, + type Requirement, +} + +///| +type Doc = Document + +///| +using @basic {type Location} + +///| +using @list {type List} + +///| +using @syntax {trait MapVisitor} + +///| +using @attribute {type Attribute} + +///| +using @cmp {maximum} diff --git a/fmt/internal/format/moon.pkg b/fmt/internal/format/moon.pkg new file mode 100644 index 00000000..47dccb48 --- /dev/null +++ b/fmt/internal/format/moon.pkg @@ -0,0 +1,16 @@ +import { + "moonbitlang/parser/syntax", + "moonbitlang/parser/basic", + "moonbitlang/parser/tokens", + "moonbit-community/prettyprinter" @pp, + "moonbitlang/parser/lexer", + "moonbitlang/parser/handrolled_parser", + "moonbitlang/parser/fmt/internal/comment", + "moonbitlang/parser/attribute", + "moonbitlang/core/cmp", + "moonbitlang/core/list", +} + +import { + "moonbitlang/core/list", +} for "wbtest" diff --git a/fmt/internal/format/pkg.generated.mbti b/fmt/internal/format/pkg.generated.mbti new file mode 100644 index 00000000..1fc89b04 --- /dev/null +++ b/fmt/internal/format/pkg.generated.mbti @@ -0,0 +1,27 @@ +// Generated using `moon info`, DON'T EDIT IT +package "moonbitlang/parser/fmt/internal/format" + +import { + "moonbitlang/core/list", + "moonbitlang/parser/basic", + "moonbitlang/parser/syntax", + "moonbitlang/parser/tokens", +} + +// Values +pub fn debug_comment(String) -> (String, String) + +pub fn format(String, block_line? : Bool) -> String + +pub fn impls_to_string(@list.List[@syntax.Impl]) -> String + +pub fn parse(String) -> (@list.List[@syntax.Impl], Array[@basic.Report], Array[(@tokens.Token, @basic.Position, @basic.Position)]) + +// Errors + +// Types and methods + +// Type aliases + +// Traits + diff --git a/fmt/internal/format/remove_group.mbt b/fmt/internal/format/remove_group.mbt new file mode 100644 index 00000000..f600d61e --- /dev/null +++ b/fmt/internal/format/remove_group.mbt @@ -0,0 +1,19 @@ +///| +priv struct RemoveGroup {} + +///| +impl MapVisitor for RemoveGroup with fn visit_Expr_Group( + self, + expr~, + group~, + loc~, +) { + ignore((group, loc)) + self.visit_Expr(expr) +} + +///| +fn RemoveGroup::process(impls : @syntax.Impls) -> @syntax.Impls { + let self = RemoveGroup::{ } + impls.map(x => self.visit_Impl(x)) +} diff --git a/fmt/internal/format/syntax2doc.mbt b/fmt/internal/format/syntax2doc.mbt new file mode 100644 index 00000000..9768efdb --- /dev/null +++ b/fmt/internal/format/syntax2doc.mbt @@ -0,0 +1,2603 @@ +// PLEASE READ BEFORE EDITING: +// +// 1. The syntax2doc is implemented in a divide-and-conquer style +// +// 2. Do not emit trailing space or newline +// Due to the behavior of the `nest` primitive, **`Hardline` represents the +// start of a line**, not its end. Therefore, each piece of code is responsible +// for handling its own leading newline or space, and should NOT emit trailing +// newlines and spaces (unless they are explicitly optional in the AST). +// +// 3. Consistent printing style is important for a formatter +// In MoonBit, we do not strictly enforce the "keep your code DRY" principle. +// Duplicating code is acceptable if abstraction becomes too complex. +// However, in formatter, maintaining multiple copies makes it difficult to +// ensure consistent printing style. Before duplicating code, please consider +// reusing existing helper functions. +// + +///| +priv struct Fmt { + mapper : @comment.Mapper? + block_line : Bool +} + +///| +fn fmt_comment(comment : @comment.Comment) -> Doc { + match comment.kind { + Blank => @pp.mandatory_line + Block(_) => panic() + Line(l) => @pp.mandatory_space + text(l) + @pp.mandatory_line + } +} + +///| +fn fmt_comments(comments : Array[@comment.Comment]) -> Doc { + comments.map(fmt_comment).fold(init=empty, Doc::add) +} + +///| +fn Fmt::wrap_comment(fmt : Self, key : @comment.Key?, doc : Doc) -> Doc { + let (a, b) = fmt.comments(key) + a + doc + b +} + +///| +// fn Fmt::has_comment(fmt : Self, key : @comment.Key?) -> Bool { +// match (fmt.mapper, key) { +// (Some(mapper), Some(k)) => mapper.has_comment(key) +// _ => false +// } +// } + +///| +// fn Fmt::has_trailing_comment(fmt : Self, key : @comment.Key?) -> Bool { +// match fmt.mapper { +// None => false +// Some(m) => m.has_leading_comment(key) +// } +// } + +///| +// fn Fmt::has_leading_comment(fmt : Self, key : @comment.Key?) -> Bool { +// match fmt.mapper { +// None => false +// Some(m) => m.has_trailing_comment(key) +// } +// } + +///| +/// Gets the formatted comments after to the given node's key. +fn Fmt::before_comment(fmt : Self, key : @comment.Key?) -> Doc { + match (fmt.mapper, key) { + (Some(mapper), Some(k)) => + mapper.consume_comments(key, c => c.start < k.loc().start) |> fmt_comments + _ => empty + } +} + +///| +/// Gets the formatted comments before to the given node's key. +fn Fmt::after_comment(fmt : Self, key : @comment.Key?) -> Doc { + match (fmt.mapper, key) { + (Some(mapper), Some(k)) => + mapper.consume_comments(key, c => c.start >= k.loc().start) + |> fmt_comments + _ => empty + } +} + +///| +/// Gets the formatted comments attached to the given node's key. +/// +/// Returns a tuple of two `Doc` values representing the comments before and +/// after the node. +fn Fmt::comments(fmt : Self, key : @comment.Key?) -> (Doc, Doc) { + let doc1 = fmt.before_comment(key) + let doc2 = fmt.after_comment(key) + (doc1, doc2) +} + +///| +/// Gets the token table for the node. +fn Fmt::get_table(fmt : Self, node : Location) -> @comment.NodeTable { + match fmt.mapper { + Some(tbl) => tbl.get_table(node) + None => @comment.NodeTable::empty() + } +} + +///| +fn Fmt::new(mapper? : @comment.Mapper, block_line? : Bool = true) -> Self { + { mapper, block_line } +} + +///| +fn Fmt::impls(fmt : Self, impls : List[@syntax.Impl]) -> Doc { + concat_map(impls, x => fmt.impl_(x) + hardline + hardline) //TODO: handle comments between impls +} + +///| +fn Fmt::return_and_error_type_annotation( + fmt : Self, + return_ty : @syntax.Type?, + err_ty : @syntax.ErrorType, +) -> Doc { + let arrow = if return_ty is None && err_ty is NoErrorType { + empty + } else { + space + text("->") + } + let return_type = match return_ty { + None => empty + Some(ty) => space + fmt.ty(ty) + } + let error_type = fmt.error_type(err_ty) + arrow + return_type + error_type +} + +///| +fn Fmt::impl_(fmt : Self, toplevel : @syntax.Impl) -> Doc { + match toplevel { + TopView( + quantifiers~, + source_ty~, + view_type_name~, + view_type_loc=_, + view_constrs~, + view_func_name~, + parameters~, + params_loc=_, + body~, + vis~, + loc=_, + attrs~, + doc~ + ) => { + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let vis = fmt.visibility(vis) + let enumview_ = text("enumview") + let quantifiers = fmt.type_var_binders( + quantifiers, + lbracket=None, + commas=@comment.NodeArray::empty(), + tvar_tables=@comment.NodeTableArray::empty(), + rbracket=None, + ) + let view_name = space + text(view_type_name) + let semi = switch(char(';'), empty) + let constrs = if view_constrs.is_empty() { + space + text("{") + hardline + text("}") + } else { + space + + char('{') + + nest( + hardline + series1(view_constrs.map(x => fmt.constr_decl(x)), semi), + ) + + hardline + + char('}') + } + let for_ = space + text("for") + space + fmt.ty(source_ty) + let with_ = space + text("with") + let func_name = space + fmt.binder(view_func_name) + let params = group( + fmt.parameters( + parameters, + lparen=None, + param_tables=@comment.NodeTableArray::empty(), + commas=@comment.NodeArray::empty(), + rparen=None, + ), + ) + let body = space + fmt.expr_block(body, force_newline=true) + doc + + attrs + + vis + + enumview_ + + quantifiers + + view_name + + constrs + + for_ + + with_ + + func_name + + params + + body + } + TopImpl( + self_ty~, + trait_~, + method_name~, + quantifiers~, + params~, + ret_ty~, + err_ty~, + body~, + vis~, + attrs~, + doc~, + loc=_ + ) => { + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let vis = fmt.visibility(vis) + let impl_ = text("impl") + let quantifiers = fmt.type_var_binders( + quantifiers, + lbracket=None, + commas=@comment.NodeArray::empty(), + tvar_tables=@comment.NodeTableArray::empty(), + rbracket=None, + ) + let trait_ = fmt.type_name(trait_) + let for_part = match self_ty { + None => empty + Some(ty) => space + text("for") + space + fmt.ty(ty) + } + let with_ = space + text("with") + let method_name = space + text(method_name.name) + let params = group( + fmt.parameters( + params, + lparen=None, + param_tables=@comment.NodeTableArray::empty(), + commas=@comment.NodeArray::empty(), + rparen=None, + ), + ) + let result = fmt.return_and_error_type_annotation(ret_ty, err_ty) + let body = match body { + DeclNone => empty + DeclBody(expr~) => space + fmt.expr_block(expr, force_newline=true) + DeclStubs(stubs) => { + let stubs = match stubs { + Import(module_name~, func_name~, ..) => { + let module_name = double_quoted(text(module_name)) + let func_name = double_quoted(text(func_name)) + space + module_name + space + func_name + } + Embedded(language~, code~) => { + guard language is None else { + abort("language must be None in impl stub.") + } + let code = fmt.embedded_code(code) + space + code + } + } + space + text("=") + stubs + } + } + doc + + attrs + + vis + + impl_ + + quantifiers + + space + + trait_ + + for_part + + with_ + + method_name + + params + + result + + body + } + TopTrait({ name, supers, methods, vis, attrs, doc, loc: _, is_declare }) => { + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let declare_ = if is_declare { text("declare") + space } else { empty } + let vis = fmt.visibility(vis) + let trait_ = text("trait") + let name = space + fmt.binder(name) + let supers = match supers { + Empty => empty + More(_) => + space + + colon + + group( + nest( + line + + series1( + supers.map(x => fmt.type_var_constraint(x)), + space + char('+'), + trailing=AlwaysHidden, + ), + ), + ) + } + let methods = space + + braces( + series1(methods.map(x => fmt.trait_method(x)), empty), + force_newline=true, + ) + doc + attrs + declare_ + vis + trait_ + name + supers + methods + } + TopLetDef( + binder~, + ty~, + expr~, + vis~, + is_constant~, + attrs~, + doc~, + is_declare~, + .. + ) => { + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let declare_ = if is_declare { text("declare") + space } else { empty } + let vis = fmt.visibility(vis) + let let_ = text(if is_constant { "const" } else { "let" }) + let binder = space + fmt.binder(binder) + let ty = match ty { + None => empty + Some(ty) => space + colon + fmt.ty(ty) + } + let rhs = space + char('=') + fmt.compose(expr, Expr) // TODO: compose_rhs + doc + attrs + declare_ + vis + let_ + binder + ty + rhs + } + TopTypeDef(typedecl) => fmt.typedecl(typedecl) + TopFuncDef(fun_decl~, decl_body~, where_clause~, loc~) => { + let table = fmt.get_table(loc) + let { + type_name, + name, + is_async, + decl_params, + quantifiers, + return_type, + error_type, + vis, + attrs, + doc, + .., + } = fun_decl + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let vis = fmt.visibility(vis) + let fn_ = fmt.wrap_comment(table.get_token("fn"), text("fn")) + let async_ = if is_async is Some(_) { + fmt.wrap_comment(table.get_token("async"), text("async") + space) + } else { + empty + } + let quantifiers = fmt.type_var_binders( + quantifiers, + lbracket=table.get_token("lbracket"), + commas=table.get_array("commas0"), + tvar_tables=table.get_table_array("type_var_binders"), + rbracket=table.get_token("rbracket"), + ) + let type_name_and_binder = match type_name { + None => space + fmt.binder(name) + Some(n) => space + fmt.type_name(n) + text("::") + fmt.binder(name) + } + let params = match decl_params { + None => empty + Some(xs) => + group( + fmt.parameters( + xs, + lparen=table.get_token("lparen"), + param_tables=table.get_table_array("parameters"), + commas=table.get_array("commas1"), + rparen=table.get_token("rparen"), + ), + ) + } + let result = fmt.return_and_error_type_annotation(return_type, error_type) + let (extern_, body) = match decl_body { + DeclNone => (empty, empty) + DeclBody(expr~) => (empty, space + fmt.expr_block(expr)) + DeclStubs(Import(module_name~, func_name~, ..)) => { + let equal = space + char('=') + let module_name = space + double_quoted(text(module_name)) + let func_name = space + double_quoted(text(func_name)) + (empty, equal + module_name + func_name) + } + DeclStubs(Embedded(language~, code~)) => { + let extern_ = match language { + None => empty + Some(s) => text("extern") + space + double_quoted(text(s)) + space + } + let equal = space + char('=') + let code = group(nest(line + fmt.embedded_code(code))) + (extern_, equal + code) + } + } + let layout = doc + + attrs + + vis + + extern_ + + async_ + + fn_ + + quantifiers + + type_name_and_binder + + params + + result + + fmt.where_clause(where_clause) + + body + fmt.wrap_comment(Some(Node(loc)), layout) + } + TopTest(expr~, name~, params~, attrs~, doc~, is_async~, ..) => { + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let test_ = if is_async is Some(_) { + text("async test") + } else { + text("test") + } + let name = match name { + None => empty + Some((s, _)) => space + double_quoted(text(s)) + } + let params = match params { + None => empty + Some(xs) => + space + + group( + fmt.parameters( + xs, + lparen=None, + param_tables=@comment.NodeTableArray::empty(), + commas=@comment.NodeArray::empty(), + rparen=None, + ), + ) + } + let body = space + fmt.expr_block(expr) + doc + attrs + test_ + name + params + body + } + TopExpr(expr~, is_main~, is_async~, ..) => { + let fn_ = if is_async is Some(_) { text("async fn") } else { text("fn") } + let name = space + text(if is_main { "main" } else { "init" }) + let expr = fmt.expr_block(expr) + fn_ + name + expr + } + TopImplRelation( + self_ty~, + trait_~, + quantifiers~, + vis~, + attrs~, + doc~, + is_declare~, + .. + ) => { + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let declare_ = if is_declare { text("declare") + space } else { empty } + let vis = fmt.visibility(vis) + let impl_ = text("impl") + let quantifiers = fmt.type_var_binders( + quantifiers, + lbracket=None, + commas=@comment.NodeArray::empty(), + tvar_tables=@comment.NodeTableArray::empty(), + rbracket=None, + ) + let self = space + fmt.ty(self_ty) + let for_ = space + text("for") + let trait_ = space + fmt.type_name(trait_) + doc + attrs + declare_ + vis + impl_ + quantifiers + trait_ + for_ + self + } + TopUsing(pkg~, names~, vis~, attrs~, doc~, loc=_) => { + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let vis = fmt.visibility(vis) + let using_ = text("using") + space + let pkg = char('@') + fmt.label(pkg) + fn using_item(item : (@syntax.AliasTarget, @syntax.UsingKind)) -> Doc { + let (target, kind) = item + let kind_prefix = match kind { + Value => empty + Type => text("type") + space + Trait => text("trait") + space + } + kind_prefix + fmt.alias_target(target) + } + + let targets = space + group(braces(series1(names.map(using_item), comma))) + doc + attrs + vis + using_ + pkg + targets + } + } +} + +///| +fn Fmt::embedded_code(fmt : Fmt, code : @syntax.EmbeddedCode) -> Doc { + ignore(fmt) //TODO: use fmt to access comments + match code { + CodeString(s) => double_quoted(text(s)) + CodeMultilineString(xs) => + group(separate_map(xs, hardline, s => text("#|" + s))) + } +} + +///| +fn Fmt::alias_target(fmt : Fmt, target : @syntax.AliasTarget) -> Doc { + let { binder, target } = target + let binder = fmt.binder(binder) + match target { + None => binder + Some(target) => fmt.label(target) + space + text("as") + space + binder + } +} + +///| +fn Fmt::type_var_constraint( + fmt : Fmt, + constraint : @syntax.TypeVarConstraint, +) -> Doc { + ignore(fmt) //TODO: use fmt to access comments + let { trait_, loc: _ } = constraint + longident(trait_) +} + +///| +fn Fmt::type_var_binder( + env : Fmt, + binder : @syntax.TypeVarBinder, + tvar_table~ : @comment.NodeTable, +) -> Doc { + let colon = tvar_table.get_token("colon") + let pluses = tvar_table.get_array("pluses") + let { name, constraints, name_loc: loc } = binder + let name = env.wrap_comment(Some(Node(loc)), text(name)) + let constraints = match constraints { + Empty => empty + More(_) => { + let colon = space + env.wrap_comment(colon, char(':')) + let mut traits = space + for i, constraint in constraints { + let plus = if i == 0 { + empty + } else { + env.wrap_comment(pluses[i], space + text("+")) + } + let constraint = space + env.type_var_constraint(constraint) + traits = traits + plus + constraint + } + colon + traits + } + } + name + constraints +} + +///| +// TODO: patch the parser +fn @syntax.TypeVarBinder::loc(self : Self) -> Location { + match self.constraints { + More(x, ..) => self.name_loc.merge(x.loc) + Empty => self.name_loc + } +} + +///| +fn Fmt::type_var_binders( + fmt : Fmt, + quants : List[@syntax.TypeVarBinder], + lbracket~ : @comment.Key?, + commas~ : @comment.NodeArray, + tvar_tables~ : @comment.NodeTableArray, + rbracket~ : @comment.Key?, +) -> Doc { + guard quants is More(_) else { return empty } + let sep = comma + let trailing = switch(empty, sep) + let mut acc = empty + for i, quant in quants { + let binder = fmt.type_var_binder(quant, tvar_table=tvar_tables[i]) + let constraints = if i == quants.length() - 1 { + let trailing_comment = fmt.after_comment(Some(Node(quant.loc()))) + let around_comments = fmt.wrap_comment(commas[i], empty) + trailing + trailing_comment + around_comments + } else { + let sep = fmt.wrap_comment(commas[i], sep) + sep + line + } + acc = acc + binder + constraints + } + let lp = char('[') + let rp = char(']') + let (before_l, after_l) = fmt.comments(lbracket) + let (before_r, after_r) = fmt.comments(rbracket) + group( + before_l + + lp + + nest(after_l + softline + acc + before_r) + + softline + + rp + + after_r, + ) +} + +///| +fn Fmt::trait_method(fmt : Fmt, meth : @syntax.TraitMethodDecl) -> Doc { + let { + name, + is_async, + quantifiers: _, + params, + return_type, + error_type, + loc: _, + has_default, + attrs: _, + } = meth + let async_ = if is_async is Some(_) { text("async") + space } else { empty } + let name = text(name.name) + let params = fmt.parameters( + params, + lparen=None, + rparen=None, + commas=@comment.NodeArray::empty(), + param_tables=@comment.NodeTableArray::empty(), + kind=TraitMethod, + ) + let arrow = space + text("->") + let return_type = match return_type { + None => empty + Some(ty) => space + fmt.ty(ty) + } + let error_type = fmt.error_type(error_type) + let has_default = match has_default { + None => empty + Some(_) => space + char('=') + space + text("_") + } + group(async_ + name + params + arrow + return_type + error_type + has_default) +} + +///| +fn Fmt::visibility(fmt : Fmt, vis : @syntax.Visibility) -> Doc { + match vis { + Pub(attr=modifier, loc~) => { + let table = fmt.get_table(loc) + let pub_ = fmt.wrap_comment(table.get_token("pub"), text("pub")) + let modifier = match modifier { + None => empty + Some(s) => { + let lparen = fmt.wrap_comment(table.get_token("lparen"), text("(")) + let id = fmt.wrap_comment(table.get_token("lident"), text(s)) + let rparen = fmt.wrap_comment(table.get_token("rparen"), text(")")) + lparen + id + rparen + } + } + fmt.wrap_comment(Some(Node(loc)), pub_ + modifier) + space + } + Priv(loc~) => { + let table = fmt.get_table(loc) + let priv_ = fmt.wrap_comment(table.get_token("priv"), text("priv")) + fmt.wrap_comment(Some(Node(loc)), priv_) + space + } + Default => empty + } +} + +///| +fn Fmt::docstring( + fmt : Fmt, + doc : @syntax.DocString, + is_toplevel~ : Bool, +) -> Doc { + if fmt.block_line && is_toplevel { + match doc.content { + Empty => text("///|") + hardline + More(first_line, tail~) => + if first_line.trim(char_set=" \t\n") == "" { + let first_line = text("///|") + text(first_line) + hardline + let remain_lines = concat_map(tail, x => { + text("///") + text(x) + hardline + }) + first_line + remain_lines + } else { + let new_first_line = text("///|") + hardline + let old_first_line = text("///") + text(first_line) + hardline + let remain_lines = concat_map(tail, x => { + text("///") + text(x) + hardline + }) + new_first_line + old_first_line + remain_lines + } + } + } else { + concat_map(doc.content, x => text("///") + text(x) + hardline) + } +} + +///| +fn Fmt::type_decl_binder(_ : Self, binder : @syntax.TypeDeclBinder) -> Doc { + let { name, loc: _ } = binder + match name { + None => text("_") + Some(n) => text(n) + } +} + +///| +fn Fmt::type_decl_binders( + fmt : Self, + binders : List[@syntax.TypeDeclBinder], +) -> Doc { + match binders { + Empty => empty + More(_) => + group(brackets(series1(binders.map(x => fmt.type_decl_binder(x)), comma))) + } +} + +///| +fn Fmt::constr_param(fmt : Self, constr_param : @syntax.ConstrParam) -> Doc { + let { mut_, label, ty } = constr_param + let mut_ = if mut_ { text("mut") + space } else { empty } + let label = match label { + None => empty + Some(label) => fmt.label(label) + char('~') + space + char(':') + space + } + let ty = fmt.ty(ty) + mut_ + label + ty +} + +///| +fn Fmt::constr_decl(fmt : Self, constr_decl : @syntax.ConstrDecl) -> Doc { + let { name, args, tag, loc: _, attrs, doc } = constr_decl + let doc = fmt.docstring(doc, is_toplevel=false) + let attrs = fmt.attributes(attrs) + let name = text(name.name) + let args = match args { + None => empty + Some(xs) => group(parens(series1(xs.map(x => fmt.constr_param(x)), comma))) + } + let tag = match tag { + None => empty + Some((tag, _)) => space + char('=') + space + text(tag) + } + doc + attrs + name + args + tag +} + +///| +fn Fmt::deriving_directive( + fmt : Self, + deriving : @syntax.DerivingDirective, +) -> Doc { + let { type_name, args, loc: _ } = deriving + let type_name = fmt.type_name(type_name) + let args = match args { + Empty => empty + More(_) => parens(series1(args.map(x => fmt.argument(x)), comma)) + } + type_name + args +} + +///| +fn Fmt::deriving_directives( + fmt : Self, + deriving : List[@syntax.DerivingDirective], +) -> Doc { + match deriving { + Empty => empty + More(_) => { + let derive_ = text("derive") + let xs = group( + parens(series1(deriving.map(x => fmt.deriving_directive(x)), comma)), + ) + space + derive_ + xs + } + } +} + +///| +fn Fmt::field_decl(fmt : Self, field_decl : @syntax.FieldDecl) -> Doc { + let { name, ty, mut_, vis, attrs, doc, loc: _ } = field_decl + let doc = fmt.docstring(doc, is_toplevel=false) + let attrs = fmt.attributes(attrs) + let vis = fmt.visibility(vis) + let mut_ = if mut_ { text("mut") + space } else { empty } + let name = text(name.label) + let ty = fmt.ty(ty) + doc + attrs + vis + mut_ + name + space + char(':') + space + ty +} + +///| +fn Fmt::typedecl(fmt : Fmt, typedecl : @syntax.TypeDecl) -> Doc { + ignore(fmt) //TODO: use fmt to access comments + let { + tycon, + tycon_loc: _, + params, + components, + attrs, + doc, + type_vis, + deriving, + loc: _, + is_declare, + } = typedecl + let doc = fmt.docstring(doc, is_toplevel=true) + let attrs = fmt.attributes(attrs) + let declare_ = if is_declare { text("declare") + space } else { empty } + let vis = fmt.visibility(type_vis) + let semi = switch(char(';'), empty) + match components { + Variant(constr_decls) | ExtensibleEnum(constr_decls) => { + let enum_ = text( + if components is ExtensibleEnum(_) { + "extenum" + } else { + "enum" + }, + ) + let tycon = space + text(tycon) + let params = fmt.type_decl_binders(params) + let lbrace = space + char('{') + let constrs = nest( + hardline + series1(constr_decls.map(x => fmt.constr_decl(x)), semi), + ) + let rbrace = hardline + char('}') + let deriving = fmt.deriving_directives(deriving) + doc + + attrs + + declare_ + + vis + + enum_ + + tycon + + params + + lbrace + + constrs + + rbrace + + deriving + } + Record(fields~, constr_decl~) => { + let struct_ = text("struct") + let tycon = space + text(tycon) + let params = fmt.type_decl_binders(params) + let lbrace = space + char('{') + let semi = switch(char(';'), empty) + let constr = match constr_decl { + None => empty + Some(decl) => { + let constr_doc = fmt.docstring(decl.doc, is_toplevel=false) + let constr_attrs = fmt.attributes(decl.attrs) + let constr_async = if decl.is_async is Some(_) { + text("async") + space + } else { + empty + } + let fn_ = text("fn") + let constr_quantifiers = fmt.type_var_binders( + decl.quantifiers, + lbracket=None, + commas=@comment.NodeArray::empty(), + tvar_tables=@comment.NodeTableArray::empty(), + rbracket=None, + ) + let constr_name = space + fmt.binder(decl.name) + let constr_params = match decl.decl_params { + None => empty + Some(xs) => + group( + fmt.parameters( + xs, + lparen=None, + param_tables=@comment.NodeTableArray::empty(), + commas=@comment.NodeArray::empty(), + rparen=None, + ), + ) + } + let constr_result = fmt.return_and_error_type_annotation( + decl.return_type, + decl.error_type, + ) + hardline + + hardline + + constr_doc + + constr_attrs + + constr_async + + fn_ + + constr_quantifiers + + constr_name + + constr_params + + constr_result + } + } + let fields_doc = if fields.is_empty() && constr_decl is Some(_) { + nest(constr) + } else { + nest(hardline + series1(fields.map(x => fmt.field_decl(x)), semi)) + + nest(constr) + } + let rbrace = hardline + char('}') + let deriving = fmt.deriving_directives(deriving) + doc + + attrs + + declare_ + + vis + + struct_ + + tycon + + params + + lbrace + + fields_doc + + rbrace + + deriving + } + Error(exception_decl) => { + let struct_ = text("suberror") + let tycon = space + text(tycon) + let semi = switch(char(';'), empty) + let payload = match exception_decl { + NoPayload => empty + EnumPayload(constr_decls) => + space + + braces( + series1(constr_decls.map(x => fmt.constr_decl(x)), semi), + force_newline=true, + ) + } + doc + attrs + declare_ + vis + struct_ + tycon + payload + } + ExtendEnum(target~, constructors~) => { + let enum_ = text("extenum") + let target = space + fmt.type_name(target) + let plus_equal = space + text("+=") + let constrs = space + + braces( + series1(constructors.map(x => fmt.constr_decl(x)), semi), + force_newline=true, + ) + doc + attrs + declare_ + vis + enum_ + target + plus_equal + constrs + } + Abstract => { + let type_ = text("type") + let tycon = space + text(tycon) + doc + attrs + declare_ + vis + type_ + tycon + } + TupleStruct(tys) => { + let type_ = text("struct") + let tycon = space + text(tycon) + let tys = group(parens(series1(tys.map(x => fmt.ty(x)), comma))) + let params = fmt.type_decl_binders(params) + doc + attrs + declare_ + vis + type_ + tycon + params + tys + } + Alias(ty) => { + let type_ = text("typealias") + let ty = space + fmt.ty(ty) + let as_ = space + text("as") + let tycon = space + text(tycon) + doc + attrs + declare_ + vis + type_ + ty + as_ + tycon + } + } +} + +///| +fn Fmt::constant(fmt : Fmt, constant : @syntax.Constant) -> Doc { + ignore(fmt) //TODO: use fmt to access comments + match constant { + String(s) => double_quoted(text(s)) + Char(s) => single_quoted(text(s)) + Bytes(s) => char('b') + double_quoted(text(s)) + Byte(s) => char('b') + single_quoted(text(s)) + Bool(b) => @pp.pretty(b) + BigInt(s) => text(s) + char('N') + Regex(s) => text("re") + double_quoted(text(s)) + UInt64(s) => text(s) + char('U') + char('L') + UInt(s) => text(s) + char('U') + Int64(s) => text(s) + char('L') + Float(s) => text(s) + char('F') + Double(s) | Int(s) => text(s) + } +} + +///| +fn Fmt::interps(fmt : Fmt, interps : List[@syntax.InterpElem]) -> Doc { + interps.fold(init=empty, fn(acc, x) { + match x { + Literal(repr~, ..) => acc + text(repr) + Expr(expr~, ..) => acc + text("\\{") + fmt.compose(expr, Expr) + char('}') + Source({ source, .. }) => acc + text("\\{") + text(source) + char('}') + } + }) +} + +///| +fn Fmt::multiline_string( + env : Fmt, + elems : List[@syntax.MultilineStringElem], + parens~ : Bool, + loc~ : Location, +) -> Doc { + ignore(parens) + ignore(loc) + let elems = separate_map(elems, hardline, elem => { + match elem { + String(s) => text(s) + Interp(xs) => env.interps(xs) + } + }) + group(elems) +} + +///| +fn Fmt::label(fmt : Fmt, label : @syntax.Label) -> Doc { + ignore(fmt) //TODO: use fmt to access comments + let { name, loc: _ } = label + text(name) +} + +///| +fn is_pun_eligible_for_expr( + label : @syntax.Label, + value : @syntax.Expr, +) -> Bool { + value is Ident(id={ name: Ident(name~), .. }, ..) && name == label.name +} + +///| +fn is_pun_eligible_for_pat( + label : @syntax.Label, + pat : @syntax.Pattern, +) -> Bool { + pat is Var({ name, .. }) && name == label.name +} + +///| +fn Fmt::argument(fmt : Fmt, arg : @syntax.Argument) -> Doc { + //TODO: print in compact style for short expr + + let { value, kind } = arg + match kind { + Positional => fmt.compose(value, Expr) + Labelled(label) => + if is_pun_eligible_for_expr(label, value) { + fmt.label(label) + char('~') + } else { + fmt.label(label) + char('=') + fmt.compose(value, Expr) //TODO: compose_rhs + } + LabelledOption(label~, question_loc=_) => + if is_pun_eligible_for_expr(label, value) { + fmt.label(label) + char('?') + } else { + fmt.label(label) + text("?=") + fmt.compose(value, Expr) //TODO: compose_rhs + } + LabelledPun(label) => fmt.label(label) + char('~') + LabelledOptionPun(label~, question_loc=_) => fmt.label(label) + char('?') + } +} + +///| +fn Fmt::arguments(fmt : Fmt, args : List[@syntax.Argument]) -> Doc { + // TODO: optimize list <-> array conversion and readability + let arr = args.to_array() + // Bad case: + // + // Before applying the trailing block rules, we should ensure the remaining space + // is enough for leading arguments and the head of the trailing function. + // + // ``` + // f(arg1, arg2, ..., argN, [ elems ]) + // ^^^^^^^^^^^^^^^^^^^^^^ + // f(arg1, arg2, ..., argN, (x,y) => e) + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // f(arg1, arg2, ..., argN, fn(x,y){ e }) + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~ is this part fits in current remaining space? + // ``` + // + // Consider this bad result: + // ``` + // long_function(argument1, argument2, argument3, argument4, argument5, fn( + // param1, + // param2, + // ) { + // expr + // }) + // ``` + match arr.pop() { + Some({ value: trailing_arg_value, kind: Positional } as trailing_arg) => { + let leading_args = arr.map(x => fmt.argument(x)) + fn trailing_block_style() { + let leading_doc = series1( + @list.from_array(leading_args), + comma, + trailing=AlwaysPresent, + ) + let trailing_doc = fmt.argument(trailing_arg) + let sep = if leading_args.is_empty() { empty } else { space } + group(char('(') + (group(leading_doc) + sep + trailing_doc) + char(')')) + } + + fn fallback_style() { + leading_args.push(fmt.argument(trailing_arg)) + group(parens(series1(@list.from_array(leading_args), comma))) + } + + fn switch_trailing_block(leading_req, total_req) { + ignore(total_req) //TODO: use total_req to decide the layout + dynamic(fn(ctx) { + if ctx.current_remain() >= leading_req { + trailing_block_style() + } else { + fallback_style() + } + }) + } + + let leading_args_req = leading_args.fold(init=Requirement::Space(0), fn( + acc, + x, + ) { + acc + x.requirement() + Space(2) + }) + match trailing_arg_value { + Record(_) | Array(_) => { + guard fmt.expr(trailing_arg_value) is BlockLike(doc) + let leading_req = leading_args_req + let total_req = leading_args_req + doc.requirement() + switch_trailing_block(leading_req, total_req) + } + Function(_) => { + guard fmt.expr(trailing_arg_value) is Collapsible(head, body) + let leading_req = leading_args_req + head.requirement() + let total_req = leading_args_req + + head.requirement() + + body.requirement() + switch_trailing_block(leading_req, total_req) + } + _ => fallback_style() + } + } + _ => group(parens(series1(args.map(x => fmt.argument(x)), comma))) + } +} + +///| +fn longident(id : @syntax.LongIdent) -> Doc { + match id { + Ident(name~) => text(name) + Dot(pkg~, id~) => text("@" + pkg + "." + id) + } +} + +///| +fn Fmt::var_(fmt : Self, variable : @syntax.Var) -> Doc { + ignore(fmt) //TODO: use fmt to access comments + longident(variable.name) +} + +///| +fn Fmt::binder(fmt : Self, binder : @syntax.Binder) -> Doc { + ignore(fmt) //TODO: use fmt to access comments + let { name, loc: _ } = binder + text(name) +} + +///| +fn Fmt::pattern(fmt : Self, pat : @syntax.Pattern) -> Doc { + match pat { + SpecialConstr(_) => abort("unsupported special constr") + Var(binder) => fmt.binder(binder) + Any(loc=_) => char('_') + Tuple(pats~, loc=_) => + group(parens(series1(pats.map(x => fmt.pattern(x)), comma))) + Constr(constr~, args=payload, is_open~, loc=_) => { + fn constr_pat_arg2doc(arg : @syntax.ConstrPatArg) -> Doc { + let { pat, kind } = arg + let pat_doc = fmt.pattern(pat) + match kind { + Positional => pat_doc + Labelled(label) => + if is_pun_eligible_for_pat(label, pat) { + fmt.label(label) + equal + pat_doc + } else { + fmt.label(label) + tilde + } + LabelledPun(label) => fmt.label(label) + tilde + LabelledOption(_) | LabelledOptionPun(_) => panic() + } + } + + let constr = fmt.constr(constr) + let payload = match payload { + None => empty + Some(args) => { + let args = args.map(constr_pat_arg2doc) + let args = if is_open { + // Replace deprecated @list.of usage with from_array + args.concat(@list.from_array([text("..")])) + } else { + args + } + group(parens(series(args, comma, x => x))) + } + } + group(constr + payload) + } + Constant(c~, loc=_) => fmt.constant(c) + Array(pats~, loc=_) => { + fn array_pattern2doc(pat : @syntax.ArrayPattern) -> Doc { + match pat { + Pattern(pat) => fmt.pattern(pat) + StringSpread(str~, ..) => text("..") + double_quoted(text(str)) + BytesSpread(bytes~, ..) => + text("..") + char('b') + double_quoted(text(bytes)) + ConstSpread(binder~, pkg~, loc=_) => { + let pkg = match pkg { + Some(x) => text("@\{x}.") + None => empty + } + let binder = fmt.binder(binder) + text("..") + pkg + binder + } + } + } + + match pats { + Closed(elems) => + group(brackets(series(elems, comma, array_pattern2doc))) + Open(left, right, remain) => { + let elems = left.to_array().map(array_pattern2doc) + match remain { + NoBinder => () + Binder(b) | BinderAs(b) => elems.push(text("..") + fmt.binder(b)) + Underscore => elems.push(text(".._")) + } + elems.push_iter(right.map(array_pattern2doc).iter()) + let elems = @list.from_array(elems) + group(brackets(series(elems, comma, x => x))) + } + } + } + Range(lhs~, rhs~, kind~, loc=_) => { + let lhs = fmt.pattern(lhs) + let op = text( + match kind { + Inclusive | InclusiveMissingEqual => "..=" + Exclusive => "..<" + }, + ) + let rhs = fmt.pattern(rhs) + group(lhs + op + rhs) + } + Record(fields~, is_closed~, loc=_) => { + fn field_pat2doc(field : @syntax.FieldPat) -> Doc { + let { label, pattern, is_pun, loc: _ } = field + if is_pun || is_pun_eligible_for_pat(label, pattern) { + fmt.label(label) + } else { + fmt.label(label) + colon + space + fmt.pattern(pattern) + } + } + + let elems = fields.to_array().map(field_pat2doc) + if is_closed { + elems.push(text("..")) + } + let elems = @list.from_array(elems) + group(braces(series(elems, comma, x => x))) + } + Map(elems~, is_closed~, loc=_) => { + fn map_pat2doc(elem : @syntax.MapPatElem) -> Doc { + let { key, pat, match_absent, key_loc: _, loc: _ } = elem + let key = fmt.constant(key) + let absent = if match_absent { char('?') } else { empty } + let pat = space + fmt.pattern(pat) + key + absent + colon + pat + } + + let elems = elems.to_array().map(map_pat2doc) + if is_closed { + elems.push(text("..")) + } + let elems = @list.from_array(elems) + group(braces(series(elems, comma, x => x))) + } + Constraint(pat~, ty~, loc=_) => { + let pat = fmt.pattern(pat) + let colon = space + char(':') + let ty = space + fmt.ty(ty) + group(parens(pat + colon + ty)) + } + Alias(pat~, alias_~, loc=_) => { + let pat = fmt.pattern(pat) + let as_ = space + text("as") + let alias_ = space + fmt.binder(alias_) + group(pat + as_ + alias_) + } + Or(pat1~, pat2~, loc=_) => { + let sep = line + char('|') + space + let pats = for acc = @list.from_array([pat1]), cur = pat2 { + match cur { + Or(pat1~, pat2~, loc=_) => continue acc.add(pat1), pat2 + pat => break acc.add(pat).rev() + } + } + let lparen = switch(empty, char('(') + space) + let pats = separate(pats.map(x => fmt.pattern(x)), sep) + let rparen = switch(empty, softline + char(')')) + lparen + pats + rparen + } + } +} + +///| +/// Convert ErrorType to Document, the result including the leading space. +/// +/// for example: +/// `` +/// ` noraise` +/// ` raise TYPE` +/// ` raise?` +fn Fmt::error_type(fmt : Self, err : @syntax.ErrorType) -> Doc { + match err { + NoErrorType => empty + DefaultErrorType(loc=_) => space + text("raise") + Noraise(loc=_) => space + text("noraise") + ErrorType(ty~) => space + text("raise") + space + fmt.ty(ty) + MaybeError(ty=_) => space + text("raise?") + } +} + +///| +fn Fmt::ty(fmt : Self, ty : @syntax.Type) -> Doc { + match ty { + Any(loc=_) => char('_') + Name(constr_id={ id: constr, loc: _ }, tys~, loc=_) => { + // If the user writes `Option[Int]`, keep it as is. + let constr = longident(constr) + let tys = match tys { + Empty => empty + More(_) => group(brackets(series1(tys.map(x => fmt.ty(x)), comma))) + } + constr + tys + } + Tuple(tys~, loc=_) => group(parens(series1(tys.map(x => fmt.ty(x)), comma))) + Arrow(args~, res~, err~, is_async~, loc=_) => { + let async_ = if is_async is Some(_) { + text("async") + space + } else { + empty + } + let args = group(parens(series1(args.map(x => fmt.ty(x)), comma))) + let arrow = space + text("->") + let res = space + fmt.ty(res) + let err = fmt.error_type(err) + async_ + args + arrow + res + err + } + Option(ty~, loc=_, question_loc=_) => { + let ty = match ty { + Arrow(_) => parens(fmt.ty(ty)) + Name(_) | Tuple(_) | Any(_) | Option(_) | Object(_) => fmt.ty(ty) + } + ty + char('?') + } + Object({ id: constr, loc: _ }) => { + let constr = longident(constr) + char('&') + constr + } + } +} + +///| +fn Fmt::type_name(fmt : Self, typename : @syntax.TypeName) -> Doc { + ignore(fmt) // TODO: use fmt to get comments + let { name, is_object, loc: _ } = typename + let object_mark = if is_object { char('&') } else { empty } + let name = longident(name) + object_mark + name +} + +///| +fn Fmt::constr(fmt : Self, constr : @syntax.Constructor) -> Doc { + let { extra_info, name: { name, loc: _ }, loc: _ } = constr + let extra_info = match extra_info { + NoExtraInfo => empty + TypeName(n) => fmt.type_name(n) + text("::") + TypeNameWithConstrPackage(type_name~, pkg~) => + fmt.type_name(type_name) + text("::@" + pkg + ".") + Package(p) => text("@" + p + ".") + } + let name = text(name) + extra_info + name +} + +///| +fn Fmt::parameter( + fmt : Self, + parameter : @syntax.Parameter, + table : @comment.NodeTable, + kind? : ParameterListKind = Regular, +) -> Doc { + ignore(table) // TODO: use table for comments + match parameter { + Optional(binder~, default~, ty~) => { + let binder = fmt.binder(binder) + char('?') + let ty = match ty { + None => empty + Some(ty) => space + colon + space + fmt.ty(ty) + } + let equal = space + equal + let default = space + fmt.compose(default, Expr) + binder + ty + equal + default + } + Labelled(binder~, ty~) => { + let binder = fmt.binder(binder) + char('~') + let ty = match ty { + None => empty + Some(ty) => space + colon + space + fmt.ty(ty) + } + binder + ty + } + Positional(binder~, ty~) => { + let binder = fmt.binder(binder) + let ty = match ty { + None => empty + Some(ty) => space + colon + space + fmt.ty(ty) + } + binder + ty + } + DiscardPositional(ty~, loc=_) => + if kind is TraitMethod { + guard ty is Some(ty) else { + abort("Trait method parameter must have type annotation.") + } + fmt.ty(ty) + } else { + let binder = char('_') + let ty = match ty { + None => empty + Some(ty) => space + colon + space + fmt.ty(ty) + } + binder + ty + } + QuestionOptional(binder~, ty~) => { + let binder = fmt.binder(binder) + char('?') + let ty = match ty { + None => empty + Some(ty) => space + colon + space + fmt.ty(ty) + } + binder + ty + } + } +} + +///| +priv enum ParameterListKind { + Regular + TraitMethod + ArrowFunction +} + +///| +/// +/// # Parameters +/// +/// - `kind`: indicates the context of the parameters. +/// If `kind` is: +/// - Regular: print all parameters as is. +/// - TraitMethod: hide the parameter name if it is `DiscardPositional`. +/// - ArrowFunction: omit the parentheses if there is a single parameter. +fn Fmt::parameters( + fmt : Self, + parameters : List[@syntax.Parameter], + lparen~ : @comment.Key?, + param_tables~ : @comment.NodeTableArray, + commas~ : @comment.NodeArray, + rparen~ : @comment.Key?, + kind? : ParameterListKind = Regular, +) -> Doc { + ignore(rparen) //TODO: use rparen comment + let sep = comma + let trailing = switch(empty, sep) + let mut acc = empty + for i, param in parameters { + acc += fmt.parameter(param, param_tables[i], kind~) + if i == parameters.length() - 1 { + let trailing_comment = fmt.after_comment(Some(Node(param.loc()))) + let around_comments = fmt.wrap_comment(commas[i], empty) + acc += trailing + trailing_comment + around_comments + } else { + let sep = fmt.wrap_comment(commas[i], sep) + acc += sep + line + } + } + let l = softline + match kind { + ArrowFunction if parameters is More(_, tail=Empty) => acc + _ => { + let lp = fmt.wrap_comment(lparen, char('(')) + let rp = fmt.wrap_comment(lparen, char(')')) + lp + nest(l + acc) + l + rp + } + } +} + +///| +fn Fmt::arrow_function( + fmt : Self, + parameters~ : @syntax.Parameters, + body~ : @syntax.Expr, +) -> RawExprDoc { + let params = group( + fmt.parameters( + parameters, + lparen=None, + param_tables=@comment.NodeTableArray::empty(), + commas=@comment.NodeArray::empty(), + rparen=None, + kind=ArrowFunction, + ), + ) + let arrow = space + text("=>") + let body = fmt.compose_arrow_rhs(body) + Collapsible(params + arrow, body) +} + +///| +fn Fmt::func( + fmt : Self, + func : @syntax.Func, + binder? : @syntax.Binder, +) -> RawExprDoc { + let is_annomyous = binder is None + let { parameters, body, return_type, error_type, kind, is_async, .. } = func + let async_ = if is_async is Some(_) { text("async") + space } else { empty } + let fn_ = match kind { + Arrow => empty + Lambda => text("fn") + } + let binder = match binder { + None => empty + Some(b) => space + fmt.binder(b) + } + let params = group( + fmt.parameters( + parameters, + lparen=None, + param_tables=@comment.NodeTableArray::empty(), + commas=@comment.NodeArray::empty(), + rparen=None, + ), + ) + let has_error_ty = match error_type { + NoErrorType => false + DefaultErrorType(_) | Noraise(_) | ErrorType(_) | MaybeError(_) => true + } + let arrow = match kind { + Arrow => space + text("=>") + Lambda => + if return_type is Some(_) || has_error_ty { + space + text("->") + } else { + empty + } + } + let ety = fmt.error_type(error_type) + let rty = match return_type { + None => empty + Some(ty) => space + fmt.ty(ty) + } + let body = space + fmt.expr_block(body, force_newline=!is_annomyous) + Collapsible(async_ + fn_ + binder + params + arrow + rty + ety, body) +} + +///| +fn Fmt::field(fmt : Self, field : @syntax.FieldDef) -> Doc { + let { label, expr, is_pun, loc: _ } = field + if is_pun || is_pun_eligible_for_expr(label, expr) { + fmt.label(label) + } else { + fmt.label(label) + colon + space + fmt.compose(expr, Expr) + } +} + +///| +/// in cases `(1).f()`, `(-1L).f()` and `(-1.0).f()`, the parens should be reserved +fn Fmt::expr_before_dot(fmt : Self, expr : @syntax.Expr) -> Doc { + let doc = fmt.compose(expr, Prefix) + match expr { + Constant(c=Int(_), loc=_) => group(parens(doc)) + Constant(c=Double(str) | Int64(str), loc=_) if str.has_prefix("-") => + group(parens(doc)) + _ => doc + } +} + +///| +fn Fmt::accessor(self : Self, accessor : @syntax.Accessor) -> Doc { + match accessor { + Label(label) => dot + self.label(label) + Index(tuple_index~, loc=_) => dot + text(tuple_index.to_string()) + Newtype(loc=_) => text(".inner()") + } +} + +///| +fn Fmt::loop_label( + self : Self, + label : @syntax.Label?, + trailing_colon~ : Bool, +) -> Doc { + match label { + None => empty + Some(label) => + space + + self.label(label) + + tilde + + (if trailing_colon { colon } else { empty }) + } +} + +///| +fn Fmt::lex_pattern(fmt : Self, pat : @syntax.LexPattern) -> Doc { + match pat { + Regex(lit~, offset=_, loc=_) => double_quoted(text(lit)) + ConstantRef(lid~, loc=_) => longident(lid) + RegexInterp(elems~, loc=_) => { + fn elem2doc(elem : @syntax.InterpElem) -> Doc { + match elem { + Literal(repr~, loc=_) => text(repr) + Expr(expr~, loc=_) => fmt.compose(expr, Expr) + Source({ source, loc: _ }) => text(source) + } + } + + double_quoted(concat_map(elems, elem2doc)) + } + Alias(pat~, binder~, loc=_) => + fmt.lex_pattern(pat) + space + text("as") + space + fmt.binder(binder) + Sequence(pats~, loc=_) => separate(pats.map(p => fmt.lex_pattern(p)), space) + } +} + +///| +fn Fmt::regex_pattern(fmt : Self, pat : @syntax.RegexPattern) -> Doc { + match pat { + Literal(lit~, loc=_) => text("re") + double_quoted(text(lit)) + Reference(lid~, loc=_) => longident(lid) + Sequence(pat1~, pat2~, loc=_) => + fmt.regex_pattern(pat1) + + space + + char('+') + + space + + fmt.regex_pattern(pat2) + Alternation(pat1~, pat2~, loc=_) => + fmt.regex_pattern(pat1) + + space + + char('|') + + space + + fmt.regex_pattern(pat2) + Alias(pat~, binder~, loc=_) => + fmt.regex_pattern(pat) + space + text("as") + space + fmt.binder(binder) + } +} + +///| +fn Fmt::lex_top_pattern(fmt : Self, pat : @syntax.LexTopPattern) -> Doc { + match pat { + Pattern(p) => fmt.lex_pattern(p) + Binder(b) => fmt.binder(b) + Wildcard(loc=_) => text("_") + } +} + +///| +fn Fmt::lex_top_patterns(fmt : Self, pats : List[@syntax.LexTopPattern]) -> Doc { + match pats { + More(x, tail=Empty) => fmt.lex_top_pattern(x) + _ => group(parens(series1(pats.map(p => fmt.lex_top_pattern(p)), comma))) + } +} + +///| +fn Fmt::lex_case(fmt : Self, case_ : @syntax.LexCase) -> Doc { + let { pat, pat_loc: _, guard_, body } = case_ + let pattern = fmt.lex_top_patterns(pat) + let guard_ = match guard_ { + None => empty + Some(expr) => space + text("if") + space + fmt.compose(expr, Infix(Other)) + } + let body = space + text("=>") + fmt.compose_case_rhs(body, ExprStmt) + group(group(pattern) + guard_ + body) +} + +///| +fn Fmt::lex_strategy(fmt : Self, strategy : @syntax.Label?) -> Doc { + match strategy { + None => empty + Some(label) => space + text("with") + space + fmt.label(label) + } +} + +///| +fn Fmt::case(fmt : Self, case_ : @syntax.Case) -> Doc { + let { pattern, guard_, body } = case_ + let pattern = fmt.pattern(pattern) + let guard_ = match guard_ { + None => empty + Some(expr) => space + text("if") + space + fmt.compose(expr, Infix(Other)) + } + let body = space + text("=>") + fmt.compose_case_rhs(body, ExprStmt) //TODO: compose_rhs + group(group(pattern) + guard_ + body) +} + +///| +fn Fmt::expr_block( + fmt : Self, + expr : @syntax.Expr, + force_newline? : Bool = true, + grouped? : Bool = true, +) -> Doc { + let doc = braces(fmt.compose(expr, Stmt), force_newline~) + if grouped { + group(doc) + } else { + doc + } +} + +///| +fn Fmt::where_clause(fmt : Self, where_clause : @syntax.WhereClause?) -> Doc { + match where_clause { + None => empty + Some({ fields, loc: _ }) => + space + + text("where") + + space + + group(braces(series1(fields.map(x => fmt.field(x)), comma))) + } +} + +///| +fn Fmt::list_comprehension_kind( + fmt : Self, + kind : @syntax.ListComprehensionKind, +) -> Doc { + fn binder_expr2doc(pair : (@syntax.Binder, @syntax.Expr)) -> Doc { + let (binder, expr) = pair + fmt.binder(binder) + space + equal + space + fmt.compose(expr, Infix(Other)) + } + + fn foreach_binder2doc(binder : @syntax.Binder?) -> Doc { + match binder { + None => empty + Some(x) => fmt.binder(x) + } + } + + let semi_or_newline = switch(semi + space, softline) + match kind { + Foreach(binders~, expr~, init~, continue_block~) => { + let init = match init { + Empty => empty + More(_) => + semi_or_newline + + series(init, comma, binder_expr2doc, trailing=AlwaysHidden) + } + let continue_block = match continue_block { + Empty => empty + More(_) => + semi_or_newline + + series(continue_block, comma, binder_expr2doc, trailing=AlwaysHidden) + } + text("for") + + space + + series(binders, comma, foreach_binder2doc, trailing=AlwaysHidden) + + space + + text("in") + + space + + fmt.compose(expr, Infix(Other)) + + init + + continue_block + } + For(binders~, condition~, continue_block~, for_loc=_) => { + let binders = series( + binders, + comma, + binder_expr2doc, + trailing=AlwaysHidden, + ) + let condition = condition.map(x => fmt.compose(x, Infix(Other))) + let continue_block = match continue_block { + Empty => None + More(_) => + Some( + series( + continue_block, + comma, + binder_expr2doc, + trailing=AlwaysHidden, + ), + ) + } + text("for") + + space + + binders + + semi_or_newline + + condition.unwrap_or(empty) + + semi_or_newline + + continue_block.unwrap_or(empty) + } + } +} + +///| +fn Fmt::case_block( + self : Self, + cases : List[@syntax.Case], + force_newline? : Bool = true, +) -> Doc { + group(braces(series1(cases.map(x => self.case(x)), empty), force_newline~)) +} + +///| +/// Handle chained expression like `self.f().g().field.1`. +/// +/// Ideally, the chained method calls should be formatted like below: +/// +/// ```skip +/// some_object +/// .long_method_call1(arg1, arg2) +/// .long_method_call2(arg1) +/// ``` +/// +/// However, if the lhs is too short or printing in several lines, it may +/// cause the entire expression weirdly indented. +/// +/// Bad case 1: lhs is too short +/// ```skip +/// x +/// .long_method_call1(arg1, arg2) +/// .long_method_call2(arg1) +/// ``` +/// +/// Bad case 2: lhs is printed in several lines, and the last line is too short +/// ```skip +/// { +/// field1: value1, +/// field2: value2, +/// } +/// .long_method_call1(arg1, arg2) +/// .long_method_call2(arg1) +/// ``` +/// +/// Bad case 3: the rhs is only one method call +/// ```skip +/// object +/// .long_method_call( +/// long_arg1, +/// long_arg2, +/// ) +/// ``` +fn Fmt::chained_dot_expr(fmt : Self, e : @syntax.Expr) -> RawExprDoc { + let xs = for acc = @list.empty(), cur = e { + match cur { + DotApply(self~, ..) as apply => continue acc.add(apply), self + Field(record~, ..) as field => continue acc.add(field), record + expr => break acc.add(expr) + } + } + guard xs is More(s, tail=chained_exprs) + let lhs = fmt.expr_before_dot(s) + let chained = separate_map(chained_exprs, softline, fn(expr) { + match expr { + DotApply(self=_, method_name~, args~, return_self~, loc=_) => { + let dot = if return_self { text("..") } else { text(".") } + let method_name = fmt.label(method_name) + let args = fmt.arguments(args) + dot + method_name + args + } + Field(record=_, accessor~, loc=_) => fmt.accessor(accessor) + _ => panic() // unreachable + } + }) + let apps = if chained_exprs is More(_, tail=Empty) { + group(chained) + } else { + group( + current_column_without_indent(fn(column) { + if lhs.requirement() < Space(3) { + // improve style in bad case 1 + nest(chained) + } else if column < 3 { + // improve style in bad case 2 + // multiline line lhs but `current_column - current_indent < 3` + softline + nest(chained) + } else { + nest(softline + chained) + } + }), + ) + } + // The chained `a.b.c` should be `Collapsible`, so that if the expression + // needs line wrap and occurs in the rhs of `let lhs = rhs` or `lhs = rhs`, the + // indentation will be correct. + // + // Bad format: 4 spaces indentation + // ``` + // let binder = some_long_lhs + // .long_method_call1(arg1, arg2) + // .long_method_call2(arg1) + // ``` + // + // Good format: 2 spaces indentation + // ``` + // let binder = some_long_lhs + // .long_method_call1(arg1, arg2) + // .long_method_call2(arg1) + // ``` + Collapsible(lhs, apps) +} + +///| +fn Fmt::expr(fmt : Self, expr : @syntax.Expr) -> RawExprDoc { + match expr { + Apply(func~, args~, loc~) => { + let func = match func { + // (self.label)(args) + // (self.0)(args) + Field(accessor=Label(_) | Index(_), ..) => + parens(fmt.compose(func, Simple)) + _ => fmt.compose(func, Simple) + } + let args = fmt.arguments(args) + let before_comment = fmt.before_comment(Some(Node(loc))) + let after_comment = fmt.after_comment(Some(Node(loc))) + Collapsible(before_comment + func, args + after_comment) + } + Infix(op~, lhs~, rhs~, loc=_) => { + let op_str = match op.name { + Ident(name~) => name + _ => panic() + } + let op = longident(op.name) + let layout = if op_str is ("..<" | "..=") { + let lhs = fmt.compose(lhs, Range) + let rhs = fmt.compose(rhs, Range) + group(lhs + op + rhs) + } else { + let lhs = fmt.compose( + lhs, + Infix(InfixOperand(op=op_str, pos=LeftOperand)), + ) + let rhs = fmt.compose( + rhs, + Infix(InfixOperand(op=op_str, pos=RightOperand)), + ) + // TODO: improve the style for long, nested infix expressions + lhs + space + op + space + rhs + } + Normal(layout) + } + Unary(op~, expr~, loc=_) => { + let op = fmt.var_(op) + let expr = match expr { + // -(_) or +(_) + Hole(kind=_, loc=_) => parens(fmt.compose(expr, Simple)) + _ => fmt.compose(expr, Simple) + } + let layout = group(op + expr) + Normal(layout) + } + Array(exprs~, loc~) => { + let before_comment = fmt.before_comment(Some(Node(loc))) + let doc = group(brackets(series(exprs, comma, e => fmt.compose(e, Expr)))) + let after_comment = fmt.after_comment(Some(Node(loc))) + BlockLike(before_comment + doc + after_comment) + } + ArraySpread(elems~, loc=_) => { + fn spreadable2doc(elem : @syntax.SpreadableElem) -> Doc { + match elem { + Regular(expr) => fmt.compose(expr, Expr) + Spread(expr~, loc=_) => text("..") + fmt.compose(expr, Expr) + } + } + + let doc = group(brackets(series(elems, comma, spreadable2doc))) + Normal(doc) + } + ListComprehension(kind~, guard_~, body~, loc=_) => { + let kind = fmt.list_comprehension_kind(kind) + let guard_doc = match guard_ { + None => empty + Some(expr) => + space + text("if") + space + fmt.compose(expr, Infix(Other)) + } + let body = space + text("=>") + space + fmt.compose(body, Expr) + Normal(group(brackets(kind + guard_doc + body))) + } + ArrayGet(array~, index~, loc=_) => { + let array = fmt.compose(array, Simple) + let index = fmt.compose(index, Expr) + let doc = group(array + group(brackets(index))) + Normal(doc) + } + ArrayGetSlice(array~, start_index~, end_index~, loc=_, index_loc=_) => { + let array = fmt.compose(array, Simple) + let start_index = optional(start_index, empty, e => fmt.compose(e, Expr)) + let end_index = optional(end_index, empty, e => fmt.compose(e, Expr)) + let doc = group(array + group(brackets(start_index + colon + end_index))) + Normal(doc) + } + ArraySet(array~, index~, value~, loc=_) => { + let op = equal + let array = fmt.compose(array, Simple) + let index = fmt.compose(index, Expr) + let value = fmt.compose_assign_rhs(value, Expr) // TODO: compose_rhs + let doc = group( + array + group(brackets(index)) + space + op + space + value, + ) + Normal(doc) + } + ArrayAugmentedSet(op~, array~, index~, value~, loc=_) => { + let op = fmt.var_(op) + let array = fmt.compose(array, Simple) + let index = fmt.compose(index, Expr) + let value = fmt.compose_assign_rhs(value, Expr) // TODO: compose_rhs + let doc = group( + array + group(brackets(index)) + space + op + space + value, + ) + Normal(doc) + } + Constant(c~, loc=_) => { + let doc = fmt.constant(c) + Normal(doc) + } + MultilineString(elems~, loc~) => { + let doc = fmt.multiline_string(parens=true, loc~, elems) + Normal(doc) + } + Interp(elems~, loc=_) => { + fn elem2doc(elem : @syntax.InterpElem) -> Doc { + match elem { + Literal(repr~, loc=_) => text(repr) + Expr(expr~, loc=_) => fmt.compose(expr, Expr) + Source({ source, loc: _ }) => text(source) + } + } + + let elems = concat_map(elems, elem2doc) + let doc = double_quoted(elems) + Normal(doc) + } + Constraint(expr~, ty~, loc=_) => { + let expr = fmt.compose(expr, Expr) + let ty = fmt.ty(ty) + let doc = group(parens(expr + line + colon + space + ty)) + BlockLike(doc) + } + Constr(constr~, loc=_) => { + let doc = fmt.constr(constr) + Normal(doc) + } + While(loop_cond~, loop_body~, while_else~, label~, loc=_) => { + let label = match label { + None => empty + Some(x) => fmt.label(x) + colon + } + let while_ = text("while") + let cond = space + fmt.compose(loop_cond, Infix(Other)) + let body = space + fmt.expr_block(loop_body) + let else_ = match while_else { + None => empty + Some(e) => space + fmt.expr_block(e) + } + let doc = while_ + label + cond + body + else_ + Normal(doc) + } + Function(func~, loc=_) => + match func.kind { + Arrow => fmt.arrow_function(parameters=func.parameters, body=func.body) + Lambda => fmt.func(func) + } + Ident(id~, loc~) => { + let before_comment = fmt.before_comment(Some(Node(loc))) + let doc = fmt.var_(id) + let after_comment = fmt.after_comment(Some(Node(loc))) + Normal(before_comment + doc + after_comment) + } + If(cond~, ifso~, ifnot~, loc=_) => { + let if_ = text("if") + let cond = space + fmt.compose(cond, Infix(Other)) + let ifso = space + + fmt.expr_block(ifso, force_newline=false, grouped=false) + let ifnot = match ifnot { + None => empty + Some(If(_) as e) => space + text("else") + space + fmt.compose(e, Stmt) + Some(e) => + space + + text("else") + + space + + fmt.expr_block(e, force_newline=false, grouped=false) + } + Collapsible(if_ + cond, ifso + ifnot) + } + Guard(cond~, otherwise~, body~, loc=_) => { + let guard_ = text("guard") + let cond = space + fmt.compose(cond, Infix(Other)) + let otherwise = match otherwise { + None => empty + Some(e) => space + text("else") + space + fmt.expr_block(e) + } + let body = hardline + fmt.compose(body, Stmt) + let doc = guard_ + cond + otherwise + body + Normal(doc) + } + Is(expr~, pat~, loc=_) => { + // The right-hand side of the `is` expression is a simple pattern. + // Parentheses should be added around complex patterns. + let expr = fmt.compose(expr, Expr) + let is_ = space + text("is") + let pat = match pat { + Constant(_) + | Any(_) + | Var(_) + | Constr(_) + | SpecialConstr(_) + | Tuple(_) + | Constraint(_) + | Array(_) + | Record(_) + | Map(_) => fmt.pattern(pat) + Range(_) | Or(_) | Alias(_) => parens(fmt.pattern(pat)) + } + let pat = space + pat + let doc = group(expr + is_ + pat) + Normal(doc) + } + RegexMatch(expr~, pat~, bindings~, loc=_) => { + fn binding2doc(pair : (@syntax.Label, @syntax.Binder?)) -> Doc { + let (label, binder) = pair + match binder { + None => fmt.label(label) + Some(binder) => fmt.label(label) + equal + fmt.binder(binder) + } + } + + let expr = fmt.compose(expr, Infix(Other)) + let pat = fmt.regex_pattern(pat) + let pat = if bindings.is_empty() { + pat + } else { + group(parens(pat + comma + line + series(bindings, comma, binding2doc))) + } + Normal(group(expr + space + text("=~") + space + pat)) + } + Defer(expr~, body~, loc=_) => { + let defer_ = text("defer") + let expr = space + fmt.compose(expr, Pipeline) // TODO: compose_rhs + let body = hardline + fmt.compose(body, Stmt) + let doc = defer_ + expr + body + Normal(doc) + } + LetFn(name=_, func~, body~, loc=_) => { //TODO: fixme + guard fmt.func(func) is Collapsible(a, b) + let func = a + b + let body = hardline + fmt.compose(body, Stmt) + Normal(func + body) + } + LetAnd(bindings~, body~, loc=_) => { + fn rec_binding2doc( + triple : (@syntax.Binder, @syntax.Type?, @syntax.Func), + ) { + let (binder, ty, func) = triple + let binder = fmt.binder(binder) + let ty = match ty { + None => empty + Some(ty) => space + colon + space + fmt.ty(ty) + } + let { parameters, body, .. } = func + let params = space + + group( + fmt.parameters( + parameters, + lparen=None, + param_tables=@comment.NodeTableArray::empty(), + commas=@comment.NodeArray::empty(), + rparen=None, + ), + ) + let body = space + fmt.expr_block(body) + binder + ty + space + equal + params + text("=>") + body + } + + let letrec_ = text("letrec") + let bindings = space + + separate_map(bindings, hardline + text("and") + space, rec_binding2doc) + let body = hardline + fmt.compose(body, Stmt) + let doc = letrec_ + bindings + body + Normal(doc) + } + Let(pattern~, expr~, body~, loc=_) => { + let lhs = if pattern is Constraint(pat~, ty~, loc=_) { + let pattern = fmt.pattern(pat) + let ty = fmt.ty(ty) + pattern + space + colon + ty + } else { + fmt.pattern(pattern) + } + let expr = fmt.compose_assign_rhs(expr, Expr) //TODO: compose_rhs + let doc = group(text("let") + space + lhs + space + equal + expr) + + hardline + + fmt.compose(body, Stmt) + Normal(doc) + } + Sequence(exprs~, last_expr~, loc~) => { + let before_comment = fmt.before_comment(Some(Node(loc))) + let exprs = separate(exprs.map(e => fmt.compose(e, ExprStmt)), hardline) + let last_expr = hardline + fmt.compose(last_expr, Stmt) + let after_comment = fmt.after_comment(Some(Node(loc))) + let doc = before_comment + exprs + last_expr + after_comment + Normal(doc) + } + Tuple(exprs~, loc=_) => { + let doc = group(parens(series(exprs, comma, e => fmt.compose(e, Expr)))) + Normal(doc) + } + Record(type_name~, fields~, trailing=_, loc=_) => + // Bad case: if type_name is empty and the record is initialized with + // one punning field, a comma should be added for disambiguation. + // + // ``` + // { ident } // block with one expression + // { field, } // record with one field + // TypeName::{ field } + // ``` + if type_name is None { + let record = group( + if fields is More(_, tail=Empty) { + braces( + series1( + fields.map(x => fmt.field(x)), + comma, + trailing=AlwaysPresent, + ), + ) + } else { + braces(series1(fields.map(x => fmt.field(x)), comma)) + }, + ) + BlockLike(record) + } else { + let type_name = match type_name { + None => empty + Some(type_name) => fmt.type_name(type_name) + text("::") + } + let record = group( + braces(series1(fields.map(x => fmt.field(x)), comma)), + ) + Collapsible(type_name, record) + } + RecordUpdate(type_name~, record~, fields~, loc=_) => { + let type_name = match type_name { + None => empty + Some(x) => fmt.type_name(x) + } + let dotdot_record = text("..") + fmt.compose(record, Simple) + let fields = fields.map(x => fmt.field(x)) + let record = group( + braces(series(fields.add(dotdot_record), comma, x => x)), + ) + let doc = group(type_name + text("::") + record) + Normal(doc) + } + Method(type_name~, method_name~, loc=_) => { + let type_name = fmt.type_name(type_name) + let method_name = fmt.label(method_name) + let doc = type_name + text("::") + method_name + Normal(doc) + } + Field(record=_, accessor=_, loc=_) as e => fmt.chained_dot_expr(e) + DotApply(self=_, method_name=_, args=_, return_self=_, loc=_) as e => + fmt.chained_dot_expr(e) + As(expr~, trait_~, loc=_) => { + let expr = fmt.compose(expr, Simple) + let as_ = space + text("as") + let trait_ = space + fmt.type_name(trait_) + let doc = group(expr + as_ + trait_) + Normal(doc) + } + Mutate(record~, accessor~, field~, augmented_by~, loc=_) => { + let record = fmt.expr_before_dot(record) + let accessor = fmt.accessor(accessor) + let op = match augmented_by { + None => space + text("=") + Some(op) => space + fmt.var_(op) + } + let field = space + fmt.compose(field, Expr) //TODO: compose_rhs + let doc = record + accessor + op + field + Normal(doc) + } + Match(expr~, cases~, match_loc=_, loc=_) => { + let match_ = text("match") + let cond = space + fmt.compose(expr, Infix(Other)) + let cases = space + + group( + braces( + series1(cases.map(x => fmt.case(x)), empty), + force_newline=true, + ), + ) + Collapsible(match_ + cond, cases) + } + LetMut(binder~, ty~, expr~, body~, loc=_) => { + let let_ = text("let") + let binder = space + fmt.binder(binder) + let ty = match ty { + None => empty + Some(ty) => space + fmt.ty(ty) + } + let assign = space + equal + // TODO: handle multiline string + let expr = space + fmt.compose(expr, Expr) // TODO: compose_rhs + let body = hardline + fmt.compose(body, Stmt) + let doc = let_ + binder + ty + assign + expr + body + Normal(doc) + } + Pipe(lhs~, rhs~, loc=_) => { + fn pipe2doc(lhs : @syntax.Expr, rhs) { + let lhs = match lhs { + Pipe(lhs~, rhs~, loc=_) => pipe2doc(lhs, rhs) + _ => fmt.compose(lhs, Simple) + } + let pipe = line + text("|>") + let rhs = space + fmt.compose(rhs, Infix(Other)) + lhs + pipe + rhs + } + + Normal(group(pipe2doc(lhs, rhs))) + } + RevPipe(lhs~, rhs~, loc=_) => { + let lhs = fmt.compose(lhs, Infix(Other)) + let rhs = fmt.compose(rhs, Pipeline) + Normal(group(lhs + space + text("<|") + space + rhs)) + } + Assign(var_~, expr~, augmented_by~, loc=_) => { + let var_ = fmt.var_(var_) + let op = match augmented_by { + None => space + text("=") + Some(op) => space + fmt.var_(op) + } + let expr = fmt.compose_assign_rhs(expr, Expr) //TODO: compose_rhs + let doc = var_ + op + expr + Normal(doc) + } + Hole(loc=_, kind=hole) => { + let doc = match hole { + Incomplete => text("_") + Synthesized => text("_") //TODO: sus + Todo => text("...") + } + Normal(doc) + } + Return(return_value~, loc=_) => { + let return_ = text("return") + let value = space + + optional(return_value, empty, e => { + fmt.compose_return_break_continue_rhs(e, Expr) + }) //TODO: compose_rhs + let doc = return_ + value + Normal(doc) + } + Raise(err_value~, loc=_) => { + let raise_ = text("raise") + let value = space + fmt.compose_assign_rhs(err_value, Expr) //TODO: compose_rhs + let doc = raise_ + value + Normal(doc) + } + Quantifier(kind~, binder~, binder_ty~, body~, loc=_) => { + let quantifier = match kind { + Forall => text("∀") + Exists => text("∃") + } + let doc = quantifier + + space + + fmt.binder(binder) + + space + + colon + + space + + fmt.ty(binder_ty) + + comma + + space + + fmt.compose(body, Expr) + Normal(group(doc)) + } + Implies(lhs~, rhs~, loc=_) => { + let lhs = fmt.compose(lhs, Infix(Other)) + let rhs = fmt.compose(rhs, Infix(Other)) + Normal(group(lhs + space + text("→") + space + rhs)) + } + ProofAssert(expr~, loc=_) => { + let expr = space + fmt.compose(expr, Expr) + Normal(text("proof_assert") + expr) + } + ProofLet(binder~, expr~, loc=_) => { + let expr = space + equal + space + fmt.compose(expr, Expr) + Normal(text("proof_let") + space + fmt.binder(binder) + expr) + } + Unit(faked~, loc=_) => { + let doc = if faked { empty } else { text("()") } + Normal(doc) + } + Break(arg~, label~, loc=_) => { + let break_ = text("break") + let label = fmt.loop_label(label, trailing_colon=false) + let arg = space + optional(arg, empty, e => fmt.compose(e, Expr)) //TODO: compose_rhs + let doc = break_ + label + arg + Normal(doc) + } + Continue(args~, label~, loc=_) => { + let continue_ = text("continue") + let label = fmt.loop_label(label, trailing_colon=false) + let args = if args.is_empty() { + empty + } else { + space + series(args, comma, e => fmt.compose(e, Expr)) + } + let doc = continue_ + label + args + Normal(doc) + } + For( + binders~, + condition~, + continue_block~, + body~, + for_else~, + where_clause~, + label~, + .. + ) => { + fn binder_expr2doc(pair : (@syntax.Binder, @syntax.Expr)) -> Doc { + let (binder, expr) = pair + let binder = fmt.binder(binder) + let assign = space + equal + let expr = space + fmt.compose(expr, Infix(Other)) + binder + assign + expr + } + + let label = fmt.loop_label(label, trailing_colon=true) + let for_ = text("for") + let binders_doc = space + + series(binders, comma, binder_expr2doc, trailing=AlwaysHidden) + let condition = match condition { + None => None + Some(e) => Some(fmt.compose(e, Infix(Other))) + } + let continue_block = match continue_block { + Empty => None + More(_) => + Some( + space + + series( + continue_block, + comma, + binder_expr2doc, + trailing=AlwaysHidden, + ), + ) + } + let body = space + fmt.expr_block(body) + let for_else = match for_else { + None => empty + Some(e) => space + text("else") + space + fmt.expr_block(e) + } + let semi_or_newline = switch(semi + space, softline) + let header = match (condition, continue_block) { + (None, None) => + if binders.is_empty() { + empty + } else { + space + binders_doc + } + (_, _) => + binders_doc + + semi_or_newline + + condition.unwrap_or(empty) + + semi_or_newline + + continue_block.unwrap_or(empty) + } + let where_ = fmt.where_clause(where_clause) + let doc = label + + for_ + + group(nest(softline + header)) + + body + + for_else + + where_ + Normal(doc) + } + ForEach( + binders~, + expr~, + init=_, + continue_block=_, + body~, + else_block~, + where_clause~, + label~, + loc=_ + ) => { + let label = fmt.loop_label(label, trailing_colon=true) + let for_ = text("for") + fn foreach_binder2doc(binder : @syntax.Binder?) -> Doc { + match binder { + None => empty + Some(x) => fmt.binder(x) + } + } + + let binders = space + + group(series(binders, comma, foreach_binder2doc, trailing=AlwaysHidden)) + let in_ = space + text("in") + let expr = space + fmt.compose(expr, Infix(Other)) + let body = space + fmt.expr_block(body) + let else_block = match else_block { + None => empty + Some(e) => fmt.expr_block(e) + } + let where_ = fmt.where_clause(where_clause) + let doc = label + for_ + binders + in_ + expr + body + else_block + where_ + Normal(doc) + } + Try( + body~, + catch_=catch_cases, + try_else~, + has_try=_, + try_loc=_, + catch_loc=_, + else_loc=_, + loc=_ + ) => { + let try_ = text("try") + let body = space + fmt.expr_block(body) + let catch_ = space + text("catch") + fmt.case_block(catch_cases) + let noraise_ = match try_else { + None => empty + Some(cases) => space + text("noraise") + space + fmt.case_block(cases) + } + let doc = try_ + body + catch_ + noraise_ + Normal(doc) + } + TryOperator(body~, kind=op, try_loc=_, loc=_) => { + let try_ = match op { + Question => text("try?") + Exclamation => text("try!") + } + let body = space + fmt.compose(body, Simple) + let doc = try_ + body + Normal(doc) + } + Map(elems~, loc=_) => { + fn map_expr_elem2doc(elem : @syntax.MapExprElem) -> Doc { + let { key, expr, key_loc: _, loc: _ } = elem + fmt.constant(key) + colon + space + fmt.compose(expr, Expr) + } + + let doc = group(braces(series(elems, comma, map_expr_elem2doc))) + Normal(doc) + } + TemplateWriting(expr~, template~, loc=_) => { + let expr = fmt.compose(expr, Infix(Other)) + let template = fmt.compose(template, Infix(Other)) + Normal(group(expr + space + text("<+") + space + template)) + } + IsLexMatch(expr~, strategy~, pat~, pat_loc=_, loc=_) => { + let expr = fmt.compose(expr, Expr) + let lexmatch_ = space + text("lexmatch?") + let pat = space + fmt.lex_top_patterns(pat) + let strategy = fmt.lex_strategy(strategy) + let doc = group(expr + lexmatch_ + pat + strategy) + Normal(doc) + } + LexMatch(strategy~, expr~, match_loc=_, cases~, loc=_) => { + let lexmatch_ = text("lexmatch") + let cond = space + fmt.compose(expr, Infix(Other)) + let strategy = fmt.lex_strategy(strategy) + let cases = space + + group( + braces( + series1(cases.map(x => fmt.lex_case(x)), empty), + force_newline=true, + ), + ) + Collapsible(lexmatch_ + cond + strategy, cases) + } + Group(expr~, ..) => abort(expr.to_json().stringify(indent=2)) + StaticAssert(_) => panic() + } +} diff --git a/fmt/internal/format/utils.mbt b/fmt/internal/format/utils.mbt new file mode 100644 index 00000000..c2260df1 --- /dev/null +++ b/fmt/internal/format/utils.mbt @@ -0,0 +1,148 @@ +///| +/// series of xs with separator and trailing separator +/// +/// ```skip +/// a, b, c +/// +/// a, +/// b, +/// c, +/// ``` +fn[T] series( + xs : List[T], + sep : Doc, + f : (T) -> Doc, + trailing? : TrailingSeparatorMode = PresentInNormalStyle, +) -> Doc { + let trailing = match xs { + Empty => empty + More(_, ..) => + match trailing { + AlwaysHidden => empty + AlwaysPresent => sep + PresentInNormalStyle => switch(empty, sep) + } + } + separate_map(xs, sep + line, f) + trailing +} + +///| +fn series1( + xs : List[Doc], + sep : Doc, + trailing? : TrailingSeparatorMode = PresentInNormalStyle, +) -> Doc { + series(xs, sep, x => x, trailing~) +} + +///| +priv enum TrailingSeparatorMode { + PresentInNormalStyle + AlwaysHidden + AlwaysPresent +} + +///| +/// ```skip +/// (x) +/// ( +/// x +/// ) +/// ``` +fn parens(x : Doc, force_newline? : Bool = false) -> Doc { + let l = if force_newline { hardline } else { softline } + char('(') + nest(l + x) + l + char(')') +} + +///| +/// ```skip +/// { x } +/// { +/// x +/// } +/// ``` +fn braces(x : Doc, force_newline? : Bool = false) -> Doc { + let l = if force_newline { hardline } else { line } + char('{') + nest(l + x) + l + char('}') +} + +///| +fn braces_if_not_flatten(x : Doc) -> Doc { + let lb = switch(empty, char('{')) + let rb = switch(empty, char('}')) + lb + nest(softline + x) + softline + rb +} + +///| +/// ```skip +/// [x] +/// [ +/// x +/// ] +/// ``` +fn brackets(x : Doc, force_newline? : Bool = false) -> Doc { + let l = if force_newline { hardline } else { softline } + char('[') + nest(l + x) + l + char(']') +} + +///| +fn[T] optional(x : T?, default : Doc, f : (T) -> Doc) -> Doc { + x.map(f).unwrap_or(default) +} + +///| +let empty : Doc = @pp.empty + +///| +let hardline : Doc = @pp.hardline + +///| +let line : Doc = @pp.line + +///| +let comma : Doc = char(',') + +///| +let colon : Doc = char(':') + +///| +let equal : Doc = char('=') + +///| +let dot : Doc = char('.') + +///| +let tilde : Doc = char('~') + +///| +let semi : Doc = char(';') + +///| +fn double_quoted(x : Doc) -> Doc { + char('"') + x + char('"') +} + +///| +fn single_quoted(x : Doc) -> Doc { + char('\'') + x + char('\'') +} + +///| +fn[T] concat_map(xs : List[T], f : (T) -> Doc) -> Doc { + separate_map(xs, empty, f) +} + +///| +fn[T] separate_map(xs : List[T], sep : Doc, f : (T) -> Doc) -> Doc { + @pp.separate_map(sep, xs.to_array(), f) +} + +///| +fn separate(xs : List[Doc], sep : Doc) -> Doc { + @pp.separate(sep, xs.to_array()) +} + +///| +fn current_column_without_indent(f : (Int) -> Doc) -> Doc { + dynamic(fn(ctx) { f(ctx.indent - ctx.next_indent) }) +} diff --git a/fmt/internal/format/utils_wbtest.mbt b/fmt/internal/format/utils_wbtest.mbt new file mode 100644 index 00000000..84d2845f --- /dev/null +++ b/fmt/internal/format/utils_wbtest.mbt @@ -0,0 +1,62 @@ +///| +test "series" { + let integer = fn(x : Int) { text(x.to_string()) } + let r1 = series(@list.from_array([1, 2, 3, 4]), comma, integer) + inspect( + r1, + content=( + #|1, + #|2, + #|3, + #|4, + ), + ) + let r2 = series( + @list.from_array([1, 2, 3, 4]), + comma, + integer, + trailing=AlwaysHidden, + ) + inspect( + r2, + content=( + #|1, + #|2, + #|3, + #|4 + ), + ) + let r3 = group(series(@list.from_array([1, 2, 3, 4]), comma, integer)) + inspect(r3, content="1, 2, 3, 4") +} + +///| +test "visibility token table records modifier and parentheses" { + let source = "pub(all) struct T {}\n" + let (impls, reports, tokens) = parse(source) + if reports.length() != 0 { + fail("expected pub(all) fixture to parse without diagnostics") + } + let vis_loc = match impls { + More(TopTypeDef({ type_vis: Pub(attr=Some(_), loc~), .. }), ..) => loc + _ => fail("expected a public type declaration") + } + let table = @comment.Mapper::new(tokens, impls).get_table(vis_loc) + match + ( + table.get_token("lparen"), + table.get_token("lident"), + table.get_token("rparen"), + ) { + (Some(lparen), Some(lident), Some(rparen)) => { + if lparen.loc() == lident.loc() { + fail("visibility lparen token should not be reused as modifier token") + } + if lident.loc() == rparen.loc() { + fail("visibility modifier token should not be reused as rparen token") + } + } + _ => + fail("expected visibility token table to include lparen, lident, rparen") + } +} diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence.input b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence.input new file mode 100644 index 00000000..0aeee3dd --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence.input @@ -0,0 +1,14 @@ +fn f() -> Unit { + //1 + f() //2 + //3 + g() //4 + //5 + h() //6 + //7 +} + +fn g() -> Unit {} + + + diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence.output b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence.output new file mode 100644 index 00000000..2c71cc5e --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence.output @@ -0,0 +1,153 @@ +///| +fn f() -> Unit { + //1 + f() //2 + //3 + g() //4 + //5 + h() //6 +} + +///| +fn g() -> Unit { + +} + +========================================================== +// generated file, do not edit! +[ + { + kind: //1, + loc: -2:3-2:6, + surround: (`{` (-1:16-1:17), id (lowercase start) (-3:3-3:4)), + }, + {kind: //2, loc: -3:7-3:10, surround: (`)` (-3:5-3:6), COMMENT (-4:3-4:6))}, + { + kind: //3, + loc: -4:3-4:6, + surround: (`)` (-3:5-3:6), id (lowercase start) (-5:3-5:4)), + }, + {kind: //4, loc: -5:7-5:10, surround: (`)` (-5:5-5:6), COMMENT (-6:3-6:6))}, + { + kind: //5, + loc: -6:3-6:6, + surround: (`)` (-5:5-5:6), id (lowercase start) (-7:3-7:4)), + }, + {kind: //6, loc: -7:7-7:10, surround: (`)` (-7:5-7:6), COMMENT (-8:3-8:6))}, + {kind: //7, loc: -8:3-8:6, surround: (`)` (-7:5-7:6), `}` (-9:1-9:2))}, + {kind: BLANK, loc: -9:2-10:2, surround: (`}` (-9:1-9:2), `fn` (-11:1-11:3))}, + { + kind: BLANK, + loc: -11:18-14:2, + surround: (`}` (-11:17-11:18), EOF (-15:1-15:1)), + } +] + +{ + -9:1-9:2: [ + {kind: //7, loc: -8:3-8:6, surround: (`)` (-7:5-7:6), `}` (-9:1-9:2))} + ], + -1:1-9:2: [ + {kind: BLANK, loc: -9:2-10:2, surround: (`}` (-9:1-9:2), `fn` (-11:1-11:3))} + ], + -3:3-3:6: [ + {kind: //2, loc: -3:7-3:10, surround: (`)` (-3:5-3:6), COMMENT (-4:3-4:6))} + ], + -3:3-7:6: [ + { + kind: //1, + loc: -2:3-2:6, + surround: (`{` (-1:16-1:17), id (lowercase start) (-3:3-3:4)), + }, + {kind: //6, loc: -7:7-7:10, surround: (`)` (-7:5-7:6), COMMENT (-8:3-8:6))} + ], + -5:3-5:6: [ + { + kind: //3, + loc: -4:3-4:6, + surround: (`)` (-3:5-3:6), id (lowercase start) (-5:3-5:4)), + }, + {kind: //4, loc: -5:7-5:10, surround: (`)` (-5:5-5:6), COMMENT (-6:3-6:6))} + ], + -7:3-7:6: [ + { + kind: //5, + loc: -6:3-6:6, + surround: (`)` (-5:5-5:6), id (lowercase start) (-7:3-7:4)), + } + ] +} + +{ + -1:1-9:2: { + "async": Token(None), + "fn": Token(Some(-1:1-1:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-1:5-1:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-1:6-1:7)), + "thin_arrow": Token(Some(-1:8-1:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-1:16-1:17)), + "rbrace": Token(Some(-9:1-9:2)) + }, + -11:1-11:18: { + "async": Token(None), + "fn": Token(Some(-11:1-11:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-11:5-11:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-11:6-11:7)), + "thin_arrow": Token(Some(-11:8-11:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-11:16-11:17)), + "rbrace": Token(Some(-11:17-11:18)) + } +} + +{ + -1:1-9:2: { + "async": Token(None), + "fn": Token(Some(-1:1-1:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-1:5-1:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-1:6-1:7)), + "thin_arrow": Token(Some(-1:8-1:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-1:16-1:17)), + "rbrace": Token(Some(-9:1-9:2)) + }, + -11:1-11:18: { + "async": Token(None), + "fn": Token(Some(-11:1-11:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-11:5-11:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-11:6-11:7)), + "thin_arrow": Token(Some(-11:8-11:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-11:16-11:17)), + "rbrace": Token(Some(-11:17-11:18)) + } +} diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array.input b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array.input new file mode 100644 index 00000000..d7d3d00b --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array.input @@ -0,0 +1,20 @@ +fn f() -> Unit { + //1 + f //2 + // 3 + [ //4 + g,//5 + //6 + ] //7 + h //8 + //9 +} + +fn g() -> Unit {} + + + + + + + diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array.output b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array.output new file mode 100644 index 00000000..32c05799 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array.output @@ -0,0 +1,210 @@ +///| +fn f() -> Unit { + //1 + f //2 + // 3 + [g] //7 + h //8 +} + +///| +fn g() -> Unit { + +} + +========================================================== +// generated file, do not edit! +[ + { + kind: //1, + loc: -2:3-2:6, + surround: (`{` (-1:16-1:17), id (lowercase start) (-3:3-3:4)), + }, + { + kind: //2, + loc: -3:5-3:8, + surround: (id (lowercase start) (-3:3-3:4), COMMENT (-4:3-4:7)), + }, + { + kind: // 3, + loc: -4:3-4:7, + surround: (id (lowercase start) (-3:3-3:4), `[` (-5:3-5:4)), + }, + { + kind: //4, + loc: -5:5-5:8, + surround: (`[` (-5:3-5:4), id (lowercase start) (-6:5-6:6)), + }, + {kind: //5, loc: -6:7-6:10, surround: (`,` (-6:6-6:7), COMMENT (-7:5-7:8))}, + {kind: //6, loc: -7:5-7:8, surround: (`,` (-6:6-6:7), `]` (-8:3-8:4))}, + { + kind: //7, + loc: -8:5-8:8, + surround: (`]` (-8:3-8:4), id (lowercase start) (-9:3-9:4)), + }, + { + kind: //8, + loc: -9:5-9:8, + surround: (id (lowercase start) (-9:3-9:4), COMMENT (-10:3-10:6)), + }, + { + kind: //9, + loc: -10:3-10:6, + surround: (id (lowercase start) (-9:3-9:4), `}` (-11:1-11:2)), + }, + { + kind: BLANK, + loc: -11:2-12:2, + surround: (`}` (-11:1-11:2), `fn` (-13:1-13:3)), + }, + { + kind: BLANK, + loc: -13:18-20:2, + surround: (`}` (-13:17-13:18), EOF (-21:1-21:1)), + } +] + +{ + -5:3-5:4: [ + { + kind: //4, + loc: -5:5-5:8, + surround: (`[` (-5:3-5:4), id (lowercase start) (-6:5-6:6)), + } + ], + -6:6-6:7: [ + {kind: //5, loc: -6:7-6:10, surround: (`,` (-6:6-6:7), COMMENT (-7:5-7:8))} + ], + -8:3-8:4: [ + {kind: //6, loc: -7:5-7:8, surround: (`,` (-6:6-6:7), `]` (-8:3-8:4))} + ], + -11:1-11:2: [ + { + kind: //9, + loc: -10:3-10:6, + surround: (id (lowercase start) (-9:3-9:4), `}` (-11:1-11:2)), + } + ], + -1:1-11:2: [ + { + kind: BLANK, + loc: -11:2-12:2, + surround: (`}` (-11:1-11:2), `fn` (-13:1-13:3)), + } + ], + -3:3-3:4: [ + { + kind: //2, + loc: -3:5-3:8, + surround: (id (lowercase start) (-3:3-3:4), COMMENT (-4:3-4:7)), + } + ], + -3:3-9:4: [ + { + kind: //1, + loc: -2:3-2:6, + surround: (`{` (-1:16-1:17), id (lowercase start) (-3:3-3:4)), + }, + { + kind: //8, + loc: -9:5-9:8, + surround: (id (lowercase start) (-9:3-9:4), COMMENT (-10:3-10:6)), + } + ], + -5:3-8:4: [ + { + kind: // 3, + loc: -4:3-4:7, + surround: (id (lowercase start) (-3:3-3:4), `[` (-5:3-5:4)), + }, + { + kind: //7, + loc: -8:5-8:8, + surround: (`]` (-8:3-8:4), id (lowercase start) (-9:3-9:4)), + } + ] +} + +{ + -5:3-8:4: { + "lbracket": Token(Some(-5:3-5:4)), + "comma": TokenArray([Some(-6:6-6:7)]), + "rbracket": Token(Some(-8:3-8:4)) + }, + -1:1-11:2: { + "async": Token(None), + "fn": Token(Some(-1:1-1:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-1:5-1:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-1:6-1:7)), + "thin_arrow": Token(Some(-1:8-1:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-1:16-1:17)), + "rbrace": Token(Some(-11:1-11:2)) + }, + -13:1-13:18: { + "async": Token(None), + "fn": Token(Some(-13:1-13:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-13:5-13:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-13:6-13:7)), + "thin_arrow": Token(Some(-13:8-13:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-13:16-13:17)), + "rbrace": Token(Some(-13:17-13:18)) + } +} + +{ + -5:3-8:4: { + "lbracket": Token(Some(-5:3-5:4)), + "comma": TokenArray([Some(-6:6-6:7)]), + "rbracket": Token(Some(-8:3-8:4)) + }, + -1:1-11:2: { + "async": Token(None), + "fn": Token(Some(-1:1-1:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-1:5-1:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-1:6-1:7)), + "thin_arrow": Token(Some(-1:8-1:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-1:16-1:17)), + "rbrace": Token(Some(-11:1-11:2)) + }, + -13:1-13:18: { + "async": Token(None), + "fn": Token(Some(-13:1-13:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-13:5-13:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-13:6-13:7)), + "thin_arrow": Token(Some(-13:8-13:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-13:16-13:17)), + "rbrace": Token(Some(-13:17-13:18)) + } +} diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array2.input b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array2.input new file mode 100644 index 00000000..47c632af --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array2.input @@ -0,0 +1,18 @@ +fn f() -> Unit { + //1 + f() //2 + // 3 + [ //4 + //4a + g1, //5 + //6 + g2, //7 + //8 + ] //9 + h //10 + //11 +} + +fn g() -> Unit {} + + diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array2.output b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array2.output new file mode 100644 index 00000000..740e50e9 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array2.output @@ -0,0 +1,223 @@ +///| +fn f() -> Unit { + //1 + f() //2 + // 3 + [ + //4a + g1, + //6 + g2, + ] //9 + h //10 +} + +///| +fn g() -> Unit { + +} + +========================================================== +// generated file, do not edit! +[ + { + kind: //1, + loc: -2:3-2:6, + surround: (`{` (-1:16-1:17), id (lowercase start) (-3:3-3:4)), + }, + {kind: //2, loc: -3:7-3:10, surround: (`)` (-3:5-3:6), COMMENT (-4:3-4:7))}, + {kind: // 3, loc: -4:3-4:7, surround: (`)` (-3:5-3:6), `[` (-5:3-5:4))}, + {kind: //4, loc: -5:5-5:8, surround: (`[` (-5:3-5:4), COMMENT (-6:5-6:9))}, + { + kind: //4a, + loc: -6:5-6:9, + surround: (`[` (-5:3-5:4), id (lowercase start) (-7:5-7:7)), + }, + {kind: //5, loc: -7:9-7:12, surround: (`,` (-7:7-7:8), COMMENT (-8:5-8:8))}, + { + kind: //6, + loc: -8:5-8:8, + surround: (`,` (-7:7-7:8), id (lowercase start) (-9:5-9:7)), + }, + {kind: //7, loc: -9:9-9:12, surround: (`,` (-9:7-9:8), COMMENT (-10:5-10:8))}, + {kind: //8, loc: -10:5-10:8, surround: (`,` (-9:7-9:8), `]` (-11:3-11:4))}, + { + kind: //9, + loc: -11:5-11:8, + surround: (`]` (-11:3-11:4), id (lowercase start) (-12:3-12:4)), + }, + { + kind: //10, + loc: -12:5-12:9, + surround: (id (lowercase start) (-12:3-12:4), COMMENT (-13:3-13:7)), + }, + { + kind: //11, + loc: -13:3-13:7, + surround: (id (lowercase start) (-12:3-12:4), `}` (-14:1-14:2)), + }, + { + kind: BLANK, + loc: -14:2-15:2, + surround: (`}` (-14:1-14:2), `fn` (-16:1-16:3)), + }, + { + kind: BLANK, + loc: -16:18-18:2, + surround: (`}` (-16:17-16:18), EOF (-19:1-19:1)), + } +] + +{ + -5:3-5:4: [ + {kind: //4, loc: -5:5-5:8, surround: (`[` (-5:3-5:4), COMMENT (-6:5-6:9))} + ], + -7:7-7:8: [ + {kind: //5, loc: -7:9-7:12, surround: (`,` (-7:7-7:8), COMMENT (-8:5-8:8))} + ], + -9:7-9:8: [ + { + kind: //7, + loc: -9:9-9:12, + surround: (`,` (-9:7-9:8), COMMENT (-10:5-10:8)), + } + ], + -11:3-11:4: [ + {kind: //8, loc: -10:5-10:8, surround: (`,` (-9:7-9:8), `]` (-11:3-11:4))} + ], + -14:1-14:2: [ + { + kind: //11, + loc: -13:3-13:7, + surround: (id (lowercase start) (-12:3-12:4), `}` (-14:1-14:2)), + } + ], + -1:1-14:2: [ + { + kind: BLANK, + loc: -14:2-15:2, + surround: (`}` (-14:1-14:2), `fn` (-16:1-16:3)), + } + ], + -3:3-3:6: [ + {kind: //2, loc: -3:7-3:10, surround: (`)` (-3:5-3:6), COMMENT (-4:3-4:7))} + ], + -3:3-12:4: [ + { + kind: //1, + loc: -2:3-2:6, + surround: (`{` (-1:16-1:17), id (lowercase start) (-3:3-3:4)), + }, + { + kind: //10, + loc: -12:5-12:9, + surround: (id (lowercase start) (-12:3-12:4), COMMENT (-13:3-13:7)), + } + ], + -5:3-11:4: [ + {kind: // 3, loc: -4:3-4:7, surround: (`)` (-3:5-3:6), `[` (-5:3-5:4))}, + { + kind: //9, + loc: -11:5-11:8, + surround: (`]` (-11:3-11:4), id (lowercase start) (-12:3-12:4)), + } + ], + -7:5-7:7: [ + { + kind: //4a, + loc: -6:5-6:9, + surround: (`[` (-5:3-5:4), id (lowercase start) (-7:5-7:7)), + } + ], + -9:5-9:7: [ + { + kind: //6, + loc: -8:5-8:8, + surround: (`,` (-7:7-7:8), id (lowercase start) (-9:5-9:7)), + } + ] +} + +{ + -5:3-11:4: { + "lbracket": Token(Some(-5:3-5:4)), + "comma": TokenArray([Some(-7:7-7:8), Some(-9:7-9:8)]), + "rbracket": Token(Some(-11:3-11:4)) + }, + -1:1-14:2: { + "async": Token(None), + "fn": Token(Some(-1:1-1:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-1:5-1:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-1:6-1:7)), + "thin_arrow": Token(Some(-1:8-1:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-1:16-1:17)), + "rbrace": Token(Some(-14:1-14:2)) + }, + -16:1-16:18: { + "async": Token(None), + "fn": Token(Some(-16:1-16:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-16:5-16:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-16:6-16:7)), + "thin_arrow": Token(Some(-16:8-16:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-16:16-16:17)), + "rbrace": Token(Some(-16:17-16:18)) + } +} + +{ + -5:3-11:4: { + "lbracket": Token(Some(-5:3-5:4)), + "comma": TokenArray([Some(-7:7-7:8), Some(-9:7-9:8)]), + "rbracket": Token(Some(-11:3-11:4)) + }, + -1:1-14:2: { + "async": Token(None), + "fn": Token(Some(-1:1-1:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-1:5-1:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-1:6-1:7)), + "thin_arrow": Token(Some(-1:8-1:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-1:16-1:17)), + "rbrace": Token(Some(-14:1-14:2)) + }, + -16:1-16:18: { + "async": Token(None), + "fn": Token(Some(-16:1-16:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-16:5-16:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-16:6-16:7)), + "thin_arrow": Token(Some(-16:8-16:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-16:16-16:17)), + "rbrace": Token(Some(-16:17-16:18)) + } +} diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array3.input b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array3.input new file mode 100644 index 00000000..8a6d3fe9 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array3.input @@ -0,0 +1,22 @@ +fn f() -> Unit { + //1 + f //2 + // 3 + [ //4 + [ //5 + g1, //6 (,会使67挂到g2) + //7 + g2, //8 (,导致89被移出数组,需要visit_node按顺序访问逗号) + //9 + ], //10 + ] //11 + //11a + //11b + //11c + h //12 + //13 +} + +fn g() -> Unit {} + + diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array3.output b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array3.output new file mode 100644 index 00000000..d0265a12 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/expr_sequence_array3.output @@ -0,0 +1,305 @@ +///| +fn f() -> Unit { + //1 + f //2 + // 3 + [ + [ + g1, + //7 + g2, + ], + ] //11 + //11a + //11b + //11c + h //12 +} + +///| +fn g() -> Unit { + +} + +========================================================== +// generated file, do not edit! +[ + { + kind: //1, + loc: -2:3-2:6, + surround: (`{` (-1:16-1:17), id (lowercase start) (-3:3-3:4)), + }, + { + kind: //2, + loc: -3:5-3:8, + surround: (id (lowercase start) (-3:3-3:4), COMMENT (-4:3-4:7)), + }, + { + kind: // 3, + loc: -4:3-4:7, + surround: (id (lowercase start) (-3:3-3:4), `[` (-5:3-5:4)), + }, + {kind: //4, loc: -5:5-5:8, surround: (`[` (-5:3-5:4), `[` (-6:5-6:6))}, + { + kind: //5, + loc: -6:7-6:10, + surround: (`[` (-6:5-6:6), id (lowercase start) (-7:7-7:9)), + }, + { + kind: //6 (,会使67挂到g2), + loc: -7:11-7:26, + surround: (`,` (-7:9-7:10), COMMENT (-8:7-8:10)), + }, + { + kind: //7, + loc: -8:7-8:10, + surround: (`,` (-7:9-7:10), id (lowercase start) (-9:7-9:9)), + }, + { + kind: //8 (,导致89被移出数组,需要visit_node按顺序访问逗号), + loc: -9:11-9:47, + surround: (`,` (-9:9-9:10), COMMENT (-10:7-10:10)), + }, + {kind: //9, loc: -10:7-10:10, surround: (`,` (-9:9-9:10), `]` (-11:5-11:6))}, + {kind: //10, loc: -11:8-11:12, surround: (`,` (-11:6-11:7), `]` (-12:3-12:4))}, + { + kind: //11, + loc: -12:5-12:9, + surround: (`]` (-12:3-12:4), COMMENT (-13:3-13:8)), + }, + { + kind: //11a, + loc: -13:3-13:8, + surround: (`]` (-12:3-12:4), COMMENT (-14:3-14:8)), + }, + { + kind: //11b, + loc: -14:3-14:8, + surround: (`]` (-12:3-12:4), COMMENT (-15:3-15:8)), + }, + { + kind: //11c, + loc: -15:3-15:8, + surround: (`]` (-12:3-12:4), id (lowercase start) (-16:3-16:4)), + }, + { + kind: //12, + loc: -16:5-16:9, + surround: (id (lowercase start) (-16:3-16:4), COMMENT (-17:3-17:7)), + }, + { + kind: //13, + loc: -17:3-17:7, + surround: (id (lowercase start) (-16:3-16:4), `}` (-18:1-18:2)), + }, + { + kind: BLANK, + loc: -18:2-19:2, + surround: (`}` (-18:1-18:2), `fn` (-20:1-20:3)), + }, + { + kind: BLANK, + loc: -20:18-22:2, + surround: (`}` (-20:17-20:18), EOF (-23:1-23:1)), + } +] + +{ + -5:3-5:4: [ + {kind: //4, loc: -5:5-5:8, surround: (`[` (-5:3-5:4), `[` (-6:5-6:6))} + ], + -6:5-6:6: [ + { + kind: //5, + loc: -6:7-6:10, + surround: (`[` (-6:5-6:6), id (lowercase start) (-7:7-7:9)), + } + ], + -7:9-7:10: [ + { + kind: //6 (,会使67挂到g2), + loc: -7:11-7:26, + surround: (`,` (-7:9-7:10), COMMENT (-8:7-8:10)), + } + ], + -9:9-9:10: [ + { + kind: //8 (,导致89被移出数组,需要visit_node按顺序访问逗号), + loc: -9:11-9:47, + surround: (`,` (-9:9-9:10), COMMENT (-10:7-10:10)), + } + ], + -11:5-11:6: [ + {kind: //9, loc: -10:7-10:10, surround: (`,` (-9:9-9:10), `]` (-11:5-11:6))} + ], + -11:6-11:7: [ + { + kind: //10, + loc: -11:8-11:12, + surround: (`,` (-11:6-11:7), `]` (-12:3-12:4)), + } + ], + -18:1-18:2: [ + { + kind: //13, + loc: -17:3-17:7, + surround: (id (lowercase start) (-16:3-16:4), `}` (-18:1-18:2)), + } + ], + -1:1-18:2: [ + { + kind: BLANK, + loc: -18:2-19:2, + surround: (`}` (-18:1-18:2), `fn` (-20:1-20:3)), + } + ], + -3:3-3:4: [ + { + kind: //2, + loc: -3:5-3:8, + surround: (id (lowercase start) (-3:3-3:4), COMMENT (-4:3-4:7)), + } + ], + -3:3-16:4: [ + { + kind: //1, + loc: -2:3-2:6, + surround: (`{` (-1:16-1:17), id (lowercase start) (-3:3-3:4)), + }, + { + kind: //12, + loc: -16:5-16:9, + surround: (id (lowercase start) (-16:3-16:4), COMMENT (-17:3-17:7)), + } + ], + -5:3-12:4: [ + { + kind: // 3, + loc: -4:3-4:7, + surround: (id (lowercase start) (-3:3-3:4), `[` (-5:3-5:4)), + }, + { + kind: //11, + loc: -12:5-12:9, + surround: (`]` (-12:3-12:4), COMMENT (-13:3-13:8)), + }, + { + kind: //11a, + loc: -13:3-13:8, + surround: (`]` (-12:3-12:4), COMMENT (-14:3-14:8)), + } + ], + -9:7-9:9: [ + { + kind: //7, + loc: -8:7-8:10, + surround: (`,` (-7:9-7:10), id (lowercase start) (-9:7-9:9)), + } + ], + -16:3-16:4: [ + { + kind: //11b, + loc: -14:3-14:8, + surround: (`]` (-12:3-12:4), COMMENT (-15:3-15:8)), + }, + { + kind: //11c, + loc: -15:3-15:8, + surround: (`]` (-12:3-12:4), id (lowercase start) (-16:3-16:4)), + } + ] +} + +{ + -6:5-11:6: { + "lbracket": Token(Some(-6:5-6:6)), + "comma": TokenArray([Some(-7:9-7:10), Some(-9:9-9:10)]), + "rbracket": Token(Some(-11:5-11:6)) + }, + -5:3-12:4: { + "lbracket": Token(Some(-5:3-5:4)), + "comma": TokenArray([Some(-11:6-11:7)]), + "rbracket": Token(Some(-12:3-12:4)) + }, + -1:1-18:2: { + "async": Token(None), + "fn": Token(Some(-1:1-1:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-1:5-1:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-1:6-1:7)), + "thin_arrow": Token(Some(-1:8-1:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-1:16-1:17)), + "rbrace": Token(Some(-18:1-18:2)) + }, + -20:1-20:18: { + "async": Token(None), + "fn": Token(Some(-20:1-20:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-20:5-20:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-20:6-20:7)), + "thin_arrow": Token(Some(-20:8-20:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-20:16-20:17)), + "rbrace": Token(Some(-20:17-20:18)) + } +} + +{ + -6:5-11:6: { + "lbracket": Token(Some(-6:5-6:6)), + "comma": TokenArray([Some(-7:9-7:10), Some(-9:9-9:10)]), + "rbracket": Token(Some(-11:5-11:6)) + }, + -5:3-12:4: { + "lbracket": Token(Some(-5:3-5:4)), + "comma": TokenArray([Some(-11:6-11:7)]), + "rbracket": Token(Some(-12:3-12:4)) + }, + -1:1-18:2: { + "async": Token(None), + "fn": Token(Some(-1:1-1:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-1:5-1:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-1:6-1:7)), + "thin_arrow": Token(Some(-1:8-1:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-1:16-1:17)), + "rbrace": Token(Some(-18:1-18:2)) + }, + -20:1-20:18: { + "async": Token(None), + "fn": Token(Some(-20:1-20:3)), + "lbracket": Token(None), + "commas0": TokenArray([]), + "type_var_binders": TableArray([]), + "rbracket": Token(None), + "lparen": Token(Some(-20:5-20:6)), + "parameters": TableArray([]), + "commas1": TokenArray([]), + "rparen": Token(Some(-20:6-20:7)), + "thin_arrow": Token(Some(-20:8-20:10)), + "raise_or_noraise": Token(None), + "question": Token(None), + "lbrace": Token(Some(-20:16-20:17)), + "rbrace": Token(Some(-20:17-20:18)) + } +} diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/stmt.output b/fmt/internal/testsuite/comment_test/__snapshot__/stmt.output new file mode 100644 index 00000000..daefeaf6 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/stmt.output @@ -0,0 +1,25 @@ +fn f() -> Unit { + f() + [g()] + h() +} + +fn g() -> Unit { + +} + +========================================================== +// generated file, do not edit! +{ + -3:3-3:6: ([Line("//2") (-3:7-3:10)], []), + -3:3-9:6: ( + [Line("//1") (-2:3-2:6), Line("//8") (-9:7-9:10), Line("//9") (-10:3-10:6)], + [] + ), + -5:3-8:4: ([Line("// 3") (-4:3-4:7), Line("//7") (-8:5-8:8)], []), + -6:5-6:8: ( + [Line("//4") (-5:5-5:8), Line("//5") (-6:8-6:11), Line("//6") (-7:5-7:8)], + [] + ), + -13:16-13:18: ([Blank (-11:2-12:2)], []) +} diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/top_func.input b/fmt/internal/testsuite/comment_test/__snapshot__/top_func.input new file mode 100644 index 00000000..2fabde72 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/top_func.input @@ -0,0 +1,25 @@ +// 0 +pub //1 + async //2 + //2a + fn [ //2b + T, //2c + R //2d + : //2e + Show + //2f + Compare, //2g + //3 + ] f( //3a + p1 : T1, //4 + p2~ : //4a + T2, //4b + p3? //4c + : T3, //4d + p4~ : T4 = e //4e + ) //5 + -> R raise T2 { //6 + expr + //7 +}//8 + +let a = 0 diff --git a/fmt/internal/testsuite/comment_test/__snapshot__/top_func.output b/fmt/internal/testsuite/comment_test/__snapshot__/top_func.output new file mode 100644 index 00000000..31895b47 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/__snapshot__/top_func.output @@ -0,0 +1,340 @@ + // 0 +///| +pub //1 +async //2 + //2a +fn[ //2b + T, //2c + R //2d + : //2e + Show + Compare, //2g + //3 +] f( //3a + p1 : T1, //4 + p2~ : T2, //4b + p3? : T3, //4d + p4? : T4 = e, //4e +) -> R raise T2 { + expr +} //8 + +///| +let a =0 + +========================================================== +// generated file, do not edit! +[ + {kind: // 0, loc: -1:1-1:5, surround: (None, `pub` (-2:1-2:4))}, + {kind: //1, loc: -2:5-2:8, surround: (`pub` (-2:1-2:4), `async` (-3:2-3:7))}, + { + kind: //2, + loc: -3:8-3:11, + surround: (`async` (-3:2-3:7), COMMENT (-4:2-4:6)), + }, + {kind: //2a, loc: -4:2-4:6, surround: (`async` (-3:2-3:7), `fn` (-5:2-5:4))}, + { + kind: //2b, + loc: -5:7-5:11, + surround: (`[` (-5:5-5:6), id (uppercase start) (-6:3-6:4)), + }, + { + kind: //2c, + loc: -6:6-6:10, + surround: (`,` (-6:4-6:5), id (uppercase start) (-7:3-7:4)), + }, + { + kind: //2d , + loc: -7:5-7:10, + surround: (id (uppercase start) (-7:3-7:4), `:` (-8:5-8:6)), + }, + { + kind: //2e, + loc: -8:7-8:11, + surround: (`:` (-8:5-8:6), id (uppercase start) (-9:5-9:9)), + }, + { + kind: //2f, + loc: -9:12-9:16, + surround: (`+` (-9:10-9:11), id (uppercase start) (-10:6-10:13)), + }, + { + kind: //2g, + loc: -10:15-10:19, + surround: (`,` (-10:13-10:14), COMMENT (-11:3-11:7)), + }, + { + kind: //3 , + loc: -11:3-11:7, + surround: (`,` (-10:13-10:14), `]` (-12:2-12:3)), + }, + { + kind: //3a, + loc: -12:7-12:11, + surround: (`(` (-12:5-12:6), id (lowercase start) (-13:4-13:6)), + }, + { + kind: //4, + loc: -13:13-13:16, + surround: (`,` (-13:11-13:12), POST_LABEL (-14:4-14:7)), + }, + { + kind: //4a, + loc: -14:10-14:14, + surround: (`:` (-14:8-14:9), id (uppercase start) (-15:6-15:8)), + }, + { + kind: //4b, + loc: -15:10-15:14, + surround: (`,` (-15:8-15:9), id (lowercase start) (-16:4-16:6)), + }, + {kind: //4c, loc: -16:8-16:12, surround: (`?` (-16:6-16:7), `:` (-17:6-17:7))}, + { + kind: //4d, + loc: -17:12-17:16, + surround: (`,` (-17:10-17:11), POST_LABEL (-18:4-18:7)), + }, + { + kind: //4e, + loc: -18:17-18:21, + surround: (id (lowercase start) (-18:15-18:16), `)` (-19:2-19:3)), + }, + {kind: //5, loc: -19:4-19:7, surround: (`)` (-19:2-19:3), `->` (-20:4-20:6))}, + { + kind: //6, + loc: -20:20-20:23, + surround: (`{` (-20:18-20:19), id (lowercase start) (-21:3-21:7)), + }, + { + kind: //7, + loc: -22:3-22:6, + surround: (id (lowercase start) (-21:3-21:7), `}` (-23:1-23:2)), + }, + { + kind: //8, + loc: -23:2-23:5, + surround: (`}` (-23:1-23:2), NEWLINE (-24:1-24:2)), + } +] + +{ + -2:1-2:4: [ + {kind: //1, loc: -2:5-2:8, surround: (`pub` (-2:1-2:4), `async` (-3:2-3:7))} + ], + -3:2-3:7: [ + { + kind: //2, + loc: -3:8-3:11, + surround: (`async` (-3:2-3:7), COMMENT (-4:2-4:6)), + } + ], + -5:2-5:4: [ + {kind: //2a, loc: -4:2-4:6, surround: (`async` (-3:2-3:7), `fn` (-5:2-5:4))} + ], + -5:5-5:6: [ + { + kind: //2b, + loc: -5:7-5:11, + surround: (`[` (-5:5-5:6), id (uppercase start) (-6:3-6:4)), + } + ], + -6:4-6:5: [ + { + kind: //2c, + loc: -6:6-6:10, + surround: (`,` (-6:4-6:5), id (uppercase start) (-7:3-7:4)), + } + ], + -8:5-8:6: [ + { + kind: //2e, + loc: -8:7-8:11, + surround: (`:` (-8:5-8:6), id (uppercase start) (-9:5-9:9)), + } + ], + -10:13-10:14: [ + { + kind: //2g, + loc: -10:15-10:19, + surround: (`,` (-10:13-10:14), COMMENT (-11:3-11:7)), + } + ], + -12:2-12:3: [ + { + kind: //3 , + loc: -11:3-11:7, + surround: (`,` (-10:13-10:14), `]` (-12:2-12:3)), + } + ], + -12:5-12:6: [ + { + kind: //3a, + loc: -12:7-12:11, + surround: (`(` (-12:5-12:6), id (lowercase start) (-13:4-13:6)), + } + ], + -13:11-13:12: [ + { + kind: //4, + loc: -13:13-13:16, + surround: (`,` (-13:11-13:12), POST_LABEL (-14:4-14:7)), + } + ], + -14:8-14:9: [ + { + kind: //4a, + loc: -14:10-14:14, + surround: (`:` (-14:8-14:9), id (uppercase start) (-15:6-15:8)), + } + ], + -15:8-15:9: [ + { + kind: //4b, + loc: -15:10-15:14, + surround: (`,` (-15:8-15:9), id (lowercase start) (-16:4-16:6)), + } + ], + -16:6-16:7: [ + { + kind: //4c, + loc: -16:8-16:12, + surround: (`?` (-16:6-16:7), `:` (-17:6-17:7)), + } + ], + -17:10-17:11: [ + { + kind: //4d, + loc: -17:12-17:16, + surround: (`,` (-17:10-17:11), POST_LABEL (-18:4-18:7)), + } + ], + -19:2-19:3: [ + { + kind: //5, + loc: -19:4-19:7, + surround: (`)` (-19:2-19:3), `->` (-20:4-20:6)), + } + ], + -20:18-20:19: [ + { + kind: //6, + loc: -20:20-20:23, + surround: (`{` (-20:18-20:19), id (lowercase start) (-21:3-21:7)), + } + ], + -23:1-23:2: [ + { + kind: //7, + loc: -22:3-22:6, + surround: (id (lowercase start) (-21:3-21:7), `}` (-23:1-23:2)), + } + ], + -2:1-23:2: [ + {kind: // 0, loc: -1:1-1:5, surround: (None, `pub` (-2:1-2:4))}, + { + kind: //8, + loc: -23:2-23:5, + surround: (`}` (-23:1-23:2), NEWLINE (-24:1-24:2)), + } + ], + -7:3-7:4: [ + { + kind: //2d , + loc: -7:5-7:10, + surround: (id (uppercase start) (-7:3-7:4), `:` (-8:5-8:6)), + } + ], + -10:6-10:13: [ + { + kind: //2f, + loc: -9:12-9:16, + surround: (`+` (-9:10-9:11), id (uppercase start) (-10:6-10:13)), + } + ], + -18:4-18:16: [ + { + kind: //4e, + loc: -18:17-18:21, + surround: (id (lowercase start) (-18:15-18:16), `)` (-19:2-19:3)), + } + ] +} + +{ + -2:1-2:4: { + "pub": Token(Some(-2:1-2:4)), + "lparen": Token(None), + "lident": Token(None), + "rparen": Token(None) + }, + -2:1-23:2: { + "async": Token(Some(-3:2-3:7)), + "fn": Token(Some(-5:2-5:4)), + "lbracket": Token(Some(-5:5-5:6)), + "commas0": TokenArray([Some(-6:4-6:5), Some(-10:13-10:14)]), + "type_var_binders": TableArray( + [ + {"colon": Token(None), "pluses": TokenArray([])}, + {"colon": Token(Some(-8:5-8:6)), "pluses": TokenArray([None])} + ], + ), + "rbracket": Token(Some(-12:2-12:3)), + "lparen": Token(Some(-12:5-12:6)), + "parameters": TableArray( + [ + {"colon": Token(Some(-13:7-13:8))}, + {"colon": Token(Some(-14:8-14:9))}, + {"question": Token(Some(-16:6-16:7)), "colon": Token(Some(-17:6-17:7))}, + {"colon": Token(Some(-18:8-18:9)), "equal": Token(Some(-18:13-18:14))} + ], + ), + "commas1": TokenArray( + [Some(-13:11-13:12), Some(-15:8-15:9), Some(-17:10-17:11), None], + ), + "rparen": Token(Some(-19:2-19:3)), + "thin_arrow": Token(Some(-20:4-20:6)), + "raise_or_noraise": Token(Some(-20:9-20:14)), + "question": Token(None), + "lbrace": Token(Some(-20:18-20:19)), + "rbrace": Token(Some(-23:1-23:2)) + } +} + +{ + -2:1-2:4: { + "pub": Token(Some(-2:1-2:4)), + "lparen": Token(None), + "lident": Token(None), + "rparen": Token(None) + }, + -2:1-23:2: { + "async": Token(Some(-3:2-3:7)), + "fn": Token(Some(-5:2-5:4)), + "lbracket": Token(Some(-5:5-5:6)), + "commas0": TokenArray([Some(-6:4-6:5), Some(-10:13-10:14)]), + "type_var_binders": TableArray( + [ + {"colon": Token(None), "pluses": TokenArray([])}, + {"colon": Token(Some(-8:5-8:6)), "pluses": TokenArray([None])} + ], + ), + "rbracket": Token(Some(-12:2-12:3)), + "lparen": Token(Some(-12:5-12:6)), + "parameters": TableArray( + [ + {"colon": Token(Some(-13:7-13:8))}, + {"colon": Token(Some(-14:8-14:9))}, + {"question": Token(Some(-16:6-16:7)), "colon": Token(Some(-17:6-17:7))}, + {"colon": Token(Some(-18:8-18:9)), "equal": Token(Some(-18:13-18:14))} + ], + ), + "commas1": TokenArray( + [Some(-13:11-13:12), Some(-15:8-15:9), Some(-17:10-17:11), None], + ), + "rparen": Token(Some(-19:2-19:3)), + "thin_arrow": Token(Some(-20:4-20:6)), + "raise_or_noraise": Token(Some(-20:9-20:14)), + "question": Token(None), + "lbrace": Token(Some(-20:18-20:19)), + "rbrace": Token(Some(-23:1-23:2)) + } +} diff --git a/fmt/internal/testsuite/comment_test/comment_test.mbt b/fmt/internal/testsuite/comment_test/comment_test.mbt new file mode 100644 index 00000000..35a33552 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/comment_test.mbt @@ -0,0 +1,49 @@ +///| +fn read_fixture(path : String) -> String { + @fs.read_file_to_string(path) catch { + _ => @fs.read_file_to_string(path) catch { _ => "" } + } +} + +///| +fn @test.Test::run(t : Self) -> Unit raise { + let name = t.name() + let source = read_fixture( + "./fmt/internal/testsuite/comment_test/__snapshot__/\{name}.input", + ) + // Keep parser locations stable across LF and CRLF checkouts. + let source = source + .replace_all(old="\r\n", new="\n") + .replace_all(old="\r", new="\n") + let (comments_info, formatted) = @format.debug_comment(source) + t.write(formatted) + t.writeln("==========================================================") + t.writeln("// generated file, do not edit!") + t.writeln(comments_info) + t.snapshot(filename="\{name}.output") +} + +///| +test "expr_sequence" (t : @test.Test) { + t.run() +} + +///| +test "expr_sequence_array" (t : @test.Test) { + t.run() +} + +///| +test "expr_sequence_array2" (t : @test.Test) { + t.run() +} + +///| +test "expr_sequence_array3" (t : @test.Test) { + t.run() +} + +///| +test "top_func" (t : @test.Test) { + t.run() +} diff --git a/fmt/internal/testsuite/comment_test/moon.pkg b/fmt/internal/testsuite/comment_test/moon.pkg new file mode 100644 index 00000000..50356b68 --- /dev/null +++ b/fmt/internal/testsuite/comment_test/moon.pkg @@ -0,0 +1,8 @@ +import { +} + +import { + "moonbitlang/parser/fmt/internal/format", + "moonbitlang/x/fs", + "moonbitlang/core/test", +} for "test" diff --git a/fmt/internal/testsuite/comment_test/pkg.generated.mbti b/fmt/internal/testsuite/comment_test/pkg.generated.mbti new file mode 100644 index 00000000..07f428fe --- /dev/null +++ b/fmt/internal/testsuite/comment_test/pkg.generated.mbti @@ -0,0 +1,13 @@ +// Generated using `moon info`, DON'T EDIT IT +package "moonbitlang/parser/fmt/internal/testsuite/comment_test" + +// Values + +// Errors + +// Types and methods + +// Type aliases + +// Traits + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/arrow_function.output b/fmt/internal/testsuite/style_test/__snapshot__/arrow_function.output new file mode 100644 index 00000000..dcc0770e --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/arrow_function.output @@ -0,0 +1,36 @@ +// generated file, do not edit! +///| +fn arrow_function_with_single_param() -> Unit { + x => x + 1 +} + +///| +fn arrow_function_with_multiple_params() -> Unit { + (x, y, z) => x + y + z +} + +///| +fn arrow_function_with_no_param() -> Unit { + () => 42 +} + +///| +fn arrow_function_with_block_body() -> Unit { + (x, y) => { + let sum = x + y + sum * 2 + } +} + +///| +fn arrow_function_with_long_parameters() -> Unit { + ( + param1_________________, + param2_________________, + param3_________________, + param4_________________, + ) => { + param1_________________ + param2_________________ + param3_________________ + param4_________________ + } +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/async_toplevel.output b/fmt/internal/testsuite/style_test/__snapshot__/async_toplevel.output new file mode 100644 index 00000000..4adc04ac --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/async_toplevel.output @@ -0,0 +1,11 @@ +// generated file, do not edit! +///| +async fn main { + () +} + +///| +async test "async_test" { + () +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/attribute.output b/fmt/internal/testsuite/style_test/__snapshot__/attribute.output new file mode 100644 index 00000000..03fe1eae --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/attribute.output @@ -0,0 +1,40 @@ +// generated file, do not edit! +///| +#attribute1 +#attribute2(param1, param2="value", param3=true, f(a, b, c)) +#qual.attribute3 +#qual.attribute4(param="value", flag=true) +test { + +} + +///| +#alias("xxx") +fn f() -> Unit { + +} + +///| +#cfg(target="xxx") +struct Foo1 { + +} + +///| +#cfg(target="xxx") +enum Foo2 { + +} + +///| +#cfg(target="xxx") +trait Foo3 { + +} + +///| +#cfg(target="xxx") +impl Foo4 for Int with f() { + +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/cases.output b/fmt/internal/testsuite/style_test/__snapshot__/cases.output new file mode 100644 index 00000000..697605d9 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/cases.output @@ -0,0 +1,104 @@ +// generated file, do not edit! +///| +fn case1() -> Unit { + match constant { + BigInt(s) | Double(s) | Float(s) | UInt64(s) | UInt(s) | Int64(s) | Int(s) => + text(s) + } +} + +///| +fn case2() -> Unit { + match constant { + BigInt(s) | Double(s) | Float(s) | UInt64(s) | UInt(s) | Int64(s) | Int(s) => + text(s).method1(args).method2(args) + } +} + +///| +fn case3() -> Unit { + match expr { + Group(_) | StaticAssert(_) => { + let a = b + panic() + } + } +} + +///| +fn case4() -> Unit { + match expr { + Group(_) | StaticAssert(_) => { + match expr2 { + A(_) => 100 + B(_) => 200 + } + } + Group(_) | StaticAssert(_) => { + field1: 10000000000, + field2: 20000000000, + field3: 30000000000, + } + Group(_) | StaticAssert(_) => { + field1: 10000000000, + field2: 20000000000, + field3: 30000000000, + field4: 30000000000, + } + } +} + +///| +fn case5() -> Unit { + match expr { + Group(_) | StaticAssert(_) => { field1: 1, field2: 2, field3: 3 } + } +} + +///| +fn compose() -> Unit { + match a { + Pattern(long_args, long_args) => if a { b } else { c } + Pattern(long_args, long_args) => + if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { b } else { c } + Pattern(long_args, long_args) => { + if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { + bbbbbbbbbbbbbbbbbbbbbbbbbbb + } else { + ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + } + } + Pattern(long_args, long_args) => { + if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { + bbbbbbbbbbbbbbbbbbbbbbbbbbb + } else { + ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + } + } + } + { + let a = b + match a { + Pattern(long_args, long_args) => if a { b } else { c } + Pattern(long_args, long_args) => + if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { b } else { c } + Pattern(long_args, long_args) => { + if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { + bbbbbbbbbbbbbbbbbbbbbbbbbbb + } else { + ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + } + } + Pattern(long_args, long_args) => { + if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { + bbbbbbbbbbbbbbbbbbbbbbbbbbb + } else { + ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc + } + } + } + } + let a = b + +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/chained_dot.output b/fmt/internal/testsuite/style_test/__snapshot__/chained_dot.output new file mode 100644 index 00000000..6967bac5 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/chained_dot.output @@ -0,0 +1,151 @@ +// generated file, do not edit! +///| +fn chained_dot_oneline() -> Unit { + Builder::new(args).f(args).g(args).h(args) +} + +///| +fn chained_dot() -> Unit { + Builder::new(args) + .method1(arg1___________________, arg2_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________) + .method4(arg1___________________, arg2_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +///| +fn chained_dot_long_args() -> Unit { + Builder::new(args) + .method1( + arg1___________________, + arg2_______________________, + arg3_______________________, + ) + .method2(arg1___________________, arg2_______________________) + .method3( + arg1___________________, + arg2_______________________, + arg3_______________________, + ) + .method4( + arg1___________________, + arg2_______________________, + arg3_______________________, + ) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +///| +fn chained_cascade() -> Unit { + Builder::new(args) + ..method1(arg1___________________, arg2_______________________) + ..method2(arg1___________________, arg2_______________________) + ..method3(arg1___________________, arg2_______________________) + ..method4(arg1___________________, arg2_______________________) + ..method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +///| +fn chained_dot_short_self() -> Unit { + x.method1(arg1___________________, arg2_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________) + .method4(arg1___________________, arg2_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +///| +fn chained_dot_func() -> Unit { + func( + arg1__________________________________________, + arg2__________________________________________, + ) + .method1(arg1___________________, arg2_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________) + .method4(arg1___________________, arg2_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +///| +fn chained_dot_func_with_long_args() -> Unit { + func( + arg1__________________________________________, + arg2__________________________________________, + ) + .method1( + arg1___________________, + arg2_______________________, + arg3_______________________, + ) + .method2(arg1___________________, arg2_______________________) + .method3( + arg1___________________, + arg2_______________________, + arg3_______________________, + ) + TypeName::func( + arg1__________________________________________, + arg2__________________________________________, + ) + .method1( + arg1___________________, + arg2_______________________, + arg3_______________________, + ) + .method2(arg1___________________, arg2_______________________) + .method3( + arg1___________________, + arg2_______________________, + arg3_______________________, + ) +} + +///| +fn chained_dot_in_rhs() -> Unit { + let a = Builder::new(args) + .method1(arg1___________________, arg2_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________) + .method4(arg1___________________, arg2_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) + +} + +///| +fn single_dot() -> Unit { + object.long_method( + arg1___________________, + arg2_______________________, + arg3_______________________, + ) + object.short_method(arg1, arg2, arg3) + record.field + tuple.1 +} + +///| +fn mixing_dot_apply_and_record_label_and_tuple_index() -> Unit { + record.label1.method(arg1, arg2).label2.method(arg1, arg2) + record + .label1 + .method(arg1________________, arg2________________) + .1 + .label2 + .method(arg1________________, arg2________________) + .2 + .label3 + .label4 + .method(arg1________________, arg2________________) + .method(arg1________________, arg2________________) + .3 + .4 +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/compose_arrow_rhs.output b/fmt/internal/testsuite/style_test/__snapshot__/compose_arrow_rhs.output new file mode 100644 index 00000000..27429945 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/compose_arrow_rhs.output @@ -0,0 +1,66 @@ +// generated file, do not edit! +///| +fn arrow_ident_rhs() { + (a_____________, b______________, c_____________________) => { + (c__________________________________________________________) + } + (a_____________, b______________, c_____________________) => c__ +} + +///| +fn arrow_with_record_rhs() { + (a___________, b__________, c_________________) => { punning_field, } + (a_____________, b______________, c_____________________) => { + punning_field___________________________________________________, + } + (a_____________, b______________, c_____________________) => { + field1_____________________: value_________________, + field2_____________________: value_________________, + field3_____________________: value_________________, + } +} + +///| +fn arrow_with_ident_return_break_continue() { + ignore((a_____________, b______________, c_____________________) => { + return + }) + ignore((a_____________, b______________, c_____________________) => { + return + }) + ignore((a_____________, b______________, c_____________________) => { + continue + }) +} + +///| +fn arrow_in_function_call_with_return_break_continue_rhs() { + ignore((a, b) => { return }) + ignore((a, b) => { return }) + ignore((a, b) => { continue }) +} + +///| +fn arrow_in_function_call_with_control_flow_rhs() { + ignore((a, b) => { + if e___________________ { + a____________________________________ + } else if e_______________ { + b____________________________________ + } else { + c____________________________________ + } + }) +} + +///| +fn arrow_in_function_call_with_long_expr() { + ignore((a, b) => { + f____________________________________( + a__________________________________________, + b__________________________________________, + c__________________________________________, + ) + }) +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/compose_case_rhs.output b/fmt/internal/testsuite/style_test/__snapshot__/compose_case_rhs.output new file mode 100644 index 00000000..5a7e35ac --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/compose_case_rhs.output @@ -0,0 +1,181 @@ +// generated file, do not edit! +///| +fn collapsible_apply() -> Unit { + match e { + Pattern => f(arg1, arg2) + Pattern_______________________________________________________ => + f(arg1, arg2, arg3) + Pattern_______________________________________________________ => { + f( + arg1_____________________, + arg2_____________________, + arg3_____________________, + ) + } + Pattern_______________________________________________________ => { + f_______________________________( + arg1_____________________, + arg2_____________________, + arg3_____________________, + ) + } + } +} + +///| +fn collapsible_array() -> Unit { + match e { + Pattern => [arg1, arg2] + Pattern_______________________________________________________ => [ + arg1, + arg2, + arg3, + ] + Pattern_______________________________________________________ => [ + arg1_____________________, + arg2_____________________, + arg3_____________________, + ] + } +} + +///| +fn collapsible_record() -> Unit { + match e { + Pattern => { field1, field2 } + Pattern_______________________________________________________ => { + arg1, + arg2, + arg3, + } + Pattern_______________________________________________________ => { + arg1_____________________, + arg2_____________________, + arg3_____________________, + } + } +} + +///| +fn collapsible_constraint() -> Unit { + match e { + Pattern => (expr : Anno) + Pattern_______________________________________________________ => ( + expr________ + : Anno________ + ) + Pattern_______________________________________________________ => ( + expr_______________________________________________ + : Anno_________________________________________ + ) + } +} + +///| +fn collapsible_if() -> Unit { + match e { + Pattern => if a { b } else { c } + Pattern_______________________________________________________ => + if a { b } else { c } + Pattern_______________________________________________________ => { + if a { + b________________________________ + } else { + c_____________________________ + } + } + Pattern_______________________________________________________ => { + if a_______________________________ { + b__________________________ + } else { + c________________________ + } + } + } +} + +///| +fn collapsible_ident() -> Unit { + match e { + Pattern => id + Pattern_______________________________________________________ => + id__________________ + Pattern_______________________________________________________ => + id________________________________________________________________________________ + } +} + +///| +fn collapsible_dot_apply() -> Unit { + match e { + Pattern => self.method(arg1, arg2) + Pattern_______________________________________________________ => + self.method(arg1, arg2) + Pattern_______________________________________________________ => { + self.method( + arg1__________________________, + arg2____________________, + arg3_____________________, + ) + } + Pattern_______________________________________________________ => { + [ + arg1_________________________, + arg2_____________________, + arg3________________, + ].method( + arg1__________________________, + arg2____________________, + arg3_____________________, + ) + } + } +} + +///| +fn collapsible_dot_apply_chain() -> Unit { + match e { + Pattern => self.method(arg1, arg2).method2(arg1, arg2) + Pattern_______________________________________________________ => + self.method(arg1, arg2).method(arg1, arg2) + Pattern_______________________________________________________ => { + self + .method( + arg1__________________________, + arg2____________________, + arg3_____________________, + ) + .method(arg1____________________________, arg2____________________) + } + Pattern_______________________________________________________ => { + [ + arg1_________________________, + arg2_____________________, + arg3________________, + ] + .method( + arg1__________________________, + arg2____________________, + arg3_____________________, + ) + .method(arg1____________________________, arg2____________________) + } + } +} + +///| +fn not_collapsible_assign() -> Unit { + match e { + Pattern => a = b + Pattern_______________________________________________________ => { + a________ = b_________ + } + Pattern_______________________________________________________ => { + a________ = b______________________________________________________________________ + } + Pattern_______________________________________________________ => { + a__________________________________ = b____________________________________________ + } + } +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/compose_let_rhs.output b/fmt/internal/testsuite/style_test/__snapshot__/compose_let_rhs.output new file mode 100644 index 00000000..eac54ce9 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/compose_let_rhs.output @@ -0,0 +1,109 @@ +// generated file, do not edit! +///| +fn collapsible_apply() -> Unit { + let a = f(arg1, arg2) + let Pattern_______________________________________________________ = + f(arg1, arg2, arg3) + let Pattern_______________________________________________________ = f( + arg1_____________________, + arg2_____________________, + arg3_____________________, + ) + let Pattern_______________________________________________________ = + f_______________________________( + arg1_____________________, + arg2_____________________, + arg3_____________________, + ) + +} + +///| +fn collapsible_array() -> Unit { + let Pattern = [arg1, arg2] + let Pattern_______________________________________________________ = [ + arg1, + arg2, + arg3, + ] + let Pattern_______________________________________________________ = [ + arg1_____________________, + arg2_____________________, + arg3_____________________, + ] + +} + +///| +fn collapsible_record() -> Unit { + let Pattern = { field1, field2 } + let Pattern_______________________________________________________ = { + arg1, + arg2, + arg3, + } + let Pattern_______________________________________________________ = { + arg1_____________________, + arg2_____________________, + arg3_____________________, + } + +} + +///| +fn collapsible_constraint() -> Unit { + let Pattern = (expr : Anno) + let Pattern_______________________________________________________ = ( + expr________ + : Anno________ + ) + let Pattern_______________________________________________________ = ( + expr_______________________________________________ + : Anno_________________________________________ + ) + +} + +///| +fn collapsible_if() -> Unit { + let Pattern = if a { b } else { c } + let Pattern__________________________________________________________ = + if a { b } else { c } + let Pattern___________________________________________________ = if a { + b________________________________ + } else { + c_____________________________ + } + let Pattern___________________________________________________ = + if a_______________________________ { + b__________________________ + } else { + c________________________ + } + +} + +///| +fn collapsible_dot_apply() -> Unit { + let a = self.method(arg1, arg2) + let Pattern_______________________________________________________ = + self.method(arg1, arg2, arg3) + let Pattern_________________________ = self.method( + arg1_____________________, + arg2_____________________, + arg3_____________________, + ) + let Pattern_______________________________________________________ = { + [ + arg1_________________________, + arg2__________________________, + arg3____________________, + ].method( + arg1_____________________, + arg2_____________________, + arg3_____________________, + ) + } + +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/constant_literals.output b/fmt/internal/testsuite/style_test/__snapshot__/constant_literals.output new file mode 100644 index 00000000..2f519051 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/constant_literals.output @@ -0,0 +1,13 @@ +// generated file, do not edit! +///| +fn main { + let _ = 123 + let _ = 1_000_000 + let _ = 1000L + let _ = 14U + let _ = 14UL + let _ = 3.14 + let _ = 3.14F + +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/declare_keyword.output b/fmt/internal/testsuite/style_test/__snapshot__/declare_keyword.output new file mode 100644 index 00000000..e1c2a76c --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/declare_keyword.output @@ -0,0 +1,10 @@ +// generated file, do not edit! +///| +declare type T1 + +///| +declare pub type T2 + +///| +declare pub impl Show for T1 + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/docstring.output b/fmt/internal/testsuite/style_test/__snapshot__/docstring.output new file mode 100644 index 00000000..3b128dbb --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/docstring.output @@ -0,0 +1,79 @@ +// generated file, do not edit! +///| +/// docstring1 +/// docstring2 +fn f() -> Unit { + ... +} + +///| +/// Foo +/// .... +enum Foo { + /// Ctor1 + /// xxx + Ctor1 + /// Ctor2 + /// yyy + Ctor2 +} + +///| +/// record +/// ...... +struct Record { + /// field1 + field1 : Int + /// field2 + field2 : Int +} + +///| +/// trait +trait MyTrait { + meth(Self) -> Self +} + +///| +/// impl +impl MyTrait for Foo with meth(self) { + ... +} + +///| +/// impl relation +impl MyTrait for Foo + +///| +fn main { + +} + +///| +test { + +} + +///| +fn with_block_line() -> Unit { + ... +} + +///| +/// doc +fn with_doc() -> Unit { + ... +} + +///| +/// doc +fn with_legacy_doc_style() -> Unit { + ... +} + +///| +/// +fn with_blank_doc() -> Unit { + ... +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/ffi.output b/fmt/internal/testsuite/style_test/__snapshot__/ffi.output new file mode 100644 index 00000000..8e5bb8e2 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/ffi.output @@ -0,0 +1,21 @@ +// generated file, do not edit! +///| +extern "c" fn native(x : Int) -> Int = "xxxx" + +///| +extern "js" fn js1(x : Int) -> Unit = "js_code" + +///| +extern "js" fn js2(x : Int) = + #|js_code + #|js_code + +///| +extern "js" fn js3(x : Int) = #|js_code + +///| +fn wasm1(x : Int) -> Int = "xxxx" "xxxxxx" + +///| +fn intrinsics(x : Int) -> Int = "%xxxx" + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/for_where.output b/fmt/internal/testsuite/style_test/__snapshot__/for_where.output new file mode 100644 index 00000000..561759d4 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/for_where.output @@ -0,0 +1,8 @@ +// generated file, do not edit! +///| +fn f() -> Unit { + for i = 0; i < 10; i = i + 1 { + println(i.to_string()) + } where { proof_invariant: i >= 0, proof_reasoning: "i starts at 0" } +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/lexmatch.output b/fmt/internal/testsuite/style_test/__snapshot__/lexmatch.output new file mode 100644 index 00000000..376d5d99 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/lexmatch.output @@ -0,0 +1,9 @@ +// generated file, do not edit! +///| +fn f(s : String) -> Unit { + lexmatch s { + "a" => () + _ => () + } +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/mixed_infix.output b/fmt/internal/testsuite/style_test/__snapshot__/mixed_infix.output new file mode 100644 index 00000000..faf834dd --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/mixed_infix.output @@ -0,0 +1,24 @@ +// generated file, do not edit! +///| +fn collapsible_if() -> Unit { + match e { + Pattern => if a { b } else { c } + Pattern_______________________________________________________ => + if a { b } else { c } + Pattern_______________________________________________________ => { + if a { + b________________________________ + } else { + c_____________________________ + } + } + Pattern_______________________________________________________ => { + if a_______________________________ { + b__________________________ + } else { + c________________________ + } + } + } +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/struct_constructor.output b/fmt/internal/testsuite/style_test/__snapshot__/struct_constructor.output new file mode 100644 index 00000000..4365313b --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/struct_constructor.output @@ -0,0 +1,9 @@ +// generated file, do not edit! +///| +struct S { + x : Int + y : Int + + fn new(x~ : Int, y~ : Int) -> S +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/top_function.output b/fmt/internal/testsuite/style_test/__snapshot__/top_function.output new file mode 100644 index 00000000..45a26706 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/top_function.output @@ -0,0 +1,43 @@ +// generated file, do not edit! +///| +fn id(x : Int) -> Int { + ... +} + +///| +pub fn id(x : Int) -> Int { + ... +} + +///| +pub fn[T1, T2] f(x : T1) -> T2 { + ... +} + +///| +pub fn[ + T1 : Trait1_______ + Trait2__________, + T2 : Trait1________ + Trait2_________, +] line_wrap1( + v___________ : Int, + w___________ : TyCon[ + Ty1__________________, + Ty2________________, + Ty3________________, + ], + x__________~ : Int, + y__________? : Int, + z____________? : Int = some_function( + arg1____________, + arg2____________, + arg3_______________, + ), +) -> LongTyCon[ + Ty1________________, + Ty2______________, + Ty3______________, + Ty4______________, +] raise Error { + ... +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/trailing_block.output b/fmt/internal/testsuite/style_test/__snapshot__/trailing_block.output new file mode 100644 index 00000000..c98b18c4 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/trailing_block.output @@ -0,0 +1,77 @@ +// generated file, do not edit! +///| +fn trailing_function() { + function_callback( + a______________________, + b________________________, + fn(x________, y__________) { + let a = b + a + }, + ) + function_callback(a__________________, b__________________, fn(x__, y___) { + let a = b + a + }) + function_callback( + a_____________, + b____________, + c____________________, + fn(x__, y___) { + let a = b + a + }, + ) +} + +///| +fn trailing_arrow() { + function_callback( + a______________________, + b________________________, + (x________, y__________) => { + let a = b + a + }, + ) + function_callback(a__________________, b__________________, (x__, y___) => { + let a = b + a + }) + function_callback( + a_____________, + b____________, + c____________________, + (x__, y___) => { + let a = b + a + }, + ) +} + +///| +fn trailing_array() { + function_callback( + a______________________, + b________________________, + c______________, + [ + elem______________________, + elem______________________, + elem______________________, + ], + ) + function_callback(a__________________, b__________________, [ + elem______________________, + elem______________________, + elem______________________, + ]) +} + +///| +fn flatten_style() { + function_callback(a, b, c, fn(x, y) { a }) + function_callback(a, b, c, (x, y) => a) + function_callback(a, b, c, [elem, elem, elem]) +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/trait_impl_decl.output b/fmt/internal/testsuite/style_test/__snapshot__/trait_impl_decl.output new file mode 100644 index 00000000..13266527 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/trait_impl_decl.output @@ -0,0 +1,90 @@ +// generated file, do not edit! +///| +trait Num : Show + ToJson { + add(Self, Self) -> Self + sub(lhs~ : Self, rhs~ : Self) -> Self + mul(Self, Self) -> Self = _ + div(Self, Self) -> Self raise DivError + async from_string(String) -> Self +} + +///| +trait LineWrap1 : + Trait1____ + + Trait2____ + + Trait3____ + + Trait4____ + + Trait5____ + + Trait6____ { + f( + Ty1______________, + Ty2______________, + Ty3______________, + Ty4______________, + ) -> Self + g( + Ty1______________, + Ty2______________, + Ty3______________, + Ty4______________, + ) -> Self +} + +///| +impl Trait for Type with method(self, arg1, arg2) { + ... +} + +///| +impl Trait for Type with labeled(self, label1~, label2~) { + ... +} + +///| +impl Trait for Type with type_annotation( + self : Self, + label1~ : Int, + label2~ : Bool, +) -> Unit raise Error { + ... +} + +///| +impl Trait with f() { + ... +} + +///| +pub impl Trait with f() { + ... +} + +///| +pub impl[T1 : Trait1, T2 : Trait2] Trait for Type with f() { + ... +} + +///| +impl Trait for Type + +///| +impl[T1, T2] Trait for Type[T1, T2] + +///| +pub impl[T1 : Trait1 + Trait2, T2 : Trait1 + Trait2 + Trait3] Trait for Type[ + T1__________, + T2__________, + T3__________, + T4__________, + T5__________, + T6__________, +] with line_wrap_test( + self : Self, + label1____~ : Int, + label2____~ : Bool, + label3____~ : Bool, + label4____~ : Bool, +) -> Unit raise Error { + ... +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/typedecl_alias.output b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_alias.output new file mode 100644 index 00000000..702637cb --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_alias.output @@ -0,0 +1,5 @@ +// generated file, do not edit! +fn init{ + _ +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/typedecl_derive.output b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_derive.output new file mode 100644 index 00000000..51873099 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_derive.output @@ -0,0 +1,19 @@ +// generated file, do not edit! +///| +enum DeriveLineWrap1 { + +} derive(Show, Ty1, Ty2, Ty3(prop1="xxxxxx", prop2="xxxxxx")) + +///| +enum DeriveLineWrap2 { + +} derive( + Show________________, + Ty1_____________, + Ty2______________, + Ty3( + prop1="xxxxxx", + prop2="xxxxxx", + ), +) + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/typedecl_enum.output b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_enum.output new file mode 100644 index 00000000..2e928167 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_enum.output @@ -0,0 +1,58 @@ +// generated file, do not edit! +///| +enum List[T] { + Nil + Cons(T, List[T]) +} + +///| +enum CStyleEnum { + Mode1 = 1 + Mode2 = 2 + Mode3 = 3 +} + +///| +enum WithLabelEnum { + Ctor1(label~ : Int, Bool) + Ctor2(mut label~ : Int, Bool) +} + +///| +enum EmptyEnum { + +} + +///| +enum TypeParamsLineWrap[T1, T2, T3, T4, T5] { + +} + +///| +enum TypeParamsLineWrap2[ + T1______________, + T2____________, + T3___________, + T4___________, + T5____________, +] { + +} + +///| +enum ConstrLineWrap { + Constr2(Ty1, Ty2, Ty3, mut label~ : Ty4) + Constr1( + Ty1_________________, + Ty2________________, + TyCon[ + Ty1_____________, + Ty2____________, + Ty3____________, + Ty3_____________, + Ty4___________, + ], + mut label~ : Ty4_______________, + ) +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/typedecl_struct.output b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_struct.output new file mode 100644 index 00000000..92ccd97f --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_struct.output @@ -0,0 +1,36 @@ +// generated file, do not edit! +///| +struct List[T] { + value : T + mut next : List[T]? +} + +///| +struct TypeParamsLineWrap1[T1, T2, T2, T3, T4, T5] { + +} + +///| +struct TypeParamsLineWrap2[ + T1_________, + T2_________, + T2_________, + T3_________, + T4_________, + T5________, +] { + +} + +///| +struct FieldLineWrap { + field1 : TyCon[ + Ty1_______, + Ty2________, + Ty3___________, + Ty4_________, + Ty5__________, + ] + field2 : Bool +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/typedecl_suberror.output b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_suberror.output new file mode 100644 index 00000000..f4e5818f --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_suberror.output @@ -0,0 +1,37 @@ +// generated file, do not edit! +///| +suberror SubErr0 + +///| +suberror SubErr1 + +///| +suberror SubErr2 + +///| +suberror SubErr3 { + Constr1 + Constr2(Int, mut label~ : Int) +} + +///| +suberror PayloadLineWrap1 + +///| +suberror PayloadLineWrap2 + +///| +suberror PayloadLineWrap2 { + Ctor1(Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8) + Ctor2( + Ty1______, + Ty2______, + Ty3______, + Ty4______, + Ty5______, + Ty6______, + Ty7______, + mut label~ : Ty8______, + ) +} + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/typedecl_tuplestruct.output b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_tuplestruct.output new file mode 100644 index 00000000..56dceed6 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_tuplestruct.output @@ -0,0 +1,41 @@ +// generated file, do not edit! +///| +struct TupleStruct1(Int) + +///| +struct TupleStruct2(Int, Bool) + +///| +struct TupleStruct3[T1, T2, T3, T4, T5, T6](T1, T2, T3, T4, T5, T6) + +///| +struct TupleStructLineWrap1[T1______, T2______, T3______, T4______, T5______]( + T1______, + T2______, + T3______, + T4______, + T5______, +) + +///| +struct TupleStructLineWrap2[ + T1______, + T2______, + T3______, + T4______, + T5______, + T6______, +]( + T1______, + T2______, + T3______, + T4______, + T5______, + T6______, + Ty1________, + Ty2_______, + Ty3_______, + Ty4______, + Ty5______, +) + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/typedecl_type.output b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_type.output new file mode 100644 index 00000000..d3f18b85 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/typedecl_type.output @@ -0,0 +1,11 @@ +// generated file, do not edit! +///| +type Abstract1 + +///| +pub type Abstract2 + +///| +#external +type Abstract3 + diff --git a/fmt/internal/testsuite/style_test/__snapshot__/using_decl.output b/fmt/internal/testsuite/style_test/__snapshot__/using_decl.output new file mode 100644 index 00000000..229350c7 --- /dev/null +++ b/fmt/internal/testsuite/style_test/__snapshot__/using_decl.output @@ -0,0 +1,7 @@ +// generated file, do not edit! +///| +using @pkg { foo, bar } + +///| +pub using @pkg { type Foo, trait Bar } + diff --git a/fmt/internal/testsuite/style_test/fixtures/arrow_function.input b/fmt/internal/testsuite/style_test/fixtures/arrow_function.input new file mode 100644 index 00000000..b383ced6 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/arrow_function.input @@ -0,0 +1,28 @@ +fn arrow_function_with_single_param() -> Unit { + (x) => x + 1 +} + +fn arrow_function_with_multiple_params() -> Unit { + (x, y, z) => x + y + z +} + +fn arrow_function_with_no_param() -> Unit { + () => 42 +} + +fn arrow_function_with_block_body() -> Unit { + (x, y) => { + let sum = x + y + sum * 2 + } +} + +fn arrow_function_with_long_parameters() -> Unit { + (param1_________________, param2_________________, param3_________________, param4_________________) => + param1_________________ + param2_________________ + param3_________________ + param4_________________ +} + + + + + diff --git a/fmt/internal/testsuite/style_test/fixtures/async_toplevel.input b/fmt/internal/testsuite/style_test/fixtures/async_toplevel.input new file mode 100644 index 00000000..a889a325 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/async_toplevel.input @@ -0,0 +1,7 @@ +async fn main { + () +} + +async test "async_test" { + () +} diff --git a/fmt/internal/testsuite/style_test/fixtures/attribute.input b/fmt/internal/testsuite/style_test/fixtures/attribute.input new file mode 100644 index 00000000..9b027dce --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/attribute.input @@ -0,0 +1,41 @@ + +#attribute1 +#attribute2(param1, param2="value", param3=true, f(a,b,c)) +#qual.attribute3 +#qual.attribute4(param="value", flag=true) +test { + +} + +#alias("xxx") +fn f() -> Unit { + +} + +#cfg(target="xxx") +struct Foo1 {} + +#cfg(target="xxx") +enum Foo2 {} + +#cfg(target="xxx") +trait Foo3 {} + +#cfg(target="xxx") +impl Foo4 for Int with f(){} + + + + + + + + + + + + + + + + diff --git a/fmt/internal/testsuite/style_test/fixtures/cases.input b/fmt/internal/testsuite/style_test/fixtures/cases.input new file mode 100644 index 00000000..bf25ec87 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/cases.input @@ -0,0 +1,89 @@ +///| +fn case1() -> Unit { + match constant { + BigInt(s) + | Double(s) + | Float(s) + | UInt64(s) + | UInt(s) + | Int64(s) + | Int(s) => text(s) + } +} + +///| +fn case2() -> Unit { + match constant { + BigInt(s) + | Double(s) + | Float(s) + | UInt64(s) + | UInt(s) + | Int64(s) + | Int(s) => text(s).method1(args).method2(args) + } +} + +///| +fn case3() -> Unit { + match expr { + Group(_) | StaticAssert(_) => { + let a = b + panic() + } + } +} + +///| +fn case4() -> Unit { + match expr { + Group(_) | StaticAssert(_) => match expr2 { + A(_) => 100 + B(_) => 200 + } + Group(_) | StaticAssert(_) => { + field1: 10000000000, + field2: 20000000000, + field3: 30000000000, + } + Group(_) | StaticAssert(_) => { + field1: 10000000000, + field2: 20000000000, + field3: 30000000000, + field4: 30000000000, + } + } +} + +///| +fn case5() -> Unit { + match expr { + Group(_) | StaticAssert(_) => { + field1: 1, + field2: 2, + field3: 3, + } + } +} + + +fn compose() -> Unit { + match a { + Pattern(long_args, long_args) => if a { b } else { c } + Pattern(long_args, long_args) => if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { b } else { c } + Pattern(long_args, long_args) => if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { bbbbbbbbbbbbbbbbbbbbbbbbbbb } else { ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc } + Pattern(long_args, long_args) => if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { bbbbbbbbbbbbbbbbbbbbbbbbbbb } else { ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc } + } + + { + let a = b + match a { + Pattern(long_args, long_args) => if a { b } else { c } + Pattern(long_args, long_args) => if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { b } else { c } + Pattern(long_args, long_args) => if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { bbbbbbbbbbbbbbbbbbbbbbbbbbb } else { ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc } + Pattern(long_args, long_args) => if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { bbbbbbbbbbbbbbbbbbbbbbbbbbb } else { ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc } + } + + } + let a = b +} \ No newline at end of file diff --git a/fmt/internal/testsuite/style_test/fixtures/chained_dot.input b/fmt/internal/testsuite/style_test/fixtures/chained_dot.input new file mode 100644 index 00000000..b51e4171 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/chained_dot.input @@ -0,0 +1,106 @@ +fn chained_dot_oneline() -> Unit { + Builder::new(args).f(args).g(args).h(args) +} + +fn chained_dot() -> Unit { + Builder::new(args) + .method1(arg1___________________, arg2_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________) + .method4(arg1___________________, arg2_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +fn chained_dot_long_args() -> Unit { + Builder::new(args) + .method1(arg1___________________, arg2_______________________, arg3_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________, arg3_______________________) + .method4(arg1___________________, arg2_______________________, arg3_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +fn chained_cascade() -> Unit { + Builder::new(args) + ..method1(arg1___________________, arg2_______________________) + ..method2(arg1___________________, arg2_______________________) + ..method3(arg1___________________, arg2_______________________) + ..method4(arg1___________________, arg2_______________________) + ..method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +fn chained_dot_short_self() -> Unit { + x + .method1(arg1___________________, arg2_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________) + .method4(arg1___________________, arg2_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +fn chained_dot_func() -> Unit { + func( + arg1__________________________________________, + arg2__________________________________________, + ).method1(arg1___________________, arg2_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________) + .method4(arg1___________________, arg2_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +fn chained_dot_func_with_long_args() -> Unit { + func( + arg1__________________________________________, + arg2__________________________________________, + ).method1(arg1___________________, arg2_______________________, arg3_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________, arg3_______________________) + + TypeName::func( + arg1__________________________________________, + arg2__________________________________________, + ).method1(arg1___________________, arg2_______________________, arg3_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________, arg3_______________________) +} + +fn chained_dot_in_rhs() -> Unit { + let a = Builder::new(args) + .method1(arg1___________________, arg2_______________________) + .method2(arg1___________________, arg2_______________________) + .method3(arg1___________________, arg2_______________________) + .method4(arg1___________________, arg2_______________________) + .method5(arg1___________________, arg2_______________________) + .method6(arg1___________________, arg2_______________________) +} + +fn single_dot() -> Unit { + object.long_method(arg1___________________, arg2_______________________, arg3_______________________) + object.short_method(arg1, arg2, arg3) + record.field + tuple.1 +} + +fn mixing_dot_apply_and_record_label_and_tuple_index() -> Unit { + record.label1.method(arg1, arg2).label2.method(arg1, arg2) + record + .label1 + .method(arg1________________, arg2________________) + .1 + .label2 + .method(arg1________________, arg2________________) + .2 + .label3 + .label4 + .method(arg1________________, arg2________________) + .method(arg1________________, arg2________________) + .3 + .4 +} + diff --git a/fmt/internal/testsuite/style_test/fixtures/compose_arrow_rhs.input b/fmt/internal/testsuite/style_test/fixtures/compose_arrow_rhs.input new file mode 100644 index 00000000..40b0fec2 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/compose_arrow_rhs.input @@ -0,0 +1,71 @@ +fn arrow_ident_rhs(){ + (a_____________, b______________, c_____________________) => + c__________________________________________________________ + + (a_____________, b______________, c_____________________) => c__ +} + +fn arrow_with_record_rhs(){ + (a___________, b__________, c_________________) => { punning_field } + + (a_____________, b______________, c_____________________) => { + punning_field___________________________________________________ + } + + (a_____________, b______________, c_____________________) => { + field1_____________________: value_________________, + field2_____________________: value_________________, + field3_____________________: value_________________, + } +} + +fn arrow_with_ident_return_break_continue(){ + ignore((a_____________, b______________, c_____________________) => { + return + }) + ignore((a_____________, b______________, c_____________________) => { + return + }) + ignore((a_____________, b______________, c_____________________) => { + continue + }) +} + +fn arrow_in_function_call_with_return_break_continue_rhs(){ + ignore((a, b) => { + return + }) + ignore((a, b) => { + return + }) + ignore((a, b) => { + continue + }) +} + +fn arrow_in_function_call_with_control_flow_rhs(){ + ignore((a, b) => { + if e___________________ { + a____________________________________ + } else if e_______________ { + b____________________________________ + } else { + c____________________________________ + } + }) +} + +fn arrow_in_function_call_with_long_expr(){ + ignore((a, b) => { + f____________________________________( + a__________________________________________, + b__________________________________________, + c__________________________________________, + ) + }) +} + + + + + diff --git a/fmt/internal/testsuite/style_test/fixtures/compose_case_rhs.input b/fmt/internal/testsuite/style_test/fixtures/compose_case_rhs.input new file mode 100644 index 00000000..6bdbd710 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/compose_case_rhs.input @@ -0,0 +1,135 @@ +fn collapsible_apply() -> Unit { + match e { + // fits after `=>` + Pattern => f(arg1, arg2) + // fits in next line + Pattern_______________________________________________________ => f(arg1, arg2, arg3) + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + f(arg1_____________________, arg2_____________________, arg3_____________________) + // the head of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + f_______________________________(arg1_____________________, arg2_____________________, arg3_____________________) + } +} + +fn collapsible_array() -> Unit { + match e { + // fits after `=>` + Pattern => [arg1, arg2] + // fits in next line + Pattern_______________________________________________________ => [arg1, arg2, arg3] + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + [arg1_____________________, arg2_____________________, arg3_____________________] + } +} + +fn collapsible_record() -> Unit { + match e { + // fits after `=>` + Pattern => { field1, field2 } + // fits in next line + Pattern_______________________________________________________ => { arg1, arg2, arg3 } + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + { arg1_____________________, arg2_____________________, arg3_____________________ } + } +} + +fn collapsible_constraint() -> Unit { + match e { + // fits after `=>` + Pattern => (expr : Anno) + // fits in next line + Pattern_______________________________________________________ => (expr________ : Anno________) + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + (expr_______________________________________________ : Anno_________________________________________) + } +} + +fn collapsible_if() -> Unit { + match e { + // fits after `=>` + Pattern => if a { b } else { c } + // fits in next line + Pattern_______________________________________________________ => if a { b } else { c } + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => { + if a { + b________________________________ + } else { + c_____________________________ + } + } + // the head of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + if a_______________________________ { b__________________________ } else { c________________________ } + } +} + +fn collapsible_ident() -> Unit { + match e { + // fits after `=>` + Pattern => id + // fits in next line + Pattern_______________________________________________________ => id__________________ + // the collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + id________________________________________________________________________________ + } +} + +fn collapsible_dot_apply() -> Unit { + match e { + // fits after `=>` + Pattern => self.method(arg1, arg2) + // fits in next line + Pattern_______________________________________________________ => + self.method(arg1,arg2) + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + self.method(arg1__________________________, arg2____________________, arg3_____________________) + // the head of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + [arg1_________________________, arg2_____________________, arg3________________] + .method(arg1__________________________,arg2____________________, arg3_____________________) + } +} + +fn collapsible_dot_apply_chain() -> Unit { + match e { + // fits after `=>` + Pattern => self.method(arg1, arg2).method2(arg1, arg2) + // fits in next line + Pattern_______________________________________________________ => + self.method(arg1,arg2).method(arg1,arg2) + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + self.method(arg1__________________________, arg2____________________, arg3_____________________) + .method(arg1____________________________, arg2____________________) + // the head of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + [arg1_________________________, arg2_____________________, arg3________________] + .method(arg1__________________________,arg2____________________, arg3_____________________) + .method(arg1____________________________, arg2____________________) + } +} + +fn not_collapsible_assign() -> Unit { + match e { + // fits after `=>` + Pattern => a = b + // fits in next line + Pattern_______________________________________________________ => a________ = b_________ + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + a________ = b______________________________________________________________________ + // the head of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + a__________________________________ = b____________________________________________ + } +} + + diff --git a/fmt/internal/testsuite/style_test/fixtures/compose_let_rhs.input b/fmt/internal/testsuite/style_test/fixtures/compose_let_rhs.input new file mode 100644 index 00000000..d731f298 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/compose_let_rhs.input @@ -0,0 +1,71 @@ +fn collapsible_apply() -> Unit { + // fits after `=>` + let a = f(arg1, arg2) + // fits in next line + let Pattern_______________________________________________________ = f(arg1, arg2, arg3) + // the body of collapsible expression does not fit in the next line + let Pattern_______________________________________________________ = + f(arg1_____________________, arg2_____________________, arg3_____________________) + // the head of collapsible expression does not fit in the next line + let Pattern_______________________________________________________ = + f_______________________________(arg1_____________________, arg2_____________________, arg3_____________________) +} + +fn collapsible_array() -> Unit { + // fits after `=>` + let Pattern = [arg1, arg2] + // fits in next line + let Pattern_______________________________________________________ = [arg1, arg2, arg3] + // the body of collapsible expression does not fit in the next line + let Pattern_______________________________________________________ = + [arg1_____________________, arg2_____________________, arg3_____________________] +} + +fn collapsible_record() -> Unit { + // fits after `=>` + let Pattern = { field1, field2 } + // fits in next line + let Pattern_______________________________________________________ = { arg1, arg2, arg3 } + // the body of collapsible expression does not fit in the next line + let Pattern_______________________________________________________ = + { arg1_____________________, arg2_____________________, arg3_____________________ } +} + +fn collapsible_constraint() -> Unit { + // fits after `=>` + let Pattern = (expr : Anno) + // fits in next line + let Pattern_______________________________________________________ = (expr________ : Anno________) + // the body of collapsible expression does not fit in the next line + let Pattern_______________________________________________________ = + (expr_______________________________________________ : Anno_________________________________________) +} + +fn collapsible_if() -> Unit { + // fits after `=>` + let Pattern = if a { b } else { c } + // fits in next line + let Pattern__________________________________________________________ = if a { b } else { c } + // the body of collapsible expression does not fit in the next line + let Pattern___________________________________________________ = if a { + b________________________________ + } else { + c_____________________________ + } + // the head of collapsible expression does not fit in the next line + let Pattern___________________________________________________ = + if a_______________________________ { b__________________________ } else { c________________________ } +} + +fn collapsible_dot_apply() -> Unit { + // fits after `=>` + let a = self.method(arg1, arg2) + // fits in next line + let Pattern_______________________________________________________ = self.method(arg1, arg2, arg3) + // the body of collapsible expression does not fit in the next line + let Pattern_________________________ = + self.method(arg1_____________________, arg2_____________________, arg3_____________________) + // the head of collapsible expression does not fit in the next line + let Pattern_______________________________________________________ = + [arg1_________________________, arg2__________________________, arg3____________________].method(arg1_____________________, arg2_____________________, arg3_____________________) +} \ No newline at end of file diff --git a/fmt/internal/testsuite/style_test/fixtures/constant_literals.input b/fmt/internal/testsuite/style_test/fixtures/constant_literals.input new file mode 100644 index 00000000..8449a41c --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/constant_literals.input @@ -0,0 +1,11 @@ + +fn main { + let _ = 123 + let _ = 1_000_000 + let _ = 1000L + let _ = 14U + let _ = 14UL + let _ = 3.14 + let _ = 3.14F +} + diff --git a/fmt/internal/testsuite/style_test/fixtures/declare_keyword.input b/fmt/internal/testsuite/style_test/fixtures/declare_keyword.input new file mode 100644 index 00000000..9a90e564 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/declare_keyword.input @@ -0,0 +1,3 @@ +declare type T1 +declare pub type T2 +declare pub impl Show for T1 diff --git a/fmt/internal/testsuite/style_test/fixtures/docstring.input b/fmt/internal/testsuite/style_test/fixtures/docstring.input new file mode 100644 index 00000000..52781469 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/docstring.input @@ -0,0 +1,63 @@ + +/// docstring1 +/// docstring2 +fn f() -> Unit { + ... +} + +/// +/// Foo +/// .... +enum Foo { + /// Ctor1 + /// xxx + Ctor1 + /// Ctor2 + /// yyy + Ctor2 +} + +/// record +/// ...... +struct Record { + /// field1 + field1 : Int + /// field2 + field2 : Int +} + + +/// trait +trait MyTrait { + meth(Self) -> Self +} + +/// impl +impl MyTrait for Foo with meth(self) { + ... +} + +/// impl relation +impl MyTrait for Foo + +/// +fn main {} + +/// +test {} + +///| +fn with_block_line() -> Unit {...} + +///| +/// doc +fn with_doc() -> Unit {...} + +///| doc +fn with_legacy_doc_style() -> Unit {...} + +///| +/// +fn with_blank_doc() -> Unit {...} + + diff --git a/fmt/internal/testsuite/style_test/fixtures/ffi.input b/fmt/internal/testsuite/style_test/fixtures/ffi.input new file mode 100644 index 00000000..988769d4 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/ffi.input @@ -0,0 +1,13 @@ + +extern "c" fn native(x : Int) -> Int = "xxxx" +extern "js" fn js1(x : Int) -> Unit = "js_code" +extern "js" fn js2(x : Int) = + #|js_code + #|js_code + +extern "js" fn js3(x : Int) = #|js_code + +fn wasm1(x : Int) -> Int = "xxxx" "xxxxxx" +fn intrinsics(x : Int) -> Int = "%xxxx" + + diff --git a/fmt/internal/testsuite/style_test/fixtures/for_where.input b/fmt/internal/testsuite/style_test/fixtures/for_where.input new file mode 100644 index 00000000..b150580c --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/for_where.input @@ -0,0 +1,5 @@ +fn f() -> Unit { + for i = 0; i < 10; i = i + 1 { + println(i.to_string()) + } where { proof_invariant: i >= 0, proof_reasoning: "i starts at 0" } +} diff --git a/fmt/internal/testsuite/style_test/fixtures/lexmatch.input b/fmt/internal/testsuite/style_test/fixtures/lexmatch.input new file mode 100644 index 00000000..51257199 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/lexmatch.input @@ -0,0 +1,6 @@ +fn f(s : String) -> Unit { + lexmatch s { + "a" => () + _ => () + } +} diff --git a/fmt/internal/testsuite/style_test/fixtures/mixed_infix.input b/fmt/internal/testsuite/style_test/fixtures/mixed_infix.input new file mode 100644 index 00000000..13cf0f09 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/mixed_infix.input @@ -0,0 +1,20 @@ + +fn collapsible_if() -> Unit { + match e { + // fits after `=>` + Pattern => if a { b } else { c } + // fits in next line + Pattern_______________________________________________________ => if a { b } else { c } + // the body of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => { + if a { + b________________________________ + } else { + c_____________________________ + } + } + // the head of collapsible expression does not fit in the next line + Pattern_______________________________________________________ => + if a_______________________________ { b__________________________ } else { c________________________ } + } +} \ No newline at end of file diff --git a/fmt/internal/testsuite/style_test/fixtures/struct_constructor.input b/fmt/internal/testsuite/style_test/fixtures/struct_constructor.input new file mode 100644 index 00000000..ecab9aa7 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/struct_constructor.input @@ -0,0 +1,6 @@ +struct S { + x : Int + y : Int + + fn new(x~ : Int, y~ : Int) -> S +} diff --git a/fmt/internal/testsuite/style_test/fixtures/top_function.input b/fmt/internal/testsuite/style_test/fixtures/top_function.input new file mode 100644 index 00000000..c1c9108a --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/top_function.input @@ -0,0 +1,23 @@ + +fn id(x : Int) -> Int { + ... +} + +pub fn id(x : Int) -> Int { + ... +} + +pub fn[T1, T2] f(x : T1) -> T2 { + ... +} + +pub fn[T1 : Trait1_______ + Trait2__________, T2 : Trait1________ + Trait2_________] line_wrap1( + v___________ : Int, + w___________ : TyCon[Ty1__________________, Ty2________________, Ty3________________], + x__________~ : Int, + y__________? : Int, + z____________~ : Int = some_function(arg1____________, arg2____________, arg3_______________) ) -> + LongTyCon[Ty1________________, Ty2______________, Ty3______________, Ty4______________] raise Error { + ... +} + diff --git a/fmt/internal/testsuite/style_test/fixtures/trailing_block.input b/fmt/internal/testsuite/style_test/fixtures/trailing_block.input new file mode 100644 index 00000000..22a40dd0 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/trailing_block.input @@ -0,0 +1,73 @@ +fn trailing_function() { + function_callback(a______________________, b________________________, fn(x________, y__________){ + let a = b + a + }) + + function_callback(a__________________, b__________________, fn(x__, y___){ + let a = b + a + }) + + function_callback(a_____________, b____________, c____________________, fn(x__, y___){ + let a = b + a + }) +} + +fn trailing_arrow() { + function_callback(a______________________, b________________________, (x________, y__________) => { + let a = b + a + }) + + function_callback(a__________________, b__________________, (x__, y___) => { + let a = b + a + }) + + function_callback(a_____________, b____________, c____________________, (x__, y___) => { + let a = b + a + }) +} + +fn trailing_array() { + function_callback(a______________________, b________________________, c______________, [ + elem______________________, + elem______________________, + elem______________________, + ]) + + function_callback(a__________________, b__________________, [ + elem______________________, + elem______________________, + elem______________________, + ]) + +} + +fn flatten_style() { + function_callback(a,b,c, fn(x, y){ a }) + function_callback(a,b,c, (x, y) => a) + function_callback(a, b, c, [elem,elem,elem]) +} + + + + + + + + + + + + + + + + + + + diff --git a/fmt/internal/testsuite/style_test/fixtures/trait_impl_decl.input b/fmt/internal/testsuite/style_test/fixtures/trait_impl_decl.input new file mode 100644 index 00000000..dd248ae2 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/trait_impl_decl.input @@ -0,0 +1,49 @@ + +trait Num : Show + ToJson { + add(Self,Self) -> Self + sub(lhs~ : Self, rhs~ : Self) -> Self + mul(Self,Self) -> Self = _ + div(Self,Self) -> Self raise DivError + async from_string(String) -> Self +} + +trait LineWrap1 : Trait1____ + Trait2____ + Trait3____ + Trait4____ + Trait5____ + Trait6____ { + f(Ty1______________, Ty2______________, Ty3______________, Ty4______________) -> Self + g(Ty1______________, Ty2______________, Ty3______________, Ty4______________) -> Self +} + + +impl Trait for Type with method(self, arg1, arg2) { + ... +} + +impl Trait for Type with labeled(self, label1~, label2~) { + ... +} + +impl Trait for Type with type_annotation(self : Self, label1~ : Int, label2~ : Bool) -> Unit raise Error { + ... +} + +impl Trait with f() { + ... +} + +pub impl Trait with f() { + ... +} + +pub impl[T1 : Trait1, T2 : Trait2] Trait for Type with f() { + ... +} + +impl Trait for Type +impl[T1, T2] Trait for Type[T1, T2] + +pub impl[T1 : Trait1 + Trait2, T2 : Trait1 + Trait2 + Trait3] Trait for Type[T1__________, T2__________, T3__________, T4__________, T5__________, T6__________] with line_wrap_test( + self : Self, label1____~ : Int, label2____~ : Bool, label3____~ : Bool, label4____~ : Bool +) -> Unit raise Error { + ... +} + + diff --git a/fmt/internal/testsuite/style_test/fixtures/typedecl_alias.input b/fmt/internal/testsuite/style_test/fixtures/typedecl_alias.input new file mode 100644 index 00000000..4a9f5666 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/typedecl_alias.input @@ -0,0 +1 @@ +typealias (Int, Bool) as IntBoolPair diff --git a/fmt/internal/testsuite/style_test/fixtures/typedecl_derive.input b/fmt/internal/testsuite/style_test/fixtures/typedecl_derive.input new file mode 100644 index 00000000..832d503b --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/typedecl_derive.input @@ -0,0 +1,9 @@ + +enum DeriveLineWrap1 { + +} derive(Show, Ty1, Ty2, Ty3(prop1="xxxxxx", prop2="xxxxxx")) + +enum DeriveLineWrap2 { + +} derive(Show________________, Ty1_____________, Ty2______________, Ty3(prop1="xxxxxx", prop2="xxxxxx")) + diff --git a/fmt/internal/testsuite/style_test/fixtures/typedecl_enum.input b/fmt/internal/testsuite/style_test/fixtures/typedecl_enum.input new file mode 100644 index 00000000..73d2ad6a --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/typedecl_enum.input @@ -0,0 +1,31 @@ + +enum List[T] { + Nil + Cons(T, List[T]) +} + +enum CStyleEnum { + Mode1 = 1 + Mode2 = 2 + Mode3 = 3 +} + +enum WithLabelEnum { + Ctor1(label~ : Int, Bool) + Ctor2(mut label~ : Int, Bool) +} + +enum EmptyEnum {} + +enum TypeParamsLineWrap[T1, T2, T3, T4, T5] {} + +enum TypeParamsLineWrap2[T1______________, T2____________, T3___________, T4___________, T5____________] {} + +enum ConstrLineWrap { + Constr2(Ty1, Ty2, Ty3, mut label~ : Ty4) + Constr1( + Ty1_________________, Ty2________________, + TyCon[Ty1_____________, Ty2____________, Ty3____________, Ty3_____________, Ty4___________], + mut label~ : Ty4_______________) +} + diff --git a/fmt/internal/testsuite/style_test/fixtures/typedecl_struct.input b/fmt/internal/testsuite/style_test/fixtures/typedecl_struct.input new file mode 100644 index 00000000..cbf52e75 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/typedecl_struct.input @@ -0,0 +1,16 @@ + +struct List[T] { + value : T + mut next : List[T]? +} + +struct TypeParamsLineWrap1[T1, T2, T2, T3, T4, T5] {} +struct TypeParamsLineWrap2[T1_________, T2_________, T2_________, T3_________, T4_________, T5________] {} + +struct FieldLineWrap { + field1 : TyCon[Ty1_______, Ty2________, Ty3___________, Ty4_________, Ty5__________] + field2 : Bool +} + + + diff --git a/fmt/internal/testsuite/style_test/fixtures/typedecl_suberror.input b/fmt/internal/testsuite/style_test/fixtures/typedecl_suberror.input new file mode 100644 index 00000000..098b2d6f --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/typedecl_suberror.input @@ -0,0 +1,20 @@ + +suberror SubErr0 +suberror SubErr1 Int +suberror SubErr2 (Bool, Int) +suberror SubErr3 { + Constr1 + Constr2(Int, mut label~ : Int) +} + +suberror PayloadLineWrap1 (Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8) +suberror PayloadLineWrap2 (Ty1______, Ty2______, Ty3______, Ty4______, Ty5______, Ty6______, Ty7______, Ty8______) +suberror PayloadLineWrap2 { + Ctor1(Ty1, Ty2, Ty3, Ty4, Ty5, Ty6, Ty7, Ty8) + Ctor2(Ty1______, Ty2______, Ty3______, Ty4______, Ty5______, Ty6______, Ty7______, mut label~ : Ty8______) +} + + + + + diff --git a/fmt/internal/testsuite/style_test/fixtures/typedecl_tuplestruct.input b/fmt/internal/testsuite/style_test/fixtures/typedecl_tuplestruct.input new file mode 100644 index 00000000..b4b15e10 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/typedecl_tuplestruct.input @@ -0,0 +1,7 @@ + +struct TupleStruct1(Int) +struct TupleStruct2(Int, Bool) +struct TupleStruct3[T1,T2,T3,T4,T5,T6](T1,T2,T3,T4,T5,T6) +struct TupleStructLineWrap1[T1______,T2______,T3______,T4______,T5______](T1______,T2______,T3______,T4______,T5______) +struct TupleStructLineWrap2[T1______,T2______,T3______,T4______,T5______,T6______](T1______,T2______,T3______,T4______,T5______,T6______, Ty1________, Ty2_______, Ty3_______, Ty4______, Ty5______) + diff --git a/fmt/internal/testsuite/style_test/fixtures/typedecl_type.input b/fmt/internal/testsuite/style_test/fixtures/typedecl_type.input new file mode 100644 index 00000000..ed010449 --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/typedecl_type.input @@ -0,0 +1,8 @@ + +type Abstract1 + +pub type Abstract2 + +#external +type Abstract3 + diff --git a/fmt/internal/testsuite/style_test/fixtures/using_decl.input b/fmt/internal/testsuite/style_test/fixtures/using_decl.input new file mode 100644 index 00000000..659ed50f --- /dev/null +++ b/fmt/internal/testsuite/style_test/fixtures/using_decl.input @@ -0,0 +1,2 @@ +using @pkg { foo, bar } +pub using @pkg { type Foo, trait Bar } diff --git a/fmt/internal/testsuite/style_test/moon.pkg b/fmt/internal/testsuite/style_test/moon.pkg new file mode 100644 index 00000000..a53af1ba --- /dev/null +++ b/fmt/internal/testsuite/style_test/moon.pkg @@ -0,0 +1,10 @@ +import { +} + +import { + "moonbitlang/parser/fmt/internal/format", + "moonbitlang/x/fs", + "moonbitlang/parser/syntax", + "moonbitlang/parser/basic", + "moonbitlang/core/test", +} for "test" diff --git a/fmt/internal/testsuite/style_test/pkg.generated.mbti b/fmt/internal/testsuite/style_test/pkg.generated.mbti new file mode 100644 index 00000000..93fa22ec --- /dev/null +++ b/fmt/internal/testsuite/style_test/pkg.generated.mbti @@ -0,0 +1,13 @@ +// Generated using `moon info`, DON'T EDIT IT +package "moonbitlang/parser/fmt/internal/testsuite/style_test" + +// Values + +// Errors + +// Types and methods + +// Type aliases + +// Traits + diff --git a/fmt/internal/testsuite/style_test/style_test.mbt b/fmt/internal/testsuite/style_test/style_test.mbt new file mode 100644 index 00000000..78dfcc5a --- /dev/null +++ b/fmt/internal/testsuite/style_test/style_test.mbt @@ -0,0 +1,225 @@ +///| +fn read_fixture(path : String) -> String { + @fs.read_file_to_string(path) catch { + _ => @fs.read_file_to_string(path) catch { _ => "" } + } +} + +///| +fn @test.Test::run( + t : Self, + enable_ast_check? : Bool = true, + enable_idempotent_check? : Bool = true, +) -> Unit raise { + let name = t.name() + let source = read_fixture( + "./fmt/internal/testsuite/style_test/fixtures/\{name}.input", + ) + let round1 = @format.format(source) + if enable_ast_check { + ast_check(name~, source~, round1~) + } + let round2 = @format.format(round1) + if enable_idempotent_check { + idempotent_check(name~, round1~, round2~) + } + t.writeln("// generated file, do not edit!") + t.write(round1) + t.snapshot(filename="\{name}.output") +} + +///| +fn idempotent_check( + name~ : String, + round1~ : String, + round2~ : String, +) -> Unit raise { + if round1 != round2 { + fail( + ( + $| format(\{name}.mbt) is not idempotent. + $|================= round1 ================= + $|\{round1} + $|================= round2 ================= + $|\{round2} + ), + ) + } +} + +///| +fn ast_check(name~ : String, source~ : String, round1~ : String) -> Unit raise { + @basic.show_loc.val = String + let (source_ast, report1, _) = @format.parse(source) + let (round1_ast, report2, _) = @format.parse(round1) + if report1.length() != 0 { + fail( + ( + $| format(\{name}.mbt) produced diagnostics on the original source. + $|================= diagnostics ================= + $|\{report1.to_json().stringify(indent=2)})} + ), + ) + } + if report2.length() != 0 { + fail( + ( + $| format(\{name}.mbt) produced diagnostics on the formatted source. + $|================= diagnostics ================= + $|\{report2.to_json().stringify(indent=2)} + ), + ) + } + @basic.show_loc.val = Hidden + if source_ast.to_json() != round1_ast.to_json() { + fail( + ( + $| format(\{name}.mbt) changed the AST. + $|================= source AST ================= + $|\{source_ast.to_json().stringify(indent=2)} + $|================= round1 AST ================= + $|\{round1_ast.to_json().stringify(indent=2)} + ), + ) + } +} + +// ///| +// test "compose_case_rhs" (t : @test.T) { +// t.run() +// } + +// ///| +// test "compose_let_rhs" (t : @test.T) { +// t.run() +// } + +// ///| +// test "chained_dot" (t : @test.T) { +// t.run() +// } + +// ///| +// test "mixed_infix" (t : @test.T) { +// t.run() +// } + +// ///| +// test "compose_arrow_rhs" (t : @test.T) { +// t.run() +// } + +// ///| +// test "trailing_block" (t : @test.T) { +// t.run() +// } + +///| +test "cases" (t : @test.Test) { + // TODO: avoid printing trailing marks in the json + t.run(enable_ast_check=false) +} + +///| +test "typedecl_enum" (t : @test.Test) { + t.run() +} + +///| +test "typedecl_struct" (t : @test.Test) { + t.run() +} + +///| +test "typedecl_derive" (t : @test.Test) { + t.run() +} + +///| +test "typedecl_suberror" (t : @test.Test) { + // suberror syntax changed in parser 0.2.5 + t.run(enable_ast_check=false) +} + +///| +test "typedecl_type" (t : @test.Test) { + t.run() +} + +///| +test "typedecl_tuplestruct" (t : @test.Test) { + t.run() +} + +///| +test "typedecl_alias" (t : @test.Test) { + // typealias ... as ... syntax changed in parser 0.2.5 + t.run(enable_ast_check=false, enable_idempotent_check=false) +} + +///| +test "trait_impl_decl" (t : @test.Test) { + t.run() +} + +///| +test "top_function" (t : @test.Test) { + // TODO: adapt new parameter syntax in parser + t.run(enable_ast_check=false) +} + +///| +test "attribute" (t : @test.Test) { + // TODO: avoid printing attributes raw in the json + t.run(enable_ast_check=false) +} + +///| +test "ffi" (t : @test.Test) { + t.run() +} + +///| +test "arrow_function" (t : @test.Test) { + t.run() +} + +///| +test "docstring" (t : @test.Test) { + t.run(enable_ast_check=false) +} + +///| +test "constant_literals" (t : @test.Test) { + t.run() +} + +///| +test "using_decl" (t : @test.Test) { + t.run(enable_ast_check=false) +} + +///| +test "for_where" (t : @test.Test) { + t.run(enable_ast_check=false) +} + +///| +test "struct_constructor" (t : @test.Test) { + t.run(enable_ast_check=false) +} + +///| +test "declare_keyword" (t : @test.Test) { + t.run(enable_ast_check=false) +} + +///| +test "lexmatch" (t : @test.Test) { + t.run(enable_ast_check=false) +} + +///| +test "async_toplevel" (t : @test.Test) { + t.run(enable_ast_check=false) +} diff --git a/fmt/moon.pkg b/fmt/moon.pkg new file mode 100644 index 00000000..1ebe3ff3 --- /dev/null +++ b/fmt/moon.pkg @@ -0,0 +1,3 @@ +import { + "moonbitlang/parser/fmt/internal/format", +} diff --git a/fmt/pkg.generated.mbti b/fmt/pkg.generated.mbti new file mode 100644 index 00000000..c78561fd --- /dev/null +++ b/fmt/pkg.generated.mbti @@ -0,0 +1,21 @@ +// Generated using `moon info`, DON'T EDIT IT +package "moonbitlang/parser/fmt" + +import { + "moonbitlang/core/list", + "moonbitlang/parser/syntax", +} + +// Values +pub fn format(String, block_line? : Bool) -> String + +pub fn impls_to_string(@list.List[@syntax.Impl]) -> String + +// Errors + +// Types and methods + +// Type aliases + +// Traits + diff --git a/fmt/top.mbt b/fmt/top.mbt new file mode 100644 index 00000000..53a45abc --- /dev/null +++ b/fmt/top.mbt @@ -0,0 +1,3 @@ +///| +// Replace deprecated fnalias syntax +pub using @format {impls_to_string, format} diff --git a/moon.mod b/moon.mod index 75a0db3a..79158758 100644 --- a/moon.mod +++ b/moon.mod @@ -7,6 +7,7 @@ import { "moonbitlang/yacc@0.7.13", "moonbit-community/miniio@0.1.0", "moonbitlang/async@0.19.0", + "moonbit-community/prettyprinter@0.4.10", } readme = "README.md"