├── .gitignore ├── Tests ├── files │ └── comment.wl ├── TestSuite.mt ├── StandardizeNewlinesChanges.mt ├── Newlines.mt ├── LineBreakerV2.mt ├── OpenBugs.mt ├── CodeFormatter.mt ├── LineBreaking.mt ├── CodeFormatterTestUtils │ └── CodeFormatterTestUtils.wl └── Bugs.mt ├── .WolframResources ├── CodeFormatter ├── FrontEnd │ ├── SystemResources │ │ └── Bitmaps │ │ │ └── Misc │ │ │ ├── TransparentBG.9.png │ │ │ ├── popupRightBottom.9.png │ │ │ └── popupRightBottom@144dpi.9.png │ └── StyleSheets │ │ ├── Script.nb │ │ └── Package.nb ├── PacletInfo.wl.in ├── Kernel │ ├── AnchoredComments.wl │ ├── Absorb.wl │ ├── Standardize.wl │ ├── Utils.wl │ ├── RemoveLineContinuations.wl │ ├── Fragmentize.wl │ └── Notebooks.wl ├── Generate │ ├── AcceptableOperators.wl │ ├── MakeCodeFormatterAttachedCell.wl │ └── Palette.wl └── Documentation │ └── English │ └── Guides │ └── CodeFormatter.nb ├── .project ├── docs ├── options.md ├── extents.md ├── indent.md ├── compatibility.md ├── questions.md ├── airiness.md ├── linebreaker-v2.md ├── linebreaker-v1.md ├── philosophy.md └── canonicalization.md ├── cmake ├── ReplacePacletInfo.cmake ├── InstallPaclet.cmake ├── InspectFile.cmake ├── PacletInfo.cmake ├── WolframScript.cmake └── WolframKernel.cmake ├── LICENSE ├── HowToBuild.md ├── CONTRIBUTING.md ├── README.md ├── CodeTools └── Generate │ ├── CreatePacletArchive.wl │ └── GenerateSources.wl ├── scripts └── re_build_CodeFormatter.xml ├── CHANGELOG.md ├── CodeParser └── Data │ └── PrefixParselets.wl └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | build* 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Tests/files/comment.wl: -------------------------------------------------------------------------------- 1 | (*comment with line \ 2 | continuation*) 3 | 4 | 1+1 -------------------------------------------------------------------------------- /.WolframResources: -------------------------------------------------------------------------------- 1 | Resources[ 2 | Version[1], 3 | ExecutionBuildCommand["<"StandardizeNewlinesChanges-20210313-S1T3P6" 11 | ] 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeFormatter 4 | 5 | 6 | 7 | 8 | 9 | com.wolfram.eclipse.MEET.MathematicaProjectBuilder 10 | 11 | 12 | 13 | 14 | 15 | com.wolfram.eclipse.MEET.SimpleMathematicaNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/options.md: -------------------------------------------------------------------------------- 1 | 2 | # Options 3 | 4 | "NewlinesBetweenCommas" 5 | "NewlinesBetweenCompoundExpressions" 6 | "NewlinesBetweenOperators" 7 | "NewlinesInComments" 8 | "NewlinesInControl" 9 | "NewlinesInGroups" 10 | "NewlinesInScoping" 11 | 12 | 13 | all default to Automatic 14 | 15 | can be given values of: 16 | 17 | Automatic 18 | Insert 19 | Delete 20 | 21 | 22 | Automatic resolves to: 23 | 24 | usually preserve newlines, except with things like control structures, where newlines are completely redone 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/extents.md: -------------------------------------------------------------------------------- 1 | # Extents 2 | 3 | 4 | An extent is: 5 | 6 | {width, height, leadingWidth, trailingWidth} 7 | 8 | width = width of bounding box 9 | height = height of bounding box 10 | leadingWidth = width of first line 11 | trailingWidth = width of last line 12 | 13 | Some examples: 14 | 15 | 123 16 | 17 | extent is {3, 1, 3, 3} 18 | 19 | 20 | If[a, 21 | b 22 | , 23 | c 24 | ] 25 | 26 | extent is {5, 5, 5, 1} 27 | 28 | 29 | 30 | Extents form a monoid 31 | 32 | There is an identity: {0, 1, 0, 0} 33 | 34 | And there is an associative binary operation: combine 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/indent.md: -------------------------------------------------------------------------------- 1 | # indent 2 | 3 | ## "rendering" phase at the end of IndentCST 4 | 5 | There are some symbolic forms that are generated by IndentCST 6 | 7 | 8 | ``` 9 | IndentationNode[Block, ...] 10 | ``` 11 | 12 | Insert lines[]s before and after children, with proper indentation 13 | 14 | Children are in their own "block" 15 | 16 | For example, if you wanted to get: 17 | ``` 18 | { 19 | a; 20 | b; 21 | c 22 | } 23 | ``` 24 | then the symbolic form would be: 25 | ``` 26 | { 27 | LeafNode["{"], 28 | IndentationNode[Block, { 29 | a;b;c 30 | }], 31 | LeafNode["}"] 32 | } 33 | ``` 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | IndentationNode[Increment, ...] 41 | ``` 42 | 43 | Increment indentation of children 44 | 45 | 46 | 47 | ``` 48 | line[] 49 | ``` 50 | 51 | A newline followed by indentation 52 | 53 | 54 | -------------------------------------------------------------------------------- /docs/compatibility.md: -------------------------------------------------------------------------------- 1 | 2 | # Compatibility 3 | 4 | 5 | ## Source Compatibility 6 | 7 | CodeFormatter has source compatibility with 11.0+ 8 | 9 | 10 | ## FrontEnd Compatibility 11 | 12 | Any source .wl files that have `(* ::Package::"Tags" *)` or `(* ::Code::Initialization::"Tags" *)` syntax may only be edited with a version 12.3+ FE 13 | 14 | 15 | ## WolframVersion 16 | 17 | WolframVersion in PacletInfo is 12.1+ to maintain the same minimum required version as CodeParser 18 | 19 | 20 | ## Earlier Versions 21 | 22 | Manually modify WolframVersion in PacletInfo.wl to allow the paclet to be used. 23 | 24 | The message that you get when you install the paclet: 25 | ``` 26 | The paclet CodeParser was successfully installed. 27 | ``` 28 | does not necessarily mean that the paclet can be used. 29 | 30 | Make sure that the correct WolframVersion is specified. 31 | -------------------------------------------------------------------------------- /docs/questions.md: -------------------------------------------------------------------------------- 1 | # Questions 2 | 3 | ## How to force line breaking in strings? 4 | 5 | By default, tokens such as strings and comments are not broken, even when extending past the line width limit. 6 | 7 | You can control this behavior by setting: 8 | ``` 9 | CodeFormatter`BreakLines`$AllowSplittingTokens = True; 10 | ``` 11 | 12 | Now tokens will be split if the line width limit is hit in the middle of a token. 13 | 14 | 15 | 16 | 17 | 18 | 19 | ## How to change line width of formatter in Sublime? The default of 78 is way too small. 20 | 21 | You can run arbitrary code in the command, so edit the command in your settings to something like: 22 | 23 | ``` 24 | { 25 | "lsp_server_command": 26 | [ 27 | "`kernel`", 28 | "-noinit", 29 | "-noprompt", 30 | "-nopaclet", 31 | "-nostartuppaclets", 32 | "-noicon", 33 | "-run", 34 | "Needs[\"LSPServer`\"];CodeFormatter`Private`$DefaultLineWidth=120;LSPServer`StartServer[]" 35 | ] 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /cmake/ReplacePacletInfo.cmake: -------------------------------------------------------------------------------- 1 | 2 | file(READ ${PACLETINFO_IN_SOURCE} filedata) 3 | 4 | string(TIMESTAMP DATESTRING "%a %d %b %Y %H:%M:%S") 5 | 6 | string(REGEX REPLACE "BuildDate -> \"[a-zA-Z0-9 :]*\"" "BuildDate -> \"${DATESTRING}\"" filedata ${filedata}) 7 | 8 | string(REGEX REPLACE "BuildNumber -> [0-9]+" "BuildNumber -> ${BUILDNUMBER}" filedata ${filedata}) 9 | 10 | string(REGEX REPLACE "BuildWolframVersionNumber -> [0-9]+" "BuildWolframVersionNumber -> ${VERSION_NUMBER}" filedata ${filedata}) 11 | 12 | string(REGEX REPLACE "BuildWolframLibraryVersion -> [0-9]+" "BuildWolframLibraryVersion -> ${WOLFRAMLIBRARY_VERSION}" filedata ${filedata}) 13 | 14 | string(REGEX REPLACE "Transport -> \"[a-zA-Z]*\"" "Transport -> \"${TRANSPORT}\"" filedata ${filedata}) 15 | 16 | if(LOCAL_BUILD) 17 | 18 | string(REGEX REPLACE "Version -> \"[0-9\\.]+\"," "Version -> \"${LOCAL_BUILD_VERSION}\"(* local build *)," filedata ${filedata}) 19 | 20 | endif() 21 | 22 | file(WRITE ${REPLACED_PACLETINFO} "${filedata}") 23 | -------------------------------------------------------------------------------- /CodeFormatter/PacletInfo.wl.in: -------------------------------------------------------------------------------- 1 | 2 | Paclet[ 3 | Name -> "CodeFormatter", 4 | Version -> "1.9", 5 | WolframVersion -> "12.1+", 6 | Description -> "Format Wolfram Language code.", 7 | Creator -> "Brenton Bostick ", 8 | BuildDate -> "", 9 | BuildNumber -> 0, 10 | BuildWolframVersionNumber -> 0, 11 | Updating -> Automatic, 12 | Extensions -> { 13 | {"Kernel", Root -> "Kernel", Context -> "CodeFormatter`"}, 14 | (* 15 | It's my understanding that adding Prepend -> True to the FE extension causes the PacletManager to insert those paths before the ParentList in the default setting. 16 | That's exactly the right thing to do if your paclet might be both in the layout and in other paclet locations. 17 | 18 | Related threads: https://mail-archive.wolfram.com/archive/l-frontend/2020/Sep00/0133.html 19 | *) 20 | {"FrontEnd", Root -> "FrontEnd", Prepend -> True, WolframVersion -> "12.2+"}, 21 | {"Documentation", Language -> All, MainPage -> "Guides/CodeFormatter"} 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /docs/airiness.md: -------------------------------------------------------------------------------- 1 | 2 | # Airiness 3 | 4 | 5 | airiness == -1 6 | 7 | newlines in comments are deleted 8 | 9 | 10 | 11 | airiness <= -0.85 12 | 13 | newlines between commas are deleted 14 | newlines in scoping constructs are deleted 15 | 16 | 17 | 18 | airiness <= -0.75 19 | 20 | newlines in groups are deleted 21 | 22 | 23 | 24 | airiness <= -0.5 25 | 26 | newlines between operators are deleted 27 | 28 | 29 | 30 | airiness <= -0.25 31 | 32 | newlines between semicolons are deleted 33 | 34 | 35 | 36 | airiness == 0 37 | 38 | normal 39 | 40 | 41 | 42 | 0.25 <= airiness 43 | 44 | newlines between semicolons are inserted 45 | 46 | 47 | 48 | 0.5 <= airiness 49 | 50 | newlines between operators are inserted 51 | 52 | 53 | 54 | 0.75 <= airiness 55 | 56 | newlines in groups are inserted 57 | 58 | 59 | 60 | 0.85 <= airiness 61 | 62 | newlines between commas are inserted 63 | newlines in scoping constructs are inserted 64 | 65 | 66 | 67 | 1.0 <= airiness 68 | 69 | newlines in comments are inserted 70 | 71 | 72 | -------------------------------------------------------------------------------- /Tests/Newlines.mt: -------------------------------------------------------------------------------- 1 | 2 | Needs["CodeFormatter`"] 3 | 4 | Test[ 5 | CodeFormat[ 6 | "f[ 7 | 1 8 | , 9 | 2 10 | ] 11 | ", "NewlinesBetweenCommas" -> Insert] 12 | , 13 | "f[ 14 | 1 15 | , 16 | 2 17 | ]" 18 | , 19 | TestID->"Newlines-20201211-N3B5I5" 20 | ] 21 | 22 | 23 | Test[ 24 | CodeFormat["( f /@ {} )", "NewlinesBetweenOperators" -> Insert] 25 | , 26 | "( 27 | f 28 | /@ 29 | {} 30 | )" 31 | , 32 | TestID->"Newlines-20201211-C2J3H1" 33 | ] 34 | 35 | 36 | 37 | 38 | Test[ 39 | CodeFormat["f[If[a, b[]]; g[]]"] 40 | , 41 | "f[ 42 | If[a, 43 | b[] 44 | ]; 45 | g[] 46 | ]" 47 | , 48 | TestID->"Newlines-20210113-O4V3Q0" 49 | ] 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | (* 63 | stay single line at top-level 64 | *) 65 | 66 | Test[ 67 | CodeFormat["offset = 0;If[endptorder, a, b]"] 68 | , 69 | "offset = 0; If[endptorder, 70 | a 71 | , 72 | b 73 | ]" 74 | , 75 | TestID->"Newlines-20210114-Q2V0T8" 76 | ] 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Wolfram Research Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /cmake/InstallPaclet.cmake: -------------------------------------------------------------------------------- 1 | 2 | if(NOT EXISTS ${WOLFRAMKERNEL}) 3 | message(FATAL_ERROR "WOLFRAMKERNEL does not exist. WOLFRAMKERNEL: ${WOLFRAMKERNEL}") 4 | endif() 5 | 6 | set(CODE "\ 7 | Print[OutputForm[\"Calling PacletInstall...\"]]\; 8 | Check[ 9 | res = PacletInstall[\"${PACLET_OUTPUT}\", ForceVersionInstall -> True]\; 10 | , 11 | Print[OutputForm[Row[{\"$VersionNumber: \", NumberForm[$VersionNumber, {2, 1}]}]]]\; 12 | Print[OutputForm[Row[{\"Paclet WolframVersion: \", \"${PACLET_WOLFRAMVERSION}\"}]]]\; 13 | Print[OutputForm[Row[{\"To prevent this PacletInstall::compat message, update PacletInfo.wl.in with WolframVersion -> \\\"\", NumberForm[$VersionNumber, {2, 1}] ,\"\\\" and build and install again.\"}]]]; 14 | res 15 | , 16 | {PacletInstall::compat} 17 | ]\; 18 | Print[res //OutputForm]\; 19 | Print[OutputForm[\"Done PacletInstall\"]]\; 20 | If[!PacletObjectQ[res], 21 | Exit[1] 22 | ]\; 23 | Exit[0] 24 | ") 25 | 26 | execute_process( 27 | COMMAND 28 | ${WOLFRAMKERNEL} -noinit -noprompt -run ${CODE} 29 | TIMEOUT 30 | ${KERNEL_TIMEOUT} 31 | RESULT_VARIABLE 32 | INSTALL_RESULT 33 | ) 34 | 35 | if(NOT ${INSTALL_RESULT} EQUAL "0") 36 | message(FATAL_ERROR "Bad exit code from install: ${INSTALL_RESULT}") 37 | endif() 38 | -------------------------------------------------------------------------------- /cmake/InspectFile.cmake: -------------------------------------------------------------------------------- 1 | 2 | if(NOT CODEPARSER_EXE) 3 | return() 4 | endif() 5 | 6 | if(NOT EXISTS ${CODEPARSER_EXE}) 7 | return() 8 | endif() 9 | 10 | execute_process( 11 | COMMAND 12 | ${CODEPARSER_EXE} -check -file ${SRC} 13 | RESULT_VARIABLE 14 | CODEPARSER_RESULT 15 | ) 16 | 17 | if(${CODEPARSER_RESULT} EQUAL "0") 18 | return() 19 | endif() 20 | 21 | if(NOT ${CODEPARSER_RESULT} EQUAL "1") 22 | message(WARNING "Internal error. CODEPARSER_RESULT: ${CODEPARSER_RESULT}") 23 | return() 24 | endif() 25 | 26 | # 27 | # We know there was some problem, so now use CodeInspector to report the problem 28 | # 29 | 30 | if(NOT WOLFRAMKERNEL) 31 | return() 32 | endif() 33 | 34 | if(NOT EXISTS ${WOLFRAMKERNEL}) 35 | return() 36 | endif() 37 | 38 | set(CODE "\ 39 | If[FailureQ[FindFile[\"CodeInspector`\"]], Exit[0]]\;\ 40 | Needs[\"CodeInspector`\"]\;\ 41 | Print[\"Code inspection...\" //OutputForm]\;\ 42 | Print[CodeInspector`CodeInspectSummarize[File[\"${SRC}\"]] //OutputForm]\;\ 43 | Exit[1]\ 44 | ") 45 | 46 | execute_process( 47 | COMMAND 48 | ${WOLFRAMKERNEL} -noinit -noprompt -nopaclet -nostartuppaclets -run ${CODE} 49 | TIMEOUT 50 | ${KERNEL_TIMEOUT} 51 | ) 52 | 53 | message(FATAL_ERROR "File had fatal errors: ${SRC}") 54 | -------------------------------------------------------------------------------- /HowToBuild.md: -------------------------------------------------------------------------------- 1 | # Building 2 | 3 | CodeFormatter uses a Wolfram Language kernel to build a `.paclet` file. 4 | 5 | CodeFormatter uses CMake to generate build scripts. 6 | 7 | Here is an example transcript using the default make generator to build CodeFormatter: 8 | ``` 9 | cd codeformatter 10 | mkdir build 11 | cd build 12 | cmake .. 13 | cmake --build . 14 | ``` 15 | 16 | The result is a directory named `paclet` that contains the WL package source code and a built CodeFormatter `.paclet` file for installing. 17 | 18 | Inside a kernel session you may then install the paclet by evaluating: 19 | ``` 20 | PacletInstall["/path/to/build/paclet/CodeFormatter-1.9.paclet"] 21 | ``` 22 | 23 | Specify `MATHEMATICA_INSTALL_DIR` if you have Wolfram System installed in a non-default location: 24 | ``` 25 | cmake -DMATHEMATICA_INSTALL_DIR=/Applications/Mathematica.app/Contents/ .. 26 | cmake --build . 27 | ``` 28 | 29 | On Windows: 30 | ``` 31 | cmake -DMATHEMATICA_INSTALL_DIR="C:/Program Files/Wolfram Research/Mathematica/13.1" .. 32 | cmake --build . 33 | ``` 34 | 35 | ## Installing 36 | 37 | You can install the paclet from CMake: 38 | ``` 39 | cmake --install . 40 | ``` 41 | 42 | This starts a kernel and calls `PacletInstall` with the built .paclet file. 43 | -------------------------------------------------------------------------------- /Tests/LineBreakerV2.mt: -------------------------------------------------------------------------------- 1 | 2 | Needs["CodeFormatter`"] 3 | 4 | 5 | (* 6 | 7 | Was originally formatting as: 8 | 9 | f[args___] := 10 | $Failed /; (g[]) 11 | ; 12 | 13 | with the ; on a new line 14 | 15 | This test is really just that the ; looks good 16 | 17 | *) 18 | Test[ 19 | CodeFormat["f[args___] := $Failed /; (g[]);"] 20 | , 21 | "f[args___] := 22 | $Failed /; (g[]);" 23 | , 24 | TestID->"LineBreakerV2-20210929-Y8H1A7" 25 | ] 26 | 27 | 28 | 29 | 30 | 31 | (* 32 | was formatting as: 33 | 34 | NDSolve`MethodSymbol[method, ndstate["Caller"]] :: tma 35 | 36 | *) 37 | Test[ 38 | CodeFormat["NDSolve`MethodSymbol[method, ndstate[\"Caller\"]]::tma"] 39 | , 40 | "NDSolve`MethodSymbol[method, ndstate[\"Caller\"]]::tma" 41 | , 42 | TestID->"LineBreakerV2-20210929-A6Q1R6" 43 | ] 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Test[ 54 | CodeFormat["f[1;]"] 55 | , 56 | "f[1;]" 57 | , 58 | TestID->"LineBreakerV2-20211005-M2U2T7" 59 | ] 60 | 61 | 62 | 63 | Test[ 64 | CodeFormat["f[1;2;]"] 65 | , 66 | "f[ 67 | 1; 68 | 2; 69 | ]" 70 | , 71 | TestID->"LineBreakerV2-20211005-B2I2A0" 72 | ] 73 | 74 | 75 | 76 | 77 | 78 | CodeFormat["123 + 456"] 79 | 80 | Test[ 81 | CodeFormatter`Private`$LastExtent 82 | , 83 | {9, 2, 9, 0} 84 | , 85 | TestID->"LineBreakerV2-20211005-X6H3D4" 86 | ] 87 | 88 | 89 | -------------------------------------------------------------------------------- /docs/linebreaker-v2.md: -------------------------------------------------------------------------------- 1 | # Line Breaker v2 2 | 3 | 4 | a very simple line breaking algorithm 5 | 6 | tree structure 7 | 8 | not context sensitive 9 | 10 | bottom-up 11 | 12 | when the limit is hit, then the node reformats with "Multiline" -> True 13 | 14 | this may now shorten the lines in the node and the limit may not be hit any more, but oh well, we do not check again for that node 15 | 16 | going to "Multiline" -> True is "sticky", it is "poison", once it is turned on, it does not turn off 17 | 18 | this is version 2 of the line breaking algorithm 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ## current limitations of LineBreakerV2 29 | 30 | ``` 31 | "resourcePublisherNameSpaceFreeQ[name_String] := 32 | With[ 33 | { 34 | ns 35 | = 36 | DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[][\"Publishers\"]] 37 | } 38 | , 39 | !MatchQ[name, Alternatives @@ ns] 40 | ]" 41 | ``` 42 | 43 | line ends up being 91 characters long because: 44 | 45 | `DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[]["Publishers"]]` = 77 characters, so 1 line 46 | 47 | `ns = DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[]["Publishers"]]` = 82 characters, so multiline 48 | 49 | IndentationNode[Block, {the above code}] = 81 characters 50 | 51 | nothing ever forces first line to re-indent 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /cmake/PacletInfo.cmake: -------------------------------------------------------------------------------- 1 | 2 | macro(CheckPacletInfo) 3 | 4 | if(NOT EXISTS ${WOLFRAMKERNEL}) 5 | message(FATAL_ERROR "WOLFRAMKERNEL does not exist. WOLFRAMKERNEL: ${WOLFRAMKERNEL}") 6 | endif() 7 | 8 | if(LOCAL_BUILD) 9 | message(STATUS "Paclet Version ignored in local build") 10 | set(LOCAL_BUILD_VERSION 999.9) 11 | else() 12 | # 13 | # if not local build, then get Version from PacletInfo.wl 14 | # 15 | execute_process( 16 | COMMAND 17 | ${WOLFRAMKERNEL} -noinit -noprompt -nopaclet -nostartuppaclets -runfirst Pause[${KERNEL_PAUSE}]\;Print[OutputForm[Row[{Version,\ ";",\ WolframVersion}\ /.\ List\ @@\ Get["${PACLETINFO_IN_SOURCE}"]]]]\;Exit[] 18 | OUTPUT_VARIABLE 19 | PACLET_VERSIONS_LIST 20 | OUTPUT_STRIP_TRAILING_WHITESPACE 21 | WORKING_DIRECTORY 22 | ${PROJECT_SOURCE_DIR} 23 | TIMEOUT 24 | ${KERNEL_TIMEOUT} 25 | RESULT_VARIABLE 26 | PACLETINFO_RESULT 27 | ) 28 | 29 | if(NOT ${PACLETINFO_RESULT} EQUAL "0") 30 | message(FATAL_ERROR "Bad exit code from PacletInfo script: ${PACLETINFO_RESULT}") 31 | endif() 32 | 33 | list(GET PACLET_VERSIONS_LIST 0 PACLET_VERSION) 34 | list(GET PACLET_VERSIONS_LIST 1 PACLET_WOLFRAMVERSION) 35 | message(STATUS "PACLET_VERSION: ${PACLET_VERSION}") 36 | message(STATUS "PACLET_WOLFRAMVERSION: ${PACLET_WOLFRAMVERSION}") 37 | 38 | endif(LOCAL_BUILD) 39 | 40 | endmacro(CheckPacletInfo) 41 | -------------------------------------------------------------------------------- /docs/linebreaker-v1.md: -------------------------------------------------------------------------------- 1 | # Line Breaker v1 2 | 3 | 4 | Line Breaker Algorithm V1 5 | Follow a simple strategy for now: 6 | linebreak after the first acceptable operator 7 | if no breaking before reaching lineWidth2, then just insert a continuation marker 8 | acceptable operators are: 9 | openers 10 | unambiguous prefix 11 | unambiguous binary 12 | unambiguous infix 13 | unambiguous ternary 14 | 15 | 16 | 17 | ## limitations 18 | 19 | Most limitations are related to the greedy algorithm that is used. 20 | 21 | ### no back-tracking before safety margin 22 | 23 | LineBreakerV1 does not back-track looking for a good place to break. 24 | 25 | For example 26 | ``` 27 | f["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -> "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"] 28 | ``` 29 | 30 | The `"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"` extends past the 80 column limit, but it also starts before 70 column safety margin. 31 | 32 | So LineBreakerV1 is not aware of it. 33 | 34 | 35 | 36 | 37 | ### no preference between multiple choices to break 38 | 39 | This is a formatted example: 40 | 41 | ``` 42 | f["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 43 | -> "bbb"] 44 | ``` 45 | 46 | but it would be better if it formatted as: 47 | 48 | ``` 49 | f["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -> 50 | "bbb"] 51 | ``` 52 | 53 | But `->` occurs first, so it is broken before the `->` 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Wolfram® 2 | 3 | Thank you for taking the time to contribute to the [Wolfram Research](https://github.com/wolframresearch) repos on GitHub. 4 | 5 | ## Licensing of Contributions 6 | 7 | By contributing to Wolfram, you agree and affirm that: 8 | 9 | > Wolfram may release your contribution under the terms of the [MIT license](https://opensource.org/licenses/MIT); and 10 | 11 | > You have read and agreed to the [Developer Certificate of Origin](http://developercertificate.org/), version 1.1 or later. 12 | 13 | Please see [LICENSE](LICENSE) for licensing conditions pertaining 14 | to individual repositories. 15 | 16 | 17 | ## Bug reports 18 | 19 | ### Security Bugs 20 | 21 | Please **DO NOT** file a public issue regarding a security issue. 22 | Rather, send your report privately to security@wolfram.com. Security 23 | reports are appreciated and we will credit you for it. We do not offer 24 | a security bounty, but the forecast in your neighborhood will be cloudy 25 | with a chance of Wolfram schwag! 26 | 27 | ### General Bugs 28 | 29 | Please use the repository issues page to submit general bug issues. 30 | 31 | Please do not duplicate issues. 32 | 33 | Please do send a complete and well-written report to us. Note: **the 34 | thoroughness of your report will positively correlate to our willingness 35 | and ability to address it**. 36 | 37 | When reporting issues, always include: 38 | 39 | * Your version of *Mathematica*® or the Wolfram Language. 40 | * Your operating system. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeFormatter 2 | 3 | CodeFormatter is a package that provides functionality for formatting Wolfram Language code. 4 | 5 | ``` 6 | Needs["CodeFormatter`"] 7 | 8 | CodeFormat["If[a,f/@b,g/@c]"] 9 | ``` 10 | ``` 11 | Out[2]= If[a, 12 | f /@ b 13 | , 14 | g /@ c 15 | ] 16 | 17 | ``` 18 | 19 | [Formatting the Wolfram Language from WTC 2020: Watch Video (youtube)](https://www.youtube.com/watch?v=eGvvKlfaPsQ) 20 | 21 | 22 | ## Setup 23 | 24 | CodeFormatter depends on [CodeParser paclet](https://github.com/WolframResearch/codeparser). 25 | 26 | CodeFormatter and its dependencies are included in Mathematica 12.2 and above. 27 | 28 | For older versions, install CodeFormatter paclet and dependencies from the public paclet server: 29 | ``` 30 | PacletInstall["CodeParser"] 31 | PacletInstall["CodeFormatter"] 32 | ``` 33 | 34 | [Build and install the CodeFormatter paclet locally](HowToBuild.md) 35 | 36 | 37 | ## Using CodeFormatter 38 | 39 | After CodeFormatter is installed, it can be used. 40 | 41 | ``` 42 | Needs["CodeFormatter`"] 43 | 44 | CodeFormat["If[a,f/@b,g/@c]"] 45 | ``` 46 | ``` 47 | Out[2]= If[a, 48 | f /@ b 49 | , 50 | g /@ c 51 | ] 52 | 53 | ``` 54 | 55 | The input to `CodeFormat` may be a string, a `File`, or a list of bytes. 56 | 57 | 58 | ## Troubleshooting 59 | 60 | Make sure that the paclets can be found on your system: 61 | ``` 62 | Needs["CodeFormatter`"] 63 | ``` 64 | 65 | and try a basic example: 66 | ``` 67 | CodeFormat["If[a, b, c]"] 68 | ``` 69 | -------------------------------------------------------------------------------- /CodeTools/Generate/CreatePacletArchive.wl: -------------------------------------------------------------------------------- 1 | 2 | If[!MemberQ[$Path, #], PrependTo[$Path, #]]&[DirectoryName[$InputFileName, 3]] 3 | 4 | BeginPackage["CodeTools`Generate`CreatePacletArchive`"] 5 | 6 | Begin["`Private`"] 7 | 8 | (* 9 | Do not allow PacletManager to participate in finding `Generate` files 10 | 11 | PacletManager will find e.g. CodeParser/Kernel/TokenEnum.wl when asked to find CodeParser`Generate`TokenEnum` 12 | 13 | related issues: PACMAN-54 14 | *) 15 | Block[{Internal`PacletFindFile = Null&}, 16 | Needs["CodeTools`Generate`GenerateSources`"]; 17 | ] 18 | If[$VersionNumber < 12.1, 19 | Needs["PacletManager`"] 20 | ] 21 | 22 | checkBuildDir[] 23 | checkPaclet[] 24 | checkPacletLayoutDir[] 25 | 26 | 27 | If[retry, 28 | (* 29 | CreatePacletArchive may be slow on RE machines, so allow re-trying if JLink connection timeout is hit 30 | 31 | Set $connectTimeout to some large value and cross fingers (default is 20) 32 | 33 | See: RE-515885 34 | *) 35 | Needs["JLink`"]; 36 | JLink`InstallJava`Private`$connectTimeout = 300.0 37 | ] 38 | 39 | 40 | generate[] := ( 41 | 42 | Print["Calling CreatePacletArchive..."]; 43 | 44 | If[$VersionNumber >= 12.1, 45 | res = System`CreatePacletArchive[FileNameJoin[{pacletLayoutDir, paclet}], FileNameJoin[{buildDir, "paclet"}]] 46 | , 47 | res = PacletManager`PackPaclet[FileNameJoin[{pacletLayoutDir, paclet}], FileNameJoin[{buildDir, "paclet"}]] 48 | ]; 49 | 50 | Print[res]; 51 | 52 | If[!StringQ[res], 53 | Quit[1] 54 | ]; 55 | 56 | Print["Done CreatePacletArchive"] 57 | ) 58 | 59 | If[!StringQ[script], 60 | Quit[1] 61 | ] 62 | If[AbsoluteFileName[script] === AbsoluteFileName[$InputFileName], 63 | generate[] 64 | ] 65 | 66 | End[] 67 | 68 | EndPackage[] 69 | -------------------------------------------------------------------------------- /docs/philosophy.md: -------------------------------------------------------------------------------- 1 | 2 | ## Overview of formatter 3 | 4 | Using the tree structure from the parser 5 | 6 | As context-free as possible 7 | 8 | Using a pattern-based approach 9 | 10 | Questions like "is this expression too long?" are answered with pattern matching as much as possible 11 | 12 | 13 | All whitespace and newlines are discarded[\*]. 14 | 15 | \* except when there is a canonicalization issue 16 | 17 | 18 | 19 | 20 | 21 | 22 | ## Will only change whitespace and newlines 23 | 24 | Nothing to do with adding comments, adding parens, changing to FullForm, etc. 25 | 26 | Inserting (*Else*) into If statements is not formatting, it is something else. 27 | 28 | Converting And[a, b] into a && b (or vice versa) now involves output and precedence and parentheses. 29 | 30 | Using CodeFormatter to insert \[LeftDoubleBracket] \[RightDoubleBracket] characters or \[Rule] character is not formatting, it is something else. 31 | 32 | Breaking up CompoundExpression[] at top-level is not formatting, it is something else, changes semantics. 33 | 34 | 35 | Formatting is a function concrete -> concrete 36 | PrettyPrinting is a function expr -> concrete 37 | 38 | 39 | clang-format also largely only modified whitespace and newlines 40 | 41 | one exception: insert namespace comments? 42 | 43 | 44 | 45 | 46 | ## Silently fixes FormatIssues 47 | 48 | Issues like this: 49 | 50 | ``` 51 | a/.3->.4 52 | ``` 53 | 54 | are automatically fixed. 55 | 56 | It is slightly confusing to report issues like this if the formatter can just fix them automatically. 57 | 58 | These sorts of issues "belong" to the formatter, not the linter 59 | 60 | 61 | 62 | 63 | ## Why have a formatter? 64 | 65 | https://github.com/dart-lang/dart_style/wiki/FAQ#why-have-a-formatter 66 | 67 | 68 | -------------------------------------------------------------------------------- /cmake/WolframScript.cmake: -------------------------------------------------------------------------------- 1 | 2 | if(NOT EXISTS ${WOLFRAMKERNEL}) 3 | message(FATAL_ERROR "WOLFRAMKERNEL does not exist. WOLFRAMKERNEL: ${WOLFRAMKERNEL}") 4 | endif() 5 | 6 | if(NOT DEFINED RETRY_ON_FAILURE) 7 | set(RETRY_ON_FAILURE OFF) 8 | endif() 9 | 10 | if(NOT EXISTS ${SCRIPT}) 11 | message(FATAL_ERROR "SCRIPT does not exist. SCRIPT: ${SCRIPT}") 12 | endif() 13 | 14 | file(READ ${SCRIPT} script) 15 | 16 | if(script STREQUAL "") 17 | message(FATAL_ERROR "SCRIPT is empty. SCRIPT: ${SCRIPT}") 18 | endif() 19 | 20 | if(RETRY_ON_FAILURE) 21 | 22 | # 23 | # try twice 24 | # 25 | 26 | execute_process( 27 | COMMAND 28 | ${WOLFRAMKERNEL} -script ${SCRIPT} -srcDir ${SRCDIR} -buildDir ${BUILDDIR} -pacletLayoutDir ${PACLET_LAYOUT_DIR} -paclet ${PACLET} 29 | TIMEOUT 30 | ${KERNEL_TIMEOUT} 31 | RESULT_VARIABLE 32 | SCRIPT_RESULT 33 | ) 34 | 35 | if(NOT ${SCRIPT_RESULT} EQUAL "0") 36 | message(WARNING "First try: Bad exit code from script: ${SCRIPT_RESULT}; retrying...") 37 | 38 | execute_process( 39 | COMMAND 40 | ${WOLFRAMKERNEL} -retry -script ${SCRIPT} -srcDir ${SRCDIR} -buildDir ${BUILDDIR} -pacletLayoutDir ${PACLET_LAYOUT_DIR} -paclet ${PACLET} 41 | TIMEOUT 42 | ${KERNEL_TIMEOUT} 43 | RESULT_VARIABLE 44 | SCRIPT_RESULT 45 | ) 46 | 47 | if(NOT ${SCRIPT_RESULT} EQUAL "0") 48 | message(FATAL_ERROR "Second try: Bad exit code from script: ${SCRIPT_RESULT}; stopping") 49 | else() 50 | message(STATUS "Second try: Success!") 51 | endif() 52 | 53 | endif() 54 | 55 | else(RETRY_ON_FAILURE) 56 | 57 | # 58 | # only try once 59 | # 60 | 61 | execute_process( 62 | COMMAND 63 | ${WOLFRAMKERNEL} -script ${SCRIPT} -srcDir ${SRCDIR} -buildDir ${BUILDDIR} -pacletLayoutDir ${PACLET_LAYOUT_DIR} -paclet ${PACLET} 64 | TIMEOUT 65 | ${KERNEL_TIMEOUT} 66 | RESULT_VARIABLE 67 | SCRIPT_RESULT 68 | ) 69 | 70 | if(NOT ${SCRIPT_RESULT} EQUAL "0") 71 | message(FATAL_ERROR "Bad exit code from script: ${SCRIPT_RESULT}") 72 | endif() 73 | 74 | endif() 75 | -------------------------------------------------------------------------------- /docs/canonicalization.md: -------------------------------------------------------------------------------- 1 | # canonicalization 2 | 3 | strips all whitespace and newlines before formatting: canonicalizes 4 | 5 | introduced in CodeFormatter 1.4 6 | 7 | fixes hysteresis problem with Airiness 8 | 9 | 10 | 11 | ## hysteresis problem 12 | 13 | in CodeFormatter 1.3: 14 | 15 | If[a, 16 | b; 17 | c 18 | ] 19 | 20 | format with Airiness 0.0 first 21 | 22 | If[a, 23 | b; 24 | c 25 | ] 26 | 27 | format with Airiness -1.0 28 | 29 | If[a, b; c] 30 | 31 | then format again with Airiness 0.0 32 | 33 | If[a, 34 | b; c 35 | ] 36 | 37 | 38 | expected to be original formatting, but it is not 39 | 40 | 41 | 42 | 43 | the default "Preserve newline" behavior means that there is a hysteresis 44 | 45 | there is not a "canonicalization" 46 | 47 | this is because it is "hard" to format WL 48 | when to keep unknown constructs on single line vs. multiple lines? 49 | 50 | i originally just said "preserve the given newlines unless otherwise specified" 51 | 52 | But this "no canonicalization" is bad for some reasons 53 | 54 | 1. changing Airiness has a hysteresis 55 | 56 | the exact path taken through the Airiness slider matters, this is confusing 57 | 58 | Airiness is not "state-based" 59 | 60 | 61 | 2. generated code 62 | 63 | generated code may not have any newlines, so it will look bad if formatted 64 | 65 | it will look "different" than hand-written same code 66 | 67 | 68 | 69 | 70 | After thinking about the problem for a bit, I have a plan for fixing everything. 71 | 72 | need to "canonicalize" first, remove automatic behavior of "Preserve" 73 | 74 | 75 | remove "Preserve" behavior 76 | for groups: maybe just always do "Delete" as the default behavior? 77 | for CompoundExpressions: maybe just always do "Insert" ? 78 | what are all of the "Preserve" behaviors? 79 | 80 | 81 | 82 | 83 | 84 | ## some exceptions 85 | 86 | Not all newlines and whitespace are stripped 87 | 88 | multi-line comments need to remember original indentation 89 | 90 | It is desired that this comment stay at end of line: 91 | 92 | f[ 93 | 1; 94 | 2; (*2a*) 95 | 3; 96 | 4; 97 | ] 98 | 99 | 100 | This information must be kept track of 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /CodeFormatter/Kernel/AnchoredComments.wl: -------------------------------------------------------------------------------- 1 | BeginPackage["CodeFormatter`AnchoredComments`"] 2 | 3 | InsertNewlineAnchorInformationIntoComments 4 | 5 | Begin["`Private`"] 6 | 7 | Needs["CodeFormatter`"] 8 | Needs["CodeFormatter`Utils`"] 9 | Needs["CodeParser`"] 10 | Needs["CodeParser`Utils`"] 11 | 12 | (* 13 | 14 | inserts this metadata into comments: 15 | 16 | StartOfLine -> True|False 17 | EndOfLine -> True|False 18 | "Multiline" -> True|False 19 | 20 | 21 | "Multiline" is used by Indent 22 | 23 | It is not meant to mean that the comment takes up multiple lines 24 | 25 | 26 | *) 27 | 28 | InsertNewlineAnchorInformationIntoComments[cstIn_] := 29 | Catch[ 30 | Module[{cst, isWhitespacePos, isNewlinePos, commentPoss, commentMap, newPos, len, startOfLine, endOfLine, comment, commentData}, 31 | 32 | If[$Debug, 33 | Print["enter InsertNewlineAnchorInformationIntoComments"]; 34 | ]; 35 | 36 | cst = cstIn; 37 | 38 | isWhitespacePos[pos1_] := 39 | ListQ[pos1] && MatchQ[Extract[cst, pos1], LeafNode[Whitespace | Token`Boxes`MultiWhitespace, _, _]]; 40 | 41 | isNewlinePos[pos1_] := 42 | ListQ[pos1] && MatchQ[Extract[cst, pos1], LeafNode[Token`Newline, _, _]]; 43 | 44 | commentPoss = Position[cst, LeafNode[Token`Comment, _, _]]; 45 | 46 | If[$Debug, 47 | Print["commentPoss: ", commentPoss] 48 | ]; 49 | 50 | commentMap = <||>; 51 | 52 | Scan[ 53 | Function[{pos}, 54 | 55 | comment = Extract[cst, pos]; 56 | 57 | startOfLine = False; 58 | endOfLine = False; 59 | 60 | newPos = pos; 61 | newPos = Most[newPos] ~Join~ {Last[newPos] - 1}; 62 | While[Last[newPos] >= 1 && isWhitespacePos[newPos], 63 | newPos = Most[newPos] ~Join~ {Last[newPos] - 1}; 64 | ]; 65 | If[Last[newPos] >= 1 && isNewlinePos[newPos], 66 | startOfLine = True 67 | ]; 68 | 69 | newPos = pos; 70 | len = Length[Extract[cst, Most[newPos]]]; 71 | newPos = Most[newPos] ~Join~ {Last[newPos] + 1}; 72 | While[Last[newPos] <= len && isWhitespacePos[newPos], 73 | newPos = Most[newPos] ~Join~ {Last[newPos] + 1}; 74 | ]; 75 | If[Last[newPos] <= len && isNewlinePos[newPos], 76 | endOfLine = True 77 | ]; 78 | 79 | commentData = comment[[3]]; 80 | commentData = <|commentData, <|StartOfLine -> startOfLine, EndOfLine -> endOfLine, "Multiline" -> (startOfLine || endOfLine)|>|>; 81 | comment[[3]] = commentData; 82 | 83 | commentMap[pos] = comment 84 | ] 85 | , 86 | commentPoss 87 | ]; 88 | 89 | cst = ReplacePart[cst, commentMap]; 90 | 91 | cst 92 | ]] 93 | 94 | 95 | End[] 96 | 97 | EndPackage[] 98 | -------------------------------------------------------------------------------- /CodeTools/Generate/GenerateSources.wl: -------------------------------------------------------------------------------- 1 | BeginPackage["CodeTools`Generate`GenerateSources`"] 2 | 3 | buildDirFlagPosition 4 | 5 | buildDir 6 | 7 | srcDirFlagPosition 8 | 9 | srcDir 10 | 11 | script 12 | 13 | pacletFlagPosition 14 | 15 | paclet 16 | 17 | retryFlagPosition 18 | 19 | retry 20 | 21 | pacletLayoutDirFlagPosition 22 | 23 | pacletLayoutDir 24 | 25 | 26 | checkBuildDir 27 | 28 | checkSrcDir 29 | 30 | checkPaclet 31 | 32 | checkPacletLayoutDir 33 | 34 | 35 | Begin["`Private`"] 36 | 37 | buildDirFlagPosition = FirstPosition[$CommandLine, "-buildDir"] 38 | 39 | buildDir := buildDir = $CommandLine[[buildDirFlagPosition[[1]] + 1]] 40 | 41 | srcDirFlagPosition = FirstPosition[$CommandLine, "-srcDir"] 42 | 43 | srcDir := srcDir = $CommandLine[[srcDirFlagPosition[[1]] + 1]] 44 | 45 | scriptPosition = FirstPosition[$CommandLine, "-script"] 46 | 47 | script := script = $CommandLine[[scriptPosition[[1]] + 1]] 48 | 49 | pacletFlagPosition = FirstPosition[$CommandLine, "-paclet"] 50 | 51 | paclet := paclet = $CommandLine[[pacletFlagPosition[[1]] + 1]] 52 | 53 | retryFlagPosition = FirstPosition[$CommandLine, "-retry"] 54 | 55 | retry = !MissingQ[retryFlagPosition] 56 | 57 | pacletLayoutDirFlagPosition = FirstPosition[$CommandLine, "-pacletLayoutDir"] 58 | 59 | pacletLayoutDir := pacletLayoutDir = $CommandLine[[pacletLayoutDirFlagPosition[[1]] + 1]] 60 | 61 | 62 | checkBuildDir[] := 63 | Module[{}, 64 | If[MissingQ[buildDirFlagPosition], 65 | Print["Cannot proceed; buildDir flag missing"]; 66 | Quit[1] 67 | ]; 68 | 69 | If[!DirectoryQ[buildDir], 70 | Print["Cannot proceed; Unsupported buildDir: ", buildDir]; 71 | Quit[1] 72 | ]; 73 | ] 74 | 75 | 76 | checkSrcDir[] := 77 | Module[{}, 78 | If[MissingQ[srcDirFlagPosition], 79 | Print["Cannot proceed; srcDir flag missing"]; 80 | Quit[1] 81 | ]; 82 | 83 | If[!DirectoryQ[srcDir], 84 | Print["Cannot proceed; Unsupported srcDir: ", srcDir]; 85 | Quit[1] 86 | ]; 87 | ] 88 | 89 | 90 | checkPaclet[] := 91 | Module[{}, 92 | If[MissingQ[pacletFlagPosition], 93 | Print["Cannot proceed; paclet flag missing"]; 94 | Quit[1] 95 | ]; 96 | ] 97 | 98 | 99 | checkPacletLayoutDir[] := 100 | Module[{}, 101 | If[MissingQ[pacletLayoutDirFlagPosition], 102 | Print["Cannot proceed; pacletLayoutDir flag missing"]; 103 | Quit[1] 104 | ]; 105 | 106 | If[!DirectoryQ[pacletLayoutDir], 107 | Print["Cannot proceed; Unsupported pacletLayoutDir: ", pacletLayoutDir]; 108 | Quit[1] 109 | ]; 110 | 111 | If[FileNameTake[pacletLayoutDir, -1] =!= "paclet", 112 | Print["Cannot proceed; Unsupported pacletLayoutDir: ", pacletLayoutDir]; 113 | Quit[1] 114 | ]; 115 | ] 116 | 117 | End[] 118 | 119 | EndPackage[] 120 | -------------------------------------------------------------------------------- /CodeFormatter/FrontEnd/StyleSheets/Script.nb: -------------------------------------------------------------------------------- 1 | (* Content-type: application/vnd.wolfram.mathematica *) 2 | 3 | (*** Wolfram Notebook File ***) 4 | (* http://www.wolfram.com/nb *) 5 | 6 | (* CreatedBy='Mathematica 13.0' *) 7 | 8 | (*CacheID: 234*) 9 | (* Internal cache information: 10 | NotebookFileLineBreakTest 11 | NotebookFileLineBreakTest 12 | NotebookDataPosition[ 158, 7] 13 | NotebookDataLength[ 2337, 67] 14 | NotebookOptionsPosition[ 1504, 49] 15 | NotebookOutlinePosition[ 1946, 66] 16 | CellTagsIndexPosition[ 1903, 63] 17 | MenuPosition->0 18 | WindowFrame->Normal*) 19 | 20 | (* Beginning of Notebook Content *) 21 | Notebook[{ 22 | Cell[StyleData[StyleDefinitions -> "Script.nb"],ExpressionUUID->"3a9c5537-444a-4165-8f1b-2e0b98f11ea5"], 23 | 24 | Cell[StyleData["CodeFormatterHighlightColor"], 25 | LineColor->RGBColor[0.2588, 0.5098, 0.12155], 26 | LineOpacity->1., 27 | EdgeColor->RGBColor[0.2588, 0.5098, 0.12155], 28 | FrontFaceOpacity->1., 29 | BackFaceOpacity->1., 30 | Opacity->1., 31 | FontColor->RGBColor[0.2588, 0.5098, 0.12155], 32 | FontOpacity->1.,ExpressionUUID->"b0a1e796-3f9c-4259-9c1a-923a015c53ff"], 33 | 34 | Cell[StyleData["CodeFormatterTextBase"], 35 | LineIndent->0, 36 | LinebreakAdjustments->{1, 10, 1, 0, 1}, 37 | FontFamily->"Source Sans Pro", 38 | FontSize->13, 39 | FontWeight->Plain, 40 | FontSlant->Plain, 41 | PrivateFontOptions->{ 42 | "OperatorSubstitution"-> 43 | False},ExpressionUUID->"0ce1378d-a9ae-478b-8e24-b77b81a8296f"], 44 | 45 | Cell[StyleData["CodeFormatterText", StyleDefinitions -> StyleData[ 46 | "CodeFormatterTextBase"]], 47 | FontColor->GrayLevel[ 48 | 0.2],ExpressionUUID->"4b65fc49-6de8-4570-8bd2-376f995c6986"] 49 | }, 50 | WindowSize->{581.25, 654.}, 51 | WindowMargins->{{Automatic, 63}, {30.75, Automatic}}, 52 | MenuSortingValue->None, 53 | FrontEndVersion->"12.1 for Microsoft Windows (64-bit) (June 19, 2020)", 54 | StyleDefinitions->"StylesheetFormatting.nb", 55 | ExpressionUUID->"7fa73f9a-c115-4504-88bf-a826105a8cc3" 56 | ] 57 | (* End of Notebook Content *) 58 | 59 | (* Internal cache information *) 60 | (*CellTagsOutline 61 | CellTagsIndex->{} 62 | *) 63 | (*CellTagsIndex 64 | CellTagsIndex->{} 65 | *) 66 | (*NotebookFileOutline 67 | Notebook[{ 68 | Cell[574, 21, 103, 0, 39, 48, 0, "StyleData", "StyleDefinitions", "",ExpressionUUID->"3a9c5537-444a-4165-8f1b-2e0b98f11ea5"], 69 | Cell[680, 23, 337, 8, 39, 46, 0, "StyleData", "CodeFormatterHighlightColor", "All",ExpressionUUID->"b0a1e796-3f9c-4259-9c1a-923a015c53ff"], 70 | Cell[1020, 33, 297, 9, 41, 40, 0, "StyleData", "CodeFormatterTextBase", "All",ExpressionUUID->"0ce1378d-a9ae-478b-8e24-b77b81a8296f"], 71 | Cell[1320, 44, 180, 3, 41, 94, 1, "StyleData", "CodeFormatterText", "All",ExpressionUUID->"4b65fc49-6de8-4570-8bd2-376f995c6986"] 72 | } 73 | ] 74 | *) 75 | 76 | -------------------------------------------------------------------------------- /CodeFormatter/FrontEnd/StyleSheets/Package.nb: -------------------------------------------------------------------------------- 1 | (* Content-type: application/vnd.wolfram.mathematica *) 2 | 3 | (*** Wolfram Notebook File ***) 4 | (* http://www.wolfram.com/nb *) 5 | 6 | (* CreatedBy='Mathematica 13.0' *) 7 | 8 | (*CacheID: 234*) 9 | (* Internal cache information: 10 | NotebookFileLineBreakTest 11 | NotebookFileLineBreakTest 12 | NotebookDataPosition[ 158, 7] 13 | NotebookDataLength[ 2592, 74] 14 | NotebookOptionsPosition[ 1616, 52] 15 | NotebookOutlinePosition[ 2200, 73] 16 | CellTagsIndexPosition[ 2157, 70] 17 | MenuPosition->0 18 | WindowFrame->Normal*) 19 | 20 | (* Beginning of Notebook Content *) 21 | Notebook[{ 22 | Cell[StyleData[StyleDefinitions -> "Package.nb"],ExpressionUUID->"3a9c5537-444a-4165-8f1b-2e0b98f11ea5"], 23 | 24 | Cell[StyleData["CodeFormatterHighlightColor"], 25 | LineColor->RGBColor[ 26 | 0.1411764705882353, 0.592156862745098, 0.7176470588235294], 27 | LineOpacity->1., 28 | EdgeColor->RGBColor[ 29 | 0.1411764705882353, 0.592156862745098, 0.7176470588235294], 30 | FrontFaceOpacity->1., 31 | BackFaceOpacity->1., 32 | Opacity->1., 33 | FontColor->RGBColor[ 34 | 0.1411764705882353, 0.592156862745098, 0.7176470588235294], 35 | FontOpacity->1.,ExpressionUUID->"b0a1e796-3f9c-4259-9c1a-923a015c53ff"], 36 | 37 | Cell[StyleData["CodeFormatterTextBase"], 38 | LineIndent->0, 39 | LinebreakAdjustments->{1, 10, 1, 0, 1}, 40 | FontFamily->"Source Sans Pro", 41 | FontSize->13, 42 | FontWeight->Plain, 43 | FontSlant->Plain, 44 | PrivateFontOptions->{ 45 | "OperatorSubstitution"-> 46 | False},ExpressionUUID->"9e174ca1-131f-44eb-b060-37d687b1690b"], 47 | 48 | Cell[StyleData["CodeFormatterText", StyleDefinitions -> StyleData[ 49 | "CodeFormatterTextBase"]], 50 | FontColor->GrayLevel[ 51 | 0.2],ExpressionUUID->"21216e57-865f-488b-8e34-ed9cf05e9edd"] 52 | }, 53 | WindowSize->{581.25, 654.}, 54 | WindowMargins->{{Automatic, 25.5}, {33.75, Automatic}}, 55 | WindowFrame->"Normal", 56 | DockedCells->FEPrivate`FrontEndResource[ 57 | "FEExpressions", "BuiltInStylesheetToolbar"], 58 | TrackCellChangeTimes->False, 59 | MenuSortingValue->None, 60 | FrontEndVersion->"12.1 for Microsoft Windows (64-bit) (June 19, 2020)", 61 | StyleDefinitions->"StylesheetFormatting.nb", 62 | ExpressionUUID->"851c6fc7-73a1-4086-8dae-15ef124443b2" 63 | ] 64 | (* End of Notebook Content *) 65 | 66 | (* Internal cache information *) 67 | (*CellTagsOutline 68 | CellTagsIndex->{} 69 | *) 70 | (*CellTagsIndex 71 | CellTagsIndex->{} 72 | *) 73 | (*NotebookFileOutline 74 | Notebook[{ 75 | Cell[574, 21, 104, 0, 39, 49, 0, "StyleData", "StyleDefinitions", "",ExpressionUUID->"3a9c5537-444a-4165-8f1b-2e0b98f11ea5"], 76 | Cell[681, 23, 448, 11, 39, 46, 0, "StyleData", "CodeFormatterHighlightColor", "All",ExpressionUUID->"b0a1e796-3f9c-4259-9c1a-923a015c53ff"], 77 | Cell[1132, 36, 297, 9, 41, 40, 0, "StyleData", "CodeFormatterTextBase", "All",ExpressionUUID->"9e174ca1-131f-44eb-b060-37d687b1690b"], 78 | Cell[1432, 47, 180, 3, 41, 94, 1, "StyleData", "CodeFormatterText", "All",ExpressionUUID->"21216e57-865f-488b-8e34-ed9cf05e9edd"] 79 | } 80 | ] 81 | *) 82 | 83 | -------------------------------------------------------------------------------- /scripts/re_build_CodeFormatter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /CodeFormatter/Generate/AcceptableOperators.wl: -------------------------------------------------------------------------------- 1 | 2 | If[!MemberQ[$Path, #], PrependTo[$Path, #]]&[DirectoryName[$InputFileName, 3]] 3 | 4 | BeginPackage["CodeFormatter`Generate`AcceptableOperators`"] 5 | 6 | Begin["`Private`"] 7 | 8 | (* 9 | Do not allow PacletManager to participate in finding `Generate` files 10 | 11 | PacletManager will find e.g. CodeParser/Kernel/TokenEnum.wl when asked to find CodeParser`Generate`TokenEnum` 12 | 13 | related issues: PACMAN-54 14 | *) 15 | Block[{Internal`PacletFindFile = Null&}, 16 | Needs["CodeTools`Generate`GenerateSources`"]; 17 | ] 18 | 19 | 20 | checkBuildDir[] 21 | checkSrcDir[] 22 | 23 | 24 | dataDir = FileNameJoin[{srcDir, "CodeParser", "Data"}] 25 | 26 | 27 | importedPrefixParselets = Get[FileNameJoin[{dataDir, "PrefixParselets.wl"}]] 28 | 29 | importedInfixParselets = Get[FileNameJoin[{dataDir, "InfixParselets.wl"}]] 30 | 31 | 32 | normalPrefixParselets = Normal[importedPrefixParselets] 33 | 34 | normalInfixParselets = Normal[importedInfixParselets] 35 | 36 | 37 | acceptableOperators = 38 | 39 | (* 40 | Remove prefix operators that are also postfix operators 41 | *) 42 | Complement[ 43 | Cases[normalPrefixParselets, Verbatim[Rule][tok_, Parselet`PrefixOperatorParselet[_, _]] :> tok], 44 | Cases[normalInfixParselets, Verbatim[Rule][tok_, Parselet`PostfixOperatorParselet[_, _]] :> tok] 45 | ] ~Join~ 46 | 47 | Cases[normalPrefixParselets, Verbatim[Rule][tok_, Parselet`GroupParselet[_, _]] :> tok] ~Join~ 48 | Cases[normalInfixParselets, Verbatim[Rule][tok_, Parselet`BinaryOperatorParselet[_, _]] :> tok] ~Join~ 49 | Complement[ 50 | Cases[normalInfixParselets, Verbatim[Rule][tok_, Parselet`InfixOperatorParselet[_, _]] :> tok], 51 | (* 52 | Manually remove Token`Fake`ImplicitTimes 53 | 54 | Manually remove Token`Dot, because we do not want to break after a =. 55 | *) 56 | {Token`Fake`ImplicitTimes, Token`Dot} 57 | ] ~Join~ 58 | Cases[normalInfixParselets, Verbatim[Rule][tok_, Parselet`TildeParselet[]] :> tok] ~Join~ 59 | Cases[normalInfixParselets, Verbatim[Rule][tok_, Parselet`SlashColonParselet[]] :> tok] ~Join~ 60 | Cases[normalInfixParselets, Verbatim[Rule][tok_, Parselet`ColonParselet[]] :> tok] ~Join~ 61 | Cases[normalInfixParselets, Verbatim[Rule][tok_, Parselet`CommaParselet[]] :> tok] 62 | 63 | 64 | generate[] := ( 65 | 66 | Print["Generating Acceptable Operators..."]; 67 | 68 | acceptableOperatorsWL = { 69 | " 70 | (* 71 | AUTO GENERATED FILE 72 | DO NOT MODIFY 73 | *) 74 | 75 | BeginPackage[\"CodeFormatter`AcceptableOperators`\"] 76 | 77 | isAcceptableOperator 78 | 79 | Begin[\"`Private`\"] 80 | "} ~Join~ 81 | 82 | (Row[{"isAcceptableOperator[", #, "]", " ", "=", " ", "True"}]& /@ acceptableOperators) ~Join~ 83 | 84 | {} ~Join~ 85 | 86 | {" 87 | isAcceptableOperator[_] = False 88 | 89 | End[] 90 | 91 | EndPackage[] 92 | "}; 93 | 94 | Print["exporting AcceptableOperators.wl"]; 95 | res = Export[FileNameJoin[{buildDir, "paclet", "CodeFormatter", "Kernel", "AcceptableOperators.wl"}], Column[acceptableOperatorsWL], "String"]; 96 | 97 | Print[res]; 98 | 99 | If[FailureQ[res], 100 | Quit[1] 101 | ]; 102 | 103 | Print["Done Acceptable Operators"] 104 | ) 105 | 106 | If[!StringQ[script], 107 | Quit[1] 108 | ] 109 | If[AbsoluteFileName[script] === AbsoluteFileName[$InputFileName], 110 | generate[] 111 | ] 112 | 113 | End[] 114 | 115 | EndPackage[] 116 | -------------------------------------------------------------------------------- /CodeFormatter/Kernel/Absorb.wl: -------------------------------------------------------------------------------- 1 | BeginPackage["CodeFormatter`Absorb`"] 2 | 3 | absorbNewlinesIntoComments 4 | 5 | absorbNewlinesIntoSemis 6 | 7 | Begin["`Private`"] 8 | 9 | Needs["CodeFormatter`"] 10 | Needs["CodeFormatter`Utils`"] 11 | Needs["CodeParser`"] 12 | Needs["CodeParser`Utils`"] 13 | 14 | 15 | absorbNewlinesIntoComments[fsIn_] := 16 | Module[{fs, poss, toDelete, i, isWhitespacePos, isNewlinePos, len, toInsert, lastInserted1, lastInserted2, changed}, 17 | 18 | fs = fsIn; 19 | 20 | isWhitespacePos[pos1_] := 21 | ListQ[pos1] && MatchQ[Extract[fs, pos1], LeafNode[Whitespace | Token`Boxes`MultiWhitespace, _, _]]; 22 | 23 | isNewlinePos[pos1_] := 24 | ListQ[pos1] && MatchQ[Extract[fs, pos1], LeafNode[Token`Newline, _, _]]; 25 | 26 | lastInserted1 = Null; 27 | lastInserted2 = Null; 28 | 29 | toDelete = Internal`Bag[]; 30 | toInsert = Internal`Bag[]; 31 | 32 | 33 | poss = Position[fs, FragmentNode[Token`Comment, _, KeyValuePattern[StartOfLine -> False]], {1}]; 34 | 35 | If[$Debug, 36 | Print["poss (StartOfLine): ", poss]; 37 | ]; 38 | 39 | (* 40 | Eat all of the whitespace and newlines before a StartOfLine -> False comment fragment 41 | *) 42 | Scan[ 43 | Function[{pos}, 44 | i = Last[pos]; 45 | i--; 46 | While[i >= 1 && (isWhitespacePos[Most[pos] ~Join~ {i}] || isNewlinePos[Most[pos] ~Join~ {i}]), 47 | Internal`StuffBag[toDelete, Most[pos] ~Join~ {i}]; 48 | lastInserted1 = Most[pos] ~Join~ {i}; 49 | i--; 50 | ] 51 | ] 52 | , 53 | poss 54 | ]; 55 | 56 | 57 | poss = Position[fs, FragmentNode[Token`Comment, _, KeyValuePattern[EndOfLine -> False]], {1}]; 58 | 59 | If[$Debug, 60 | Print["poss (EndOfLine): ", poss]; 61 | ]; 62 | 63 | (* 64 | Eat all of the whitespace and newlines after a EndOfLine -> False comment fragment 65 | *) 66 | Scan[ 67 | Function[{pos}, 68 | len = Length[fs]; 69 | i = Last[pos]; 70 | i++; 71 | While[i <= len && (isWhitespacePos[Most[pos] ~Join~ {i}] || isNewlinePos[Most[pos] ~Join~ {i}]), 72 | Internal`StuffBag[toDelete, Most[pos] ~Join~ {i}]; 73 | lastInserted2 = Most[pos] ~Join~ {i}; 74 | i++; 75 | ] 76 | ] 77 | , 78 | poss 79 | ]; 80 | 81 | 82 | toDelete = Internal`BagPart[toDelete, All]; 83 | 84 | (* 85 | Make sure to insert a single space back before / after the comment 86 | *) 87 | If[lastInserted1 =!= Null, 88 | toDelete = DeleteCases[toDelete, lastInserted1]; 89 | fs = ReplacePart[fs, lastInserted1 -> LeafNode[Whitespace, " ", <||>]]; 90 | ]; 91 | If[lastInserted2 =!= Null, 92 | toDelete = DeleteCases[toDelete, lastInserted2]; 93 | fs = ReplacePart[fs, lastInserted2 -> LeafNode[Whitespace, " ", <||>]]; 94 | ]; 95 | 96 | If[$Debug, 97 | Print["toDelete: ", toDelete]; 98 | ]; 99 | 100 | fs = Delete[fs, toDelete]; 101 | 102 | changed = !empty[toDelete]; 103 | 104 | {fs, changed} 105 | ] 106 | 107 | 108 | absorbNewlinesIntoSemis[fsIn_] := 109 | Module[{fs, poss, toDelete, i, isWhitespacePos, isNewlinePos, len}, 110 | 111 | fs = fsIn; 112 | 113 | isWhitespacePos[pos1_] := 114 | ListQ[pos1] && MatchQ[Extract[fs, pos1], LeafNode[Whitespace | Token`Boxes`MultiWhitespace, _, _]]; 115 | 116 | isNewlinePos[pos1_] := 117 | ListQ[pos1] && MatchQ[Extract[fs, pos1], LeafNode[Token`Newline, _, _]]; 118 | 119 | toDelete = Internal`Bag[]; 120 | 121 | 122 | poss = Position[fs, LeafNode[Token`Semi, _, KeyValuePattern["Toplevel" -> True]], {1}]; 123 | 124 | If[$Debug, 125 | Print["poss: ", poss]; 126 | ]; 127 | 128 | (* 129 | Eat all of the whitespace and newlines before a "Toplevel" -> True Token`Semi leaf 130 | *) 131 | Scan[ 132 | Function[{pos}, 133 | i = Last[pos]; 134 | i--; 135 | While[i >= 1 && (isWhitespacePos[Most[pos] ~Join~ {i}] || isNewlinePos[Most[pos] ~Join~ {i}]), 136 | Internal`StuffBag[toDelete, Most[pos] ~Join~ {i}]; 137 | i--; 138 | ] 139 | ] 140 | , 141 | poss 142 | ]; 143 | 144 | 145 | toDelete = Internal`BagPart[toDelete, All]; 146 | 147 | If[$Debug, 148 | Print["toDelete: ", toDelete]; 149 | ]; 150 | 151 | fs = Delete[fs, toDelete]; 152 | 153 | fs 154 | ] 155 | 156 | 157 | End[] 158 | 159 | EndPackage[] 160 | -------------------------------------------------------------------------------- /Tests/OpenBugs.mt: -------------------------------------------------------------------------------- 1 | 2 | Needs["CodeFormatter`"] 3 | 4 | Needs["CodeParser`"] 5 | 6 | 7 | (* 8 | OPEN BUG: Airiness -> 1 and anchored comments that absorb newlines do not play well together 9 | *) 10 | Test[ 11 | CodeFormat["If(*1*)[(*2*)a(*3*),(*4*)b(*5*)]", Airiness -> 1] 12 | , 13 | "If(*1*)[ 14 | (*2*) 15 | a 16 | (*3*) 17 | , 18 | (*4*) 19 | b 20 | (*5*) 21 | ]" 22 | , 23 | TestID->"OpenBugs-20210113-C5B5D4" 24 | ] 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | (* 35 | 36 | OPEN BUG 37 | 38 | current limitations of LineBreakerV2: 39 | 40 | line ends up being 91 characters long because: 41 | 42 | "DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[][\"Publishers\"]]"" = 77 characters, so 1 line 43 | 44 | "ns = DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[][\"Publishers\"]]"" = 82 characters, so multiline 45 | 46 | IndentationNode[Block, {the above code}] = 81 characters 47 | 48 | nothing ever forces first line to actually re-indent 49 | 50 | 51 | It doesn't really matter what the actual result is 52 | The test is for <80 character lines 53 | *) 54 | 55 | Test[ 56 | CodeFormat[ 57 | "resourcePublisherNameSpaceFreeQ[name_String] := 58 | With[ 59 | { 60 | ns 61 | = 62 | DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[][\"Publishers\"]] 63 | } 64 | , 65 | !MatchQ[name, Alternatives @@ ns] 66 | ]" 67 | , 68 | "BreakLinesMethod" -> "LineBreakerV2" 69 | ] 70 | , 71 | "resourcePublisherNameSpaceFreeQ[name_String] := 72 | With[ 73 | { 74 | ns 75 | = 76 | DeleteMissing[ 77 | publisherResourceNameSpace 78 | /@ 79 | allPublisherInfo[][ 80 | \"Publishers\" 81 | ] 82 | ] 83 | } 84 | , 85 | !MatchQ[name, Alternatives @@ ns] 86 | ]" 87 | , 88 | TestID->"OpenBugs-20211007-J5F4K7" 89 | ] 90 | 91 | 92 | 93 | Test[ 94 | CodeFormat["1 + 2222222222222222222222222 + 3", "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV2"] 95 | , 96 | "1 + 2222222222222222222222222 + 97 | 3" 98 | , 99 | TestID->"OpenBugs-20211007-B3W2W2" 100 | ] 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | (* 109 | OPEN BUG 110 | 111 | LineBreakerV2 112 | 113 | if indenting would go over line width, then there should be a way to not indent 114 | 115 | *) 116 | 117 | 118 | input = ToString[Nest[f, x, 30], InputForm, PageWidth -> Infinity] 119 | 120 | Test[ 121 | CodeFormat[input, "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV2"] 122 | , 123 | "f[ 124 | f[ 125 | f[ 126 | f[ 127 | f[ 128 | f[ 129 | f[ 130 | f[ 131 | f[ 132 | f[ 133 | f[ 134 | f[ 135 | f[ 136 | f[ 137 | f[ 138 | f[ 139 | f[ 140 | f[ 141 | f[ 142 | f[ 143 | f[ 144 | f[ 145 | f[ 146 | f[ 147 | f[f[f[f[f[f[x]]]]]] 148 | ] 149 | ] 150 | ] 151 | ] 152 | ] 153 | ] 154 | ] 155 | ] 156 | ] 157 | ] 158 | ] 159 | ] 160 | ] 161 | ] 162 | ] 163 | ] 164 | ] 165 | ] 166 | ] 167 | ] 168 | ] 169 | ] 170 | ] 171 | ]" 172 | , 173 | TestID->"OpenBugs-20211007-H9I5O1" 174 | ] 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | (* 187 | OPEN BUG 188 | 189 | the Token`Fake`ImplicitTimes is on its own line, but it is invisible 190 | 191 | so there should only be 1 newline 192 | *) 193 | 194 | TestMatch[ 195 | CodeFormat[str, "LineWidth" -> 120, "BreakLinesMethod" -> "LineBreakerV2"] 196 | , 197 | "( 198 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 199 | b 200 | )" 201 | , 202 | {} 203 | , 204 | TestID->"OpenBugs-20211007-J3W2U8" 205 | ] 206 | 207 | 208 | 209 | 210 | 211 | (* 212 | OPEN BUG 213 | 214 | non-anchored comments should insert spaces where appropriate 215 | *) 216 | 217 | Test[ 218 | CodeFormat["{edgeforms (*color*)}"] 219 | , 220 | "{edgeforms (*color*)}" 221 | , 222 | TestID->"OpenBugs-20211007-V0I8A3" 223 | ] 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | (* 236 | OPEN BUG 237 | 238 | comments should be indented properly 239 | *) 240 | 241 | Test[ 242 | CodeFormat[ 243 | "Module[ 244 | {x} 245 | , 246 | (* 247 | a comment 248 | *) 249 | f[x] 250 | ]"] 251 | , 252 | "Module[ 253 | {x} 254 | , 255 | (* 256 | a comment 257 | *) 258 | f[x] 259 | ]" 260 | , 261 | TestID->"OpenBugs-20211007-K9U3B8" 262 | ] 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /CodeFormatter/Generate/MakeCodeFormatterAttachedCell.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | (* ::Section::Closed:: *) 4 | (*Package Header*) 5 | 6 | 7 | Unprotect["CodeFormatterCell`*"]; 8 | 9 | 10 | ClearAll["CodeFormatterCell`*"]; 11 | 12 | 13 | BeginPackage["CodeFormatterCell`"]; 14 | 15 | 16 | If[!MemberQ[$ContextPath, "CodeFormatter`Generate`UIElements`"], 17 | Get[ToFileName[{DirectoryName[NotebookFileName[]]}, "UIElements.wl"]] 18 | ]; 19 | 20 | 21 | (* ::Section::Closed:: *) 22 | (*Code Formatter Docked Cell*) 23 | 24 | 25 | toCell[contents_] := Cell[BoxData @ ToBoxes[contents]] 26 | 27 | 28 | formatter = 29 | DynamicModule[{}, 30 | Grid[ 31 | {{ 32 | Dynamic[FEPrivate`FrontEndResource["CodeFormatterStrings", "AirinessLabel"]], 33 | Spacer[10], 34 | DynamicModule[{semicolons, operators, groups, commas, ctrlStruct, scopingStruct, comments}, 35 | DynamicWrapper[(* DynamicWrapper is part of fix 402825 *) 36 | #, 37 | {semicolons, operators, groups, commas, ctrlStruct, scopingStruct, comments, CodeFormatter`$InteractiveAiriness} = 38 | Lookup[ 39 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], 40 | { 41 | "NewlinesBetweenSemicolons", "NewlinesBetweenOperators", "NewlinesInGroups", "NewlinesBetweenCommas", "NewlinesInControl", 42 | "NewlinesInScoping", "NewlinesInComments", "Airiness"}, 43 | Automatic], 44 | TrackedSymbols :> {} (* only trigger if the CurrentValue changes *) 45 | ]& @ 46 | AirinessSlider[ 47 | Dynamic[ 48 | CodeFormatter`$InteractiveAiriness, 49 | { 50 | Automatic, 51 | (* this fires on slider mouse up but after internal side effect of setting "CodeFormat" CurrentValue key-values *) 52 | Function[{val, expr}, 53 | expr = val; 54 | If[$VersionNumber >= 12.2, CodeFormatter`Notebooks`formatSelectedCell[]], 55 | HoldAll 56 | ] 57 | } 58 | ], 59 | Dynamic[{semicolons, operators, groups, commas, ctrlStruct, scopingStruct, comments}] 60 | ], 61 | Initialization :> ( 62 | (* Populate the local option value variables. *) 63 | With[{optVals = Replace[CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], Except[_Association] -> <||>]}, 64 | semicolons = Lookup[optVals, "NewlinesBetweenSemicolons", Automatic]; 65 | operators = Lookup[optVals, "NewlinesBetweenOperators", Automatic]; 66 | groups = Lookup[optVals, "NewlinesInGroups", Automatic]; 67 | commas = Lookup[optVals, "NewlinesBetweenCommas", Automatic]; 68 | ctrlStruct = Lookup[optVals, "NewlinesInControl", Automatic]; 69 | scopingStruct = Lookup[optVals, "NewlinesInScoping", Automatic]; 70 | comments = Lookup[optVals, "NewlinesInComments", Automatic]]; 71 | ) 72 | ], 73 | 74 | delimiter, 75 | 76 | Style[tr["IndentationLabel"], "CodeFormatterText"], 77 | Spacer[10], 78 | DynamicWrapper[(* DynamicWrapper is part of fix 402825 *) 79 | #, 80 | {CodeFormatter`$InteractiveIndentationCharacter, CodeFormatter`$InteractiveTabWidth} = 81 | Lookup[ 82 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], 83 | {"InteractiveIndentationCharacter", "InteractiveTabWidth"}, 84 | Automatic], 85 | TrackedSymbols :> {} (* only trigger if the CurrentValue changes *) 86 | ]& @ 87 | IndentationMenu[ 88 | Dynamic[CodeFormatter`$InteractiveIndentationCharacter], 89 | Function[{val}, If[$VersionNumber >= 12.2, CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "InteractiveIndentationCharacter"}] = val]], 90 | Dynamic[CodeFormatter`$InteractiveTabWidth], 91 | Function[{val}, If[$VersionNumber >= 12.2, CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "InteractiveTabWidth"}] = val]], 92 | Function[If[$VersionNumber >= 12.2, CodeFormatter`Notebooks`formatSelectedCell[]]] 93 | ] 94 | }}, 95 | Spacings -> {0, 0}, Alignment -> {Center, Center}, BaseStyle -> "CodeFormatterText" 96 | ], 97 | 98 | SynchronousInitialization -> False, 99 | 100 | Initialization :> ( 101 | Module[{opts}, 102 | 103 | Needs["CodeFormatter`Notebooks`"]; 104 | 105 | If[$VersionNumber >= 12.2, 106 | 107 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "InteractiveReparse"}] = True; 108 | opts = Replace[CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], Except[_Association] -> <||>]; 109 | 110 | CodeFormatter`$InteractiveAiriness = Lookup[opts, "Airiness", 0]; 111 | CodeFormatter`$InteractiveTabWidth = Lookup[opts, "InteractiveTabWidth", "4"]; 112 | CodeFormatter`$InteractiveIndentationCharacter = Lookup[opts, "InteractiveIndentationCharacter", "space"]; 113 | CodeFormatter`$InteractiveReparse = Lookup[opts, "InteractiveReparse", True]; 114 | ]; 115 | ]; 116 | 117 | (* Make sure the open/close button in the Toolbar package is displayed *) 118 | CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "CodeFormatter", "ShowDropDown"}] = True 119 | 120 | ) 121 | ]; 122 | 123 | 124 | (* ::Section::Closed:: *) 125 | (*Output*) 126 | 127 | 128 | codeFormatter = FileNameJoin[{ParentDirectory[NotebookDirectory[]], "FrontEnd", "TextResources", "CodeFormatter.tr"}]; 129 | 130 | 131 | (* Ensure WIReS resources are visible *) 132 | ResourceFunction["AddResourceSystem", ResourceSystemBase -> "https://www.internalcloud.wolfram.com/obj/resourcesystem/api/1.0"]["WIReS"]; 133 | 134 | 135 | ResourceFunction["WriteTextResource"][codeFormatter, "@@resource CodeFormatterLocalizedExpressions English", "PackageToolbarPreferencesCell" -> toCell[formatter]] 136 | 137 | 138 | (* ::Section::Closed:: *) 139 | (*Package Footer*) 140 | 141 | 142 | EndPackage[]; 143 | -------------------------------------------------------------------------------- /Tests/CodeFormatter.mt: -------------------------------------------------------------------------------- 1 | (* Wolfram Language Test file *) 2 | 3 | Needs["CodeFormatter`"] 4 | Needs["CodeParser`"] 5 | 6 | 7 | 8 | 9 | (* 10 | Non-contiguous brackets 11 | *) 12 | Test[ 13 | CodeFormat["f[ [1]]"] 14 | , 15 | "f[[1]]" 16 | , 17 | TestID -> "CodeFormatter-20191111-O8Y0I1" 18 | ] 19 | 20 | Test[ 21 | CodeFormat["f[[1] ]"] 22 | , 23 | "f[[1]]" 24 | , 25 | TestID -> "CodeFormatter-20191111-X2P2T6" 26 | ] 27 | 28 | Test[ 29 | CodeFormat["f[[1] ]"] 30 | , 31 | "f[[1]]" 32 | , 33 | TestID -> "CodeFormatter-20191111-C0H0R2" 34 | ] 35 | 36 | 37 | 38 | 39 | (* 40 | Bad UTF-8 encoding 41 | *) 42 | Test[ 43 | CodeFormat[{206}] 44 | , 45 | Missing["UnsafeCharacterEncoding_IncompleteUTF8Sequence"] 46 | , 47 | TestID -> "CodeFormatter-20191111-W3S9H1" 48 | ] 49 | 50 | 51 | 52 | (* 53 | Top-level line continuations 54 | *) 55 | Test[ 56 | CodeFormat["{a \\\n+1}"] 57 | , 58 | "{a + 1}" 59 | , 60 | TestID -> "CodeFormatter-20191111-P7F7L2" 61 | ] 62 | 63 | 64 | 65 | 66 | 67 | 68 | Test[ 69 | CodeFormat["1.2`->3"] 70 | , 71 | "1.2` -> 3" 72 | , 73 | TestID -> "CodeFormatter-20191111-H4M7F5" 74 | ] 75 | 76 | Test[ 77 | CodeFormat["a-->0"] 78 | , 79 | "a-- > 0" 80 | , 81 | TestID -> "CodeFormatter-20191111-O9V1N1" 82 | ] 83 | 84 | Test[ 85 | CodeFormat["a--=0"] 86 | , 87 | "a-- = 0" 88 | , 89 | TestID -> "CodeFormatter-20191111-H2F5P8" 90 | ] 91 | 92 | Test[ 93 | CodeFormat["<||>=0"] 94 | , 95 | "<||> = 0" 96 | , 97 | TestID -> "CodeFormatter-20191111-F4G3N8" 98 | ] 99 | 100 | Test[ 101 | CodeFormat["t/.3"] 102 | , 103 | "t / .3" 104 | , 105 | TestID -> "CodeFormatter-20191111-L7X1O9" 106 | ] 107 | 108 | Test[ 109 | CodeFormat["a++=0"] 110 | , 111 | "a++ = 0" 112 | , 113 | TestID -> "CodeFormatter-20191111-F4G6G5" 114 | ] 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | Test[ 123 | CodeFormat["a = ."] 124 | , 125 | "a =." 126 | , 127 | TestID -> "CodeFormatter-20191111-M7F5A1" 128 | ] 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | Test[ 137 | CodeFormat["a "] 138 | , 139 | "a" 140 | , 141 | TestID -> "CodeFormatter-20191111-C8L0H3" 142 | ] 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | (* 151 | Comments are not modified 152 | *) 153 | Test[ 154 | CodeFormat["(*a\\\nb*)c"] 155 | , 156 | "(*a\\\nb*) c" 157 | , 158 | TestID -> "CodeFormatter-20191112-S2I3Y2" 159 | ] 160 | 161 | 162 | 163 | Test[ 164 | CodeFormat["0.."] 165 | , 166 | "0 .." 167 | , 168 | TestID -> "CodeFormatter-20191113-P5R8O4" 169 | ] 170 | 171 | 172 | 173 | 174 | 175 | Test[ 176 | CodeFormat["_..."] 177 | , 178 | "_. .." 179 | , 180 | TestID -> "CodeFormatter-20191113-T8E6X9" 181 | ] 182 | 183 | 184 | 185 | 186 | 187 | 188 | (* 189 | Implicit Times 190 | *) 191 | 192 | Test[ 193 | CodeFormat["1.2`a"] 194 | , 195 | "1.2` a" 196 | , 197 | TestID -> "CodeFormatter-20191111-H8F1J5" 198 | ] 199 | 200 | Test[ 201 | CodeFormat["1.2` a"] 202 | , 203 | "1.2` a" 204 | , 205 | TestID -> "CodeFormatter-20191114-A1W5Y9" 206 | ] 207 | 208 | Test[ 209 | CodeFormat["_.0"] 210 | , 211 | "_. 0" 212 | , 213 | TestID -> "CodeFormatter-20191113-C0N8Q4" 214 | ] 215 | 216 | Test[ 217 | CodeFormat["_. 0"] 218 | , 219 | "_. 0" 220 | , 221 | TestID -> "CodeFormatter-20191114-Y5X5K7" 222 | ] 223 | 224 | Test[ 225 | CodeFormat["a_.b"] 226 | , 227 | "a_. b" 228 | , 229 | TestID -> "CodeFormatter-20191113-K9N4C8" 230 | ] 231 | 232 | Test[ 233 | CodeFormat["a_. b"] 234 | , 235 | "a_. b" 236 | , 237 | TestID -> "CodeFormatter-20191114-C8O3Z3" 238 | ] 239 | 240 | Test[ 241 | CodeFormat["____"] 242 | , 243 | "___ _" 244 | , 245 | TestID -> "CodeFormatter-20191113-X8H4W7" 246 | ] 247 | 248 | Test[ 249 | CodeFormat["___ _"] 250 | , 251 | "___ _" 252 | , 253 | TestID -> "CodeFormatter-20191114-A8N3P3" 254 | ] 255 | 256 | 257 | Test[ 258 | CodeFormat["iMelToHz[args_,opts_]"] 259 | , 260 | "iMelToHz[args_, opts_]" 261 | , 262 | TestID -> "CodeFormatter-20191121-K3W7G3" 263 | ] 264 | 265 | 266 | 267 | Test[ 268 | CodeFormat[ 269 | " 270 | foo[bar[[1, 2] 271 | ]] 272 | "] 273 | , 274 | "foo[bar[[1, 2]]]" 275 | , 276 | TestID->"CodeFormatter-20200406-V9L3L5" 277 | ] 278 | 279 | 280 | Test[ 281 | CodeFormat["{0. ...}"] 282 | , 283 | "{0. ...}" 284 | , 285 | TestID->"CodeFormatter-20200715-K5E0Q1" 286 | ] 287 | 288 | 289 | 290 | Test[ 291 | CodeFormat["a\"\t\""] 292 | , 293 | "a \" \"" 294 | , 295 | TestID->"CodeFormatter-20200803-Y4E5E2" 296 | ] 297 | 298 | 299 | 300 | Test[ 301 | CodeFormat["a(*\t*)&"] 302 | , 303 | "a(* *)&" 304 | , 305 | TestID->"CodeFormatter-20200803-U6J1P6" 306 | ] 307 | 308 | 309 | Test[ 310 | CodeFormat["If[a, b, c]"] 311 | , 312 | "If[a, 313 | b 314 | , 315 | c 316 | ]" 317 | , 318 | TestID->"CodeFormatter-20200805-Q8H6M7" 319 | ] 320 | 321 | 322 | TestMatch[ 323 | CodeFormat["\\((*\\ \\(Test\\ comment\\)\\ *)\\)"] 324 | , 325 | _String 326 | , 327 | TestID->"CodeFormatter-20200813-B6S4H2" 328 | ] 329 | 330 | 331 | 332 | 333 | Test[ 334 | CodeFormat["a_ .."] 335 | , 336 | "a_ .." 337 | , 338 | TestID->"CodeFormatter-20210113-C2T7E8" 339 | ] 340 | 341 | 342 | 343 | 344 | Test[ 345 | CodeFormat[{}] 346 | , 347 | "" 348 | , 349 | TestID->"CodeFormatter-20210902-R7Y1U6" 350 | ] 351 | 352 | 353 | 354 | (* 355 | bug 421585 356 | *) 357 | Test[ 358 | CodeFormatCST[LeafNode[Symbol, "x", <||>]] 359 | , 360 | "x\n" 361 | , 362 | TestID->"CodeFormatter-20220329-E0H9Q0" 363 | ] 364 | 365 | 366 | Test[ 367 | CodeFormat["a/b"] 368 | , 369 | "a / b" 370 | , 371 | TestID->"CodeFormatter-20220915-B6S2B8" 372 | ] 373 | 374 | Test[ 375 | CodeFormat["1/0"] 376 | , 377 | "1/0" 378 | , 379 | TestID->"CodeFormatter-20220915-V8Q0W0" 380 | ] 381 | 382 | 383 | Test[ 384 | CodeFormat["1/-0"] 385 | , 386 | "1/-0" 387 | , 388 | TestID->"CodeFormatter-20221018-Q2X8M9" 389 | ] 390 | 391 | Test[ 392 | CodeFormat["10^9"] 393 | , 394 | "10^9" 395 | , 396 | TestID->"CodeFormatter-20221018-Z7H7W5" 397 | ] 398 | 399 | Test[ 400 | CodeFormat["10^-9"] 401 | , 402 | "10^-9" 403 | , 404 | TestID->"CodeFormatter-20221018-B5K0S1" 405 | ] 406 | 407 | 408 | 409 | Test[ 410 | CodeFormat["Test[1/0, ComplexInfinity, {Power::infy}, TestID->\"test\"]"] 411 | , 412 | "\ 413 | Test[ 414 | 1/0 415 | , 416 | ComplexInfinity 417 | , 418 | {Power::infy} 419 | , 420 | TestID -> \"test\" 421 | ]" 422 | , 423 | TestID->"CodeFormatter-20220915-G5E9I2" 424 | ] 425 | 426 | -------------------------------------------------------------------------------- /cmake/WolframKernel.cmake: -------------------------------------------------------------------------------- 1 | 2 | if(NOT DEFINED MATHEMATICA_INSTALL_DIR) 3 | if(CMAKE_HOST_WIN32) 4 | set(MATHEMATICA_INSTALL_DIR "C:/Program Files/Wolfram Research/Mathematica/13.1") 5 | elseif(CMAKE_HOST_APPLE) 6 | set(MATHEMATICA_INSTALL_DIR /Applications/Mathematica.app/Contents) 7 | else() 8 | set(MATHEMATICA_INSTALL_DIR /usr/local/Wolfram/Mathematica/13.1) 9 | endif() 10 | endif() 11 | 12 | if(CMAKE_HOST_WIN32) 13 | set(WOLFRAMKERNEL_DEFAULT ${MATHEMATICA_INSTALL_DIR}/wolfram.exe) 14 | set(WOLFRAMLIBRARY_INCLUDE_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/IncludeFiles/C) 15 | # 16 | # in versions before 11.2, there were 2 separate paths: 17 | # SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions/mldev64/include 18 | # SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions/mldev64/lib 19 | # 20 | # starting in 11.2, the single path for MathLink includes and MathLink libs is: 21 | # SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions 22 | # 23 | if(EXISTS ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions/mldev64/include) 24 | set(MATHLINK_INCLUDE_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions/mldev64/include) 25 | else() 26 | set(MATHLINK_INCLUDE_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions) 27 | endif() 28 | if(EXISTS ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions/mldev64/lib) 29 | set(MATHLINK_LIB_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions/mldev64/lib) 30 | else() 31 | set(MATHLINK_LIB_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/Windows-x86-64/CompilerAdditions) 32 | endif() 33 | elseif(CMAKE_HOST_APPLE) 34 | set(WOLFRAMKERNEL_DEFAULT ${MATHEMATICA_INSTALL_DIR}/MacOS/WolframKernel) 35 | set(WOLFRAMLIBRARY_INCLUDE_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/IncludeFiles/C) 36 | set(MATHLINK_INCLUDE_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/MacOSX-x86-64/CompilerAdditions) 37 | set(MATHLINK_LIB_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/MacOSX-x86-64/CompilerAdditions) 38 | else() 39 | set(WOLFRAMKERNEL_DEFAULT ${MATHEMATICA_INSTALL_DIR}/Executables/WolframKernel) 40 | set(WOLFRAMLIBRARY_INCLUDE_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/IncludeFiles/C) 41 | set(MATHLINK_INCLUDE_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions) 42 | set(MATHLINK_LIB_DIR_DEFAULT ${MATHEMATICA_INSTALL_DIR}/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions) 43 | endif() 44 | 45 | macro(CheckWolframKernel) 46 | 47 | if(NOT EXISTS ${WOLFRAMKERNEL}) 48 | message(FATAL_ERROR "WOLFRAMKERNEL does not exist. WOLFRAMKERNEL: ${WOLFRAMKERNEL}") 49 | endif() 50 | 51 | # 52 | # get $Version 53 | # 54 | execute_process( 55 | COMMAND 56 | ${WOLFRAMKERNEL} -noinit -noprompt -nopaclet -nostartuppaclets -runfirst Pause[${KERNEL_PAUSE}]\;Print[OutputForm[$Version]]\;Exit[] 57 | OUTPUT_VARIABLE 58 | VERSION 59 | OUTPUT_STRIP_TRAILING_WHITESPACE 60 | WORKING_DIRECTORY 61 | ${PROJECT_SOURCE_DIR} 62 | TIMEOUT 63 | ${KERNEL_TIMEOUT} 64 | RESULT_VARIABLE 65 | VERSION_RESULT 66 | ) 67 | 68 | message(STATUS "VERSION: ${VERSION}") 69 | 70 | if(NOT ${VERSION_RESULT} EQUAL "0") 71 | message(WARNING "Bad exit code from Version script: ${VERSION_RESULT}; Continuing") 72 | endif() 73 | 74 | # 75 | # get $VersionNumber 76 | # 77 | execute_process( 78 | COMMAND 79 | ${WOLFRAMKERNEL} -noinit -noprompt -nopaclet -nostartuppaclets -runfirst Pause[${KERNEL_PAUSE}]\;Print[OutputForm[Floor[100\ $VersionNumber\ +\ $ReleaseNumber]]]\;Exit[] 80 | OUTPUT_VARIABLE 81 | VERSION_NUMBER 82 | OUTPUT_STRIP_TRAILING_WHITESPACE 83 | WORKING_DIRECTORY 84 | ${PROJECT_SOURCE_DIR} 85 | TIMEOUT 86 | ${KERNEL_TIMEOUT} 87 | RESULT_VARIABLE 88 | VERSION_NUMBER_RESULT 89 | ) 90 | 91 | message(STATUS "VERSION_NUMBER: ${VERSION_NUMBER}") 92 | 93 | if(NOT ${VERSION_NUMBER} GREATER_EQUAL 1100) 94 | message(FATAL_ERROR "Wolfram Kernel must be at least version 11.0: ${VERSION_NUMBER}") 95 | endif() 96 | 97 | if(NOT ${VERSION_NUMBER_RESULT} EQUAL "0") 98 | message(WARNING "Bad exit code from VersionNumber script: ${VERSION_NUMBER_RESULT}; Continuing") 99 | endif() 100 | 101 | # 102 | # get $SystemID 103 | # 104 | execute_process( 105 | COMMAND 106 | ${WOLFRAMKERNEL} -noinit -noprompt -nopaclet -nostartuppaclets -runfirst Pause[${KERNEL_PAUSE}]\;Print[OutputForm[$SystemID]]\;Exit[] 107 | OUTPUT_VARIABLE 108 | SYSTEMID 109 | OUTPUT_STRIP_TRAILING_WHITESPACE 110 | WORKING_DIRECTORY 111 | ${PROJECT_SOURCE_DIR} 112 | TIMEOUT 113 | ${KERNEL_TIMEOUT} 114 | RESULT_VARIABLE 115 | SYSTEMID_RESULT 116 | ) 117 | 118 | message(STATUS "SYSTEMID: ${SYSTEMID}") 119 | 120 | if(NOT ${SYSTEMID_RESULT} EQUAL "0") 121 | message(WARNING "Bad exit code from SystemID script: ${SYSTEMID_RESULT}; Continuing") 122 | endif() 123 | 124 | # 125 | # get $SystemWordLength 126 | # 127 | execute_process( 128 | COMMAND 129 | ${WOLFRAMKERNEL} -noinit -noprompt -nopaclet -nostartuppaclets -runfirst Pause[${KERNEL_PAUSE}]\;Print[OutputForm[$SystemWordLength]]\;Exit[] 130 | OUTPUT_VARIABLE 131 | SYSTEMWORDLENGTH 132 | OUTPUT_STRIP_TRAILING_WHITESPACE 133 | WORKING_DIRECTORY 134 | ${PROJECT_SOURCE_DIR} 135 | TIMEOUT 136 | ${KERNEL_TIMEOUT} 137 | RESULT_VARIABLE 138 | SYSTEMWORDLENGTH_RESULT 139 | ) 140 | 141 | message(STATUS "SYSTEMWORDLENGTH: ${SYSTEMWORDLENGTH}") 142 | 143 | if(NOT ${SYSTEMWORDLENGTH_RESULT} EQUAL "0") 144 | message(WARNING "Bad exit code from SystemWordLength script: ${SYSTEMWORDLENGTH_RESULT}; Continuing") 145 | endif() 146 | 147 | # 148 | # Make sure that CMake and Mathematica agree about 32-bit or 64-bit 149 | # 150 | if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "") 151 | # CMAKE_SIZEOF_VOID_P is not set; CXX is probably not enabled 152 | elseif(${CMAKE_SIZEOF_VOID_P} EQUAL 4) 153 | if(NOT ${SYSTEMWORDLENGTH} EQUAL 32) 154 | message(FATAL_ERROR 155 | "CMake is reporting 32-bit; Mathematica is reporting: ${SYSTEMWORDLENGTH}\n" 156 | "HINT: On Windows, you probably need to specify -A x64" 157 | ) 158 | endif() 159 | elseif(${CMAKE_SIZEOF_VOID_P} EQUAL 8) 160 | if(NOT ${SYSTEMWORDLENGTH} EQUAL 64) 161 | message(FATAL_ERROR "CMake is reporting 64-bit; Mathematica is reporting: ${SYSTEMWORDLENGTH}") 162 | endif() 163 | else() 164 | message(FATAL_ERROR "CMake is reporting neither 32-bit nor 64-bit. CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}") 165 | endif() 166 | 167 | endmacro(CheckWolframKernel) 168 | -------------------------------------------------------------------------------- /Tests/LineBreaking.mt: -------------------------------------------------------------------------------- 1 | 2 | Needs["CodeFormatter`"] 3 | 4 | str = " 5 | $notebookStatusToWeight = { 6 | \"ObsoleteFlag\" -> 0.000001, (* don't use 10^-6 or Rational[...] will cause issues in the web deployed search index *) 7 | \"AwaitingFutureDesignReviewFlag\" -> .25, 8 | \"NewInOldVersion\" -> 0.00001, 9 | \"None\" -> 1. 10 | }; 11 | " 12 | 13 | (* 14 | Just verify that sanity check passed 15 | *) 16 | TestMatch[ 17 | CodeFormat[str, "LineWidth" -> 120] 18 | , 19 | _String 20 | , 21 | TestID->"LineBreaking-20200804-Z9K7C7" 22 | ] 23 | 24 | 25 | 26 | 27 | (* 28 | Handle complex continuations 29 | *) 30 | 31 | str = " 32 | LaunchKernels::unicore = \"The default parallel kernel configuration does not launch any kernels on a single-core machine.\\ 33 | \tUse LaunchKernels[n] to launch n kernels anyway.\" 34 | "; 35 | 36 | (* 37 | Just verify that sanity check passed 38 | *) 39 | TestMatch[ 40 | CodeFormat[str, "LineWidth" -> 120] 41 | , 42 | _String 43 | , 44 | TestID->"LineBreaking-20200804-J5A7C1" 45 | ] 46 | 47 | 48 | str = " 49 | {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, \\[Infinity]} 50 | "; 51 | 52 | (* 53 | Just verify that sanity check passed 54 | *) 55 | TestMatch[ 56 | CodeFormat[str, "LineWidth" -> 120] 57 | , 58 | _String 59 | , 60 | TestID->"LineBreaking-20200804-Z8Q2C4" 61 | ] 62 | 63 | 64 | str = "\"0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 \\\"\"" 65 | 66 | (* 67 | Just verify that sanity check passed 68 | *) 69 | TestMatch[ 70 | CodeFormat[str, "LineWidth" -> 120] 71 | , 72 | _String 73 | , 74 | TestID->"LineBreaking-20200804-U4J1K2" 75 | ] 76 | 77 | 78 | 79 | 80 | (* 81 | Harder to test splitting up long names 82 | 83 | Splitting up a long name does not result in bad syntax and sanity check failure 84 | 85 | So do a direct test for the continuation being placed before the long name 86 | *) 87 | str = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\[DiscretionaryParagraphSeparator]\""; 88 | 89 | Test[ 90 | Block[{CodeFormatter`LineBreakerV1`$AllowSplittingTokens = True}, 91 | CodeFormat[str, "LineWidth" -> 120, "BreakLinesMethod" -> "LineBreakerV1"] 92 | ] 93 | , 94 | "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\\n\\[DiscretionaryParagraphSeparator]\"" 95 | , 96 | TestID->"LineBreaking-20200805-A0T7P8" 97 | ] 98 | 99 | Test[ 100 | CodeFormat[str, "LineWidth" -> 120, "BreakLinesMethod" -> "LineBreakerV2"] 101 | , 102 | "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\[DiscretionaryParagraphSeparator]\"" 103 | , 104 | TestID->"LineBreaking-20211007-X4E1B8" 105 | ] 106 | 107 | 108 | 109 | 110 | (* 111 | Make sure to handle multiple escapes 112 | *) 113 | str = "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\\"\\\"\"" 114 | 115 | TestMatch[ 116 | CodeFormat[str, "LineWidth" -> 120] 117 | , 118 | _String 119 | , 120 | TestID->"LineBreaking-20200805-S2Y1J5" 121 | ] 122 | 123 | 124 | 125 | (* 126 | Test breaking on \[ 127 | *) 128 | str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + \\[FormalX]" 129 | 130 | TestMatch[ 131 | CodeFormat[str, "LineWidth" -> 120] 132 | , 133 | _String 134 | , 135 | TestID->"LineBreaking-20200805-W8K3O3" 136 | ] 137 | 138 | (* 139 | Test breaking on \: 140 | *) 141 | str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + \\:03b1" 142 | 143 | TestMatch[ 144 | CodeFormat[str, "LineWidth" -> 120] 145 | , 146 | _String 147 | , 148 | TestID->"LineBreaking-20200810-T5K2E1" 149 | ] 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | (* 158 | Test breaking on implicit Times 159 | *) 160 | 161 | str = "(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b)" 162 | 163 | TestMatch[ 164 | Block[{CodeFormatter`Private`$DisableSanityChecking = True, 165 | CodeFormatter`LineBreakerV1`$AllowSplittingTokens = True}, 166 | CodeFormat[str, "LineWidth" -> 120, "BreakLinesMethod" -> "LineBreakerV1"] 167 | ] 168 | , 169 | "(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 170 | b)" 171 | , 172 | {} 173 | , 174 | TestID->"LineBreaking-20200805-S9F1H1" 175 | ] 176 | 177 | 178 | 179 | 180 | (* 181 | Test nested comments 182 | *) 183 | TestMatch[ 184 | CodeFormat["(*(**)*)", "LineWidth" -> 5] 185 | , 186 | _String 187 | , 188 | TestID->"LineBreaking-20200808-F9R3L9" 189 | ] 190 | 191 | 192 | 193 | (* 194 | Test sequences of longnames 195 | *) 196 | 197 | str = 198 | " 199 | lowcaseQ := 200 | StringMatchQ[s, RegularExpression[\"[\\[Alpha]\\[Beta]\\[Gamma]\\[Delta]\\[Epsilon]\\[CurlyKappa]\\[Lambda]\\[Mu]\\[Nu]\\[Xi]\\[Omicron]\\[Pi]\\[CurlyPi]\\[Rho]\\[Chi]\\[Psi]]\"]] 201 | " 202 | 203 | TestMatch[ 204 | CodeFormat[str, "LineWidth" -> 40, "SafetyMargin" -> 10, Airiness -> 0] 205 | , 206 | _String 207 | , 208 | TestID->"LineBreaking-20200811-V0V3B3" 209 | ] 210 | 211 | 212 | 213 | 214 | (* 215 | bug 406342 216 | 217 | was breaking as: 218 | 219 | resourcePublisherNameSpaceFreeQ[name_String] := 220 | With[{ns = DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo 221 | [][\"Publishers\"]]}, 222 | !MatchQ[name, Alternatives @@ ns] 223 | ] 224 | 225 | *) 226 | Test[ 227 | CodeFormat[ 228 | "resourcePublisherNameSpaceFreeQ[name_String] := 229 | With[{ns = DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[][\"Publishers\"]]}, 230 | !MatchQ[name, Alternatives @@ ns] 231 | ]", "LineWidth" -> 80, "BreakLinesMethod" -> "LineBreakerV1"] 232 | , 233 | "resourcePublisherNameSpaceFreeQ[name_String] := 234 | With[{ns = DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[ 235 | ][\"Publishers\"]]}, 236 | !MatchQ[name, Alternatives @@ ns] 237 | ]" 238 | , 239 | TestID->"LineBreaking-20210304-N7V4P5" 240 | ] 241 | 242 | Test[ 243 | CodeFormat[ 244 | "resourcePublisherNameSpaceFreeQ[name_String] := 245 | With[{ns = DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[][\"Publishers\"]]}, 246 | !MatchQ[name, Alternatives @@ ns] 247 | ]", "LineWidth" -> 80, "BreakLinesMethod" -> "LineBreakerV2"] 248 | , 249 | "resourcePublisherNameSpaceFreeQ[name_String] := 250 | With[ 251 | { 252 | ns 253 | = 254 | DeleteMissing[publisherResourceNameSpace /@ allPublisherInfo[][\"Publishers\"]] 255 | } 256 | , 257 | !MatchQ[name, Alternatives @@ ns] 258 | ]" 259 | , 260 | TestID->"LineBreaking-20210304-N7V4P5" 261 | ] 262 | 263 | 264 | 265 | 266 | (* 267 | ensure that ]] are not broken 268 | *) 269 | Test[ 270 | CodeFormat["aaaaaaaa[[]]", "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV1"] 271 | , 272 | "aaaaaaaa[[]]" 273 | , 274 | TestID->"LineBreaking-20211007-T6O3Z1" 275 | ] 276 | 277 | Test[ 278 | CodeFormat["aaaaaaaa[[]]", "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV2"] 279 | , 280 | "aaaaaaaa[[]]" 281 | , 282 | TestID->"LineBreaking-20211007-N3A7W7" 283 | ] 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /CodeFormatter/Kernel/Standardize.wl: -------------------------------------------------------------------------------- 1 | BeginPackage["CodeFormatter`Standardize`"] 2 | 3 | StandardizeEmbeddedNewlines 4 | 5 | StandardizeEmbeddedTabs 6 | 7 | Begin["`Private`"] 8 | 9 | Needs["CodeFormatter`"] 10 | Needs["CodeFormatter`Utils`"] 11 | Needs["CodeParser`"] 12 | Needs["CodeParser`Utils`"] 13 | 14 | 15 | StandardizeEmbeddedNewlines::usage = "StandardizeEmbeddedNewlines[cst, newline] standardizes the newlines in cst." 16 | 17 | StandardizeEmbeddedNewlines[cstIn:CallNode[_, _, _], newline_String] := 18 | Catch[ 19 | Module[{cst, data}, 20 | 21 | cst = cstIn; 22 | 23 | data = cst[[3]]; 24 | 25 | (* 26 | Only proceed if LineColumn convention 27 | *) 28 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 29 | Throw[cst] 30 | ]; 31 | 32 | standardizeEmbeddedNewlines[cst, newline] 33 | ]] 34 | 35 | StandardizeEmbeddedNewlines[cstIn:_[_, _String, _], newline_String] := 36 | Catch[ 37 | Module[{cst, data}, 38 | 39 | cst = cstIn; 40 | 41 | data = cst[[3]]; 42 | 43 | (* 44 | Only proceed if LineColumn convention 45 | *) 46 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 47 | Throw[cst] 48 | ]; 49 | 50 | standardizeEmbeddedNewlines[cst, newline] 51 | ]] 52 | 53 | StandardizeEmbeddedNewlines[cstIn:_[_, _List, _], newline_String] := 54 | Catch[ 55 | Module[{cst, data, firstChild, firstChildData}, 56 | 57 | cst = cstIn; 58 | 59 | data = cst[[3]]; 60 | 61 | If[empty[cst[[2]]], 62 | Throw[cst] 63 | ]; 64 | 65 | firstChild = cst[[2, 1]]; 66 | 67 | If[MissingQ[firstChild], 68 | Throw[cst] 69 | ]; 70 | 71 | (* 72 | cst may be a ContainerNode with no source info 73 | so look at first child to determine source convention 74 | *) 75 | firstChildData = firstChild[[3]]; 76 | 77 | (* 78 | Only proceed if LineColumn convention 79 | *) 80 | If[!MatchQ[firstChildData, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 81 | Throw[cst] 82 | ]; 83 | 84 | standardizeEmbeddedNewlines[cst, newline] 85 | ]] 86 | 87 | standardizeEmbeddedNewlines[cstIn_, newline_String] := 88 | Catch[ 89 | Module[{cst, data, embeddedNewlines, mapSpecs, tuples, poss, tokStartLocs, grouped}, 90 | 91 | cst = cstIn; 92 | 93 | data = cst[[3]]; 94 | 95 | If[KeyExistsQ[data, "EmbeddedNewlines"], 96 | 97 | (* 98 | -5 is where LeafNode[xxx, xxx, <|Source->{{1,1},{1,1}}|>] is 99 | 100 | -3 is where LeafNode[xxx, xxx, <||>] is 101 | 102 | There may be LeafNodes in metadata such as SyntaxIssues or AbstractSyntaxIssues, so remove those 103 | 104 | Line continuations in multiline strings and multiline comments will be handled later 105 | *) 106 | poss = Position[cst, LeafNode[_, _, _], {-5, -3}]; 107 | poss = Cases[poss, {___Integer}]; 108 | 109 | tokStartLocs = #[[3, Key[Source], 1]]& /@ Extract[cst, poss]; 110 | 111 | (* 112 | Group by starting SourceLocation 113 | *) 114 | grouped = GroupBy[Transpose[{tokStartLocs, poss}, {2, 1}], #[[1]]&]; 115 | 116 | embeddedNewlines = data["EmbeddedNewlines"]; 117 | 118 | (* 119 | FIXME: use Lookup[] 120 | *) 121 | mapSpecs = Map[ 122 | Function[{newlineLoc}, 123 | 124 | tuples = grouped[newlineLoc]; 125 | 126 | (* 127 | The token associated with this location may have been abstracted away and now missing 128 | *) 129 | If[!MissingQ[tuples], 130 | tuples 131 | , 132 | Nothing 133 | ] 134 | ] 135 | , 136 | embeddedNewlines 137 | ]; 138 | 139 | mapSpecs = Flatten[mapSpecs, 1]; 140 | 141 | cst = MapAt[convertEmbeddedNewlines[#, "FormatOnly" -> True, "NewlineString" -> newline]&, cst, mapSpecs[[All, 2]]]; 142 | 143 | (* 144 | 145 | To be used later 146 | 147 | KeyDropFrom[data, "EmbeddedNewlines"]; 148 | *) 149 | 150 | cst[[3]] = data; 151 | ]; 152 | 153 | cst 154 | ]] 155 | 156 | 157 | 158 | StandardizeEmbeddedTabs::usage = "StandardizeEmbeddedTabs[cst, newline, tabWidth] standardizes tabs in cst." 159 | 160 | StandardizeEmbeddedTabs[cstIn:CallNode[_, _, _], newline_String, tabWidth_Integer] := 161 | Catch[ 162 | Module[{cst, data}, 163 | 164 | cst = cstIn; 165 | 166 | data = cst[[3]]; 167 | 168 | (* 169 | Only proceed if LineColumn convention 170 | *) 171 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 172 | Throw[cst] 173 | ]; 174 | 175 | standardizeEmbeddedTabs[cst, newline, tabWidth] 176 | ]] 177 | 178 | StandardizeEmbeddedTabs[cstIn:_[_, _String, _], newline_String, tabWidth_Integer] := 179 | Catch[ 180 | Module[{cst, data}, 181 | 182 | cst = cstIn; 183 | 184 | data = cst[[3]]; 185 | 186 | (* 187 | Only proceed if LineColumn convention 188 | *) 189 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 190 | Throw[cst] 191 | ]; 192 | 193 | standardizeEmbeddedTabs[cst, newline, tabWidth] 194 | ]] 195 | 196 | StandardizeEmbeddedTabs[cstIn:_[_, _List, _], newline_String, tabWidth_Integer] := 197 | Catch[ 198 | Module[{cst, data, firstChild, firstChildData}, 199 | 200 | cst = cstIn; 201 | 202 | data = cst[[3]]; 203 | 204 | If[empty[cst[[2]]], 205 | Throw[cst] 206 | ]; 207 | 208 | firstChild = cst[[2, 1]]; 209 | 210 | If[MissingQ[firstChild], 211 | Throw[cst] 212 | ]; 213 | 214 | (* 215 | cst may be a ContainerNode with no source info 216 | so look at first child to determine source convention 217 | *) 218 | firstChildData = firstChild[[3]]; 219 | 220 | (* 221 | Only proceed if LineColumn convention 222 | *) 223 | If[!MatchQ[firstChildData, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 224 | Throw[cst] 225 | ]; 226 | 227 | standardizeEmbeddedTabs[cst, newline, tabWidth] 228 | ]] 229 | 230 | standardizeEmbeddedTabs[cstIn_, newline_String, tabWidth_Integer] := 231 | Catch[ 232 | Module[{cst, data, embeddedTabs, mapSpecs, tuples, poss, tokStartLocs, grouped}, 233 | 234 | cst = cstIn; 235 | 236 | data = cst[[3]]; 237 | 238 | If[KeyExistsQ[data, "EmbeddedTabs"], 239 | 240 | (* 241 | -5 is where LeafNode[xxx, xxx, <|Source->{{1,1},{1,1}}|>] is 242 | 243 | -3 is where LeafNode[xxx, xxx, <||>] is 244 | 245 | There may be LeafNodes in metadata such as SyntaxIssues or AbstractSyntaxIssues, so remove those 246 | 247 | Line continuations in multiline strings and multiline comments will be handled later 248 | *) 249 | poss = Position[cst, LeafNode[_, _, _], {-5, -3}]; 250 | poss = Cases[poss, {___Integer}]; 251 | 252 | tokStartLocs = #[[3, Key[Source], 1]]& /@ Extract[cst, poss]; 253 | 254 | (* 255 | Group by starting SourceLocation 256 | *) 257 | grouped = GroupBy[Transpose[{tokStartLocs, poss}, {2, 1}], #[[1]]&]; 258 | 259 | embeddedTabs = data["EmbeddedTabs"]; 260 | 261 | (* 262 | FIXME: use Lookup[] 263 | *) 264 | mapSpecs = Map[ 265 | Function[{newlineLoc}, 266 | 267 | tuples = grouped[newlineLoc]; 268 | 269 | (* 270 | The token associated with this location may have been abstracted away and now missing 271 | *) 272 | If[!MissingQ[tuples], 273 | tuples 274 | , 275 | Nothing 276 | ] 277 | ] 278 | , 279 | embeddedTabs 280 | ]; 281 | 282 | mapSpecs = Flatten[mapSpecs, 1]; 283 | 284 | cst = MapAt[convertEmbeddedTabs[#, "FormatOnly" -> True, "NewlineString" -> newline, "TabWidth" -> tabWidth]&, cst, mapSpecs[[All, 2]]]; 285 | 286 | (* 287 | 288 | To be used later 289 | 290 | KeyDropFrom[data, "EmbeddedTabs"]; 291 | *) 292 | 293 | cst[[3]] = data; 294 | ]; 295 | 296 | cst 297 | ]] 298 | 299 | 300 | End[] 301 | 302 | EndPackage[] 303 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 1.9 - XX Dec, 2022 3 | 4 | 5 | ## 1.8 - 10 Oct, 2022 6 | 7 | Format `1 / 0` as `1/0`. 8 | 9 | Special casing for `Test[input, expected, TestID -> xxx]`. 10 | 11 | Prevent palette from becoming blank when removing the last preset. 12 | 13 | ### Fixes 14 | 15 | Fix 427231: CodeFormat on empty quotes throws many errors. 16 | 17 | Fix bug where "Save as Present" button is outside the draw area of the palette. 18 | 19 | 20 | ## 1.7 - 4 July, 2022 21 | 22 | Update various places based on changes in CodeParser 23 | 24 | 25 | ## 1.6 - 12 May, 2022 26 | 27 | Format WithCleanup 28 | 29 | 30 | ### Fixes 31 | 32 | Fix 421585: `CodeFormatCST[LeafNode[...]]` fails 33 | 34 | partial fix for 422410: CodeFormatter has trouble with generated code 35 | 36 | 37 | ## 1.5.1 - 28 Mar, 2022 38 | 39 | ### Fixes 40 | 41 | Fixes 421036: asynchronous initialization of Package Editor toolbar 42 | 43 | 44 | ## 1.5 - 7 Mar, 2022 45 | 46 | Treat `a // b // c` as single InfixSlashSlash node and format as: 47 | ``` 48 | a // 49 | b // 50 | c 51 | ``` 52 | 53 | https://github.com/WolframResearch/codeformatter/issues/2 54 | 55 | Add undocumented flag $CleanLexicalVariables 56 | 57 | Implement 419551: special formatting for LibraryFunction 58 | 59 | 60 | ### Fixes 61 | 62 | Fix 417300: CodeFormatCST strips last token of child node 63 | 64 | Fix 416665: fix the SaveAs dialog's window height on MacOS 65 | 66 | Fix 417935: `"Break in scoping structures" -> Always` did not have proper effect 67 | 68 | Also fix `"Break in control structures" -> Always` 69 | 70 | Fix 419285: Cannot format single tokens 71 | 72 | Fix 402825: let the palette and package toolbars communicate updates 73 | 74 | Fix 418025: impose stronger condition on appearance of Update button 75 | 76 | Fix unreported bug $DisableSanityChecking was not being respected in box formatting 77 | 78 | Bugfix 418228: formatter marked dirty after reset 79 | 80 | Two issues with the key "FormatMethod". 81 | * 1. it was not properly set during initialization so did not take the value of any existing preset. 82 | * 2. when a preset resets, the key also did not take the preset value 83 | 84 | Fix 420185: Cannot format `a~b~c` in a notebook 85 | 86 | 87 | ## 1.4 - 25 Oct, 2021 88 | 89 | Do not allow PacletManager to participate in finding \`Generate\` files 90 | 91 | 92 | Palette Feature 1: Airiness Slider or Newline Togglers 93 | 94 | The airiness of code formatting is controlled via the familiar slider 95 | control or a new set of toggler controls. Both update the same 96 | underlying options. The slider can be considered a coarse-grained control 97 | while the togglers individually target various newline formatting 98 | options. These fine-grained options include visualizations and tooltips 99 | to describe the formatting behavior that they modify. 100 | 101 | Palette Feature 2: Presets 102 | 103 | The state of the formatting options can be saved between desktop 104 | sessions and recalled at a later time. Multiple formatting presets can 105 | be saved. The most recently selected preset is remembered automatically 106 | between desktop sessions. Presets can be updated to match the current 107 | state of the formatting options. The indentation style is included as 108 | part of the preset. 109 | 110 | Naming and editing the presets is done via subdialogs because editable 111 | InputFields are not allowed in floating palettes. 112 | 113 | Implementation Details: 114 | 115 | * some formatting controls exist in the Package and Script editors. The 116 | highlight colors are controlled from "CodeFormatterHighlightColor" 117 | styles defined in the paclet's Package.nb and Script.nb stylesheets 118 | * speech bubble popups are modeled after those from Drawing Tools 119 | * the palette state is saved between sessions via key-values stored in 120 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", 121 | "CodeFormat"}] 122 | 123 | 124 | Abstract the outer `[]` and inner `[]` groups into a single `[[]]` group 125 | 126 | Ensure that `]]` are not broken 127 | 128 | 129 | Canonicalize input before indenting 130 | 131 | Rely on graphical syntax; no more reliance on whitespace / newlines that were passed in 132 | 133 | 2 stages: top-down indenting, then symbolic rendering 134 | 135 | 136 | If `;` is last in CompoundExpression, then rename to something special 137 | 138 | Rename Token\`Semi -> Token\`Fake\`SemiBeforeImplicitNull 139 | 140 | Allow for easier processing later 141 | 142 | 143 | Add LineBreakerV2 method, off by default 144 | 145 | 146 | ### Fixes 147 | 148 | Related to 413985: CodeFormat does not handle empty list of bytes 149 | 150 | Return unevaluated for now 151 | 152 | 153 | Explicitly handle `CodeFormat[{}]` 154 | 155 | Fix 414042: `&` on newline giving linter warnings 156 | 157 | Fix unreported bug: malformed Whitespace could be generated in certain edge cases 158 | 159 | Fix problems with merging temporary line continuations 160 | 161 | Fix 415177: ``"Get::noopen: Cannot open Forms`." `` message when building 162 | 163 | Fix 415178: spurious DynamicImageSize warning 164 | 165 | Handle selecting Section cells and pressing `"Format Cell"` 166 | 167 | Add a menu position for the CodeFormatter palette so that the Code related palettes appear in their own section of the Palettes menu. 168 | 169 | 170 | ## 1.3 - 30 Aug, 2021 171 | 172 | Notes on compatibility have been added to docs/compatibility.md 173 | 174 | Change default Airiness to be Automatic 175 | 176 | 177 | ## 1.2 - 25 Mar, 2021 178 | 179 | Give slightly saner error message when CodeFormat is given bad arguments 180 | 181 | Renaming various `"Newline"` things to `"NewlineString"` and `"CompoundExpressions"` things to `"Semicolons"` 182 | 183 | Allow `f[` to be formatted in FE 184 | 185 | Massive refactoring effort for 12.3 186 | 187 | Refactor different "passes" into their own files 188 | 189 | Canonicalize what the formatter outputs as not caring about newlines and whitespace that it is given (except in some cases with comments where we care) 190 | 191 | 192 | ### Fixes 193 | 194 | Fix formatting `a_b ..` 195 | 196 | Fix bug that resulted in multiple newlines being inserted 197 | 198 | Demonstrate bug by doing: 199 | ``` 200 | CodeFormat[" 201 | f[ 202 | 1 203 | , 204 | 2 205 | ] 206 | ", "NewlinesBetweenCommas" -> Insert] 207 | ``` 208 | 209 | and see that there are multiple newlines inserted. 210 | 211 | Fix 404196, bad formatting of CompoundExpression in places 212 | 213 | Fix 399281, formatter was not handling grouped cells 214 | 215 | Fix 406342, line breaking and having: 216 | ``` 217 | { 218 | f 219 | [] 220 | } 221 | ``` 222 | is weird 223 | 224 | 225 | ## 1.1.1 - 8 Dec, 2020 226 | 227 | Included in Mathematica 12.2 228 | 229 | ### Fixes 230 | 231 | The formatter side of what needs to be fixed for 398836: Code Formatting palette turns `a b` into `ab` 232 | 233 | Remove excess implicit Times tokens where needed. 234 | 235 | Also make sure to do sanity checking on formatting cells. This will prevent any bugs in the formatter from propagating errors into user source code. 236 | 237 | Fix CodeFormat mis-formatting last comma in Switch 238 | 239 | Convert string literals in palette to use FrontEndResource 240 | 241 | Add text resources to be used by FE 242 | 243 | Make sure to only use the FE text resources as labels for the radio button bar 244 | 245 | The actual values should still be "tab" and "space" 246 | 247 | Translation of CodeFormatting palette's strings 248 | 249 | Adding simplified Chinese strings 250 | 251 | Redesign CodeFormatter settings DockedCell and palette 252 | 253 | Handle integer Airiness values of -1, 0, 1 254 | 255 | - Increased the width of the Indentation Menu to fit the Spanish resource `"TabMenuItem" -> "Tabulaciones"` in CodeFormatter.tr 256 | - Fixed a misalignment of the Indentation Menu in the formatter toolbar. 257 | - Reduced the margin at the top of the formatter palette (looks a bit neater now). 258 | 259 | Start adding individual style options, and have Airiness resolve to these options 260 | 261 | Fix the conflation of level == 0 with being top-level 262 | 263 | I was testing level == 0 when formatting CompoundExpressions as a way to prevent newlines from being inserted (CompoundExpressions on a single line at top-level need to stay on a single line) 264 | 265 | Introduce a new symbol $Toplevel and control that appropriately 266 | 267 | Provide a message for when a cell cannot currently be formatted 268 | 269 | 270 | ## 1.1 - 30 Sep, 2020 271 | 272 | Initial public release 273 | 274 | ### API 275 | 276 | Added CodeFormat function 277 | 278 | Added Code Formatting palette 279 | 280 | Added `"Format Cell"` button to Package Editor toolbar 281 | -------------------------------------------------------------------------------- /Tests/CodeFormatterTestUtils/CodeFormatterTestUtils.wl: -------------------------------------------------------------------------------- 1 | BeginPackage["CodeFormatterTestUtils`"] 2 | 3 | formatTest 4 | 5 | formatPackageEditorTest 6 | 7 | Begin["`Private`"] 8 | 9 | Needs["CodeFormatter`"] 10 | Needs["CodeFormatter`Notebooks`"] 11 | Needs["CodeParser`"] 12 | Needs["CodeParser`Utils`"] 13 | Needs["PacletManager`"] 14 | 15 | 16 | Options[formatTest] = { 17 | "FileNamePrefixPattern" -> "", 18 | "FileSizeLimit" -> {0, Infinity}, 19 | "DryRun" -> False, 20 | "LineWidth" -> 120, 21 | "SafetyMargin" -> 10, 22 | Airiness -> 0, 23 | PerformanceGoal -> "Speed" 24 | } 25 | 26 | formatTest[file_String, i_Integer, OptionsPattern[]] := 27 | Catch[ 28 | Module[{dryRun, prefix, limit, res, lineWidth, airiness, margin, performanceGoal, actual, firstLine, implicitTimesInserted}, 29 | 30 | prefix = OptionValue["FileNamePrefixPattern"]; 31 | limit = OptionValue["FileSizeLimit"]; 32 | dryRun = OptionValue["DryRun"]; 33 | lineWidth = OptionValue["LineWidth"]; 34 | margin = OptionValue["SafetyMargin"]; 35 | airiness = OptionValue[Airiness]; 36 | performanceGoal = OptionValue[PerformanceGoal]; 37 | 38 | If[$Debug, Print["file1: ", File[file]]]; 39 | 40 | If[FileType[file] === File, 41 | If[FileByteCount[file] > limit[[2]], 42 | Throw[Null] 43 | ]; 44 | If[FileByteCount[file] < limit[[1]], 45 | Throw[Null] 46 | ]; 47 | ]; 48 | 49 | (* 50 | figure out if first line is special 51 | *) 52 | If[FileByteCount[file] > 0, 53 | Quiet[ 54 | (* 55 | Importing a file containing only \n gives a slew of different messages and fails 56 | bug 363161 57 | Remove this Quiet when bug is resolved 58 | *) 59 | firstLine = Import[file, {"Lines", 1}]; 60 | If[FailureQ[firstLine], 61 | firstLine = ""; 62 | ] 63 | ]; 64 | Which[ 65 | (* special encoded file format *) 66 | StringMatchQ[firstLine, "(*!1"~~("A"|"B"|"C"|"D"|"H"|"I"|"N"|"O")~~"!*)mcm"], 67 | Throw[Failure["EncodedFile", <|"File" -> File[file]|>]] 68 | , 69 | (* wl script *) 70 | StringStartsQ[firstLine, "#!"], 71 | Throw[Failure["WLScript", <|"File" -> File[file]|>]] 72 | ]; 73 | ]; 74 | 75 | Check[ 76 | Check[ 77 | 78 | implicitTimesInserted = False; 79 | CodeFormatter`Private`$LastExtent = Indeterminate; 80 | 81 | res = CodeFormat[File[file], "LineWidth" -> lineWidth, "SafetyMargin" -> margin, Airiness -> airiness, PerformanceGoal -> performanceGoal]; 82 | , 83 | implicitTimesInserted = True; 84 | , 85 | {CodeFormat::implicittimesaftercontinuation} 86 | ]; 87 | 88 | If[FailureQ[res], 89 | 90 | If[implicitTimesInserted, 91 | Throw[Failure["ImplicitTimesAfterLineContinuation", <|"File" -> File[file]|>]] 92 | ]; 93 | Print[ 94 | Style[Row[{"index: ", i, " ", File[file]}], 95 | Red]]; 96 | Print[Style[Row[{"index: ", i, " ", res}], Red]]; 97 | Throw[res, "Uncaught"] 98 | ]; 99 | 100 | If[!StringQ[res], 101 | Print[ 102 | Style[Row[{"index: ", i, " ", File[file]}], 103 | Red]]; 104 | Throw[res, "UncaughtNotAString"] 105 | ]; 106 | 107 | If[dryRun === False, 108 | Export[file, res, "Text"] 109 | ]; 110 | 111 | If[CodeFormatter`Private`$LastExtent === CodeFormatter`Private`$OutOfDate, 112 | Throw[Failure["OutOfDateExtent", <|"File" -> File[file]|>]] 113 | ]; 114 | 115 | If[!MatchQ[CodeFormatter`Private`$LastExtent, {_Integer, _Integer, _Integer, _Integer}], 116 | Print[ 117 | Style[Row[{"index: ", i, " ", File[file]}], 118 | Red]]; 119 | Throw[CodeFormatter`Private`$LastExtent, "UncaughtBadExtent"] 120 | ]; 121 | 122 | actual = actualExtent[res]; 123 | If[!(actual[[1]] === CodeFormatter`Private`$LastExtent[[1]] && actual[[2]] === CodeFormatter`Private`$LastExtent[[2]]), 124 | Print[ 125 | Style[Row[{"index: ", i, " ", File[file]}], 126 | Red]]; 127 | Throw[<| "PrecomputedExtent" -> CodeFormatter`Private`$LastExtent, "ActualExtent" -> actual|>, "UncaughtMiscalculatedExtent"] 128 | ] 129 | 130 | , 131 | If[!implicitTimesInserted, 132 | (* 133 | only fire if not handled by other check 134 | *) 135 | Print[ 136 | Style[Row[{"index: ", i, " ", File[file]}], 137 | Darker[Orange]]]; 138 | Print[ 139 | Style[$MessageList, Darker[Orange]]]; 140 | ]; 141 | ] 142 | ]] 143 | 144 | 145 | actualExtent[s_String] := 146 | Module[{split, width, height, firstWidth, lastWidth}, 147 | split = StringSplit[s, "\n", All]; 148 | width = Max[StringLength /@ split]; 149 | height = Length[split]; 150 | firstWidth = StringLength[First[split]]; 151 | lastWidth = StringLength[Last[split]]; 152 | {width, height, firstWidth, lastWidth} 153 | ] 154 | 155 | 156 | 157 | 158 | Options[formatPackageEditorTest] = { 159 | "FileNamePrefixPattern" -> "", 160 | "FileSizeLimit" -> {0, Infinity}, 161 | "DryRun" -> False, 162 | "LineWidth" -> 120, 163 | "SafetyMargin" -> 10, 164 | Airiness -> 0, 165 | PerformanceGoal -> "Speed" 166 | } 167 | 168 | formatPackageEditorTest[file_String, i_Integer, OptionsPattern[]] := 169 | Catch[ 170 | Module[{dryRun, prefix, limit, res, lineWidth, airiness, margin, nbObj, nb, cells, cell1, performaceGoal, firstLine, box}, 171 | 172 | prefix = OptionValue["FileNamePrefixPattern"]; 173 | limit = OptionValue["FileSizeLimit"]; 174 | dryRun = OptionValue["DryRun"]; 175 | lineWidth = OptionValue["LineWidth"]; 176 | margin = OptionValue["SafetyMargin"]; 177 | airiness = OptionValue[Airiness]; 178 | performaceGoal = OptionValue[PerformanceGoal]; 179 | 180 | If[$Debug, Print["file1: ", File[file]]]; 181 | 182 | If[FileType[file] === File, 183 | If[FileByteCount[file] > limit[[2]], 184 | Throw[Null] 185 | ]; 186 | If[FileByteCount[file] < limit[[1]], 187 | Throw[Null] 188 | ]; 189 | ]; 190 | 191 | (* 192 | figure out if first line is special 193 | *) 194 | If[FileByteCount[file] > 0, 195 | Quiet[ 196 | (* 197 | Importing a file containing only \n gives a slew of different messages and fails 198 | bug 363161 199 | Remove this Quiet when bug is resolved 200 | *) 201 | firstLine = Import[file, {"Lines", 1}]; 202 | If[FailureQ[firstLine], 203 | firstLine = ""; 204 | ] 205 | ]; 206 | Which[ 207 | (* special encoded file format *) 208 | StringMatchQ[firstLine, "(*!1"~~("A"|"B"|"C"|"D"|"H"|"I"|"N"|"O")~~"!*)mcm"], 209 | Throw[Failure["EncodedFile", <|"File" -> File[file]|>]] 210 | , 211 | (* wl script *) 212 | StringStartsQ[firstLine, "#!"], 213 | Throw[Failure["WLScript", <|"File" -> File[file]|>]] 214 | ]; 215 | ]; 216 | 217 | 218 | nbObj = NotebookOpen[file, Visible -> False]; 219 | nb = NotebookGet[nbObj]; 220 | 221 | cells = If[Length[#] >= 2 && #[[2]] == "Code", #, Nothing]& /@ nb[[1]]; 222 | 223 | 224 | Check[ 225 | Do[ 226 | 227 | (* 228 | work around very common FE bug 229 | 230 | Related bugs: 395301 231 | *) 232 | cell1 = FrontEndExecute[FrontEnd`ReparseBoxStructurePacket[cell]]; 233 | 234 | box = cell1[[1, 1]]; 235 | 236 | res = CodeFormatter`Notebooks`Private`formatInputContents[box]; 237 | 238 | If[FailureQ[res], 239 | 240 | If[TrueQ[implicitTimesInserted], 241 | Throw[Failure["ImplicitTimesAfterLineContinuation", <|"File" -> File[file]|>]] 242 | ]; 243 | 244 | If[MatchQ[res, _Failure] && res[[1]] == "SanityCheckFailed", 245 | If[MemberQ[sanityCheckExceptions, FileNameTake[file]], 246 | Print[ 247 | Style[Row[{"index: ", i, " ", File[file]}]]]; 248 | Print["sanity check exception"] 249 | ] 250 | ]; 251 | 252 | Print[ 253 | Style[Row[{"index: ", i, " ", File[file]}], 254 | Red]]; 255 | Print[Style[Row[{"index: ", i, " ", res}], Red]]; 256 | Throw[res, "Uncaught"] 257 | ]; 258 | 259 | (* If[!StringQ[res], 260 | Print[ 261 | Style[Row[{"index: ", i, " ", File[file]}], 262 | Red]]; 263 | Throw[res, "UncaughtNotAString"] 264 | ] *) 265 | , 266 | {cell, cells} 267 | ] 268 | , 269 | If[!TrueQ[implicitTimesInserted], 270 | (* 271 | only fire if not handled by other check 272 | *) 273 | Print[ 274 | Style[Row[{"index: ", i, " ", File[file]}], 275 | Darker[Orange]]]; 276 | Print[ 277 | Style[$MessageList, Darker[Orange]]]; 278 | ] 279 | ]; 280 | 281 | NotebookClose[nbObj]; 282 | 283 | res 284 | ]] 285 | 286 | 287 | sanityCheckExceptions = { 288 | 289 | (* 290 | FE parses trailing ; differently than kernel 291 | 292 | Related bugs: 400268 293 | *) 294 | "PlanetaryAstronomy.m" 295 | } 296 | 297 | 298 | End[] 299 | 300 | EndPackage[] 301 | -------------------------------------------------------------------------------- /Tests/Bugs.mt: -------------------------------------------------------------------------------- 1 | (* Wolfram Language Test file *) 2 | 3 | Needs["CodeFormatter`"] 4 | 5 | Needs["CodeParser`"] 6 | 7 | 8 | 9 | (* 10 | bug 398836 11 | *) 12 | cst = CodeConcreteParseBox[RowBox[{"a", " ", "b"}]] 13 | 14 | Test[ 15 | CodeFormatCST[cst] 16 | ,"a b\n" 17 | , 18 | TestID->"Bugs-20201016-T9R8A5" 19 | ] 20 | 21 | 22 | 23 | 24 | 25 | Test[ 26 | CodeFormat["1 + 2222222222222222222222222 + 3", "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV1"] 27 | , 28 | "1 + 2222222222222222222222222 + 29 | 3" 30 | , 31 | TestID->"Bugs-20201016-J1C9L9" 32 | ] 33 | 34 | 35 | 36 | 37 | 38 | (* 39 | Bug 404109 40 | *) 41 | 42 | input = ToString[Nest[f, x, 30], InputForm, PageWidth -> Infinity] 43 | 44 | Test[ 45 | CodeFormat[input, "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV1"] 46 | , 47 | "f[f[f[f[f[f[ 48 | f[f[f[f[f[f[ 49 | f[f[f[f[f[f[ 50 | f[f[f[f[f[f[ 51 | f[f[f[f[f[f[ 52 | x]]]]]]]]]] 53 | ]]]]]]]]]]] 54 | ]]]]]]]]]" 55 | , 56 | TestID->"Bugs-20210112-W2I4Y2" 57 | ] 58 | 59 | 60 | 61 | 62 | input = StringJoin["f[", Table["#1 ", {40}], "]"] 63 | 64 | Test[ 65 | CodeFormat[input, "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV1"] 66 | , 67 | "f[#1 #1 #1 68 | #1 #1 #1 #1 69 | #1 #1 #1 #1 70 | #1 #1 #1 #1 71 | #1 #1 #1 #1 72 | #1 #1 #1 #1 73 | #1 #1 #1 #1 74 | #1 #1 #1 #1 75 | #1 #1 #1 #1 76 | #1 #1 #1 #1 77 | #1]" 78 | , 79 | TestID->"Bugs-20210112-U7K8N2" 80 | ] 81 | 82 | 83 | (* 84 | is this strange? 85 | *) 86 | Test[ 87 | CodeFormat[input, "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV2"] 88 | , 89 | "f[ 90 | #1 91 | 92 | #1 93 | 94 | #1 95 | 96 | #1 97 | 98 | #1 99 | 100 | #1 101 | 102 | #1 103 | 104 | #1 105 | 106 | #1 107 | 108 | #1 109 | 110 | #1 111 | 112 | #1 113 | 114 | #1 115 | 116 | #1 117 | 118 | #1 119 | 120 | #1 121 | 122 | #1 123 | 124 | #1 125 | 126 | #1 127 | 128 | #1 129 | 130 | #1 131 | 132 | #1 133 | 134 | #1 135 | 136 | #1 137 | 138 | #1 139 | 140 | #1 141 | 142 | #1 143 | 144 | #1 145 | 146 | #1 147 | 148 | #1 149 | 150 | #1 151 | 152 | #1 153 | 154 | #1 155 | 156 | #1 157 | 158 | #1 159 | 160 | #1 161 | 162 | #1 163 | 164 | #1 165 | 166 | #1 167 | 168 | #1 169 | ]" 170 | , 171 | TestID->"Bugs-20211007-C8M2O4" 172 | ] 173 | 174 | 175 | input = 176 | "f[ 177 | \"1:eJxTTMoPSmVmYGBgAmJeKA0Big4Wru5FP/n1HU4vdN32uVYIzpfQv6vC1vjd 178 | 3kzqQLTCQQ04X/r1IzMpoD4Yn6mCW0WjTgiuv+N0vcf+2u/2MPNhfCmQeqA5 179 | ML5sVIr1faA9MP0wPsx8GB9mP0w/zH3o7ofxG4JLVKb/f2DP3jjVuTtHAM4/ 180 | AXLf3wf2NuHRG/cDxWF8mH0w/gwtialXTmo6wPTv/NP+5Xa5Adx8GJ/r+uIC 181 | 21OacP6nS75JAhUGDjD9MD7MfBgfZj9MP8x96O4HAGYxoDo=\"]" 182 | 183 | Test[ 184 | CodeFormat[input, "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV1"] 185 | , 186 | "f[\"1:eJxTTMoPSmVmYGBgAmJeKA0Big4Wru5FP/n1HU4vdN32uVYIzpfQv6vC1vjd 187 | 3kzqQLTCQQ04X/r1IzMpoD4Yn6mCW0WjTgiuv+N0vcf+2u/2MPNhfCmQeqA5 188 | ML5sVIr1faA9MP0wPsx8GB9mP0w/zH3o7ofxG4JLVKb/f2DP3jjVuTtHAM4/ 189 | AXLf3wf2NuHRG/cDxWF8mH0w/gwtialXTmo6wPTv/NP+5Xa5Adx8GJ/r+uIC 190 | 21OacP6nS75JAhUGDjD9MD7MfBgfZj9MP8x96O4HAGYxoDo=\" 191 | ]" 192 | , 193 | TestID->"Bugs-20210113-K8Y8F3" 194 | ] 195 | 196 | Test[ 197 | CodeFormat[input, "LineWidth" -> 20, "BreakLinesMethod" -> "LineBreakerV2"] 198 | , 199 | "f[ 200 | \"1:eJxTTMoPSmVmYGBgAmJeKA0Big4Wru5FP/n1HU4vdN32uVYIzpfQv6vC1vjd 201 | 3kzqQLTCQQ04X/r1IzMpoD4Yn6mCW0WjTgiuv+N0vcf+2u/2MPNhfCmQeqA5 202 | ML5sVIr1faA9MP0wPsx8GB9mP0w/zH3o7ofxG4JLVKb/f2DP3jjVuTtHAM4/ 203 | AXLf3wf2NuHRG/cDxWF8mH0w/gwtialXTmo6wPTv/NP+5Xa5Adx8GJ/r+uIC 204 | 21OacP6nS75JAhUGDjD9MD7MfBgfZj9MP8x96O4HAGYxoDo=\" 205 | ]" 206 | , 207 | TestID->"Bugs-20211007-F2G5T9" 208 | ] 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | (* 217 | bug 407712 218 | *) 219 | Test[ 220 | CodeFormat["unSetMyGlobal:=(MyGlobal=.)"] 221 | , 222 | "unSetMyGlobal := 223 | (MyGlobal =.)" 224 | , 225 | TestID->"Bugs-20210406-H6V8N9" 226 | ] 227 | 228 | 229 | 230 | 231 | 232 | 233 | (* 234 | bug 408992 235 | *) 236 | Test[ 237 | CodeFormat["a:b"] 238 | , 239 | "a:b" 240 | , 241 | TestID->"Bugs-20210426-L8Q5O5" 242 | ] 243 | 244 | Test[ 245 | CodeFormat["a:b|c"] 246 | , 247 | "a : b | c" 248 | , 249 | TestID->"Bugs-20210426-P4B2D7" 250 | ] 251 | 252 | 253 | 254 | 255 | 256 | (* 257 | bug 409143 258 | *) 259 | Test[ 260 | CodeFormat["foo::bars"] 261 | , 262 | "foo::bars" 263 | , 264 | TestID->"Bugs-20210428-I2Z4W1" 265 | ] 266 | 267 | 268 | 269 | (* 270 | bug 414042 271 | 272 | it is ok if this changes format 273 | 274 | just need to make sure that & is not on a new line 275 | *) 276 | Test[ 277 | CodeFormat[ 278 | "f[] := Block[{}, Block[{}, 279 | If[validationQ, 280 | datasetValidation[[\"BoundingBox\"]] = MapThread[PadCorner[#] /@ #2&, 281 | {datasetValidation[[\"Size\"]], datasetValidation[[\"BoundingBox\"]]}]; 282 | ]; 283 | ]]", "IndentationString" -> "\t", "BreakLinesMethod" -> "LineBreakerV1"] 284 | , 285 | "f[] := 286 | Block[{}, 287 | Block[{}, 288 | If[validationQ, 289 | datasetValidation[[\"BoundingBox\"]] = MapThread[PadCorner[#] /@ #2&, 290 | {datasetValidation[[\"Size\"]], datasetValidation[[\"BoundingBox\"]]}]; 291 | ]; 292 | ] 293 | ]" 294 | , 295 | TestID->"Bugs-20210903-D6C3Y9" 296 | ] 297 | 298 | Test[ 299 | CodeFormat[ 300 | "f[] := Block[{}, Block[{}, 301 | If[validationQ, 302 | datasetValidation[[\"BoundingBox\"]] = MapThread[PadCorner[#] /@ #2&, 303 | {datasetValidation[[\"Size\"]], datasetValidation[[\"BoundingBox\"]]}]; 304 | ]; 305 | ]]", "IndentationString" -> "\t", "BreakLinesMethod" -> "LineBreakerV2"] 306 | , 307 | "f[] := 308 | Block[{}, 309 | Block[{}, 310 | If[validationQ, 311 | datasetValidation[[\"BoundingBox\"]] = 312 | MapThread[ 313 | PadCorner[#] /@ #2& 314 | , 315 | {datasetValidation[[\"Size\"]], datasetValidation[[\"BoundingBox\"]]} 316 | ]; 317 | ]; 318 | ] 319 | ]" 320 | , 321 | TestID->"Bugs-20211007-A3T2R2" 322 | ] 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | (* 331 | was giving StringLength::string messages 332 | *) 333 | TestMatch[ 334 | CodeFormat["(Sum[a b c, {i, 1, 9}](*+ 335 | xxx*)) 336 | "] 337 | , 338 | "( 339 | Sum[a b c, {i, 1, 9}](*+ 340 | xxx*) )" 341 | , 342 | TestID->"Bugs-20210929-O7G2W2" 343 | ] 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | (* 352 | bug 417300 353 | *) 354 | 355 | cst = CodeConcreteParse["add[a_, b_]"] 356 | 357 | first = cst[[2, 1]] 358 | 359 | Test[ 360 | CodeFormatCST[first] 361 | , 362 | "add[a_, b_] 363 | " 364 | , 365 | TestID->"Bugs-20211117-D2N8Q0" 366 | ] 367 | 368 | cst = CodeConcreteParse["(a+b)"] 369 | 370 | first = cst[[2, 1]] 371 | 372 | Test[ 373 | CodeFormatCST[first] 374 | , 375 | "(a + b) 376 | " 377 | , 378 | TestID->"Bugs-20211117-K3G2M8" 379 | ] 380 | 381 | 382 | 383 | 384 | 385 | 386 | (* 387 | bug 417935 388 | *) 389 | Test[ 390 | CodeFormat["Module[{x = 10}, x + 1]", "NewlinesInScoping" -> Insert] 391 | , 392 | "\ 393 | Module[ 394 | {x = 10} 395 | , 396 | x + 1 397 | ]" 398 | , 399 | TestID->"Bugs-20211202-Z3N3I9" 400 | ] 401 | 402 | Test[ 403 | CodeFormat["With[{a=1},{b=2},{c=3},a]", "NewlinesInScoping" -> Insert] 404 | , 405 | "\ 406 | With[ 407 | {a = 1} 408 | , 409 | {b = 2} 410 | , 411 | {c = 3} 412 | , 413 | a 414 | ]" 415 | , 416 | TestID->"Bugs-20211202-X4S6G6" 417 | ] 418 | 419 | Test[ 420 | CodeFormat["If[a,b,c]", "NewlinesInControl" -> Insert] 421 | , 422 | "\ 423 | If[ 424 | a 425 | , 426 | b 427 | , 428 | c 429 | ]" 430 | , 431 | TestID->"Bugs-20211202-V3O6H9" 432 | ] 433 | 434 | Test[ 435 | CodeFormat["Switch[a,b,c,d,e]", "NewlinesInControl" -> Insert] 436 | , 437 | "\ 438 | Switch[ 439 | a 440 | , 441 | b 442 | , 443 | c 444 | , 445 | d 446 | , 447 | e 448 | ]" 449 | , 450 | TestID->"Bugs-20211202-C8Q5W0" 451 | ] 452 | 453 | Test[ 454 | CodeFormat["Which[a,b,c,d]", "NewlinesInControl" -> Insert] 455 | , 456 | "\ 457 | Which[ 458 | a 459 | , 460 | b 461 | , 462 | c 463 | , 464 | d 465 | ]" 466 | , 467 | TestID->"Bugs-20211202-N0K6F5" 468 | ] 469 | 470 | Test[ 471 | CodeFormat["For[start,test,inc,body]", "NewlinesInControl" -> Insert] 472 | , 473 | "\ 474 | For[ 475 | start 476 | , 477 | test 478 | , 479 | inc 480 | , 481 | body 482 | ]" 483 | , 484 | TestID->"Bugs-20211202-B7O2N0" 485 | ] 486 | 487 | 488 | cst = 489 | ContainerNode[ 490 | String, {CallNode[{LeafNode[Symbol, "Module", <||>]}, GroupNode[ 491 | GroupSquare, {LeafNode[Token`OpenSquare, "[", <||>], 492 | InfixNode[ 493 | Comma, {GroupNode[ 494 | List, {LeafNode[Token`OpenCurly, "{", <||>], 495 | InfixNode[ 496 | Comma, {LeafNode[Symbol, "a", <||>], 497 | LeafNode[Token`Comma, ",", <||>], 498 | LeafNode[Symbol, "b", <||>]}, <||>], 499 | LeafNode[Token`CloseCurly, "}", <||>]}, <||>], 500 | LeafNode[Token`Comma, ",", <||>], 501 | LeafNode[Symbol, "foo", <||>]}, <||>], 502 | LeafNode[Token`CloseSquare, "]", <||>]}, <||>], <||>]}, <||>] 503 | 504 | Test[ 505 | CodeFormatCST[cst] 506 | , 507 | "\ 508 | Module[{a, b}, 509 | foo 510 | ] 511 | " 512 | , 513 | TestID->"Bugs-20220429-S6P7I8" 514 | ] 515 | 516 | 517 | Test[ 518 | CodeFormat[""] 519 | , 520 | "" 521 | , 522 | TestID->"Bugs-20220817-R3W7C9" 523 | ] 524 | -------------------------------------------------------------------------------- /CodeParser/Data/PrefixParselets.wl: -------------------------------------------------------------------------------- 1 | 2 | (* 3 | CodeParser Data file 4 | 5 | Do not modify this file directly 6 | *) 7 | 8 | <| 9 | 10 | Token`String -> Parselet`LeafParselet[], 11 | Token`Integer -> Parselet`LeafParselet[], 12 | Token`Real -> Parselet`LeafParselet[], 13 | Token`Rational -> Parselet`LeafParselet[], 14 | Token`LinearSyntaxBlob -> Parselet`LeafParselet[], 15 | 16 | Token`Unknown -> Parselet`PrefixErrorParselet[], 17 | Token`Whitespace -> Parselet`PrefixErrorParselet[], 18 | Token`InternalNewline -> Parselet`PrefixErrorParselet[], 19 | Token`Comment -> Parselet`PrefixErrorParselet[], 20 | 21 | Token`EndOfFile -> Parselet`PrefixEndOfFileParselet[], 22 | 23 | Token`Error`ExpectedEqual -> Parselet`PrefixErrorParselet[], 24 | Token`Error`Number -> Parselet`PrefixErrorParselet[], 25 | Token`Error`UnhandledCharacter -> Parselet`PrefixErrorParselet[], 26 | Token`Error`ExpectedLetterlike -> Parselet`PrefixErrorParselet[], 27 | Token`Error`Aborted -> Parselet`PrefixErrorParselet[], 28 | Token`Error`ExpectedOperand -> Parselet`PrefixErrorParselet[], 29 | Token`Error`ExpectedTag -> Parselet`PrefixErrorParselet[], 30 | Token`Error`ExpectedFile -> Parselet`PrefixErrorParselet[], 31 | Token`Error`UnterminatedComment -> Parselet`PrefixErrorParselet[], 32 | Token`Error`UnterminatedString -> Parselet`PrefixErrorParselet[], 33 | Token`Error`UnterminatedFileString -> Parselet`PrefixErrorParselet[], 34 | Token`Error`UnterminatedLinearSyntaxBlob -> Parselet`PrefixErrorParselet[], 35 | Token`Error`UnsupportedToken -> Parselet`PrefixErrorParselet[], 36 | Token`Error`UnexpectedCloser -> Parselet`PrefixErrorParselet[], 37 | Token`Error`UnsafeCharacterEncoding -> Parselet`PrefixErrorParselet[], 38 | Token`Error`UnexpectedCommentCloser -> Parselet`PrefixErrorParselet[], 39 | 40 | 41 | Token`BarGreater -> Parselet`PrefixCloserParselet[], 42 | Token`CloseCurly -> Parselet`PrefixCloserParselet[], 43 | Token`CloseParen -> Parselet`PrefixCloserParselet[], 44 | Token`CloseSquare -> Parselet`PrefixCloserParselet[], 45 | Token`LongName`CloseCurlyDoubleQuote -> Parselet`PrefixCloserParselet[], 46 | Token`LongName`CloseCurlyQuote -> Parselet`PrefixCloserParselet[], 47 | Token`LongName`RightAngleBracket -> Parselet`PrefixCloserParselet[], 48 | Token`LongName`RightAssociation -> Parselet`PrefixCloserParselet[], 49 | Token`LongName`RightBracketingBar -> Parselet`PrefixCloserParselet[], 50 | Token`LongName`RightCeiling -> Parselet`PrefixCloserParselet[], 51 | Token`LongName`RightDoubleBracket -> Parselet`PrefixCloserParselet[], 52 | Token`LongName`RightDoubleBracketingBar -> Parselet`PrefixCloserParselet[], 53 | Token`LongName`RightFloor -> Parselet`PrefixCloserParselet[], 54 | 55 | 56 | Token`Minus -> Parselet`PrefixOperatorParselet[Precedence`Prefix`Minus, Minus], 57 | Token`Plus -> Parselet`PrefixOperatorParselet[Precedence`Prefix`Plus, Plus], 58 | Token`Bang -> Parselet`PrefixOperatorParselet[Precedence`Prefix`Bang, Not], 59 | Token`PlusPlus -> Parselet`PrefixOperatorParselet[Precedence`Prefix`PlusPlus, PreIncrement], 60 | Token`MinusMinus -> Parselet`PrefixOperatorParselet[Precedence`Prefix`MinusMinus, PreDecrement], 61 | 62 | Token`BangBang -> Parselet`PrefixOperatorParselet[Precedence`Fake`Prefix`BangBang, CodeParser`PrefixNot2], 63 | 64 | Token`LongName`PlusMinus -> Parselet`PrefixOperatorParselet[Precedence`Prefix`LongName`PlusMinus, PlusMinus], 65 | Token`LongName`Sum -> Parselet`PrefixOperatorParselet[Precedence`LongName`Sum, Sum], 66 | Token`LongName`Not -> Parselet`PrefixOperatorParselet[Precedence`LongName`Not, Not], 67 | Token`LongName`Sqrt -> Parselet`PrefixOperatorParselet[Precedence`LongName`Sqrt, Sqrt], 68 | Token`LongName`MinusPlus -> Parselet`PrefixOperatorParselet[Precedence`Prefix`LongName`MinusPlus, MinusPlus], 69 | Token`LongName`DifferentialD -> Parselet`PrefixOperatorParselet[Precedence`LongName`DifferentialD, DifferentialD], 70 | Token`LongName`CapitalDifferentialD -> Parselet`PrefixOperatorParselet[Precedence`LongName`CapitalDifferentialD, CapitalDifferentialD], 71 | Token`LongName`Minus -> Parselet`PrefixOperatorParselet[Precedence`Prefix`LongName`Minus, Minus], 72 | Token`LongName`Del -> Parselet`PrefixOperatorParselet[Precedence`LongName`Del, Del], 73 | Token`LongName`Square -> Parselet`PrefixOperatorParselet[Precedence`LongName`Square, Square], 74 | 75 | 76 | Token`Comma -> Parselet`PrefixCommaParselet[], 77 | Token`LongName`InvisibleComma -> Parselet`PrefixCommaParselet[], 78 | 79 | 80 | Token`LongName`Product -> Parselet`PrefixOperatorParselet[Precedence`LongName`Product, Product], 81 | Token`LongName`ContinuedFractionK -> Parselet`PrefixOperatorParselet[Precedence`LongName`ContinuedFractionK, ContinuedFractionK], 82 | Token`LongName`CircleTimes -> Parselet`PrefixOperatorParselet[Precedence`Prefix`LongName`CircleTimes, CircleTimes], 83 | Token`LongName`ForAll -> Parselet`PrefixOperatorParselet[Precedence`LongName`ForAll, ForAll], 84 | Token`LongName`Exists -> Parselet`PrefixOperatorParselet[Precedence`LongName`Exists, Exists], 85 | Token`LongName`NotExists -> Parselet`PrefixOperatorParselet[Precedence`LongName`NotExists, NotExists], 86 | Token`LongName`Coproduct -> Parselet`PrefixOperatorParselet[Precedence`Prefix`LongName`Coproduct, Coproduct], 87 | Token`LongName`Piecewise -> Parselet`PrefixOperatorParselet[Precedence`LongName`Piecewise, Piecewise], 88 | Token`LongName`InvisiblePrefixScriptBase -> Parselet`PrefixOperatorParselet[Precedence`LongName`InvisiblePrefixScriptBase, System`InvisiblePrefixScriptBase], 89 | Token`LongName`ExpectationE -> Parselet`PrefixOperatorParselet[Precedence`LongName`ExpectationE, ExpectationE], 90 | Token`LongName`CubeRoot -> Parselet`PrefixOperatorParselet[Precedence`LongName`CubeRoot, CubeRoot], 91 | Token`LongName`ProbabilityPr -> Parselet`PrefixOperatorParselet[Precedence`LongName`ProbabilityPr, ProbabilityPr], 92 | 93 | Token`LinearSyntax`Bang -> Parselet`PrefixOperatorParselet[Precedence`LinearSyntax`Bang, CodeParser`PrefixLinearSyntaxBang], 94 | Token`LinearSyntax`At -> Parselet`PrefixUnsupportedTokenParselet[], 95 | Token`LinearSyntax`Amp -> Parselet`PrefixUnsupportedTokenParselet[], 96 | Token`LinearSyntax`Star -> Parselet`PrefixUnsupportedTokenParselet[], 97 | Token`LinearSyntax`Under -> Parselet`PrefixUnsupportedTokenParselet[], 98 | Token`LinearSyntax`Caret -> Parselet`PrefixUnsupportedTokenParselet[], 99 | Token`LinearSyntax`Space -> Parselet`PrefixUnsupportedTokenParselet[], 100 | Token`LinearSyntax`Percent -> Parselet`PrefixUnsupportedTokenParselet[], 101 | Token`LinearSyntax`Plus -> Parselet`PrefixUnsupportedTokenParselet[], 102 | Token`LinearSyntax`Slash -> Parselet`PrefixUnsupportedTokenParselet[], 103 | Token`LinearSyntax`BackTick -> Parselet`PrefixUnsupportedTokenParselet[], 104 | 105 | Token`LinearSyntax`CloseParen -> Parselet`PrefixUnsupportedTokenParselet[], 106 | 107 | 108 | (* 109 | Groups 110 | *) 111 | Token`OpenParen -> Parselet`GroupParselet[Token`OpenParen, CodeParser`GroupParen], 112 | Token`OpenSquare -> Parselet`GroupParselet[Token`OpenSquare, CodeParser`GroupSquare], 113 | Token`OpenCurly -> Parselet`GroupParselet[Token`OpenCurly, List], 114 | Token`LessBar -> Parselet`GroupParselet[Token`LessBar, Association], 115 | Token`ColonColonOpenSquare -> Parselet`GroupParselet[Token`ColonColonOpenSquare, CodeParser`GroupTypeSpecifier], 116 | Token`LongName`LeftAngleBracket -> Parselet`GroupParselet[Token`LongName`LeftAngleBracket, AngleBracket], 117 | Token`LongName`LeftCeiling -> Parselet`GroupParselet[Token`LongName`LeftCeiling, Ceiling], 118 | Token`LongName`LeftFloor -> Parselet`GroupParselet[Token`LongName`LeftFloor, Floor], 119 | Token`LongName`LeftDoubleBracket -> Parselet`GroupParselet[Token`LongName`LeftDoubleBracket, CodeParser`GroupDoubleBracket], 120 | Token`LongName`LeftBracketingBar -> Parselet`GroupParselet[Token`LongName`LeftBracketingBar, BracketingBar], 121 | Token`LongName`LeftDoubleBracketingBar -> Parselet`GroupParselet[Token`LongName`LeftDoubleBracketingBar, DoubleBracketingBar], 122 | Token`LongName`LeftAssociation -> Parselet`GroupParselet[Token`LongName`LeftAssociation, Association], 123 | Token`LongName`OpenCurlyQuote -> Parselet`GroupParselet[Token`LongName`OpenCurlyQuote, CurlyQuote], 124 | Token`LongName`OpenCurlyDoubleQuote -> Parselet`GroupParselet[Token`LongName`OpenCurlyDoubleQuote, CurlyDoubleQuote], 125 | 126 | (* 127 | Special 128 | *) 129 | 130 | (* 131 | context sensitive parsing of x_ 132 | *) 133 | Token`Symbol -> Parselet`SymbolParselet[], 134 | 135 | (* 136 | context sensitive parsing of _x 137 | *) 138 | Token`Under -> Parselet`UnderParselet[1], 139 | Token`UnderUnder -> Parselet`UnderParselet[2], 140 | Token`UnderUnderUnder -> Parselet`UnderParselet[3], 141 | 142 | Token`UnderDot -> Parselet`UnderDotParselet[], 143 | 144 | 145 | Token`Hash -> Parselet`HashParselet[], 146 | Token`HashHash -> Parselet`HashHashParselet[], 147 | 148 | Token`Percent -> Parselet`PercentParselet[], 149 | Token`PercentPercent -> Parselet`LeafParselet[], 150 | 151 | (* 152 | prefix, infix, postfix 153 | *) 154 | Token`SemiSemi -> Parselet`SemiSemiParselet[], 155 | 156 | (* 157 | Has to handle \[Integral] f \[DifferentialD] x 158 | *) 159 | Token`LongName`Integral -> Parselet`IntegralParselet[Integrate, Integral], 160 | Token`LongName`ContourIntegral -> Parselet`IntegralParselet[ContourIntegral, ContourIntegral], 161 | Token`LongName`DoubleContourIntegral -> Parselet`IntegralParselet[DoubleContourIntegral, DoubleContourIntegral], 162 | Token`LongName`ClockwiseContourIntegral -> Parselet`IntegralParselet[ClockwiseContourIntegral, ClockwiseContourIntegral], 163 | Token`LongName`CounterClockwiseContourIntegral -> Parselet`IntegralParselet[CounterClockwiseContourIntegral, CounterClockwiseContourIntegral], 164 | 165 | (* 166 | stringify next token (as a file] 167 | *) 168 | Token`LessLess -> Parselet`LessLessParselet[], 169 | 170 | 171 | Token`QuestionQuestion -> Parselet`PrefixUnsupportedTokenParselet[], 172 | 173 | (* 174 | Also use for operators that are only valid in StandardForm. 175 | e.g., \[Gradient] does not have an interpretation in InputForm 176 | 177 | \[Gradient] is not letterlike, so it needs some kind of categorization, 178 | but it also needs to be prevented from making any valid parses. 179 | *) 180 | Token`LongName`Gradient -> Parselet`PrefixUnsupportedTokenParselet[], 181 | Token`LongName`Divergence -> Parselet`PrefixUnsupportedTokenParselet[], 182 | Token`LongName`Curl -> Parselet`PrefixUnsupportedTokenParselet[], 183 | Token`LongName`Limit -> Parselet`PrefixUnsupportedTokenParselet[], 184 | Token`LongName`MaxLimit -> Parselet`PrefixUnsupportedTokenParselet[], 185 | Token`LongName`MinLimit -> Parselet`PrefixUnsupportedTokenParselet[], 186 | (* 187 | technically, \[AutoLeftMatch] foo \[AutoRightMatch] does parse as 188 | AutoMatch[foo] in InputForm but this is not documented, 189 | and I'm not going to support it 190 | *) 191 | Token`LongName`AutoLeftMatch -> Parselet`PrefixUnsupportedTokenParselet[], 192 | Token`LongName`AutoRightMatch -> Parselet`PrefixUnsupportedTokenParselet[], 193 | Token`LongName`DiscreteShift -> Parselet`PrefixUnsupportedTokenParselet[], 194 | Token`LongName`DifferenceDelta -> Parselet`PrefixUnsupportedTokenParselet[], 195 | Token`LongName`DiscreteRatio -> Parselet`PrefixUnsupportedTokenParselet[], 196 | Token`LongName`Laplacian -> Parselet`PrefixUnsupportedTokenParselet[], 197 | Token`LongName`PartialD -> Parselet`PrefixUnsupportedTokenParselet[] 198 | 199 | |> 200 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # https://cmake.org/cmake/help/latest/release/3.15.html 4 | # The cmake(1) command gained a new --install option. This may be used after building a project to run installation without using the generated build system or the native build tool. 5 | # 6 | cmake_minimum_required(VERSION 3.15) 7 | 8 | project(codeformatter 9 | LANGUAGES 10 | NONE 11 | ) 12 | 13 | set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 14 | include(WolframKernel) 15 | include(PacletInfo) 16 | 17 | # 18 | # Used for quickly reporting syntax errors in WL source files 19 | # 20 | find_program(CODEPARSER_EXE 21 | NAMES 22 | codeparser codeparser.exe 23 | HINTS 24 | ${CODEPARSER_EXE_DIR} 25 | ) 26 | 27 | set(PACLET "CodeFormatter") 28 | set(PACLET_LAYOUT_DIR "paclet" CACHE FILEPATH "Path to complete, built paclet layout (relative to build directory)") 29 | set(WOLFRAMKERNEL ${WOLFRAMKERNEL_DEFAULT} CACHE FILEPATH "Path to WolframKernel") 30 | set(LOCAL_BUILD OFF CACHE BOOL "Local build") 31 | # 32 | # Time to pause when starting kernel 33 | # 34 | # Work-around for bug 349779 is to pause ~1 second 35 | # bug 349779 was fixed in version 12.0 36 | # 37 | # Related bugs: 349779 38 | # 39 | set(KERNEL_PAUSE 0 CACHE STRING "Kernel startup pause") 40 | # 41 | # Timeout for starting a kernel and getting a result 42 | # 43 | # RE machines can be very slow when starting a kernel, so we need to be very generous with this timeout 44 | # 45 | # Should be at least 10 minutes = 600 seconds 46 | # 47 | # Evidence suggests that when bug 349779 strikes, the kernel does exit after 30 minutes = 1800 seconds 48 | # bug 349779 was fixed in version 12.0 49 | # 50 | # Related bugs: 349779 51 | # Related issues: RE-514227 52 | # 53 | set(KERNEL_TIMEOUT 600 CACHE STRING "Kernel startup timeout") 54 | 55 | if(NOT DEFINED BUILDNUMBER) 56 | set(BUILDNUMBER 0) 57 | endif() 58 | message(STATUS "BUILDNUMBER: ${BUILDNUMBER}") 59 | message(STATUS "CMAKE_VERSION: ${CMAKE_VERSION}") 60 | message(STATUS "PACLET: ${PACLET}") 61 | message(STATUS "PACLET_LAYOUT_DIR: ${PACLET_LAYOUT_DIR}") 62 | message(STATUS "WOLFRAMKERNEL: ${WOLFRAMKERNEL}") 63 | message(STATUS "LOCAL_BUILD: ${LOCAL_BUILD}") 64 | if(LOCAL_BUILD) 65 | message(STATUS "Configuring for local build") 66 | endif() 67 | # message(STATUS "CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}") 68 | message(STATUS "KERNEL_PAUSE: ${KERNEL_PAUSE}") 69 | message(STATUS "KERNEL_TIMEOUT: ${KERNEL_TIMEOUT}") 70 | message(STATUS "CODEPARSER_EXE: ${CODEPARSER_EXE}") 71 | if(NOT CODEPARSER_EXE) 72 | message(STATUS "Optional tool CODEPARSER_EXE was not found; skipping") 73 | endif() 74 | 75 | set(STATIC_WL_PACLET_KERNEL_SOURCES 76 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/Absorb.wl 77 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/Abstract.wl 78 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/AnchoredComments.wl 79 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/CodeFormatter.wl 80 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/Fragmentize.wl 81 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/Indent.wl 82 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/LineBreakerV1.wl 83 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/Notebooks.wl 84 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/RemoveLineContinuations.wl 85 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/Standardize.wl 86 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Kernel/Utils.wl 87 | ) 88 | 89 | set(STATIC_WL_PACLET_FRONTEND_SOURCES 90 | ${PROJECT_SOURCE_DIR}/CodeFormatter/FrontEnd/StyleSheets/Dialog.nb 91 | ${PROJECT_SOURCE_DIR}/CodeFormatter/FrontEnd/StyleSheets/Package.nb 92 | ${PROJECT_SOURCE_DIR}/CodeFormatter/FrontEnd/StyleSheets/Script.nb 93 | ${PROJECT_SOURCE_DIR}/CodeFormatter/FrontEnd/SystemResources/Bitmaps/Misc/popupRightBottom.9.png 94 | ${PROJECT_SOURCE_DIR}/CodeFormatter/FrontEnd/SystemResources/Bitmaps/Misc/popupRightBottom@144dpi.9.png 95 | ${PROJECT_SOURCE_DIR}/CodeFormatter/FrontEnd/SystemResources/Bitmaps/Misc/TransparentBG.9.png 96 | ${PROJECT_SOURCE_DIR}/CodeFormatter/FrontEnd/TextResources/CodeFormatter.tr 97 | ) 98 | 99 | set(PACLETINFO_IN_SOURCE 100 | ${PROJECT_SOURCE_DIR}/${PACLET}/PacletInfo.wl.in 101 | ) 102 | 103 | set(GENERATED_WL_PACLET_KERNEL_SOURCES 104 | ${PROJECT_BINARY_DIR}/paclet/CodeFormatter/Kernel/AcceptableOperators.wl 105 | ) 106 | 107 | set(GENERATED_WL_PACLET_FRONTEND_SOURCES 108 | ${PROJECT_BINARY_DIR}/paclet/CodeFormatter/FrontEnd/Palettes/CodeFormatter.nb 109 | ) 110 | 111 | 112 | 113 | # 114 | # Set VERSION_NUMBER, SYSTEMID, and PACLET_VERSION 115 | # 116 | CheckWolframKernel() 117 | CheckPacletInfo() 118 | 119 | # 120 | # Force re-configure if PacletInfo.wl.in changes, e.g. paclet version is changed and name of .paclet has changed 121 | # 122 | set_property( 123 | DIRECTORY 124 | APPEND 125 | PROPERTY 126 | CMAKE_CONFIGURE_DEPENDS 127 | ${PACLETINFO_IN_SOURCE} 128 | ) 129 | 130 | 131 | if(NOT VERSION_NUMBER GREATER_EQUAL 1210) 132 | message(WARNING "VERSION_NUMBER is below 1210; ForceVersionInstall was added in 12.1. Installing paclets via CMake may not work. (VERSION_NUMBER is ${VERSION_NUMBER})") 133 | endif() 134 | 135 | 136 | file(MAKE_DIRECTORY 137 | ${PROJECT_BINARY_DIR}/paclet/${PACLET} 138 | ${PROJECT_BINARY_DIR}/paclet/${PACLET}/FrontEnd/Palettes/ 139 | ) 140 | 141 | 142 | # 143 | # Copy WL source files 144 | # 145 | 146 | set(REPLACED_PACLETINFO ${PROJECT_BINARY_DIR}/paclet/${PACLET}/PacletInfo.wl) 147 | 148 | add_custom_command( 149 | OUTPUT 150 | ${REPLACED_PACLETINFO} 151 | COMMAND 152 | ${CMAKE_COMMAND} -DSRC=${PACLETINFO_IN_SOURCE} -DCODEPARSER_EXE=${CODEPARSER_EXE} -DWOLFRAMKERNEL=${WOLFRAMKERNEL} -DKERNEL_TIMEOUT=${KERNEL_TIMEOUT} -P ${PROJECT_SOURCE_DIR}/cmake/InspectFile.cmake 153 | COMMAND 154 | ${CMAKE_COMMAND} -DTRANSPORT=${TRANSPORT} -DBUILDNUMBER=${BUILDNUMBER} -DVERSION_NUMBER=${VERSION_NUMBER} -DWOLFRAMLIBRARY_VERSION=${WOLFRAMLIBRARY_VERSION} -DLOCAL_BUILD=${LOCAL_BUILD} -DLOCAL_BUILD_VERSION=${LOCAL_BUILD_VERSION} -DPACLETINFO_IN_SOURCE=${PACLETINFO_IN_SOURCE} -DREPLACED_PACLETINFO=${REPLACED_PACLETINFO} -P ${PROJECT_SOURCE_DIR}/cmake/ReplacePacletInfo.cmake 155 | DEPENDS 156 | ${PACLETINFO_IN_SOURCE} 157 | ${PROJECT_SOURCE_DIR}/cmake/InspectFile.cmake 158 | ${PROJECT_SOURCE_DIR}/cmake/ReplacePacletInfo.cmake 159 | ) 160 | 161 | 162 | # 163 | # static Kernel sources 164 | # 165 | foreach(SRC ${STATIC_WL_PACLET_KERNEL_SOURCES}) 166 | file(RELATIVE_PATH REL ${PROJECT_SOURCE_DIR}/${PACLET}/Kernel/ ${SRC}) 167 | add_custom_command( 168 | OUTPUT 169 | ${PROJECT_BINARY_DIR}/paclet/${PACLET}/Kernel/${REL} 170 | COMMAND 171 | ${CMAKE_COMMAND} -DSRC=${SRC} -DCODEPARSER_EXE=${CODEPARSER_EXE} -DWOLFRAMKERNEL=${WOLFRAMKERNEL} -DKERNEL_TIMEOUT=${KERNEL_TIMEOUT} -P ${PROJECT_SOURCE_DIR}/cmake/InspectFile.cmake 172 | COMMAND 173 | ${CMAKE_COMMAND} -E copy ${SRC} ${PROJECT_BINARY_DIR}/paclet/${PACLET}/Kernel/${REL} 174 | DEPENDS 175 | ${SRC} 176 | ${PROJECT_SOURCE_DIR}/cmake/InspectFile.cmake 177 | ) 178 | list(APPEND COPIED_WL_PACLET_SOURCES ${PROJECT_BINARY_DIR}/paclet/${PACLET}/Kernel/${REL}) 179 | endforeach() 180 | 181 | # 182 | # static FrontEnd sources 183 | # 184 | foreach(SRC ${STATIC_WL_PACLET_FRONTEND_SOURCES}) 185 | file(RELATIVE_PATH REL ${PROJECT_SOURCE_DIR}/${PACLET}/FrontEnd/ ${SRC}) 186 | add_custom_command( 187 | OUTPUT 188 | ${PROJECT_BINARY_DIR}/paclet/${PACLET}/FrontEnd/${REL} 189 | # 190 | # Do not inspect FrontEnd sources 191 | # 192 | COMMAND 193 | ${CMAKE_COMMAND} -E copy ${SRC} ${PROJECT_BINARY_DIR}/paclet/${PACLET}/FrontEnd/${REL} 194 | DEPENDS 195 | ${SRC} 196 | ) 197 | list(APPEND COPIED_WL_PACLET_SOURCES ${PROJECT_BINARY_DIR}/paclet/${PACLET}/FrontEnd/${REL}) 198 | endforeach() 199 | 200 | 201 | 202 | # 203 | # generated srcs 204 | # 205 | 206 | # 207 | # AcceptableOperators files 208 | # 209 | add_custom_command( 210 | OUTPUT 211 | ${PROJECT_BINARY_DIR}/paclet/CodeFormatter/Kernel/AcceptableOperators.wl 212 | COMMAND 213 | ${CMAKE_COMMAND} -DSCRIPT=${PROJECT_SOURCE_DIR}/CodeFormatter/Generate/AcceptableOperators.wl -DSRCDIR=${PROJECT_SOURCE_DIR} -DBUILDDIR=${PROJECT_BINARY_DIR} -DWOLFRAMKERNEL=${WOLFRAMKERNEL} -DKERNEL_TIMEOUT=${KERNEL_TIMEOUT} -P ${PROJECT_SOURCE_DIR}/cmake/WolframScript.cmake 214 | COMMAND 215 | ${CMAKE_COMMAND} -DSRC=${PROJECT_BINARY_DIR}/paclet/CodeFormatter/Kernel/AcceptableOperators.wl -DCODEPARSER_EXE=${CODEPARSER_EXE} -DWOLFRAMKERNEL=${WOLFRAMKERNEL} -DKERNEL_TIMEOUT=${KERNEL_TIMEOUT} -P ${PROJECT_SOURCE_DIR}/cmake/InspectFile.cmake 216 | DEPENDS 217 | ${PROJECT_SOURCE_DIR}/CodeParser/Data/InfixParselets.wl 218 | ${PROJECT_SOURCE_DIR}/CodeParser/Data/PrefixParselets.wl 219 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Generate/AcceptableOperators.wl 220 | ${PROJECT_SOURCE_DIR}/CodeTools/Generate/GenerateSources.wl 221 | ${PROJECT_SOURCE_DIR}/cmake/WolframScript.cmake 222 | ${PROJECT_SOURCE_DIR}/cmake/InspectFile.cmake 223 | VERBATIM 224 | WORKING_DIRECTORY 225 | ${PROJECT_SOURCE_DIR} 226 | ) 227 | 228 | # 229 | # CodeFormatter.nb 230 | # 231 | add_custom_command( 232 | OUTPUT 233 | ${PROJECT_BINARY_DIR}/paclet/CodeFormatter/FrontEnd/Palettes/CodeFormatter.nb 234 | COMMAND 235 | ${CMAKE_COMMAND} -DSCRIPT=${PROJECT_SOURCE_DIR}/CodeFormatter/Generate/Palette.wl -DSRCDIR=${PROJECT_SOURCE_DIR} -DBUILDDIR=${PROJECT_BINARY_DIR} -DWOLFRAMKERNEL=${WOLFRAMKERNEL} -DKERNEL_TIMEOUT=${KERNEL_TIMEOUT} -P ${PROJECT_SOURCE_DIR}/cmake/WolframScript.cmake 236 | DEPENDS 237 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Generate/Palette.wl 238 | ${PROJECT_SOURCE_DIR}/CodeFormatter/Generate/UIElements.wl 239 | ${PROJECT_SOURCE_DIR}/CodeTools/Generate/GenerateSources.wl 240 | ${PROJECT_SOURCE_DIR}/cmake/WolframScript.cmake 241 | VERBATIM 242 | WORKING_DIRECTORY 243 | ${PROJECT_SOURCE_DIR} 244 | ) 245 | 246 | 247 | # 248 | # paclet layout 249 | # 250 | 251 | set(PACLET_SOURCES 252 | ${REPLACED_PACLETINFO} 253 | ${COPIED_WL_PACLET_SOURCES} 254 | ${GENERATED_WL_PACLET_KERNEL_SOURCES} 255 | ${GENERATED_WL_PACLET_FRONTEND_SOURCES} 256 | ) 257 | 258 | 259 | 260 | # 261 | # paclet archive 262 | # 263 | 264 | if(LOCAL_BUILD) 265 | set(PACLET_OUTPUT ${PROJECT_BINARY_DIR}/paclet/${PACLET}-${LOCAL_BUILD_VERSION}.paclet) 266 | else(LOCAL_BUILD) 267 | set(PACLET_OUTPUT ${PROJECT_BINARY_DIR}/paclet/${PACLET}-${PACLET_VERSION}.paclet) 268 | endif(LOCAL_BUILD) 269 | 270 | add_custom_target(create-paclet-archive 271 | ALL 272 | DEPENDS 273 | ${PACLET_OUTPUT} 274 | ) 275 | 276 | # 277 | # CreatePacletArchive 278 | # 279 | add_custom_command( 280 | OUTPUT 281 | ${PACLET_OUTPUT} 282 | COMMAND 283 | # 284 | # CreatePacletArchive may be slow on RE machines, so allow re-trying if JLink connection timeout is hit 285 | # 286 | # see: RE-515885 287 | # 288 | ${CMAKE_COMMAND} -DRETRY_ON_FAILURE=ON -DSCRIPT=${PROJECT_SOURCE_DIR}/CodeTools/Generate/CreatePacletArchive.wl -DBUILDDIR=${PROJECT_BINARY_DIR} -DPACLET_LAYOUT_DIR=${PACLET_LAYOUT_DIR} -DPACLET=${PACLET} -DKERNEL_TIMEOUT=${KERNEL_TIMEOUT} -DWOLFRAMKERNEL=${WOLFRAMKERNEL} -P ${PROJECT_SOURCE_DIR}/cmake/WolframScript.cmake 289 | DEPENDS 290 | ${PACLET_SOURCES} 291 | ${PROJECT_SOURCE_DIR}/CodeTools/Generate/CreatePacletArchive.wl 292 | ${PROJECT_SOURCE_DIR}/CodeTools/Generate/GenerateSources.wl 293 | ${PROJECT_SOURCE_DIR}/cmake/WolframScript.cmake 294 | VERBATIM 295 | WORKING_DIRECTORY 296 | ${PROJECT_BINARY_DIR} 297 | ) 298 | 299 | install( 300 | CODE 301 | "execute_process(COMMAND ${CMAKE_COMMAND} -DPACLET_OUTPUT=${PACLET_OUTPUT} -DPACLET_WOLFRAMVERSION=${PACLET_WOLFRAMVERSION} \"-DWOLFRAMKERNEL=${WOLFRAMKERNEL}\" -DKERNEL_TIMEOUT=${KERNEL_TIMEOUT} -P ${PROJECT_SOURCE_DIR}/cmake/InstallPaclet.cmake)" 302 | COMPONENT 303 | paclet 304 | ) 305 | -------------------------------------------------------------------------------- /CodeFormatter/Kernel/Utils.wl: -------------------------------------------------------------------------------- 1 | BeginPackage["CodeFormatter`Utils`"] 2 | 3 | trivia 4 | ws 5 | nl 6 | comment 7 | matchNewlineQ 8 | matchCommentFragmentNewlineQ 9 | 10 | 11 | lexSort 12 | lexOrderingForLists 13 | 14 | 15 | firstToken 16 | lastToken 17 | 18 | 19 | firstLeafNode 20 | lastLeafNode 21 | 22 | 23 | isOpener 24 | isCloser 25 | 26 | 27 | insertNecessarySpaces 28 | 29 | 30 | betterRiffle 31 | 32 | surround 33 | 34 | 35 | removeWhitespaceAndNewlines 36 | 37 | 38 | Begin["`Private`"] 39 | 40 | Needs["CodeFormatter`"] 41 | Needs["CodeParser`"] 42 | Needs["CodeParser`Utils`"] 43 | 44 | 45 | trivia = LeafNode[Whitespace | Token`Boxes`MultiWhitespace | Token`Comment | Token`Newline, _, _] 46 | 47 | ws = LeafNode[Whitespace | Token`Boxes`MultiWhitespace, _, _] 48 | 49 | nl = LeafNode[Token`Newline, _, _] 50 | 51 | comment = LeafNode[Token`Comment, _, _] 52 | 53 | matchNewlineQ = MatchQ[nl] 54 | 55 | (* 56 | FIXME: is it wrong that "\[IndentingNewLine]" can get through to here? 57 | *) 58 | matchCommentFragmentNewlineQ := MatchQ[FragmentNode[Token`Comment, $CurrentNewlineString | "\[IndentingNewLine]", _]] 59 | 60 | 61 | 62 | (* 63 | Define lexical ordering to use for lists 64 | 65 | The default Wolfram Language ordering is not the same as common lexical ordering 66 | *) 67 | lexOrderingForLists[{}, {}] := 0 68 | lexOrderingForLists[{}, b_List] := 1 69 | lexOrderingForLists[a_List, {}] := -1 70 | lexOrderingForLists[a_List, b_List] := 71 | Order[Take[a, 1], Take[b, 1]] /. 72 | 0 :> lexOrderingForLists[Drop[a, 1], Drop[b, 1]] 73 | 74 | (* 75 | 76 | Unreported bug in v11.0: 77 | 78 | In[77]:= Sort[{{1, 1}, {1, 3}, {1, 2}}, lexOrderingForLists] 79 | 80 | Out[77]= {{1, 1}, {1, 3}, {1, 2}} 81 | 82 | Fixed in 11.1 83 | 84 | checkUnreportedSortBug1[] sets the flag $WorkaroundUnreportedSortBug1 to True if we still need to workaround unreported Sort bug 1 85 | *) 86 | checkUnreportedSortBug1[] := 87 | Module[{res}, 88 | res = Sort[{{1, 1}, {1, 3}, {1, 2}}, lexOrderingForLists]; 89 | Switch[res, 90 | {{1, 1}, {1, 3}, {1, 2}}, 91 | True 92 | , 93 | {{1, 1}, {1, 2}, {1, 3}}, 94 | False 95 | ] 96 | ] 97 | 98 | 99 | 100 | $WorkaroundUnreportedSortBug1 = checkUnreportedSortBug1[] 101 | 102 | 103 | (* 104 | Yes, this is slower than it needs to be 105 | But it is simple and reliable 106 | *) 107 | bubbleLexSort[listIn_] := 108 | Module[{list, len, tmp}, 109 | list = listIn; 110 | len = Length[list]; 111 | Do[ 112 | If[lexOrderingForLists[list[[i]], list[[j]]] == -1, 113 | tmp = list[[i]]; 114 | list[[i]] = list[[j]]; 115 | list[[j]] = tmp 116 | ]; 117 | , 118 | {i, 1, len} 119 | , 120 | {j, i, len} 121 | ]; 122 | list 123 | ] 124 | 125 | 126 | If[$WorkaroundUnreportedSortBug1, 127 | lexSort = bubbleLexSort 128 | , 129 | (* 130 | TODO: v12.0 introduced SortBy[list, f, p] 131 | when targeting v12.0 as a minimum, then can use SortBy[list, ToCharacterCode, lexOrderingForLists] 132 | *) 133 | lexSort = Sort[#, lexOrderingForLists]& 134 | ] 135 | 136 | 137 | 138 | 139 | firstToken[LeafNode[tok_, _, _]] := tok 140 | 141 | firstToken[CallNode[{first_, ___}, _, _]] := firstToken[first] 142 | 143 | firstToken[_[_, {first_, ___}, _]] := firstToken[first] 144 | 145 | 146 | 147 | lastToken[LeafNode[tok_, _, _]] := tok 148 | 149 | lastToken[_[_, {___, last_}, _]] := lastToken[last] 150 | 151 | 152 | 153 | firstLeafNode[leaf:LeafNode[_, _, _]] := 154 | 155 | firstLeafNode[CallNode[{first_, ___}, _, _]] := firstLeafNode[first] 156 | 157 | firstLeafNode[_[_, {first_, ___}, _]] := firstLeafNode[first] 158 | 159 | 160 | 161 | lastLeafNode[leaf:LeafNode[_, _, _]] := leaf 162 | 163 | lastLeafNode[_[_, {___, last_}, _]] := lastLeafNode[last] 164 | 165 | 166 | 167 | isOpener[Token`LessBar] = True 168 | isOpener[Token`OpenCurly] = True 169 | isOpener[Token`OpenParen] = True 170 | isOpener[Token`OpenSquare] = True 171 | isOpener[Token`ColonColonOpenSquare] = True 172 | isOpener[Token`LongName`OpenCurlyDoubleQuote] = True 173 | isOpener[Token`LongName`OpenCurlyQuote] = True 174 | isOpener[Token`LongName`LeftAngleBracket] = True 175 | isOpener[Token`LongName`LeftAssociation] = True 176 | isOpener[Token`LongName`LeftBracketingBar] = True 177 | isOpener[Token`LongName`LeftCeiling] = True 178 | isOpener[Token`LongName`LeftDoubleBracket] = True 179 | isOpener[Token`LongName`LeftDoubleBracketingBar] = True 180 | isOpener[Token`LongName`LeftFloor] = True 181 | isOpener[Token`LinearSyntax`OpenParen] = True 182 | 183 | isOpener[_] = False 184 | 185 | 186 | isCloser[Token`BarGreater] = True 187 | isCloser[Token`CloseCurly] = True 188 | isCloser[Token`CloseParen] = True 189 | isCloser[Token`CloseSquare] = True 190 | isCloser[Token`LongName`CloseCurlyDoubleQuote] = True 191 | isCloser[Token`LongName`CloseCurlyQuote] = True 192 | isCloser[Token`LongName`RightAngleBracket] = True 193 | isCloser[Token`LongName`RightAssociation] = True 194 | isCloser[Token`LongName`RightBracketingBar] = True 195 | isCloser[Token`LongName`RightCeiling] = True 196 | isCloser[Token`LongName`RightDoubleBracket] = True 197 | isCloser[Token`LongName`RightDoubleBracketingBar] = True 198 | isCloser[Token`LongName`RightFloor] = True 199 | isCloser[Token`LinearSyntax`CloseParen] = True 200 | 201 | isCloser[_] = False 202 | 203 | 204 | 205 | (* 206 | Used by CodeMinifier also 207 | *) 208 | 209 | insertNecessarySpaces[tokensIn_] := 210 | Module[{poss, tokens, toInsert, poss1, poss2, changed}, 211 | 212 | If[$Debug, 213 | Print["insertNecessarySpaces: ", tokensIn]; 214 | ]; 215 | 216 | tokens = tokensIn; 217 | toInsert = {}; 218 | 219 | (* 220 | Insert space for 1.2` +3 221 | *) 222 | poss = Position[tokens, LeafNode[Real, str_ /; StringEndsQ[str, "`"], _]]; 223 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Plus | Token`Minus, _, _]])&]; 224 | Scan[(AppendTo[toInsert, #+1])&, poss]; 225 | 226 | (* 227 | Insert space for a. 5 228 | *) 229 | poss = Position[tokens, LeafNode[Token`Dot | Token`DotDot | Token`DotDotDot, _, _]]; 230 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Integer | Real | Rational, _, _]])&]; 231 | Scan[(AppendTo[toInsert, #+1])&, poss]; 232 | 233 | (* 234 | Insert space for 2 .a 235 | *) 236 | poss = Position[tokens, LeafNode[Integer | Real | Rational, _, _]]; 237 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Dot | Token`DotDot | Token`DotDotDot, _, _]])&]; 238 | Scan[(AppendTo[toInsert, #+1])&, poss]; 239 | 240 | (* 241 | Insert space for a_ .b 242 | 243 | This used to also check for UnderDot, as in: a_. .b 244 | 245 | But this behavior was fixed in 12.2 and a_. .b may now be minified to a_..b 246 | 247 | FIXME: a_b.. is formatted as a_b .. because both a_ and a_b are converted to LeafNode[PatternBlank] 248 | but we really only care about inserting a space after a_ 249 | Maybe convert a_b to something different than LeafNode[PatternBlank] ? 250 | *) 251 | poss = Position[tokens, LeafNode[Token`Under | PatternBlank, _, _]]; 252 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Dot | Token`DotDot | Token`DotDotDot, _, _]])&]; 253 | Scan[(AppendTo[toInsert, #+1])&, poss]; 254 | 255 | (* 256 | Insert space for a_..b 257 | *) 258 | poss = Position[tokens, LeafNode[Token`UnderDot | PatternOptionalDefault, _, _]]; 259 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Dot | Token`DotDot | Token`DotDotDot, _, _]])&]; 260 | Scan[(AppendTo[toInsert, #+1])&, poss]; 261 | 262 | (* 263 | Insert space for a - -b 264 | *) 265 | poss = Position[tokens, LeafNode[Token`Minus, _, _]]; 266 | poss1 = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Minus | Token`MinusMinus, _, _]])&]; 267 | Scan[(AppendTo[toInsert, #+1])&, poss1]; 268 | (* 269 | Insert space for a - -1 270 | *) 271 | poss1 = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Integer | Real | Rational, str_ /; StringStartsQ[str, "-"], _]])&]; 272 | Scan[(AppendTo[toInsert, #+1])&, poss1]; 273 | 274 | (* 275 | Insert space for a! ! 276 | *) 277 | poss = Position[tokens, LeafNode[Token`Bang, _, _]]; 278 | poss1 = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Bang | Token`BangBang | Token`Equal | Token`BangEqual | Token`EqualEqual | Token`EqualEqualEqual, _, _]])&]; 279 | Scan[(AppendTo[toInsert, #+1])&, poss1]; 280 | 281 | (* 282 | Insert space for a + +b 283 | *) 284 | poss = Position[tokens, LeafNode[Token`Plus, _, _]]; 285 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Plus | Token`PlusPlus, _, _]])&]; 286 | Scan[(AppendTo[toInsert, #+1])&, poss]; 287 | 288 | (* 289 | Insert space for a & & + b 290 | *) 291 | poss = Position[tokens, LeafNode[Token`Amp, _, _]]; 292 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Amp, _, _]])&]; 293 | Scan[(AppendTo[toInsert, #+1])&, poss]; 294 | 295 | (* 296 | Insert space for a < << b 297 | *) 298 | poss = Position[tokens, LeafNode[Token`Less, _, _]]; 299 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`LessLess, _, _]])&]; 300 | Scan[(AppendTo[toInsert, #+1])&, poss]; 301 | 302 | (* 303 | Insert space for a >> b ! 304 | 305 | tutorial/OperatorInputForms 306 | File Names 307 | *) 308 | poss = Position[tokens, LeafNode[String, str_ /; !StringStartsQ[str, "\""], _]]; 309 | poss = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Bang, _, _]])&]; 310 | Scan[(AppendTo[toInsert, #+1])&, poss]; 311 | 312 | toInsert = ReverseSort[toInsert]; 313 | 314 | If[$Debug, 315 | Print["toInsert: ", toInsert]; 316 | ]; 317 | 318 | tokens = Fold[Function[{toks, pos}, Insert[toks, LeafNode[Whitespace, " ", <||>], pos]], tokens, toInsert]; 319 | 320 | 321 | (* 322 | Now replace implicit Times with a space 323 | 324 | If the implicit Times is at the end of a line, then insert actual * character 325 | *) 326 | poss = Position[tokens, LeafNode[Token`Fake`ImplicitTimes, _, _]]; 327 | poss1 = Select[poss, (#[[1]] < Length[tokens] && MatchQ[tokens[[#[[1]]+1]], LeafNode[Token`Newline, _, _]])&]; 328 | poss2 = Complement[poss, poss1]; 329 | 330 | tokens = ReplacePart[tokens, poss1 -> LeafNode[Token`Star, "*", <||>]]; 331 | tokens = ReplacePart[tokens, poss2 -> LeafNode[Token`Fake`ImplicitTimes, " ", <||>]]; 332 | 333 | changed = !empty[toInsert] || !empty[poss]; 334 | 335 | {tokens, changed} 336 | ] 337 | 338 | 339 | 340 | (* 341 | It is convenient to have a function that does: 342 | 343 | betterRiffle[{1}, {2}] => {1} 344 | 345 | Unfortunately, Riffle does this: 346 | Riffle[{1}, {2}] => {1, 2} 347 | 348 | So introduce betterRiffle 349 | *) 350 | betterRiffle[{a_}, _] := {a} 351 | 352 | betterRiffle[a_, b_] := Riffle[a, b] 353 | 354 | 355 | 356 | (* 357 | surround[list, x] => surround the elements of list with x 358 | 359 | This is useful for surrounding comments with newlines 360 | But if there are no comments, then no newlines are injected 361 | 362 | surround[{}, x] => {} 363 | surround[{c1, c2, c3}, x] => {x, c1, x, c2, x, c3, x} 364 | *) 365 | 366 | Options[surround] = { 367 | "AlreadyPresent" -> {} 368 | } 369 | 370 | surround[{}, _, OptionsPattern[]] := {} 371 | 372 | surround[a_, b_, "AlreadyPresent" -> {Before, After}] := Riffle[a, b, {2, -2, 2}] 373 | 374 | surround[a_, b_, "AlreadyPresent" -> {Before}] := Riffle[a, b, {2, -1, 2}] 375 | 376 | surround[a_, b_, "AlreadyPresent" -> {After}] := Riffle[a, b, {1, -2, 2}] 377 | 378 | surround[a_, b_, OptionsPattern[]] := Riffle[a, b, {1, -1, 2}] 379 | 380 | 381 | 382 | removeWhitespaceAndNewlines[cst_] := 383 | DeleteCases[cst, LeafNode[Whitespace | Token`Boxes`MultiWhitespace | Token`Newline, _, _], Infinity] 384 | 385 | 386 | 387 | End[] 388 | 389 | EndPackage[] 390 | -------------------------------------------------------------------------------- /CodeFormatter/Kernel/RemoveLineContinuations.wl: -------------------------------------------------------------------------------- 1 | BeginPackage["CodeFormatter`RemoveLineContinuations`"] 2 | 3 | RemoveSimpleLineContinuations 4 | 5 | RemoveComplexLineContinuations 6 | 7 | RemoveRemainingSimpleLineContinuations 8 | 9 | Begin["`Private`"] 10 | 11 | Needs["CodeFormatter`"] 12 | Needs["CodeFormatter`Utils`"] 13 | Needs["CodeParser`"] 14 | Needs["CodeParser`Utils`"] 15 | 16 | 17 | (* 18 | 19 | Line continuations inside of strings or comments are "complex": 20 | Formatting matters 21 | 22 | All other line continuations are simple: 23 | inside or outside of other tokens 24 | outside of strings or comments 25 | 26 | *) 27 | 28 | 29 | 30 | 31 | RemoveSimpleLineContinuations::usage = "RemoveSimpleLineContinuations[cst] removes simple line continuations from cst." 32 | 33 | RemoveSimpleLineContinuations[cstIn:CallNode[_, _, _]] := 34 | Catch[ 35 | Module[{data, cst}, 36 | 37 | cst = cstIn; 38 | 39 | data = cst[[3]]; 40 | 41 | (* 42 | Only proceed if LineColumn convention 43 | *) 44 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 45 | Throw[cst] 46 | ]; 47 | 48 | removeSimpleLineContinuations[cst] 49 | ]] 50 | 51 | RemoveSimpleLineContinuations[cstIn:_[_, _String, _]] := 52 | Catch[ 53 | Module[{data, cst}, 54 | 55 | cst = cstIn; 56 | 57 | data = cst[[3]]; 58 | 59 | (* 60 | Only proceed if LineColumn convention 61 | *) 62 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 63 | Throw[cst] 64 | ]; 65 | 66 | removeSimpleLineContinuations[cst] 67 | ]] 68 | 69 | RemoveSimpleLineContinuations[cstIn:_[_, _List, _]] := 70 | Catch[ 71 | Module[{data, cst, firstChild, firstChildData}, 72 | 73 | cst = cstIn; 74 | 75 | data = cst[[3]]; 76 | 77 | If[empty[cst[[2]]], 78 | Throw[cst] 79 | ]; 80 | 81 | (* 82 | cst may be a ContainerNode with no source info 83 | so look at first child to determine source convention 84 | *) 85 | firstChild = cst[[2, 1]]; 86 | 87 | If[MissingQ[firstChild], 88 | Throw[cst] 89 | ]; 90 | 91 | firstChildData = firstChild[[3]]; 92 | 93 | (* 94 | Only proceed if LineColumn convention 95 | *) 96 | If[!MatchQ[firstChildData, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 97 | Throw[cst] 98 | ]; 99 | 100 | removeSimpleLineContinuations[cst] 101 | ]] 102 | 103 | removeSimpleLineContinuations[cstIn_] := 104 | Catch[ 105 | Module[{data, cst, tokStartLocs, simpleLineContinuations, grouped, poss, tuples, mapSpecs, 106 | embeddedNewlineStartLocs, extracted, complexLineContinuations}, 107 | 108 | cst = cstIn; 109 | 110 | data = cst[[3]]; 111 | 112 | If[KeyExistsQ[data, "SimpleLineContinuations"], 113 | 114 | (* 115 | -5 is where LeafNode[xxx, xxx, <|Source->{{1,1},{1,1}}|>] is 116 | 117 | -3 is where LeafNode[xxx, xxx, <||>] is 118 | 119 | There may be LeafNodes in metadata such as SyntaxIssues or AbstractSyntaxIssues, so remove those 120 | *) 121 | poss = Position[cst, LeafNode[_, _, _], {-5, -3}]; 122 | poss = Cases[poss, {___Integer}]; 123 | 124 | extracted = Extract[cst, poss]; 125 | 126 | tokStartLocs = #[[3, Key[Source], 1]]& /@ extracted; 127 | 128 | (* 129 | Group by starting SourceLocation 130 | *) 131 | grouped = GroupBy[Transpose[{tokStartLocs, poss}, {2, 1}], #[[1]]&]; 132 | 133 | 134 | 135 | (* 136 | Filter out multiline strings and multiline comments: they are not simple 137 | *) 138 | If[KeyExistsQ[data, "EmbeddedNewlines"], 139 | 140 | embeddedNewlineStartLocs = data["EmbeddedNewlines"]; 141 | 142 | KeyDropFrom[grouped, embeddedNewlineStartLocs] 143 | ]; 144 | 145 | If[KeyExistsQ[data, "ComplexLineContinuations"], 146 | 147 | complexLineContinuations = data["ComplexLineContinuations"]; 148 | 149 | KeyDropFrom[grouped, complexLineContinuations] 150 | ]; 151 | 152 | 153 | 154 | simpleLineContinuations = data["SimpleLineContinuations"]; 155 | 156 | (* 157 | FIXME: use Lookup[] 158 | *) 159 | mapSpecs = Map[ 160 | Function[{contLoc}, 161 | 162 | tuples = grouped[contLoc]; 163 | 164 | (* 165 | The token associated with this location may have been processed away and now missing 166 | *) 167 | If[!MissingQ[tuples], 168 | tuples 169 | , 170 | Nothing 171 | ] 172 | ] 173 | , 174 | simpleLineContinuations 175 | ]; 176 | 177 | mapSpecs = Flatten[mapSpecs, 1]; 178 | 179 | (* 180 | There may be more than 1 simple line continuation, so use FixedPoint 181 | *) 182 | cst = MapAt[FixedPoint[removeSimpleLineContinuation, #]&, cst, mapSpecs[[All, 2]]]; 183 | 184 | If[empty[simpleLineContinuations], 185 | KeyDropFrom[data, "SimpleLineContinuations"] 186 | , 187 | data["SimpleLineContinuations"] = simpleLineContinuations 188 | ]; 189 | 190 | cst[[3]] = data; 191 | ]; 192 | 193 | cst 194 | ]] 195 | 196 | RemoveComplexLineContinuations::usage = "RemoveComplexLineContinuations[cst] removes complex line continuations from cst." 197 | 198 | RemoveComplexLineContinuations[cstIn:CallNode[_, _, _]] := 199 | Catch[ 200 | Module[{data, cst, tokStartLocs, grouped, poss, tuples, mapSpecs, 201 | extracted, complexLineContinuations, firstChildData}, 202 | 203 | cst = cstIn; 204 | 205 | data = cst[[3]]; 206 | 207 | (* 208 | Only proceed if LineColumn convention 209 | *) 210 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 211 | Throw[cst] 212 | ]; 213 | 214 | removeComplexLineContinuations[cst] 215 | ]] 216 | 217 | RemoveComplexLineContinuations[cstIn:_[_, _String, _]] := 218 | Catch[ 219 | Module[{data, cst, tokStartLocs, grouped, poss, tuples, mapSpecs, 220 | extracted, complexLineContinuations, firstChildData}, 221 | 222 | cst = cstIn; 223 | 224 | data = cst[[3]]; 225 | 226 | (* 227 | Only proceed if LineColumn convention 228 | *) 229 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 230 | Throw[cst] 231 | ]; 232 | 233 | removeComplexLineContinuations[cst] 234 | ]] 235 | 236 | RemoveComplexLineContinuations[cstIn:_[_, _List, _]] := 237 | Catch[ 238 | Module[{data, cst, firstChild, firstChildData}, 239 | 240 | cst = cstIn; 241 | 242 | data = cst[[3]]; 243 | 244 | If[empty[cst[[2]]], 245 | Throw[cst] 246 | ]; 247 | 248 | (* 249 | cst may be a ContainerNode with no source info 250 | so look at first child to determine source convention 251 | *) 252 | firstChild = cst[[2, 1]]; 253 | 254 | If[MissingQ[firstChild], 255 | Throw[cst] 256 | ]; 257 | 258 | firstChildData = firstChild[[3]]; 259 | 260 | (* 261 | Only proceed if LineColumn convention 262 | *) 263 | If[!MatchQ[firstChildData, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 264 | Throw[cst] 265 | ]; 266 | 267 | removeComplexLineContinuations[cst] 268 | ]] 269 | 270 | removeComplexLineContinuations[cstIn_] := 271 | Catch[ 272 | Module[{data, cst, tokStartLocs, grouped, poss, tuples, mapSpecs, 273 | extracted, complexLineContinuations}, 274 | 275 | cst = cstIn; 276 | 277 | data = cst[[3]]; 278 | 279 | If[KeyExistsQ[data, "ComplexLineContinuations"], 280 | 281 | (* 282 | -5 is where LeafNode[xxx, xxx, <|Source->{{1,1},{1,1}}|>] is 283 | 284 | -3 is where LeafNode[xxx, xxx, <||>] is 285 | 286 | There may be LeafNodes in metadata such as SyntaxIssues or AbstractSyntaxIssues, so remove those 287 | *) 288 | 289 | (* 290 | Only search for strings 291 | 292 | for comments, just leave alone 293 | 294 | it's not a "real" continuation; it could be the result of ASCII art or something 295 | *) 296 | poss = Position[cst, LeafNode[String, _, _], {-5, -3}]; 297 | poss = Cases[poss, {___Integer}]; 298 | 299 | extracted = Extract[cst, poss]; 300 | 301 | tokStartLocs = #[[3, Key[Source], 1]]& /@ extracted; 302 | 303 | (* 304 | Group by starting SourceLocation 305 | *) 306 | grouped = GroupBy[Transpose[{tokStartLocs, poss}, {2, 1}], #[[1]]&]; 307 | 308 | complexLineContinuations = data["ComplexLineContinuations"]; 309 | 310 | (* 311 | FIXME: use Lookup[] 312 | *) 313 | mapSpecs = Map[ 314 | Function[{contLoc}, 315 | 316 | tuples = grouped[contLoc]; 317 | 318 | (* 319 | The token associated with this location may have been processed away and now missing 320 | *) 321 | If[!MissingQ[tuples], 322 | tuples 323 | , 324 | Nothing 325 | ] 326 | ] 327 | , 328 | complexLineContinuations 329 | ]; 330 | 331 | mapSpecs = Flatten[mapSpecs, 1]; 332 | 333 | cst = MapAt[removeComplexLineContinuations, cst, mapSpecs[[All, 2]]]; 334 | 335 | KeyDropFrom[data, "ComplexLineContinuations"]; 336 | 337 | cst[[3]] = data; 338 | ]; 339 | 340 | cst 341 | ]] 342 | 343 | (* 344 | There may be "simple line continuations" that were left behind because they belonged to a multiline string or comment 345 | 346 | Ex: 347 | 348 | x + \ 349 | "abc 350 | def" 351 | 352 | We remove the simple line continuation here, but we DO NOT remove the preceding whitespace 353 | 354 | Note: we only care about the LAST preceding whitespace 355 | 356 | Ex: 357 | 358 | x + \ 359 | \ 360 | "abc 361 | def" 362 | 363 | the last preceding whitespace is 3 spaces, so keep that 364 | 365 | Also recompute the Source of the multiline token to reflect the new whitespace start 366 | 367 | Need to recompute Source because it is used later 368 | *) 369 | RemoveRemainingSimpleLineContinuations::usage = 370 | "RemoveRemainingSimpleLineContinuations[cst] removes simple line continuations from cst." 371 | 372 | RemoveRemainingSimpleLineContinuations[cstIn:CallNode[_, _, _]] := 373 | Catch[ 374 | Module[{data, cst}, 375 | 376 | cst = cstIn; 377 | 378 | data = cst[[3]]; 379 | 380 | (* 381 | Only proceed if LineColumn convention 382 | *) 383 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 384 | Throw[cst] 385 | ]; 386 | 387 | removeRemainingSimpleLineContinuations[cst] 388 | ]] 389 | 390 | RemoveRemainingSimpleLineContinuations[cstIn:_[_, _String, _]] := 391 | Catch[ 392 | Module[{data, cst}, 393 | 394 | cst = cstIn; 395 | 396 | data = cst[[3]]; 397 | 398 | (* 399 | Only proceed if LineColumn convention 400 | *) 401 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 402 | Throw[cst] 403 | ]; 404 | 405 | removeRemainingSimpleLineContinuations[cst] 406 | ]] 407 | 408 | RemoveRemainingSimpleLineContinuations[cstIn:_[_, _List, _]] := 409 | Catch[ 410 | Module[{data, cst, firstChild, firstChildData}, 411 | 412 | cst = cstIn; 413 | 414 | data = cst[[3]]; 415 | 416 | If[empty[cst[[2]]], 417 | Throw[cst] 418 | ]; 419 | 420 | firstChild = cst[[2, 1]]; 421 | 422 | If[MissingQ[firstChild], 423 | Throw[cst] 424 | ]; 425 | 426 | firstChildData = firstChild[[3]]; 427 | 428 | (* 429 | Only proceed if LineColumn convention 430 | *) 431 | If[!MatchQ[firstChildData, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 432 | Throw[cst] 433 | ]; 434 | 435 | removeRemainingSimpleLineContinuations[cst] 436 | ]] 437 | 438 | removeRemainingSimpleLineContinuations[cstIn_] := 439 | Catch[ 440 | Module[{data, cst, tokStartLocs, simpleLineContinuations, grouped, poss, tuples, mapSpecs, extracted}, 441 | 442 | cst = cstIn; 443 | 444 | data = cst[[3]]; 445 | 446 | If[KeyExistsQ[data, "SimpleLineContinuations"], 447 | 448 | (* 449 | -5 is where LeafNode[xxx, xxx, <|Source->{{1,1},{1,1}}|>] is 450 | 451 | -3 is where LeafNode[xxx, xxx, <||>] is 452 | 453 | There may be LeafNodes in metadata such as SyntaxIssues or AbstractSyntaxIssues, so remove those 454 | *) 455 | poss = Position[cst, LeafNode[_, _, _], {-5, -3}]; 456 | poss = Cases[poss, {___Integer}]; 457 | 458 | extracted = Extract[cst, poss]; 459 | 460 | tokStartLocs = #[[3, Key[Source], 1]]& /@ extracted; 461 | 462 | (* 463 | Group by starting SourceLocation 464 | *) 465 | grouped = GroupBy[Transpose[{tokStartLocs, poss}, {2, 1}], #[[1]]&]; 466 | 467 | simpleLineContinuations = data["SimpleLineContinuations"]; 468 | 469 | (* 470 | FIXME: use Lookup[] 471 | *) 472 | mapSpecs = Map[ 473 | Function[{contLoc}, 474 | 475 | tuples = grouped[contLoc]; 476 | 477 | (* 478 | The token associated with this location may have been processed away and now missing 479 | *) 480 | If[!MissingQ[tuples], 481 | tuples 482 | , 483 | Nothing 484 | ] 485 | ] 486 | , 487 | simpleLineContinuations 488 | ]; 489 | 490 | mapSpecs = Flatten[mapSpecs, 1]; 491 | 492 | (* 493 | There may be more than 1 simple line continuation, so use FixedPoint 494 | *) 495 | cst = MapAt[FixedPoint[removeRemainingSimpleLineContinuation, #]&, cst, mapSpecs[[All, 2]]]; 496 | 497 | KeyDropFrom[data, "SimpleLineContinuations"]; 498 | 499 | cst[[3]] = data; 500 | ]; 501 | 502 | cst 503 | ]] 504 | 505 | End[] 506 | 507 | EndPackage[] 508 | -------------------------------------------------------------------------------- /CodeFormatter/Generate/Palette.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | If[!MemberQ[$Path, #], PrependTo[$Path, #]]&[DirectoryName[$InputFileName, 3]] 4 | 5 | BeginPackage["CodeFormatter`Generate`Palette`"] 6 | 7 | Begin["`Private`"] 8 | 9 | (* 10 | auto-load any symbols that require the PacletManager that may appear later 11 | 12 | AppearanceRules appears in CodeFormatter`Generate`UIElements` 13 | 14 | this prevents e.g. "Get::noopen: Cannot open Forms`." messages when building 15 | 16 | related bugs: 415177 17 | *) 18 | AppearanceRules 19 | (* 20 | Do not allow PacletManager to participate in finding `Generate` files 21 | 22 | PacletManager will find e.g. CodeParser/Kernel/TokenEnum.wl when asked to find CodeParser`Generate`TokenEnum` 23 | 24 | related issues: PACMAN-54 25 | *) 26 | Block[{Internal`PacletFindFile = Null&}, 27 | Needs["CodeFormatter`Generate`UIElements`"]; 28 | Needs["CodeTools`Generate`GenerateSources`"]; 29 | ] 30 | 31 | 32 | checkBuildDir[] 33 | 34 | 35 | generatePalette[] := 36 | Module[{nb, res}, 37 | 38 | Print["UsingFrontEnd... \[WatchIcon]"]; 39 | 40 | UsingFrontEnd[ 41 | 42 | nb = 43 | CreatePalette[ Deploy @ 44 | Highlighted[ 45 | DynamicModule[{semicolons, operators, groups, commas, ctrlStruct, scopingStruct, comments, activePresetKey}, 46 | Column[{ 47 | FormatButton[{dialogSuggestedWidth, 25}], 48 | 49 | DynamicWrapper[(* DynamicWrapper is part of fix 402825 *) 50 | #, 51 | {semicolons, operators, groups, commas, ctrlStruct, scopingStruct, comments, CodeFormatter`$InteractiveAiriness} = 52 | Lookup[ 53 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], 54 | { 55 | "NewlinesBetweenSemicolons", "NewlinesBetweenOperators", "NewlinesInGroups", "NewlinesBetweenCommas", "NewlinesInControl", 56 | "NewlinesInScoping", "NewlinesInComments", "Airiness"}, 57 | Automatic], 58 | TrackedSymbols :> {} (* only trigger if the CurrentValue changes *) 59 | ]& @ 60 | AirinessControl[(* includes AirinessSlider and newline controls hidden behind an opener *) 61 | Dynamic[ 62 | CodeFormatter`$InteractiveAiriness, 63 | { 64 | Automatic, 65 | (* this fires on slider mouse up but after internal side effect of setting "CodeFormat" CurrentValue key-values *) 66 | Function[{val, expr}, 67 | expr = val; 68 | If[$VersionNumber >= 12.2, CodeFormatter`Notebooks`formatSelectedCell[]], 69 | HoldAll]}], 70 | Dynamic[CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "CodeFormatter", "ShowLinebreakRules"}, False]], 71 | Dynamic[{semicolons, operators, groups, commas, ctrlStruct, scopingStruct, comments}]], 72 | 73 | Style[tr["IndentationLabel"], "CodeFormatterText"], 74 | 75 | (* This needs a Spacings override so it's not too far from the IndentationLabel *) 76 | DynamicWrapper[(* DynamicWrapper is part of fix 402825 *) 77 | #, 78 | {CodeFormatter`$InteractiveIndentationCharacter, CodeFormatter`$InteractiveTabWidth} = 79 | Lookup[ 80 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], 81 | {"InteractiveIndentationCharacter", "InteractiveTabWidth"}, 82 | Automatic], 83 | TrackedSymbols :> {} (* only trigger if the CurrentValue changes *) 84 | ]& @ 85 | IndentationMenu[ 86 | Dynamic[CodeFormatter`$InteractiveIndentationCharacter], 87 | Function[{val}, If[$VersionNumber >= 12.2, CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "InteractiveIndentationCharacter"}] = val]], 88 | Dynamic[CodeFormatter`$InteractiveTabWidth], 89 | Function[{val}, If[$VersionNumber >= 12.2, CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "InteractiveTabWidth"}] = val]], 90 | Function[If[$VersionNumber >= 12.2, CodeFormatter`Notebooks`formatSelectedCell[]]] 91 | ], 92 | 93 | PresetControls[ 94 | Dynamic[activePresetKey], 95 | Dynamic[{semicolons, operators, groups, commas, ctrlStruct, scopingStruct, comments}], 96 | Dynamic[{CodeFormatter`$InteractiveIndentationCharacter, CodeFormatter`$InteractiveTabWidth}] 97 | ] 98 | }, ItemSize -> {0, 0}, Spacings -> {{}, {{{2}}, 4 -> 0.5}}, Alignment -> Left], 99 | 100 | SaveDefinitions -> True, 101 | 102 | Initialization :> 103 | Module[{opts}, 104 | 105 | Needs["CodeFormatter`Notebooks`"]; 106 | 107 | If[$VersionNumber >= 12.2, 108 | 109 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "InteractiveReparse"}] = True; 110 | opts = Replace[CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], Except[_Association] -> <||>]; 111 | 112 | (* fill the options with those from the last used preset if it exists; override values from the previous session *) 113 | activePresetKey = AbsoluteCurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "LastPresetUsed"}, None]; 114 | If[KeyExistsQ[AbsoluteCurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "Presets"}, <||>], activePresetKey], 115 | opts = <|opts, AbsoluteCurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "Presets", activePresetKey}]|> 116 | ]; 117 | (* "FormatMethod" is saved in a preset so don't redefine it if not needed *) 118 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "FormatMethod"}] = Lookup[opts, "FormatMethod", "AirinessSlider"]; 119 | 120 | (* If a preset exists then these symbols take the existing preset values, or the indicated default if no preset or preset value exists *) 121 | CodeFormatter`$InteractiveAiriness = Lookup[opts, "Airiness", 0]; 122 | CodeFormatter`$InteractiveTabWidth = Lookup[opts, "InteractiveTabWidth", "4"]; 123 | CodeFormatter`$InteractiveIndentationCharacter = Lookup[opts, "InteractiveIndentationCharacter", "space"]; 124 | CodeFormatter`$InteractiveReparse = Lookup[opts, "InteractiveReparse", True]; 125 | 126 | semicolons = Lookup[opts, "NewlinesBetweenSemicolons", Automatic]; 127 | operators = Lookup[opts, "NewlinesBetweenOperators", Automatic]; 128 | groups = Lookup[opts, "NewlinesInGroups", Automatic]; 129 | commas = Lookup[opts, "NewlinesBetweenCommas", Automatic]; 130 | ctrlStruct = Lookup[opts, "NewlinesInControl", Automatic]; 131 | scopingStruct = Lookup[opts, "NewlinesInScoping", Automatic]; 132 | comments = Lookup[opts, "NewlinesInComments", Automatic]; 133 | 134 | (* Corner case: saving a preset relies on CurrentValues being accurate, so set them at initialization to the current state of the interface *) 135 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}] = <| 136 | (* don't blow away existing presets or other non-interface values *) 137 | CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], 138 | (* overwrite relevant values with current state of interface *) 139 | "InteractiveIndentationCharacter" -> CodeFormatter`$InteractiveIndentationCharacter, 140 | "InteractiveTabWidth" -> CodeFormatter`$InteractiveTabWidth, 141 | "Airiness" -> CodeFormatter`$InteractiveAiriness, 142 | "NewlinesBetweenSemicolons" -> semicolons, 143 | "NewlinesBetweenOperators" -> operators, 144 | "NewlinesInGroups" -> groups, 145 | "NewlinesBetweenCommas" -> commas, 146 | "NewlinesInControl" -> ctrlStruct, 147 | "NewlinesInScoping" -> scopingStruct, 148 | "NewlinesInComments" -> comments|>; 149 | ]; 150 | ] 151 | ], 152 | Background -> BackgroundCol, RoundingRadius -> 0, FrameMargins -> {{8, 8}, {8, 8}} 153 | ] 154 | , 155 | (* 156 | WindowTitle->Dynamic[FEPrivate`FrontEndResource["CodeFormatterStrings", "PaletteTitle"]] 157 | 158 | Cannot use FrontEndResource for WindowTitle because $Failed will appear in FE Palettes Menu 159 | 160 | Related bugs: 401490 161 | *) 162 | WindowTitle -> FEPrivate`FrontEndResource["CodeFormatterStrings", "PaletteTitle"], 163 | Background -> BackgroundCol, 164 | MenuSortingValue -> 1150, (* Group the Code palettes together -- 416653 *) 165 | Saveable -> False, 166 | StyleDefinitions -> 167 | Notebook[ 168 | { 169 | Cell[StyleData[StyleDefinitions -> "Palette.nb"]], 170 | Cell[StyleData["CodeFormatterHighlightColor"], 171 | System`EdgeColor -> GrayLevel[0.2], 172 | FontColor -> GrayLevel[0.2], (* for FrameStyle *) 173 | System`LineColor -> GrayLevel[0.2], (* for joined Line elements *) 174 | Opacity[1.]], 175 | Cell[StyleData["CodeFormatterNewlineColor"], 176 | FontColor -> RGBColor[1, 0.5, 0]], 177 | Cell[StyleData["CodeFormatterNewlineFillColor"], 178 | System`FrontFaceColor -> RGBColor[1, 0.5, 0], 179 | Opacity[1.]], 180 | Cell[StyleData["CodeFormatterNewlineFillColorSubtle"], 181 | System`FrontFaceColor -> RGBColor[0.988235, 0.882353, 0.780392], 182 | Opacity[1.]], 183 | Cell[StyleData["CodeFormatterTextBase"], 184 | FontFamily -> "Source Sans Pro", 185 | FontSize -> 13, 186 | FontWeight -> Plain, 187 | FontSlant -> Plain, 188 | LinebreakAdjustments -> {1, 10, 1, 0, 1}, 189 | LineIndent -> 0, 190 | PrivateFontOptions -> {"OperatorSubstitution" -> False}], 191 | Cell[StyleData["CodeFormatterText", StyleDefinitions -> StyleData["CodeFormatterTextBase"]], 192 | FontColor -> GrayLevel[0.2]], 193 | Cell[StyleData["ButtonCommonOptions"], 194 | FrameBoxOptions -> { 195 | Alignment -> Center, 196 | FrameMargins -> 4, 197 | FrameStyle -> None, 198 | ImageSize -> {{38, Full}, {19.5, Full}}, 199 | RoundingRadius -> 3}], 200 | Cell[StyleData["ButtonGray2Normal", StyleDefinitions -> StyleData["ButtonCommonOptions"]], 201 | FontColor->GrayLevel[0.2], 202 | Background->GrayLevel[1], 203 | FrameBoxOptions->{FrameStyle->{GrayLevel[166/255]}}], 204 | Cell[StyleData["ButtonGray2Hover", StyleDefinitions -> StyleData["ButtonCommonOptions"]], 205 | FontColor->GrayLevel[0.2], 206 | Background->GrayLevel[1], 207 | FrameBoxOptions->{FrameStyle->{GrayLevel[0.2]}}], 208 | Cell[StyleData["ButtonGray2Pressed", StyleDefinitions -> StyleData["ButtonCommonOptions"]], 209 | FontColor->GrayLevel[1], 210 | Background->GrayLevel[166/255]], 211 | Cell[StyleData["ButtonGray2Disabled", StyleDefinitions -> StyleData["ButtonCommonOptions"]], 212 | FontColor->GrayLevel[0.7, 0.5], 213 | Background->GrayLevel[1, 0.5], 214 | FrameBoxOptions->{FrameStyle->{GrayLevel[166/255, 0.5]}}]}, 215 | StyleDefinitions -> "PrivateStylesheetFormatting.nb"] 216 | ]; 217 | 218 | If[!MatchQ[nb, _NotebookObject], 219 | Print["CreatePalette failed: ", nb]; 220 | Quit[1] 221 | ]; 222 | 223 | Quiet[DeleteFile[FileNameJoin[{buildDir, "paclet", "CodeFormatter", "FrontEnd", "Palettes", "CodeFormatter.nb"}]], DeleteFile::fdnfnd]; 224 | 225 | Print["saving CodeFormatter.nb"]; 226 | res = NotebookSave[nb, FileNameJoin[{buildDir, "paclet", "CodeFormatter", "FrontEnd", "Palettes", "CodeFormatter.nb"}]]; 227 | 228 | Print[res]; 229 | 230 | If[res =!= Null, 231 | Quit[1] 232 | ]; 233 | ]; 234 | 235 | (* 236 | NotebookSave may fail, but give no indication, 237 | so need to explicitly check that file was created 238 | bug 429251 239 | *) 240 | If[!FileExistsQ[FileNameJoin[{buildDir, "paclet", "CodeFormatter", "FrontEnd", "Palettes", "CodeFormatter.nb"}]], 241 | Quit[1] 242 | ]; 243 | 244 | Print["Done UsingFrontEnd"]; 245 | ] 246 | 247 | generate[] := ( 248 | 249 | Print["Generating Palette..."]; 250 | 251 | generatePalette[]; 252 | 253 | Print["Done Palette"] 254 | ) 255 | 256 | If[!StringQ[script], 257 | Quit[1] 258 | ] 259 | If[AbsoluteFileName[script] === AbsoluteFileName[$InputFileName], 260 | generate[] 261 | ] 262 | 263 | End[] 264 | 265 | EndPackage[] 266 | -------------------------------------------------------------------------------- /CodeFormatter/Documentation/English/Guides/CodeFormatter.nb: -------------------------------------------------------------------------------- 1 | (* Content-type: application/vnd.wolfram.mathematica *) 2 | 3 | (*** Wolfram Notebook File ***) 4 | (* http://www.wolfram.com/nb *) 5 | 6 | (* CreatedBy='Mathematica 12.1' *) 7 | 8 | (*CacheID: 234*) 9 | (* Internal cache information: 10 | NotebookFileLineBreakTest 11 | NotebookFileLineBreakTest 12 | NotebookDataPosition[ 158, 7] 13 | NotebookDataLength[ 14194, 368] 14 | NotebookOptionsPosition[ 7457, 231] 15 | NotebookOutlinePosition[ 8795, 264] 16 | CellTagsIndexPosition[ 8752, 261] 17 | WindowFrame->Normal*) 18 | 19 | (* Beginning of Notebook Content *) 20 | Notebook[{ 21 | Cell[TextData[{ 22 | "New in: ", 23 | Cell["12.1", "HistoryData", 24 | CellTags->"New",ExpressionUUID->"aa597994-a9c9-4826-8fe6-2f585b9cf8eb"], 25 | " | Modified in: ", 26 | Cell[" ", "HistoryData", 27 | CellTags->"Modified",ExpressionUUID->"e4debf67-6462-43a1-92f7-acf384d99cab"], 28 | " | Obsolete in: ", 29 | Cell[" ", "HistoryData", 30 | CellTags->"Obsolete",ExpressionUUID->"14155e67-7e10-452d-abe9-8db6a8102d6a"], 31 | " | Excised in: ", 32 | Cell[" ", "HistoryData", 33 | CellTags->"Excised",ExpressionUUID->"3103b75a-7cd7-4257-b533-7d45e6e3e2b6"] 34 | }], "History", 35 | CellID->1247902091,ExpressionUUID->"8d440145-306a-4c66-8b70-299b2e2ab580"], 36 | 37 | Cell["Created by: brenton on 05-11-2020 16:32:13", "AuthorDate", 38 | CellID->1197901439,ExpressionUUID->"99a69f33-9da4-4ae2-bb35-622a666acdf1"], 39 | 40 | Cell[CellGroupData[{ 41 | 42 | Cell["Categorization", "CategorizationSection", 43 | CellID->1122911449,ExpressionUUID->"47759e13-13e4-4024-85ee-f7682e3ff9ea"], 44 | 45 | Cell["Guide", "Categorization", 46 | CellLabel->"Entity Type", 47 | CellID->686433507,ExpressionUUID->"4085f135-8f0b-441e-8500-fe1bb940038a"], 48 | 49 | Cell["CodeFormatter Package", "Categorization", 50 | CellLabel->"Paclet Name", 51 | CellID->605800465,ExpressionUUID->"7cc1be9d-285b-449d-8adb-71312dd3b3e6"], 52 | 53 | Cell["CodeFormatter`", "Categorization", 54 | CellLabel->"Context", 55 | CellID->468444828,ExpressionUUID->"b4d45425-3b60-4ab4-9e2e-153eeb04874a"], 56 | 57 | Cell["CodeFormatter/guide/CodeFormatter", "Categorization", 58 | CellLabel->"URI",ExpressionUUID->"5d43fe39-d411-4694-b810-9c568bcdf484"] 59 | }, Closed]], 60 | 61 | Cell[CellGroupData[{ 62 | 63 | Cell["Synonyms", "SynonymsSection", 64 | CellID->1427418553,ExpressionUUID->"a8552192-fed1-48e4-b8ad-acd9be693f71"], 65 | 66 | Cell["XXXX", "Synonyms", 67 | CellID->1251652828,ExpressionUUID->"19cfc894-bb13-4f3e-851e-9e031568e4ec"] 68 | }, Closed]], 69 | 70 | Cell[CellGroupData[{ 71 | 72 | Cell["Keywords", "KeywordsSection", 73 | CellID->1427428552,ExpressionUUID->"15e94a9f-4641-4e39-b4ba-21f690b37225"], 74 | 75 | Cell["XXXX", "Keywords", 76 | CellID->1251852827,ExpressionUUID->"a840abd8-4385-4b5d-abc2-a8adf6522f44"] 77 | }, Closed]], 78 | 79 | Cell[CellGroupData[{ 80 | 81 | Cell["Details", "DetailsSection", 82 | CellID->307771771,ExpressionUUID->"b368ca46-f21a-4c83-b09e-76a3c028266e"], 83 | 84 | Cell["XXXX", "Details", 85 | CellLabel->"Lead", 86 | CellID->383431442,ExpressionUUID->"56f942e0-589d-45e8-aa65-63f8d869e967"], 87 | 88 | Cell["XXXX", "Details", 89 | CellLabel->"Developers", 90 | CellID->350963985,ExpressionUUID->"b9463758-e551-4c27-838d-c5ea17454174"], 91 | 92 | Cell["XXXX", "Details", 93 | CellLabel->"Authors", 94 | CellID->96477600,ExpressionUUID->"cb2c5c68-8a94-49bf-89e6-39deb6a58415"], 95 | 96 | Cell["XXXX", "Details", 97 | CellLabel->"Feature Name", 98 | CellID->529741587,ExpressionUUID->"a716d026-bac3-475e-97fc-1e3a8dc3e18c"], 99 | 100 | Cell["XXXX", "Details", 101 | CellLabel->"QA", 102 | CellID->45519574,ExpressionUUID->"3dfd84af-d05d-48bf-9e19-6d32d47fda7a"], 103 | 104 | Cell["XXXX", "Details", 105 | CellLabel->"DA", 106 | CellID->139713968,ExpressionUUID->"c8783e9d-cc6b-4e3c-b304-b6f22c5b1fad"], 107 | 108 | Cell["XXXX", "Details", 109 | CellLabel->"Docs", 110 | CellID->129138584,ExpressionUUID->"4f2570b6-b6e9-43a8-b60d-c795b105e277"], 111 | 112 | Cell["XXXX", "Details", 113 | CellLabel->"Features Page Notes", 114 | CellID->5902045,ExpressionUUID->"a35d1e5b-1292-4b38-90ea-1d8471c12f85"], 115 | 116 | Cell["XXXX", "Details", 117 | CellLabel->"Comments", 118 | CellID->240026365,ExpressionUUID->"0dee56da-4dd9-4f66-9799-2f0531594096"] 119 | }, Closed]], 120 | 121 | Cell[CellGroupData[{ 122 | 123 | Cell["Related Web Resources", "WebResourcesSection", 124 | CellChangeTimes->{{3.5563685782844915`*^9, 3.556368581373351*^9}}, 125 | CellID->58909300,ExpressionUUID->"8e02b8fd-e49c-4753-8b00-aaad50ab9daf"], 126 | 127 | Cell["XXXX", "WebResources", 128 | CellLabel->"Training Courses", 129 | CellID->34010858,ExpressionUUID->"be05a11f-32f1-4d02-8394-db4f890f9b07"], 130 | 131 | Cell["XXXX", "WebResources", 132 | CellLabel->"Videos", 133 | CellID->319325756,ExpressionUUID->"352f705c-a8db-472b-a5ec-5a2e46a94db3"], 134 | 135 | Cell["XXXX", "WebResources", 136 | CellLabel->"Demonstrations", 137 | CellID->277281505,ExpressionUUID->"1de01eec-450f-4988-a7c2-413d204f749e"], 138 | 139 | Cell["XXXX", "WebResources", 140 | CellLabel->"Community", 141 | CellID->55925990,ExpressionUUID->"80d7446d-5b0d-44e9-9a07-6f49947c6e41"] 142 | }, Closed]], 143 | 144 | Cell[CellGroupData[{ 145 | 146 | Cell["CodeFormatter", "GuideTitle", 147 | CellChangeTimes->{{3.7982216316725407`*^9, 3.79822164478999*^9}, { 148 | 3.816535745273432*^9, 3.816535745546521*^9}}, 149 | CellID->942062912,ExpressionUUID->"ce315a22-1b4a-489c-a84e-14a333f54807"], 150 | 151 | Cell["\<\ 152 | CodeFormatter is a package for formatting Wolfram Language code.\ 153 | \>", "GuideAbstract", 154 | CellChangeTimes->{{3.798221647412822*^9, 3.7982216703351383`*^9}}, 155 | CellID->2001916300,ExpressionUUID->"e04d964a-e0de-4199-906b-5659a8047e29"] 156 | }, Open ]], 157 | 158 | Cell[CellGroupData[{ 159 | 160 | Cell["", "GuideFunctionsSection", 161 | CellID->1866139230,ExpressionUUID->"d78953ea-9b06-4870-9058-bcf8dcc7d80d"], 162 | 163 | Cell[TextData[{ 164 | Cell[BoxData[ 165 | ButtonBox["CodeFormat", 166 | BaseStyle->"Link", 167 | ButtonData->"paclet:CodeFormatter/ref/CodeFormat"]], "InlineGuideFunction", 168 | ExpressionUUID->"3775080e-1a6f-4614-b5ab-1c0ebd7d8885"], 169 | " \[LongDash] returns a string of formatted WL code." 170 | }], "GuideText", 171 | CellChangeTimes->{{3.7982216761448917`*^9, 3.798221707549184*^9}, { 172 | 3.798221900560666*^9, 3.7982219006969757`*^9}}, 173 | CellID->203374175,ExpressionUUID->"415127a6-756b-4831-ae4d-a971d934af23"], 174 | 175 | Cell[TextData[{ 176 | Cell[BoxData[ 177 | "XXXX"], "InlineGuideFunction",ExpressionUUID-> 178 | "6e523a2a-0c06-4aae-bd3d-34d0cd6caabd"], 179 | " \[LongDash] XXXX" 180 | }], "GuideText", 181 | CellID->1463276848,ExpressionUUID->"2b56be64-d6a8-40f3-badb-3975ad0ff234"], 182 | 183 | Cell[CellGroupData[{ 184 | 185 | Cell["\t", "GuideDelimiter", 186 | CellID->311258892,ExpressionUUID->"a91407d5-e5c6-4d76-b84e-2ba7bee1bc6a"], 187 | 188 | Cell["XXXX . XXXX . ", "InlineGuideFunctionListing", 189 | CellID->58033752,ExpressionUUID->"c42cc31c-5e4c-4d35-8009-532fa5e4ea92"] 190 | }, Open ]] 191 | }, Open ]], 192 | 193 | Cell[CellGroupData[{ 194 | 195 | Cell["Tutorials", "GuideTutorialsSection", 196 | CellID->415694126,ExpressionUUID->"a3d9191b-5a60-4b5a-b67e-164157d85d3a"], 197 | 198 | Cell["XXXX", "GuideTutorial", 199 | CellID->806871991,ExpressionUUID->"2865c3dc-d9eb-4324-ba17-956ca965284c"], 200 | 201 | Cell["XXXX", "GuideTutorial", 202 | CellID->1885805579,ExpressionUUID->"80f6ab1a-9d69-46bf-a617-823d7a0ff3c5"] 203 | }, Open ]], 204 | 205 | Cell[CellGroupData[{ 206 | 207 | Cell["Workflow Guides", "GuideWorkflowGuidesSection", 208 | CellID->25329611,ExpressionUUID->"a98cd77d-7b04-4134-b3dd-d36151cff9b0"], 209 | 210 | Cell["XXXX", "GuideWorkflowGuide", 211 | CellID->672144280,ExpressionUUID->"289696a3-54cc-4ce1-91ae-b2c1ab1748e8"], 212 | 213 | Cell["XXXX", "GuideWorkflowGuide", 214 | CellID->79611718,ExpressionUUID->"d9846a6d-176d-4744-aa8d-6ab8061f3e10"] 215 | }, Open ]], 216 | 217 | Cell[CellGroupData[{ 218 | 219 | Cell["More About", "GuideMoreAboutSection", 220 | CellID->23220180,ExpressionUUID->"85046e26-a7a1-4d48-82a2-74e2badc4739"], 221 | 222 | Cell["XXXX", "GuideMoreAbout", 223 | CellID->1567025153,ExpressionUUID->"362370b0-b051-4d92-8553-4e9f0fcee460"], 224 | 225 | Cell["XXXX", "GuideMoreAbout", 226 | CellID->252299663,ExpressionUUID->"531b2dce-25dc-42ae-83a3-d40eeb672aa3"] 227 | }, Open ]], 228 | 229 | Cell["Related Links", "GuideRelatedLinksSection", 230 | CellID->415694148,ExpressionUUID->"45594b4f-2eba-4476-ad05-14c00c017cbc"] 231 | }, 232 | WindowSize->{700, 770}, 233 | WindowMargins->{{11, Automatic}, {Automatic, 24}}, 234 | TaggingRules->{ 235 | "DocuToolsSettings" -> { 236 | "ShowMetaDataMessage" -> "False", "$ApplicationName" -> "", "$LinkBase" -> 237 | "CodeAssistance", "$ApplicationDirectory" -> 238 | "/Users/brenton/development/stash/COD/codeassistance/CodeAssistance/", 239 | "$DocumentationDirectory" -> 240 | "/Users/brenton/development/stash/COD/codeassistance/CodeAssistance/\ 241 | Documentation/English/"}, 242 | "DocuToolsSettingsInternal" -> { 243 | "$PacletVersion" -> "0.10.2225", "$ApplicationName" -> "Instrumentation", 244 | "$LinkBase" -> "Instrumentation", "$ApplicationDirectory" -> 245 | "/Users/brenton/development/stash/COD/instrumentation/Instrumentation/", 246 | "$DocumentationDirectory" -> 247 | "/Users/brenton/development/stash/COD/instrumentation/Instrumentation/\ 248 | Documentation/English/", "$UseNewPageDialog" -> ""}, "Author" -> "brenton", 249 | "CreationDate" -> "05-11-2020 16:32:13"}, 250 | FrontEndVersion->"13.0 for Mac OS X x86 (64-bit) (October 7, 2021)", 251 | StyleDefinitions->FrontEnd`FileName[{"Wolfram"}, "GuidePageStyles.nb", 252 | CharacterEncoding -> "UTF-8"], 253 | ExpressionUUID->"4bb7a6bc-c5a5-44e0-a35a-28ef914bcc6e" 254 | ] 255 | (* End of Notebook Content *) 256 | 257 | (* Internal cache information *) 258 | (*CellTagsOutline 259 | CellTagsIndex->{} 260 | *) 261 | (*CellTagsIndex 262 | CellTagsIndex->{} 263 | *) 264 | (*NotebookFileOutline 265 | Notebook[{ 266 | Cell[558, 20, 601, 14, 24, "History",ExpressionUUID->"8d440145-306a-4c66-8b70-299b2e2ab580", 267 | CellID->1247902091], 268 | Cell[1162, 36, 140, 1, 20, "AuthorDate",ExpressionUUID->"99a69f33-9da4-4ae2-bb35-622a666acdf1", 269 | CellID->1197901439], 270 | Cell[CellGroupData[{ 271 | Cell[1327, 41, 123, 1, 29, "CategorizationSection",ExpressionUUID->"47759e13-13e4-4024-85ee-f7682e3ff9ea", 272 | CellID->1122911449], 273 | Cell[1453, 44, 133, 2, 30, "Categorization",ExpressionUUID->"4085f135-8f0b-441e-8500-fe1bb940038a", 274 | CellID->686433507], 275 | Cell[1589, 48, 149, 2, 30, "Categorization",ExpressionUUID->"7cc1be9d-285b-449d-8adb-71312dd3b3e6", 276 | CellID->605800465], 277 | Cell[1741, 52, 138, 2, 30, "Categorization",ExpressionUUID->"b4d45425-3b60-4ab4-9e2e-153eeb04874a", 278 | CellID->468444828], 279 | Cell[1882, 56, 133, 1, 30, "Categorization",ExpressionUUID->"5d43fe39-d411-4694-b810-9c568bcdf484"] 280 | }, Closed]], 281 | Cell[CellGroupData[{ 282 | Cell[2052, 62, 111, 1, 19, "SynonymsSection",ExpressionUUID->"a8552192-fed1-48e4-b8ad-acd9be693f71", 283 | CellID->1427418553], 284 | Cell[2166, 65, 100, 1, 19, "Synonyms",ExpressionUUID->"19cfc894-bb13-4f3e-851e-9e031568e4ec", 285 | CellID->1251652828] 286 | }, Closed]], 287 | Cell[CellGroupData[{ 288 | Cell[2303, 71, 111, 1, 19, "KeywordsSection",ExpressionUUID->"15e94a9f-4641-4e39-b4ba-21f690b37225", 289 | CellID->1427428552], 290 | Cell[2417, 74, 100, 1, 19, "Keywords",ExpressionUUID->"a840abd8-4385-4b5d-abc2-a8adf6522f44", 291 | CellID->1251852827] 292 | }, Closed]], 293 | Cell[CellGroupData[{ 294 | Cell[2554, 80, 108, 1, 19, "DetailsSection",ExpressionUUID->"b368ca46-f21a-4c83-b09e-76a3c028266e", 295 | CellID->307771771], 296 | Cell[2665, 83, 118, 2, 30, "Details",ExpressionUUID->"56f942e0-589d-45e8-aa65-63f8d869e967", 297 | CellID->383431442], 298 | Cell[2786, 87, 124, 2, 30, "Details",ExpressionUUID->"b9463758-e551-4c27-838d-c5ea17454174", 299 | CellID->350963985], 300 | Cell[2913, 91, 120, 2, 30, "Details",ExpressionUUID->"cb2c5c68-8a94-49bf-89e6-39deb6a58415", 301 | CellID->96477600], 302 | Cell[3036, 95, 126, 2, 30, "Details",ExpressionUUID->"a716d026-bac3-475e-97fc-1e3a8dc3e18c", 303 | CellID->529741587], 304 | Cell[3165, 99, 115, 2, 30, "Details",ExpressionUUID->"3dfd84af-d05d-48bf-9e19-6d32d47fda7a", 305 | CellID->45519574], 306 | Cell[3283, 103, 116, 2, 30, "Details",ExpressionUUID->"c8783e9d-cc6b-4e3c-b304-b6f22c5b1fad", 307 | CellID->139713968], 308 | Cell[3402, 107, 118, 2, 30, "Details",ExpressionUUID->"4f2570b6-b6e9-43a8-b60d-c795b105e277", 309 | CellID->129138584], 310 | Cell[3523, 111, 131, 2, 30, "Details",ExpressionUUID->"a35d1e5b-1292-4b38-90ea-1d8471c12f85", 311 | CellID->5902045], 312 | Cell[3657, 115, 122, 2, 30, "Details",ExpressionUUID->"0dee56da-4dd9-4f66-9799-2f0531594096", 313 | CellID->240026365] 314 | }, Closed]], 315 | Cell[CellGroupData[{ 316 | Cell[3816, 122, 194, 2, 19, "WebResourcesSection",ExpressionUUID->"8e02b8fd-e49c-4753-8b00-aaad50ab9daf", 317 | CellID->58909300], 318 | Cell[4013, 126, 134, 2, 30, "WebResources",ExpressionUUID->"be05a11f-32f1-4d02-8394-db4f890f9b07", 319 | CellID->34010858], 320 | Cell[4150, 130, 125, 2, 30, "WebResources",ExpressionUUID->"352f705c-a8db-472b-a5ec-5a2e46a94db3", 321 | CellID->319325756], 322 | Cell[4278, 134, 133, 2, 30, "WebResources",ExpressionUUID->"1de01eec-450f-4988-a7c2-413d204f749e", 323 | CellID->277281505], 324 | Cell[4414, 138, 127, 2, 30, "WebResources",ExpressionUUID->"80d7446d-5b0d-44e9-9a07-6f49947c6e41", 325 | CellID->55925990] 326 | }, Closed]], 327 | Cell[CellGroupData[{ 328 | Cell[4578, 145, 226, 3, 77, "GuideTitle",ExpressionUUID->"ce315a22-1b4a-489c-a84e-14a333f54807", 329 | CellID->942062912], 330 | Cell[4807, 150, 241, 4, 27, "GuideAbstract",ExpressionUUID->"e04d964a-e0de-4199-906b-5659a8047e29", 331 | CellID->2001916300] 332 | }, Open ]], 333 | Cell[CellGroupData[{ 334 | Cell[5085, 159, 109, 1, 70, "GuideFunctionsSection",ExpressionUUID->"d78953ea-9b06-4870-9058-bcf8dcc7d80d", 335 | CellID->1866139230], 336 | Cell[5197, 162, 482, 10, 24, "GuideText",ExpressionUUID->"415127a6-756b-4831-ae4d-a971d934af23", 337 | CellID->203374175], 338 | Cell[5682, 174, 236, 6, 23, "GuideText",ExpressionUUID->"2b56be64-d6a8-40f3-badb-3975ad0ff234", 339 | CellID->1463276848], 340 | Cell[CellGroupData[{ 341 | Cell[5943, 184, 103, 1, 26, "GuideDelimiter",ExpressionUUID->"a91407d5-e5c6-4d76-b84e-2ba7bee1bc6a", 342 | CellID->311258892], 343 | Cell[6049, 187, 126, 1, 20, "InlineGuideFunctionListing",ExpressionUUID->"c42cc31c-5e4c-4d35-8009-532fa5e4ea92", 344 | CellID->58033752] 345 | }, Open ]] 346 | }, Open ]], 347 | Cell[CellGroupData[{ 348 | Cell[6224, 194, 117, 1, 72, "GuideTutorialsSection",ExpressionUUID->"a3d9191b-5a60-4b5a-b67e-164157d85d3a", 349 | CellID->415694126], 350 | Cell[6344, 197, 104, 1, 22, "GuideTutorial",ExpressionUUID->"2865c3dc-d9eb-4324-ba17-956ca965284c", 351 | CellID->806871991], 352 | Cell[6451, 200, 105, 1, 22, "GuideTutorial",ExpressionUUID->"80f6ab1a-9d69-46bf-a617-823d7a0ff3c5", 353 | CellID->1885805579] 354 | }, Open ]], 355 | Cell[CellGroupData[{ 356 | Cell[6593, 206, 127, 1, 72, "GuideWorkflowGuidesSection",ExpressionUUID->"a98cd77d-7b04-4134-b3dd-d36151cff9b0", 357 | CellID->25329611], 358 | Cell[6723, 209, 109, 1, 22, "GuideWorkflowGuide",ExpressionUUID->"289696a3-54cc-4ce1-91ae-b2c1ab1748e8", 359 | CellID->672144280], 360 | Cell[6835, 212, 108, 1, 22, "GuideWorkflowGuide",ExpressionUUID->"d9846a6d-176d-4744-aa8d-6ab8061f3e10", 361 | CellID->79611718] 362 | }, Open ]], 363 | Cell[CellGroupData[{ 364 | Cell[6980, 218, 117, 1, 72, "GuideMoreAboutSection",ExpressionUUID->"85046e26-a7a1-4d48-82a2-74e2badc4739", 365 | CellID->23220180], 366 | Cell[7100, 221, 106, 1, 22, "GuideMoreAbout",ExpressionUUID->"362370b0-b051-4d92-8553-4e9f0fcee460", 367 | CellID->1567025153], 368 | Cell[7209, 224, 105, 1, 22, "GuideMoreAbout",ExpressionUUID->"531b2dce-25dc-42ae-83a3-d40eeb672aa3", 369 | CellID->252299663] 370 | }, Open ]], 371 | Cell[7329, 228, 124, 1, 72, "GuideRelatedLinksSection",ExpressionUUID->"45594b4f-2eba-4476-ad05-14c00c017cbc", 372 | CellID->415694148] 373 | } 374 | ] 375 | *) 376 | 377 | -------------------------------------------------------------------------------- /CodeFormatter/Kernel/Fragmentize.wl: -------------------------------------------------------------------------------- 1 | (* ::Package::"Tags"-><|"SuspiciousSessionSymbol" -> <|Enabled -> False|>|>:: *) 2 | 3 | BeginPackage["CodeFormatter`Fragmentize`"] 4 | 5 | Fragmentize 6 | 7 | mergeTemporaryLineContinuations 8 | 9 | Begin["`Private`"] 10 | 11 | Needs["CodeFormatter`"] 12 | Needs["CodeFormatter`Utils`"] 13 | Needs["CodeParser`"] 14 | Needs["CodeParser`Utils`"] 15 | 16 | 17 | (* 18 | Fragmentize leaf nodes 19 | 20 | Fragmentizing is converting: 21 | 22 | LeafNode[String, "abc", <||>] 23 | 24 | into 25 | 26 | LeafNode[String, {FragmentNode[String, "abc", <||>]}, <||>] 27 | 28 | We do NOT want to fragmentize ALL LeafNodes, that would not be intuitive 29 | 30 | Actual leaves will be fragmentized in various ways: 31 | 32 | overlong leafs split on line breaks 33 | 34 | multiline strings have temporary line continuations introduced 35 | 36 | multiline comments have temporary line continuations introduced 37 | 38 | others? 39 | 40 | *) 41 | Fragmentize[cstIn:CallNode[_, _, _]] := 42 | Catch[ 43 | Module[{cst, data}, 44 | 45 | cst = cstIn; 46 | 47 | cst = fragmentizeCommentGroups[cst]; 48 | 49 | data = cst[[3]]; 50 | 51 | (* 52 | Only proceed if LineColumn convention 53 | *) 54 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 55 | Throw[cst] 56 | ]; 57 | 58 | fragmentize[cst] 59 | ]] 60 | 61 | Fragmentize[cstIn:_[_, _String, _]] := 62 | Catch[ 63 | Module[{cst, data}, 64 | 65 | cst = cstIn; 66 | 67 | cst = fragmentizeCommentGroups[cst]; 68 | 69 | data = cst[[3]]; 70 | 71 | (* 72 | Only proceed if LineColumn convention 73 | *) 74 | If[!MatchQ[data, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 75 | Throw[cst] 76 | ]; 77 | 78 | fragmentize[cst] 79 | ]] 80 | 81 | Fragmentize[cstIn:_[_, _List, _]] := 82 | Catch[ 83 | Module[{cst, data, firstChild, firstChildData}, 84 | 85 | cst = cstIn; 86 | 87 | cst = fragmentizeCommentGroups[cst]; 88 | 89 | data = cst[[3]]; 90 | 91 | If[empty[cst[[2]]], 92 | Throw[cst] 93 | ]; 94 | 95 | firstChild = cst[[2, 1]]; 96 | 97 | If[MissingQ[firstChild], 98 | Throw[cst] 99 | ]; 100 | 101 | (* 102 | cst may be a ContainerNode with no source info 103 | so look at first child to determine source convention 104 | *) 105 | firstChildData = firstChild[[3]]; 106 | 107 | (* 108 | Only proceed if LineColumn convention 109 | *) 110 | If[!MatchQ[firstChildData, KeyValuePattern[Source -> {{_, _}, {_, _}}]], 111 | Throw[cst] 112 | ]; 113 | 114 | fragmentize[cst] 115 | ]] 116 | 117 | fragmentize[cstIn_] := 118 | Catch[ 119 | Module[{commentPoss, nonCommentPoss, poss, cst, tokStartLocs, grouped, data, embeddedNewlines, mapSpecs, embeddedNewlinePoss, tuples, 120 | allOtherCommentPoss}, 121 | 122 | cst = cstIn; 123 | 124 | data = cst[[3]]; 125 | 126 | (* 127 | Special case multiline strings and multiline comments with LineColumn convention 128 | 129 | Must preserve the original number of columns preceding the string 130 | 131 | We do this by inserting a newline, then the original number of spaces. 132 | 133 | A line continuation is inserted to help with semantics of inserting a newline (this may not ultimately be needed) 134 | Also, the line continuation helps to communicate the "separateness" of the string or comment 135 | *) 136 | 137 | (* 138 | Used to be just: 139 | 140 | poss = Position[] 141 | 142 | but was split into commentPoss and nonCommentPoss for efficiency purposes later 143 | *) 144 | commentPoss = Position[cst, LeafNode[Token`Comment, _, _], {-5, -3}]; 145 | nonCommentPoss = Position[cst, LeafNode[Except[Token`Comment], _, _], {-5, -3}]; 146 | commentPoss = Cases[commentPoss, {___Integer}]; 147 | nonCommentPoss = Cases[nonCommentPoss, {___Integer}]; 148 | 149 | poss = commentPoss ~Join~ nonCommentPoss; 150 | 151 | tokStartLocs = #[[3, Key[Source], 1]]& /@ Extract[cst, poss]; 152 | 153 | (* 154 | Group by starting SourceLocation 155 | *) 156 | grouped = GroupBy[Transpose[{tokStartLocs, poss}, {2, 1}], #[[1]]&]; 157 | 158 | (* 159 | "EmbeddedNewlines" may not exist 160 | *) 161 | embeddedNewlines = Lookup[data, "EmbeddedNewlines", {}]; 162 | 163 | (* 164 | FIXME: use Lookup[] 165 | *) 166 | mapSpecs = Map[ 167 | Function[{newlineLoc}, 168 | 169 | tuples = grouped[newlineLoc]; 170 | 171 | (* 172 | The token associated with this location may have been abstracted away and now missing 173 | *) 174 | If[!MissingQ[tuples], 175 | tuples 176 | , 177 | Nothing 178 | ] 179 | ] 180 | , 181 | embeddedNewlines 182 | ]; 183 | 184 | mapSpecs = Flatten[mapSpecs, 1]; 185 | embeddedNewlinePoss = mapSpecs[[All, 2]]; 186 | 187 | cst = MapAt[fragmentizeMultilineLeafNode, cst, embeddedNewlinePoss]; 188 | 189 | If[$Debug, 190 | Print["after fragmentizeMultilineLeafNode: ", cst]; 191 | ]; 192 | 193 | (* 194 | This used to be: 195 | 196 | allOtherPoss = Complement[poss, embeddedNewlinePoss]; 197 | 198 | but we only care about comments, so only process comments 199 | *) 200 | allOtherCommentPoss = Complement[commentPoss, embeddedNewlinePoss]; 201 | 202 | If[$Debug, 203 | Print["allOtherCommentPoss: ", allOtherCommentPoss]; 204 | ]; 205 | 206 | (* 207 | Now also fragmentize the remaining comments 208 | *) 209 | cst = MapAt[fragmentizeComment, cst, allOtherCommentPoss]; 210 | 211 | If[$Debug, 212 | Print["after fragmentizeComment: ", cst]; 213 | ]; 214 | 215 | cst 216 | ]] 217 | 218 | 219 | fragmentizeMultilineLeafNode[LeafNode[String, s_, data:KeyValuePattern[Source -> {{_, _}, {_, _}}]]] := 220 | Module[{origSpaces}, 221 | origSpaces = data[[Key[Source], 1, 2]] - 1; 222 | LeafNode[ 223 | String 224 | , 225 | Flatten[{ 226 | FragmentNode[String, "\\" <> $CurrentNewlineString, <|"Temporary" -> True|>], 227 | Table[FragmentNode[String, " ", <|"Temporary" -> True|>], origSpaces], 228 | Function[{cases}, { 229 | FragmentNode[String, #, <| "LastFragmentInString" -> False |>]& /@ Most[cases], 230 | FragmentNode[String, Last[cases], <| "LastFragmentInString" -> True |>] 231 | }][ 232 | DeleteCases[StringSplit[s, x:$CurrentNewlineString :> x], ""] 233 | ] 234 | }] 235 | , 236 | data 237 | ] 238 | ] 239 | 240 | (* 241 | Make sure to fragmentize ( * and * ) here 242 | 243 | And fragmentize nested comments also 244 | *) 245 | fragmentizeMultilineLeafNode[LeafNode[Token`Comment, s_, data:KeyValuePattern[Source -> {{_, _}, {_, _}}]]] := 246 | Module[{origSpaces}, 247 | origSpaces = data[[Key[Source], 1, 2]] - 1; 248 | LeafNode[ 249 | Token`Comment 250 | , 251 | Flatten[{ 252 | FragmentNode[Token`Comment, "\\" <> $CurrentNewlineString, <| "Temporary" -> True |>], 253 | Table[FragmentNode[Token`Comment, " ", <| "Temporary" -> True |>], origSpaces], 254 | FragmentNode[Token`Comment, #, <||>]& /@ DeleteCases[StringSplit[s, x:"(*"|"*)"|$CurrentNewlineString :> x], ""] 255 | }] 256 | , 257 | data 258 | ] 259 | ] 260 | 261 | (* 262 | Could be processed because it has the same Source as a multiline leaf node 263 | *) 264 | fragmentizeMultilineLeafNode[n:LeafNode[Token`Fake`ImplicitNull, _, _]] := 265 | n 266 | 267 | fragmentizeMultilineLeafNode[args___] := 268 | Failure["Unhandled", <| "Function" -> fragmentizeMultilineLeafNode, "Arguments" -> HoldForm[{args}] |>] 269 | 270 | 271 | (* 272 | Make sure to fragmentize ( * and * ) here 273 | 274 | And fragmentize nested comments also 275 | *) 276 | fragmentizeComment[LeafNode[Token`Comment, s_, data_]] := 277 | LeafNode[Token`Comment, 278 | FragmentNode[Token`Comment, #, <||>]& /@ DeleteCases[StringSplit[s, x:"(*"|"*)" :> x], ""] 279 | , 280 | data 281 | ] 282 | 283 | 284 | 285 | (* 286 | Flatten comment groups 287 | *) 288 | 289 | fragmentizeCommentGroups[cstIn_] := 290 | Module[{cst, poss}, 291 | 292 | cst = cstIn; 293 | 294 | poss = Position[cst, GroupNode[Comment, _, _]]; 295 | 296 | cst = MapAt[convertCommentGroups, cst, poss]; 297 | 298 | cst 299 | ] 300 | 301 | convertCommentGroups[c:GroupNode[Comment, boxs_, data_]] := 302 | Module[{leafs, src, origSpaces}, 303 | src = data[Source]; 304 | leafs = Cases[boxs, LeafNode[_, _, _], Infinity]; 305 | 306 | If[MemberQ[leafs, LeafNode[String, "\[IndentingNewLine]" | "\n", _]], 307 | origSpaces = 0; 308 | (* 309 | Do a little hack here 310 | 311 | It is hard to tell how many spaces were in front of the multiline comment 312 | 313 | so count how many spaces were before the closer and use that 314 | *) 315 | Do[ 316 | If[MatchQ[leafs[[i]], LeafNode[String, "\[IndentingNewLine]" | "\n", _]], 317 | Break[] 318 | ]; 319 | If[MatchQ[leafs[[i]], LeafNode[String, s_ /; StringMatchQ[s, " "..], _]], 320 | origSpaces += StringLength[leafs[[i]][[2]]] 321 | ]; 322 | , 323 | {i, Length[leafs] - 1, 2, -1} 324 | ]; 325 | 326 | LeafNode[ 327 | Token`Comment 328 | , 329 | {FragmentNode[Token`Comment, "\\" <> $CurrentNewlineString, <| "Temporary" -> True |>]} ~Join~ 330 | Table[FragmentNode[String, " ", <| "Temporary" -> True |>], origSpaces] ~Join~ 331 | (FragmentNode[Token`Comment, #[[2]], <||>]& /@ leafs) 332 | , 333 | data 334 | ] 335 | , 336 | (* single line *) 337 | LeafNode[ 338 | Token`Comment 339 | , 340 | FragmentNode[Token`Comment, #[[2]], <||>]& /@ leafs 341 | , 342 | data 343 | ] 344 | ] 345 | ] 346 | 347 | 348 | 349 | 350 | mergeTemporaryLineContinuations[fsIn_] := 351 | Module[{fs, poss, lc, onePast, numberOfOriginalSpaces, numberOfBeforeChars, originalSpacesSpec, deleteSpecs, takeSpec, 352 | commentReplaceSpecs}, 353 | 354 | fs = fsIn; 355 | 356 | If[$Debug, 357 | Print["fs: ", fs]; 358 | ]; 359 | 360 | lc = FragmentNode[_, "\\" <> $CurrentNewlineString, _]; 361 | 362 | poss = Position[fs, lc, {1}]; 363 | 364 | deleteSpecs = Internal`Bag[]; 365 | commentReplaceSpecs = {}; 366 | Function[{pos}, 367 | 368 | If[$Debug, 369 | Print["pos: ", pos]; 370 | ]; 371 | 372 | (* 373 | Count how many spaces after the line continuation 374 | *) 375 | onePast = NestWhile[(# + 1)&, pos[[1]] + 1, (# <= Length[fs] && MatchQ[fs[[#]], (LeafNode|FragmentNode)[_, " ", _]])&]; 376 | 377 | originalSpacesSpec = {pos[[1]] + 1, onePast}; 378 | 379 | If[$Debug, 380 | Print["originalSpacesSpec: ", originalSpacesSpec]; 381 | ]; 382 | 383 | numberOfOriginalSpaces = (onePast) - (pos[[1]] + 1); 384 | 385 | (* 386 | Count how many characters before the line continuation (but after any previous newline) 387 | *) 388 | onePast = NestWhile[(# - 1)&, pos[[1]] - 1, (# >= 1 && !MatchQ[fs[[#]], (LeafNode|FragmentNode)[_, $CurrentNewlineString, _]])&]; 389 | 390 | takeSpec = {onePast + 1, pos[[1]] - 1}; 391 | 392 | If[$Debug, 393 | Print["takeSpec: ", takeSpec]; 394 | ]; 395 | 396 | numberOfBeforeChars = 397 | Total[ 398 | (* 399 | Make sure to treat implicit Times as " " 400 | *) 401 | StringLength /@ (Take[fs, takeSpec] /. LeafNode[Token`Fake`ImplicitTimes, _, _] -> LeafNode[Whitespace, " ", <||>])[[All, 2]] 402 | ]; 403 | 404 | If[$Debug, 405 | Print["numberOfBeforeChars: ", numberOfBeforeChars]; 406 | Print["numberOfOriginalSpaces: ", numberOfOriginalSpaces]; 407 | ]; 408 | 409 | Which[ 410 | (* 411 | numberOfBeforeChars == 0 && numberOfOriginalSpaces == 0, 412 | (* 413 | not mergeable; the line continuation will be dropped later 414 | *) 415 | If[$Debug, 416 | Print["not mergeable 1"]; 417 | ]; 418 | Nothing 419 | , 420 | *) 421 | (* 422 | either there are more original spaces (so fine to merge back) 423 | 424 | a::usage = \ 425 | "text 426 | text" 427 | 428 | turns into => 429 | 430 | a::usage = "text 431 | text" 432 | 433 | *) 434 | numberOfBeforeChars <= numberOfOriginalSpaces, 435 | (* 436 | make sure to include the line continuation itself to be removed 437 | *) 438 | If[$Debug, 439 | Print["mergeable 1"]; 440 | ]; 441 | 442 | Scan[Internal`StuffBag[deleteSpecs, {#}]&, Range[pos[[1]], originalSpacesSpec[[2]] - (numberOfOriginalSpaces - numberOfBeforeChars) - 1]]; 443 | , 444 | (* 445 | or whatever is overlapping is spaces, so also fine to merge back 446 | 447 | a::usage = 448 | \ 449 | "text 450 | text" 451 | 452 | turns into => 453 | 454 | a::usage = 455 | "text 456 | text" 457 | 458 | *) 459 | MatchQ[Take[fs, takeSpec], {(LeafNode|FragmentNode)[_, " ", _]...}], 460 | If[$Debug, 461 | Print["mergeable 2"]; 462 | ]; 463 | (* 464 | make sure to include the line continuation itself 465 | *) 466 | Scan[Internal`StuffBag[deleteSpecs, {#}]&, Range[takeSpec[[1]], pos[[1]]]]; 467 | , 468 | (* 469 | 470 | If the string itself starts with " and then immediately a newline, then it is fine to merge 471 | (we will not mess up any alignment, because there is no alignment on the first line) 472 | 473 | a::usage = \ 474 | " 475 | xxx" 476 | 477 | turns into => 478 | 479 | a::usage = " 480 | xxx" 481 | 482 | *) 483 | MatchQ[fs[[originalSpacesSpec[[2]]]], FragmentNode[String, "\"", _] | FragmentNode[Token`Comment, "(*", _]], 484 | (* 485 | make sure to include the line continuation itself to be removed 486 | *) 487 | If[$Debug, 488 | Print["mergeable 3"]; 489 | ]; 490 | Scan[Internal`StuffBag[deleteSpecs, {#}]&, Range[pos[[1]], originalSpacesSpec[[2]] - 1]]; 491 | , 492 | MatchQ[Take[fs, {Max[pos[[1]] - (numberOfBeforeChars - numberOfOriginalSpaces), 1], pos[[1]] - 1}], {(LeafNode|FragmentNode)[Whitespace, _, _]...}], 493 | (* 494 | 495 | If[ \ 496 | "x 497 | x" 498 | ] 499 | 500 | turns into 501 | 502 | If[ "x 503 | x" 504 | ] 505 | 506 | *) 507 | If[$Debug, 508 | Print["mergeable 4"]; 509 | ]; 510 | Scan[Internal`StuffBag[deleteSpecs, {#}]&, Range[Max[pos[[1]] - (numberOfBeforeChars - numberOfOriginalSpaces), 1], pos[[1]] + numberOfOriginalSpaces]]; 511 | , 512 | MatchQ[fs[[pos[[1]]]], FragmentNode[Token`Comment, "\\" <> $CurrentNewlineString, _]], 513 | (* 514 | Always ok to remove line continuation from a comment 515 | But make sure to leave a newline 516 | *) 517 | (* 518 | AppendTo[commentReplaceSpecs, pos] 519 | *) 520 | Null 521 | , 522 | MatchQ[fs[[pos[[1]]]], FragmentNode[String, "\\" <> $CurrentNewlineString, _]], 523 | (* 524 | 525 | Something like: 526 | 527 | a::usage = \ 528 | "xx 529 | xx" 530 | 531 | turns into 532 | 533 | a::usage = "xx 534 | xx" 535 | 536 | The internal alignment of the string is changed. 537 | Give a message. 538 | 539 | *) 540 | If[TrueQ[$DisableBadMerging], 541 | Message[CodeFormat::multiline]; 542 | , 543 | Scan[Internal`StuffBag[deleteSpecs, {#}]&, Range[pos[[1]], originalSpacesSpec[[2]] - 1]]; 544 | ] 545 | ] 546 | 547 | ] /@ poss; 548 | 549 | deleteSpecs = Internal`BagPart[deleteSpecs, All]; 550 | 551 | If[$Debug, 552 | Print["commentReplaceSpecs: ", commentReplaceSpecs]; 553 | Print["deleteSpecs: ", deleteSpecs]; 554 | ]; 555 | 556 | fs = ReplacePart[fs, commentReplaceSpecs -> FragmentNode[Token`Comment, $CurrentNewlineString, <||>]]; 557 | fs = Delete[fs, deleteSpecs]; 558 | 559 | If[$Debug, 560 | Print["newFs: ", fs]; 561 | ]; 562 | 563 | (* 564 | cleanup any remaining line continuations 565 | *) 566 | (* 567 | newFs = newFs /. { 568 | lc :> Sequence @@ line[0], 569 | (* 570 | space fragments are now orphaned, so need to convert back to LeafNodes 571 | *) 572 | FragmentNode[_, " ", data_] :> LeafNode[Whitespace, " ", data], 573 | FragmentNode[tag_, str_, data_] :> LeafNode[tag, str, data] 574 | }; 575 | *) 576 | 577 | fs 578 | ] 579 | 580 | 581 | End[] 582 | 583 | EndPackage[] 584 | -------------------------------------------------------------------------------- /CodeFormatter/Kernel/Notebooks.wl: -------------------------------------------------------------------------------- 1 | (* ::Package::"Tags"-><|"NoVariables" -> <|"Module" -> <|Enabled -> False|>|>|>:: *) 2 | 3 | BeginPackage["CodeFormatter`Notebooks`"] 4 | 5 | 6 | formatSelectedCell 7 | 8 | (* 9 | formatSelectedNotebook 10 | *) 11 | 12 | 13 | Begin["`Private`"] 14 | 15 | Needs["CodeFormatter`"] 16 | Needs["CodeFormatter`Utils`"] 17 | Needs["CodeParser`"] 18 | Needs["CodeParser`Utils`"] 19 | 20 | 21 | $LintTextFontWeight = "Medium" 22 | 23 | 24 | formatSelectedCell[] := 25 | Catch[ 26 | Module[{nb, read, toWrite}, 27 | 28 | nb = InputNotebook[]; 29 | 30 | (* 31 | first try determining the kind of selection 32 | *) 33 | read = NotebookRead[nb]; 34 | 35 | Switch[read, 36 | _Cell | {_Cell...}, 37 | (* 38 | plain cursor, single cell selected, multiple cells selected 39 | *) 40 | Null 41 | , 42 | _, 43 | (* 44 | Anything else 45 | Some selection of boxes 46 | Not supported right now 47 | Return immediately 48 | *) 49 | Throw[Null] 50 | ]; 51 | 52 | SelectionMove[nb, All, Cell, AutoScroll -> False]; 53 | 54 | read = NotebookRead[nb]; 55 | 56 | Switch[read, 57 | _RowBox, 58 | (* 59 | Some selection of boxes 60 | Not supported right now 61 | Return immediately 62 | *) 63 | Throw[Null] 64 | , 65 | Cell[___], 66 | (* 67 | wrap List around single Cell, then proceed 68 | *) 69 | read = {read} 70 | ]; 71 | 72 | CurrentValue[nb, WindowStatusArea] = "Formatting selected cell..."; 73 | 74 | toWrite = MapIndexed[Function[{cell, index}, 75 | Function[val, CurrentValue[nb, WindowStatusArea] = "Formatting selected cell... " <> ToString[Floor[100 index[[1]]/Length[read]]] <> "%"; val]@ 76 | formatContents[cell] 77 | ], read]; 78 | 79 | NotebookWrite[nb, toWrite, All]; 80 | 81 | CurrentValue[nb, WindowStatusArea] = ""; 82 | 83 | Null 84 | ]] 85 | 86 | 87 | (* 88 | input: cell is a Cell[] expression, can be Input, Code, cell group, etc. 89 | 90 | handles CellGroups recursively 91 | 92 | return the formatted contents 93 | *) 94 | formatContents[cell_] := 95 | Module[{formatted}, 96 | Switch[cell, 97 | (* 98 | "Program" base case 99 | *) 100 | Cell[_, "Program", ___], 101 | 102 | formatted = cell; 103 | 104 | If[CodeFormatter`$InteractiveReparse, 105 | formatted = FrontEndExecute[FrontEnd`ReparseBoxStructurePacket[formatted]] 106 | ]; 107 | 108 | formatted = formatProgramCellContents[formatted[[1]]]; 109 | 110 | If[!FailureQ[formatted], 111 | (* 112 | return the successful formatted cell 113 | *) 114 | Cell[formatted, Sequence @@ cell[[2;;]]] 115 | , 116 | (* 117 | just return the bad input cell 118 | *) 119 | cell 120 | ] 121 | , 122 | (* 123 | "Input" | "Code" base case 124 | only RowBoxes are supported for now 125 | 126 | This also handles case where b is a single token with no RowBoxes or anything 127 | *) 128 | Cell[BoxData[b_], "Input" | "Code", ___] /; Complement[Union[Cases[b, _Symbol, Infinity, Heads -> True]], {List, RowBox}] === {}, 129 | 130 | formatted = cell; 131 | 132 | If[CodeFormatter`$InteractiveReparse, 133 | formatted = FrontEndExecute[FrontEnd`ReparseBoxStructurePacket[formatted]] 134 | ]; 135 | 136 | formatted = formatInputContents[formatted[[1, 1]]]; 137 | 138 | If[!FailureQ[formatted], 139 | (* 140 | return the successful formatted cell 141 | *) 142 | Cell[BoxData[formatted], Sequence @@ cell[[2;;]]] 143 | , 144 | (* 145 | Ideally, this would be a Beep[] and populate the Why the Beep? dialog 146 | 147 | But that is not really possible 148 | 149 | So just do a message for now 150 | 151 | Related threads: https://mail-archive.wolfram.com/archive/l-kernel/2020/Aug00/0035.html 152 | *) 153 | Message[CodeFormat::cellfailure]; 154 | (* 155 | just return the bad input cell 156 | *) 157 | cell 158 | ] 159 | , 160 | (* 161 | "Output" base case 162 | just pass through 163 | *) 164 | Cell[BoxData[_], "Output", ___], 165 | cell 166 | , 167 | 168 | (* 169 | unrecognized BoxData cell 170 | *) 171 | Cell[BoxData[_], _, ___], 172 | Message[CodeFormat::cellfailure]; 173 | (* 174 | just return the bad input cell 175 | *) 176 | cell 177 | , 178 | 179 | (* 180 | Recursively handle CellGroups 181 | Supports Code cells that have been evaluated and have Output grouped with them 182 | *) 183 | Cell[CellGroupData[_, _]], 184 | Cell[CellGroupData[formatContents /@ cell[[1, 1]], cell[[1, 2]]]] 185 | , 186 | (* 187 | Anything else 188 | Section cells, etc. 189 | just pass through 190 | *) 191 | _, 192 | cell 193 | ] 194 | ] 195 | 196 | 197 | (* 198 | 199 | formatSelectedNotebook[] is currently disabled because of a bug in the FE that prevents undo from working after NotebookPut 200 | 201 | Related bugs: 395592 202 | *) 203 | (* 204 | formatSelectedNotebook[] := 205 | Catch[ 206 | Module[{nb, read, toWrite, cells, cellsToWrite}, 207 | 208 | nb = InputNotebook[]; 209 | 210 | CurrentValue[nb, WindowStatusArea] = "formatting notebook... 0%"; 211 | 212 | read = NotebookGet[nb]; 213 | 214 | $LastNBRead = read; 215 | 216 | cells = read[[1]]; 217 | 218 | If[$reparse, 219 | cells = FrontEndExecute[FrontEnd`ReparseBoxStructurePacket[#]]& /@ cells 220 | ]; 221 | 222 | cellsToWrite = 223 | MapIndexed[Function[{cell, index}, 224 | CurrentValue[nb, WindowStatusArea] = "formatting notebook... " <> ToString[Floor[100 index[[1]]/Length[cells]]] <> "%"; 225 | Switch[cell, 226 | Cell[_, "Program", ___], 227 | ReplacePart[cell, {1} -> formatProgramCellContents[cell[[1]]]] 228 | , 229 | Cell[BoxData[_], "Input" | "Code", ___], 230 | ReplacePart[cell, {1, 1} -> formatInputContents[cell[[1, 1]]]] 231 | , 232 | _, 233 | cell 234 | ] 235 | ], cells]; 236 | 237 | toWrite = read; 238 | toWrite[[1]] = cellsToWrite; 239 | 240 | $LastNBToWrite = toWrite; 241 | 242 | NotebookPut[toWrite, nb]; 243 | 244 | CurrentValue[nb, WindowStatusArea] = ""; 245 | ]] 246 | *) 247 | 248 | 249 | (* 250 | Return string 251 | *) 252 | 253 | getNewlineRules[] := 254 | Replace[ 255 | Normal[ 256 | KeyTake[ 257 | Replace[AbsoluteCurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat"}], Except[_Association] -> <||>], 258 | { 259 | "NewlinesBetweenSemicolons", "NewlinesBetweenOperators", "NewlinesInGroups", 260 | "NewlinesBetweenCommas", "NewlinesInControl", "NewlinesInScoping", 261 | "NewlinesInComments"}]], 262 | {True -> Insert, False -> Delete}, 263 | {2}] 264 | 265 | formatProgramCellContents[contents_String] := 266 | Catch[ 267 | Module[{formatted, airiness, indentationString, tabWidth, formatOptions, method}, 268 | 269 | airiness = massageAiriness[CodeFormatter`$InteractiveAiriness]; 270 | tabWidth = massageTabWidth[CodeFormatter`$InteractiveTabWidth]; 271 | indentationString = massageIndentationString[CodeFormatter`$InteractiveIndentationCharacter, tabWidth]; 272 | method = CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "FormatMethod"}]; 273 | formatOptions = 274 | Join[ 275 | {"IndentationString" -> indentationString, "TabWidth" -> tabWidth}, 276 | Switch[method, "NewlineRules", getNewlineRules[], _, {Airiness -> airiness}]]; 277 | 278 | formatted = CodeFormat[contents, formatOptions]; 279 | If[FailureQ[formatted], 280 | Throw[formatted] 281 | ]; 282 | formatted = StringTrim[formatted, "\n"..]; 283 | formatted 284 | ]] 285 | 286 | (* 287 | Return boxes 288 | *) 289 | formatInputContents[contentsBox_] := 290 | Catch[ 291 | Module[{cst, formatted, formattedBox, airiness, indentationString, tabWidth, agg, cst2, agg2, aggToCompare, agg2ToCompare, newline, 292 | issues, formatOptions, method}, 293 | 294 | airiness = massageAiriness[CodeFormatter`$InteractiveAiriness]; 295 | tabWidth = massageTabWidth[CodeFormatter`$InteractiveTabWidth]; 296 | indentationString = massageIndentationString[CodeFormatter`$InteractiveIndentationCharacter, tabWidth]; 297 | newline = "\n"; 298 | method = CurrentValue[$FrontEnd, {CodeAssistOptions, "CodeToolsOptions", "CodeFormat", "FormatMethod"}]; 299 | formatOptions = 300 | Join[ 301 | {"IndentationString" -> indentationString, "TabWidth" -> tabWidth}, 302 | Switch[method, "NewlineRules", getNewlineRules[], _, {Airiness -> airiness}]]; 303 | 304 | (* 305 | convert boxes to form that is understood by formatter 306 | *) 307 | cst = CodeConcreteParseBox[contentsBox]; 308 | If[FailureQ[cst], 309 | Throw[cst] 310 | ]; 311 | 312 | formatted = CodeFormatCST[cst, formatOptions]; 313 | If[FailureQ[formatted], 314 | Throw[formatted] 315 | ]; 316 | 317 | formatted = StringTrim[formatted, "\n"..]; 318 | 319 | 320 | (* 321 | CodeFormatCST does not do sanity checking, so must do it here 322 | *) 323 | If[CodeFormatter`Private`$DisableSanityChecking, 324 | 325 | cst = CodeConcreteParse[formatted]; 326 | If[FailureQ[cst], 327 | Throw[cst] 328 | ]; 329 | (* 330 | trick ToStandardFormBoxes into thinking that cst came from boxes 331 | *) 332 | cst[[1]] = Box; 333 | formattedBox = ToStandardFormBoxes[cst]; 334 | If[FailureQ[formattedBox], 335 | Throw[formattedBox] 336 | ]; 337 | Throw[formattedBox] 338 | ]; 339 | 340 | (* 341 | Sanity Checking 342 | *) 343 | 344 | agg = CodeParser`Abstract`Aggregate[cst]; 345 | agg = expandMultiSingleQuote[agg]; 346 | agg = expandTernaryOptionalPattern[agg]; 347 | agg = expandInfixTilde[agg]; 348 | agg = coalesceLineContinuation[agg]; 349 | agg = flattenGroupMissingCloserNode[agg]; 350 | 351 | (* 352 | $CleanLexicalVariables changes the aggregate syntax, so must also run here in sanity check 353 | *) 354 | If[$CleanLexicalVariables, 355 | agg = CodeFormatter`Abstract`cleanLexicalVariables[agg] 356 | ]; 357 | 358 | cst2 = CodeConcreteParse[formatted]; 359 | 360 | If[FailureQ[cst2], 361 | Throw[cst2] 362 | ]; 363 | 364 | If[MatchQ[cst2[[3]], KeyValuePattern[SyntaxIssues -> {___, SyntaxIssue["UnrecognizedCharacter", _, _, _], ___}]], 365 | 366 | issues = Lookup[cst2[[3]], SyntaxIssues]; 367 | issues = boldify[#[[2]]]& /@ issues; 368 | 369 | (* 370 | This message is indicating that there is a syntax error in the input, such as: 371 | f["\."] 372 | *) 373 | Message[CodeFormat::syntaxissues, issues] 374 | ]; 375 | 376 | agg2 = CodeParser`Abstract`Aggregate[cst2]; 377 | agg2 = reduceSpan[agg2]; 378 | 379 | agg2[[1]] = Box; 380 | 381 | aggToCompare = agg /. _Association -> <||>; 382 | agg2ToCompare = agg2 /. _Association -> <||>; 383 | 384 | If[aggToCompare =!= agg2ToCompare, 385 | If[$Debug, 386 | Print["aggToCompare: ", aggToCompare]; 387 | Print["agg2ToCompare: ", agg2ToCompare] 388 | ]; 389 | Throw[Failure["SanityCheckFailed", <||>]] 390 | ]; 391 | 392 | 393 | cst = CodeConcreteParse[formatted]; 394 | If[FailureQ[cst], 395 | Throw[cst] 396 | ]; 397 | (* 398 | trick ToStandardFormBoxes into thinking that cst came from boxes 399 | *) 400 | cst[[1]] = Box; 401 | formattedBox = ToStandardFormBoxes[cst]; 402 | If[FailureQ[formattedBox], 403 | Throw[formattedBox] 404 | ]; 405 | formattedBox 406 | ]] 407 | 408 | 409 | (* 410 | boxes use a single token for ''' 411 | and text uses multiple tokens 412 | 413 | must make sure that they are the same when we do a sanity check 414 | 415 | it is easier to expand the single token into multiple tokens 416 | *) 417 | expandMultiSingleQuote[agg_] := 418 | agg /. { 419 | PostfixNode[Derivative, {rand_, LeafNode[Token`Boxes`MultiSingleQuote, s_, _]}, _] :> 420 | Nest[PostfixNode[Derivative, {#, LeafNode[Token`SingleQuote, "'", <||>]}, <||>]&, rand, StringLength[s]] 421 | } 422 | 423 | (* 424 | boxes use a single RowBox for a:b:c 425 | and text uses 2 nested BinaryNodes 426 | 427 | must make sure that they are the same when we do a sanity check 428 | 429 | it is easier to expand the single RowBox into multiple BinaryNodes 430 | *) 431 | expandTernaryOptionalPattern[agg_] := 432 | agg //. { 433 | TernaryNode[TernaryOptionalPattern, {a_, op1_, b_, op2_, c_}, _] :> 434 | BinaryNode[Optional, {BinaryNode[Pattern, {a, op1, b}, <||>], op2, c}, <||>] 435 | } 436 | 437 | (* 438 | boxes use a single RowBox for a ~ b ~ c ~ d ~ e 439 | and text uses 2 nested TernaryNodes 440 | 441 | must make sure that they are the same when we do a sanity check 442 | 443 | it is easier to expand the single RowBox into multiple TernaryNodes 444 | *) 445 | expandInfixTilde[agg_] := 446 | agg //. { 447 | InfixNode[InfixTilde, {a_, op1_, b_, op2_, c_}, _] :> 448 | TernaryNode[TernaryTilde, {a, op1, b, op2, c}, <||>] 449 | , 450 | InfixNode[InfixTilde, {a_, op1_, b_, op2_, c_, rest___}, _] :> 451 | TernaryNode[TernaryTilde, {TernaryNode[TernaryTilde, {a, op1, b, op2, c}, <||>], rest}, <||>] 452 | } 453 | 454 | (* 455 | A single ;; does not create a RowBox, so is not parsed correctly 456 | Need to reduce the correctly parsed ;; with implicit tokens to a single ;; 457 | *) 458 | reduceSpan[agg_] := 459 | agg /. { 460 | BinaryNode[Span, {LeafNode[Token`Fake`ImplicitOne, _, _], tok:LeafNode[Token`SemiSemi, _, _], LeafNode[Token`Fake`ImplicitAll, _, _]}, _] :> 461 | tok 462 | } 463 | 464 | coalesceLineContinuation[aggIn_] := 465 | Module[{agg, poss}, 466 | 467 | agg = aggIn; 468 | 469 | poss = Position[agg, {___, BoxNode[RowBox, {{_, LeafNode[Token`Boxes`LineContinuation, _, _]}}, _], ___}]; 470 | 471 | agg = MapAt[lcReplace1, agg, poss]; 472 | 473 | poss = Position[agg, {___, CodeParser`LeafNode[Token`Boxes`LineContinuation, _, _], ___}]; 474 | 475 | agg = MapAt[lcReplace2, agg, poss]; 476 | 477 | agg 478 | ] 479 | 480 | lcReplace1[l_] := 481 | SequenceReplace[l, {BoxNode[RowBox, {{first_, LeafNode[Token`Boxes`LineContinuation, lc_, _]}}, _], LeafNode[tag_, str_, _]} :> 482 | Sequence @@ {first, LeafNode[tag, lc <> str, <||>]}] 483 | 484 | lcReplace2[{LeafNode[Token`Boxes`LineContinuation, lc_, _], GroupNode[tag_, {LeafNode[tag1_, str_, _], rest___}, data_]}] := 485 | GroupNode[tag, {LeafNode[tag1, lc <> str, <||>], rest}, data] 486 | 487 | lcReplace2[l_] := 488 | SequenceReplace[l, { 489 | {LeafNode[Token`Boxes`LineContinuation, lc_, _], LeafNode[tag_, str_, data_]} :> 490 | LeafNode[tag, lc <> str, data] 491 | , 492 | {LeafNode[Token`Boxes`LineContinuation, lc_, _], PrefixNode[tag_, {LeafNode[tag1_, str_, _], rest___}, data_]} :> 493 | PrefixNode[tag, {LeafNode[tag1, lc <> str, <||>], rest}, data] 494 | } 495 | ] 496 | 497 | (* 498 | Parsing: 499 | 500 | f[ 501 | 502 | as a string leaves all of the subsequent content unparsed 503 | 504 | so try to reproduce that behavior with boxes 505 | 506 | *) 507 | flattenGroupMissingCloserNode[agg_] := 508 | agg /. GroupMissingCloserNode[tag_, children_, data_] :> GroupMissingCloserNode[tag, Flatten[flatten /@ children], data] 509 | 510 | flatten[n:LeafNode[_, _, _]] := n 511 | 512 | flatten[_[_, children_List, _]] := flatten /@ children 513 | 514 | 515 | 516 | 517 | 518 | (* 519 | replace `` and ** markup 520 | *) 521 | boldify[s_String] := 522 | StringReplace[s, { 523 | RegularExpression["``(.*?)``"] :> ToString[Style["$1", "Program", FontWeight->$LintTextFontWeight], StandardForm], 524 | RegularExpression["\\*\\*(.*?)\\*\\*"] :> ToString[Style["$1", "Program", FontWeight->$LintTextFontWeight], StandardForm], 525 | RegularExpression["\\?\\?(.*?)\\?\\?"] :> "$1"} 526 | ] 527 | 528 | 529 | 530 | 531 | massageAiriness[a_Real] := a 532 | massageAiriness[a_Integer] := a 533 | 534 | massageAiriness[_] := Automatic 535 | 536 | 537 | massageIndentationString[s_String, tabWidth_] := 538 | Module[{}, 539 | Switch[s, 540 | "space", 541 | StringRepeat[" ", tabWidth] 542 | , 543 | "tab", 544 | "\t" 545 | , 546 | _, 547 | StringRepeat[" ", tabWidth] 548 | ] 549 | ] 550 | 551 | massageIndentationString[___] := $DefaultIndentationString 552 | 553 | 554 | massageTabWidth[s_String] := 555 | Module[{parsed}, 556 | parsed = CodeConcreteParseLeaf[s]; 557 | Switch[parsed, 558 | LeafNode[Integer, _, _], 559 | FromNode[parsed] 560 | , 561 | _, 562 | $DefaultTabWidth 563 | ] 564 | ] 565 | 566 | massageTabWidth[_] := $DefaultTabWidth 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | End[] 575 | 576 | EndPackage[] 577 | --------------------------------------------------------------------------------