├── CodeFormatter.m ├── Examples.nb ├── LICENSE └── README.md /CodeFormatter.m: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | (* Mathematica Package *) 4 | 5 | (* :Title: CodeFormatter *) 6 | 7 | (* :Author: Leonid B. Shifrin *) 8 | 9 | (* :Summary: Pretty-printer for Mathematica code in box form *) 10 | 11 | (* :Context: CodeFormatter` *) 12 | 13 | (* :Package version: 1.0 *) 14 | 15 | (* :Copyright: Copyright 2012, Leonid B.Shifrin. 16 | The package is released under MIT Open Source license 17 | *) 18 | 19 | (* :History: Version 1.0 March 2012 *) 20 | 21 | (* :Keywords: code, format, pretty-print *) 22 | 23 | (* :Mathematica version: 8.0 *) 24 | 25 | 26 | 27 | BeginPackage["CodeFormatter`"] 28 | (* Exported symbols added here with SymbolName::usage *) 29 | 30 | FullCodeFormat::usage = 31 | "FullCodeFormat[code_] takes some Mathematica expression (in most cases that would 32 | probably be some code) in the box form, and attempts to format this expression to 33 | make it easier to read and more understandable, by inserting tabs and new lines. 34 | The formatted boxed expression is then returned"; 35 | 36 | FullCodeFormatCompact::usage = 37 | "FullCodeFormatCompact[code_] works like FullCodeFormat[code_], but uses setting which 38 | make the resulting formatted expression more compact-looking"; 39 | 40 | 41 | 42 | SEFormat::usage = 43 | "SEFormat[code] formats code in a custom way applicable to use in mathematica.stackexchange.com 44 | posts "; 45 | 46 | 47 | CodeFormatterMakeBoxes::usage = "CodeFormatterMakeBoxes[expr] returns a simplified box representation 48 | for expr. The formatter can use it to handle code which it can't handle using MakeBoxes"; 49 | 50 | CodeFormatterPrint::usage = "CodeFormatterPrint[f] pretty-prints definitions (OwnValues, DownValues, UpValues 51 | and SubValues), associated with the symbol"; 52 | 53 | CodeFormatterSpelunk::usage = "CodeFormatterSpelunk[f] pretty-prints main definitions (OwnValues, 54 | DownValues, UpValues and SubValues) for symbol f, using short names for all symbols in the code 55 | 56 | CodeFormatterSpelunk[f,boxFunction] uses a custom function boxFunction to convert definitions to 57 | boxes. The default value is CodeFormatterMakeBoxes, but one can also try MakeBoxes - if the latter 58 | works, it may produce somewhat better result 59 | "; 60 | 61 | 62 | CodeFormatted::usage = "CodeFormatted[code] prints a cell with the formatted code. This function 63 | has an attribute HoldAll"; 64 | 65 | Begin["`Private`"] 66 | (* Implementation of the package *) 67 | 68 | 69 | $supportedBoxes = {StyleBox, TagBox, FractionBox,ItemBox (*,DynamicModuleBox*)}; 70 | $combineTopLevelStatements = True; 71 | $maxLineLength = 70; 72 | $alignClosingBracket = True; 73 | $alignClosingList = True; 74 | $alignClosingParen = True; 75 | $alwaysBreakCompoundExpressions = False; 76 | $alignClosingScopingBracket = True; 77 | $alignClosingIfBracket = True; 78 | $defaultTabWidth = 4; 79 | $useSpacesForTabs = True; 80 | $overallTab = 4; 81 | 82 | 83 | 84 | ClearAll[preprocess]; 85 | preprocess[boxes_] := 86 | boxes //. 87 | {RowBox[{("\t" | "\n") .., expr___}] :> expr} //. 88 | { 89 | s_String /; StringMatchQ[s, Whitespace|""] :> Sequence[], 90 | RowBox[{r_RowBox}] :> r, 91 | RowBox[{}]:>Sequence[], 92 | RowBox[{"Association", "[",inner__,"]"}]:> RowBox[{"<|",inner,"|>"}] 93 | }; 94 | 95 | 96 | ClearAll[$blocks, blockQ]; 97 | $blocks = { 98 | ModuleBlock, BlockBlock, WithBlock, SetBlock, SetDelayedBlock, 99 | CompoundExpressionBlock, GeneralHeadBlock, HeadBlock, ElemBlock, 100 | GeneralBlock, ParenBlock, ListBlock, PatternBlock, PatternTestBlock, 101 | FunctionBlock, AlternativesBlock, StatementBlock, NewlineBlock, 102 | MultilineBlock, FinalTabBlock, GeneralSplitHeadBlock, EmptySymbol, 103 | SemicolonSeparatedGeneralBlock, SuppressedCompoundExpressionBlock, 104 | GeneralBoxBlock, TagSetDelayedBlock, TagSetBlock, ApplyBlock, 105 | ApplyLevel1Block, RuleBlock, RuleDelayedBlock, MapBlock, FunApplyBlock, 106 | IfBlock, IfBlock, IfCommentBlock,DataArrayBlock,ReplaceAllBlock, 107 | ReplaceRepeatedBlock, CustomTabBlock 108 | }; 109 | 110 | blockQ[block_Symbol] := 111 | MemberQ[$blocks, block]; 112 | 113 | 114 | ClearAll[boxQ, boxNArgs] 115 | 116 | boxQ[box_Symbol] := 117 | MemberQ[$supportedBoxes, box]; 118 | 119 | 120 | boxNArgs[_StyleBox] = 1; 121 | boxNArgs[_FractionBox] = 2; 122 | boxNArgs[_TagBox] = 1; 123 | boxNArgs[_ItemBox]=2; 124 | 125 | 126 | boxNArgs[_] := 127 | Throw[$Failed, boxNArgs]; 128 | 129 | 130 | ClearAll[strsymQ]; 131 | strsymQ[s_String] := 132 | StringMatchQ[ s, (LetterCharacter | "$") ~~ ((WordCharacter | "$" | "`") ...)]; 133 | 134 | ClearAll[preformat]; 135 | (* SetAttributes[preformat,HoldAll]; *) 136 | 137 | preformat[expr : (box_?boxQ[args___])] := 138 | With[ {n = boxNArgs[expr]}, 139 | GeneralBoxBlock[box, 140 | n, 141 | Sequence @@ Map[preformat, Take[{args}, n]], 142 | Sequence @@ Drop[{args}, n] 143 | ] 144 | ]; 145 | 146 | 147 | 148 | preformat[CustomTabBlock[expr_,width_]]:= 149 | TabBlock[preformat[expr],True,width]; 150 | 151 | 152 | preformat[ 153 | RowBox[{ 154 | head : ("Module" | "Block" | "With"), 155 | "[", RowBox[{decl_RowBox, ",", body_RowBox}], "]" 156 | }] 157 | ] := 158 | (head /. {"Module" -> ModuleBlock, "Block" -> BlockBlock, "With" -> WithBlock})[ 159 | preformat@decl, 160 | preformat@body 161 | ]; 162 | 163 | preformat[RowBox[{lhs_, assignment : (":=" | "="), rhs_}]] := 164 | (assignment /. {":=" :> SetDelayedBlock, "=" :> SetBlock})[ 165 | preformat[lhs], 166 | preformat[rhs] 167 | ]; 168 | 169 | preformat[ 170 | RowBox[{s_String?strsymQ, "/:", lhs_, assignment : (":=" | "="), 171 | rhs_}]] := 172 | (assignment /. {":=" :> TagSetDelayedBlock, 173 | "=" :> TagSetBlock})[s, preformat[lhs], preformat[rhs]]; 174 | 175 | preformat[RowBox[{fn_, "@@", expr_}]] := 176 | ApplyBlock[preformat@fn, preformat@expr]; 177 | 178 | preformat[RowBox[{fn_, "@@@", expr_}]] := 179 | ApplyLevel1Block[preformat@fn, preformat@expr]; 180 | 181 | preformat[RowBox[{fn_, "/@", expr_}]] := 182 | MapBlock[preformat@fn, preformat@expr]; 183 | 184 | preformat[RowBox[{fn_, "@", expr_}]] := 185 | FunApplyBlock[preformat@fn, preformat@expr]; 186 | 187 | preformat[RowBox[{lhs_, "\[Rule]", rhs_}]] := 188 | RuleBlock[preformat@lhs, preformat@rhs]; 189 | 190 | preformat[RowBox[{lhs_, "\[RuleDelayed]", rhs_}]] := 191 | RuleDelayedBlock[preformat@lhs, preformat@rhs]; 192 | 193 | preformat[RowBox[{lhs_,"//.","\[VeryThinSpace]",rhs_}]]:= 194 | ReplaceRepeatedBlock[preformat[lhs],preformat[rhs]]; 195 | 196 | preformat[RowBox[{lhs_,"/.","\[VeryThinSpace]",rhs_}]]:= 197 | ReplaceAllBlock[preformat[lhs],preformat[rhs]]; 198 | 199 | preformat[RowBox[{p_, "?", test_}]] := 200 | PatternTestBlock[preformat[p], preformat[test]]; 201 | 202 | preformat[RowBox[{body_, "&"}]] := 203 | FunctionBlock[preformat[body]]; 204 | 205 | preformat[RowBox[alts : {PatternSequence[_, "|"] .., _}]] := 206 | AlternativesBlock @@ Map[preformat, alts[[1 ;; -1 ;; 2]]]; 207 | 208 | preformat[RowBox[{"If", "[", RowBox[{cond_, ",", iftrue_, ",", iffalse_}], "]"}]] := 209 | IfBlock[preformat@cond, preformat@iftrue, preformat@iffalse]; 210 | 211 | 212 | 213 | preformat[RowBox[elems : {PatternSequence[_, ";"] ..}]] := 214 | SuppressedCompoundExpressionBlock @@ Map[ 215 | Map[preformat, StatementBlock @@ DeleteCases[#, ";"]] &, 216 | Split[elems, # =!= ";" &]]; 217 | 218 | preformat[RowBox[elems : {PatternSequence[_, ";"] .., _}]] := 219 | CompoundExpressionBlock @@ Map[ 220 | Map[preformat, StatementBlock @@ DeleteCases[#, ";"]] &, 221 | Split[elems, # =!= ";" &]]; 222 | 223 | preformat[RowBox[elems_List]] /; ! FreeQ[elems, "\n" | "\t", 1] := 224 | preformat[RowBox[DeleteCases[elems, "\n" | "\t"]]]; 225 | 226 | preformat[RowBox[{"{", elems___, "}"}]] := 227 | ListBlock @@ Map[preformat, {elems}]; 228 | 229 | preformat[RowBox[{"<|",elems___,"|>"}]]:= 230 | DataArrayBlock @@ Map[preformat, {elems}]; 231 | 232 | preformat[RowBox[{"(", elems__, ")"}]] := 233 | ParenBlock @@ Map[preformat, {elems}]; 234 | 235 | preformat[RowBox[{p_String?strsymQ, ":", elem_}]] := 236 | PatternBlock[p, preformat@elem]; 237 | 238 | preformat[RowBox[{p_String?strsymQ, ":", elem_, ":", def_}]] := 239 | PatternBlock[p, preformat@elem, preformat@def]; 240 | 241 | preformat[RowBox[{head_, "[", elems___, "]"}]] := 242 | GeneralHeadBlock[preformat@head, 243 | Sequence @@ Map[preformat, {elems}]]; 244 | 245 | preformat[RowBox[elems : {PatternSequence[_, ","] .., _}]] := 246 | SemicolonSeparatedGeneralBlock @@ 247 | Map[preformat, DeleteCases[elems, ","]]; 248 | 249 | preformat[RowBox[elems_List]] := 250 | GeneralBlock @@ Map[preformat, elems]; 251 | 252 | preformat[block_?blockQ[args_]] := 253 | block @@ Map[preformat, {args}]; 254 | 255 | preformat[a_?AtomQ] := a; 256 | 257 | preformat[expr_] := 258 | Throw[{$Failed, expr}, preformat]; 259 | 260 | 261 | 262 | 263 | 264 | ClearAll[processPreformatted]; 265 | processPreformatted[GeneralBlock[blocks__]]/;$combineTopLevelStatements := 266 | GeneralBlock[Sequence @@ Map[processPreformatted, {blocks}]]; 267 | 268 | processPreformatted[ 269 | preformatted : SemicolonSeparatedGeneralBlock[elems___] 270 | ]/;$combineTopLevelStatements := 271 | GeneralBoxBlock[RowBox[{##}] &, Length[{elems}], elems]; 272 | 273 | processPreformatted[arg_] := arg; 274 | 275 | 276 | 277 | 278 | ClearAll[tabify]; 279 | tabify[expr_] /; ! FreeQ[expr, TabBlock[_]|TabBlock[_,_]] := 280 | tabify[expr //. TabBlock[sub_] :> TabBlock[sub, True,$tabWidth]]; 281 | 282 | tabify[(block_?blockQ /; ! MemberQ[{TabBlock, FinalTabBlock}, block])[elems___]] := 283 | block @@ Map[tabify, {elems}]; 284 | 285 | tabify[TabBlock[FinalTabBlock[el_, flag_,fwidth_:$tabWidth], tflag_,width_:$tabWidth]] := 286 | FinalTabBlock[tabify[TabBlock[el, tflag,width]], flag,fwidth]; 287 | 288 | tabify[TabBlock[NewlineBlock[el_, flag_], _,width_:$tabWidth]] := 289 | tabify[NewlineBlock[TabBlock[el, True,width], flag]]; 290 | 291 | tabify[TabBlock[t_TabBlock, flag_,width_:$tabWidth]] := 292 | tabify[TabBlock[tabify[t], flag,width]]; 293 | 294 | tabify[TabBlock[GeneralBoxBlock[box_, n_, args___], flag_,width_:$tabWidth]] := 295 | GeneralBoxBlock[box, n, 296 | Sequence @@ Map[tabify[TabBlock[#, flag,width]] &, Take[{args}, n]], 297 | Sequence @@ Drop[{args}, n] 298 | ]; 299 | 300 | tabify[TabBlock[(block_?blockQ /; ! MemberQ[{TabBlock}, block])[ elems___], flag_,width_:$tabWidth]] := 301 | FinalTabBlock[ 302 | block @@ Map[tabify@TabBlock[#, False,width] &, {elems}], 303 | flag, 304 | width]; 305 | 306 | tabify[TabBlock[a_?AtomQ, flag_,width_:$tabWidth]] := 307 | FinalTabBlock[a, flag,width]; 308 | 309 | tabify[expr_] := expr; 310 | 311 | 312 | 313 | ClearAll[isNextNewline]; 314 | isNextNewline[_NewlineBlock] := True; 315 | 316 | isNextNewline[block : (_?blockQ | TabBlock)[fst_, ___]] := 317 | isNextNewline[fst]; 318 | 319 | isNextNewline[_] := False; 320 | 321 | 322 | 323 | ClearAll[postformat]; 324 | postformat[GeneralBlock[elems__]] := 325 | RowBox[postformat /@ {elems}]; 326 | (* Note: BlankSequence in body intentional, to allow for closing element *) 327 | postformat[(head : ModuleBlock | BlockBlock | WithBlock)[vars_, body__]] := 328 | RowBox[{ 329 | head /. {ModuleBlock -> "Module", BlockBlock -> "Block", WithBlock -> "With"}, 330 | "[", 331 | RowBox[{ 332 | postformat[vars], ",", 333 | Sequence @@ (Map[postformat, {body}] //. 334 | EmptySymbol[] :> Sequence[]) 335 | }], 336 | "]"} 337 | ]; 338 | 339 | postformat[(head : SetBlock | SetDelayedBlock | RuleBlock | RuleDelayedBlock)[lhs_, rhs_]] := 340 | RowBox[{ 341 | postformat@lhs, head /. { 342 | SetBlock -> "=", SetDelayedBlock -> ":=", 343 | RuleBlock -> "\[Rule]", RuleDelayedBlock -> "\[RuleDelayed]" 344 | }, 345 | postformat@rhs 346 | }]; 347 | 348 | postformat[(head : TagSetBlock | TagSetDelayedBlock)[s_, lhs_, rhs_]] := 349 | RowBox[{ 350 | postformat@s, "/:", postformat@lhs, 351 | head /. {TagSetBlock -> "=", TagSetDelayedBlock -> ":="}, 352 | postformat@rhs}]; 353 | 354 | postformat[(head : MapBlock | ApplyLevel1Block | ApplyBlock | FunApplyBlock)[f_, expr_]] := 355 | RowBox[{ 356 | postformat@f, head /. { 357 | ApplyBlock -> "@@", ApplyLevel1Block -> "@@@", 358 | MapBlock -> "/@", FunApplyBlock -> "@" 359 | }, 360 | postformat@expr 361 | }]; 362 | 363 | postformat[ReplaceAllBlock[lhs_,rhs_]]:= 364 | RowBox[{postformat@lhs,"/.","\[VeryThinSpace]",postformat@rhs}] 365 | 366 | postformat[ReplaceRepeatedBlock[lhs_,rhs_]]:= 367 | RowBox[{postformat@lhs,"//.","\[VeryThinSpace]",postformat@rhs}] 368 | 369 | postformat[AlternativesBlock[elems__]] := 370 | RowBox[Riffle[postformat /@ {elems}, "|"]]; 371 | 372 | postformat[FunctionBlock[body_]] := 373 | RowBox[{postformat@body, "&"}]; 374 | 375 | postformat[PatternTestBlock[p_, body_]] := 376 | RowBox[{postformat@p, "?", postformat@body}]; 377 | 378 | postformat[CompoundExpressionBlock[elems__]] := 379 | RowBox[Riffle[postformat /@ {elems}, ";"]]; 380 | 381 | (* Note: fragile! *) 382 | 383 | postformat[IfBlock[if_, cond_, iftrue_, ifcomment_, iffalse_, closingElement_]] /; ! FreeQ[ifcomment, IfCommentBlock] := 384 | RowBox[{postformat@if, "[", 385 | RowBox[{postformat@cond, ",", postformat@iftrue, ",", 386 | postformat@ifcomment, postformat@iffalse, 387 | postformat@closingElement //. EmptySymbol[] :> Sequence[]}], 388 | "]"}]; 389 | 390 | postformat[IfBlock[cond_, iftrue_, iffalse_]] := 391 | RowBox[{"If", "[", 392 | RowBox[{postformat@cond, ",", postformat@iftrue, ",", 393 | postformat@iffalse}], 394 | "]"}]; 395 | 396 | postformat[IfCommentBlock[]] := 397 | RowBox[{"(*", " ", "else", " ", "*)"}]; 398 | 399 | postformat[SuppressedCompoundExpressionBlock[elems__]] := 400 | RowBox[Append[Riffle[postformat /@ {elems}, ";"], ";"]]; 401 | 402 | postformat[ListBlock[elems___]] := 403 | RowBox[{"{", 404 | Sequence @@ (Map[postformat, {elems}] //. 405 | EmptySymbol[] :> Sequence[]), "}"}]; 406 | 407 | postformat[DataArrayBlock[elems___]] := 408 | RowBox[{"<|", 409 | Sequence @@ (Map[postformat, {elems}] //. 410 | EmptySymbol[] :> Sequence[]), "|>"}]; 411 | 412 | postformat[ParenBlock[elems__]] := 413 | RowBox[{"(", 414 | Sequence @@ (Map[postformat, {elems}] //. 415 | EmptySymbol[] :> Sequence[]), ")"}]; 416 | 417 | postformat[PatternBlock[name_, pt_]] := 418 | RowBox[{postformat@name, ":", postformat@pt}]; 419 | 420 | postformat[PatternBlock[name_, pt_, def_]] := 421 | RowBox[{postformat@name, ":", postformat@pt, ":", postformat@def}]; 422 | 423 | postformat[GeneralHeadBlock[head_, elems___]] := 424 | RowBox[{postformat@head, "[", 425 | Sequence @@ Riffle[postformat /@ {elems}, ","], "]"}]; 426 | 427 | postformat[GeneralSplitHeadBlock[head_, elems___, Tabbed[]]] := 428 | RowBox[{postformat@head, "[", 429 | Sequence @@ Riffle[postformat /@ {elems}, ","],(*"\n","\t", *) 430 | "]"}]; 431 | 432 | postformat[GeneralSplitHeadBlock[head_, elems___]] := 433 | With[ {formattedElems = postformat /@ {elems}}, 434 | RowBox[{postformat@head, "[", 435 | Sequence @@ Riffle[Most[formattedElems], ","], 436 | Last[formattedElems] //. EmptySymbol[] :> Sequence[], "]"}] 437 | ]; 438 | 439 | postformat[GeneralBlock[elems___]] := 440 | RowBox[Riffle[postformat /@ {elems}, ","]]; 441 | 442 | postformat[StatementBlock[elem_]] := 443 | postformat[elem]; 444 | 445 | postformat[MultilineBlock[elems__]] := 446 | RowBox[Riffle[postformat /@ {elems}, "\n"]]; 447 | 448 | 449 | 450 | postformat[NewlineBlock[elem_?isNextNewline, False]] := 451 | postformat@elem; 452 | 453 | 454 | postformat[SemicolonSeparatedGeneralBlock[elems__]] := 455 | RowBox[Riffle[postformat /@ {elems}, ","]]; 456 | 457 | postformat[NewlineBlock[elem_, _]] := 458 | RowBox[{"\n", postformat@elem}]; 459 | 460 | postformat[GeneralBoxBlock[box_, n_, args___]] := 461 | box[ 462 | Sequence @@ Map[postformat, Take[{args}, n]], 463 | Sequence @@ Drop[{args}, n] 464 | ]; 465 | 466 | 467 | 468 | 469 | postformat[FinalTabBlock[expr_, True,width_]] := 470 | RowBox[{ 471 | If[$useSpacesForTabs,StringJoin[ConstantArray[" ",{width}]],"\t"], 472 | postformat@expr 473 | }]; 474 | 475 | postformat[FinalTabBlock[expr_, False,_]] := 476 | postformat@expr; 477 | 478 | postformat[EmptySymbol[]] := 479 | EmptySymbol[]; 480 | 481 | postformat[a_?AtomQ] := a; 482 | 483 | 484 | postformat[arg_] := 485 | Throw[{$Failed, arg}, postformat]; 486 | 487 | 488 | 489 | 490 | Clear[maxLen]; 491 | maxLen[boxes : (_RowBox | _?boxQ[___])] := 492 | Max@Replace[ 493 | Split[ 494 | Append[Cases[boxes, s_String, Infinity], "\n"], # =!= "\n" &], 495 | {s___, ("\t" | " ") ..., "\n"} :> 496 | Total[{s} /. {"\t" -> $tabWidth, ss_ :> StringLength[ss]}], 497 | {1}]; 498 | 499 | 500 | maxLen[expr_] := 501 | With[ {boxes = postformat@expr}, 502 | maxLen[boxes] /; MatchQ[boxes, (_RowBox | _?boxQ[___])] 503 | ]; 504 | 505 | maxLen[expr_] := 506 | Throw[{$Failed, expr}, maxLen]; 507 | 508 | 509 | 510 | 511 | ClearAll[$closingElementRules]; 512 | $closingElementRules = { 513 | "Bracket" :> $alignClosingBracket , 514 | "List" :> $alignClosingList, 515 | "Parenthesis" :> $alignClosingParen, 516 | "ScopingBracket" :> $alignClosingScopingBracket, 517 | "IfBracket" :> $alignClosingIfBracket 518 | }; 519 | 520 | ClearAll[closingElement]; 521 | closingElement[type_String] := 522 | Unevaluated[ 523 | If[TrueQ@type, 524 | NewlineBlock[EmptySymbol[], True], 525 | (* else *) 526 | EmptySymbol[] 527 | ] 528 | ] /. $closingElementRules; 529 | 530 | 531 | 532 | 533 | ClearAll[needSplitQ]; 534 | needSplitQ[expr_, currentTab_] := 535 | maxLen[expr] > $maxLineLength - currentTab; 536 | 537 | 538 | ClearAll[format]; 539 | 540 | format[expr_] := 541 | format[expr, 0]; 542 | 543 | format[expr : GeneralBoxBlock[box_, n_, args___], currentTab_] := 544 | With[ {splitQ = needSplitQ[expr, currentTab]}, 545 | GeneralBoxBlock[box, n, 546 | If[ n > 0, 547 | format[First@{args}, currentTab], 548 | Sequence @@ {} 549 | ], 550 | Sequence @@ 551 | Map[format[If[ splitQ, 552 | NewlineBlock[#, False], 553 | # 554 | ], currentTab] &, 555 | Take[{args}, {2, n}]], 556 | Sequence @@ Drop[{args}, n] 557 | ] 558 | ]; 559 | 560 | 561 | format[TabBlock[expr_], currentTab_] := 562 | TabBlock[format[expr, currentTab + $tabWidth]]; 563 | 564 | format[TabBlock[expr_,True,width_],currentTab_]:= 565 | TabBlock[format[expr,currentTab+width],True,width]; 566 | 567 | format[NewlineBlock[expr_, flag_], currentTab_] := 568 | NewlineBlock[format[expr, currentTab], flag]; 569 | 570 | format[block_?blockQ[left___, sc : (_ModuleBlock | _BlockBlock | _WithBlock), right___], currentTab_] := 571 | format[block[left, NewlineBlock[sc, True], right], currentTab]; 572 | 573 | format[(head : ModuleBlock | BlockBlock | WithBlock)[vars_, body_], 574 | currentTab_] := 575 | head[ 576 | format[vars, currentTab], 577 | format[NewlineBlock[TabBlock[body], False], currentTab], 578 | closingElement["ScopingBracket"] 579 | ]; 580 | 581 | format[(head : SetDelayedBlock)[lhs_, rhs_], currentTab_] := 582 | head[ 583 | format[lhs, currentTab], 584 | format[NewlineBlock[TabBlock[rhs], False], currentTab] 585 | ]; 586 | 587 | 588 | format[(head : (ReplaceAllBlock|ReplaceRepeatedBlock))[lhs_, rhs_], currentTab_] := 589 | head[ 590 | format[lhs, currentTab], 591 | format[NewlineBlock[TabBlock[rhs], False], currentTab] 592 | ]; 593 | 594 | format[TagSetDelayedBlock[s_, lhs_, rhs_], currentTab_] := 595 | TagSetDelayedBlock[ 596 | format[s, currentTab], 597 | format[lhs, currentTab], 598 | format[NewlineBlock[TabBlock[rhs], False], currentTab] 599 | ]; 600 | 601 | format[expr : (head : (SetBlock | RuleBlock | RuleDelayedBlock))[lhs_, 602 | rhs_], currentTab_] /; needSplitQ[expr, currentTab] := 603 | head[ 604 | format[lhs, currentTab], 605 | format[NewlineBlock[TabBlock[rhs], False], currentTab] 606 | ]; 607 | 608 | 609 | format[(ce : (CompoundExpressionBlock | SuppressedCompoundExpressionBlock))[elems__], currentTab_] := 610 | With[ {formatted = Map[format[#, currentTab] &, {elems}]}, 611 | (ce @@ Map[NewlineBlock[#, False] &, formatted]) /; 612 | $alwaysBreakCompoundExpressions || !FreeQ[formatted, NewlineBlock] 613 | ]; 614 | 615 | 616 | format[StatementBlock[el_], currentTab_] := 617 | StatementBlock[format[el, currentTab]]; 618 | 619 | format[expr : IfBlock[cond_, iftrue_, iffalse_], currentTab_] /; 620 | needSplitQ[expr, currentTab] := 621 | With[ {formatF = 622 | format[TabBlock@NewlineBlock[#, False], currentTab] &}, 623 | IfBlock[ 624 | format["If", currentTab], 625 | formatF@cond, 626 | formatF@iftrue, 627 | formatF@IfCommentBlock[], 628 | formatF@iffalse, 629 | closingElement["IfBracket"] 630 | ] 631 | ]; 632 | 633 | format[expr : GeneralHeadBlock[head_, elems___], currentTab_] := 634 | With[ {splitQ = needSplitQ[expr, currentTab]}, 635 | GeneralSplitHeadBlock(* GeneralHeadBlock *)[ 636 | format[head, currentTab], 637 | Sequence @@ Map[ 638 | format[If[ splitQ, 639 | TabBlock@NewlineBlock[#, False], 640 | # 641 | ], 642 | currentTab] &, 643 | {elems}], 644 | closingElement["Bracket"] 645 | ] /; splitQ 646 | ]; 647 | 648 | format[expr : (ListBlock[elems___]), currentTab_] /; needSplitQ[expr, currentTab] := 649 | NewlineBlock[ 650 | ListBlock[ 651 | Sequence @@ Map[format[TabBlock@NewlineBlock[#, False], currentTab] &, {elems}], 652 | closingElement["List"] 653 | ], 654 | True]; 655 | 656 | format[expr : (ParenBlock[elems___]), currentTab_] /; needSplitQ[expr, currentTab] := 657 | NewlineBlock[ 658 | ParenBlock[ 659 | Sequence @@ Map[format[TabBlock@NewlineBlock[#, False], currentTab] &, {elems}], 660 | closingElement["Parenthesis"] 661 | ], 662 | True]; 663 | 664 | format[expr : ((head : (ApplyBlock | ApplyLevel1Block | MapBlock | FunApplyBlock))[f_, e_]), currentTab_] /; 665 | needSplitQ[expr, currentTab] := 666 | head[ 667 | format[f, currentTab], 668 | format[TabBlock@NewlineBlock[e, False], currentTab] 669 | ]; 670 | 671 | (* For a generic block, it is not obvious that we have to tab, so we \ 672 | don't*) 673 | format[expr : (block_?blockQ[elems___]), currentTab_] := 674 | With[ {splitQ = needSplitQ[expr, currentTab]}, 675 | block @@ Map[ 676 | format[If[ splitQ, 677 | NewlineBlock[#, False], 678 | # 679 | ], currentTab] &, 680 | {elems}] 681 | ]; 682 | 683 | format[a_?AtomQ, _] := a; 684 | 685 | 686 | $tabWidth = 4; 687 | 688 | 689 | 690 | ClearAll[FullCodeFormat, FullCodeFormatCompact]; 691 | FullCodeFormat[boxes_] := 692 | Block[{$tabWidth = $defaultTabWidth}, 693 | postformat@ 694 | tabify@format@processPreformatted@preformat@preprocess@boxes 695 | ]; 696 | 697 | FullCodeFormatCompact[boxes_] := 698 | Block[ {$alignClosingBracket = False, 699 | $alignClosingList = False, 700 | $alignClosingParen = False, 701 | $alignClosingScopingBracket = False, 702 | $alignClosingIfBracket = False}, 703 | FullCodeFormat[boxes] 704 | ]; 705 | 706 | 707 | SEFormat[boxes_,lineWidth_,tabWidth_, overallTab_]:= 708 | Block[{$defaultTabWidth = tabWidth, $maxLineLength = lineWidth,$overallTab= overallTab , 709 | $useSpacesForTabs = True}, 710 | FullCodeFormat@CustomTabBlock[boxes,$overallTab] 711 | ]; 712 | 713 | 714 | 715 | (*============================================================================================*) 716 | (*========= Simplified version of MakeBoxes ==============*) 717 | (*============================================================================================*) 718 | 719 | 720 | ClearAll[$infixRules, infixForm, infixQ, $multiInfixRules, multiInfixQ, multiInfixFormSeparator]; 721 | 722 | $infixRules = { 723 | Set -> "=", 724 | SetDelayed -> ":=", 725 | Rule -> "->", 726 | RuleDelayed -> ":>", 727 | Map -> "/@", 728 | Apply -> "@@", 729 | Pattern -> ":", 730 | PatternTest -> "?", 731 | Condition -> "/;", 732 | Optional -> ":", 733 | ReplaceAll -> "/.", 734 | ReplaceRepeated -> "//." 735 | }; 736 | 737 | $multiInfixRules = { 738 | Alternatives -> "|", 739 | And -> "&&", 740 | Or -> "||", 741 | Plus -> "+", 742 | Times -> "*", 743 | CompoundExpression -> ";", 744 | Less -> "<", 745 | LessEqual -> "<=", 746 | Greater -> ">", 747 | GreaterEqual -> ">=", 748 | Equal -> "==", 749 | Unequal -> "!=", 750 | SameQ -> "===", 751 | UnsameQ -> "=!=" 752 | }; 753 | 754 | infixForm[s_] := s /. $infixRules; 755 | 756 | SetAttributes[{infixQ, multiInfixQ}, HoldAll]; 757 | infixQ[s_Symbol] := MemberQ[$infixRules[[All, 1]], HoldPattern[s]]; 758 | 759 | multiInfixQ[s_Symbol] := 760 | MemberQ[$multiInfixRules[[All, 1]], HoldPattern[s]]; 761 | 762 | multiInfixFormSeparator[s_Symbol] := s /. $multiInfixRules; 763 | 764 | 765 | 766 | ClearAll[boxesRiffle]; 767 | SetAttributes[boxesRiffle, HoldRest]; 768 | boxesRiffle[separator_, elems___] := 769 | RowBox@Riffle[Map[makeBoxes, Unevaluated[{elems}]], separator]; 770 | 771 | 772 | ClearAll[makeBoxes]; 773 | SetAttributes[makeBoxes, HoldAllComplete]; 774 | makeBoxes[args___] := boxesRiffle[",", args]; 775 | 776 | makeBoxes[List[args___]] := RowBox[{"{", makeBoxes[args], "}"}]; 777 | 778 | makeBoxes[ Verbatim[Pattern][sym_Symbol, 779 | bl : (Verbatim[Blank][___] | Verbatim[BlankSequence][___] | 780 | Verbatim[BlankNullSequence][___])]] := 781 | makeBoxes[sym] <> ToString[Unevaluated@bl]; 782 | 783 | makeBoxes[Function[body_]] := RowBox[{makeBoxes[body], "&"}]; 784 | 785 | makeBoxes[Slot[i_Integer]] := "#" <> ToString[i]; 786 | 787 | makeBoxes[SlotSequence[i_Integer]] := "##" <> ToString[i]; 788 | 789 | makeBoxes[(h_Symbol?infixQ)[lhs_, rhs_]] := 790 | RowBox[{makeBoxes[lhs], infixForm[h], makeBoxes[rhs]}]; 791 | 792 | makeBoxes[(h_Symbol?multiInfixQ)[args___]] := 793 | RowBox[{"(", boxesRiffle[multiInfixFormSeparator[h], args], ")"}]; 794 | 795 | makeBoxes[head_[elems___]] := 796 | RowBox[{makeBoxes[head], "[", makeBoxes[elems], "]"}]; 797 | 798 | makeBoxes[s_ /; AtomQ[Unevaluated[s]]] := MakeBoxes[s]; 799 | 800 | 801 | ClearAll[CodeFormatterMakeBoxes]; 802 | SetAttributes[CodeFormatterMakeBoxes,HoldAllComplete]; 803 | CodeFormatterMakeBoxes[args__]:=makeBoxes[args] 804 | 805 | 806 | 807 | (*============================================================================================*) 808 | (*========= Printing definitions and spelunking ==============*) 809 | (*============================================================================================*) 810 | 811 | 812 | ClearAll[prn]; 813 | prn = CellPrint[Cell[BoxData[#], "Input"]] &; 814 | 815 | 816 | ClearAll[getDefContexts]; 817 | getDefContexts[f_Symbol] := 818 | Union@Flatten@Map[getDefContexts[f, #] &, globalProperties[]]; 819 | 820 | getDefContexts[f_Symbol, prop_] := 821 | Union[Cases[prop[f], s_Symbol :> Context[s], Infinity, Heads -> True]]; 822 | 823 | 824 | ClearAll[globalProperties]; 825 | globalProperties[] := {DownValues, SubValues, UpValues, OwnValues}; 826 | 827 | 828 | ClearAll[$boxFunction]; 829 | $boxFunction = CodeFormatterMakeBoxes; 830 | 831 | 832 | ClearAll[defBoxes]; 833 | defBoxes[f_Symbol] := 834 | Flatten@Map[defBoxes[f, #] &, globalProperties[]]; 835 | 836 | defBoxes[f_Symbol, prop_] := 837 | Cases[prop[f], 838 | Verbatim[RuleDelayed][Verbatim[HoldPattern][lhs_], 839 | rhs_] :> $boxFunction@SetDelayed[lhs, rhs]] 840 | 841 | 842 | ClearAll[printDefs]; 843 | printDefs[f_Symbol] := 844 | Scan[Composition[prn, FullCodeFormat], defBoxes[f]]; 845 | 846 | 847 | ClearAll[CodeFormatterPrint]; 848 | CodeFormatterPrint[f_Symbol] := 849 | ( 850 | CellPrint[Cell[ToString[f], "Subsubsection"]]; 851 | printDefs[f] 852 | ); 853 | 854 | 855 | ClearAll[withSymbolDefContexts]; 856 | SetAttributes[withSymbolDefContexts, HoldAll]; 857 | withSymbolDefContexts[f_Symbol, code_] := 858 | Block[{$ContextPath = 859 | Join[DeleteCases[getDefContexts[f], "System`"], $ContextPath]}, 860 | code]; 861 | 862 | 863 | ClearAll[spelunk]; 864 | spelunk[f_Symbol, boxFunction_: $boxFunction] := 865 | Block[{$boxFunction = boxFunction}, 866 | withSymbolDefContexts[f, 867 | CellPrint[Cell[ToString[f], "Subsubsection"]]; printDefs[f]] 868 | ]; 869 | 870 | 871 | ClearAll[CodeFormatterSpelunk]; 872 | CodeFormatterSpelunk = spelunk; 873 | 874 | 875 | 876 | ClearAll[codeFormatted]; 877 | SetAttributes[codeFormatted,HoldAll]; 878 | codeFormatted[code_]:= 879 | With[{formatted = Catch[FullCodeFormat @ MakeBoxes @ code, _]}, 880 | prn[formatted] /; !MatchQ[formatted, {$Failed,_}] 881 | ]; 882 | 883 | codeFormatted[code_]:= 884 | prn @ FullCodeFormat @ CodeFormatterMakeBoxes @ code; 885 | 886 | 887 | ClearAll[CodeFormatted]; 888 | SetAttributes[CodeFormatted,HoldAll]; 889 | 890 | CodeFormatted /: (h:(Set|SetDelayed|UpSet|UpSetDelayed))[lhs_,CodeFormatted[rhs]]:= 891 | CodeFormatted[h[lhs,rhs]]; 892 | 893 | CodeFormatted /: (h:(TagSet|TagSetDelayed))[tag_,lhs_,CodeFormatted[rhs]]:= 894 | CodeFormatted[h[tag,lhs,rhs]]; 895 | 896 | CodeFormatted /: CompoundExpression[prev___,CodeFormatted[last_]]:= 897 | CodeFormatted[CompoundExpression[prev,last]]; 898 | 899 | CodeFormatted[code_]:= codeFormatted[code]; 900 | 901 | 902 | 903 | End[] 904 | 905 | EndPackage[] 906 | 907 | 908 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Leonid Shifrin 2 | 3 | This project is licensed under the MIT license, 4 | 5 | http://opensource.org/licenses/MIT 6 | 7 | Here is the license text: 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining 10 | a copy of this software and associated documentation files (the "Software"), 11 | to deal in the Software without restriction, including without limitation 12 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | and / or sell copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 22 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##CodeFormatter.m 2 | 3 | ###Formatting Mathematica code 4 | 5 | `CodeFormatter.m` is a pretty-printer / code formatter for 6 | code written in the *Mathematica* language, which is also 7 | written in *Mathematica*. It works on the box level at the 8 | moment. The direct support for strings has not yet been 9 | implemented, but one can always convert code to boxes and 10 | use it then. Only a few boxes are currently supported, but 11 | those cover a vast majority of cases encountered for normal 12 | (not typeset) *Mathematica* code. The formatter is extensible 13 | and the support for more boxes will be added in the future. 14 | 15 | ###Installation 16 | 17 | The installation procedure is standard, as for any *Mathematica* 18 | package: 19 | 20 | - Download the package 21 | - Place it into one of the directories where *Mathematica* 22 | can find it, for example in a directory returned by evaluating 23 | `FileNameJoin[{$UserBaseDirectory,"Applications"}]` 24 | - Call ``Needs["CodeFormatter`"]`` 25 | 26 | ###How to use 27 | 28 | There are currently two public functions, `FullCodeFormat` and 29 | `FullCodeFormatCompact`. Each of them accepts code in the box 30 | form, as a single argument. You can use `MakeBoxes` to convert 31 | code to boxes first, if you have it as an expression. What is 32 | returned is a formatted code, also in the box form. I recommend 33 | using some helper function such as this: 34 | 35 | prn = CellPrint[Cell[BoxData[#], "Input"]] & 36 | 37 | To apply to the resulting formatted code. As an example, you can 38 | try something like this: 39 | 40 | prn@FullCodeFormat@MakeBoxes@ 41 | Module[{a, b}, a = 1; Block[{c, d}, c = a + 1; d = b + 2]; b] 42 | 43 | or, as a more interesting example (I intentionally did not format 44 | this one here): 45 | 46 | prn@FullCodeFormat@MakeBoxes[ 47 | SetAttributes[CleanUp, HoldAll]; 48 | CleanUp[expr_, cleanup_] := 49 | Module[{exprFn, result, abort = False, rethrow = True, seq}, 50 | exprFn[] := expr; result = CheckAbort[ Catch[Catch[result 51 | = exprFn[]; rethrow = False; result], _, seq[##1] &], abort 52 | = True]; cleanup; If[abort, Abort[]]; If[rethrow, Throw[result 53 | /. seq -> Sequence]]; result]] 54 | 55 | Note that in some cases, the `prn@FullCodeFormat@MakeBoxes` may result in 56 | an error because the formatter might not yet support some of the boxes 57 | created by `MakeBoxes`. In this case, you may try to use the simplified 58 | function `CodeFormatterMakeBoxes`, in place of `MakeBoxes`: 59 | 60 | prn @ FullCodeFormat @ CodeFormatterMakeBoxes @ your-code 61 | 62 | 63 | There are also functions `CodeFormatterPrint[f]` to pretty-print definitions 64 | for a symbol `f`, and `CodeFormatterSpelunk[f]`, which does the same, but 65 | strips off all contexts in symbol names. 66 | 67 | Also, there is a short-cut function `CodeFormatted`, which can be wrapped 68 | around the input code in the "Input" cell, and when executed (SHIFT+ENTER), 69 | it will print below the cell with formatted code. 70 | 71 | 72 | ###Further resources 73 | 74 | The notebook coming with the package contains many more examples 75 | 76 | ###License 77 | 78 | This package is released under MIT Open Source license. The copy of the license can be found [in the project](https://github.com/lshifr/CodeFormatter/blob/master/LICENSE) 79 | 80 | 81 | 82 | 83 | 84 | --------------------------------------------------------------------------------