├── .gitignore ├── LICENSE ├── README.md ├── Setup.hs ├── cow.cabal ├── default.nix ├── demos ├── big.html ├── big.ltx ├── bigL.js ├── bigR.js ├── change.sty ├── cow.css ├── cow.js ├── diffL.js ├── diffOut.html ├── diffOut.ltx ├── diffR.js ├── jquery.js └── old │ ├── blarg.html │ ├── blarg.ltx │ ├── blarg.pdf │ ├── change.sty │ ├── cow.css │ ├── cow.js │ ├── diffL.js │ ├── diffR.js │ ├── illustration.ltx │ ├── illustration.pdf │ ├── jquery.js │ ├── move.html │ ├── move.ltx │ ├── move.pdf │ ├── moveL.js │ ├── moveOut.html │ ├── moveOut.ltx │ ├── moveR.js │ └── test.js ├── nix ├── haskell.nix ├── nixpkgs.nix └── pinned.nix ├── rough-logo.png ├── shell.nix └── src └── Cow ├── Diff.hs ├── Examples.hs ├── Language ├── JavaScript.hs ├── JavaScript │ └── Value.hs └── Token.hs ├── Main.hs ├── ParseTree.hs └── ParseTree ├── Read.hs └── Viz.hs /.gitignore: -------------------------------------------------------------------------------- 1 | # Nix build results 2 | result 3 | 4 | # Emacs backup files: 5 | *~ 6 | \#*\# 7 | 8 | # Haskell build artifacts 9 | *.hi 10 | *.o 11 | dist 12 | dist-newstyle 13 | 14 | # IPython (IHaskell) notebook stuff 15 | *.ipynb* 16 | 17 | # Files generated by LaTeX 18 | out.* 19 | _region* 20 | *.aux 21 | *.log 22 | .\#* 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cow: Structural Version Control 2 | 3 | Normal diff and merge tools treat programs as plain text. They are easily confused by superficial changes to the code and can't take advantage of the code's inherent structure for deeper analysis. 4 | 5 | Language specific **semantic version control** tools can provide a high level of analysis but are tied to the syntax and semantics of specific languages. Tools that understand Java class and method declarations¹ can provide an improved diff and merge experience but don't easily generalize. You could extend the approach to languages with substantially similar models (like C♯) but working with more distinct languages would require new logic. 6 | 7 | The approach here takes a middle ground: we take advantage of the *structure* of the text without being tied to its semantics. By operating on **parse trees** with no deeper semantic information, our algorithms can work for any language that we can parse while still giving more insight than a tool constrained to plain text. 8 | 9 | #### footnotes 10 | ¹ Like the [SemanticMerge] system. 11 | 12 | [SemanticMerge]: https://www.semanticmerge.com/ 13 | 14 | ## Architecture Overview 15 | 16 | The current approach works as a pipeline of several distinct algorithms. Each step will produce an intermediate form with a clear interface, making it easy to plug additional logic in between any two steps. 17 | 18 | 1. **Parsing**: the only language-specific part of the pipeline is producing parse trees. In this context, a **parse tree** is a slightly lower-level representation than an abstract syntax tree: it's a tree that's only labeled at its leaves, with exactly one leaf node per token. Reading out the leaves left-to-right recreates the input stream of tokens. 19 | 20 | Each language we support will need a custom parser. In practice, it seems hard to reuse existing language parsers because the behavior we need for diffing and merging is different from what compilers and most other tools require: 21 | 22 | * We can make distinctions the compiler wouldn't, like detecting statements grouped with blank lines. 23 | * We want to preserve as much lexical information as possible to recreate the program text—especially for merging. 24 | * Controlling how the code is parsed gives us fine control over the resulting diff, letting us selectively ignore things we don't care about. Think of this is a sophisticated, syntax-aware alternative to ignoring whitespace with normal diff. 25 | 26 | It's definitely worth exploring round-tripping parsers like `ghc-exactprint`, although I suspect there are still advantages to hand-writing parsers specifically for this tool. 27 | 28 | 2. **Parse tree diff**: we find a minimal number of additions and deletions to go between two parse trees. The tree structure is used to consolidate multiple changes into one: if you changed most of the lines in a block of code, it's useful to think of that as a single action rather than a bunch of separate actions per line. The threshold for when to consolidate changes is based on a heuristic which will have to be tuned per-language. 29 | 30 | This diff reflects the structure of the code, so it can reduce the amount of noise that text-based algorithms provide. The main advantage, though, is that this extra structure can be fed into further analysis steps, giving us higher-level information about the code differences. 31 | 32 | The current algorithm for calculating the needed changes is a modification of the [Wagner-Fischer] algorithm for normal string edit distance. It's based on dynamic programming and runs in O(n²) time and space where n is the total number of nodes in the tree (including internal nodes). 33 | 34 | 3. **Substructure detection**: the next step identifies which blocks of code (ie subtrees) correspond between the two trees being compared, with some heuristic for determining when two subtrees are "close enough". This information is used to find blocks of code that were moved and can also be used for additional analysis by a plugin. 35 | 36 | It's useful to think of this as a graph problem where each subtree is a node with edges to each other subtree weighted based on some distance between them (perhaps based on the distances calculated in step 2). Given this graph, there are two possible approaches I'm considering: 37 | 38 | * *bipartite graph matching*: the earlier proof of concept found matching substructures by finding the best match between subtrees from the input and output and keeping all the resulting pairs that were close enough in weight. This approach is solid in common cases where you just move a block of code, but doesn't detect more complex transformations like copying a subtree into *two* places in the new tree. 39 | 40 | * *clustering*: the main alternative I'm considering now is some sort of clustering algorithm that tries to find all the subtrees that are "close enough". This will be able to detect more complex relationships in the code but will likely also depend more on the weights and heuristics used to identify clusters. 41 | 42 | I've currently implemented the bipartite graph matching system, but I'm starting to suspect a more general clustering approach is a better bet. 43 | 44 | 4. **Merging**: the final (optional) step is merging and conflict resolution. One of the neat advantages to how the tree diff was designed for step 2 is that the resulting diff is a parse tree itself with some additional annotations. This allows us to find conflicts by doing a diff-of-diffs. 45 | 46 | Once we have a diff-of-diffs, we should be able to resolve more conflicts than a purely text-based system thanks to the high-level information we derived in the previous steps. For example, we can apply *both* a move *and* a modification to a block even if the changes physically overlap in the text. 47 | 48 | This step is the least-developed in the current system. The basic idea worked well in the old proof-of-concept but I've rethought many of the details of the previous steps without revisting merging. I'll put more thought and work towards merging once I get the previous few steps working satisfactorily. 49 | 50 | [Wagner-Fischer]: https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm 51 | 52 | ## Progress 53 | 54 | This project is at an early proof-of-concept stage. At this point I've figured out the core parse-tree-diff algorithm and implemented the bipartite-matching-based substructure detection step, but have not really tuned this for real use. The only language currently supported is JavaScript, with a pretty rough parser written in Parsec. 55 | 56 | ## History 57 | 58 | Most of the code comes from a few years ago and is purely exploratory in nature. It was the final project for CS164, the programming languages class at Berkeley. I worked on the project with Ankur Dave whose insights helped shaped much of the design. 59 | 60 | I didn't really know what I wanted to accomplish so it's rough and hard to follow. I also never managed to get the diff algorithm working efficiently (partly because it was never well-specified), so never tested anything on non-trivial amounts of code. 61 | 62 | I've started working on the project again recently. Thanks largely to collaboration with Dimitri DeFigueiredo I've come up with a significantly more concrete idea of what I want to accomplish in each part of the system, and I've figured out an efficient way to implement the parse tree diff algorithm based on [Wagner-Fischer]. 63 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /cow.cabal: -------------------------------------------------------------------------------- 1 | Name: cow 2 | Version: 0.3.0.0 3 | Synopsis: Semantic version control: language-aware diff and merge. 4 | Description: An advanced diff and merge tool that parses code files and then compares the resulting parse trees rather than the actual text of the program. 5 | Homepage: http://jelv.is/cow 6 | 7 | License: GPL-3 8 | License-file: LICENSE 9 | 10 | 11 | Author: Ankur Dave, Tikhon Jelvis 12 | Maintainer: Tikhon Jelvis 13 | 14 | Category: Development 15 | 16 | Build-type: Simple 17 | 18 | Cabal-version: >=1.9.2 19 | 20 | Library 21 | GHC-options: -rtsopts -O2 22 | Hs-source-dirs: src 23 | 24 | Build-depends: array >= 0.3 25 | , base 26 | , containers >= 0.4 27 | , diagrams-contrib >= 1.3 28 | , diagrams-lib >= 1.3 29 | , diagrams-svg >= 1.3 30 | , lens >= 4.13 31 | , mtl >= 2 32 | , parsec >= 3 33 | , text >= 1.2 34 | 35 | Exposed-modules: Cow.Diff 36 | , Cow.Examples 37 | , Cow.Language.JavaScript 38 | , Cow.Language.JavaScript.Value 39 | , Cow.Language.Token 40 | , Cow.ParseTree 41 | , Cow.ParseTree.Read 42 | , Cow.ParseTree.Viz 43 | 44 | Executable cow 45 | Main-is: src/Cow/Main.hs 46 | Build-depends: base 47 | , cow 48 | , diagrams-svg >= 1.3 49 | , lens >= 4.13 50 | , parsec >= 3 51 | , text >= 1.2 52 | GHC-options: -rtsopts -O2 53 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import nix/nixpkgs.nix {} 2 | , compiler ? null 3 | , haskellPackages ? import nix/haskell.nix { compiler = compiler; } 4 | }: 5 | haskellPackages.developPackage { 6 | name = "cow"; 7 | root = (pkgs.lib.cleanSourceWith { 8 | src = ./.; 9 | filter = path: type: 10 | let 11 | name = baseNameOf (toString path); 12 | ignored = ["dist" "dist-newstyle"]; 13 | in 14 | builtins.all (ignored-file: name != ignored-file) ignored && 15 | !pkgs.lib.hasPrefix ".ghc.environment" name && 16 | pkgs.lib.cleanSourceFilter path type; 17 | }).outPath; 18 | 19 | # Disable "smart" Nix-shell detection because it is impure (depends 20 | # on the IN_NIX_SHELL environment variable), which can cause 21 | # hard-to-debug Nix issues. 22 | # 23 | # Instead, we have an explicit shell.nix with extra shell-specific 24 | # settings. 25 | returnShellEnv = false; 26 | } 27 | -------------------------------------------------------------------------------- /demos/big.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TikhonJelvis/Cow/65832f9826ede4c1380cdc35a42f2b85b1bfebea/demos/big.html -------------------------------------------------------------------------------- /demos/big.ltx: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | \usepackage{change} 3 | \usepackage[margin=1in, paperwidth=100in, textwidth=100in, paperheight=10in]{geometry} 4 | \begin{document} 5 | \synttree[\Non{\uppercase{root}}] 6 | \end{document} 7 | -------------------------------------------------------------------------------- /demos/bigL.js: -------------------------------------------------------------------------------- 1 | function Solitaire() { 2 | var board = new Board({ 3 | rootId : "Klondike", 4 | magicalX : 106 5 | }); 6 | 7 | var totalDeck = new Deck(board.collapsedType, 0, 0, { 8 | filter : function () { return false; }, 9 | draggable : false 10 | }); 11 | totalDeck.initialize(true); 12 | 13 | var discard = new Deck(board.collapsedType, 1, 0, { 14 | filter : function(card) { return false; } 15 | }); 16 | board.addDeck(discard); 17 | 18 | totalDeck.shuffle(); 19 | totalDeck.setAction(function () { 20 | var top = totalDeck.peek(); 21 | 22 | if (!top) { 23 | discard.deal(totalDeck, discard.getCards().length, true); 24 | } else { 25 | top.setFaceUp(true); 26 | totalDeck.remove(top); 27 | discard.addTop(top); 28 | } 29 | }); 30 | board.addDeck(totalDeck); 31 | 32 | var pile; 33 | var endPiles = []; 34 | 35 | for (var i = 1; i <= 7; i++) { 36 | pile = new Deck(board.defaultType, i - 1, 1); 37 | totalDeck.deal(pile, i, true); 38 | 39 | var topCard = pile.peek(); 40 | if (topCard) { 41 | topCard.setFaceUp(true); 42 | } 43 | 44 | pile.setFilter((function (pile) { 45 | return function (card) { 46 | var top = pile.peek(); 47 | 48 | if (top) { 49 | return (top.getColor() != card.getColor()) && 50 | top.getRank() - card.getRank() ==1; 51 | } else { 52 | return card.getRank() == 13; 53 | } 54 | }; 55 | })(pile)); 56 | 57 | pile.observe((function (pile) { 58 | return function (event) { 59 | if (event.type == "remove") { 60 | if (pile.peek()) { 61 | pile.peek().setFaceUp(true); 62 | } 63 | } 64 | }; 65 | })(pile)); 66 | 67 | board.addDeck(pile); 68 | } 69 | 70 | for (i = 0; i < 4; i++) { 71 | endPiles[i] = new Deck(board.collapsedType, 3 + i, 0); 72 | board.addDeck(endPiles[i]); 73 | endPiles[i].setFilter((function (pile) { 74 | return function (card, deck, size) { 75 | if (size > 1) { 76 | return false; 77 | } 78 | 79 | var top = pile.peek(); 80 | 81 | if (top) { 82 | return (top.getSuit() == card.getSuit()) && 83 | (card.getRank() - top.getRank() == 1); 84 | } else { 85 | return card.getRank() == 1; 86 | } 87 | }; 88 | })(endPiles[i])); 89 | } 90 | } 91 | $(document).ready(function() { new Solitaire(); }); -------------------------------------------------------------------------------- /demos/bigR.js: -------------------------------------------------------------------------------- 1 | function Solitaire() { 2 | var board = new Board({ 3 | rootId : "Klondike", 4 | magicalX : 106 5 | }); 6 | 7 | var totalDeck = new Deck(board.collapsedType, 0, 0, { 8 | filter : function () { return false; }, 9 | draggable : false 10 | }); 11 | totalDeck.initialize(false); 12 | 13 | var discard = new Deck(board.collapsedType, 1, 0, { 14 | filter : function(card) { return false; } 15 | }); 16 | board.addDeck(discard); 17 | 18 | totalDeck.shuffle(); 19 | totalDeck.setAction(action); 20 | 21 | function action() { 22 | var top = totalDeck.peek(); 23 | 24 | if (!top) { 25 | discard.deal(totalDeck, discard.getCards().length, true); 26 | } else { 27 | top.setFaceUp(true); 28 | totalDeck.remove(top); 29 | discard.addTop(top); 30 | } 31 | } 32 | board.addDeck(totalDeck); 33 | 34 | var pile; 35 | var endPiles = []; 36 | 37 | for (var i = 1; i <= 7; i++) { 38 | pile = new Deck(board.defaultType, i - 1, 1); 39 | totalDeck.deal(pile, i, true); 40 | 41 | if (topCard) { 42 | var topCard = pile.peek(); 43 | topCard.setFaceUp(true); 44 | } 45 | 46 | pile.setFilter((function (pile) { 47 | return function (card) { 48 | var top = pile.peek(); 49 | 50 | if (top) { 51 | return (top.getColor() != card.getColor()) && 52 | top.getRank() - card.getRank() ==1; 53 | } else { 54 | return card.getRank() == 13; 55 | } 56 | }; 57 | })(pile)); 58 | 59 | pile.observe((function (pile) { 60 | return function (event) { 61 | if (event.type == "remove") { 62 | if (pile.peek()) { 63 | pile.peek().setFaceUp(true); 64 | } 65 | } 66 | }; 67 | })(pile)); 68 | 69 | board.addDeck(pile); 70 | } 71 | 72 | for (i = 0; i < 4; i++) { 73 | endPiles[i] = new Deck(board.collapsedType, 3 + i, 0); 74 | board.addDeck(endPiles[i]); 75 | endPiles[i].setFilter((function (pile) { 76 | return function (card, deck, size) { 77 | if (size > 1) { 78 | return false; 79 | } 80 | 81 | var topCard = pile.peek(); 82 | 83 | if (topCard) { 84 | return (topCard.getSuit() == card.getSuit()) && 85 | (card.getRank() - topCard.getRank() == 1); 86 | } else { 87 | return card.getRank() == 1; 88 | } 89 | }; 90 | })(endPiles[i])); 91 | } 92 | } 93 | $(document).ready(function() { new Solitaire(); }); -------------------------------------------------------------------------------- /demos/change.sty: -------------------------------------------------------------------------------- 1 | % Formatting for the Change type in Cow.Type 2 | 3 | \RequirePackage{color} 4 | \RequirePackage{synttree} 5 | \RequirePackage[usenames,dvipsnames]{xcolor} 6 | 7 | \newcommand{\Non}[1]{$#1$} 8 | \newcommand{\Mod}[2]{\colorbox{SkyBlue}{$#1 \to #2$}} 9 | \newcommand{\Ins}[1]{\colorbox{Emerald}{#1}} 10 | \newcommand{\Del}[1]{\colorbox{Maroon}{#1}} 11 | \newcommand{\From}[2]{\colorbox{Goldenrod}{$\langle #1 \rangle$#2}} 12 | \newcommand{\To}[2]{\colorbox{GreenYellow}{$\langle #1 \rangle$#2}} 13 | \newcommand{\Conflict}[2]{#1 $\ne$ #2} 14 | \newcommand{\NoConflict}[1]{#1} -------------------------------------------------------------------------------- /demos/cow.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family : monospace; 3 | } 4 | 5 | .block { 6 | padding-left : 1em; 7 | } 8 | 9 | .ins { 10 | background-color : #3D3; 11 | } 12 | 13 | .del { 14 | background-color : #D33; 15 | } 16 | 17 | .mod { 18 | background-color : #D90; 19 | } 20 | 21 | .to { 22 | background-color : #99F; 23 | } 24 | 25 | .from { 26 | background-color : #F99; 27 | } -------------------------------------------------------------------------------- /demos/cow.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $("span.tagged").mouseover(function () { 3 | var classNumber = $(this).attr('class').match(/n\d+/)[0]; 4 | 5 | $("." + classNumber).css('background', '#99D'); 6 | }); 7 | 8 | $("span.tagged").mouseout(function () { 9 | var classNumber = $(this).attr('class').match(/n\d+/)[0]; 10 | 11 | $("." + classNumber).css('background', ''); 12 | }); 13 | 14 | $("span.from").mouseover(function () { 15 | var idNumber = $(this).attr('class').match(/id\d+/)[0]; 16 | 17 | $("." + idNumber + ".to").css("box-shadow", "5px 5px 5px #AAA"); 18 | }); 19 | $("span.from").mouseout(function () { 20 | var idNumber = $(this).attr('class').match(/id\d+/)[0]; 21 | 22 | $("." + idNumber + ".to").css("box-shadow", ""); 23 | }); 24 | $("span.to").mouseover(function () { 25 | var idNumber = $(this).attr('class').match(/id\d+/)[0]; 26 | 27 | console.log($("." + idNumber + ".from").css("box-shadow", "5px 5px 5px #AAA")); 28 | $("." + idNumber + ".from").css("box-shadow", "5px 5px 5px #AAA"); 29 | }); 30 | $("span.to").mouseover(function () { 31 | var idNumber = $(this).attr('class').match(/id\d+/)[0]; 32 | 33 | $("." + idNumber + ".from").css("box-shadow", ""); 34 | }); 35 | }); -------------------------------------------------------------------------------- /demos/diffL.js: -------------------------------------------------------------------------------- 1 | var x = 0, 2 | y = 0; 3 | 4 | while (x == 0) { 5 | x++ 6 | y++ 7 | console.log(x, y) 8 | } -------------------------------------------------------------------------------- /demos/diffOut.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cow: Semantic Diffing and Merging 4 | 5 | 6 | 7 | 8 | 9 | var x a = 0.0 y b = 0.0

while (x a == 0.0 ) {
x a
y b
console . log (x a , y b )
}


alert ("Hello, world" )

function (a , b ) {
return a + b
}
10 | 11 | 12 | -------------------------------------------------------------------------------- /demos/diffOut.ltx: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | \usepackage{change} 3 | \usepackage[margin=1in, paperwidth=100in, textwidth=100in, paperheight=10in]{geometry} 4 | \begin{document} 5 | \synttree[\Non{\uppercase{root}}Node {rootLabel = \Non{\textsc{var}}, subForest = [Node {rootLabel = \Non{\uppercase{assignment}}, subForest = [Node {rootLabel = \Mod{x}{a}, subForest = []},Node {rootLabel = \Non{$0$}, subForest = []}]},Node {rootLabel = \Non{\uppercase{assignment}}, subForest = [Node {rootLabel = \Mod{y}{b}, subForest = []},Node {rootLabel = \Non{$0$}, subForest = []}]}]} Node {rootLabel = \Non{\textsc{while}}, subForest = [Node {rootLabel = \Non{\uppercase{init}}, subForest = [Node {rootLabel = \Non{$==$}, subForest = [Node {rootLabel = \Mod{x}{a}, subForest = []},Node {rootLabel = \Non{$0$}, subForest = []}]}]},Node {rootLabel = \Non{\uppercase{block}}, subForest = [Node {rootLabel = \Non{$post++$}, subForest = [Node {rootLabel = \Mod{x}{a}, subForest = []}]},Node {rootLabel = \Non{$post++$}, subForest = [Node {rootLabel = \Mod{y}{b}, subForest = []}]},Node {rootLabel = \Non{dot}, subForest = [Node {rootLabel = \Non{console}, subForest = []},Node {rootLabel = \Non{\uppercase{call}}, subForest = [Node {rootLabel = \Non{log}, subForest = []},Node {rootLabel = \Non{\uppercase{args}}, subForest = [Node {rootLabel = \Mod{x}{a}, subForest = []},Node {rootLabel = \Mod{y}{b}, subForest = []}]}]}]}]}]} Node {rootLabel = \Ins{\uppercase{call}}, subForest = [Node {rootLabel = \Ins{alert}, subForest = []},Node {rootLabel = \Ins{\uppercase{args}}, subForest = [Node {rootLabel = \Ins{"Hello, world"}, subForest = []}]}]} Node {rootLabel = \Ins{\uppercase{function}}, subForest = [Node {rootLabel = \Ins{\uppercase{parameters}}, subForest = [Node {rootLabel = \Ins{a}, subForest = []},Node {rootLabel = \Ins{b}, subForest = []}]},Node {rootLabel = \Ins{\uppercase{block}}, subForest = [Node {rootLabel = \Ins{\textsc{return}}, subForest = [Node {rootLabel = \Ins{$+$}, subForest = [Node {rootLabel = \Ins{a}, subForest = []},Node {rootLabel = \Ins{b}, subForest = []}]}]}]}]}] 6 | \end{document} 7 | -------------------------------------------------------------------------------- /demos/diffR.js: -------------------------------------------------------------------------------- 1 | var a = 0, 2 | b = 0; 3 | 4 | while (a == 0) { 5 | a++ 6 | b++ 7 | console.log(a, b) 8 | } 9 | 10 | alert("Hello, world"); 11 | 12 | function (a, b) { 13 | return a + b 14 | } -------------------------------------------------------------------------------- /demos/jquery.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.7.2 jquery.com | jquery.org/license */ 2 | (function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
"+""+"
",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
t
",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( 3 | a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f 4 | .clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); -------------------------------------------------------------------------------- /demos/old/blarg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cow: Semantic Diffing and Merging 4 | 5 | 6 | 7 | 8 | 9 | var x a = 0.0 y b = 0.0

while (x a == 0.0 ) {
x a
y b
console . log (x a , y b )
}


alert ("Hello, world" )

function (a , b ) {
return a + b
}
10 | 11 | 12 | -------------------------------------------------------------------------------- /demos/old/blarg.ltx: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | \usepackage{change} 3 | \usepackage[margin=1in, paperwidth=100in, textwidth=100in, paperheight=10in]{geometry} 4 | \begin{document} 5 | \synttree[\Non{\uppercase{root}}[\Non{\textsc{var}}[\Non{\uppercase{assignment}}[\Mod{x}{a}] [\Non{$0$}]] [\Non{\uppercase{assignment}}[\Mod{y}{b}] [\Non{$0$}]]] [\Non{\textsc{while}}[\Non{\uppercase{init}}[\Non{$==$}[\Mod{x}{a}] [\Non{$0$}]]] [\Non{\uppercase{block}}[\Non{$post++$}[\Mod{x}{a}]] [\Non{$post++$}[\Mod{y}{b}]] [\Non{dot}[\Non{console}] [\Non{\uppercase{call}}[\Non{log}] [\Non{\uppercase{args}}[\Mod{x}{a}] [\Mod{y}{b}]]]]]] [\Ins{\uppercase{call}}[\Ins{alert}] [\Ins{\uppercase{args}}[\Ins{"Hello, world"}]]] [\Ins{\uppercase{function}}[\Ins{\uppercase{parameters}}[\Ins{a}] [\Ins{b}]] [\Ins{\uppercase{block}}[\Ins{\textsc{return}}[\Ins{$+$}[\Ins{a}] [\Ins{b}]]]]]] 6 | \end{document} 7 | -------------------------------------------------------------------------------- /demos/old/blarg.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TikhonJelvis/Cow/65832f9826ede4c1380cdc35a42f2b85b1bfebea/demos/old/blarg.pdf -------------------------------------------------------------------------------- /demos/old/change.sty: -------------------------------------------------------------------------------- 1 | % Formatting for the Change type in Cow.Type 2 | 3 | \RequirePackage{color} 4 | \RequirePackage{synttree} 5 | \RequirePackage[usenames,dvipsnames]{xcolor} 6 | 7 | \newcommand{\Non}[1]{$#1$} 8 | \newcommand{\Mod}[2]{\colorbox{SkyBlue}{$#1 \to #2$}} 9 | \newcommand{\Ins}[1]{\colorbox{Emerald}{#1}} 10 | \newcommand{\Del}[1]{\colorbox{Maroon}{#1}} 11 | \newcommand{\From}[2]{\colorbox{Goldenrod}{$\langle #1 \rangle$#2}} 12 | \newcommand{\To}[2]{\colorbox{GreenYellow}{$\langle #1 \rangle$#2}} 13 | \newcommand{\Conflict}[2]{#1 $\ne$ #2} 14 | \newcommand{\NoConflict}[1]{#1} -------------------------------------------------------------------------------- /demos/old/cow.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family : monospace; 3 | } 4 | 5 | .block { 6 | padding-left : 1em; 7 | } 8 | 9 | .ins { 10 | background-color : #3D3; 11 | } 12 | 13 | .del { 14 | background-color : #D33; 15 | } 16 | 17 | .mod { 18 | background-color : #D90; 19 | } 20 | 21 | .to { 22 | background-color : #99F; 23 | } 24 | 25 | .from { 26 | background-color : #F99; 27 | } -------------------------------------------------------------------------------- /demos/old/cow.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $("span.tagged").mouseover(function () { 3 | var classNumber = $(this).attr('class').match(/n\d+/)[0]; 4 | 5 | $("." + classNumber).css('background', '#99D'); 6 | }); 7 | 8 | $("span.tagged").mouseout(function () { 9 | var classNumber = $(this).attr('class').match(/n\d+/)[0]; 10 | 11 | $("." + classNumber).css('background', ''); 12 | }); 13 | 14 | $("span.from").mouseover(function () { 15 | var idNumber = $(this).attr('class').match(/id\d+/)[0]; 16 | 17 | $("." + idNumber + ".to").css("box-shadow", "5px 5px 5px #AAA"); 18 | }); 19 | $("span.from").mouseout(function () { 20 | var idNumber = $(this).attr('class').match(/id\d+/)[0]; 21 | 22 | $("." + idNumber + ".to").css("box-shadow", ""); 23 | }); 24 | $("span.to").mouseover(function () { 25 | var idNumber = $(this).attr('class').match(/id\d+/)[0]; 26 | 27 | console.log($("." + idNumber + ".from").css("box-shadow", "5px 5px 5px #AAA")); 28 | $("." + idNumber + ".from").css("box-shadow", "5px 5px 5px #AAA"); 29 | }); 30 | $("span.to").mouseover(function () { 31 | var idNumber = $(this).attr('class').match(/id\d+/)[0]; 32 | 33 | $("." + idNumber + ".from").css("box-shadow", ""); 34 | }); 35 | }); -------------------------------------------------------------------------------- /demos/old/diffL.js: -------------------------------------------------------------------------------- 1 | var x = 0, 2 | y = 0; 3 | 4 | while (x == 0) { 5 | x++ 6 | y++ 7 | console.log(x, y) 8 | } -------------------------------------------------------------------------------- /demos/old/diffR.js: -------------------------------------------------------------------------------- 1 | var a = 0, 2 | b = 0; 3 | 4 | while (a == 0) { 5 | a++ 6 | b++ 7 | console.log(a, b) 8 | } 9 | 10 | alert("Hello, world"); 11 | 12 | function (a, b) { 13 | return a + b 14 | } -------------------------------------------------------------------------------- /demos/old/illustration.ltx: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | \usepackage[margin=1in, paperwidth=40in, textwidth=40in, paperheight=10in]{geometry} 3 | \usepackage{change} 4 | \begin{document} 5 | \synttree[\Non{\uppercase{root}}[\Non{\uppercase{function}}[\Non{foo}] [\Non{\uppercase{parameters}}[\Non{a}] [\Non{b}]] [\Non{\uppercase{block}}[\Non{\textsc{return}}[\Non{$+$}[\Non{a}] [\Non{b}]]]]] [\Non{\uppercase{function}}[\Non{foo2}] [\Non{\uppercase{parameters}}[\Non{a}] [\Non{b}]] [\Non{\uppercase{block}}[\Non{\textsc{return}}[\Non{$-$}[\Non{a}] [\Non{b}]]]]]] 6 | \newpage 7 | \synttree[\Non{\uppercase{root}}[\Non{\uppercase{function}}[\Non{foo}] [\Non{\uppercase{parameters}}[\Non{a}] [\Non{b}]] [\Non{\uppercase{block}}[\Non{\textsc{return}}[\Non{$+$}[\Non{a}] [\Non{b}]]]]] [\Non{\uppercase{function}}[\Non{foo2}] [\Non{\uppercase{parameters}}[\Mod{a}{c}] [\Mod{b}{d}]] [\Non{\uppercase{block}}[\Non{\textsc{return}}[\Non{$-$}[\Mod{a}{c}] [\Mod{b}{d}]]]]]] 8 | \synttree[\Non{\uppercase{root}}[\Non{\uppercase{function}}[\Non{foo}] [\Non{\uppercase{parameters}}[\Non{a}] [\Non{b}]] [\Non{\uppercase{block}}[\Non{\textsc{return}}[\Non{$+$}[\Non{a}] [\Non{b}]]] [\To{19}{\uppercase{function}}[\To{19}{foo2}] [\To{19}{\uppercase{parameters}}[\To{19}{a}] [\To{19}{b}]] [\To{19}{\uppercase{block}}[\To{19}{\textsc{return}}[\To{19}{$-$}[\To{19}{a}] [\To{19}{b}]]]]]]] [\From{19}{\uppercase{function}}[\From{19}{foo2}] [\From{19}{\uppercase{parameters}}[\From{19}{a}] [\From{19}{b}]] [\From{19}{\uppercase{block}}[\From{19}{\textsc{return}}[\From{19}{$-$}[\From{19}{a}] [\From{19}{b}]]]]]]\\ \\ \\ \\ 9 | \synttree[\Non{\Non{\uppercase{root}}}[\Non{\Non{\uppercase{function}}}[\Non{\Non{foo}}] [\Non{\Non{\uppercase{parameters}}}[\Non{\Non{a}}] [\Non{\Non{b}}]] [\Non{\Non{\uppercase{block}}}[\Non{\Non{\textsc{return}}}[\Non{\Non{$+$}}[\Non{\Non{a}}] [\Non{\Non{b}}]]] [\Ins{\To{19}{\uppercase{function}}}[\Ins{\To{19}{foo2}}] [\Ins{\To{19}{\uppercase{parameters}}}[\Ins{\To{19}{a}}] [\Ins{\To{19}{b}}]] [\Ins{\To{19}{\uppercase{block}}}[\Ins{\To{19}{\textsc{return}}}[\Ins{\To{19}{$-$}}[\Ins{\To{19}{a}}] [\Ins{\To{19}{b}}]]]]]]] [\Mod{\Non{\uppercase{function}}}{\From{19}{\uppercase{function}}}[\Mod{\Non{foo2}}{\From{19}{foo2}}] [\Mod{\Non{\uppercase{parameters}}}{\From{19}{\uppercase{parameters}}}[\Mod{\Mod{a}{c}}{\From{19}{a}}] [\Mod{\Mod{b}{d}}{\From{19}{b}}]] [\Mod{\Non{\uppercase{block}}}{\From{19}{\uppercase{block}}}[\Mod{\Non{\textsc{return}}}{\From{19}{\textsc{return}}}[\Mod{\Non{$-$}}{\From{19}{$-$}}[\Mod{\Mod{a}{c}}{\From{19}{a}}] [\Mod{\Mod{b}{d}}{\From{19}{b}}]]]]]]\\ \\ \\ \\ 10 | \synttree[\NoConflict{\Non{\uppercase{root}}}[\NoConflict{\Non{\uppercase{function}}}[\NoConflict{\Non{foo}}] [\NoConflict{\Non{\uppercase{parameters}}}[\NoConflict{\Non{a}}] [\NoConflict{\Non{b}}]] [\NoConflict{\Non{\uppercase{block}}}[\NoConflict{\Non{\textsc{return}}}[\NoConflict{\Non{$+$}}[\NoConflict{\Non{a}}] [\NoConflict{\Non{b}}]]] [\NoConflict{\To{19}{\uppercase{function}}}[\NoConflict{\To{19}{foo2}}] [\NoConflict{\To{19}{\uppercase{parameters}}}[\NoConflict{\To{19}{a}}] [\NoConflict{\To{19}{b}}]] [\NoConflict{\To{19}{\uppercase{block}}}[\NoConflict{\To{19}{\textsc{return}}}[\NoConflict{\To{19}{$-$}}[\NoConflict{\To{19}{a}}] [\NoConflict{\To{19}{b}}]]]]]]] [\NoConflict{\From{19}{\uppercase{function}}}[\NoConflict{\From{19}{foo2}}] [\NoConflict{\From{19}{\uppercase{parameters}}}[\Conflict{\Mod{a}{c}}{\From{19}{a}}] [\Conflict{\Mod{b}{d}}{\From{19}{b}}]] [\NoConflict{\From{19}{\uppercase{block}}}[\NoConflict{\From{19}{\textsc{return}}}[\NoConflict{\From{19}{$-$}}[\Conflict{\Mod{a}{c}}{\From{19}{a}}] [\Conflict{\Mod{b}{d}}{\From{19}{b}}]]]]]] 11 | \end{document} 12 | -------------------------------------------------------------------------------- /demos/old/illustration.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TikhonJelvis/Cow/65832f9826ede4c1380cdc35a42f2b85b1bfebea/demos/old/illustration.pdf -------------------------------------------------------------------------------- /demos/old/move.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cow: Semantic Diffing and Merging 4 | 5 | 6 | 7 | 8 | 9 | function foo (a , b ) {
return a + b
function bar (c , d ) {
return c - d
}
}


function bar (a , b ) {
return a - b
}
10 | 11 | 12 | -------------------------------------------------------------------------------- /demos/old/move.ltx: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | \usepackage{change} 3 | \usepackage[margin=1in, paperwidth=100in, textwidth=100in, paperheight=10in]{geometry} 4 | \begin{document} 5 | \synttree[\Non{\uppercase{root}}[\Non{\uppercase{function}}[\Non{foo}] [\Non{\uppercase{parameters}}[\Non{a}] [\Non{b}]] [\Non{\uppercase{block}}[\Non{\textsc{return}}[\Non{$+$}[\Non{a}] [\Non{b}]]] [\To{19}{\uppercase{function}}[\To{19}{bar}] [\To{19}{\uppercase{parameters}}[\To{19}{c}] [\To{19}{d}]] [\To{19}{\uppercase{block}}[\To{19}{\textsc{return}}[\To{19}{$-$}[\To{19}{c}] [\To{19}{d}]]]]]]] [\From{19}{\uppercase{function}}[\From{19}{bar}] [\From{19}{\uppercase{parameters}}[\From{19}{a}] [\From{19}{b}]] [\From{19}{\uppercase{block}}[\From{19}{\textsc{return}}[\From{19}{$-$}[\From{19}{a}] [\From{19}{b}]]]]]] 6 | \end{document} 7 | -------------------------------------------------------------------------------- /demos/old/move.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TikhonJelvis/Cow/65832f9826ede4c1380cdc35a42f2b85b1bfebea/demos/old/move.pdf -------------------------------------------------------------------------------- /demos/old/moveL.js: -------------------------------------------------------------------------------- 1 | function foo (a, b) { 2 | return a + b; 3 | } 4 | 5 | function bar (a, b) { 6 | return a - b; 7 | } -------------------------------------------------------------------------------- /demos/old/moveOut.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cow: Semantic Diffing and Merging 4 | 5 | 6 | 7 | 8 | 9 | function foo (a , b ) {
return a + b
function bar (c , d ) {
return c - d
}
}


function bar (a , b ) {
return a - b
}
10 | 11 | 12 | -------------------------------------------------------------------------------- /demos/old/moveOut.ltx: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{article} 2 | \usepackage{change} 3 | \usepackage[margin=1in, paperwidth=100in, textwidth=100in, paperheight=10in]{geometry} 4 | \begin{document} 5 | \synttree[\Non{\uppercase{root}}[\Non{\uppercase{function}}[\Non{foo}] [\Non{\uppercase{parameters}}[\Non{a}] [\Non{b}]] [\Non{\uppercase{block}}[\Non{\textsc{return}}[\Non{$+$}[\Non{a}] [\Non{b}]]] [\To{19}{\uppercase{function}}[\To{19}{bar}] [\To{19}{\uppercase{parameters}}[\To{19}{c}] [\To{19}{d}]] [\To{19}{\uppercase{block}}[\To{19}{\textsc{return}}[\To{19}{$-$}[\To{19}{c}] [\To{19}{d}]]]]]]] [\From{19}{\uppercase{function}}[\From{19}{bar}] [\From{19}{\uppercase{parameters}}[\From{19}{a}] [\From{19}{b}]] [\From{19}{\uppercase{block}}[\From{19}{\textsc{return}}[\From{19}{$-$}[\From{19}{a}] [\From{19}{b}]]]]]] 6 | \end{document} 7 | -------------------------------------------------------------------------------- /demos/old/moveR.js: -------------------------------------------------------------------------------- 1 | function foo (a, b) { 2 | return a + b; 3 | 4 | function bar (c, d) { 5 | return c - d; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demos/old/test.js: -------------------------------------------------------------------------------- 1 | var array = [1,2,3,2 + 5, blarg(100)] 2 | 3 | if (typeof abc === "undefined") { 4 | abc = def 5 | } 6 | 7 | var a = 10, 8 | b, 9 | c; 10 | 11 | function blarg(a, b, c) { 12 | var d = a + b * c 13 | 14 | return { 15 | result: a + d * c + a, 16 | method : function () { 17 | return { 18 | a : a, 19 | "b" : b, 20 | "c" : c 21 | } 22 | } 23 | } 24 | } 25 | 26 | for (var i = 0; i < 10; i++) { 27 | console.log(i) 28 | for (var j = 0; j < i; j++) console.log(j) 29 | } 30 | 31 | while (a > 0) { 32 | a -= 2 33 | console.log("The value is: " + a, "Blarg") 34 | a += 10 35 | a -= 11 36 | } 37 | 38 | (function (a, b) { 39 | return a + b; 40 | })(1, 2) 41 | 42 | var obj = { 43 | hello : "world", 44 | "complicated" : 42, 45 | nested : { 46 | a : 10, 47 | b : 11, 48 | empty : {}, 49 | method : function (a, b) { 50 | while (a > b) { 51 | --a; 52 | b++; 53 | console.log(a, b); 54 | if (typeof a == typeof b) console.log("The same!") 55 | } 56 | 57 | return { 58 | a : a, 59 | b : b 60 | }; 61 | } 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /nix/haskell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import ./nixpkgs.nix {} 2 | , compiler ? null 3 | }: 4 | if compiler == null 5 | then pkgs.haskellPackages 6 | else pkgs.haskell.packages.${compiler} 7 | -------------------------------------------------------------------------------- /nix/nixpkgs.nix: -------------------------------------------------------------------------------- 1 | # Pinnable versions of Nixpkgs and all-cabal-hashes. 2 | # 3 | # If you do not specify any arguments, this will default to whatever 4 | # version of Nixpkgs you have in your configured channel. 5 | # 6 | # The nixpkgs-version argument should have two fields: 7 | # * rev: the Git revision to pin 8 | # * sha256: a SHA-256 hash to check the fetched version of Nixpkgs is correct 9 | { nixpkgs-revision ? null 10 | , all-cabal-hashes-rev ? null 11 | }: 12 | let 13 | nixpkgs = 14 | if nixpkgs-revision == null 15 | then 16 | else (import {}).fetchFromGitHub { 17 | owner = "NixOS"; 18 | repo = "nixpkgs"; 19 | rev = nixpkgs-revision.rev; 20 | sha256 = nixpkgs-revision.sha256; 21 | }; 22 | 23 | haskell-overlay = self: super: { 24 | all-cabal-hashes = 25 | if all-cabal-hashes-rev == null 26 | then super.all-cabal-hashes 27 | else builtins.fetchurl { 28 | url = "https://github.com/commercialhaskell/all-cabal-hashes/archive/${all-cabal-hashes-rev}.tar.gz"; 29 | name = "all-cabal-hashes-${all-cabal-hashes-rev}.tar.gz"; 30 | }; 31 | }; 32 | in 33 | import nixpkgs { overlays = [haskell-overlay]; } 34 | -------------------------------------------------------------------------------- /nix/pinned.nix: -------------------------------------------------------------------------------- 1 | # Pinned version of Nixpkgs 2 | # * Nixpkgs pinned to nixpkgs-unstable on 2020-04-03 3 | # * all-cabal-hashes pinned to latest version on 2020-04-03 4 | 5 | {}: 6 | import ./nixpkgs.nix { 7 | nixpkgs-revision = { 8 | rev = "05f0934825c2a0750d4888c4735f9420c906b388"; 9 | sha256 = "1g8c2w0661qn89ajp44znmwfmghbbiygvdzq0rzlvlpdiz28v6gy"; 10 | }; 11 | all-cabal-hashes-rev = "90b24a91103dca4f0df6cb28cecb205a7d7ab650"; 12 | } 13 | -------------------------------------------------------------------------------- /rough-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TikhonJelvis/Cow/65832f9826ede4c1380cdc35a42f2b85b1bfebea/rough-logo.png -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import nix/nixpkgs.nix {} 2 | , compiler ? null 3 | , haskellPackages ? import ./haskell.nix { compiler = compiler; } 4 | , cow ? import ./. { compiler = compiler; } 5 | }: 6 | let 7 | haskellPackages = 8 | if compiler == null 9 | then pkgs.haskellPackages 10 | else pkgs.haskell.packages.${compiler}; 11 | 12 | # Extra development tools to expose inside a Nix shell: 13 | haskellDevelopmentTools = with haskellPackages; 14 | [ cabal-install stylish-haskell ]; 15 | in 16 | pkgs.lib.overrideDerivation cow.env (old: { 17 | nativeBuildInputs = old.nativeBuildInputs ++ haskellDevelopmentTools; 18 | }) 19 | -------------------------------------------------------------------------------- /src/Cow/Diff.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# LANGUAGE PatternGuards #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# LANGUAGE TemplateHaskell #-} 5 | module Cow.Diff where 6 | 7 | import Control.Lens 8 | import Control.Monad.State (evalState) 9 | 10 | import Data.Array (Array, (!)) 11 | import qualified Data.Array as Array 12 | import Data.List (minimumBy) 13 | import Data.Maybe (mapMaybe) 14 | import Data.Ord (comparing) 15 | 16 | import Cow.ParseTree (NodeType (..), Parse', Parse (..)) 17 | import qualified Cow.ParseTree as Tree 18 | 19 | type Weight = Double 20 | 21 | -- | A function which assigns every subtree a weight. 22 | type Weigh leaf = Parse' leaf -> Parse Weight leaf 23 | 24 | -- TODO: Figure out how this function behaves and whether it 25 | -- actually makes any sense! 26 | -- | Calculate the weight of a tree exponentially discounting each 27 | -- additional level. 28 | expDiscount :: (leaf -> Weight) -> Weight -> Weigh leaf 29 | expDiscount ℓ α (Leaf _ leaf) = Leaf (ℓ leaf) leaf 30 | expDiscount ℓ α (Node _ children) = Node (1 + α * sumOf (each . Tree.annots) result) result 31 | where result = expDiscount ℓ α <$> children 32 | 33 | -- TODO: Clarify and verify! 34 | -- | The weight of a node is n% of its possible range. 35 | -- 36 | -- The minimum weight of a node is the largest(?) weight of any one 37 | -- child (otherwise we do not get a valid distance metric). The 38 | -- maximum for the heuristic to be useful is the sum of all its 39 | -- children. 40 | -- 41 | -- This heuristic is equal to minimum + n * (maximum - minimum). 42 | -- 43 | -- Leaves have weight 1 by fiat. 44 | percentage :: (leaf -> Weight) -> Weight -> Weigh leaf 45 | percentage ℓ α (Leaf _ leaf) = Leaf (ℓ leaf) leaf 46 | percentage ℓ α (Node _ children) = Node (min + α * (max - min)) children' 47 | where -- TODO: make children NonEmpty? 48 | Just min = minimumOf (each . Tree.topAnnot) children' 49 | max = sumOf (each . Tree.topAnnot) children' 50 | 51 | children' = percentage ℓ α <$> children 52 | 53 | -- | A few arrays representing the structure of a tree for fast 54 | -- indexing. 55 | data Tables leaf = Tables { 56 | -- | The next non-child node in a preorder traversal. 57 | _jumps :: Array Int Int, 58 | -- | The contents of the node itself, indexed in preorder. 59 | _nodes :: Array Int (NodeType leaf), 60 | -- | The weight of each node—how much removing it should cost. 61 | _weights :: Array Int Weight 62 | } 63 | 64 | makeLenses ''Tables 65 | 66 | -- | Given a tree, creates arrays that specify its structure in a way 67 | -- that's fast to index. 68 | tables :: Weigh leaf -> Parse' leaf -> Tables leaf 69 | tables weigh tree = Tables { _jumps = Tree.preorderTable $ Tree.jumps tree 70 | , _nodes = Tree.nodeTable tree 71 | , _weights = Tree.preorderTable $ weigh tree 72 | } 73 | 74 | -- | Specifies what to do with a preorder index into the tree. If a 75 | -- subtree is added or removed, all of its children are also added or 76 | -- removed. 77 | -- 78 | -- Note how the problem is *symmetric*: removing corresponds to 79 | -- removing a node from the input tree and adding corresponds to 80 | -- removing a node in the output tree. 81 | data Action = Add { _loc :: Int } 82 | | Remove { _loc :: Int } deriving (Show, Eq) 83 | 84 | makeLenses ''Action 85 | 86 | makePrisms ''Action 87 | 88 | -- | An edit script is all the actions needed to go from the input 89 | -- tree to the output tree. 90 | type Script = [Action] 91 | 92 | -- | This data type contains both a numeric distance *and* a (lazy) 93 | -- edit script that led to it. 94 | data Dist = Dist { _dist :: Double 95 | , _edits :: Script 96 | } deriving (Show, Eq) 97 | 98 | makeLenses ''Dist 99 | 100 | type DistTable = Array (Int, Int) Dist 101 | 102 | -- | Produce the dynamic programming array for comparing two parse 103 | -- trees using our modified string edit distance algorithm. 104 | distTable :: Eq a => (Parse' a -> Parse Weight a) -> Parse' a -> Parse' a -> DistTable 105 | distTable weigh input output = ds 106 | where (endIn, endOut) = (Tree.size input, Tree.size output) 107 | bounds = ((0, 0), (endIn, endOut)) 108 | 109 | (tableIn, tableOut) = (tables weigh input, tables weigh output) 110 | 111 | ds = Array.listArray bounds [d i o | (i, o) <- Array.range bounds] 112 | 113 | -- Traverses the parts of the structure at indices [a-b). 114 | between a b = traversed . indices (`elem` [a..b - 1]) 115 | 116 | d i o | i == endIn && o == endOut = Dist 0 [] 117 | d i o | i == endIn = Dist (sumOf (weights . between o endOut) tableOut) [Add o] 118 | d i o | o == endOut = Dist (sumOf (weights . between i endIn) tableIn) [Remove i] 119 | d i o = case (in_, out) of 120 | (TypeLeaf in_, TypeLeaf out) | in_ == out -> 121 | ds ! (i + 1, o + 1) 122 | _ -> 123 | minimumBy (comparing _dist) $ inputs ++ outputs 124 | where (in_, out) = (_nodes tableIn ! i, _nodes tableOut ! o) 125 | inputs | TypeLeaf{} <- in_ = 126 | [ ds ! (i + 1, o) & dist +~ _weights tableIn ! i 127 | & edits %~ (Remove i :) 128 | ] 129 | | TypeNode <- in_ = 130 | [ ds ! (i + 1, o) 131 | , let next = _jumps tableIn ! i in 132 | ds ! (next, o) & dist +~ _weights tableIn ! i 133 | & edits %~ (Remove i :) 134 | ] 135 | outputs | TypeLeaf{} <- out = 136 | [ ds ! (i, o + 1) & dist +~ _weights tableOut ! o 137 | & edits %~ (Add o :) 138 | ] 139 | | TypeNode <- out = 140 | [ ds ! (i, o + 1) 141 | , let next = _jumps tableOut ! o in 142 | ds ! (i, next) & dist +~ _weights tableOut ! o 143 | & edits %~ (Add o :) 144 | ] 145 | 146 | -- | Calculate the distance between two trees using our metric. 147 | diff :: Eq a => Weigh a -> Parse' a -> Parse' a -> Dist 148 | diff weigh left right = distTable weigh left right ! (0, 0) 149 | 150 | -- | Annotates a node in a tree with whether it was added, removed or 151 | -- left unchanged by an edit script. 152 | data Action' = Add' | Remove' | None' deriving (Show, Eq) 153 | 154 | -- | Compares two trees and returns all the added and removed 155 | -- subtrees. 156 | toSubTrees :: Eq a => Weigh a -> Parse' a -> Parse' a -> [Parse Action' a] 157 | toSubTrees weigh input output = mapMaybe go actions 158 | where actions = diff weigh input output ^. edits 159 | 160 | go (Remove i) = Tree.getSubTree i input <&> Tree.annots .~ Remove' 161 | go (Add o) = Tree.getSubTree o output <&> Tree.annots .~ Add' 162 | 163 | annotateTrees :: forall a. Eq a => Weigh a -> Parse' a -> Parse' a -> 164 | (Parse Action' a, Parse Action' a) 165 | annotateTrees weigh input output = (input', output') 166 | where input' = go Remove' removed $ Tree.preorder input 167 | output' = go Add' added $ Tree.preorder output 168 | 169 | go to targets (Leaf n leaf) 170 | | n `elem` targets = Leaf to leaf 171 | | otherwise = Leaf None' leaf 172 | go to targets node@(Node n children) 173 | | n `elem` targets = node & Tree.annots .~ to 174 | | otherwise = Node None' $ go to targets <$> children 175 | 176 | actions = diff weigh input output ^. edits 177 | removed = actions ^.. each . _Remove 178 | added = actions ^.. each . _Add 179 | -------------------------------------------------------------------------------- /src/Cow/Examples.hs: -------------------------------------------------------------------------------- 1 | -- | This modules contains example trees useful for interactively 2 | -- experimenting with the system. Later on, it will also contain 3 | -- example uses of other parts of the framework. 4 | module Cow.Examples where 5 | 6 | import Data.Array (Array) 7 | import qualified Data.Array as Array 8 | import Data.Foldable 9 | import Data.List (intercalate) 10 | import qualified Data.Text as Text 11 | 12 | import Diagrams.Backend.SVG.CmdLine 13 | import Diagrams.Prelude 14 | 15 | import Text.Parsec (parse) 16 | import Text.Printf (printf) 17 | 18 | import Cow.Diff 19 | import qualified Cow.Language.JavaScript as JS 20 | import qualified Cow.Language.JavaScript.Value as JS 21 | import Cow.Language.Token 22 | import Cow.ParseTree 23 | import Cow.ParseTree.Read 24 | import Cow.ParseTree.Viz 25 | 26 | -- | A reasonably small tree that has multiple levels and is 27 | -- relatively unblaanced. 28 | base :: Parse' String 29 | base = show <$> readTree' "[[1][2[3 4]][5[6][7[10[8 9 10 11 12 25]10][13 14 15]]]]" 30 | 31 | -- | The base tree with some leaves deleted. 32 | deleted :: Parse' String 33 | deleted = show <$> readTree' "[[1][2[3 4]][5[6][20[10[8 9 10 12 25 27 29 2]12][13 14 15]]]]" 34 | 35 | base', deleted' :: Parse Action' String 36 | (base', deleted') = annotateTrees (expDiscount (const 1) 0.1) base deleted 37 | 38 | big :: Parse' String 39 | big = show <$> readTree' "[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 29] 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 56] 57 [58 59 60] 61 62]]" 40 | 41 | big' :: Parse' String 42 | big' = show <$> readTree' "[1 [2 [3 4 5 [6 7 8 [9 10 11 [1 2 3 4] 16 17] 18 [64 [20 [22 24] 25 [26 27 29] 30]] 33] 34 [35 36 [37] 42]] 43 45 [46 47 48 49 50] 50 50] 51 [52 53 [54 55 56] 57 62]]" 43 | 44 | -- | Diffs and renders a pair of trees side by side for easy comparison. 45 | renderDiffTrees :: (Eq a, Show a) => (a -> Weight) -> Parse' a -> Parse' a -> Diagram SVG 46 | renderDiffTrees weigh input output = trees & translateY 5 & pad 1.1 & bg white 47 | where trees = renderDiffTree (input' & traversed %~ show) 48 | ||| strutX 10 49 | ||| renderDiffTree (output' & traversed %~ show) 50 | (input', output') = annotateTrees (expDiscount weigh 0.1) input output 51 | 52 | -- | Renders the diff trees for two JavaScript files. 53 | renderFileDiff :: FilePath -> FilePath -> IO (Diagram SVG) 54 | renderFileDiff pathIn pathOut = do in_ <- readFile pathIn 55 | out_ <- readFile pathOut 56 | let treeIn = parse JS.program pathIn $ Text.pack in_ 57 | treeOut = parse JS.program pathIn $ Text.pack out_ 58 | case (,) <$> treeIn <*> treeOut of 59 | Left err -> print err >> return undefined 60 | Right trees -> return $ render trees 61 | 62 | where render = uncurry (renderDiffTrees $ JS.weigh . _value) 63 | 64 | -- ** Helper functions 65 | 66 | -- | Print a distTable in a reasonably readable format. 67 | printTable :: Array (Int, Int) Double -> IO () 68 | printTable table = forM_ (rows $ toList table) $ \ row -> 69 | putStrLn $ intercalate " " $ map (printf "%.2f") row 70 | where ((_, _), (width', _)) = Array.bounds table 71 | 72 | rows [] = [] 73 | rows ls = take (width' + 1) ls : rows (drop (width' + 1) ls) 74 | -------------------------------------------------------------------------------- /src/Cow/Language/JavaScript.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE MonadComprehensions #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# LANGUAGE TemplateHaskell #-} 5 | -- | A basic JavaScript parser that produces a ParseTree with Token 6 | -- values as leaves. The Token values keep track of whitespace 7 | -- consumed during lexing which can be accessed to accurately print 8 | -- the parsed input but does not factor into equality comparisons or 9 | -- other operations. 10 | -- 11 | -- The parser has not been deeply tested so do not expect 100% 12 | -- conformance to actual JavaScript syntax. 13 | module Cow.Language.JavaScript where 14 | 15 | import Control.Lens hiding (noneOf) 16 | import Control.Monad (join) 17 | 18 | import qualified Data.Char as Char 19 | import Data.Maybe (fromMaybe, listToMaybe, maybeToList) 20 | import Data.Monoid 21 | import Data.Text (Text) 22 | import qualified Data.Text as Text 23 | import Data.Text.Lens 24 | 25 | import Text.Parsec hiding (label) 26 | import qualified Text.Parsec.Expr as Expr 27 | import Text.Parsec.Text 28 | 29 | import Numeric (readDec, readHex, readInt, readOct) 30 | 31 | import Cow.Language.JavaScript.Value 32 | import Cow.Language.Token 33 | import Cow.ParseTree 34 | 35 | -- * Parsing 36 | 37 | -- ** Parsing helpers: 38 | 39 | -- | Whitespace characters that are skippable—@space@ but without 40 | -- newlines which need to be tracked to support JavaScript arcane 41 | -- semicolon rules. 42 | skippable :: Parser Char 43 | skippable = satisfy $ \ x -> Char.isSpace x && x /= '\n' 44 | 45 | -- TODO: Should this consume the final 46 | -- newline as well? 47 | -- | Line comments like '// this is a comment'. 48 | lineComment :: Parser Value 49 | lineComment = do literal "//" 50 | contents <- many $ noneOf "\n" 51 | return $ LineComment $ Text.pack contents 52 | 53 | -- | Block comments that can span multiple lines (like '/* comment 54 | -- */'). 55 | -- 56 | -- Note that block comments *do not nest* in JavaScript! 57 | blockComment :: Parser Value 58 | blockComment = do contents <- between (literal "/*") (literal "*/") anything 59 | return $ BlockComment $ Text.pack contents 60 | where anything = many $ noneOf "*" <|> (try $ char '*' <* notFollowedBy (char '/')) 61 | 62 | -- | Either kind of comment. 63 | comment :: Parser Value 64 | comment = try lineComment <|> blockComment 65 | 66 | -- | Converts a parser that doesn't care about whitespace into one 67 | -- that consumes and saves the whitespace *after* the token, as well 68 | -- as handling comments. 69 | tokenize :: Parser Value -> Parser Term 70 | tokenize value = do val <- value 71 | spaces <- Text.pack <$> many skippable 72 | let token = Leaf' $ Token spaces val 73 | optionMaybe (try $ tokenize comment) <&> \case 74 | Just comment -> Node' [token, comment] 75 | Nothing -> token 76 | 77 | -- | Tokenizes a parser to *also* consume newlines—used for tokens 78 | -- that prevent automatic semicolon insertion (ASI). 79 | noASI :: Parser Value -> Parser Term 80 | noASI value = do val <- value 81 | spaces <- Text.pack <$> many space 82 | let token = Leaf' $ Token spaces val 83 | optionMaybe (try $ noASI comment) <&> \case 84 | Just comment -> Node' [token, comment] 85 | Nothing -> token 86 | 87 | -- | Parses delimited lists like '1,2,3,4,' tokenizing all the pieces 88 | -- (both values *and* delimiters). 89 | -- 90 | -- Allows an optional trailing delimiter, as long as there is at least 91 | -- one expression. (So just ',' won't parse!) 92 | tokenizedList :: Parser Term -- ^ The separator (usually a comma). 93 | -> Parser Term -- ^ The expression between separators. 94 | -> Parser [Term] 95 | tokenizedList sep exp = do entries <- join <$> many (try entry) 96 | final <- optionMaybe exp 97 | return $ entries <> maybeToList final 98 | where -- a single 'exp' followed by a separator 99 | entry = (\ a b -> [a, b]) <$> exp <*> sep 100 | 101 | -- | A character that makes up a valid JavaScript identifier. 102 | -- 103 | -- This might not be entirely complete with obscure but valid Unicode 104 | -- characters like zero-width joiners. 105 | idChar :: Parser Char 106 | idChar = alphaNum <|> oneOf "$_" 107 | 108 | -- | Parses a keyword, based on @reserved@ from @Text.Parse.Token@. 109 | keyword :: Text -> Parser Term 110 | keyword keyword = tokenize $ Keyword keyword <$ parseKeyword 111 | where parseKeyword = 112 | do literal keyword 113 | notFollowedBy idChar ("end of " <> Text.unpack keyword) 114 | 115 | -- | Parses a valid name which can start with a letter/$/_ followed by 116 | -- any number of letters/numbers/$/_. 117 | identifier :: Parser Name 118 | identifier = Name <$> (Text.cons <$> startChar <*> rest) "identifier" 119 | where startChar = letter <|> oneOf "$_" 120 | rest = Text.pack <$> many idChar 121 | 122 | -- | Parse a literal string of characters (ie a keyword) into a 'Text' 123 | -- value. 124 | literal :: Text -> Parser Text 125 | literal str = Text.pack <$> (string $ Text.unpack str) 126 | 127 | -- | Parses some punctuation into a token. As far as I know, 128 | -- semicolons should never be inserted after punctuation. 129 | punct :: Value -> Text -> Parser Term 130 | punct value mark = noASI $ value <$ literal mark 131 | 132 | -- | Parses a condition (the '(..)' in 'if (..)', 'while (..)', 133 | -- 'switch (..)' and so on). 134 | condition :: Parser Term -> Parser Term 135 | condition contents = do open <- punct CondStart "(" 136 | stuff <- contents 137 | close <- punct CondEnd ")" 138 | return $ Node' [open, stuff, close] 139 | 140 | -- ** Language Productions 141 | 142 | -- | The end of a line of code: a semicolon or a newline. 143 | terminator :: Parser Term 144 | terminator = noASI $ Semicolon <$ char ';' 145 | <|> LineEnd <$ char '\n' 146 | <|> LineEnd <$ eof 147 | 148 | -- TODO: Figure out how to handle ternary operator properly? 149 | -- | All the legal JavaScript binary operators, ordered by precedence. 150 | operators :: [[Text]] 151 | operators = [["."], ["*", "/", "%"], ["+", "-"], 152 | [">>>", ">>", "<<"], ["<=", "<", ">=", ">", "instanceof", "in"], 153 | ["===", "==", "!==", "!="], ["&"], ["^"], ["|"], ["&&"], ["||"], 154 | ["=", "*=", "/=", "%=", "+=", "-=", ">>=", "<<=", "&=", "|=", "^="]] 155 | 156 | 157 | -- | All the legal JavaScript unary operators. 158 | unaryOperators :: [[Text]] 159 | unaryOperators = [["new"], ["++", "--"], ["+", "-", "~", "!", "typeof", "void", "delete"]] 160 | 161 | 162 | -- | All the reserved words in JavaScript which aren't unary/binary 163 | -- operators. 164 | keywords :: [Text] 165 | keywords = ["break", "catch", "const", "continue", "do", "export", 166 | "for", "function", "if", "import", "label", "case", "default", 167 | "let", "return", "switch", "throw", "try", 168 | "var", "while", "with", "yield"] 169 | 170 | -- | Any valid identifier that is not already a reserved keyword (list 171 | -- in @keywords@). 172 | variable :: Parser Term 173 | variable = tokenize $ identifier >>= notKeyword 174 | where notKeyword (Name name) 175 | | name `elem` keywords = unexpected ("reserved keyword " ++ Text.unpack name) 176 | | otherwise = return . Variable $ Name name 177 | 178 | -- | Labels are identifiers which can be used by 'break' and 179 | -- 'continue' to jump out of nested loops. 180 | label :: Parser Term 181 | label = tokenize $ Label <$> identifier 182 | 183 | -- | Parses JavaScript string literals, including both normal escapes 184 | -- and Unicode (both '\uXXXX' and '\u{XXXXXX}' formats). 185 | stringLiteral :: Parser Term 186 | stringLiteral = tokenize $ String <$> contents 187 | where contents = do start <- oneOf "'\"" 188 | contents <- many $ strChar start 189 | char start "end of string" 190 | return $ Text.pack contents 191 | strChar start = satisfy (`notElem` [start, '\\']) <|> escape "string character" 192 | 193 | escape = char '\\' *> (escapeChar <|> unicodeEscape) 194 | escapeChar = escaped <$> oneOf "0btnvfr'\"\\\n" "escape character" 195 | escaped c = let Just res = lookup c escapes in res 196 | escapes = [('b', '\b'), ('t', '\t'), ('n', '\n'), ('v', '\v'), ('0', '\0'), 197 | ('f', '\f'), ('r', '\r'), ('\'', '\''), ('\\', '\\')] 198 | 199 | -- parses \uXXXX and \u{X} … \u{XXXXXX} as Unicode characters 200 | unicodeEscape = char 'u' *> (fourDigit <|> other) "unicode escape sequence" 201 | where toChar = Char.chr . read . ("0x" ++) 202 | fourDigit = toChar <$> count 4 hexDigit 203 | other = toChar <$> between (char '{') (char '}') (many1 hexDigit) 204 | 205 | 206 | 207 | -- TODO: Handle malformed literals like 0923, 0b1233, -0x1.2 208 | -- | Parses JavaScript numeric literals including other bases ('0x10') 209 | -- and floating point numbers with exponents ('1.5e10'). 210 | number :: Parser Term 211 | number = tokenize $ Num <$> (try intLit <|> floatLit) 212 | where -- an integer literal *not* in decimal (ie 0x10 but not 10) 213 | intLit = do sign <- (negate <$ try (char '-') <|> id <$ optional (char '+')) 214 | format <- parseFormat 215 | num <- many1 digit 216 | return . sign . toFormat format $ num 217 | parseFormat = choice $ try . string <$> ["0x", "0X", "0b", "0B", "0"] 218 | toFormat format = fromMaybe read $ toReader <$> lookup format formats 219 | toReader f = fst . head . f 220 | formats = [("0", readOct), ("0x", readHex), ("0X", readHex), 221 | ("0b", readBinary), ("0B", readBinary)] 222 | readBinary = readInt 2 (`elem` ['0','1']) (read . (:[])) 223 | 224 | -- a float literal: 105, 10.5, 10e5, 10.1e5… etc 225 | floatLit = do sign <- (negate <$ try (char '-') <|> id <$ optional (char '+')) 226 | num <- try $ many1 digit 227 | dec <- optionMaybe $ char '.' *> many1 digit 228 | exp <- optionMaybe $ oneOf "eE" *> many1 digit 229 | let dec' = fromMaybe "" $ ("." ++) <$> dec 230 | exp' = fromMaybe "" $ ("e" ++) <$> exp 231 | return . sign . read $ num <> dec' <> exp' 232 | 233 | -- | The index part of an expression indexing into an array: ie the 234 | -- '[f(x)]' from 'g(y)[f(x)]'. 235 | indexExpression :: Parser Term 236 | indexExpression = do open <- punct IndexStart "[" 237 | index <- expression 238 | close <- punct IndexEnd "]" 239 | return $ Node' [open, index, close] 240 | 241 | -- | The arguments of a function call expression: ie the '(1,2,3)' of 242 | -- 'f(1,2,3)'. 243 | callExpression :: Parser Term 244 | callExpression = do open <- punct CallStart "(" 245 | args <- tokenizedList (punct CallSep ",") expression 246 | close <- punct CallEnd ")" 247 | return . Node' $ (open : args) ++ [close] 248 | 249 | -- | An expression with a mix of function calls and array indexing 250 | -- like 'f(1)[2](3)'. 251 | -- 252 | -- Separated out to deal with left-recursion. 253 | callsAndIndices :: Parser Term 254 | callsAndIndices = do base <- simpleAtom 255 | calls <- many1 (callExpression <|> indexExpression) 256 | return . Node' $ base : calls 257 | 258 | -- | Parses array literals like '[1,f(x), g(x)[3] + z]'. 259 | arrayLiteral :: Parser Term 260 | arrayLiteral = do open <- punct ArrayStart "[" 261 | items <- tokenizedList (punct ArraySep ",") expression 262 | close <- punct ArrayEnd "]" 263 | return . Node' $ (open : items) ++ [close] 264 | 265 | -- | Parses JavaScript object literals like '{a : 10, b : 11}'. 266 | objectLiteral :: Parser Term 267 | objectLiteral = do open <- punct ObjStart "{" 268 | pairs <- tokenizedList (punct ObjSep ",") pair 269 | close <- punct ObjEnd "}" 270 | return . Node' $ (open : pairs) ++ [close] 271 | where -- a key value pair in an object, like 'a : 10' 272 | pair = do field <- try stringLiteral <|> variable 273 | sep <- punct ObjColon ":" 274 | val <- expression 275 | return $ Node' [field, sep, val] 276 | 277 | -- | A block is a series of statements surrounded by '{' and '}'. The 278 | -- braces are not optional! (The optional case for conditionals and 279 | -- loops is handled separately.) 280 | block :: Parser Term 281 | block = do open <- punct BlockStart "{" 282 | body <- many statement 283 | close <- punct BlockEnd "}" 284 | return . Node' $ (open : body) ++ [close] 285 | 286 | -- | A function expression. The function can *optionally* have a name: 287 | -- this covers both 'function () {}' and 'function foo() {}'. 288 | functionLiteral :: Parser Term 289 | functionLiteral = do func <- keyword "function" 290 | name <- optionMaybe variable 291 | open <- punct ArgStart "(" 292 | args <- tokenizedList (punct ArgSep ",") variable 293 | close <- punct ArgEnd ")" 294 | body <- block 295 | let argList = Node' $ (open : args) ++ [close] 296 | return $ Node' $ func : (maybeToList name ++ [argList, body]) 297 | 298 | -- | A self-contained piece of a JavaScript expression. 299 | -- 300 | -- This is split from @atom@ to avoid left-recursion. 301 | simpleAtom :: Parser Term 302 | simpleAtom = stringLiteral 303 | <|> try functionLiteral 304 | <|> try arrayLiteral 305 | <|> try objectLiteral 306 | <|> try number 307 | <|> try variable 308 | <|> parens 309 | where parens = do open <- punct ParenStart "(" 310 | exp <- expression 311 | close <- punct ParenEnd ")" 312 | return $ Node' [open, exp, close] 313 | 314 | -- | Any JavaScript expression without any operators. 315 | atom :: Parser Term 316 | atom = try callsAndIndices <|> simpleAtom 317 | 318 | -- | Any JavaScript expression except for the ternary operator ('a ? b : c'). 319 | expression' :: Parser Term 320 | expression' = Expr.buildExpressionParser table atom "expression" 321 | where table = [post "++", post "--"] : 322 | (map (map pref) unaryOperators ++ 323 | map (map bin) operators) 324 | 325 | op op left right = Node' [left, op, right] 326 | unOp op arg = Node' [op, arg] 327 | postOp op arg = Node' [arg, op] 328 | 329 | bin opStr = Expr.Infix (op <$> operator opStr) Expr.AssocLeft 330 | pref opStr = Expr.Prefix (unOp <$> operator opStr) 331 | post opStr = Expr.Postfix (postOp <$> asiOp opStr) 332 | 333 | operator = try . noASI . fmap Operator . opLiteral 334 | asiOp = try . tokenize . fmap Operator . opLiteral 335 | 336 | opLiteral str = literal str <* notFollowedBy (oneOf ".*/%+-><=!&^|") 337 | 338 | 339 | -- | Parses the ternary operator ('a ? b : c'). 340 | ternary :: Parser Term 341 | ternary = do cond <- expression' 342 | start <- noASI $ Operator <$> literal "?" 343 | then_ <- expression 344 | end <- noASI $ Operator <$> literal ":" 345 | else_ <- expression 346 | return $ Node' [cond, start, then_, end, else_] 347 | 348 | -- | Any JavaScript expression. 349 | expression :: Parser Term 350 | expression = try ternary <|> expression' 351 | 352 | -- | Declaring a variable with 'var', 'let' or 'const'. Currently does 353 | -- not support destructuring assignment. 354 | declaration :: Parser Term 355 | declaration = do start <- keyword "var" <|> keyword "let" <|> keyword "const" 356 | defs <- tokenizedList (punct DeclSep ",") varDef 357 | return $ Node' $ start : defs 358 | where varDef = do name <- variable 359 | val <- optionMaybe setting 360 | return $ Node' $ name : maybeToList val 361 | setting = do op <- tokenize $ Operator <$> literal "=" 362 | val <- expression 363 | return $ Node' [op, val] 364 | 365 | 366 | -- | An 'if' statement with an optional 'else' clause afterwards. 367 | ifElse :: Parser Term 368 | ifElse = do if' <- keyword "if" 369 | cond <- condition expression 370 | body <- statement 371 | else' <- maybeToList <$> optionMaybe elseClause 372 | return . Node' $ [if', cond, body] ++ else' 373 | where elseClause = do else' <- keyword "else" 374 | body <- statement 375 | return $ Node' [else', body] 376 | 377 | -- | A 'try { ... }' statement with any number of 'catch' clauses and 378 | -- an optional 'finally' clause. 379 | -- 380 | -- This will parse if there are no 'catch' *or* 'finally' clauses even 381 | -- though that isn't strictly valid JavaScript. 382 | tryCatch :: Parser Term 383 | tryCatch = do try <- keyword "try" 384 | body <- block 385 | catches <- many catch 386 | finally <- maybeToList <$> optionMaybe finally 387 | return . Node' $ [try, body] ++ catches ++ finally 388 | where catch = do start <- keyword "catch" 389 | open <- punct CatchStart "(" 390 | eName <- variable 391 | close <- punct CatchEnd ")" 392 | body <- block 393 | return $ Node' [start, open, eName, close, body] 394 | finally = do start <- keyword "finally" 395 | body <- block 396 | return $ Node' [start, body] 397 | 398 | -- | Parses a switch statement ('switch (foo) { case 'a': ... default: ... }'). 399 | switch :: Parser Term 400 | switch = do start <- keyword "switch" 401 | cond <- condition expression 402 | body <- caseBlock 403 | return $ Node' [start, cond, body] 404 | where caseBlock = do open <- punct BlockStart "{" 405 | body <- many (case_ <|> default_) 406 | close <- punct BlockEnd "}" 407 | return . Node' $ (open : body) ++ [close] 408 | case_ = do start <- keyword "case" 409 | val <- expression 410 | end <- punct CaseColon ":" 411 | body <- many (try statement) 412 | return . Node' $ [start, val, end] ++ body 413 | default_ = do start <- keyword "default" 414 | end <- punct CaseColon ":" 415 | body <- many statement 416 | return . Node' $ [start, end] ++ body 417 | 418 | -- | Simple 'while' loops. 419 | while :: Parser Term 420 | while = do start <- keyword "while" 421 | cond <- condition expression 422 | body <- statement 423 | return $ Node' [start, cond, body] 424 | 425 | -- | do-while loops. Do people even use these any more? 426 | doWhile :: Parser Term 427 | doWhile = do start <- keyword "do" 428 | body <- block 429 | while <- keyword "while" 430 | cond <- condition expression 431 | return $ Node' [start, body, while, cond] 432 | 433 | -- | For loops of the form 'for (;;)'. 434 | for :: Parser Term 435 | for = do start <- keyword "for" 436 | cond <- condition forCondition 437 | body <- statement 438 | return $ Node' [start, cond, body] 439 | where forCondition = do init <- expression <|> declaration 440 | sep1 <- punct ForSep ";" 441 | cond <- expression 442 | sep2 <- punct ForSep ";" 443 | end <- expression 444 | return $ Node' [init, sep1, cond, sep2, end] 445 | 446 | -- | A 'for (x in ls)' or 'for (x of ls)' loop. 447 | for' :: Text -> Parser Term 448 | for' sep = do start <- keyword "for" 449 | cond <- condition for'Condition 450 | body <- statement 451 | return $ Node' [start, cond, body] 452 | where for'Condition = do var <- optionMaybe $ keyword "var" 453 | name <- variable 454 | in_ <- keyword sep 455 | ls <- expression 456 | let var' = maybeToList var 457 | return $ Node' $ var' ++ [name, in_, ls] 458 | 459 | -- | A 'with' statement which you probably *shouldn't* use any 460 | -- more—but I'm sure people do. 461 | with :: Parser Term 462 | with = do start <- keyword "with" 463 | cond <- condition expression 464 | body <- statement 465 | return $ Node' [start, cond, body] 466 | 467 | -- | A JavaScript statement, including its terminator (ie explicit or 468 | -- implicit semicolon). 469 | statement :: Parser Term 470 | statement = do contents <- statement' 471 | -- terminator is optional to handle end of block (ie '{ 472 | -- 1 + 2 }' on one line) 473 | end <- optionMaybe terminator 474 | return . Node' $ contents : maybeToList end 475 | where statement' = try block 476 | <|> try expression 477 | <|> try declaration 478 | <|> keyworded "return" expression 479 | <|> keyworded "throw" expression 480 | <|> keyworded "break" label 481 | <|> keyworded "continue" label 482 | <|> keyword "debugger" 483 | <|> try labelDecl 484 | <|> try ifElse 485 | <|> try tryCatch 486 | <|> try switch 487 | <|> try while 488 | <|> try doWhile 489 | <|> try for 490 | <|> try (for' "in") 491 | <|> try (for' "of") 492 | <|> try with 493 | <|> tokenize comment 494 | keyworded word val = try $ do start <- keyword word 495 | end <- optionMaybe val 496 | return . Node' $ start : maybeToList end 497 | labelDecl = do l <- label 498 | c <- punct LabelStart ":" 499 | return $ Node' [l, c] 500 | 501 | -- | Parses a whole JavaScript program. 502 | program :: Parser Term 503 | program = many space *> (Node' <$> many statement) <* eof 504 | -------------------------------------------------------------------------------- /src/Cow/Language/JavaScript/Value.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE TemplateHaskell #-} 3 | module Cow.Language.JavaScript.Value where 4 | 5 | import Control.Lens 6 | 7 | import Data.Text (Text) 8 | import qualified Data.Text as Text 9 | import Data.Text.Lens 10 | 11 | import Text.Printf (printf) 12 | 13 | import Cow.Language.Token 14 | import Cow.ParseTree 15 | 16 | 17 | -- | The name of an identifier or object key, kept abstract to 18 | -- differentiate from strings. (Not 100% sure about this design 19 | -- decision.) 20 | newtype Name = Name { _name :: Text } deriving Eq 21 | 22 | makeLenses ''Name 23 | 24 | instance Show Name where show n = n ^. name . _Text 25 | 26 | -- | The kinds of tokens we ultimately parse. 27 | -- 28 | -- Note that this is not pure lexing data: differentiating between 29 | -- some of these requires actual parsing. 30 | data Value = Variable Name 31 | | Num Double 32 | | String Text 33 | | Regex Text -- TODO: better type for regex? 34 | 35 | | Label Name 36 | | LabelStart -- the colon after a label name 37 | 38 | -- TODO: Exauhstive types for operators and keywords? 39 | | Keyword Text 40 | | Operator Text 41 | 42 | | Semicolon 43 | | LineEnd 44 | 45 | -- expressions in parentheses like (1 + 2), but *not* 46 | -- argument lists 47 | | ParenStart 48 | | ParenEnd 49 | 50 | -- array literals ([1,f(2),"foo",[1]]) 51 | | ArrayStart 52 | | ArrayEnd 53 | | ArraySep 54 | 55 | -- array indexing (the [-] part of a[1]) 56 | | IndexStart -- [ 57 | | IndexEnd -- ] 58 | 59 | -- arguments to called functions (the (1 + 2, 3) part of f(1 + 2, 3). 60 | | CallStart 61 | | CallEnd 62 | | CallSep 63 | 64 | -- argument lists (function definitions but *not* calls) 65 | | ArgStart 66 | | ArgEnd 67 | | ArgSep 68 | 69 | | DeclSep -- the ',' in 'var x = 10, y;' 70 | 71 | -- object literals 72 | | ObjStart 73 | | ObjEnd 74 | | ObjKey Name 75 | | ObjSep 76 | | ObjColon -- TODO: better name? 77 | 78 | -- loop/if/switch conditions (ie the (..) in if(..) and while(..) 79 | | CondStart 80 | | CondEnd 81 | 82 | | ForSep -- the ; in a for(;;) loop. 83 | 84 | | CaseColon -- the ':' in 'case "foo":' 85 | 86 | -- the parens after a catch (ie (..) from 'catch (e) { .. }') 87 | | CatchStart 88 | | CatchEnd 89 | 90 | -- blocks: function bodies and control flow 91 | | BlockStart 92 | | BlockEnd 93 | 94 | -- comments 95 | | LineComment Text -- // .. 96 | | BlockComment Text -- /* .. */ 97 | deriving (Eq) 98 | 99 | makePrisms ''Value 100 | 101 | instance Show Value where 102 | show = \case 103 | Variable (Name n) -> Text.unpack n 104 | Num n -> printf "%.2f" n 105 | String text -> show $ Text.unpack text 106 | Regex regex -> printf "/%s/" $ Text.unpack regex 107 | Label (Name l) -> printf "%s:" $ Text.unpack l 108 | LabelStart -> ":" 109 | Keyword word -> printf "<%s>" $ Text.unpack word 110 | Operator op -> Text.unpack op 111 | Semicolon -> ";" 112 | LineEnd -> "<;>" 113 | ParenStart -> "(" 114 | ParenEnd -> ")" 115 | ArrayStart -> "[" 116 | ArrayEnd -> "]" 117 | ArraySep -> "," 118 | IndexStart -> "[" 119 | IndexEnd -> "]" 120 | CallStart -> "(" 121 | CallEnd -> ")" 122 | CallSep -> "," 123 | ArgStart -> "(" 124 | ArgEnd -> ")" 125 | ArgSep -> "," 126 | ObjStart -> "{" 127 | ObjEnd -> "}" 128 | ObjKey (Name key) -> printf "%s :" $ Text.unpack key 129 | ObjSep -> "," 130 | ObjColon -> ":" 131 | CondStart -> "(" 132 | CondEnd -> ")" 133 | ForSep -> ";" 134 | CaseColon -> ":" 135 | CatchStart -> "(" 136 | CatchEnd -> ")" 137 | BlockStart -> "{" 138 | BlockEnd -> "}" 139 | LineComment text -> printf "//%s" $ Text.unpack text 140 | BlockComment text -> printf "/*%s*/" $ Text.unpack text 141 | 142 | -- | Assigns a weight to each token based on how important it 143 | -- is. Tokens that carry a lot of user-specific information are the 144 | -- most important with a weight of '1', followed by keywords and 145 | -- operators and finally punctuation at a low weight of '0.25'. The 146 | -- numbers are arbitrary, but the idea is to *roughly* capture what is 147 | -- and isn't important to the meaning of a program. 148 | weigh :: Value -> Double 149 | weigh value | important value = 1 150 | | medium value = 0.5 151 | | otherwise = 0.2 152 | where important = \case 153 | Variable{} -> True 154 | Num{} -> True 155 | String{} -> True 156 | Regex{} -> True 157 | Label{} -> True 158 | ObjKey{} -> True 159 | LineComment{} -> True 160 | BlockComment{} -> True 161 | _ -> False 162 | medium = \case 163 | Keyword{} -> True 164 | Operator{} -> True 165 | _ -> False 166 | 167 | -- | A term is any valid fragment of JavaScript. This handles 168 | -- whitespace (thanks to 'Token') and can be put directly into a 169 | -- larger tree (ie a larger 'Term'). 170 | type Term = Parse' (Token Value) 171 | -------------------------------------------------------------------------------- /src/Cow/Language/Token.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | -- | Tokens that I work with preserve the whitespace that follows them 3 | -- so that we can reconstruct the input text from the tree, but ignore 4 | -- the whitespace for logical operations (comparing tokens for 5 | -- equality and so on). 6 | module Cow.Language.Token where 7 | 8 | import Control.Lens 9 | 10 | import Data.Text (Text) 11 | 12 | import Cow.ParseTree 13 | 14 | 15 | -- | A single token that preserves the whitespace consumed in parsing 16 | -- it. 17 | data Token a = Token 18 | { _whitespace :: Text 19 | -- ^ The whitespace *before* this token. 20 | , _value :: a 21 | -- ^ The semantic role of this token (ie list 22 | -- separator, object key) with any relevant content 23 | -- (ie the identifier itself). 24 | } 25 | 26 | makeLenses ''Token 27 | 28 | instance Eq a => Eq (Token a) where 29 | t1 == t2 = t1 ^. value == t2 ^. value 30 | 31 | instance Show a => Show (Token a) where show = show . _value 32 | 33 | -------------------------------------------------------------------------------- /src/Cow/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Control.Applicative ((*>), (<$>), (<*), (<*>)) 4 | 5 | import System.Environment (getArgs) 6 | 7 | import Text.ParserCombinators.Parsec 8 | 9 | main = putStrLn "Cow" 10 | -------------------------------------------------------------------------------- /src/Cow/ParseTree.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | {-# LANGUAGE PatternSynonyms #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | {-# LANGUAGE DeriveFunctor #-} 6 | {-# LANGUAGE LambdaCase #-} 7 | {-# LANGUAGE MonadComprehensions #-} 8 | module Cow.ParseTree where 9 | 10 | import Control.Monad.State (evalState, get, modify) 11 | 12 | import Data.Array (Array) 13 | import qualified Data.Array as Array 14 | import Data.Foldable 15 | import Data.Maybe (listToMaybe, mapMaybe) 16 | import Data.Traversable 17 | import qualified Data.Tree as Rose 18 | 19 | import Control.Lens 20 | 21 | -- | A parse tree that carries the parsed tokens in its leaves, along 22 | -- with arbitrary annotations at each node. 23 | data Parse annot leaf = Node annot [Parse annot leaf] 24 | | Leaf annot leaf 25 | deriving (Show, Eq, Functor) 26 | 27 | -- | A parse tree where we don't care about the annotations at each 28 | -- node, only the leaves. 29 | type Parse' leaf = Parse () leaf 30 | 31 | pattern Node' children = Node () children 32 | 33 | pattern Leaf' leaf = Leaf () leaf 34 | 35 | -- TODO: This is probably woefully inefficient! 36 | instance Foldable (Parse annot) where 37 | foldr f z = foldr f z . toListOf leaves 38 | 39 | instance Traversable (Parse annot) where 40 | traverse = traverseOf leaves 41 | 42 | -- | A lens which accesses the top-level annotation of a tree, but not 43 | -- the annotations of the children of the node (if any). 44 | topAnnot :: Lens (Parse annot leaf) (Parse annot leaf) annot annot 45 | topAnnot f (Leaf annot leaf) = (\ a -> Leaf a leaf) <$> f annot 46 | topAnnot f (Node annot children) = (\ a -> Node a children) <$> f annot 47 | 48 | -- | A traversal that targets every annotation in the tree in preorder. 49 | annots :: Traversal (Parse annot leaf) (Parse annot' leaf) annot annot' 50 | annots f (Leaf annot leaf) = (\ a -> Leaf a leaf) <$> f annot 51 | annots f (Node annot children) = Node <$> f annot <*> traverse (annots f) children 52 | 53 | -- | A traversal that targets every leaf in order. 54 | leaves :: Traversal (Parse annot leaf) (Parse annot leaf') leaf leaf' 55 | leaves f (Leaf annot leaf) = Leaf annot <$> f leaf 56 | leaves f (Node annot children) = Node annot <$> traverse (leaves f) children 57 | 58 | -- | The size of the tree, counting *all* of the nodes, not just the 59 | -- leaves. 60 | size :: Parse annot leaf -> Int 61 | size (Node _ children) = 1 + sum (map size children) 62 | size (Leaf _ _) = 1 63 | 64 | -- | Turns the parse tree into a rose tree, discarding all the 65 | -- annotations. The internal nodes are labeled with 'Nothing' and the 66 | -- leaves with 'Just leaf'. 67 | toRoseTree :: Parse annot leaf -> Rose.Tree (Maybe leaf) 68 | toRoseTree (Node _ children) = Rose.Node Nothing $ map toRoseTree children 69 | toRoseTree (Leaf _ leaf) = Rose.Node (Just leaf) [] 70 | 71 | -- | Turns the parse tree into a rose tree, keeping the annotations as 72 | -- well as the values at the leaves. 73 | toRoseTreeAnnot :: Parse annot leaf -> Rose.Tree (annot, Maybe leaf) 74 | toRoseTreeAnnot (Node annot children) = 75 | Rose.Node (annot, Nothing) $ map toRoseTreeAnnot children 76 | toRoseTreeAnnot (Leaf annot leaf) = Rose.Node (annot, Just leaf) [] 77 | 78 | -- | A preorder traversal of a tree, annotating each node with its 79 | -- position in the traversal, starting with 0. 80 | preorder :: Parse annot leaf -> Parse Int leaf 81 | preorder = iset (indexing annots) id 82 | 83 | -- | A preorder traversal, keeping the old annotations rather than 84 | -- replacing them. 85 | preorder' :: Parse annot leaf -> Parse (Int, annot) leaf 86 | preorder' = iover (indexing annots) (,) 87 | 88 | -- | Gets the subtree rooted at the given index in a preorder 89 | -- traversal. 90 | getSubTree :: Int -> Parse annot leaf -> Maybe (Parse annot leaf) 91 | getSubTree n = go . preorder' 92 | where go (Leaf (i, annot) leaf) = [Leaf annot leaf | i == n] 93 | go (Node (i, annot) children) 94 | | i == n = Just $ Node annot $ children <&> annots %~ snd 95 | | otherwise = listToMaybe $ mapMaybe go children 96 | 97 | -- TODO: Is this actually right? 98 | -- | Compiles the next non-child node for each node in a preorder 99 | -- traversal. (The next non-child node may not exist, but it will 100 | -- still be compiled.) 101 | jumps :: Parse annot leaf -> Parse Int leaf 102 | jumps tree = go $ preorder tree 103 | where go node@(Node n children) = Node (n + size node) (map go children) 104 | go (Leaf n leaf) = Leaf (n + 1) leaf 105 | 106 | -- | Compiles an array of each annotation in the tree in a preorder 107 | -- traversal. 108 | preorderTable :: Parse annot leaf -> Array Int annot 109 | preorderTable tree = Array.listArray (0, size tree - 1) $ tree ^.. annots 110 | 111 | data NodeType leaf = TypeNode | TypeLeaf leaf deriving (Show, Eq, Functor) 112 | 113 | -- | Takes a preorder traversal of a tree and turns it into an array 114 | -- which specifies whether each node was a node or a leaf (with its 115 | -- contents). 116 | nodeTable :: Parse' leaf -> Array Int (NodeType leaf) 117 | nodeTable tree = Array.listArray (0, size tree - 1) $ go tree 118 | where go (Node _ children) = TypeNode : (children >>= go) 119 | go (Leaf _ leaf) = [TypeLeaf leaf] 120 | -------------------------------------------------------------------------------- /src/Cow/ParseTree/Read.hs: -------------------------------------------------------------------------------- 1 | module Cow.ParseTree.Read where 2 | 3 | import Cow.ParseTree 4 | 5 | import Text.Parsec 6 | import Text.Parsec.String 7 | 8 | -- | A helper function that reads trees of ints from a string. Each 9 | -- node is denoted with square brackets which contain its children, 10 | -- separated by spaces. Leaves are just numbers. 11 | -- 12 | -- For example, [[1][2[3 4]]] parses to: 13 | -- 14 | -- o 15 | -- / \ 16 | -- o o 17 | -- / / \ 18 | -- 1 2 o 19 | -- / \ 20 | -- 3 4 21 | -- 22 | readTree :: String -> Either ParseError (Parse' Int) 23 | readTree str = parse tree "" str 24 | where tree = Leaf () <$> leaf 25 | <|> Node () <$> node 26 | 27 | leaf = read <$> many1 digit <* spaces 28 | node = between (char '[' *> spaces) (char ']' *> spaces) (many tree) 29 | 30 | -- | An unsafe version of readTree where a bad parse results in a 31 | -- runtime error. 32 | readTree' :: String -> Parse' Int 33 | readTree' str = case readTree str of 34 | Left err -> error $ show err 35 | Right res -> res 36 | -------------------------------------------------------------------------------- /src/Cow/ParseTree/Viz.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE LambdaCase #-} 4 | {-# LANGUAGE NoMonomorphismRestriction #-} 5 | {-# LANGUAGE ScopedTypeVariables #-} 6 | {-# LANGUAGE ViewPatterns #-} 7 | module Cow.ParseTree.Viz where 8 | 9 | import Control.Monad (foldM) 10 | import Control.Monad.State (evalState, get, put) 11 | 12 | import Data.Functor ((<$>)) 13 | import Data.Maybe (fromMaybe, listToMaybe) 14 | import qualified Data.Tree as Rose 15 | 16 | import Diagrams.Backend.SVG.CmdLine 17 | import Diagrams.Prelude 18 | import Diagrams.TwoD.Layout.Tree 19 | 20 | import Cow.Diff 21 | import Cow.ParseTree 22 | import Cow.ParseTree.Read 23 | 24 | -- | Render a tree with functions to control edge colors (based on the 25 | -- the nodes they're connecting) and node shapes. 26 | renderAnnotTree edgeColor renderNode parseTree = renderTree' renderNode renderEdge tree 27 | where tree = clusterLayoutTree parseTree 28 | renderEdge (annot1, p1) (annot2, p2) = curve p1 p2 # lc (edgeColor annot1 annot2) # lw 5 29 | 30 | -- Connects nodes with a Bézier curve to help avoid 31 | -- overlapping edges and make connections easy to follow 32 | curve start@(unp2 -> (x1, y1)) end@(unp2 -> (x2, y2)) = 33 | fromLocSegments $ [ bezier3 (r2 (0, 0)) 34 | (r2 (dx, dy/4)) 35 | (r2 (dx, dy)) 36 | ] `at` p2 (x1, y1) 37 | where (dx, dy) = (x2 - x1, y2 - y1) 38 | 39 | -- | Renders trees annotated with add/remove/unchanged actions. If an 40 | -- internal is added or removed, all its children and edges are 41 | -- colored. 42 | renderDiffTree = renderAnnotTree edgeColor $ \case 43 | (action, Nothing) -> circle 0.5 # fc (colorOf action) # lw 5 44 | (action, Just str) -> (strutY 0.5 === text str) <> rect (w str) 1 # fc (bgOf action) 45 | # lc (bgOf action) 46 | where colorOf Add' = green 47 | colorOf Remove' = red 48 | colorOf None' = black 49 | 50 | bgOf action = blend 0.6 (colorOf action) white 51 | 52 | w label = fromIntegral (length label) * charWidth + nodeSpacing - 1 53 | 54 | edgeColor (action, _) _ = colorOf action 55 | 56 | -- | Renders a parse tree ignoring its annotations. 57 | renderParseTree = renderAnnotTree (\ a b -> black) renderNode 58 | where -- Render leaves as white circles and nodes as black dots 59 | renderNode (_, Nothing) = circle 0.2 # fc black 60 | renderNode (_, Just str) = text str <> rect (w str) 1 # fc white 61 | 62 | w label = fromIntegral (length label) * charWidth + nodeSpacing - 1 63 | 64 | -- TODO: abstract over this! 65 | nodeSpacing, charWidth :: (Floating n, Ord n) => n 66 | nodeSpacing = 4 67 | charWidth = 0.25 68 | 69 | -- | Calculates the y coordinate of each node in a tree, counting up 70 | -- from the leaves which are all at y = 0. 71 | nodeY :: (Floating n, Ord n) => Parse annot leaf -> Parse (n, annot) leaf 72 | nodeY (Leaf annot leaf) = Leaf (0, annot) leaf 73 | nodeY (Node annot children) = Node (maximum depths + nodeSpacing, annot) children' 74 | where children' = nodeY <$> children 75 | depths = children' ^.. each . topAnnot . _1 76 | 77 | -- | Calculates the x coordinate for each node in a tree. The leaves 78 | -- are all evenly arranged at the bottom of the tree, with each 79 | -- internal node centered *relative to its leaves* (not necessarily 80 | -- its direct sub-nodes). 81 | nodeX :: (Floating n, Ord n) => Parse annot String -> Parse (n, annot) String 82 | nodeX tree = offsetAndCenter $ nodeWidth tree 83 | where -- Calculate the x offset of every node in a tree to center 84 | -- the root node and move the subtrees relative to each other 85 | -- as needed. 86 | offsetAndCenter (Leaf (width, annot) leaf) = Leaf (width / 2, annot) leaf 87 | offsetAndCenter (Node (width, annot) children) = 88 | Node (width / 2, annot) $ reverse offsetChildren 89 | where offsetChildren = evalState (foldM go [] $ offsetAndCenter <$> children) 0 90 | go children' node = do 91 | offset <- get 92 | let node' = node & annots . _1 +~ offset 93 | put $ offset + 2 * (node ^. topAnnot . _1) 94 | return $ node' : children' 95 | 96 | -- Calculate how many leaves are under each node (any number 97 | -- of levels down). Leaves have a width that depends on the 98 | -- length of their label, with 'nodeSpacing' padding. 99 | nodeWidth (Leaf annot leaf) = Leaf (nodeSpacing + textWidth leaf, annot) leaf 100 | where textWidth str = fromIntegral (length str) * charWidth 101 | nodeWidth (Node annot children) = Node (sum widths, annot) children' 102 | where children' = nodeWidth <$> children 103 | widths = children' ^.. each . topAnnot . _1 104 | 105 | -- | Lays a whole tree out with everything aligned from the leaves up. 106 | clusterLayoutTree :: (Floating n, Ord n) => Parse annot String -> 107 | Rose.Tree ((annot, Maybe String), P2 n) 108 | clusterLayoutTree = fmap go . toRoseTreeAnnot . (annots %~ toP2) . nodeX . nodeY 109 | where go ((pos, annot), leaf) = ((annot, leaf), pos) 110 | toP2 (width, (depth, annot)) = (p2 (width, depth), annot) 111 | 112 | exampleTree :: Parse () String 113 | exampleTree = show <$> readTree' "[[1][2[3 4]][5[6][7[[8 9 10 11 12]][13 14 15]]]]" 114 | 115 | exampleTreeDiagram = renderParseTree exampleTree # translateY 10 # pad 1.1 # bg white 116 | --------------------------------------------------------------------------------