Explorar el Código

initial commit for GitHub

Mike Hamburg hace 16 años
commit
2e423d9517
Se han modificado 100 ficheros con 10920 adiciones y 0 borrados
  1. 32 0
      README/COPYRIGHT
  2. 36 0
      README/INSTALL
  3. 30 0
      README/bsd.txt
  4. 339 0
      README/gpl-2.0.txt
  5. 674 0
      README/gpl-3.0.txt
  6. 16 0
      browserTest/browserTest.html
  7. 128 0
      browserTest/browserUtil.js
  8. 44 0
      browserTest/rhinoUtil.js
  9. 59 0
      browserTest/test.css
  10. BIN
      compress/compiler.jar
  11. 15 0
      compress/compress_with_closure.sh
  12. 13 0
      compress/compress_with_yui.sh
  13. 8 0
      compress/dewindowize.pl
  14. 22 0
      compress/digitize.pl
  15. 32 0
      compress/opacify.pl
  16. 69 0
      compress/remove_constants.pl
  17. BIN
      compress/yuicompressor-2.4.2.jar
  18. 208 0
      core/aes.js
  19. 166 0
      core/bitArray.js
  20. 185 0
      core/ccm.js
  21. 56 0
      core/codecBase64.js
  22. 37 0
      core/codecBytes.js
  23. 30 0
      core/codecHex.js
  24. 39 0
      core/codecString.js
  25. 271 0
      core/convenience.js
  26. 40 0
      core/hmac.js
  27. 171 0
      core/ocb2.js
  28. 54 0
      core/pbkdf2.js
  29. 368 0
      core/random.js
  30. 216 0
      core/sha256.js
  31. 60 0
      core/sjcl.js
  32. BIN
      demo/alpha-arrow.png
  33. 179 0
      demo/example.css
  34. 153 0
      demo/example.js
  35. 137 0
      demo/form.js
  36. 208 0
      demo/index.html
  37. 183 0
      jsdoc_toolkit-2.3.3-beta/README.txt
  38. 33 0
      jsdoc_toolkit-2.3.3-beta/app/frame.js
  39. 102 0
      jsdoc_toolkit-2.3.3-beta/app/frame/Chain.js
  40. 144 0
      jsdoc_toolkit-2.3.3-beta/app/frame/Dumper.js
  41. 84 0
      jsdoc_toolkit-2.3.3-beta/app/frame/Hash.js
  42. 171 0
      jsdoc_toolkit-2.3.3-beta/app/frame/Link.js
  43. 10 0
      jsdoc_toolkit-2.3.3-beta/app/frame/Namespace.js
  44. 134 0
      jsdoc_toolkit-2.3.3-beta/app/frame/Opt.js
  45. 26 0
      jsdoc_toolkit-2.3.3-beta/app/frame/Reflection.js
  46. 93 0
      jsdoc_toolkit-2.3.3-beta/app/frame/String.js
  47. 129 0
      jsdoc_toolkit-2.3.3-beta/app/frame/Testrun.js
  48. 26 0
      jsdoc_toolkit-2.3.3-beta/app/handlers/FOODOC.js
  49. 26 0
      jsdoc_toolkit-2.3.3-beta/app/handlers/XMLDOC.js
  50. 159 0
      jsdoc_toolkit-2.3.3-beta/app/handlers/XMLDOC/DomReader.js
  51. 16 0
      jsdoc_toolkit-2.3.3-beta/app/handlers/XMLDOC/XMLDoc.js
  52. 292 0
      jsdoc_toolkit-2.3.3-beta/app/handlers/XMLDOC/XMLParse.js
  53. 104 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC.js
  54. 204 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/DocComment.js
  55. 294 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/DocTag.js
  56. 126 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/JsDoc.js
  57. 109 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/JsPlate.js
  58. 144 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Lang.js
  59. 144 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Parser.js
  60. 33 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/PluginManager.js
  61. 644 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Symbol.js
  62. 242 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/SymbolSet.js
  63. 41 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/TextStream.js
  64. 18 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Token.js
  65. 332 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/TokenReader.js
  66. 133 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/TokenStream.js
  67. 32 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Util.js
  68. 507 0
      jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Walker.js
  69. 111 0
      jsdoc_toolkit-2.3.3-beta/app/main.js
  70. 20 0
      jsdoc_toolkit-2.3.3-beta/app/plugins/commentSrcJson.js
  71. 16 0
      jsdoc_toolkit-2.3.3-beta/app/plugins/frameworkPrototype.js
  72. 10 0
      jsdoc_toolkit-2.3.3-beta/app/plugins/functionCall.js
  73. 62 0
      jsdoc_toolkit-2.3.3-beta/app/plugins/publishSrcHilite.js
  74. 10 0
      jsdoc_toolkit-2.3.3-beta/app/plugins/symbolLink.js
  75. 31 0
      jsdoc_toolkit-2.3.3-beta/app/plugins/tagParamConfig.js
  76. 43 0
      jsdoc_toolkit-2.3.3-beta/app/plugins/tagSynonyms.js
  77. 348 0
      jsdoc_toolkit-2.3.3-beta/app/run.js
  78. 144 0
      jsdoc_toolkit-2.3.3-beta/app/t/TestDoc.js
  79. 13 0
      jsdoc_toolkit-2.3.3-beta/app/t/runner.js
  80. 342 0
      jsdoc_toolkit-2.3.3-beta/app/test.js
  81. 24 0
      jsdoc_toolkit-2.3.3-beta/app/test/addon.js
  82. 14 0
      jsdoc_toolkit-2.3.3-beta/app/test/anon_inner.js
  83. 31 0
      jsdoc_toolkit-2.3.3-beta/app/test/augments.js
  84. 26 0
      jsdoc_toolkit-2.3.3-beta/app/test/augments2.js
  85. 46 0
      jsdoc_toolkit-2.3.3-beta/app/test/borrows.js
  86. 23 0
      jsdoc_toolkit-2.3.3-beta/app/test/borrows2.js
  87. 22 0
      jsdoc_toolkit-2.3.3-beta/app/test/config.js
  88. 18 0
      jsdoc_toolkit-2.3.3-beta/app/test/constructs.js
  89. 10 0
      jsdoc_toolkit-2.3.3-beta/app/test/encoding.js
  90. 12 0
      jsdoc_toolkit-2.3.3-beta/app/test/encoding_other.js
  91. 54 0
      jsdoc_toolkit-2.3.3-beta/app/test/event.js
  92. 14 0
      jsdoc_toolkit-2.3.3-beta/app/test/exports.js
  93. 39 0
      jsdoc_toolkit-2.3.3-beta/app/test/functions_anon.js
  94. 33 0
      jsdoc_toolkit-2.3.3-beta/app/test/functions_nested.js
  95. 13 0
      jsdoc_toolkit-2.3.3-beta/app/test/global.js
  96. 25 0
      jsdoc_toolkit-2.3.3-beta/app/test/globals.js
  97. 10 0
      jsdoc_toolkit-2.3.3-beta/app/test/ignore.js
  98. 16 0
      jsdoc_toolkit-2.3.3-beta/app/test/inner.js
  99. 477 0
      jsdoc_toolkit-2.3.3-beta/app/test/jsdoc_test.js
  100. 33 0
      jsdoc_toolkit-2.3.3-beta/app/test/lend.js

+ 32 - 0
README/COPYRIGHT

@@ -0,0 +1,32 @@
+SJCL used to be in the public domain.  Now it's:
+
+Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh, Stanford University.
+
+This is for liability reasons.  (Speaking of which, SJCL comes with NO
+WARRANTY WHATSOEVER, express or implied, to the limit of applicable
+law.)
+
+SJCL is dual-licensed under the GNU GPL version 2.0 or higher, and a
+2-clause BSD license.  You may use SJCL under the terms of either of
+these licenses.  For your convenience, the GPL versions 2.0 and 3.0
+and the 2-clause BSD license are included here.  Additionally, you may
+serve "crunched" copies of sjcl (i.e. those with comments removed,
+and other transformations to reduce code size) without any copyright
+notice.
+
+SJCL includes JsDoc toolkit, YUI compressor, Closure compressor,
+JSLint and the CodeView template in its build system.  These programs'
+copyrights are owned by other people.  They are distributed here under
+the MPL, MIT, BSD, Apache and JSLint licenses.  Codeview is "free for
+download" but has no license attached; it is Copyright 2010 Wouter Bos.
+
+The BSD license is (almost?) strictly more permissive, but the
+additionally licensing under the GPL allows us to use OCB 2.0 code
+royalty-free (at least, if OCB 2.0's creator Phil Rogaway has anything
+to say about it).  Note that if you redistribute SJCL under a license
+other than the GPL, you or your users may need to pay patent licensing
+fees for OCB 2.0.
+
+There may be patents which apply to SJCL other than Phil Rogaway's OCB
+patents.  We suggest that you consult legal counsel before using SJCL
+in a commercial project.

+ 36 - 0
README/INSTALL

@@ -0,0 +1,36 @@
+SJCL comes with a file sjcl.js pre-built.  This default build includes
+all the modules except for sjcl.codec.bytes (because the demo site doesn't
+use it).  All you need to do to install is copy this file to your web
+server and start using it.
+
+SJCL is divided into modules implementing various cryptographic and
+convenience functions.  If you don't need them all for your application,
+you can reconfigure SJCL for a smaller code size.  To do this, you can
+run
+
+./configure --without-all --with-aes --with-sha256 ...
+
+Then type
+
+make
+
+to rebuild sjcl.js.  This will also create a few intermediate files
+core*.js; you can delete these automatically by typing
+
+make sjcl.js tidy
+
+instead.  You will need make, perl, bash and java to rebuild SJCL.
+
+Some of the modules depend on other modules; configure should handle this
+automatically unless you tell it --without-FOO --with-BAR, where BAR
+depends on FOO.  If you do this, configure will yell at you.
+
+SJCL is compressed by stripping comments, shortening variable names, etc.
+You can also pass a --compress argument to configure to change the
+compressor.  By default SJCL uses some perl/sh scripts and Google's
+Closure compressor.
+
+If you reconfigure SJCL, it is recommended that you run the included test
+suite by typing "make test".  If this prints "FAIL" or segfaults, SJCL
+doesn't work; please file a bug.
+

+ 30 - 0
README/bsd.txt

@@ -0,0 +1,30 @@
+Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   1. Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation
+are those of the authors and should not be interpreted as representing
+official policies, either expressed or implied, of the authors.

+ 339 - 0
README/gpl-2.0.txt

@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 674 - 0
README/gpl-3.0.txt

@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ 16 - 0
browserTest/browserTest.html

@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+        
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>SJCL browser test</title>
+  <link rel="stylesheet" type="text/css" href="test.css"/>
+  <script type="text/javascript" src="browserUtil.js"></script>
+  <script type="text/javascript" src="../test/run_tests_browser.js"></script>
+</head>
+<body onload="testCores(['sjcl.js'])">
+  <h1>SJCL browser test</h1>
+  <div id="status">Waiting for tests to begin...</div>
+  <div id="print"></div>
+</body>
+</html>

+ 128 - 0
browserTest/browserUtil.js

@@ -0,0 +1,128 @@
+browserUtil = {};
+
+browserUtil.isRhino = (typeof(window) === 'undefined');
+
+/**
+ * Pause (for the graphics to update and the script timer to clear), then run the
+ * specified action.
+ */
+browserUtil.pauseAndThen = function (cb) {
+  cb && window.setTimeout(cb, 1);
+};
+
+/**
+ * Iterate using continuation-passing style.
+ */
+browserUtil.cpsIterate = function (f, start, end, pause, callback) {
+  var pat = pause ? browserUtil.pauseAndThen : function (cb) { cb && cb(); };
+  function go() {
+    var called = false;
+    if (start >= end) {
+      pat(callback);
+    } else {
+      pat(function () { f(start, function () {
+        if (!called) { called = true; start++; go(); }
+      }); });
+    }
+  }
+  go (start);
+};
+
+/**
+ * Map a function over an array using continuation-passing style.
+ */
+browserUtil.cpsMap = function (map, list, pause, callback) {
+  browserUtil.cpsIterate(function (i, cb) { map(list[i], i, list.length, cb); },
+                         0, list.length, pause, callback);
+}
+
+/** Cache for remotely loaded scripts. */
+browserUtil.scriptCache = {}
+
+/** Load several scripts, then call back */
+browserUtil.loadScripts = function(scriptNames, cbSuccess, cbError) {
+  var head = document.getElementsByTagName('head')[0];
+  browserUtil.cpsMap(function (script, i, n, cb) {
+    var scriptE = document.createElement('script'), xhr, loaded = false;
+    
+    browserUtil.status("Loading script " + script);
+    
+    if (window.location.protocol === "file:") {
+      /* Can't make an AJAX request for files.
+       * But, we know the load time will be short, so timeout-based error
+       * detection is fine.
+       */
+      scriptE.onload = function () {
+        loaded = true;
+        cb();
+      };
+      scriptE.onerror = function(err) {
+        cbError && cbError(script, err, cb);
+      };
+      script.onreadystatechange = function() {
+        if (this.readyState == 'complete' || this.readyState == 'loaded') {
+          loaded = true;
+          cb();
+        }
+      };
+      scriptE.type = 'text/javascript';
+      scriptE.src = script+"?"+(new Date().valueOf());
+      window.setTimeout(function () {
+        loaded || cbError && cbError(script, "timeout expired", cb);
+      }, 100);
+      head.appendChild(scriptE);
+    } else if (browserUtil.scriptCache[script] !== undefined) {
+      try {
+        scriptE.appendChild(document.createTextNode(browserUtil.scriptCache[script]));
+      } catch (e) {
+        scriptE.text = browserUtil.scriptCache[script];
+      }
+      head.appendChild(scriptE);
+      cb();
+    } else {
+      var xhr = new XMLHttpRequest();
+      xhr.onreadystatechange = function() {
+        if (xhr.readyState == 4) {
+          if (xhr.status == 200) {
+            browserUtil.scriptCache[script] = xhr.responseText;
+            try {
+              scriptE.appendChild(document.createTextNode(xhr.responseText));
+            } catch (e) {
+              scriptE.text = xhr.responseText;
+            }
+            head.appendChild(scriptE);
+            cb();
+          } else {
+            cbError && cbError(script, xhr.status, cb);
+          }
+        }
+      }
+      xhr.open("GET", script+"?"+(new Date().valueOf()), true);
+      xhr.send();
+    }
+  }, scriptNames, false, cbSuccess);
+};
+
+/** Write a message to the console */
+browserUtil.write = function(type, message) {
+  var d1 = document.getElementById("print"), d2 = document.createElement("div"), d3 = document.createElement("div");
+  d3.className = type;
+  d3.appendChild(document.createTextNode(message));
+  d2.appendChild(d3);
+  d1.appendChild(d2);
+  return { update: function (type2, message2) {
+    var d4 = document.createElement("div");
+    d4.className = type2 + " also";
+    d4.appendChild(document.createTextNode(message2));
+    d2.insertBefore(d4, d3);
+  }};
+};
+
+/** Write a newline.  Does nothing in the browser. */
+browserUtil.writeNewline = function () { };
+
+/** Write a message to the status line */
+browserUtil.status = function(message) {
+  var d1 = document.getElementById("status");
+  d1.replaceChild(document.createTextNode(message), d1.firstChild);
+};

+ 44 - 0
browserTest/rhinoUtil.js

@@ -0,0 +1,44 @@
+browserUtil = {
+  isRhino: true,
+  
+  pauseAndThen: function (cb) { cb(); },
+  
+  cpsIterate: function (f, start, end, pause, callback) {
+    function go() {
+      var called = false;
+      if (start >= end) {
+        callback && callback();
+      } else {
+        f(start, function () {
+          if (!called) { called = true; start++; go(); }
+        });
+      }
+    }
+    go (start);
+  },
+  
+  cpsMap: function (map, list, pause, callback) {
+    browserUtil.cpsIterate(function (i, cb) { map(list[i], i, list.length, cb); },
+                           0, list.length, pause, callback);
+  },
+
+  loadScripts: function(scriptNames, callback) {
+    for (i=0; i<scriptNames.length; i++) {
+      load(scriptNames[i]);
+      callback && callback();
+    }
+  },
+  
+  write: function(type, message) {
+    print(message);
+    return { update: function (type2, message2) {
+      if (type2 === 'pass') { print("  + " + message2); }
+      else if (type2 === 'unimplemented') { print("  ? " + message2); }
+      else { print("  - " + message2); }
+    }};
+  },
+  
+  writeNewline: function () { print(""); },
+  
+  status: function(message) {}
+};

+ 59 - 0
browserTest/test.css

@@ -0,0 +1,59 @@
+* {
+  margin: 0px;
+  padding: 0px;
+  font-family: Arial, Helvetica, FreeSans, sans;
+}
+
+#print {
+  position: relative;
+  width: 40em;
+  margin: 0px auto;
+  padding: 5px;
+}
+
+#print div {
+  position: relative;
+}
+
+.pass { color: #0A0; }
+.fail { color: #A00; }
+.unimplemented { color: #F80; }
+
+.begin {
+  text-align: center;
+  padding-bottom: 2px;
+  border-bottom: 1px solid #aaa;
+  margin: 0px auto 2px auto;
+}
+
+.all {
+  text-align: center;
+  font-weight: bold;
+}
+
+*+* > .begin, *+* > .all {
+  margin-top: 1em;
+}
+
+.also {
+  float: right;
+  width: 17em;
+  text-align: right;
+}
+
+h1 {
+  text-align: center;
+  background: #8A0000;
+  padding: 5px;
+  color: white;
+}
+
+#status {
+  padding: 3px 10px 3px 5px;
+  background: #d5c490;
+  color: #444;
+  font-size: 0.8em;
+  margin-bottom: 1em;
+  height: 1.3em;
+  vertical-align: middle;
+}

BIN
compress/compiler.jar


+ 15 - 0
compress/compress_with_closure.sh

@@ -0,0 +1,15 @@
+#!/bin/bash
+
+DIR=`dirname $0`
+
+$DIR/remove_constants.pl $1 | $DIR/opacify.pl > ._tmpRC.js
+
+echo -n '"use strict";'
+java -jar $DIR/compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS \
+     --js ._tmpRC.js \
+     | $DIR/digitize.pl \
+     | $DIR/dewindowize.pl
+     
+
+rm -f ._tmpRC.js
+

+ 13 - 0
compress/compress_with_yui.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Compress $1 with YUI Compressor 2.4.2, returning the compressed script on stdout
+
+DIR=`dirname $0`
+
+$DIR/remove_constants.pl $1 > ._tmpRC.js
+
+java -jar $DIR/yuicompressor-2.4.2.jar ._tmpRC.js \
+  | $DIR/digitize.pl
+
+rm -f ._tmpRC.js
+

+ 8 - 0
compress/dewindowize.pl

@@ -0,0 +1,8 @@
+#!/usr/bin/env perl
+
+while (<>) {
+  s/window\.sjcl\s*=/var sjcl=/g;
+  s/window\.sjcl/sjcl/g;
+  print;
+}
+

+ 22 - 0
compress/digitize.pl

@@ -0,0 +1,22 @@
+#!/usr/bin/env perl
+
+# Convert numbers to hex, when doing so is likely to increase compressibility.
+# This actually makes the script slightly longer, but generally makes it compress
+# to something shorter.
+#
+# Here we're targeting constants like 0xFF, 0xFFFF0000, 0x10101, 0x100000000, etc.
+
+sub digitize {
+    my $number = shift;
+    if ($number >= 256) {
+	my $nn = sprintf("%x", $number);
+	if ($nn =~ /^[01f]+$/i) { return "0x$nn"; }
+    }
+    return $number;
+}
+
+while (<>) {
+    s/([^a-zA-Z0-9_])(\d+)/$1 . digitize $2/eg;
+    print;
+}
+

+ 32 - 0
compress/opacify.pl

@@ -0,0 +1,32 @@
+#!/usr/bin/env perl
+
+# This script is a hack.
+#
+# Opacify all non-private names by turning them into strings.
+# That way, the Google compressor won't rename them.
+#
+# The script ignores properties whose names begin with _, because they
+# are believed to be private.
+#
+# XXX TODO FIXME: this messes with strings, so it screws up exceptions.
+
+my $script = join '', <>;
+
+# remove comments
+$script =~ s=/\*([^\*]|\*+[^\/])*\*/==g;
+$script =~ s=//.*==g;
+
+# stringify property names
+$script =~ s=\.([a-zA-Z0-9][_a-zA-Z0-9]*)=['$1']=g;
+
+# stringify sjcl
+$script =~ s=(?:var\s+)?sjcl(\.|\s*\=)=window['sjcl']$1=g;
+
+# stringify object notation
+$script =~ s=([\{,]\s*)([a-zA-Z0-9][_a-zA-Z0-9]*):=$1'$2':=g;
+
+# Export sjcl.  This is a bit of a hack, and might get replaced later.
+print $script;
+
+# not necessary with windowization.
+# print "window\['sjcl'\] = sjcl;\n";

+ 69 - 0
compress/remove_constants.pl

@@ -0,0 +1,69 @@
+#!/usr/bin/env perl
+
+# This script is a hack.  It identifies things which it believes to be
+# constant, then replaces them throughout the code.
+#
+# Constants are identified as properties declared in object notation
+# with values consisting only of capital letters and underscores.  If
+# the first character is an underscore, the constant is private, and
+# can be removed entirely.
+#
+# The script dies if any two constants have the same property name but
+# different values.
+my $script = join '', <>;
+
+# remove comments
+$script =~ s=/\*([^\*]|\*+[^\/])*\*/==g;
+$script =~ s=//.*==g;
+
+sub preserve {
+  my $stuff = shift;
+  $stuff =~ s/,//;
+  return $stuff;
+}
+
+my %constants = ();
+
+sub add_constant {
+  my ($name, $value) = @_;
+  if (defined $constants{$name} && $constants{$name} ne $value) {
+    print STDERR "variant constant $name = $value";
+    die;
+  } else {
+    $constants{$name} = $value;
+    #print STDERR "constant: $name = $value\n";
+  }
+}
+
+# find private constants
+while ($script =~
+  s/([,\{]) \s*              # indicator that this is part of an object
+    (_[A-Z0-9_]+) \s* : \s*  # all-caps variable name beginning with _
+    (\d+|0x[0-9A-Fa-f]+) \s* # numeric value
+    ([,\}])                  # next part of object
+   /preserve "$1$4"/ex) {
+   add_constant $2, $3;
+}
+
+my $script2 = '';
+
+# find public constants
+while ($script =~
+  s/^(.*?)                   # beginning of script
+    ([,\{]) \s*              # indicator that this is part of an object
+    ([A-Z0-9_]+) \s* : \s*   # all-caps variable name
+    (\d+|0x[0-9A-Fa-f]+) \s* # numeric value
+    ([,\}])                  # next part of object([,\{]) \s*
+   /$5/esx) {
+   $script2 .= "$1$2$3:$4";
+   add_constant $3, $4;
+}
+
+$script = "$script2$script";
+
+foreach (keys %constants) {
+  my $value = $constants{$_};
+  $script =~ s/(?:[a-zA-Z0-9_]+\.)+$_(?=[^a-zA-Z0-9_])/$value/g;
+}
+
+print $script;

BIN
compress/yuicompressor-2.4.2.jar


+ 208 - 0
core/aes.js

@@ -0,0 +1,208 @@
+/** @fileOverview Low-level AES implementation.
+ *
+ * This file contains a low-level implementation of AES, optimized for
+ * size and for efficiency on several browsers.  It is based on
+ * OpenSSL's aes_core.c, a public-domain implementation by Vincent
+ * Rijmen, Antoon Bosselaers and Paulo Barreto.
+ *
+ * An older version of this implementation is available in the public
+ * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
+ * Stanford University 2008-2010 and BSD-licensed for liability
+ * reasons.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * Schedule out an AES key for both encryption and decryption.  This
+ * is a low-level class.  Use a cipher mode to do bulk encryption.
+ *
+ * @constructor
+ * @param {Array} key The key as an array of 4, 6 or 8 words.
+ *
+ * @class Advanced Encryption Standard (low-level interface)
+ */
+sjcl.cipher.aes = function (key) {
+  if (!this._tables[0][0][0]) {
+    this._precompute();
+  }
+  
+  var i, j, tmp,
+    encKey, decKey,
+    sbox = this._tables[0][4], decTable = this._tables[1],
+    keyLen = key.length, rcon = 1;
+  
+  if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
+    throw new sjcl.exception.invalid("invalid aes key size");
+  }
+  
+  this._key = [encKey = key.slice(0), decKey = []];
+  
+  // schedule encryption keys
+  for (i = keyLen; i < 4 * keyLen + 28; i++) {
+    tmp = encKey[i-1];
+    
+    // apply sbox
+    if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) {
+      tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255];
+      
+      // shift rows and add rcon
+      if (i%keyLen === 0) {
+        tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24;
+        rcon = rcon<<1 ^ (rcon>>7)*283;
+      }
+    }
+    
+    encKey[i] = encKey[i-keyLen] ^ tmp;
+  }
+  
+  // schedule decryption keys
+  for (j = 0; i; j++, i--) {
+    tmp = encKey[j&3 ? i : i - 4];
+    if (i<=4 || j<4) {
+      decKey[j] = tmp;
+    } else {
+      decKey[j] = decTable[0][sbox[tmp>>>24      ]] ^
+                  decTable[1][sbox[tmp>>16  & 255]] ^
+                  decTable[2][sbox[tmp>>8   & 255]] ^
+                  decTable[3][sbox[tmp      & 255]];
+    }
+  }
+};
+
+sjcl.cipher.aes.prototype = {
+  // public
+  /* Something like this might appear here eventually
+  name: "AES",
+  blockSize: 4,
+  keySizes: [4,6,8],
+  */
+  
+  /**
+   * Encrypt an array of 4 big-endian words.
+   * @param {Array} data The plaintext.
+   * @return {Array} The ciphertext.
+   */
+  encrypt:function (data) { return this._crypt(data,0); },
+  
+  /**
+   * Decrypt an array of 4 big-endian words.
+   * @param {Array} data The ciphertext.
+   * @return {Array} The plaintext.
+   */
+  decrypt:function (data) { return this._crypt(data,1); },
+  
+  /**
+   * The expanded S-box and inverse S-box tables.  These will be computed
+   * on the client so that we don't have to send them down the wire.
+   *
+   * There are two tables, _tables[0] is for encryption and
+   * _tables[1] is for decryption.
+   *
+   * The first 4 sub-tables are the expanded S-box with MixColumns.  The
+   * last (_tables[01][4]) is the S-box itself.
+   *
+   * @private
+   */
+  _tables: [[[],[],[],[],[]],[[],[],[],[],[]]],
+
+  /**
+   * Expand the S-box tables.
+   *
+   * @private
+   */
+  _precompute: function () {
+   var encTable = this._tables[0], decTable = this._tables[1],
+       sbox = encTable[4], sboxInv = decTable[4],
+       i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec;
+
+    // Compute double and third tables
+   for (i = 0; i < 256; i++) {
+     th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i;
+   }
+   
+   for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
+     // Compute sbox
+     s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4;
+     s = s>>8 ^ s&255 ^ 99;
+     sbox[x] = s;
+     sboxInv[s] = x;
+     
+     // Compute MixColumns
+     x8 = d[x4 = d[x2 = d[x]]];
+     tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100;
+     tEnc = d[s]*0x101 ^ s*0x1010100;
+     
+     for (i = 0; i < 4; i++) {
+       encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8;
+       decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8;
+     }
+   }
+   
+   // Compactify.  Considerable speedup on Firefox.
+   for (i = 0; i < 5; i++) {
+     encTable[i] = encTable[i].slice(0);
+     decTable[i] = decTable[i].slice(0);
+   }
+  },
+  
+  /**
+   * Encryption and decryption core.
+   * @param {Array} input Four words to be encrypted or decrypted.
+   * @param dir The direction, 0 for encrypt and 1 for decrypt.
+   * @return {Array} The four encrypted or decrypted words.
+   * @private
+   */
+  _crypt:function (input, dir) {
+    if (input.length !== 4) {
+      throw new sjcl.exception.invalid("invalid aes block size");
+    }
+    
+    var key = this._key[dir],
+        // state variables a,b,c,d are loaded with pre-whitened data
+        a = input[0]           ^ key[0],
+        b = input[dir ? 3 : 1] ^ key[1],
+        c = input[2]           ^ key[2],
+        d = input[dir ? 1 : 3] ^ key[3],
+        a2, b2, c2,
+        
+        nInnerRounds = key.length/4 - 2,
+        i,
+        kIndex = 4,
+        out = [0,0,0,0],
+        table = this._tables[dir],
+        
+        // load up the tables
+        t0    = table[0],
+        t1    = table[1],
+        t2    = table[2],
+        t3    = table[3],
+        sbox  = table[4];
+ 
+    // Inner rounds.  Cribbed from OpenSSL.
+    for (i = 0; i < nInnerRounds; i++) {
+      a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex];
+      b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1];
+      c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2];
+      d  = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3];
+      kIndex += 4;
+      a=a2; b=b2; c=c2;
+    }
+        
+    // Last round.
+    for (i = 0; i < 4; i++) {
+      out[dir ? 3&-i : i] =
+        sbox[a>>>24      ]<<24 ^ 
+        sbox[b>>16  & 255]<<16 ^
+        sbox[c>>8   & 255]<<8  ^
+        sbox[d      & 255]     ^
+        key[kIndex++];
+      a2=a; a=b; b=c; c=d; d=a2;
+    }
+    
+    return out;
+  }
+};
+

+ 166 - 0
core/bitArray.js

@@ -0,0 +1,166 @@
+/** @fileOverview Arrays of bits, encoded as arrays of Numbers.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** @namespace Arrays of bits, encoded as arrays of Numbers.
+ *
+ * @description
+ * <p>
+ * These objects are the currency accepted by SJCL's crypto functions.
+ * </p>
+ *
+ * <p>
+ * Most of our crypto primitives operate on arrays of 4-byte words internally,
+ * but many of them can take arguments that are not a multiple of 4 bytes.
+ * This library encodes arrays of bits (whose size need not be a multiple of 8
+ * bits) as arrays of 32-bit words.  The bits are packed, big-endian, into an
+ * array of words, 32 bits at a time.  Since the words are double-precision
+ * floating point numbers, they fit some extra data.  We use this (in a private,
+ * possibly-changing manner) to encode the number of bits actually  present
+ * in the last word of the array.
+ * </p>
+ *
+ * <p>
+ * Because bitwise ops clear this out-of-band data, these arrays can be passed
+ * to ciphers like AES which want arrays of words.
+ * </p>
+ */
+sjcl.bitArray = {
+  /**
+   * Array slices in units of bits.
+   * @param {bitArray a} The array to slice.
+   * @param {Number} bstart The offset to the start of the slice, in bits.
+   * @param {Number} bend The offset to the end of the slice, in bits.  If this is undefined,
+   * slice until the end of the array.
+   * @return {bitArray} The requested slice.
+   */
+  bitSlice: function (a, bstart, bend) {
+    a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1);
+    return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart);
+  },
+
+  /**
+   * Concatenate two bit arrays.
+   * @param {bitArray} a1 The first array.
+   * @param {bitArray} a2 The second array.
+   * @return {bitArray} The concatenation of a1 and a2.
+   */
+  concat: function (a1, a2) {
+    if (a1.length === 0 || a2.length === 0) {
+      return a1.concat(a2);
+    }
+    
+    var out, i, last = a1[a1.length-1], shift = sjcl.bitArray.getPartial(last);
+    if (shift === 32) {
+      return a1.concat(a2);
+    } else {
+      return sjcl.bitArray._shiftRight(a2, shift, last|0, a1.slice(0,a1.length-1));
+    }
+  },
+
+  /**
+   * Find the length of an array of bits.
+   * @param {bitArray} a The array.
+   * @return {Number} The length of a, in bits.
+   */
+  bitLength: function (a) {
+    var l = a.length, x;
+    if (l === 0) { return 0; }
+    x = a[l - 1];
+    return (l-1) * 32 + sjcl.bitArray.getPartial(x);
+  },
+
+  /**
+   * Truncate an array.
+   * @param {bitArray} a The array.
+   * @param {Number} len The length to truncate to, in bits.
+   * @return {bitArray} A new array, truncated to len bits.
+   */
+  clamp: function (a, len) {
+    if (a.length * 32 < len) { return a; }
+    a = a.slice(0, Math.ceil(len / 32));
+    var l = a.length;
+    len = len & 31;
+    if (l > 0 && len) {
+      a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1);
+    }
+    return a;
+  },
+
+  /**
+   * Make a partial word for a bit array.
+   * @param {Number} len The number of bits in the word.
+   * @param {Number} x The bits.
+   * @param {Number} [0] _end Pass 1 if x has already been shifted to the high side.
+   * @return {Number} The partial word.
+   */
+  partial: function (len, x, _end) {
+    if (len === 32) { return x; }
+    return (_end ? x|0 : x << (32-len)) + len * 0x10000000000;
+  },
+
+  /**
+   * Get the number of bits used by a partial word.
+   * @param {Number} x The partial word.
+   * @return {Number} The number of bits used by the partial word.
+   */
+  getPartial: function (x) {
+    return Math.round(x/0x10000000000) || 32;
+  },
+
+  /**
+   * Compare two arrays for equality in a predictable amount of time.
+   * @param {bitArray} a The first array.
+   * @param {bitArray} b The second array.
+   * @return {boolean} true if a == b; false otherwise.
+   */
+  equal: function (a, b) {
+    if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) {
+      return false;
+    }
+    var x = 0, i;
+    for (i=0; i<a.length; i++) {
+      x |= a[i]^b[i];
+    }
+    return (x === 0);
+  },
+
+  /** Shift an array right.
+   * @param {bitArray} a The array to shift.
+   * @param {Number} shift The number of bits to shift.
+   * @param {Number} [carry=0] A byte to carry in
+   * @param {bitArray} [out=[]] An array to prepend to the output.
+   * @private
+   */
+  _shiftRight: function (a, shift, carry, out) {
+    var i, last2=0, shift2;
+    if (out === undefined) { out = []; }
+    
+    for (; shift >= 32; shift -= 32) {
+      out.push(carry);
+      carry = 0;
+    }
+    if (shift === 0) {
+      return out.concat(a);
+    }
+    
+    for (i=0; i<a.length; i++) {
+      out.push(carry | a[i]>>>shift);
+      carry = a[i] << (32-shift);
+    }
+    last2 = a.length ? a[a.length-1] : 0;
+    shift2 = sjcl.bitArray.getPartial(last2);
+    out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1));
+    return out;
+  },
+  
+  /** xor a block of 4 words together.
+   * @private
+   */
+  _xor4: function(x,y) {
+    return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]];
+  }
+};

+ 185 - 0
core/ccm.js

@@ -0,0 +1,185 @@
+/** @fileOverview CCM mode implementation.
+ *
+ * Special thanks to Roy Nicholson for pointing out a bug in our
+ * implementation.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** @namespace CTR mode with CBC MAC. */
+sjcl.mode.ccm = {
+  /** The name of the mode.
+   * @constant
+   */
+  name: "ccm",
+  
+  /** Encrypt in CCM mode.
+   * @static
+   * @param {Object} prf The pseudorandom function.  It must have a block size of 16 bytes.
+   * @param {bitArray} plaintext The plaintext data.
+   * @param {bitArray} iv The initialization value.
+   * @param {bitArray} [adata=[]] The authenticated data.
+   * @param {Number} [tlen=64] the desired tag length, in bits.
+   * @return {bitArray} The encrypted data, an array of bytes.
+   */
+  encrypt: function(prf, plaintext, iv, adata, tlen) {
+    var L, i, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8;
+    tlen = tlen || 64;
+    adata = adata || [];
+    
+    if (ivl < 7) {
+      throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");
+    }
+    
+    // compute the length of the length
+    for (L=2; L<4 && ol >>> 8*L; L++) {}
+    if (L < 15 - ivl) { L = 15-ivl; }
+    iv = w.clamp(iv,8*(15-L));
+    
+    // compute the tag
+    tag = sjcl.mode.ccm._computeTag(prf, plaintext, iv, adata, tlen, L);
+    
+    // encrypt
+    out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L);
+    
+    return w.concat(out.data, out.tag);
+  },
+  
+  /** Decrypt in CCM mode.
+   * @static
+   * @param {Object} prf The pseudorandom function.  It must have a block size of 16 bytes.
+   * @param {bitArray} ciphertext The ciphertext data.
+   * @param {bitArray} iv The initialization value.
+   * @param {bitArray} [[]] adata The authenticated data.
+   * @param {Number} [64] tlen the desired tag length, in bits.
+   * @return {bitArray} The decrypted data.
+   */
+  decrypt: function(prf, ciphertext, iv, adata, tlen) {
+    tlen = tlen || 64;
+    adata = adata || [];
+    var L, i, 
+        w=sjcl.bitArray,
+        ivl = w.bitLength(iv) / 8,
+        ol = w.bitLength(ciphertext), 
+        out = w.clamp(ciphertext, ol - tlen),
+        tag = w.bitSlice(ciphertext, ol - tlen), tag2;
+    
+
+    ol = (ol - tlen) / 8;
+        
+    if (ivl < 7) {
+      throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");
+    }
+    
+    // compute the length of the length
+    for (L=2; L<4 && ol >>> 8*L; L++) {}
+    if (L < 15 - ivl) { L = 15-ivl; }
+    iv = w.clamp(iv,8*(15-L));
+    
+    // decrypt
+    out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L);
+    
+    // check the tag
+    tag2 = sjcl.mode.ccm._computeTag(prf, out.data, iv, adata, tlen, L);
+    if (!w.equal(out.tag, tag2)) {
+      throw new sjcl.exception.corrupt("ccm: tag doesn't match");
+    }
+    
+    return out.data;
+  },
+
+  /* Compute the (unencrypted) authentication tag, according to the CCM specification
+   * @param {Object} prf The pseudorandom function.
+   * @param {bitArray} plaintext The plaintext data.
+   * @param {bitArray} iv The initialization value.
+   * @param {bitArray} adata The authenticated data.
+   * @param {Number} tlen the desired tag length, in bits.
+   * @return {bitArray} The tag, but not yet encrypted.
+   * @private
+   */
+  _computeTag: function(prf, plaintext, iv, adata, tlen, L) {
+    // compute B[0]
+    var q, mac, field = 0, offset = 24, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4;
+
+    tlen /= 8;
+  
+    // check tag length and message length
+    if (tlen % 2 || tlen < 4 || tlen > 16) {
+      throw new sjcl.exception.invalid("ccm: invalid tag length");
+    }
+  
+    if (adata.length > 0xFFFFFFFF || plaintext.length > 0xFFFFFFFF) {
+      // I don't want to deal with extracting high words from doubles.
+      throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");
+    }
+
+    // mac the flags
+    mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)];
+
+    // mac the iv and length
+    mac = w.concat(mac, iv);
+    mac[3] |= w.bitLength(plaintext)/8;
+    mac = prf.encrypt(mac);
+    
+  
+    if (adata.length) {
+      // mac the associated data.  start with its length...
+      tmp = w.bitLength(adata)/8;
+      if (tmp <= 0xFEFF) {
+        macData = [w.partial(16, tmp)];
+      } else if (tmp <= 0xFFFFFFFF) {
+        macData = w.concat([w.partial(16,0xFFFE)], [tmp]);
+      } // else ...
+    
+      // mac the data itself
+      macData = w.concat(macData, adata);
+      for (i=0; i<macData.length; i += 4) {
+        mac = prf.encrypt(xor(mac, macData.slice(i,i+4)));
+      }
+    }
+  
+    // mac the plaintext
+    for (i=0; i<plaintext.length; i+=4) {
+      mac = prf.encrypt(xor(mac, plaintext.slice(i,i+4)));
+    }
+
+    return w.clamp(mac, tlen * 8);
+  },
+
+  /** CCM CTR mode.
+   * Encrypt or decrypt data and tag with the prf in CCM-style CTR mode.
+   * May mutate its arguments.
+   * @param {Object} prf The PRF.
+   * @param {bitArray} data The data to be encrypted or decrypted.
+   * @param {bitArray} iv The initialization vector.
+   * @param {bitArray} tag The authentication tag.
+   * @param {Number} tlen The length of th etag, in bits.
+   * @param {Number} L The CCM L value.
+   * @return {Object} An object with data and tag, the en/decryption of data and tag values.
+   * @private
+   */
+  _ctrMode: function(prf, data, iv, tag, tlen, L) {
+    var enc, i, w=sjcl.bitArray, xor = w._xor4, ctr, b, l = data.length, bl=w.bitLength(data);
+
+    // start the ctr
+    ctr = w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4);
+    
+    // en/decrypt the tag
+    tag = w.bitSlice(xor(tag,prf.encrypt(ctr)), 0, tlen);
+  
+    // en/decrypt the data
+    if (!l) { return {tag:tag, data:[]}; }
+    
+    for (i=0; i<l; i+=4) {
+      ctr[3]++;
+      enc = prf.encrypt(ctr);
+      data[i]   ^= enc[0];
+      data[i+1] ^= enc[1];
+      data[i+2] ^= enc[2];
+      data[i+3] ^= enc[3];
+    }
+    return { tag:tag, data:w.clamp(data,bl) };
+  }
+};

+ 56 - 0
core/codecBase64.js

@@ -0,0 +1,56 @@
+/** @fileOverview Bit array codec implementations.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** @namespace Base64 encoding/decoding */
+sjcl.codec.base64 = {
+  /** The base64 alphabet.
+   * @private
+   */
+  _chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
+  
+  /** Convert from a bitArray to a base64 string. */
+  fromBits: function (arr, _noEquals) {
+    var out = "", i, bits=0, c = sjcl.codec.base64._chars, ta=0, bl = sjcl.bitArray.bitLength(arr);
+    for (i=0; out.length * 6 < bl; ) {
+      out += c.charAt((ta ^ arr[i]>>>bits) >>> 26);
+      if (bits < 6) {
+        ta = arr[i] << (6-bits);
+        bits += 26;
+        i++;
+      } else {
+        ta <<= 6;
+        bits -= 6;
+      }
+    }
+    while ((out.length & 3) && !_noEquals) { out += "="; }
+    return out;
+  },
+  
+  /** Convert from a base64 string to a bitArray */
+  toBits: function(str) {
+    str = str.replace(/\s|=/g,'');
+    var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x;
+    for (i=0; i<str.length; i++) {
+      x = c.indexOf(str.charAt(i));
+      if (x < 0) {
+        throw new sjcl.exception.invalid("this isn't base64!");
+      }
+      if (bits > 26) {
+        bits -= 26;
+        out.push(ta ^ x>>>bits);
+        ta  = x << (32-bits);
+      } else {
+        bits += 6;
+        ta ^= x << (32-bits);
+      }
+    }
+    if (bits&56) {
+      out.push(sjcl.bitArray.partial(bits&56, ta, 1));
+    }
+    return out;
+  }
+};

+ 37 - 0
core/codecBytes.js

@@ -0,0 +1,37 @@
+/** @fileOverview Bit array codec implementations.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** @namespace Arrays of bytes */
+sjcl.codec.bytes = {
+  /** Convert from a bitArray to an array of bytes. */
+  fromBits: function (arr) {
+    var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp;
+    for (i=0; i<bl/8; i++) {
+      if ((i&3) === 0) {
+        tmp = arr[i/4];
+      }
+      out.push(tmp >>> 24);
+      tmp <<= 8;
+    }
+    return out;
+  },
+  /** Convert from an array of bytes to a bitArray. */
+  toBits: function (bytes) {
+    var out = [], i, tmp=0;
+    for (i=0; i<bytes.length; i++) {
+      tmp = tmp << 8 | bytes[i];
+      if ((i&3) === 3) {
+        out.push(tmp);
+        tmp = 0;
+      }
+    }
+    if (i&3) {
+      out.push(sjcl.bitArray.partial(8*(i&3), tmp));
+    }
+    return out;
+  }
+};

+ 30 - 0
core/codecHex.js

@@ -0,0 +1,30 @@
+/** @fileOverview Bit array codec implementations.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** @namespace Hexadecimal */
+sjcl.codec.hex = {
+  /** Convert from a bitArray to a hex string. */
+  fromBits: function (arr) {
+    var out = "", i, x;
+    for (i=0; i<arr.length; i++) {
+      out += ((arr[i]|0)+0xF00000000000).toString(16).substr(4);
+    }
+    return out.substr(0, sjcl.bitArray.bitLength(arr)/4);//.replace(/(.{8})/g, "$1 ");
+  },
+  /** Convert from a hex string to a bitArray. */
+  toBits: function (str) {
+    var i, out=[], len;
+    str = str.replace(/\s|0x/g, "");
+    len = str.length;
+    str = str + "00000000";
+    for (i=0; i<str.length; i+=8) {
+      out.push(parseInt(str.substr(i,8),16)^0);
+    }
+    return sjcl.bitArray.clamp(out, len*4);
+  }
+};
+

+ 39 - 0
core/codecString.js

@@ -0,0 +1,39 @@
+/** @fileOverview Bit array codec implementations.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+ 
+/** @namespace UTF-8 strings */
+sjcl.codec.utf8String = {
+  /** Convert from a bitArray to a UTF-8 string. */
+  fromBits: function (arr) {
+    var out = "", bl = sjcl.bitArray.bitLength(arr), i, tmp;
+    for (i=0; i<bl/8; i++) {
+      if ((i&3) === 0) {
+        tmp = arr[i/4];
+      }
+      out += String.fromCharCode(tmp >>> 24);
+      tmp <<= 8;
+    }
+    return decodeURIComponent(escape(out));
+  },
+  
+  /** Convert from a UTF-8 string to a bitArray. */
+  toBits: function (str) {
+    str = unescape(encodeURIComponent(str));
+    var out = [], i, tmp=0;
+    for (i=0; i<str.length; i++) {
+      tmp = tmp << 8 | str.charCodeAt(i);
+      if ((i&3) === 3) {
+        out.push(tmp);
+        tmp = 0;
+      }
+    }
+    if (i&3) {
+      out.push(sjcl.bitArray.partial(8*(i&3), tmp));
+    }
+    return out;
+  }
+};

+ 271 - 0
core/convenience.js

@@ -0,0 +1,271 @@
+/** @fileOverview Convenince functions centered around JSON encapsulation.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+ 
+ /** @namespace JSON encapsulation */
+ sjcl.json = {
+  /** Default values for encryption */
+  defaults: { v:1, iter:1000, ks:128, ts:64, mode:"ccm", adata:"", cipher:"aes" },
+
+  /** Simple encryption function.
+   * @param {String|bitArray} password The password or key.
+   * @param {String} plaintext The data to encrypt.
+   * @param {Object} [params] The parameters including tag, iv and salt.
+   * @param {Object} [rp] A returned version with filled-in parameters.
+   * @return {String} The ciphertext.
+   * @throws {sjcl.exception.invalid} if a parameter is invalid.
+   */
+  encrypt: function (password, plaintext, params, rp) {
+    params = params || {};
+    rp = rp || {};
+    
+    var j = sjcl.json, p = j._add({ iv: sjcl.random.randomWords(4,0) },
+                                  j.defaults), tmp, prp;
+    j._add(p, params);
+    if (typeof p.salt === "string") {
+      p.salt = sjcl.codec.base64.toBits(p.salt);
+    }
+    if (typeof p.iv === "string") {
+      p.iv = sjcl.codec.base64.toBits(p.iv);
+    }
+    
+    if (!sjcl.mode[p.mode] ||
+        !sjcl.cipher[p.cipher] ||
+        (typeof password === "string" && p.iter <= 100) ||
+        (p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
+        (p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
+        (p.iv.length < 2 || p.iv.length > 4)) {
+      throw new sjcl.exception.invalid("json encrypt: invalid parameters");
+    }
+    
+    if (typeof password === "string") {
+      tmp = sjcl.misc.cachedPbkdf2(password, p);
+      password = tmp.key.slice(0,p.ks/32);
+      p.salt = tmp.salt;
+    }
+    if (typeof plaintext === "string") {
+      plaintext = sjcl.codec.utf8String.toBits(plaintext);
+    }
+    prp = new sjcl.cipher[p.cipher](password);
+    
+    /* return the json data */
+    j._add(rp, p);
+    rp.key = password;
+    
+    /* do the encryption */
+    p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, p.adata, p.tag);
+    
+    return j.encode(j._subtract(p, j.defaults));
+  },
+  
+  /** Simple decryption function.
+   * @param {String|bitArray} password The password or key.
+   * @param {String} ciphertext The ciphertext to decrypt.
+   * @param {Object} [params] Additional non-default parameters.
+   * @param {Object} [rp] A returned object with filled parameters.
+   * @return {String} The plaintext.
+   * @throws {sjcl.exception.invalid} if a parameter is invalid.
+   * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt.
+   */
+  decrypt: function (password, ciphertext, params, rp) {
+    params = params || {};
+    rp = rp || {};
+    
+    var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),j.decode(ciphertext)), params, true), ct, tmp, prp;
+    if (typeof p.salt === "string") {
+      p.salt = sjcl.codec.base64.toBits(p.salt);
+    }
+    if (typeof p.iv === "string") {
+      p.iv = sjcl.codec.base64.toBits(p.iv);
+    }
+    
+    if (!sjcl.mode[p.mode] ||
+        !sjcl.cipher[p.cipher] ||
+        (typeof password === "string" && p.iter <= 100) ||
+        (p.ts !== 64 && p.ts !== 96 && p.ts !== 128) ||
+        (p.ks !== 128 && p.ks !== 192 && p.ks !== 256) ||
+        (!p.iv) ||
+        (p.iv.length < 2 || p.iv.length > 4)) {
+      throw new sjcl.exception.invalid("json decrypt: invalid parameters");
+    }
+    
+    if (typeof password === "string") {
+      tmp = sjcl.misc.cachedPbkdf2(password, p);
+      password = tmp.key.slice(0,p.ks/32);
+      p.salt  = tmp.salt;
+    }
+    prp = new sjcl.cipher[p.cipher](password);
+    
+    /* do the decryption */
+    ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, p.adata, p.tag);
+    
+    /* return the json data */
+    j._add(rp, p);
+    rp.key = password;
+    
+    return sjcl.codec.utf8String.fromBits(ct);
+  },
+  
+  /** Encode a flat structure into a JSON string.
+   * @param {Object} obj The structure to encode.
+   * @return {String} A JSON string.
+   * @throws {sjcl.exception.invalid} if obj has a non-alphanumeric property.
+   * @throws {sjcl.exception.bug} if a parameter has an unsupported type.
+   */
+  encode: function (obj) {
+    var i, out='{', comma='';
+    for (i in obj) {
+      if (obj.hasOwnProperty(i)) {
+        if (!i.match(/^[a-z0-9]+$/i)) {
+          throw new sjcl.exception.invalid("json encode: invalid property name");
+        }
+        out += comma + i + ':';
+        comma = ',';
+        
+        switch (typeof obj[i]) {
+        case 'number':
+        case 'boolean':
+          out += obj[i];
+          break;
+          
+        case 'string':
+          out += '"' + escape(obj[i]) + '"';
+          break;
+        
+        case 'object':
+          out += '"' + sjcl.codec.base64.fromBits(obj[i],1) + '"';
+          break;
+        
+        default:
+          throw new sjcl.exception.bug("json encode: unsupported type");
+        }
+      }
+    }
+    return out+'}';
+  },
+  
+  /** Decode a simple (flat) JSON string into a structure.  The ciphertext,
+   * adata, salt and iv will be base64-decoded.
+   * @param {String} str The string.
+   * @return {Object} The decoded structure.
+   * @throws {sjcl.exception.invalid} if str isn't (simple) JSON.
+   */
+  decode: function (str) {
+    str = str.replace(/\s/g,'');
+    if (!str.match(/^\{.*\}$/)) { 
+      throw new sjcl.exception.invalid("json decode: this isn't json!");
+    }
+    var a = str.replace(/^\{|\}$/g, '').split(/,/), out={}, i, m;
+    for (i=0; i<a.length; i++) {
+      if (!(m=a[i].match(/^([a-z][a-z0-9]*):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i))) {
+        throw new sjcl.exception.invalid("json decode: this isn't json!");
+      }
+      if (m[2]) {
+        out[m[1]] = parseInt(m[2],10);
+      } else {
+        out[m[1]] = m[1].match(/^(ct|salt|iv)$/) ? sjcl.codec.base64.toBits(m[3]) : unescape(m[3]);
+      }
+    }
+    return out;
+  },
+  
+  /** Insert all elements of src into target, modifying and returning target.
+   * @param {Object} target The object to be modified.
+   * @param {Object} src The object to pull data from.
+   * @param {boolean} [requireSame=false] If true, throw an exception if any field of target differs from corresponding field of src.
+   * @return {Object} target.
+   * @private
+   */
+  _add: function (target, src, requireSame) {
+    if (target === undefined) { target = {}; }
+    if (src === undefined) { return target; }
+    var i;
+    for (i in src) {
+      if (src.hasOwnProperty(i)) {
+        if (requireSame && target[i] !== undefined && target[i] !== src[i]) {
+          throw new sjcl.exception.invalid("required parameter overridden");
+        }
+        target[i] = src[i];
+      }
+    }
+    return target;
+  },
+  
+  /** Remove all elements of minus from plus.  Does not modify plus.
+   * @private
+   */
+  _subtract: function (plus, minus) {
+    var out = {}, i;
+    
+    for (i in plus) {
+      if (plus.hasOwnProperty(i) && plus[i] !== minus[i]) {
+        out[i] = plus[i];
+      }
+    }
+    
+    return out;
+  },
+  
+  /** Return only the specified elements of src.
+   * @private
+   */
+  _filter: function (src, filter) {
+    var out = {}, i;
+    for (i=0; i<filter.length; i++) {
+      if (src[filter[i]] !== undefined) {
+        out[filter[i]] = src[filter[i]];
+      }
+    }
+    return out;
+  }
+};
+
+/** Simple encryption function; convenient shorthand for sjcl.json.encrypt.
+ * @param {String|bitArray} password The password or key.
+ * @param {String} plaintext The data to encrypt.
+ * @param {Object} [params] The parameters including tag, iv and salt.
+ * @param {Object} [rp] A returned version with filled-in parameters.
+ * @return {String} The ciphertext.
+ */
+sjcl.encrypt = sjcl.json.encrypt;
+
+/** Simple decryption function; convenient shorthand for sjcl.json.decrypt.
+ * @param {String|bitArray} password The password or key.
+ * @param {String} ciphertext The ciphertext to decrypt.
+ * @param {Object} [params] Additional non-default parameters.
+ * @param {Object} [rp] A returned object with filled parameters.
+ * @return {String} The plaintext.
+ */
+sjcl.decrypt = sjcl.json.decrypt;
+
+/** The cache for cachedPbkdf2.
+ * @private
+ */
+sjcl.misc._pbkdf2Cache = {};
+
+/** Cached PBKDF2 key derivation.
+ * @param {String} The password.  
+ * @param {Object} The derivation params (iteration count and optional salt).
+ * @return {Object} The derived data in key, the salt in salt.
+ */
+sjcl.misc.cachedPbkdf2 = function (password, obj) {
+  var cache = sjcl.misc._pbkdf2Cache, c, cp, str, salt, iter;
+  
+  obj = obj || {};
+  iter = obj.iter || 1000;
+  
+  /* open the cache for this password and iteration count */
+  cp = cache[password] = cache[password] || {};
+  c = cp[iter] = cp[iter] || { firstSalt: (obj.salt && obj.salt.length) ?
+                     obj.salt.slice(0) : sjcl.random.randomWords(2,0) };
+          
+  salt = (obj.salt === undefined) ? c.firstSalt : obj.salt;
+  
+  c[salt] = c[salt] || sjcl.misc.pbkdf2(password, salt, obj.iter);
+  return { key: c[salt].slice(0), salt:salt.slice(0) };
+};
+
+

+ 40 - 0
core/hmac.js

@@ -0,0 +1,40 @@
+/** @fileOverview HMAC implementation.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** HMAC with the specified hash function.
+ * @constructor
+ * @param {bitArray} key the key for HMAC.
+ * @param {Object} [hash=sjcl.hash.sha256] The hash function to use.
+ */
+sjcl.misc.hmac = function (key, Hash) {
+  this._hash = Hash = Hash || sjcl.hash.sha256;
+  var exKey = [[],[]], i,
+      bs = Hash.prototype.blockSize / 32;
+  this._baseHash = [new Hash(), new Hash()];
+
+  if (key.length > bs) {
+    key = Hash.hash(key);
+  }
+  
+  for (i=0; i<bs; i++) {
+    exKey[0][i] = key[i]^0x36363636;
+    exKey[1][i] = key[i]^0x5C5C5C5C;
+  }
+  
+  this._baseHash[0].update(exKey[0]);
+  this._baseHash[1].update(exKey[1]);
+};
+
+/** HMAC with the specified hash function.  Also called encrypt since it's a prf.
+ * @param {bitArray|String} data The data to mac.
+ * @param {Codec} [encoding] the encoding function to use.
+ */
+sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (data, encoding) {
+  var w = new (this._hash)(this._baseHash[0]).update(data, encoding).finalize();
+  return new (this._hash)(this._baseHash[1]).update(w).finalize();
+};
+

+ 171 - 0
core/ocb2.js

@@ -0,0 +1,171 @@
+/** @fileOverview OCB 2.0 implementation
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** @namespace
+ * Phil Rogaway's Offset CodeBook mode, version 2.0.
+ * May be covered by US and international patents.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+sjcl.mode.ocb2 = {
+  /** The name of the mode.
+   * @constant
+   */
+  name: "ocb2",
+  
+  /** Encrypt in OCB mode, version 2.0.
+   * @param {Object} prp The block cipher.  It must have a block size of 16 bytes.
+   * @param {bitArray} plaintext The plaintext data.
+   * @param {bitArray} iv The initialization value.
+   * @param {bitArray} [adata=[]] The authenticated data.
+   * @param {Number} [tlen=64] the desired tag length, in bits.
+   * @param [false] premac 1 if the authentication data is pre-macced with PMAC.
+   * @return The encrypted data, an array of bytes.
+   * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits.
+   */
+  encrypt: function(prp, plaintext, iv, adata, tlen, premac) {
+    if (sjcl.bitArray.bitLength(iv) !== 128) {
+      throw new sjcl.exception.invalid("ocb iv must be 128 bits");
+    }
+    var i,
+        times2 = sjcl.mode.ocb2._times2,
+        w = sjcl.bitArray,
+        xor = w._xor4,
+        checksum = [0,0,0,0],
+        delta = times2(prp.encrypt(iv)),
+        bi, bl,
+        output = [],
+        pad;
+        
+    adata = adata || [];
+    tlen  = tlen || 64;
+  
+    for (i=0; i+4 < plaintext.length; i+=4) {
+      /* Encrypt a non-final block */
+      bi = plaintext.slice(i,i+4);
+      checksum = xor(checksum, bi);
+      output = output.concat(xor(delta,prp.encrypt(xor(delta, bi))));
+      delta = times2(delta);
+    }
+    
+    /* Chop out the final block */
+    bi = plaintext.slice(i);
+    bl = w.bitLength(bi);
+    pad = prp.encrypt(xor(delta,[0,0,0,bl]));
+    bi = w.clamp(xor(bi,pad), bl);
+    
+    /* Checksum the final block, and finalize the checksum */
+    checksum = xor(checksum,xor(bi,pad));
+    checksum = prp.encrypt(xor(checksum,xor(delta,times2(delta))));
+    
+    /* MAC the header */
+    if (adata.length) {
+      checksum = xor(checksum, premac ? adata : sjcl.mode.ocb2.pmac(prp, adata));
+    }
+    
+    return output.concat(w.concat(bi, w.clamp(checksum, tlen)));
+  },
+  
+  /** Decrypt in OCB mode.
+   * @param {Object} prp The block cipher.  It must have a block size of 16 bytes.
+   * @param {bitArray} ciphertext The ciphertext data.
+   * @param {bitArray} iv The initialization value.
+   * @param {bitArray} [adata=[]] The authenticated data.
+   * @param {Number} [tlen=64] the desired tag length, in bits.
+   * @param {boolean} [premac=false] true if the authentication data is pre-macced with PMAC.
+   * @return The decrypted data, an array of bytes.
+   * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits.
+   * @throws {sjcl.exception.corrupt} if if the message is corrupt.
+   */
+  decrypt: function(prp, ciphertext, iv, adata, tlen, premac) {
+    if (sjcl.bitArray.bitLength(iv) !== 128) {
+      throw new sjcl.exception.invalid("ocb iv must be 128 bits");
+    }
+    tlen  = tlen || 64;
+    var i,
+        times2 = sjcl.mode.ocb2._times2,
+        w = sjcl.bitArray,
+        xor = w._xor4,
+        checksum = [0,0,0,0],
+        delta = times2(prp.encrypt(iv)),
+        bi, bl,
+        len = sjcl.bitArray.bitLength(ciphertext) - tlen,
+        output = [],
+        pad;
+        
+    adata = adata || [];
+  
+    for (i=0; i+4 < len/32; i+=4) {
+      /* Decrypt a non-final block */
+      bi = xor(delta, prp.decrypt(xor(delta, ciphertext.slice(i,i+4))));
+      checksum = xor(checksum, bi);
+      output = output.concat(bi);
+      delta = times2(delta);
+    }
+    
+    /* Chop out and decrypt the final block */
+    bl = len-i*32;
+    pad = prp.encrypt(xor(delta,[0,0,0,bl]));
+    bi = xor(pad, w.clamp(ciphertext.slice(i),bl));
+    
+    /* Checksum the final block, and finalize the checksum */
+    checksum = xor(checksum, bi);
+    checksum = prp.encrypt(xor(checksum, xor(delta, times2(delta))));
+    
+    /* MAC the header */
+    if (adata.length) {
+      checksum = xor(checksum, premac ? adata : sjcl.mode.ocb2.pmac(prp, adata));
+    }
+    
+    if (!w.equal(w.clamp(checksum, tlen), w.bitSlice(ciphertext, len))) {
+      throw new sjcl.exception.corrupt("ocb: tag doesn't match");
+    }
+    
+    return output.concat(w.clamp(bi,bl));
+  },
+  
+  /** PMAC authentication for OCB associated data.
+   * @param {Object} prp The block cipher.  It must have a block size of 16 bytes.
+   * @param {bitArray} adata The authenticated data.
+   */
+  pmac: function(prp, adata) {
+    var i,
+        times2 = sjcl.mode.ocb2._times2,
+        w = sjcl.bitArray,
+        xor = w._xor4,
+        checksum = [0,0,0,0],
+        delta = prp.encrypt([0,0,0,0]),
+        bi;
+        
+    delta = xor(delta,times2(times2(delta)));
+ 
+    for (i=0; i+4<adata.length; i+=4) {
+      delta = times2(delta);
+      checksum = xor(checksum, prp.encrypt(xor(delta, adata.slice(i,i+4))));
+    }
+    
+    bi = adata.slice(i);
+    if (w.bitLength(bi) < 128) {
+      delta = xor(delta,times2(delta));
+      bi = w.concat(bi,[0x80000000|0]);
+    }
+    checksum = xor(checksum, bi);
+    return prp.encrypt(xor(times2(xor(delta,times2(delta))), checksum));
+  },
+  
+  /** Double a block of words, OCB style.
+   * @private
+   */
+  _times2: function(x) {
+    return [x[0]<<1 ^ x[1]>>>31,
+            x[1]<<1 ^ x[2]>>>31,
+            x[2]<<1 ^ x[3]>>>31,
+            x[3]<<1 ^ (x[0]>>>31)*0x87];
+  }
+};

+ 54 - 0
core/pbkdf2.js

@@ -0,0 +1,54 @@
+/** @fileOverview Password-based key-derivation function, version 2.0.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** Password-Based Key-Derivation Function, version 2.0.
+ *
+ * Generate keys from passwords using PBKDF2-HMAC-SHA256.
+ *
+ * This is the method specified by RSA's PKCS #5 standard.
+ *
+ * @param {bitArray|String} password  The password.
+ * @param {bitArray} salt The salt.  Should have lots of entropy.
+ * @param {Number} [count=1000] The number of iterations.  Higher numbers make the function slower but more secure.
+ * @param {Number} [length] The length of the derived key.  Defaults to the
+                            output size of the hash function.
+ * @param {Object} [Prff=sjcl.misc.hmac] The pseudorandom function family.
+ * @return {bitArray} the derived key.
+ */
+sjcl.misc.pbkdf2 = function (password, salt, count, length, Prff) {
+  count = count || 1000;
+  
+  if (length < 0 || count < 0) {
+    throw sjcl.exception.invalid("invalid params to pbkdf2");
+  }
+  
+  if (typeof password === "string") {
+    password = sjcl.codec.utf8String.toBits(password);
+  }
+  
+  Prff = Prff || sjcl.misc.hmac;
+  
+  var prf = new Prff(password),
+      u, ui, i, j, k, out = [], b = sjcl.bitArray;
+
+  for (k = 1; 32 * out.length < (length || 1); k++) {
+    u = ui = prf.encrypt(b.concat(salt,[k]));
+    
+    for (i=1; i<count; i++) {
+      ui = prf.encrypt(ui);
+      for (j=0; j<ui.length; j++) {
+        u[j] ^= ui[j];
+      }
+    }
+    
+    out = out.concat(u);
+  }
+
+  if (length) { out = b.clamp(out, length); }
+
+  return out;
+};

+ 368 - 0
core/random.js

@@ -0,0 +1,368 @@
+/** @fileOverview Random number generator.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/** @namespace Random number generator
+ *
+ * @description
+ * <p>
+ * This random number generator is a derivative of Ferguson and Schneier's
+ * generator Fortuna.  It collects entropy from various events into several
+ * pools, implemented by streaming SHA-256 instances.  It differs from
+ * ordinary Fortuna in a few ways, though.
+ * </p>
+ *
+ * <p>
+ * Most importantly, it has an entropy estimator.  This is present because
+ * there is a strong conflict here between making the generator available
+ * as soon as possible, and making sure that it doesn't "run on empty".
+ * In Fortuna, there is a saved state file, and the system is likely to have
+ * time to warm up.
+ * </p>
+ *
+ * <p>
+ * Second, because users are unlikely to stay on the page for very long,
+ * and to speed startup time, the number of pools increases logarithmically:
+ * a new pool is created when the previous one is actually used for a reseed.
+ * This gives the same asymptotic guarantees as Fortuna, but gives more
+ * entropy to early reseeds.
+ * </p>
+ *
+ * <p>
+ * The entire mechanism here feels pretty klunky.  Furthermore, there are
+ * several improvements that should be made, including support for
+ * dedicated cryptographic functions that may be present in some browsers;
+ * state files in local storage; cookies containing randomness; etc.  So
+ * look for improvements in future versions.
+ * </p>
+ */
+sjcl.random = {
+  /** Generate several random words, and return them in an array
+   * @param {Number} nwords The number of words to generate.
+   */
+  randomWords: function (nwords, paranoia) {
+    var out = [], i, readiness = this.isReady(paranoia), g;
+  
+    if (readiness === this._NOT_READY) {
+      throw new sjcl.exception.notready("generator isn't seeded");
+    } else if (readiness & this._REQUIRES_RESEED) {
+      this._reseedFromPools(!(readiness & this._READY));
+    }
+  
+    for (i=0; i<nwords; i+= 4) {
+      if ((i+1) % this._MAX_WORDS_PER_BURST === 0) {
+        this._gate();
+      }
+   
+      g = this._gen4words();
+      out.push(g[0],g[1],g[2],g[3]);
+    }
+    this._gate();
+  
+    return out.slice(0,nwords);
+  },
+  
+  setDefaultParanoia: function (paranoia) {
+    this._defaultParanoia = paranoia;
+  },
+  
+  /**
+   * Add entropy to the pools.
+   * @param data The entropic value.  Should be a 32-bit integer, array of 32-bit integers, or string
+   * @param {Number} estimatedEntropy The estimated entropy of data, in bits
+   * @param {String} source The source of the entropy, eg "mouse"
+   */
+  addEntropy: function (data, estimatedEntropy, source) {
+    source = source || "user";
+  
+    var id,
+      i, ty = 0, tmp,
+      t = (new Date()).valueOf(),
+      robin = this._robins[source],
+      oldReady = this.isReady();
+      
+    id = this._collectorIds[source];
+    if (id === undefined) { id = this._collectorIds[source] = this._collectorIdNext ++; }
+      
+    if (robin === undefined) { robin = this._robins[source] = 0; }
+    this._robins[source] = ( this._robins[source] + 1 ) % this._pools.length;
+  
+    switch(typeof(data)) {
+      
+    case "number":
+      data=[data];
+      ty=1;
+      break;
+      
+    case "object":
+      if (estimatedEntropy === undefined) {
+       /* horrible entropy estimator */
+       estimatedEntropy = 0;
+       for (i=0; i<data.length; i++) {
+          tmp= data[i];
+          while (tmp>0) {
+           estimatedEntropy++;
+           tmp = tmp >>> 1;
+          }
+        }
+      }
+      this._pools[robin].update([id,this._eventId++,ty||2,estimatedEntropy,t,data.length].concat(data));
+      break;
+      
+    case "string":
+      if (estimatedEntropy === undefined) {
+       /* English text has just over 1 bit per character of entropy.
+        * But this might be HTML or something, and have far less
+        * entropy than English...  Oh well, let's just say one bit.
+        */
+       estimatedEntropy = data.length;
+      }
+      this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]);
+      this._pools[robin].update(data);
+      break;
+      
+    default:
+      
+      throw new sjcl.exception.bug("random: addEntropy only supports number, array or string");
+    }
+  
+    /* record the new strength */
+    this._poolEntropy[robin] += estimatedEntropy;
+    this._poolStrength += estimatedEntropy;
+  
+    /* fire off events */
+    if (oldReady === this._NOT_READY) {
+      if (this.isReady() !== this._NOT_READY) {
+        this._fireEvent("seeded", Math.max(this._strength, this._poolStrength));
+      }
+      this._fireEvent("progress", this.getProgress());
+    }
+  },
+  
+  /** Is the generator ready? */
+  isReady: function (paranoia) {
+    var entropyRequired = this._PARANOIA_LEVELS[ (paranoia !== undefined) ? paranoia : this._defaultParanoia ];
+  
+    if (this._strength && this._strength >= entropyRequired) {
+      return (this._poolEntropy[0] > this._BITS_PER_RESEED && (new Date()).valueOf() > this._nextReseed) ?
+        this._REQUIRES_RESEED | this._READY :
+        this._READY;
+    } else {
+      return (this._poolStrength >= entropyRequired) ?
+        this._REQUIRES_RESEED | this._NOT_READY :
+        this._NOT_READY;
+    }
+  },
+  
+  /** Get the generator's progress toward readiness, as a fraction */
+  getProgress: function (paranoia) {
+    var entropyRequired = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._defaultParanoia ];
+  
+    if (this._strength >= entropyRequired) {
+      return 1.0;
+    } else {
+      return (this._poolStrength > entropyRequired) ?
+        1.0 :
+        this._poolStrength / entropyRequired;
+    }
+  },
+  
+  /** start the built-in entropy collectors */
+  startCollectors: function () {
+    if (this._collectorsStarted) { return; }
+  
+    if (window.addEventListener) {
+      window.addEventListener("load", this._loadTimeCollector, false);
+      window.addEventListener("mousemove", this._mouseCollector, false);
+    } else if (document.attachEvent) {
+      document.attachEvent("onload", this._loadTimeCollector);
+      document.attachEvent("onmousemove", this._mouseCollector);
+    }
+    else {
+      throw new sjcl.exception.bug("can't attach event");
+    }
+  
+    this._collectorsStarted = true;
+  },
+  
+  /** stop the built-in entropy collectors */
+  stopCollectors: function () {
+    if (!this._collectorsStarted) { return; }
+  
+    if (window.removeEventListener) {
+      window.removeEventListener("load", this._loadTimeCollector);
+      window.removeEventListener("mousemove", this._mouseCollector);
+    } else if (window.detachEvent) {
+      window.detachEvent("onload", this._loadTimeCollector);
+      window.detachEvent("onmousemove", this._mouseCollector);
+    }
+    this._collectorsStarted = false;
+  },
+  
+  /* use a cookie to store entropy.
+  useCookie: function (all_cookies) {
+      throw new sjcl.exception.bug("random: useCookie is unimplemented");
+  },*/
+  
+  /** add an event listener for progress or seeded-ness. */
+  addEventListener: function (name, callback) {
+    this._callbacks[name][this._callbackI++] = callback;
+  },
+  
+  /** remove an event listener for progress or seeded-ness */
+  removeEventListener: function (name, cb) {
+    var i, j, cbs=this._callbacks[name], jsTemp=[];
+  
+    /* I'm not sure if this is necessary; in C++, iterating over a
+     * collection and modifying it at the same time is a no-no.
+     */
+  
+    for (j in cbs) {
+     if (cbs.hasOwnProperty[j] && cbs[j] === cb) {
+        jsTemp.push(j);
+      }
+    }
+  
+    for (i=0; i<jsTemp.length; i++) {
+      j = jsTemp[i];
+      delete cbs[j];
+    }
+  },
+  
+  /* private */
+  _pools                   : [new sjcl.hash.sha256()],
+  _poolEntropy             : [0],
+  _reseedCount             : 0,
+  _robins                  : {},
+  _eventId                 : 0,
+  
+  _collectorIds            : {},
+  _collectorIdNext         : 0,
+  
+  _strength                : 0,
+  _poolStrength            : 0,
+  _nextReseed              : 0,
+  _key                     : [0,0,0,0,0,0,0,0],
+  _counter                 : [0,0,0,0],
+  _cipher                  : undefined,
+  _defaultParanoia         : 6,
+  
+  /* event listener stuff */
+  _collectorsStarted       : false,
+  _callbacks               : {progress: {}, seeded: {}},
+  _callbackI               : 0,
+  
+  /* constants */
+  _NOT_READY               : 0,
+  _READY                   : 1,
+  _REQUIRES_RESEED         : 2,
+
+  _MAX_WORDS_PER_BURST     : 65536,
+  _PARANOIA_LEVELS         : [0,48,64,96,128,192,256,384,512,768,1024],
+  _MILLISECONDS_PER_RESEED : 30000,
+  _BITS_PER_RESEED         : 80,
+  
+  /** Generate 4 random words, no reseed, no gate.
+   * @private
+   */
+  _gen4words: function () {
+    for (var i=0; i<4; i++) {
+      this._counter[i] = this._counter[i]+1 | 0;
+      if (this._counter[i]) { break; }
+    }
+    return this._cipher.encrypt(this._counter);
+  },
+  
+  /* Rekey the AES instance with itself after a request, or every _MAX_WORDS_PER_BURST words.
+   * @private
+   */
+  _gate: function () {
+    this._key = this._gen4words().concat(this._gen4words());
+    this._cipher = new sjcl.cipher.aes(this._key);
+  },
+  
+  /** Reseed the generator with the given words
+   * @private
+   */
+  _reseed: function (seedWords) {
+    this._key = sjcl.hash.sha256.hash(this._key.concat(seedWords));
+    this._cipher = new sjcl.cipher.aes(this._key);
+    for (var i=0; i<4; i++) {
+      this._counter[i] = this._counter[i]+1 | 0;
+      if (this._counter[i]) { break; }
+    }
+  },
+  
+  /** reseed the data from the entropy pools
+   * @param full If set, use all the entropy pools in the reseed.
+   */
+  _reseedFromPools: function (full) {
+    var reseedData = [], strength = 0, i;
+  
+    this._nextReseed = reseedData[0] =
+      (new Date()).valueOf() + this._MILLISECONDS_PER_RESEED;
+    
+    for (i=0; i<16; i++) {
+      /* On some browsers, this is cryptographically random.  So we might
+       * as well toss it in the pot and stir...
+       */
+      reseedData.push(Math.random()*0x100000000|0);
+    }
+    
+    for (i=0; i<this._pools.length; i++) {
+     reseedData = reseedData.concat(this._pools[i].finalize());
+     strength += this._poolEntropy[i];
+     this._poolEntropy[i] = 0;
+   
+     if (!full && (this._reseedCount & (1<<i))) { break; }
+    }
+  
+    /* if we used the last pool, push a new one onto the stack */
+    if (this._reseedCount >= 1 << this._pools.length) {
+     this._pools.push(new sjcl.hash.sha256());
+     this._poolEntropy.push(0);
+    }
+  
+    /* how strong was this reseed? */
+    this._poolStrength -= strength;
+    if (strength > this._strength) {
+      this._strength = strength;
+    }
+  
+    this._reseedCount ++;
+    this._reseed(reseedData);
+  },
+  
+  _mouseCollector: function (ev) {
+    var x = ev.x || ev.clientX || ev.offsetX, y = ev.y || ev.clientY || ev.offsetY;
+    sjcl.random.addEntropy([x,y], 2, "mouse");
+  },
+  
+  _loadTimeCollector: function (ev) {
+    var d = new Date();
+    sjcl.random.addEntropy(d, 2, "loadtime");
+  },
+  
+  _fireEvent: function (name, arg) {
+    var j, cbs=sjcl.random._callbacks[name], cbsTemp=[];
+    /* TODO: there is a race condition between removing collectors and firing them */ 
+
+    /* I'm not sure if this is necessary; in C++, iterating over a
+     * collection and modifying it at the same time is a no-no.
+     */
+  
+    for (j in cbs) {
+     if (cbs.hasOwnProperty(j)) {
+        cbsTemp.push(cbs[j]);
+     }
+    }
+  
+    for (j=0; j<cbsTemp.length; j++) {
+     cbsTemp[j](arg);
+    }
+  }
+};
+

+ 216 - 0
core/sha256.js

@@ -0,0 +1,216 @@
+/** @fileOverview Javascript SHA-256 implementation.
+ *
+ * An older version of this implementation is available in the public
+ * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
+ * Stanford University 2008-2010 and BSD-licensed for liability
+ * reasons.
+ *
+ * Special thanks to Aldo Cortesi for pointing out several bugs in
+ * this code.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+/**
+ * Context for a SHA-256 operation in progress.
+ * @constructor
+ * @class Secure Hash Algorithm, 256 bits.
+ */
+sjcl.hash.sha256 = function (hash) {
+  if (!this._key[0]) { this._precompute(); }
+  if (hash) {
+    this._h = hash._h.slice(0);
+    this._buffer = hash._buffer.slice(0);
+    this._length = hash._length;
+  } else {
+    this.reset();
+  }
+};
+
+/**
+ * Hash a string or an array of words.
+ * @static
+ * @param {bitArray|String} data the data to hash.
+ * @return {bitArray} The hash value, an array of 16 big-endian words.
+ */
+sjcl.hash.sha256.hash = function (data) {
+  return (new sjcl.hash.sha256()).update(data).finalize();
+};
+
+sjcl.hash.sha256.prototype = {
+  /**
+   * The hash's block size, in bits.
+   * @constant
+   */
+  blockSize: 512,
+   
+  /**
+   * Reset the hash state.
+   * @return this
+   */
+  reset:function () {
+    this._h = this._init.slice(0);
+    this._buffer = [];
+    this._length = 0;
+    return this;
+  },
+  
+  /**
+   * Input several words to the hash.
+   * @param {bitArray|String} data the data to hash.
+   * @return this
+   */
+  update: function (data) {
+    if (typeof data === "string") {
+      data = sjcl.codec.utf8String.toBits(data);
+    }
+    var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data),
+        ol = this._length,
+        nl = this._length = ol + sjcl.bitArray.bitLength(data);
+    for (i = 512+ol & -512; i <= nl; i+= 512) {
+      this._block(b.splice(0,16));
+    }
+    return this;
+  },
+  
+  /**
+   * Complete hashing and output the hash value.
+   * @return {bitArray} The hash value, an array of 16 big-endian words.
+   */
+  finalize:function () {
+    var i, b = this._buffer, h = this._h;
+
+    // Round out and push the buffer
+    b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]);
+    
+    // Round out the buffer to a multiple of 16 words, less the 2 length words.
+    for (i = b.length + 2; i & 15; i++) {
+      b.push(0);
+    }
+    
+    // append the length
+    b.push(Math.floor(this._length / 0x100000000));
+    b.push(this._length | 0);
+
+    while (b.length) {
+      this._block(b.splice(0,16));
+    }
+
+    this.reset();
+    return h;
+  },
+
+  /**
+   * The SHA-256 initialization vector, to be precomputed.
+   * @private
+   */
+  _init:[],
+  /*
+  _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
+  */
+  
+  /**
+   * The SHA-256 hash key, to be precomputed.
+   * @private
+   */
+  _key:[],
+  /*
+  _key:
+    [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+     0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+     0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+     0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+     0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+     0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+     0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+     0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
+  */
+
+
+  /**
+   * Function to precompute _init and _key.
+   * @private
+   */
+  _precompute: function () {
+    var i = 0, prime = 2, factor;
+
+    function frac(x) { return (x-Math.floor(x)) * 0x100000000 | 0; }
+
+    outer: for (; i<64; prime++) {
+      for (factor=2; factor*factor <= prime; factor++) {
+        if (prime % factor === 0) {
+          // not a prime
+          continue outer;
+        }
+      }
+      
+      if (i<8) {
+        this._init[i] = frac(Math.pow(prime, 1/2));
+      }
+      this._key[i] = frac(Math.pow(prime, 1/3));
+      i++;
+    }
+  },
+  
+  /**
+   * Perform one cycle of SHA-256.
+   * @param {bitArray} words one block of words.
+   * @private
+   */
+  _block:function (words) {  
+    var i, tmp, a, b,
+      w = words.slice(0),
+      h = this._h,
+      k = this._key,
+      h0 = h[0], h1 = h[1], h2 = h[2], h3 = h[3],
+      h4 = h[4], h5 = h[5], h6 = h[6], h7 = h[7];
+
+    /* Rationale for placement of |0 :
+     * If a value can overflow is original 32 bits by a factor of more than a few
+     * million (2^23 ish), there is a possibility that it might overflow the
+     * 53-bit mantissa and lose precision.
+     *
+     * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
+     * propagates around the loop, and on the hash state h[].  I don't believe
+     * that the clamps on h4 and on h0 are strictly necessary, but it's close
+     * (for h4 anyway), and better safe than sorry.
+     *
+     * The clamps on h[] are necessary for the output to be correct even in the
+     * common case and for short inputs.
+     */
+    for (i=0; i<64; i++) {
+      // load up the input word for this round
+      if (i<16) {
+        tmp = w[i];
+      } else {
+        a   = w[(i+1 ) & 15];
+        b   = w[(i+14) & 15];
+        tmp = w[i&15] = ((a>>>7  ^ a>>>18 ^ a>>>3  ^ a<<25 ^ a<<14) + 
+                         (b>>>17 ^ b>>>19 ^ b>>>10 ^ b<<15 ^ b<<13) +
+                         w[i&15] + w[(i+9) & 15]) | 0;
+      }
+      
+      tmp = (tmp + h7 + (h4>>>6 ^ h4>>>11 ^ h4>>>25 ^ h4<<26 ^ h4<<21 ^ h4<<7) +  (h6 ^ h4&(h5^h6)) + k[i]); // | 0;
+      
+      // shift register
+      h7 = h6; h6 = h5; h5 = h4;
+      h4 = h3 + tmp | 0;
+      h3 = h2; h2 = h1; h1 = h0;
+
+      h0 = (tmp +  ((h1&h2) ^ (h3&(h1^h2))) + (h1>>>2 ^ h1>>>13 ^ h1>>>22 ^ h1<<30 ^ h1<<19 ^ h1<<10)) | 0;
+    }
+
+    h[0] = h[0]+h0 | 0;
+    h[1] = h[1]+h1 | 0;
+    h[2] = h[2]+h2 | 0;
+    h[3] = h[3]+h3 | 0;
+    h[4] = h[4]+h4 | 0;
+    h[5] = h[5]+h5 | 0;
+    h[6] = h[6]+h6 | 0;
+    h[7] = h[7]+h7 | 0;
+  }
+};
+
+

+ 60 - 0
core/sjcl.js

@@ -0,0 +1,60 @@
+/** @fileOverview Javascript cryptography implementation.
+ *
+ * Crush to remove comments, shorten variable names and
+ * generally reduce transmission size.
+ *
+ * @author Emily Stark
+ * @author Mike Hamburg
+ * @author Dan Boneh
+ */
+
+"use strict";
+/*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */
+/*global document, window, escape, unescape */
+
+/** @namespace The Stanford Javascript Crypto Library, top-level namespace. */
+var sjcl = {
+  /** @namespace Symmetric ciphers. */
+  cipher: {},
+
+  /** @namespace Hash functions.  Right now only SHA256 is implemented. */
+  hash: {},
+  
+  /** @namespace Block cipher modes of operation. */
+  mode: {},
+
+  /** @namespace Miscellaneous.  HMAC and PBKDF2. */
+  misc: {},
+  
+  /**
+   * @namespace Bit array encoders and decoders.
+   *
+   * @description
+   * The members of this namespace are functions which translate between
+   * SJCL's bitArrays and other objects (usually strings).  Because it
+   * isn't always clear which direction is encoding and which is decoding,
+   * the method names are "fromBits" and "toBits".
+   */
+  codec: {},
+  
+  /** @namespace Exceptions. */
+  exception: {
+    /** @class Ciphertext is corrupt. */
+    corrupt: function(message) {
+      this.toString = function() { return "CORRUPT: "+this.message; };
+      this.message = message;
+    },
+    
+    /** @class Invalid parameter. */
+    invalid: function(message) {
+      this.toString = function() { return "INVALID: "+this.message; };
+      this.message = message;
+    },
+    
+    /** @class Bug or missing feature in SJCL. */
+    bug: function(message) {
+      this.toString = function() { return "BUG: "+this.message; };
+      this.message = message;
+    }
+  }
+};

BIN
demo/alpha-arrow.png


+ 179 - 0
demo/example.css

@@ -0,0 +1,179 @@
+* {
+  margin: 0px;
+  padding: 0px;
+  font-family: Arial, Helvetica, FreeSans, sans;
+}
+
+h1 {
+  text-align: center;
+  background: #eee;
+  padding: 5px;
+  margin-bottom: 0.6em;
+  font-size: 1.5em;
+}
+
+.header {
+  width: 650px;
+  margin: 0px auto 1em;
+}
+
+p+p {
+  margin-top: 1em;
+}
+
+.explanation {
+  color: #555;
+  margin-top: 0.3em;
+}
+
+.section+.section, .explanation+.section {
+  margin-top: 1.5em;
+}
+
+.hex {
+  text-transform: uppercase;
+}
+
+.hex, .base64, #ciphertext {
+  font-family: 'Courier', mono;
+}
+
+.wide, textarea {
+  width: 100%;
+  margin: 0px -4px;
+  font-size: inherit;
+  text-align: left;
+}
+
+textarea+*, .wide+* {
+  margin-top: 0.3em;
+}
+
+/* bulk object placement */
+#theForm {
+  position: relative;
+  width: 940px;
+  margin: 0px auto;
+  font-size: 0.8em;
+}
+
+.column {
+  top: 0px;
+  width: 300px;
+}
+
+.box {
+  border: 2px solid #999;
+  padding: 7px;
+  margin-bottom: 20px;
+  -moz-border-radius: 7px;
+  -webkit-border-radius: 7px;
+}
+
+#cmode { position: absolute; left: 640px; }
+#ctexts { position: absolute; left: 320px; }
+
+.floatright {
+  float: right;
+  text-align: right;
+}
+
+a {
+  cursor: pointer;
+  color: #282;
+}
+
+a.random, #buttons a { text-decoration: none; }
+a.random:hover, a.random:focus { text-decoration: underline; }
+
+h2 {
+  margin: -7px -7px 3px -7px;
+  text-align: center;
+  font-size: 1.2em;
+  color: white;
+  background: #999;
+}
+
+#pplaintext { border-color: #f65; }
+#pplaintext h2 { background: #f65; }
+
+#ppassword { border-color: #4a4; }
+#ppassword h2 { background: #4a4; }
+
+#pciphertext { border-color: #78f; }
+#pciphertext h2 { background: #78f; }
+
+#buttons { text-align: center; margin-top: -20px; }
+
+a#doPbkdf2, a#encrypt, a#decrypt {
+  display: inline-block;
+  text-align: center;
+  height: 43px;
+  padding-top: 20px;
+  width: 50px;
+  background: url('alpha-arrow.png') no-repeat bottom center;
+  vertical-align: middle;
+  border: none;
+  color: white;
+  overflow: hidden;
+}
+
+.turnDown {
+  display: inline-block;
+  padding-bottom: 3px;
+  -moz-transform: rotate(90deg);	
+  -webkit-transform: rotate(90deg);	
+  transform: rotate(90deg);
+  background-color: inherit;
+}
+
+.turnUp {
+  display: inline-block;
+  padding-bottom: 3px;
+  -moz-transform: rotate(-90deg);	
+  -webkit-transform: rotate(-90deg);	
+  transform: rotate(-90deg);
+  background-color: inherit;	
+}
+
+.buttons a.disabled {
+  background-color: #ccc ! important;
+  cursor: inherit ! important;
+}
+
+a#encrypt { background-color: #f65; margin-bottom: 2px; }
+a#encrypt:hover, a#encrypt:focus { background-color: #f76; }
+a#encrypt:active { background-color: #f87; }
+
+a#decrypt {
+  height: 36px;
+  padding-top: 27px;
+  background: url('alpha-arrow.png') no-repeat top center;
+  background-color: #78f;
+  margin-top: 2px;
+}
+a#decrypt:hover { background-color: #89f; }
+a#decrypt:focus { background-color: #89f; }
+a#decrypt:active { background-color: #9af; }
+
+#ppassword, #pkey, #pmode, #pplaintext, #pciphertext {
+  -moz-border-radius: 7px;
+  -webkit-border-radius: 7px;
+}
+input[type='text'], input[type='password'], textarea {
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+  font-size: inherit;
+  border: 1px solid #444;
+  padding: 3px;
+}
+
+input[type='text']:focus,  input[type='password']:focus, textarea:focus {
+  border-color: red;
+}
+
+input[type="radio"], input[type="checkbox"] {
+  position: relative;
+  top: 0.15em;
+  margin-right: -0.15em;
+}

+ 153 - 0
demo/example.js

@@ -0,0 +1,153 @@
+/* keep track of which salts have been used. */
+var form, usedIvs = {'':1}, usedSalts = {'':1};
+
+/* enter actions */
+var enterActions = {
+  password: doPbkdf2,
+  salt: doPbkdf2,
+  iter: doPbkdf2
+};
+
+function loaded() {
+  form = new formHandler('theForm', enterActions);
+  form._extendedKey = [];
+  sjcl.random.startCollectors();
+  document.getElementById("password").focus();
+}
+
+/* there's probaby a better way to tell the user something, but oh well */
+function error(x) {
+  alert(x);
+}
+
+/* compute PBKDF2 on the password. */
+function doPbkdf2(decrypting) {
+  var v = form.get(), salt=v.salt, key, hex = sjcl.codec.hex.fromBits, p={},
+      password = v.password;
+  
+  p.iter = v.iter;
+  
+  if (password.length == 0) {
+    if (decrypting) { error("Can't decrypt: need a password!"); }
+    return;
+  }
+  
+  if (salt.length === 0 && decrypting) {
+    error("Can't decrypt: need a salt for PBKDF2!");
+    return;
+  }
+  
+  if (decrypting || !v.freshsalt || !usedSalts[v.salt]) {
+    p.salt = v.salt;
+  }
+  
+  p = sjcl.misc.cachedPbkdf2(password, p);
+  form._extendedKey = p.key;
+  v.key = p.key.slice(0, v.keysize/32);
+  v.salt = p.salt;
+  
+  form.set(v);
+  form.plaintext.el.select();
+}
+/* Encrypt a message */
+function doEncrypt() {
+  var v = form.get(), iv = v.iv, password = v.password, key = v.key, adata = v.adata, aes, plaintext=v.plaintext, rp = {}, ct, p;
+  
+  if (plaintext === '' && v.ciphertext.length) { return; }
+  if (key.length == 0 && password.length == 0) {
+    error("need a password or key!");
+    return;
+  }
+  
+  p = { adata:v.adata,
+        iter:v.iter,
+        mode:v.mode,
+        ts:parseInt(v.tag),
+        ks:parseInt(v.keysize) };
+  if (!v.freshiv || !usedIvs[v.iv]) { iv:v.iv; }
+  if (!v.freshsalt || !usedSalts[v.salt]) { p.salt = v.salt; }
+  ct = sjcl.encrypt(password || key, plaintext, p, rp).replace(/,/g,",\n");
+
+  v.iv = rp.iv;
+  usedIvs[rp.iv] = 1;
+  if (rp.salt) {
+    v.salt = rp.salt;
+    usedSalts[rp.salt] = 1;
+  }
+  v.key = rp.key;
+  
+  if (v.json) {
+    v.ciphertext = ct;
+    v.adata = '';
+  } else {
+    v.ciphertext = ct.match(/ct:"([^"]*)"/)[1]; //"
+  }
+  
+  v.plaintext = '';
+  
+  form.set(v);
+  form.ciphertext.el.select();
+}
+
+/* Decrypt a message */
+function doDecrypt() {
+  var v = form.get(), iv = v.iv, key = v.key, adata = v.adata, aes, ciphertext=v.ciphertext, rp = {};
+  
+  if (ciphertext.length === 0) { return; }
+  if (!v.password && !v.key.length) {
+    error("Can't decrypt: need a password or key!"); return;
+  }
+  
+  if (ciphertext.match("{")) {
+    /* it's jsonized */
+    try {
+      v.plaintext = sjcl.decrypt(v.password || v.key, ciphertext, {}, rp);
+    } catch(e) {
+      error("Can't decrypt: "+e);
+      return;
+    }
+    v.mode = rp.mode;
+    v.iv = rp.iv;
+    v.adata = rp.adata;
+    if (v.password) {
+      v.salt = rp.salt;
+      v.iter = rp.iter;
+      v.keysize = rp.ks;
+      v.tag = rp.ts;
+    }
+    v.key = rp.key;
+    v.ciphertext = "";
+    document.getElementById('plaintext').select();
+  } else {
+    /* it's raw */
+    ciphertext = sjcl.codec.base64.toBits(ciphertext);
+    if (iv.length === 0) {
+      error("Can't decrypt: need an IV!"); return;
+    }
+    if (key.length === 0) {
+      if (v.password.length) {
+        doPbkdf2(true);
+        key = v.key;
+      }
+    }
+    aes = new sjcl.cipher.aes(key);
+    
+    try {
+      v.plaintext = sjcl.codec.utf8String.fromBits(sjcl.mode[v.mode].decrypt(aes, ciphertext, iv, v.adata, v.tag));
+      v.ciphertext = "";
+      document.getElementById('plaintext').select();
+    } catch (e) {
+      error("Can't decrypt: " + e);
+    }
+  }
+  form.set(v);
+}
+
+function extendKey(size) {
+  form.key.set(form._extendedKey.slice(0,size));
+}
+
+function randomize(field, words, paranoia) {
+  form[field].set(sjcl.random.randomWords(words, paranoia));
+  if (field == 'salt') { form.key.set([]); }
+}

+ 137 - 0
demo/form.js

@@ -0,0 +1,137 @@
+/* Hackish form handling system. */
+function hasClass(e, cl) {
+  return (" "+e.className+" ").match(" "+cl+" ");
+}
+
+function stopPropagation(e) {
+  e.preventDefault && e.preventDefault();
+  e.cancelBubble = true;
+}
+
+/* proxy for a form object, with appropriate encoder/decoder */
+function formElement(el) {
+  this.el = el;
+}
+formElement.prototype = {
+  get: function() {
+    var el = this.el;
+    if (el.type == "checkbox") {
+      return el.checked;
+    } else if (hasClass(el, "numeric")) {
+      return parseInt(el.value);
+    } else if (hasClass(el, "hex")) {
+      return sjcl.codec.hex.toBits(el.value);
+    } else if (hasClass(el, "base64")) {
+      return sjcl.codec.base64.toBits(el.value);
+    } else {
+      return el.value;
+    }
+  },
+  
+  set: function(x) {
+    var el = this.el;
+    if (el.type == "checkbox") {
+      el.checked = x; return;
+    } else if (hasClass(el, "hex")) {
+      if (typeof x !== 'string') {
+        x = sjcl.codec.hex.fromBits(x);
+      }
+      x = x.toUpperCase().replace(/ /g,'').replace(/(.{8})/g, "$1 ").replace(/ $/, '');
+    } else if (hasClass(el, "base64")) {
+      if (typeof x !== 'string') {
+        x = sjcl.codec.base64.fromBits(x);
+      }
+      x = x.replace(/\s/g,'').replace(/(.{32})/g, "$1\n").replace(/\n$/, '');
+    }
+    el.value = x;
+  }
+}
+
+function radioGroup(name) {
+  this.name = name;
+}
+radioGroup.prototype = {
+  get: function() {
+    var els = document.getElementsByName(this.name), i;
+    for (i=0; i<els.length; i++) {
+      if (els[i].checked) {
+        return els[i].value;
+      }
+    }
+  },
+  
+  set: function(x) {
+    var els = document.getElementsByName(this.name), i;
+    for (i=0; i<els.length; i++) {
+      els[i].checked = (els[i].value == x);
+    }
+  }
+}
+
+function formHandler(formName, enterActions) {
+  var i, els = [], tmp, name;
+  this._elNames = [];
+  
+  tmp = document.getElementById(formName).getElementsByTagName('input');
+  for (i=0; i<tmp.length; i++) { els.push(tmp[i]); }
+  
+  tmp = document.getElementById(formName).getElementsByTagName('textarea');
+  for (i=0; i<tmp.length; i++) { els.push(tmp[i]); }
+  
+  for (i=0; i<els.length; i++) {
+    name = els[i].name  || els[i].id;
+    
+    /* enforce numeric properties of element */
+    els[i].onkeypress = (function(e) {
+      return function(ev) {
+        ev = ev || window.event;
+        var key = ev.keyCode || ev.which,
+            keyst = String.fromCharCode(ev.charCode || ev.keyCode),
+            ente = enterActions[e.name||e.id];
+        
+        if (ev.ctrlKey || ev.metaKey) {
+          return;
+        }
+        
+        (key == 13) && ente && ente();
+        
+        if (hasClass(e, 'numeric') && ev.charCode && !keyst.match(/[0-9]/)) {
+          stopPropagation(ev); return false;
+        } else if (hasClass(e, 'hex') && ev.charCode && !keyst.match(/[0-9a-fA-F ]/)) {
+          stopPropagation(ev); return false;
+        }
+      }
+    })(els[i]);
+    
+    if (els[i].type == 'radio') {
+      if (this[name] === undefined) {
+        this[name] = new radioGroup(name);
+        this._elNames.push(name);
+      }
+    } else {
+      /* code to get the value of an element */
+      this[name] = new formElement(els[i]);
+      this._elNames.push(name);
+    }
+  }
+}
+
+formHandler.prototype = {
+  get:function() {
+    var i, out = {}, en = this._elNames;
+    for (i=0; i<en.length; i++) {
+      out[en[i]] = this[en[i]].get();
+    }
+    return out;
+  },
+  
+  set:function(o) {
+    var i;
+    for (i in o) {
+      if (o.hasOwnProperty(i) && this.hasOwnProperty(i)) {
+        this[i].set(o[i]);
+      }
+    }
+  }
+}
+

+ 208 - 0
demo/index.html

@@ -0,0 +1,208 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+        
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>SJCL demo</title>
+  <link rel="stylesheet" type="text/css" href="example.css"/>
+  <script type="text/javascript" src="../sjcl.js"></script>
+  <script type="text/javascript" src="form.js"></script>
+  <script type="text/javascript" src="example.js"></script>
+</head>
+<body onload="loaded()">
+  <h1>SJCL demo</h1>
+
+  <div class="header">
+  <p>This page is a demo of the Stanford Javascript Crypto Library. To get started, just type in a password in the left pane and a secret message in the middle pane, then click "encrypt". Encryption takes place in your browser and we never see the plaintext.</p>
+  
+  <p>SJCL has lots of other options, many of which are shown in the grey boxes.</p>
+  </div>
+
+  <form id="theForm" onsubmit="return false;">
+  <div class="column" id="ckey">
+    <!-- Password and pbkdf2 parameters -->
+    <div class="box" id="ppassword">
+      <h2>Password</h2>
+      <div class="section">
+        <label for="password">Password:</label>
+        <input type="password" class="wide" name="password" id="password" autocomplete="off" tabindex="1"/>
+        <p class="explanation">
+          Choose a strong, random password.
+        </p>
+      </div>
+    </div>
+    
+    <div class="box" id="pkey">
+      <h2>Key Derivation</h2>
+      <div class="section">
+        <div>
+          <label for="salt"">Salt:</label>
+          <a class="random floatright" href="javascript:randomize('salt',2,0)">random</a>
+        </div>
+        <input type="text" id="salt" class="wide hex" autocomplete="off" size="17" maxlength="35"/>
+        <input type="checkbox" name="freshsalt" id="freshsalt" autocomplete="off" checked="checked"/>
+        <label for="freshsalt">Use fresh random salt for each new password</label>
+        <p class="explanation">
+          Salt adds more variability to your key, and prevents attackers
+          from using <a href="http://en.wikipedia.org/wiki/Rainbow_table">rainbow tables</a> to attack it.
+        </p>     
+      </div>
+
+      <div class="section">
+        <label for="iter">Strengthen by a factor of:</label>
+        <input type="text" name="iter" id="iter" value="1000" class="numeric" size="5" maxlength="5" autocomplete="off"/>
+        <p class="explanation">
+          Strengthening makes it slower to compute the key corresponding to your
+          password.  This makes it take much longer for an attacker to guess it.
+        </p>
+      </div>
+
+      <div class="section">
+        Key size:
+        <input type="radio" name="keysize" value="128" id="key128" checked="checked" autocomplete="off" onclick="extendKey(4)"/>
+        <label for="key128">128</label>
+        <input type="radio" name="keysize" value="192" id="key192" autocomplete="off" onclick="extendKey(6)"/>
+        <label for="key192">192</label>
+        <input type="radio" name="keysize" value="256" id="key256" autocomplete="off" onclick="extendKey(8)"/>
+        <label for="key256">256</label>
+        <p class="explanation">
+          128 bits should be secure enough, but you can generate a longer
+          key if you wish.
+        </p>
+      </div>
+
+      <!-- cipher key -->
+      <div class="section">
+        <div>
+          <label for="key">Key:</label>
+          <!--
+          <a class="random floatright" href="javascript:randomizeKey()">random</a>
+          -->
+        </div>
+        <textarea id="key" name="key" class="hex" rows="2" autocomplete="off"></textarea>
+        <p class="explanation">
+          This key is computed from your password, salt and strengthening factor.  It
+          will be used internally by the cipher.  Instead of using a password, you can
+          enter a key here directly.  If you do, it should be 32, 48 or 64 hexadecimal
+          digits (128, 192 or 256 bits).
+        </p>
+      </div>
+     
+    </div>
+  </div>
+    
+    <!-- mode controls -->
+  <div class="column" id="cmode">
+    <div class="box">
+      <h2>Cipher Parameters</h2>
+      <p class="explanation">
+        SJCL encrypts your data with the <a href="http://en.wikipedia.org/wiki/Advanced_Encryption_Standard"><acronym title="Advanced Encryption Standard">AES</acronym></a> block cipher.
+      </p>
+      <div class="section">
+        Cipher mode:
+        <input type="radio" name="mode" value="ccm" id="ccm" checked="checked" autocomplete="off"/>
+        <label for="ccm"><acronym title="Counter mode with Cipher block chaining Message authentication code">CCM</acronym></label>
+        <input type="radio" name="mode" value="ocb2" id="ocb2" autocomplete="off"/>
+        <label for="ocb2"><acronym title="Offset CodeBook mode, version 2.0">OCB2</acronym></label>
+        <p class="explanation">
+          The cipher mode is a standard for how to use AES and other
+          algorithms to encrypt and authenticate your message.
+          <a href="http://en.wikipedia.org/wiki/OCB_mode">OCB2 mode</a>
+          is slightly faster and has more features, but
+          <a href="http://en.wikipedia.org/wiki/CCM_mode">CCM mode</a> has wider
+          support because it is not patented.
+        </p>
+      </div>
+      
+      <div class="section">
+        <div>
+          <label for="iv">Initialization vector:</label>
+          <a class="random floatright" href="javascript:randomize('iv',4,0)">random</a>
+        </div>
+        <input type="text" name="iv" id="iv" class="wide hex" size="32" maxlength="35" autocomplete="off"/>
+        <input type="checkbox" id="freshiv" autocomplete="off" checked="checked"/>
+        <label for="freshiv">Choose a new random IV for every message.</label>
+        <p class="explanation">
+          The IV needs to be different for every message you send.  It adds
+          randomness to your message, so that the same message will look
+          different each time you send it.
+        </p>
+        <p class="explanation">
+          Be careful: CCM mode doesn't use
+          the whole IV, so changing just part of it isn't enough.
+        </p>
+      </div>
+      
+      <div class="section">
+        Authentication strength:
+        <input type="radio" name="tag" value="64" id="tag64" autocomplete="off" checked="checked"/>
+        <label for="tag64">64</label>
+        <input type="radio" name="tag" value="96" id="tag96" autocomplete="off"/>
+        <label for="tag96">96</label>
+        <input type="radio" name="tag" value="128" id="tag128" autocomplete="off"/>
+        <label for="tag128">128</label>
+        <p class="explanation">
+          SJCL adds a an authentication tag to your message to make sure
+          nobody changes it.  The longer the authentication tag, the harder it is
+          for somebody to change your encrypted message without you noticing.  64
+          bits is probably enough.
+        </p>
+      </div>
+      
+      <div class="section">
+        <input type="checkbox" name="json" id="json" autocomplete="off" checked="checked"/>
+        <label for="json">Send the parameters and authenticated data along
+          with the message.</label>
+         <p class="explanation">
+           These parameters are required to decrypt your message later.  If the
+           person you're sending the message to knows them, you don't need to send
+           them so your message will be shorter.
+         </p>
+         <p class="explanation">
+           Default parameters won't be sent.  Your password won't be sent, either.
+           The salt and iv will be encoded in base64 instead of hex, so they'll
+           look different from what's in the box.
+         </p>
+      </div>
+    </div>
+  </div>
+  
+  <div class="column" id="ctexts">
+    <div id="pplaintext" class="box">
+      <h2>Plaintext</h2>
+      <div class="section">
+        <label for="plaintext">Secret message:</label>
+        <textarea id="plaintext" autocomplete="off" rows="5" tabindex="2"></textarea>
+        <div class="explanation">
+          This message will be encrypted, so that nobody can read it or change it
+          without your password.
+        </div>
+      </div>  
+   
+      <div class="section">
+        <label for="adata">Authenticated data:</label>
+        <textarea id="adata" autocomplete="off" tabindex="3"></textarea>
+        <div class="explanation">
+          This auxilliary message isn't secret, but its integrity will be checked
+          along with the integrity of the message.
+        </div>
+      </div>
+    </div>
+    
+    <div id="buttons">
+      <a href="javascript:doEncrypt()" id="encrypt" tabindex="4"><span class="turnDown">encrypt</span></a>
+      <a href="javascript:doDecrypt()" id="decrypt" tabindex="6"><span class="turnUp">decrypt</span></a>
+    </div>
+    
+    <div id="pciphertext" class="box">
+      <h2>Ciphertext</h2>
+      <label for="ciphertext">Ciphertext:</label>
+      <textarea id="ciphertext" autocomplete="off" rows="7" tabindex="5"></textarea>
+      <div class="explanation">
+        Your message, encrypted and authenticated so that nobody can read it
+        or change it without your password.
+      </div>
+    </div>
+  </form>
+</body>
+</html>

+ 183 - 0
jsdoc_toolkit-2.3.3-beta/README.txt

@@ -0,0 +1,183 @@
+======================================================================
+
+DESCRIPTION:
+
+This is the source code for JsDoc Toolkit, an automatic documentation
+generation tool for JavaScript. It is written in JavaScript and is run
+from a command line (or terminal) using Java and Mozilla's Rhino
+JavaScript runtime engine.
+
+Using this tool you can automatically turn JavaDoc-like comments in
+your JavaScript source code into published output files, such as HTML
+or XML.
+
+For more information, to report a bug, or to browse the technical
+documentation for this tool please visit the official JsDoc Toolkit
+project homepage at http://code.google.com/p/jsdoc-toolkit/
+
+For the most up-to-date documentation on JsDoc Toolkit see the 
+official wiki at http://code.google.com/p/jsdoc-toolkit/w/list
+
+======================================================================
+
+REQUIREMENTS:
+
+JsDoc Toolkit is known to work with:
+java version "1.6.0_03"
+Java(TM) SE Runtime Environment (build 1.6.0_03-b05)
+on Windows XP,
+and java version "1.5.0_19"
+Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_19-b02-304)
+on Mac OS X 10.5.
+
+Other versions of java may or may not work with JsDoc Toolkit.
+
+======================================================================
+
+USAGE:
+
+Running JsDoc Toolkit requires you to have Java installed on your
+computer. For more information see http://www.java.com/getjava/
+
+Before running the JsDoc Toolkit app you should change your current
+working directory to the jsdoc-toolkit folder. Then follow the
+examples below, or as shown on the project wiki.
+
+On a computer running Windows a valid command line to run JsDoc
+Toolkit might look like this:
+
+> java -jar jsrun.jar app\run.js -a -t=templates\jsdoc mycode.js
+
+On Mac OS X or Linux the same command would look like this:
+
+$ java -jar jsrun.jar app/run.js -a -t=templates/jsdoc mycode.js
+
+The above assumes your current working directory contains jsrun.jar,
+the "app" and "templates" subdirectories from the standard JsDoc
+Toolkit distribution and that the relative path to the code you wish
+to document is "mycode.js".
+
+The output documentation files will be saved to a new directory named
+"out" (by default) in the current directory, or if you specify a
+-d=somewhere_else option, to the somewhere_else directory.
+
+For help (usage notes) enter this on the command line:
+
+$ java -jar jsrun.jar app/run.js --help
+
+More information about the various command line options used by JsDoc
+Toolkit are available on the project wiki.
+
+======================================================================
+
+RUNNING VIA SHELL SCRIPT
+
+Avi Deitcher has contributed the file jsrun.sh with the following usage notes:
+
+A script to simplify running jsdoc from the command-line, especially when
+running from within a development or build environment such as ant.
+
+Normally, to run jsdoc, you need a command-line as the following:
+java -Djsdoc.dir=/some/long/dir/path/to/jsdoc -jar
+/some/long/dir/path/to/jsdoc/jsrun.jar /some/long/dir/path/to/jsdoc/app/run.js
+-t=template -r=4 /some/long/dir/path/to/my/src/code
+
+This can get tedious to redo time and again, and difficult to use from within a build environment.
+
+To simplify the process, jsrun.sh will automatically run this path, as well as passing through any arguments.
+
+Usage: jsrun.sh <run.js arguments>
+
+All <run.js arguments> will be passed through.
+Additionally, jsrun.sh will take the following actions:
+1) If the environment variable JSDOCDIR is set, it will add
+"-Djsdoc.dir=$JSDOCDIR" to the command-line
+2) If the environment variable JSDOCTEMPLATEDIR is set, it will add
+"-Djsdoc.template.dir=$JSDOCTEMPLATEDIR" to the command-line
+3) java with the appropriate path to jsrun.jar and run.js will be instantiated
+
+If not variables are set, it is assumed that the path to jsrun.jar and app/ is in the current working directory.
+
+Example:
+# jsrun.sh ./src/
+Assuming JSDOCDIR=/some/path/to/my/jsdoc will cause the following command to
+execute:
+java -Djsdoc.dir=/some/path/to/my/jsdoc -jar /some/path/to/my/jsdoc/jsrun.jar
+/some/path/to/my/jsdoc/app/run.js ./src/
+
+======================================================================
+
+TESTING:
+
+To run the suite of unit tests included with JsDoc Toolkit enter this
+on the command line:
+
+$ java -jar jsrun.jar app/run.js -T
+
+To see a dump of the internal data structure that JsDoc Toolkit has
+built from your source files use this command:
+
+$ java -jar jsrun.jar app/run.js mycode.js -Z
+
+======================================================================
+
+LICENSE:
+
+JSDoc.pm
+
+This project is based on the JSDoc.pm tool, created by Michael
+Mathews and Gabriel Reid. More information on JsDoc.pm can
+be found on the JSDoc.pm homepage: http://jsdoc.sourceforge.net/
+
+Complete documentation on JsDoc Toolkit can be found on the project
+wiki at http://code.google.com/p/jsdoc-toolkit/w/list
+
+Rhino
+
+Rhino (JavaScript in Java) is open source and licensed by Mozilla
+under the MPL 1.1 or later/GPL 2.0 or later licenses, the text of
+which is available at http://www.mozilla.org/MPL/
+
+You can obtain the source code for Rhino from the Mozilla web site at
+http://www.mozilla.org/rhino/download.html
+
+JsDoc Toolkit is a larger work that uses the Rhino JavaScript engine
+but is not derived from it in any way. The Rhino library is used 
+without modification and without any claims whatsoever.
+
+The Rhino Debugger
+
+You can obtain more information about the Rhino Debugger from the 
+Mozilla web site at http://www.mozilla.org/rhino/debugger.html
+
+JsDoc Toolkit is a larger work that uses the Rhino Debugger but
+is not derived from it in any way. The Rhino Debugger is used
+without modification and without any claims whatsoever.
+
+JsDoc Toolkit
+
+All code specific to JsDoc Toolkit are free, open source and licensed
+for use under the X11/MIT License.
+
+JsDoc Toolkit is Copyright (c)2009 Michael Mathews <micmath@gmail.com>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms below.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions: The above copyright notice and this
+permission notice must be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 33 - 0
jsdoc_toolkit-2.3.3-beta/app/frame.js

@@ -0,0 +1,33 @@
+IO.include("frame/Opt.js");
+IO.include("frame/Chain.js");
+IO.include("frame/Link.js");
+IO.include("frame/String.js");
+IO.include("frame/Hash.js");
+IO.include("frame/Namespace.js");
+//IO.include("frame/Reflection.js");
+
+/** A few helper functions to make life a little easier. */
+
+function defined(o) {
+	return (o !== undefined);
+}
+
+function copy(o) { // todo check for circular refs
+	if (o == null || typeof(o) != 'object') return o;
+	var c = new o.constructor();
+	for(var p in o)	c[p] = copy(o[p]);
+	return c;
+}
+
+function isUnique(arr) {
+	var l = arr.length;
+	for(var i = 0; i < l; i++ ) {
+		if (arr.lastIndexOf(arr[i]) > i) return false;
+	}
+	return true;
+}
+
+/** Returns the given string with all regex meta characters backslashed. */
+RegExp.escapeMeta = function(str) {
+	return str.replace(/([$^\\\/()|?+*\[\]{}.-])/g, "\\$1");
+}

+ 102 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/Chain.js

@@ -0,0 +1,102 @@
+/**@constructor*/
+function ChainNode(object, link) {
+	this.value = object;
+	this.link = link; // describes this node's relationship to the previous node
+}
+
+/**@constructor*/
+function Chain(valueLinks) {
+	this.nodes = [];
+	this.cursor = -1;
+	
+	if (valueLinks && valueLinks.length > 0) {
+		this.push(valueLinks[0], "//");
+		for (var i = 1, l = valueLinks.length; i < l; i+=2) {
+			this.push(valueLinks[i+1], valueLinks[i]);
+		}
+	}
+}
+
+Chain.prototype.push = function(o, link) {
+	if (this.nodes.length > 0 && link) this.nodes.push(new ChainNode(o, link));
+	else this.nodes.push(new ChainNode(o));
+}
+
+Chain.prototype.unshift = function(o, link) {
+	if (this.nodes.length > 0 && link) this.nodes[0].link = link;
+	this.nodes.unshift(new ChainNode(o));
+	this.cursor++;
+}
+
+Chain.prototype.get = function() {
+	if (this.cursor < 0 || this.cursor > this.nodes.length-1) return null;
+	return this.nodes[this.cursor];
+}
+
+Chain.prototype.first = function() {
+	this.cursor = 0;
+	return this.get();
+}
+
+Chain.prototype.last = function() {
+	this.cursor = this.nodes.length-1;
+	return this.get();
+}
+
+Chain.prototype.next = function() {
+	this.cursor++;
+	return this.get();
+}
+
+Chain.prototype.prev = function() {
+	this.cursor--;
+	return this.get();
+}
+
+Chain.prototype.toString = function() {
+	var string = "";
+	for (var i = 0, l = this.nodes.length; i < l; i++) {
+		if (this.nodes[i].link) string += " -("+this.nodes[i].link+")-> ";
+		string += this.nodes[i].value.toString();
+	}
+	return string;
+}
+
+Chain.prototype.joinLeft = function() {
+	var result = "";
+	for (var i = 0, l = this.cursor; i < l; i++) {
+		if (result && this.nodes[i].link) result += this.nodes[i].link;
+		result += this.nodes[i].value.toString();
+	}
+	return result;
+}
+
+
+/* USAGE:
+
+var path = "one/two/three.four/five-six";
+var pathChain = new Chain(path.split(/([\/.-])/));
+print(pathChain);
+
+var lineage = new Chain();
+lineage.push("Port");
+lineage.push("Les", "son");
+lineage.push("Dawn", "daughter");
+lineage.unshift("Purdie", "son");
+
+print(lineage);
+
+// walk left
+for (var node = lineage.last(); node !== null; node = lineage.prev()) {
+	print("< "+node.value);
+}
+
+// walk right
+var node = lineage.first()
+while (node !== null) {
+	print(node.value);
+	node = lineage.next();
+	if (node && node.link) print("had a "+node.link+" named");
+}
+
+*/

+ 144 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/Dumper.js

@@ -0,0 +1,144 @@
+/**
+ * @class
+<pre>
+This is a lightly modified version of Kevin Jones' JavaScript
+library Data.Dump. To download the original visit:
+    <a href="http://openjsan.org/doc/k/ke/kevinj/Data/Dump/">http://openjsan.org/doc/k/ke/kevinj/Data/Dump/</a>
+
+AUTHORS
+
+The Data.Dump JavaScript module is written by Kevin Jones 
+(kevinj@cpan.org), based on Data::Dump by Gisle Aas (gisle@aas.no),
+based on Data::Dumper by Gurusamy Sarathy (gsar@umich.edu).
+
+COPYRIGHT
+
+Copyright 2007 Kevin Jones. Copyright 1998-2000,2003-2004 Gisle Aas.
+Copyright 1996-1998 Gurusamy Sarathy.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the Perl Artistic License
+
+See http://www.perl.com/perl/misc/Artistic.html
+</pre>
+ * @static
+ */
+Dumper = {
+	/** @param [...] The objects to dump. */
+	dump: function () {
+	    if (arguments.length > 1)
+	        return this._dump(arguments);
+	    else if (arguments.length == 1)
+	        return this._dump(arguments[0]);
+	    else
+	        return "()";
+	},
+	
+	_dump: function (obj) {
+		if (typeof obj == 'undefined') return 'undefined';
+		var out;
+		if (obj.serialize) { return obj.serialize(); }
+		var type = this._typeof(obj);
+		if (obj.circularReference) obj.circularReference++;
+		switch (type) {
+			case 'circular':
+				out = "{ //circularReference\n}";
+				break;
+			case 'object':
+				var pairs = new Array;
+				
+				for (var prop in obj) {
+					if (prop != "circularReference" && obj.hasOwnProperty(prop)) { //hide inherited properties
+						pairs.push(prop + ': ' + this._dump(obj[prop]));
+					}
+				}
+	
+				out = '{' + this._format_list(pairs) + '}';
+				break;
+	
+			case 'string':
+				for (var prop in Dumper.ESC) {
+					if (Dumper.ESC.hasOwnProperty(prop)) {
+						obj = obj.replace(prop, Dumper.ESC[prop]);
+					}
+				}
+	
+			// Escape UTF-8 Strings
+				if (obj.match(/^[\x00-\x7f]*$/)) {
+					out = '"' + obj.replace(/\"/g, "\\\"").replace(/([\n\r]+)/g, "\\$1") + '"';
+				}
+				else {
+					out = "unescape('"+escape(obj)+"')";
+				}
+				break;
+	
+			case 'array':
+				var elems = new Array;
+	
+				for (var i=0; i<obj.length; i++) {
+					elems.push( this._dump(obj[i]) );
+				}
+	
+				out = '[' + this._format_list(elems) + ']';
+				break;
+	
+			case 'date':
+			// firefox returns GMT strings from toUTCString()...
+			var utc_string = obj.toUTCString().replace(/GMT/,'UTC');
+				out = 'new Date("' + utc_string + '")';
+				break;
+	
+			case 'element':
+				// DOM element
+				out = this._dump_dom(obj);
+				break;
+		
+				default:
+					out = obj;
+		}
+	
+		out = String(out).replace(/\n/g, '\n    ');
+		out = out.replace(/\n    (.*)$/,"\n$1");
+	
+		return out;
+	},
+	
+	_format_list: function (list) {
+		if (!list.length) return '';
+		var nl = list.toString().length > 60 ? '\n' : ' ';
+		return nl + list.join(',' + nl) + nl;
+    },
+    
+    _typeof: function (obj) {
+    	if (obj && obj.circularReference && obj.circularReference > 1) return 'circular';
+		if (Array.prototype.isPrototypeOf(obj)) return 'array';
+		if (Date.prototype.isPrototypeOf(obj)) return 'date';
+		if (typeof obj.nodeType != 'undefined') return 'element';
+		return typeof(obj);
+	},
+	
+	_dump_dom: function (obj) {
+		return '"' + Dumper.nodeTypes[obj.nodeType] + '"';
+	}
+};
+
+Dumper.ESC = {
+    "\t": "\\t",
+    "\n": "\\n",
+    "\f": "\\f"
+};
+
+Dumper.nodeTypes = {
+    1: "ELEMENT_NODE",
+    2: "ATTRIBUTE_NODE",
+    3: "TEXT_NODE",
+    4: "CDATA_SECTION_NODE",
+    5: "ENTITY_REFERENCE_NODE",
+    6: "ENTITY_NODE",
+    7: "PROCESSING_INSTRUCTION_NODE",
+    8: "COMMENT_NODE",
+    9: "DOCUMENT_NODE",
+    10: "DOCUMENT_TYPE_NODE",
+    11: "DOCUMENT_FRAGMENT_NODE",
+    12: "NOTATION_NODE"
+};

+ 84 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/Hash.js

@@ -0,0 +1,84 @@
+/**
+	@constructor
+	@example
+	var _index = new Hash();
+	_index.set("a", "apple");
+	_index.set("b", "blue");
+	_index.set("c", "coffee");
+
+	for (var p = _index.first(); p; p = _index.next()) {
+		print(p.key+" is for "+p.value);
+	}
+	
+ */
+var Hash = function() {
+	this._map = {};
+	this._keys = [];
+	this._vals = [];
+	this.reset();
+}
+
+Hash.prototype.set = function(k, v) {
+	if (k != "") {
+		this._keys.push(k);
+		this._map["="+k] = this._vals.length;
+		this._vals.push(v);
+	}
+}
+
+Hash.prototype.replace = function(k, k2, v) {
+	if (k == k2) return;
+
+	var offset = this._map["="+k];
+	this._keys[offset] = k2;
+	if (typeof v != "undefined") this._vals[offset] = v;
+	this._map["="+k2] = offset;
+	delete(this._map["="+k]);
+}
+
+Hash.prototype.drop = function(k) {
+	if (k != "") {
+		var offset = this._map["="+k];
+		this._keys.splice(offset, 1);
+		this._vals.splice(offset, 1);
+		delete(this._map["="+k]);
+		for (var p in this._map) {
+			if (this._map[p] >= offset) this._map[p]--;
+		}
+		if (this._cursor >= offset && this._cursor > 0) this._cursor--;
+	}
+}
+
+Hash.prototype.get = function(k) {
+	if (k != "") {
+		return this._vals[this._map["="+k]];
+	}
+}
+
+Hash.prototype.keys = function() {
+	return this._keys;
+}
+
+Hash.prototype.hasKey = function(k) {
+	if (k != "") {
+		return (typeof this._map["="+k] != "undefined");
+	}
+}
+
+Hash.prototype.values = function() {
+	return this._vals;
+}
+
+Hash.prototype.reset = function() {
+	this._cursor = 0;
+}
+
+Hash.prototype.first = function() {
+	this.reset();
+	return this.next();
+}
+
+Hash.prototype.next = function() {
+	if (this._cursor++ < this._keys.length)
+		return {key: this._keys[this._cursor-1], value: this._vals[this._cursor-1]};
+}

+ 171 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/Link.js

@@ -0,0 +1,171 @@
+/** Handle the creation of HTML links to documented symbols.
+	@constructor
+*/
+function Link() {
+	this.alias = "";
+	this.src = "";
+	this.file = "";
+	this.text = "";
+	this.innerName = "";
+	this.classLink = false;
+	this.targetName = "";
+	
+	this.target = function(targetName) {
+		if (defined(targetName)) this.targetName = targetName;
+		return this;
+	}
+	this.inner = function(inner) {
+		if (defined(inner)) this.innerName = inner;
+		return this;
+	}
+	this.withText = function(text) {
+		if (defined(text)) this.text = text;
+		return this;
+	}
+	this.toSrc = function(filename) {
+		if (defined(filename)) this.src = filename;
+		return this;
+	}
+	this.toSymbol = function(alias) {
+		if (defined(alias)) this.alias = new String(alias);
+		return this;
+	}
+	this.toClass = function(alias) {
+		this.classLink = true;
+		return this.toSymbol(alias);
+	}
+	this.toFile = function(file) {
+		if (defined(file)) this.file = file;
+		return this;
+	}
+	
+	this.toString = function() {
+		var linkString;
+		var thisLink = this;
+
+		if (this.alias) {
+			linkString = this.alias.replace(/(^|[^a-z$0-9_#.:^-])([|a-z$0-9_#.:^-]+)($|[^a-z$0-9_#.:^-])/i,
+				function(match, prematch, symbolName, postmatch) {
+					var symbolNames = symbolName.split("|");
+					var links = [];
+					for (var i = 0, l = symbolNames.length; i < l; i++) {
+						thisLink.alias = symbolNames[i];
+						links.push(thisLink._makeSymbolLink(symbolNames[i]));
+					}
+					return prematch+links.join("|")+postmatch;
+				}
+			);
+		}
+		else if (this.src) {
+			linkString = thisLink._makeSrcLink(this.src);
+		}
+		else if (this.file) {
+			linkString = thisLink._makeFileLink(this.file);
+		}
+
+		return linkString;
+	}
+}
+
+/** prefixed for hashes */
+Link.hashPrefix = "";
+
+/** Appended to the front of relative link paths. */
+Link.base = "";
+
+Link.symbolNameToLinkName = function(symbol) {
+	var linker = "";
+	if (symbol.isStatic) linker = ".";
+	else if (symbol.isInner) linker = "-";
+	
+	return Link.hashPrefix+linker+symbol.name;
+}
+
+Link.getSymbol= function(alias) {
+    var symbol= Link.symbolSet.getSymbol(alias);
+    
+    if (symbol)
+        return symbol;
+        
+    if ('#'!==alias.charAt(0) || !Link.currentSymbol)
+        return null;
+    
+    //  resolve relative name
+    var container= Link.currentSymbol;
+    
+    while (container)
+    {
+        symbol= Link.symbolSet.getSymbol(container.alias + alias);
+        if (symbol)
+            return symbol;
+        
+        //  No superclass
+        if (!container.augments.length)
+            return null;
+        
+        container= Link.symbolSet.getSymbol(container.augments[0].desc);
+    }
+    
+    return null;
+}
+
+/** Create a link to another symbol. */
+Link.prototype._makeSymbolLink = function(alias) {
+	var linkBase = Link.base+publish.conf.symbolsDir;
+	var linkTo = Link.getSymbol(alias);
+	var linkPath;
+	var target = (this.targetName)? " target=\""+this.targetName+"\"" : "";
+
+	// if there is no symbol by that name just return the name unaltered
+	if (!linkTo)
+	    return this.text || alias;
+	
+	// it's a symbol in another file
+	else {
+		if (!linkTo.is("CONSTRUCTOR") && !linkTo.isNamespace) { // it's a method or property
+			linkPath= (Link.filemap) ? Link.filemap[linkTo.memberOf] :
+				      escape(linkTo.memberOf) || "_global_";
+			if (linkTo.isEvent)
+				linkPath += publish.conf.ext + "#event:" + Link.symbolNameToLinkName(linkTo);
+			else
+				linkPath += publish.conf.ext + "#" + Link.symbolNameToLinkName(linkTo);
+		}
+		else {
+			linkPath = (Link.filemap)? Link.filemap[linkTo.alias] : escape(linkTo.alias);
+			linkPath += publish.conf.ext;// + (this.classLink? "":"#" + Link.hashPrefix + "constructor");
+		}
+		linkPath = linkBase + linkPath
+	}
+        
+	var linkText= this.text || alias;
+    
+	var link = {linkPath: linkPath, linkText: linkText, linkInner: (this.innerName? "#"+this.innerName : "")};
+	
+	if (typeof JSDOC.PluginManager != "undefined") {
+		JSDOC.PluginManager.run("onSymbolLink", link);
+	}
+	
+	return "<a href=\""+link.linkPath+link.linkInner+"\""+target+">"+link.linkText+"</a>";
+}
+
+/** Create a link to a source file. */
+Link.prototype._makeSrcLink = function(srcFilePath) {
+	var target = (this.targetName)? " target=\""+this.targetName+"\"" : "";
+		
+	// transform filepath into a filename
+	var srcFile = srcFilePath.replace(/\.\.?[\\\/]/g, "").replace(/[:\\\/]/g, "_");
+	var outFilePath = Link.base + publish.conf.srcDir + srcFile + publish.conf.ext;
+
+	if (!this.text) this.text = FilePath.fileName(srcFilePath);
+	return "<a href=\""+outFilePath+"\""+target+">"+this.text+"</a>";
+}
+
+/** Create a link to a source file. */
+Link.prototype._makeFileLink = function(filePath) {
+	var target = (this.targetName)? " target=\""+this.targetName+"\"" : "";
+		
+	var outFilePath =  Link.base + filePath;
+
+	if (!this.text) this.text = filePath;
+	return "<a href=\""+outFilePath+"\""+target+">"+this.text+"</a>";
+}

+ 10 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/Namespace.js

@@ -0,0 +1,10 @@
+_global_ = this;
+
+function Namespace(name, f) {
+	var n = name.split(".");
+	for (var o = _global_, i = 0, l = n.length; i < l; i++) {
+		o = o[n[i]] = o[n[i]] || {};
+	}
+	
+	if (f) f();
+}

+ 134 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/Opt.js

@@ -0,0 +1,134 @@
+/** @namespace */
+Opt = {
+	/**
+	 * Get commandline option values.
+	 * @param {Array} args Commandline arguments. Like ["-a=xml", "-b", "--class=new", "--debug"]
+	 * @param {object} optNames Map short names to long names. Like {a:"accept", b:"backtrace", c:"class", d:"debug"}.
+	 * @return {object} Short names and values. Like {a:"xml", b:true, c:"new", d:true}
+	 */
+	get: function(args, optNames) {
+		var opt = {"_": []}; // the unnamed option allows multiple values
+		for (var i = 0; i < args.length; i++) {
+			var arg = new String(args[i]);
+			var name;
+			var value;
+			if (arg.charAt(0) == "-") {
+				if (arg.charAt(1) == "-") { // it's a longname like --foo
+					arg = arg.substring(2);
+					var m = arg.split("=");
+					name = m.shift();
+					value = m.shift();
+					if (typeof value == "undefined") value = true;
+					
+					for (var n in optNames) { // convert it to a shortname
+						if (name == optNames[n]) {
+							name = n;
+						}
+					}
+				}
+				else { // it's a shortname like -f
+					arg = arg.substring(1);
+					var m = arg.split("=");
+					name = m.shift();
+					value = m.shift();
+					if (typeof value == "undefined") value = true;
+					
+					for (var n in optNames) { // find the matching key
+						if (name == n || name+'[]' == n) {
+							name = n;
+							break;
+						}
+					}
+				}
+				if (name.match(/(.+)\[\]$/)) { // it's an array type like n[]
+					name = RegExp.$1;
+					if (!opt[name]) opt[name] = [];
+				}
+				
+				if (opt[name] && opt[name].push) {
+					opt[name].push(value);
+				}
+				else {
+					opt[name] = value;
+				}
+			}
+			else { // not associated with any optname
+				opt._.push(args[i]);
+			}
+		}
+		return opt;
+	}
+}
+
+/*t:
+	plan(11, "Testing Opt.");
+	
+	is(
+		typeof Opt,
+		"object",
+		"Opt is an object."
+	);
+	
+	is(
+		typeof Opt.get,
+		"function",
+		"Opt.get is a function."
+	);
+	
+	var optNames = {a:"accept", b:"backtrace", c:"class", d:"debug", "e[]":"exceptions"};
+	var t_options = Opt.get(["-a=xml", "-b", "--class=new", "--debug", "-e=one", "-e=two", "foo", "bar"], optNames);
+	
+	is(
+		t_options.a,
+		"xml",
+		"an option defined with a short name can be accessed by its short name."
+	);
+	
+	is(
+		t_options.b,
+		true,
+		"an option defined with a short name and no value are true."
+	);
+	
+	is(
+		t_options.c,
+		"new",
+		"an option defined with a long name can be accessed by its short name."
+	);
+	
+	is(
+		t_options.d,
+		true,
+		"an option defined with a long name and no value are true."
+	);
+	
+	is(
+		typeof t_options.e,
+		"object",
+		"an option that can accept multiple values is defined."
+	);
+	
+	is(
+		t_options.e.length,
+		2,
+		"an option that can accept multiple values can have more than one value."
+	);
+	
+	is(
+		t_options.e[1],
+		"two",
+		"an option that can accept multiple values can be accessed as an array."
+	);
+	
+	is(
+		typeof t_options._,
+		"object",
+		"the property '_' is defined for unnamed options."
+	);
+	
+	is(
+		t_options._[0],
+		"foo",
+		"the property '_' can be accessed as an array."
+	);
+ */

+ 26 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/Reflection.js

@@ -0,0 +1,26 @@
+/**@constructor*/
+function Reflection(obj) {
+	this.obj = obj;
+}
+
+Reflection.prototype.getConstructorName = function() {
+	if (this.obj.constructor.name) return this.obj.constructor.name;
+	var src = this.obj.constructor.toSource();
+	var name = src.substring(name.indexOf("function")+8, src.indexOf('(')).replace(/ /g,'');
+	return name;
+}
+
+Reflection.prototype.getMethod = function(name) {
+	for (var p in this.obj) {
+		if (p == name && typeof(this.obj[p]) == "function") return this.obj[p];
+	}
+	return null;
+}
+
+Reflection.prototype.getParameterNames = function() {
+	var src = this.obj.toSource();
+	src = src.substring(
+		src.indexOf("(", 8)+1, src.indexOf(")")
+	);
+	return src.split(/, ?/);
+}

+ 93 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/String.js

@@ -0,0 +1,93 @@
+/**
+	@name String
+	@class Additions to the core string object.
+*/
+
+/** @author Steven Levithan, released as public domain. */
+String.prototype.trim = function() {
+	var str = this.replace(/^\s+/, '');
+	for (var i = str.length - 1; i >= 0; i--) {
+		if (/\S/.test(str.charAt(i))) {
+			str = str.substring(0, i + 1);
+			break;
+		}
+	}
+	return str;
+}
+/*t:
+	plan(6, "Testing String.prototype.trim.");
+	
+	var s = "   a bc   ".trim();
+	is(s, "a bc", "multiple spaces front and back are trimmed.");
+
+	s = "a bc\n\n".trim();
+	is(s, "a bc", "newlines only in back are trimmed.");
+	
+	s = "\ta bc".trim();
+	is(s, "a bc", "tabs only in front are trimmed.");
+	
+	s = "\n \t".trim();
+	is(s, "", "an all-space string is trimmed to empty.");
+	
+	s = "a b\nc".trim();
+	is(s, "a b\nc", "a string with no spaces in front or back is trimmed to itself.");
+	
+	s = "".trim();
+	is(s, "", "an empty string is trimmed to empty.");
+
+*/
+
+String.prototype.balance = function(open, close) {
+	var i = 0;
+	while (this.charAt(i) != open) {
+		if (i == this.length) return [-1, -1];
+		i++;
+	}
+	
+	var j = i+1;
+	var balance = 1;
+	while (j < this.length) {
+		if (this.charAt(j) == open) balance++;
+		if (this.charAt(j) == close) balance--;
+		if (balance == 0) break;
+		j++;
+		if (j == this.length) return [-1, -1];
+	}
+	
+	return [i, j];
+}
+/*t:
+	plan(16, "Testing String.prototype.balance.");
+	
+	var s = "{abc}".balance("{","}");
+	is(s[0], 0, "opener in first is found.");
+	is(s[1], 4, "closer in last is found.");
+	
+	s = "ab{c}de".balance("{","}");
+	is(s[0], 2, "opener in middle is found.");
+	is(s[1], 4, "closer in middle is found.");
+	
+	s = "a{b{c}de}f".balance("{","}");
+	is(s[0], 1, "nested opener is found.");
+	is(s[1], 8, "nested closer is found.");
+	
+	s = "{}".balance("{","}");
+	is(s[0], 0, "opener with no content is found.");
+	is(s[1], 1, "closer with no content is found.");
+	
+	s = "".balance("{","}");
+	is(s[0], -1, "empty string opener is -1.");
+	is(s[1], -1, "empty string closer is -1.");
+	
+	s = "{abc".balance("{","}");
+	is(s[0], -1, "opener with no closer returns -1.");
+	is(s[1], -1, "no closer returns -1.");
+	
+	s = "abc".balance("{","}");
+	is(s[0], -1, "no opener or closer returns -1 for opener.");
+	is(s[1], -1, "no opener or closer returns -1 for closer.");
+	
+	s = "a<bc}de".balance("<","}");
+	is(s[0], 1, "unmatching opener is found.");
+	is(s[1], 4, "unmatching closer is found.");
+*/

+ 129 - 0
jsdoc_toolkit-2.3.3-beta/app/frame/Testrun.js

@@ -0,0 +1,129 @@
+/**
+ * @fileOverview
+ * @name JsTestrun
+ * @author Michael Mathews micmath@gmail.com
+ * @url $HeadURL: http://jsdoc-toolkit.googlecode.com/svn/trunk/jsdoc-toolkit/app/frame/Testrun.js $
+ * @revision $Id: Testrun.js 418 2008-01-15 21:40:33Z micmath $
+ * @license <a href="http://en.wikipedia.org/wiki/MIT_License">X11/MIT License</a>
+ *          (See the accompanying README file for full details.)
+ */
+ 
+/**
+	Yet another unit testing tool for JavaScript.
+	@author Michael Mathews <a href="mailto:micmath@gmail.com">micmath@gmail.com</a>
+	@param {object} testCases Properties are testcase names, values are functions to execute as tests.
+*/
+function testrun(testCases) {
+	var ran = 0;
+	for (t in testCases) {
+		var result = testCases[t]();
+		ran++;
+	}
+	
+	return testrun.reportOut+"-------------------------------\n"+((testrun.fails>0)? ":( Failed "+testrun.fails+"/" : ":) Passed all ")+testrun.count+" test"+((testrun.count == 1)? "":"s")+".\n";
+}
+
+
+testrun.count = 0;
+testrun.current = null;
+testrun.passes = 0;
+testrun.fails = 0;
+testrun.reportOut = "";
+
+/** @private */
+testrun.report = function(text) {
+	testrun.reportOut += text+"\n";
+}
+
+/**
+	Check if test evaluates to true.
+	@param {string} test To be evaluated.
+	@param {string} message Optional. To be displayed in the report.
+	@return {boolean} True if the string test evaluates to true.
+*/
+ok = function(test, message) {
+	testrun.count++;
+	
+	var result;
+	try {
+		result = eval(test);
+		
+		if (result) {
+			testrun.passes++;
+			testrun.report("    OK "+testrun.count+" - "+((message != null)? message : ""));
+		}
+		else {
+			testrun.fails++;
+			testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : ""));
+		}
+	}
+	catch(e) {
+		testrun.fails++
+		testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : ""));
+
+	}
+}
+
+/**
+	Check if test is same as expected.
+	@param {string} test To be evaluated.
+	@param {string} expected
+	@param {string} message Optional. To be displayed in the report. 
+	@return {boolean} True if (test == expected). Note that the comparison is not a strict equality check.
+*/
+is = function(test, expected, message) {
+	testrun.count++;
+	
+	var result;
+	try {
+		result = eval(test);
+		
+		if (result == expected) {
+			testrun.passes++
+			testrun.report("    OK "+testrun.count+" - "+((message != null)? message : ""));
+		}
+		else {
+			testrun.fails++
+			testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : ""));
+			testrun.report("expected: "+expected);
+			testrun.report("     got: "+result);
+		}
+	}
+	catch(e) {
+		testrun.fails++
+		testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : ""));
+		testrun.report("expected: "+expected);
+		testrun.report("     got: "+result);}
+}
+
+/**
+	Check if test matches pattern.
+	@param {string} test To be evaluated.
+	@param {string} pattern Used to create a RegExp.
+	@param {string} message Optional. To be displayed in the report.
+	@return {boolean} True if test matches pattern.
+*/
+like = function(test, pattern, message) {
+	testrun.count++;
+
+	var result;
+	try {
+		result = eval(test);
+		var rgx = new RegExp(pattern);
+		
+		if (rgx.test(result)) {
+			testrun.passes++
+			testrun.report("    OK "+testrun.count+" - "+((message != null)? message : ""));
+		}
+		else {
+			testrun.fails++
+			testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : ""));
+			testrun.report("       this: "+result);
+			testrun.report("is not like: "+pattern);
+		}
+	}
+	catch(e) {
+		testrun.fails++
+		testrun.report("NOT OK "+testrun.count+" - "+((message != null)? message : ""));
+	}
+}

+ 26 - 0
jsdoc_toolkit-2.3.3-beta/app/handlers/FOODOC.js

@@ -0,0 +1,26 @@
+/**
+   This is the main container for the FOODOC handler.
+   @namespace
+*/
+FOODOC = {
+};
+
+/** The current version string of this application. */
+FOODOC.VERSION = "1.0";
+
+FOODOC.handle = function(srcFile, src) {
+	LOG.inform("Handling file '" + srcFile + "'");
+	
+	return [
+		new JSDOC.Symbol(
+			"foo",
+			[],
+			"VIRTUAL",
+			new JSDOC.DocComment("/** This is a foo. */")
+		)
+	];
+};
+
+FOODOC.publish = function(symbolgroup) {
+	LOG.inform("Publishing symbolgroup.");
+};

+ 26 - 0
jsdoc_toolkit-2.3.3-beta/app/handlers/XMLDOC.js

@@ -0,0 +1,26 @@
+/**
+ * This is the main container for the XMLDOC handler.
+ * @namespace
+ * @author Brett Fattori (bfattori@fry.com)
+ * @version $Revision: 498 $
+ */
+XMLDOC = {
+	
+};
+
+/** The current version string of this application. */
+XMLDOC.VERSION = "1.0";
+
+/** Include the library necessary to handle XML files */
+IO.includeDir("handlers/XMLDOC/");
+
+/**
+ * @type Symbol[]
+ */
+XMLDOC.handle = function(srcFile, src) {
+	
+};
+
+XMLDOC.publish = function(symbolgroup) {
+	
+}

+ 159 - 0
jsdoc_toolkit-2.3.3-beta/app/handlers/XMLDOC/DomReader.js

@@ -0,0 +1,159 @@
+LOG.inform("XMLDOC.DomReader loaded");
+
+XMLDOC.DomReader = function(root) {
+
+   this.dom = root;
+
+   /**
+    * The current node the reader is on
+    */
+   this.node = root;
+
+   /**
+    * Get the current node the reader is on
+    * @type XMLDOC.Parser.node
+    */
+   XMLDOC.DomReader.prototype.getNode = function() {
+      return this.node;
+   };
+
+   /**
+    * Set the node the reader should be positioned on.
+    * @param node {XMLDOC.Parser.node}
+    */
+   XMLDOC.DomReader.prototype.setNode = function(node) {
+      this.node = node;
+   };
+
+   /**
+    * A helper method to make sure the current node will
+    * never return null, unless null is passed as the root.
+    * @param step {String} An expression to evaluate - should return a node or null
+    */
+   XMLDOC.DomReader.prototype.navigate = function(step) {
+      var n;
+      if ((n = step) != null)
+      {
+         this.node = n;
+         return this.node;
+      }
+      return null;
+   };
+
+   /**
+    * Get the root node of the current node's document.
+    */
+   XMLDOC.DomReader.prototype.root = function() {
+      this.navigate(this.dom);
+   };
+
+   /**
+    * Get the parent of the current node.
+    */
+   XMLDOC.DomReader.prototype.parent = function() {
+      return this.navigate(this.node.parentNode());
+   };
+
+   /**
+    * Get the first child of the current node.
+    */
+   XMLDOC.DomReader.prototype.firstChild = function() {
+      return this.navigate(this.node.firstChild());
+   };
+
+   /**
+    * Get the last child of the current node.
+    */
+   XMLDOC.DomReader.prototype.lastChild = function() {
+      return this.navigate(this.node.lastChild());
+   };
+
+   /**
+    * Get the next sibling of the current node.
+    */
+   XMLDOC.DomReader.prototype.nextSibling = function() {
+      return this.navigate(this.node.nextSibling());
+   };
+
+   /**
+    * Get the previous sibling of the current node.
+    */
+   XMLDOC.DomReader.prototype.prevSibling = function() {
+      return this.navigate(this.node.prevSibling());
+   };
+
+   //===============================================================================================
+   // Support methods
+
+   /**
+    * Walk the tree starting with the current node, calling the plug-in for
+    * each node visited.  Each time the plug-in is called, the DomReader
+    * is passed as the only parameter.  Use the {@link XMLDOC.DomReader#getNode} method
+    * to access the current node.   <i>This method uses a depth first traversal pattern.</i>
+    *
+    * @param srcFile {String} The source file being evaluated
+    */
+   XMLDOC.DomReader.prototype.getSymbols = function(srcFile)
+   {
+      XMLDOC.DomReader.symbols = [];
+      XMLDOC.DomReader.currentFile = srcFile;
+      JSDOC.Symbol.srcFile = (srcFile || "");
+
+      if (defined(JSDOC.PluginManager)) {
+         JSDOC.PluginManager.run("onDomGetSymbols", this);
+      }
+
+      return XMLDOC.DomReader.symbols;
+   };
+
+   /**
+    * Find the node with the given name using a depth first traversal.
+    * Does not modify the DomReader's current node.
+    *
+    * @param name {String} The name of the node to find
+    * @return the node that was found, or null if not found
+    */
+   XMLDOC.DomReader.prototype.findNode = function(name)
+   {
+      var findNode = null;
+
+      // Start at the current node and move into the subtree,
+      // looking for the node with the given name
+      function deeper(node, find)
+      {
+         var look = null;
+
+         if (node) {
+            if (node.name == find)
+            {
+               return node;
+            }
+
+            if (node.firstChild())
+            {
+               look = deeper(node.firstChild(), find);
+            }
+
+            if (!look && node.nextSibling())
+            {
+               look = deeper(node.nextSibling(), find);
+            }
+         }
+
+         return look;
+      }
+
+      return deeper(this.getNode().firstChild(), name);
+   };
+
+   /**
+    * Find the next node with the given name using a depth first traversal.
+    *
+    * @param name {String} The name of the node to find
+    */
+   XMLDOC.DomReader.prototype.findPreviousNode = function(name)
+   {
+   };
+
+};
+

+ 16 - 0
jsdoc_toolkit-2.3.3-beta/app/handlers/XMLDOC/XMLDoc.js

@@ -0,0 +1,16 @@
+LOG.inform("XMLDOC.symbolize loaded");
+
+/**
+ * Convert the source file to a set of symbols
+ */
+XMLDOC.symbolize = function(srcFile, src) {
+
+   LOG.inform("Symbolizing file '" + srcFile + "'");
+
+   // XML files already have a defined structure, so we don't need to
+   // do anything but parse them.  The DOM reader can create a symbol
+   // table from the parsed XML.
+   var dr = new XMLDOC.DomReader(XMLDOC.Parser.parse(src));
+   return dr.getSymbols(srcFile);
+
+};

+ 292 - 0
jsdoc_toolkit-2.3.3-beta/app/handlers/XMLDOC/XMLParse.js

@@ -0,0 +1,292 @@
+LOG.inform("XMLDOC.Parser loaded");
+
+/**
+ * XML Parser object.  Returns an {@link #XMLDOC.Parser.node} which is
+ * the root element of the parsed document.
+ * <p/>
+ * By default, this parser will only handle well formed XML.  To
+ * allow the parser to handle HTML, set the <tt>XMLDOC.Parser.strictMode</tt>
+ * variable to <tt>false</tt> before calling <tt>XMLDOC.Parser.parse()</tt>.
+ * <p/>
+ * <i>Note: If you pass poorly formed XML, it will cause the parser to throw
+ * an exception.</i>
+ *
+ * @author Brett Fattori (bfattori@fry.com)
+ * @author $Author: micmath $
+ * @version $Revision: 497 $
+ */
+XMLDOC.Parser = {};
+
+/**
+ * Strict mode setting.  Setting this to false allows HTML-style source to
+ * be parsed.  Normally, well formed XML has defined end tags, or empty tags
+ * are properly formed.  Default: <tt>true</tt>
+ * @type Boolean
+ */
+XMLDOC.Parser.strictMode = true;
+
+/**
+ * A node in an XML Document.  Node types are ROOT, ELEMENT, COMMENT, PI, and TEXT.
+ * @param parent {XMLDOC.Parser.node} The parent node
+ * @param name {String} The node name
+ * @param type {String} One of the types
+ */
+XMLDOC.Parser.node = function(parent, name, type)
+{
+   this.name = name;
+   this.type = type || "ELEMENT";
+   this.parent = parent;
+   this.charData = "";
+   this.attrs = {};
+   this.nodes = [];
+   this.cPtr = 0;
+
+   XMLDOC.Parser.node.prototype.getAttributeNames = function() {
+      var a = [];
+      for (var o in this.attrs)
+      {
+         a.push(o);
+      }
+
+      return a;
+   };
+
+   XMLDOC.Parser.node.prototype.getAttribute = function(attr) {
+      return this.attrs[attr];
+   };
+
+   XMLDOC.Parser.node.prototype.setAttribute = function(attr, val) {
+      this.attrs[attr] = val;
+   };
+
+   XMLDOC.Parser.node.prototype.getChild = function(idx) {
+      return this.nodes[idx];
+   };
+
+   XMLDOC.Parser.node.prototype.parentNode = function() {
+      return this.parent;
+   };
+
+   XMLDOC.Parser.node.prototype.firstChild = function() {
+      return this.nodes[0];
+   };
+
+   XMLDOC.Parser.node.prototype.lastChild = function() {
+      return this.nodes[this.nodes.length - 1];
+   };
+
+   XMLDOC.Parser.node.prototype.nextSibling = function() {
+      var p = this.parent;
+      if (p && (p.nodes.indexOf(this) + 1 != p.nodes.length))
+      {
+         return p.getChild(p.nodes.indexOf(this) + 1);
+      }
+      return null;
+   };
+
+   XMLDOC.Parser.node.prototype.prevSibling = function() {
+      var p = this.parent;
+      if (p && (p.nodes.indexOf(this) - 1 >= 0))
+      {
+         return p.getChild(p.nodes.indexOf(this) - 1);
+      }
+      return null;
+   };
+};
+
+/**
+ * Parse an XML Document from the specified source.  The XML should be
+ * well formed, unless strict mode is disabled, then the parser will
+ * handle HTML-style XML documents.
+ * @param src {String} The source to parse
+ */
+XMLDOC.Parser.parse = function(src)
+{
+   var A = [];
+
+   // Normailize whitespace
+   A = src.split("\r\n");
+   src = A.join("\n");
+   A = src.split("\r");
+   src = A.join("\n");
+
+   // Remove XML and DOCTYPE specifier
+   src.replace(/<\?XML .*\?>/i, "");
+   src.replace(/<!DOCTYPE .*\>/i, "");
+
+   // The document is the root node and cannot be modified or removed
+   var doc = new XMLDOC.Parser.node(null, "ROOT", "DOCUMENT");
+
+   // Let's break it down
+   XMLDOC.Parser.eat(doc, src);
+
+   return doc;
+};
+
+/**
+ * The XML fragment processing routine.  This method is private and should not be called
+ * directly.
+ * @param parentNode {XMLDOC.Parser.node} The node which is the parent of this fragment
+ * @param src {String} The source within the fragment to process
+ * @private
+ */
+XMLDOC.Parser.eat = function(parentNode, src)
+{
+   // A simple tag def
+   var reTag = new RegExp("<(!|)(\\?|--|)((.|\\s)*?)\\2>","g");
+
+   // Special tag types
+   var reCommentTag = /<!--((.|\s)*?)-->/;
+   var rePITag = /<\?((.|\s)*?)\?>/;
+
+   // A start tag (with potential empty marker)
+   var reStartTag = /<(.*?)( +([\w_\-]*)=(\"|')(.*)\4)*(\/)?>/;
+
+   // An empty HTML style tag (not proper XML, but we'll accept it so we can process HTML)
+   var reHTMLEmptyTag = /<(.*?)( +([\w_\-]*)=(\"|')(.*)\4)*>/;
+
+   // Fully enclosing tag with nested tags
+   var reEnclosingTag = /<(.*?)( +([\w_\-]*)=(\"|')(.*?)\4)*>((.|\s)*?)<\/\1>/;
+
+   // Breaks down attributes
+   var reAttributes = new RegExp(" +([\\w_\\-]*)=(\"|')(.*?)\\2","g");
+
+   // Find us a tag
+   var tag;
+   while ((tag = reTag.exec(src)) != null)
+   {
+      if (tag.index > 0)
+      {
+         // The next tag has some text before it
+         var text = src.substring(0, tag.index).replace(/^[ \t\n]+((.|\n)*?)[ \t\n]+$/, "$1");
+
+         if (text.length > 0 && (text != "\n"))
+         {
+            var txtnode = new XMLDOC.Parser.node(parentNode, "", "TEXT");
+            txtnode.charData = text;
+
+            // Append the new text node
+            parentNode.nodes.push(txtnode);
+         }
+
+         // Reset the lastIndex of reTag
+         reTag.lastIndex -= src.substring(0, tag.index).length;
+
+         // Eat the text
+         src = src.substring(tag.index);
+      }
+
+      if (reCommentTag.test(tag[0]))
+      {
+         // Is this a comment?
+         var comment = new XMLDOC.Parser.node(parentNode, "", "COMMENT");
+         comment.charData = reCommentTag.exec(tag[0])[1];
+
+         // Append the comment
+         parentNode.nodes.push(comment);
+
+         // Move the lastIndex of reTag
+         reTag.lastIndex -= tag[0].length;
+
+         // Eat the tag
+         src = src.replace(reCommentTag, "");
+      }
+      else if (rePITag.test(tag[0]))
+      {
+         // Is this a processing instruction?
+         var pi = new XMLDOC.Parser.node(parentNode, "", "PI");
+         pi.charData = rePITag.exec(tag[0])[1];
+
+         // Append the processing instruction
+         parentNode.nodes.push(pi);
+
+         // Move the lastIndex of reTag
+         reTag.lastIndex -= tag[0].length;
+
+         // Eat the tag
+         src = src.replace(rePITag, "");
+      }
+      else if (reStartTag.test(tag[0]))
+      {
+         // Break it down
+         var e = reStartTag.exec(tag[0]);
+         var elem = new XMLDOC.Parser.node(parentNode, e[1], "ELEMENT");
+
+         // Get attributes from the tag
+         var a;
+         while ((a = reAttributes.exec(e[2])) != null )
+         {
+            elem.attrs[a[1]] = a[3];
+         }
+
+         // Is this an empty XML-style tag?
+         if (e[6] == "/")
+         {
+            // Append the empty element
+            parentNode.nodes.push(elem);
+
+            // Move the lastIndex of reTag (include the start tag length)
+            reTag.lastIndex -= e[0].length;
+
+            // Eat the tag
+            src = src.replace(reStartTag, "");
+         }
+         else
+         {
+            // Check for malformed XML tags
+            var htmlParsed = false;
+            var htmlStartTag = reHTMLEmptyTag.exec(src);
+
+            // See if there isn't an end tag within this block
+            var reHTMLEndTag = new RegExp("</" + htmlStartTag[1] + ">");
+            var htmlEndTag = reHTMLEndTag.exec(src);
+
+            if (XMLDOC.Parser.strictMode && htmlEndTag == null)
+            {
+               // Poorly formed XML fails in strict mode
+               var err = new Error("Malformed XML passed to XMLDOC.Parser... Error contains malformed 'src'");
+               err.src = src;
+               throw err;
+            }
+            else if (htmlEndTag == null)
+            {
+               // This is an HTML-style empty tag, store the element for it in non-strict mode
+               parentNode.nodes.push(elem);
+
+               // Eat the tag
+               src = src.replace(reHTMLEmptyTag, "");
+               htmlParsed = true;
+            }
+
+            // If we didn't parse HTML-style, it must be an enclosing tag
+            if (!htmlParsed)
+            {
+               var enc = reEnclosingTag.exec(src);
+
+               // Go deeper into the document
+               XMLDOC.Parser.eat(elem, enc[6]);
+
+               // Append the new element node
+               parentNode.nodes.push(elem);
+
+               // Eat the tag
+               src = src.replace(reEnclosingTag, "");
+            }
+         }
+
+         // Reset the lastIndex of reTag
+         reTag.lastIndex = 0;
+      }
+   }
+
+   // No tag was found... append the text if there is any
+   src = src.replace(/^[ \t\n]+((.|\n)*?)[ \t\n]+$/, "$1");
+   if (src.length > 0 && (src != "\n"))
+   {
+      var txtNode = new XMLDOC.Parser.node(parentNode, "", "TEXT");
+      txtNode.charData = src;
+
+      // Append the new text node
+      parentNode.nodes.push(txtNode);
+   }
+};

+ 104 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC.js

@@ -0,0 +1,104 @@
+/**
+	@overview
+	@date $Date: 2009-10-28 23:25:32 +0000 (Wed, 28 Oct 2009) $
+	@version $Revision: 816 $ 
+	@location $HeadURL: http://jsdoc-toolkit.googlecode.com/svn/trunk/jsdoc-toolkit/app/lib/JSDOC.js $
+	@name JSDOC.js
+ */
+
+/**
+	This is the main container for the JSDOC application.
+	@namespace
+*/
+JSDOC = {
+};
+
+/**
+	@requires Opt
+ */
+if (typeof arguments == "undefined") arguments = [];
+JSDOC.opt = Opt.get(
+	arguments, 
+	{
+		a: "allfunctions", 
+		c: "conf",
+		d: "directory",
+		"D[]": "define",
+		e: "encoding",
+		"E[]": "exclude",
+		h: "help",
+		n: "nocode",
+		o: "out",
+		p: "private",
+		q: "quiet",
+		r: "recurse",
+		S: "securemodules",
+		s: "suppress",
+		t: "template",
+		T: "testmode",
+		u: "unique",
+		v: "verbose",
+		x: "ext"
+	}
+);
+
+/** The current version string of this application. */
+JSDOC.VERSION = "2.3.3-beta";
+
+/** Print out usage information and quit. */
+JSDOC.usage = function() {
+	print("USAGE: java -jar jsrun.jar app/run.js [OPTIONS] <SRC_DIR> <SRC_FILE> ...");
+	print("");
+	print("OPTIONS:");
+	print("  -a or --allfunctions\n          Include all functions, even undocumented ones.\n");
+	print("  -c or --conf\n          Load a configuration file.\n");
+	print("  -d=<PATH> or --directory=<PATH>\n          Output to this directory (defaults to \"out\").\n");
+	print("  -D=\"myVar:My value\" or --define=\"myVar:My value\"\n          Multiple. Define a variable, available in JsDoc as JSDOC.opt.D.myVar.\n");
+	print("  -e=<ENCODING> or --encoding=<ENCODING>\n          Use this encoding to read and write files.\n");
+	print("  -E=\"REGEX\" or --exclude=\"REGEX\"\n          Multiple. Exclude files based on the supplied regex.\n");
+	print("  -h or --help\n          Show this message and exit.\n");
+	print("  -n or --nocode\n          Ignore all code, only document comments with @name tags.\n");
+	print("  -o=<PATH> or --out=<PATH>\n          Print log messages to a file (defaults to stdout).\n");
+	print("  -p or --private\n          Include symbols tagged as private, underscored and inner symbols.\n");
+	print("  -q or --quiet\n          Do not output any messages, not even warnings.\n");
+	print("  -r=<DEPTH> or --recurse=<DEPTH>\n          Descend into src directories.\n");
+	print("  -s or --suppress\n          Suppress source code output.\n");
+	print("  -S or --securemodules\n          Use Secure Modules mode to parse source code.\n");
+	print("  -t=<PATH> or --template=<PATH>\n          Required. Use this template to format the output.\n");
+	print("  -T or --test\n          Run all unit tests and exit.\n");
+	print("  -u or --unique\n          Force file names to be unique, but not based on symbol names.\n");
+	print("  -v or --verbose\n          Provide verbose feedback about what is happening.\n");
+	print("  -x=<EXT>[,EXT]... or --ext=<EXT>[,EXT]...\n          Scan source files with the given extension/s (defaults to js).\n");
+	
+	quit();
+}
+
+/*t:
+	plan(4, "Testing JSDOC namespace.");
+	
+	is(
+		typeof JSDOC,
+		"object",
+		"JSDOC.usage is a function."
+	);
+	
+	is(
+		typeof JSDOC.VERSION,
+		"string",
+		"JSDOC.VERSION is a string."
+	);
+	
+	is(
+		typeof JSDOC.usage,
+		"function",
+		"JSDOC.usage is a function."
+	);
+	
+	is(
+		typeof JSDOC.opt,
+		"object",
+		"JSDOC.opt is a object."
+	);
+ */
+
+if (this.IO) IO.includeDir("lib/JSDOC/");

+ 204 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/DocComment.js

@@ -0,0 +1,204 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/**
+	Create a new DocComment. This takes a raw documentation comment,
+	and wraps it in useful accessors.
+	@class Represents a documentation comment object.
+ */ 
+JSDOC.DocComment = function(/**String*/comment) {
+	this.init();
+	if (typeof comment != "undefined") {
+		this.parse(comment);
+	}
+}
+
+JSDOC.DocComment.prototype.init = function() {
+	this.isUserComment = true;
+	this.src           = "";
+	this.meta          = "";
+	this.tagTexts      = [];
+	this.tags          = [];
+}
+
+/**
+	@requires JSDOC.DocTag
+ */
+JSDOC.DocComment.prototype.parse = function(/**String*/comment) {
+	if (comment == "") {
+		comment = "/** @desc */";
+		this.isUserComment = false;
+	}
+	
+	this.src = JSDOC.DocComment.unwrapComment(comment);
+	
+	this.meta = "";
+	if (this.src.indexOf("#") == 0) {
+		this.src.match(/#(.+[+-])([\s\S]*)$/);
+		if (RegExp.$1) this.meta = RegExp.$1;
+		if (RegExp.$2) this.src = RegExp.$2;
+	}
+	
+	if (typeof JSDOC.PluginManager != "undefined") {
+		JSDOC.PluginManager.run("onDocCommentSrc", this);
+	}
+	
+	this.fixDesc();
+
+	this.src = JSDOC.DocComment.shared+"\n"+this.src;
+	
+	this.tagTexts = 
+		this.src
+		.split(/(^|[\r\n])\s*@/)
+		.filter(function($){return $.match(/\S/)});
+	
+	/**
+		The tags found in the comment.
+		@type JSDOC.DocTag[]
+	 */
+	this.tags = this.tagTexts.map(function($){return new JSDOC.DocTag($)});
+	
+	if (typeof JSDOC.PluginManager != "undefined") {
+		JSDOC.PluginManager.run("onDocCommentTags", this);
+	}
+}
+
+/*t:
+	plan(5, "testing JSDOC.DocComment");
+	requires("../frame/String.js");
+	requires("../lib/JSDOC/DocTag.js");
+	
+	var com = new JSDOC.DocComment("/**@foo some\n* comment here*"+"/");
+	is(com.tagTexts[0], "foo some\ncomment here", "first tag text is found.");
+	is(com.tags[0].title, "foo", "the title is found in a comment with one tag.");
+	
+	var com = new JSDOC.DocComment("/** @foo first\n* @bar second*"+"/");
+	is(com.getTag("bar").length, 1, "getTag() returns one tag by that title.");
+	
+	JSDOC.DocComment.shared = "@author John Smith";
+	var com = new JSDOC.DocComment("/**@foo some\n* comment here*"+"/");
+	is(com.tags[0].title, "author", "shared comment is added.");
+	is(com.tags[1].title, "foo", "shared comment is added to existing tag.");
+*/
+
+/**
+	If no @desc tag is provided, this function will add it.
+ */
+JSDOC.DocComment.prototype.fixDesc = function() {
+	if (this.meta && this.meta != "@+") return;
+	if (/^\s*[^@\s]/.test(this.src)) {				
+		this.src = "@desc "+this.src;
+	}
+}
+
+/*t:
+	plan(5, "testing JSDOC.DocComment#fixDesc");
+	
+	var com = new JSDOC.DocComment();
+	
+	com.src = "this is a desc\n@author foo";
+	com.fixDesc();
+	is(com.src, "@desc this is a desc\n@author foo", "if no @desc tag is provided one is added.");
+
+	com.src = "x";
+	com.fixDesc();
+	is(com.src, "@desc x", "if no @desc tag is provided one is added to a single character.");
+
+	com.src = "\nx";
+	com.fixDesc();
+	is(com.src, "@desc \nx", "if no @desc tag is provided one is added to return and character.");
+	
+	com.src = " ";
+	com.fixDesc();
+	is(com.src, " ", "if no @desc tag is provided one is not added to just whitespace.");
+
+	com.src = "";
+	com.fixDesc();
+	is(com.src, "", "if no @desc tag is provided one is not added to empty.");
+*/
+
+/**
+	Remove slash-star comment wrapper from a raw comment string.
+	@type String
+ */
+JSDOC.DocComment.unwrapComment = function(/**String*/comment) {
+	if (!comment) return "";
+	var unwrapped = comment.replace(/(^\/\*\*|\*\/$)/g, "").replace(/^\s*\* ?/gm, "");
+	return unwrapped;
+}
+
+/*t:
+	plan(5, "testing JSDOC.DocComment.unwrapComment");
+	
+	var com = "/**x*"+"/";
+	var unwrapped = JSDOC.DocComment.unwrapComment(com);
+	is(unwrapped, "x", "a single character jsdoc is found.");
+	
+	com = "/***x*"+"/";
+	unwrapped = JSDOC.DocComment.unwrapComment(com);
+	is(unwrapped, "x", "three stars are allowed in the opener.");
+	
+	com = "/****x*"+"/";
+	unwrapped = JSDOC.DocComment.unwrapComment(com);
+	is(unwrapped, "*x", "fourth star in the opener is kept.");
+	
+	com = "/**x\n * y\n*"+"/";
+	unwrapped = JSDOC.DocComment.unwrapComment(com);
+	is(unwrapped, "x\ny\n", "leading stars and spaces are trimmed.");
+	
+	com = "/**x\n *   y\n*"+"/";
+	unwrapped = JSDOC.DocComment.unwrapComment(com);
+	is(unwrapped, "x\n  y\n", "only first space after leading stars are trimmed.");
+*/
+
+/**
+	Provides a printable version of the comment.
+	@type String
+ */
+JSDOC.DocComment.prototype.toString = function() {
+	return this.src;
+}
+
+/*t:
+	plan(1, "testing JSDOC.DocComment#fixDesc");
+	var com = new JSDOC.DocComment();
+	com.src = "foo";
+	is(""+com, "foo", "stringifying a comment returns the unwrapped src.");
+*/
+
+/**
+	Given the title of a tag, returns all tags that have that title.
+	@type JSDOC.DocTag[]
+ */
+JSDOC.DocComment.prototype.getTag = function(/**String*/tagTitle) {
+	return this.tags.filter(function($){return $.title == tagTitle});
+}
+
+JSDOC.DocComment.prototype.deleteTag = function(/**String*/tagTitle) {
+	this.tags = this.tags.filter(function($){return $.title != tagTitle})
+}
+
+/*t:
+	plan(1, "testing JSDOC.DocComment#getTag");
+	requires("../frame/String.js");
+	requires("../lib/JSDOC/DocTag.js");
+	
+	var com = new JSDOC.DocComment("/**@foo some\n* @bar\n* @bar*"+"/");
+	is(com.getTag("bar").length, 2, "getTag returns expected number of tags.");
+*/
+
+/**
+	Used to store the currently shared tag text.
+*/
+JSDOC.DocComment.shared = "";
+
+/*t:
+	plan(2, "testing JSDOC.DocComment.shared");
+	requires("../frame/String.js");
+	requires("../lib/JSDOC/DocTag.js");
+	
+	JSDOC.DocComment.shared = "@author Michael";
+	
+	var com = new JSDOC.DocComment("/**@foo\n* @foo*"+"/");
+	is(com.getTag("author").length, 1, "getTag returns shared tag.");
+	is(com.getTag("foo").length, 2, "getTag returns unshared tags too.");
+*/

+ 294 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/DocTag.js

@@ -0,0 +1,294 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/**
+	@constructor
+ */
+JSDOC.DocTag = function(src) {
+	this.init();
+	if (typeof src != "undefined") {
+		this.parse(src);
+	}
+}
+
+/**
+	Create and initialize the properties of this.
+ */
+JSDOC.DocTag.prototype.init = function() {
+	this.title        = "";
+	this.type         = "";
+	this.name         = "";
+	this.isOptional   = false;
+	this.defaultValue = "";
+	this.desc         = "";
+	
+	return this;
+}
+
+/**
+	Populate the properties of this from the given tag src.
+	@param {string} src
+ */
+JSDOC.DocTag.prototype.parse = function(src) {
+	if (typeof src != "string") throw "src must be a string not "+(typeof src);
+
+	try {
+		src = this.nibbleTitle(src);
+		if (JSDOC.PluginManager) {
+			JSDOC.PluginManager.run("onDocTagSynonym", this);
+		}
+		
+		src = this.nibbleType(src);
+		
+		// only some tags are allowed to have names.
+		if (this.title == "param" || this.title == "property" || this.title == "config") { // @config is deprecated
+			src = this.nibbleName(src);
+		}
+	}
+	catch(e) {
+		if (LOG) LOG.warn(e);
+		else throw e;
+	}
+	this.desc = src; // whatever is left
+	
+	// example tags need to have whitespace preserved
+	if (this.title != "example") this.desc = this.desc.trim();
+	
+	if (JSDOC.PluginManager) {
+		JSDOC.PluginManager.run("onDocTag", this);
+	}
+}
+
+/**
+	Automatically called when this is stringified.
+ */
+JSDOC.DocTag.prototype.toString = function() {
+	return this.desc;
+}
+
+/*t:
+	plan(1, "testing JSDOC.DocTag#toString");
+	
+	var tag = new JSDOC.DocTag("param {object} date A valid date.");
+	is(""+tag, "A valid date.", "stringifying a tag returns the desc.");
+ */
+
+/**
+	Find and shift off the title of a tag.
+	@param {string} src
+	@return src
+ */
+JSDOC.DocTag.prototype.nibbleTitle = function(src) {
+	if (typeof src != "string") throw "src must be a string not "+(typeof src);
+	
+	var parts = src.match(/^\s*(\S+)(?:\s([\s\S]*))?$/);
+
+	if (parts && parts[1]) this.title = parts[1];
+	if (parts && parts[2]) src = parts[2];
+	else src = "";
+	
+	return src;
+}
+
+/*t:
+	plan(8, "testing JSDOC.DocTag#nibbleTitle");
+	
+	var tag = new JSDOC.DocTag();
+	
+	tag.init().nibbleTitle("aTitleGoesHere");
+	is(tag.title, "aTitleGoesHere", "a title can be found in a single-word string.");
+	
+	var src = tag.init().nibbleTitle("aTitleGoesHere and the rest");
+	is(tag.title, "aTitleGoesHere", "a title can be found in a multi-word string.");
+	is(src, "and the rest", "the rest is returned when the title is nibbled off.");
+	
+	src = tag.init().nibbleTitle("");
+	is(tag.title, "", "given an empty string the title is empty.");
+	is(src, "", "the rest is empty when the tag is empty.");
+
+	var src = tag.init().nibbleTitle(" aTitleGoesHere\n  a description");
+	is(tag.title, "aTitleGoesHere", "leading and trailing spaces are not part of the title.");
+	is(src, "  a description", "leading spaces (less one) are part of the description.");
+
+	tag.init().nibbleTitle("a.Title::Goes_Here foo");
+	is(tag.title, "a.Title::Goes_Here", "titles with punctuation are allowed.");
+ */
+
+/**
+	Find and shift off the type of a tag.
+	@requires frame/String.js
+	@param {string} src
+	@return src
+ */
+JSDOC.DocTag.prototype.nibbleType = function(src) {
+	if (typeof src != "string") throw "src must be a string not "+(typeof src);
+	
+	if (src.match(/^\s*\{/)) {
+		var typeRange = src.balance("{", "}");
+		if (typeRange[1] == -1) {
+			throw "Malformed comment tag ignored. Tag type requires an opening { and a closing }: "+src;
+		}
+		this.type = src.substring(typeRange[0]+1, typeRange[1]).trim();
+		this.type = this.type.replace(/\s*,\s*/g, "|"); // multiples can be separated by , or |
+		src = src.substring(typeRange[1]+1);
+	}
+	
+	return src;
+}
+
+/*t:
+	plan(5, "testing JSDOC.DocTag.parser.nibbleType");
+	requires("../frame/String.js");
+	
+	var tag = new JSDOC.DocTag();
+	
+	tag.init().nibbleType("{String[]} aliases");
+	is(tag.type, "String[]", "type can have non-alpha characters.");
+	
+	tag.init().nibbleType("{ aTypeGoesHere  } etc etc");
+	is(tag.type, "aTypeGoesHere", "type is trimmed.");
+	
+	tag.init().nibbleType("{ oneType, twoType ,\n threeType  } etc etc");
+	is(tag.type, "oneType|twoType|threeType", "multiple types can be separated by commas.");
+	
+	var error;
+	try { tag.init().nibbleType("{widget foo"); }
+	catch(e) { error = e; }
+	is(typeof error, "string", "malformed tag type throws error.");
+	isnt(error.indexOf("Malformed"), -1, "error message tells tag is malformed.");
+ */
+
+/**
+	Find and shift off the name of a tag.
+	@requires frame/String.js
+	@param {string} src
+	@return src
+ */
+JSDOC.DocTag.prototype.nibbleName = function(src) {
+	if (typeof src != "string") throw "src must be a string not "+(typeof src);
+	
+	src = src.trim();
+	
+	// is optional?
+	if (src.charAt(0) == "[") {
+		var nameRange = src.balance("[", "]");
+		if (nameRange[1] == -1) {
+			throw "Malformed comment tag ignored. Tag optional name requires an opening [ and a closing ]: "+src;
+		}
+		this.name = src.substring(nameRange[0]+1, nameRange[1]).trim();
+		this.isOptional = true;
+		
+		src = src.substring(nameRange[1]+1);
+		
+		// has default value?
+		var nameAndValue = this.name.split("=");
+		if (nameAndValue.length) {
+			this.name = nameAndValue.shift().trim();
+			this.defaultValue = nameAndValue.join("=");
+		}
+	}
+	else {
+		var parts = src.match(/^(\S+)(?:\s([\s\S]*))?$/);
+		if (parts) {
+			if (parts[1]) this.name = parts[1];
+			if (parts[2]) src = parts[2].trim();
+			else src = "";
+		}
+	}	
+
+	return src;
+}
+
+/*t:
+	requires("../frame/String.js");
+	plan(9, "testing JSDOC.DocTag.parser.nibbleName");
+	
+	var tag = new JSDOC.DocTag();
+	
+	tag.init().nibbleName("[foo] This is a description.");
+	is(tag.isOptional, true, "isOptional syntax is detected.");
+	is(tag.name, "foo", "optional param name is found.");
+ 	
+	tag.init().nibbleName("[foo] This is a description.");
+	is(tag.isOptional, true, "isOptional syntax is detected when no type.");
+	is(tag.name, "foo", "optional param name is found when no type.");
+	
+	tag.init().nibbleName("[foo=7] This is a description.");
+ 	is(tag.name, "foo", "optional param name is found when default value.");
+ 	is(tag.defaultValue, 7, "optional param default value is found when default value.");
+ 	
+ 	//tag.init().nibbleName("[foo= a value] This is a description.");
+ 	//is(tag.defaultValue, " a value", "optional param default value is found when default value has spaces (issue #112).");
+ 	
+ 	tag.init().nibbleName("[foo=[]] This is a description.");
+ 	is(tag.defaultValue, "[]", "optional param default value is found when default value is [] (issue #95).");
+ 	
+ 	tag.init().nibbleName("[foo=a=b] This is a description.");
+ 	is(tag.name, "foo", "optional param name is found when default value is a=b.");
+ 	is(tag.defaultValue, "a=b", "optional param default value is found when default value is a=b.")
+ */
+
+/*t:
+	plan(32, "Testing JSDOC.DocTag.parser.");
+	requires("../frame/String.js");
+	
+ 	var tag = new JSDOC.DocTag();
+ 	
+ 	is(typeof tag, "object", "JSDOC.DocTag.parser with an empty string returns an object.");
+ 	is(typeof tag.title, "string", "returned object has a string property 'title'.");
+ 	is(typeof tag.type, "string", "returned object has a string property 'type'.");
+ 	is(typeof tag.name, "string", "returned object has a string property 'name'.");
+ 	is(typeof tag.defaultValue, "string", "returned object has a string property 'defaultValue'.");
+ 	is(typeof tag.isOptional, "boolean", "returned object has a boolean property 'isOptional'.");
+ 	is(typeof tag.desc, "string", "returned object has a string property 'desc'.");
+  
+  	tag = new JSDOC.DocTag("param {widget} foo");
+  	is(tag.title, "param", "param title is found.");
+  	is(tag.name, "foo", "param name is found when desc is missing.");
+ 	is(tag.desc, "", "param desc is empty when missing.");
+ 	
+ 	tag = new JSDOC.DocTag("param {object} date A valid date.");
+ 	is(tag.name, "date", "param name is found with a type.");
+ 	is(tag.type, "object", "param type is found.");
+ 	is(tag.desc, "A valid date.", "param desc is found with a type.");
+ 	
+  	tag = new JSDOC.DocTag("param aName a description goes\n    here.");
+	is(tag.name, "aName", "param name is found without a type.");
+ 	is(tag.desc, "a description goes\n    here.", "param desc is found without a type.");
+ 	
+ 	tag = new JSDOC.DocTag("param {widget}");
+ 	is(tag.name, "", "param name is empty when it is not given.");
+	
+	tag = new JSDOC.DocTag("param {widget} [foo] This is a description.");
+	is(tag.name, "foo", "optional param name is found.");
+	
+	tag = new JSDOC.DocTag("return {aType} This is a description.");
+	is(tag.type, "aType", "when return tag has no name, type is found.");
+	is(tag.desc, "This is a description.", "when return tag has no name, desc is found.");
+	
+	tag = new JSDOC.DocTag("author Joe Coder <jcoder@example.com>");
+	is(tag.title, "author", "author tag has a title.");
+	is(tag.type, "", "the author tag has no type.");
+	is(tag.name, "", "the author tag has no name.");
+	is(tag.desc, "Joe Coder <jcoder@example.com>", "author tag has desc.");
+	
+	tag = new JSDOC.DocTag("private \t\n  ");
+	is(tag.title, "private", "private tag has a title.");
+	is(tag.type, "", "the private tag has no type.");
+	is(tag.name, "", "the private tag has no name.");
+	is(tag.desc, "", "private tag has no desc.");
+
+	tag = new JSDOC.DocTag("example\n   example(code);\n   more();");
+	is(tag.desc, "   example(code);\n   more();", "leading whitespace (less one) in examples code is preserved.");
+	
+	tag = new JSDOC.DocTag("param theName  \n");
+	is(tag.name, "theName", "name only is found.");
+	
+	tag = new JSDOC.DocTag("type theDesc  \n");
+	is(tag.desc, "theDesc", "desc only is found.");
+	
+	tag = new JSDOC.DocTag("type {theType} \n");
+	is(tag.type, "theType", "type only is found.");
+	
+	tag = new JSDOC.DocTag("");
+	is(tag.title, "", "title is empty when tag is empty.");
+ */

+ 126 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/JsDoc.js

@@ -0,0 +1,126 @@
+/**
+	@constructor
+	@param [opt] Used to override the commandline options. Useful for testing.
+	@version $Id: JsDoc.js 773 2009-01-24 09:42:04Z micmath $
+*/
+JSDOC.JsDoc = function(/**object*/ opt) {
+	if (opt) {
+		JSDOC.opt = opt;
+	}
+	
+	if (JSDOC.opt.h) {
+		JSDOC.usage();
+		quit();
+	}
+	
+	// defend against options that are not sane 
+	if (JSDOC.opt._.length == 0) {
+		LOG.warn("No source files to work on. Nothing to do.");
+		quit();
+	}
+	if (JSDOC.opt.t === true || JSDOC.opt.d === true) {
+		JSDOC.usage();
+	}
+	
+	if (typeof JSDOC.opt.d == "string") {
+		if (!JSDOC.opt.d.charAt(JSDOC.opt.d.length-1).match(/[\\\/]/)) {
+			JSDOC.opt.d = JSDOC.opt.d+"/";
+		}
+		LOG.inform("Output directory set to '"+JSDOC.opt.d+"'.");
+		IO.mkPath(JSDOC.opt.d);
+	}
+	if (JSDOC.opt.e) IO.setEncoding(JSDOC.opt.e);
+	
+	// the -r option: scan source directories recursively
+	if (typeof JSDOC.opt.r == "boolean") JSDOC.opt.r = 10;
+	else if (!isNaN(parseInt(JSDOC.opt.r))) JSDOC.opt.r = parseInt(JSDOC.opt.r);
+	else JSDOC.opt.r = 1;
+	
+	// the -D option: define user variables
+	var D = {};
+	if (JSDOC.opt.D) {
+		for (var i = 0; i < JSDOC.opt.D.length; i++) {
+			var defineParts = JSDOC.opt.D[i].split(":", 2);
+			if (defineParts) D[defineParts[0]] = defineParts[1];
+		}
+	}
+	JSDOC.opt.D = D;
+	// combine any conf file D options with the commandline D options
+	if (defined(JSDOC.conf)) for (var c in JSDOC.conf.D) {
+ 		if (!defined(JSDOC.opt.D[c])) {
+ 			JSDOC.opt.D[c] = JSDOC.conf.D[c];
+ 		}
+ 	}
+	
+	// Give plugins a chance to initialize
+	if (defined(JSDOC.PluginManager)) {
+		JSDOC.PluginManager.run("onInit", JSDOC.opt);
+	}
+
+	JSDOC.opt.srcFiles = JSDOC.JsDoc._getSrcFiles();
+	JSDOC.JsDoc._parseSrcFiles();
+	JSDOC.JsDoc.symbolSet = JSDOC.Parser.symbols;
+}
+
+/**
+	Retrieve source file list.
+	@returns {String[]} The pathnames of the files to be parsed.
+ */
+JSDOC.JsDoc._getSrcFiles = function() {
+	JSDOC.JsDoc.srcFiles = [];
+	
+	var ext = ["js"];
+	if (JSDOC.opt.x) {
+		ext = JSDOC.opt.x.split(",").map(function($) {return $.toLowerCase()});
+	}
+	
+	for (var i = 0; i < JSDOC.opt._.length; i++) {
+		JSDOC.JsDoc.srcFiles = JSDOC.JsDoc.srcFiles.concat(
+			IO.ls(JSDOC.opt._[i], JSDOC.opt.r).filter(
+				function($) {
+					var thisExt = $.split(".").pop().toLowerCase();
+					
+					if (JSDOC.opt.E) {
+						for(var n = 0; n < JSDOC.opt.E.length; n++) {
+							if ($.match(new RegExp(JSDOC.opt.E[n]))) {
+								LOG.inform("Excluding " + $);
+								return false; // if the file matches the regex then it's excluded.
+							}
+						}
+					}
+					
+					return (ext.indexOf(thisExt) > -1); // we're only interested in files with certain extensions
+				}
+			)
+		);
+	}
+	
+	return JSDOC.JsDoc.srcFiles;
+}
+
+JSDOC.JsDoc._parseSrcFiles = function() {
+	JSDOC.Parser.init();
+	for (var i = 0, l = JSDOC.JsDoc.srcFiles.length; i < l; i++) {
+		var srcFile = JSDOC.JsDoc.srcFiles[i];
+		
+		if (JSDOC.opt.v) LOG.inform("Parsing file: " + srcFile);
+		
+		try {
+			var src = IO.readFile(srcFile);
+		}
+		catch(e) {
+			LOG.warn("Can't read source file '"+srcFile+"': "+e.message);
+		}
+
+		var tr = new JSDOC.TokenReader();
+		var ts = new JSDOC.TokenStream(tr.tokenize(new JSDOC.TextStream(src)));
+
+		JSDOC.Parser.parse(ts, srcFile);
+
+	}
+	JSDOC.Parser.finish();
+
+	if (JSDOC.PluginManager) {
+		JSDOC.PluginManager.run("onFinishedParsing", JSDOC.Parser.symbols);
+	}
+}

+ 109 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/JsPlate.js

@@ -0,0 +1,109 @@
+/**
+	@constructor
+*/
+JSDOC.JsPlate = function(templateFile) {
+	if (templateFile) this.template = IO.readFile(templateFile);
+	
+	this.templateFile = templateFile;
+	this.code = "";
+	this.parse();
+}
+
+JSDOC.JsPlate.prototype.parse = function() {
+	this.template = this.template.replace(/\{#[\s\S]+?#\}/gi, "");
+	this.code = "var output=\u001e"+this.template;
+
+	this.code = this.code.replace(
+		/<for +each="(.+?)" +in="(.+?)" *>/gi, 
+		function (match, eachName, inName) {
+			return "\u001e;\rvar $"+eachName+"_keys = keys("+inName+");\rfor(var $"+eachName+"_i = 0; $"+eachName+"_i < $"+eachName+"_keys.length; $"+eachName+"_i++) {\rvar $"+eachName+"_last = ($"+eachName+"_i == $"+eachName+"_keys.length-1);\rvar $"+eachName+"_key = $"+eachName+"_keys[$"+eachName+"_i];\rvar "+eachName+" = "+inName+"[$"+eachName+"_key];\routput+=\u001e";
+		}
+	);	
+	this.code = this.code.replace(/<if test="(.+?)">/g, "\u001e;\rif ($1) { output+=\u001e");
+	this.code = this.code.replace(/<elseif test="(.+?)"\s*\/>/g, "\u001e;}\relse if ($1) { output+=\u001e");
+	this.code = this.code.replace(/<else\s*\/>/g, "\u001e;}\relse { output+=\u001e");
+	this.code = this.code.replace(/<\/(if|for)>/g, "\u001e;\r};\routput+=\u001e");
+	this.code = this.code.replace(
+		/\{\+\s*([\s\S]+?)\s*\+\}/gi,
+		function (match, code) {
+			code = code.replace(/"/g, "\u001e"); // prevent qoute-escaping of inline code
+			code = code.replace(/(\r?\n)/g, " ");
+			return "\u001e+ ("+code+") +\u001e";
+		}
+	);
+	this.code = this.code.replace(
+		/\{!\s*([\s\S]+?)\s*!\}/gi,
+		function (match, code) {
+			code = code.replace(/"/g, "\u001e"); // prevent qoute-escaping of inline code
+			code = code.replace(/(\n)/g, " ");
+			return "\u001e; "+code+";\routput+=\u001e";
+		}
+	);
+	this.code = this.code+"\u001e;";
+
+	this.code = this.code.replace(/(\r?\n)/g, "\\n");
+	this.code = this.code.replace(/"/g, "\\\"");
+	this.code = this.code.replace(/\u001e/g, "\"");
+}
+
+JSDOC.JsPlate.prototype.toCode = function() {
+	return this.code;
+}
+
+JSDOC.JsPlate.keys = function(obj) {
+	var keys = [];
+	if (obj.constructor.toString().indexOf("Array") > -1) {
+		for (var i = 0; i < obj.length; i++) {
+			keys.push(i);
+		}
+	}
+	else {
+		for (var i in obj) {
+			keys.push(i);
+		}
+	}
+	return keys;
+};
+
+JSDOC.JsPlate.values = function(obj) {
+	var values = [];
+	if (obj.constructor.toString().indexOf("Array") > -1) {
+		for (var i = 0; i < obj.length; i++) {
+			values.push(obj[i]);
+		}
+	}
+	else {
+		for (var i in obj) {
+			values.push(obj[i]);
+		}
+	}
+	return values;
+};
+
+JSDOC.JsPlate.prototype.process = function(data, compact) {
+	var keys = JSDOC.JsPlate.keys;
+	var values = JSDOC.JsPlate.values;
+	
+	try {
+		eval(this.code);
+	}
+	catch (e) {
+		print(">> There was an error evaluating the compiled code from template: "+this.templateFile);
+		print("   The error was on line "+e.lineNumber+" "+e.name+": "+e.message);
+		var lines = this.code.split("\r");
+		if (e.lineNumber-2 >= 0) print("line "+(e.lineNumber-1)+": "+lines[e.lineNumber-2]);
+		print("line "+e.lineNumber+": "+lines[e.lineNumber-1]);
+		print("");
+	}
+	
+	if (compact) { // patch by mcbain.asm
+ 		// Remove lines that contain only space-characters, usually left by lines in the template
+ 		// which originally only contained JSPlate tags or code. This makes it easier to write
+ 		// non-tricky templates which still put out nice code (not bloated with extra lines).
+ 		// Lines purposely left blank (just a line ending) are left alone.
+ 		output = output.replace(/\s+?(\r?)\n/g, "$1\n");
+ 	}
+ 	
+	/*debug*///print(this.code);
+	return output;
+}

+ 144 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Lang.js

@@ -0,0 +1,144 @@
+/**
+	@namespace
+*/
+JSDOC.Lang = {
+}
+
+JSDOC.Lang.isBuiltin = function(name) {
+	return (JSDOC.Lang.isBuiltin.coreObjects.indexOf(name) > -1);
+}
+JSDOC.Lang.isBuiltin.coreObjects = ['_global_', 'Array', 'Boolean', 'Date', 'Error', 'Function', 'Math', 'Number', 'Object', 'RegExp', 'String'];
+
+JSDOC.Lang.whitespace = function(ch) {
+	return JSDOC.Lang.whitespace.names[ch];
+}
+JSDOC.Lang.whitespace.names = {
+	" ":      "SPACE",
+	"\f":     "FORMFEED",
+	"\t":     "TAB",
+	"\u0009": "UNICODE_TAB",
+	"\u000A": "UNICODE_NBR",
+	"\u0008": "VERTICAL_TAB"
+};
+
+JSDOC.Lang.newline = function(ch) {
+	return JSDOC.Lang.newline.names[ch];
+}
+JSDOC.Lang.newline.names = {
+	"\n":     "NEWLINE",
+	"\r":     "RETURN",
+	"\u000A": "UNICODE_LF",
+	"\u000D": "UNICODE_CR",
+	"\u2029": "UNICODE_PS",
+	"\u2028": "UNICODE_LS"
+};
+
+JSDOC.Lang.keyword = function(word) {
+	return JSDOC.Lang.keyword.names["="+word];
+}
+JSDOC.Lang.keyword.names = {
+	"=break":      "BREAK",
+	"=case":       "CASE",
+	"=catch":      "CATCH",
+	"=const":      "VAR",
+	"=continue":   "CONTINUE",
+	"=default":    "DEFAULT",
+	"=delete":     "DELETE",
+	"=do":         "DO",
+	"=else":       "ELSE",
+	"=false":      "FALSE",
+	"=finally":    "FINALLY",
+	"=for":        "FOR",
+	"=function":   "FUNCTION",
+	"=if":         "IF",
+	"=in":         "IN",
+	"=instanceof": "INSTANCEOF",
+	"=new":        "NEW",
+	"=null":       "NULL",
+	"=return":     "RETURN",
+	"=switch":     "SWITCH",
+	"=this":       "THIS",
+	"=throw":      "THROW",
+	"=true":       "TRUE",
+	"=try":        "TRY",
+	"=typeof":     "TYPEOF",
+	"=void":       "VOID",
+	"=while":      "WHILE",
+	"=with":       "WITH",
+	"=var":        "VAR"
+};
+
+JSDOC.Lang.punc = function(ch) {
+	return JSDOC.Lang.punc.names[ch];
+}
+JSDOC.Lang.punc.names = {
+	";":   "SEMICOLON",
+	",":   "COMMA",
+	"?":   "HOOK",
+	":":   "COLON",
+	"||":  "OR", 
+	"&&":  "AND",
+	"|":   "BITWISE_OR",
+	"^":   "BITWISE_XOR",
+	"&":   "BITWISE_AND",
+	"===": "STRICT_EQ", 
+	"==":  "EQ",
+	"=":   "ASSIGN",
+	"!==": "STRICT_NE",
+	"!=":  "NE",
+	"<<":  "LSH",
+	"<=":  "LE", 
+	"<":   "LT",
+	">>>": "URSH",
+	">>":  "RSH",
+	">=":  "GE",
+	">":   "GT", 
+	"++":  "INCREMENT",
+	"--":  "DECREMENT",
+	"+":   "PLUS",
+	"-":   "MINUS",
+	"*":   "MUL",
+	"/":   "DIV", 
+	"%":   "MOD",
+	"!":   "NOT",
+	"~":   "BITWISE_NOT",
+	".":   "DOT",
+	"[":   "LEFT_BRACKET",
+	"]":   "RIGHT_BRACKET",
+	"{":   "LEFT_CURLY",
+	"}":   "RIGHT_CURLY",
+	"(":   "LEFT_PAREN",
+	")":   "RIGHT_PAREN"
+};
+
+JSDOC.Lang.matching = function(name) {
+	return JSDOC.Lang.matching.names[name];
+}
+JSDOC.Lang.matching.names = {
+	"LEFT_PAREN": "RIGHT_PAREN",
+	"RIGHT_PAREN": "LEFT_PAREN",
+	"LEFT_CURLY": "RIGHT_CURLY",
+	"RIGHT_CURLY": "LEFT_CURLY",
+	"LEFT_BRACE": "RIGHT_BRACE",
+	"RIGHT_BRACE": "LEFT_BRACE"
+}
+
+JSDOC.Lang.isNumber = function(str) {
+	return /^(\.[0-9]|[0-9]+\.|[0-9])[0-9]*([eE][+-][0-9]+)?$/i.test(str);
+}
+
+JSDOC.Lang.isHexDec = function(str) {
+	return /^0x[0-9A-F]+$/i.test(str);
+}
+
+JSDOC.Lang.isWordChar = function(str) {
+	return /^[a-zA-Z0-9$_.]+$/.test(str);
+}
+
+JSDOC.Lang.isSpace = function(str) {
+	return (typeof JSDOC.Lang.whitespace(str) != "undefined");
+}
+
+JSDOC.Lang.isNewline = function(str) {
+	return (typeof JSDOC.Lang.newline(str) != "undefined");
+}

+ 144 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Parser.js

@@ -0,0 +1,144 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/**
+	@namespace
+	@requires JSDOC.Walker
+	@requires JSDOC.Symbol
+	@requires JSDOC.DocComment
+*/
+JSDOC.Parser = {
+	conf: {
+		ignoreCode:               JSDOC.opt.n,
+		ignoreAnonymous:           true, // factory: true
+		treatUnderscoredAsPrivate: true, // factory: true
+		explain:                   false // factory: false
+	},
+	
+	addSymbol: function(symbol) {
+
+		if (JSDOC.Parser.rename) {
+			for (var n in JSDOC.Parser.rename) {
+				if (symbol.alias.indexOf(n) == 0) {
+					if (symbol.name == symbol.alias) {
+						symbol.name = symbol.name.replace(n, JSDOC.Parser.rename[n]);
+					}
+					symbol.alias = symbol.alias.replace(n, JSDOC.Parser.rename[n]);
+				}
+			}
+		}
+		
+		if (JSDOC.opt.S) {
+			if (typeof JSDOC.Parser.secureModules == "undefined") JSDOC.Parser.secureModules = {};
+			if (/^exports\./.test(symbol.alias)) {
+				symbol.srcFile.match(/(^|[\\\/])([^\\\/]+)\.js/i);
+				var fileNS = RegExp.$2;
+				
+				// need to create the namespace associated with this file first
+				if (!JSDOC.Parser.secureModules[fileNS]) {
+					JSDOC.Parser.secureModules[fileNS] = 1;
+					var nsSymbol = new JSDOC.Symbol(fileNS, [], "GLOBAL", new JSDOC.DocComment(""));
+					nsSymbol.isNamespace = true;
+					nsSymbol.srcFile = "";
+					nsSymbol.isPrivate = false;
+					nsSymbol.srcFile = symbol.srcFile;
+					nsSymbol.desc = (JSDOC.Parser.symbols.getSymbol(symbol.srcFile) || {desc: ""}).desc;
+					JSDOC.Parser.addSymbol(nsSymbol);
+				}
+				
+				symbol.alias = symbol.alias.replace(/^exports\./, fileNS + '.');
+				symbol.name = symbol.name.replace(/^exports\./, '');
+				symbol.memberOf = fileNS;
+				symbol.isStatic = true;
+			}
+		}
+
+		// if a symbol alias is documented more than once the last one with the user docs wins
+		if (JSDOC.Parser.symbols.hasSymbol(symbol.alias)) {
+ 			var oldSymbol = JSDOC.Parser.symbols.getSymbol(symbol.alias);
+			if (oldSymbol.comment.isUserComment) {
+				if (symbol.comment.isUserComment) { // old and new are both documented
+					LOG.warn("The symbol '"+symbol.alias+"' is documented more than once.");
+				}
+				else { // old is documented but new isn't
+					return;
+				}
+			}
+		}
+		
+		// we don't document anonymous things
+		if (JSDOC.Parser.conf.ignoreAnonymous && symbol.name.match(/\$anonymous\b/)) return;
+
+		// uderscored things may be treated as if they were marked private, this cascades
+		if (JSDOC.Parser.conf.treatUnderscoredAsPrivate && symbol.name.match(/[.#-]_[^.#-]+$/)) {
+			if (!symbol.comment.getTag("public").length > 0) symbol.isPrivate = true;
+		}
+		
+		// -p flag is required to document private things
+		if (!JSDOC.opt.p && symbol.isPrivate) return; // issue #161 fixed by mcbain.asm
+		
+		// ignored things are not documented, this doesn't cascade
+		if (symbol.isIgnored) return;
+		JSDOC.Parser.symbols.addSymbol(symbol);
+	},
+	
+	addBuiltin: function(name) {
+		var builtin = new JSDOC.Symbol(name, [], "CONSTRUCTOR", new JSDOC.DocComment(""));
+		builtin.isNamespace = true;
+		builtin.srcFile = "";
+		builtin.isPrivate = false;
+		JSDOC.Parser.addSymbol(builtin);
+		return builtin;
+	},
+	
+	init: function() {
+		JSDOC.Parser.symbols = new JSDOC.SymbolSet();
+		JSDOC.Parser.walker = new JSDOC.Walker();
+	},
+	
+	finish: function() {
+		JSDOC.Parser.symbols.relate();		
+		
+		// make a litle report about what was found
+		if (JSDOC.Parser.conf.explain) {
+			var symbols = JSDOC.Parser.symbols.toArray();
+			var srcFile = "";
+			for (var i = 0, l = symbols.length; i < l; i++) {
+				var symbol = symbols[i];
+				if (srcFile != symbol.srcFile) {
+					srcFile = symbol.srcFile;
+					print("\n"+srcFile+"\n-------------------");
+				}
+				print(i+":\n  alias => "+symbol.alias + "\n  name => "+symbol.name+ "\n  isa => "+symbol.isa + "\n  memberOf => " + symbol.memberOf + "\n  isStatic => " + symbol.isStatic + ",  isInner => " + symbol.isInner+ ",  isPrivate => " + symbol.isPrivate);
+			}
+			print("-------------------\n");
+		}
+	}
+}
+
+JSDOC.Parser.parse = function(/**JSDOC.TokenStream*/ts, /**String*/srcFile) {
+	JSDOC.Symbol.srcFile = (srcFile || "");
+	JSDOC.DocComment.shared = ""; // shared comments don't cross file boundaries
+	
+	if (!JSDOC.Parser.walker) JSDOC.Parser.init();
+	JSDOC.Parser.walker.walk(ts); // adds to our symbols
+	
+	// filter symbols by option
+	for (var p = JSDOC.Parser.symbols._index.first(); p; p = JSDOC.Parser.symbols._index.next()) {
+		var symbol = p.value;
+		
+		if (!symbol) continue;
+		
+		if (symbol.is("FILE") || symbol.is("GLOBAL")) {
+			continue;
+		}
+		else if (!JSDOC.opt.a && !symbol.comment.isUserComment) {
+			JSDOC.Parser.symbols.deleteSymbol(symbol.alias);
+		}
+		
+		if (/#$/.test(symbol.alias)) { // we don't document prototypes
+			JSDOC.Parser.symbols.deleteSymbol(symbol.alias);
+		}
+	}
+	
+	return JSDOC.Parser.symbols.toArray();
+}

+ 33 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/PluginManager.js

@@ -0,0 +1,33 @@
+/**
+	@namespace Holds functionality related to running plugins.
+*/
+JSDOC.PluginManager = {
+}
+
+/**
+	@param name A unique name that identifies that plugin.
+	@param handlers A collection of named functions. The names correspond to hooks in the core code.
+*/
+JSDOC.PluginManager.registerPlugin = function(/**String*/name, /**Object*/handlers) {
+	if (!defined(JSDOC.PluginManager.plugins))
+		/** The collection of all plugins. Requires a unique name for each.
+		*/
+		JSDOC.PluginManager.plugins = {};
+	
+	
+	JSDOC.PluginManager.plugins[name] = handlers;
+}
+
+/**
+	@param hook The name of the hook that is being caught.
+	@param target Any object. This will be passed as the only argument to the handler whose
+	name matches the hook name. Handlers cannot return a value, so must modify the target
+	object to have an effect.
+*/
+JSDOC.PluginManager.run = function(/**String*/hook, /**Mixed*/target) {
+	for (var name in JSDOC.PluginManager.plugins) {
+		if (defined(JSDOC.PluginManager.plugins[name][hook])) {
+			JSDOC.PluginManager.plugins[name][hook](target);
+		}
+	}
+}

+ 644 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Symbol.js

@@ -0,0 +1,644 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/**
+	Create a new Symbol.
+	@class Represents a symbol in the source code.
+ */
+JSDOC.Symbol = function() {
+	this.init();
+	if (arguments.length) this.populate.apply(this, arguments);
+}
+
+JSDOC.Symbol.count = 0;
+
+JSDOC.Symbol.prototype.init = function() {
+	this._name = "";
+	this._params = [];
+	this.$args = [];
+	this.addOn = "";
+	this.alias = "";
+	this.augments = [];
+	this.author = "";
+	this.classDesc = "";
+	this.comment = {};
+	this.defaultValue = undefined;
+	this.deprecated = "";
+	this.desc = "";
+	this.example = [];
+	this.exceptions = [];
+	this.fires = [];
+	this.id = JSDOC.Symbol.count++;
+	this.inherits = [];
+	this.inheritsFrom = [];
+	this.isa = "OBJECT";
+	this.isConstant = false;
+	this.isEvent = false;
+	this.isIgnored = false;
+	this.isInner = false;
+	this.isNamespace = false;
+	this.isPrivate = false;
+	this.isStatic = false;
+	this.memberOf = "";
+	this.methods = [];
+	this.properties = [];
+	this.requires = [];
+	this.returns = [];
+	this.see = [];
+	this.since = "";
+	this.srcFile = {};
+	this.type = "";
+	this.version = "";
+}
+
+JSDOC.Symbol.prototype.serialize = function() {
+	var keys = [];
+	for (var p in this) {
+		keys.push (p);
+	}
+	keys = keys.sort();
+	
+	var out = "";
+	for (var i in keys) {
+		if (typeof this[keys[i]] == "function") continue;
+		out += keys[i]+" => "+Dumper.dump(this[keys[i]])+",\n";
+	}
+	return "\n{\n" + out + "}\n";
+}
+
+JSDOC.Symbol.prototype.clone = function() {
+	var clone = new JSDOC.Symbol();
+	clone.populate.apply(clone, this.$args); // repopulate using the original arguments
+	clone.srcFile = this.srcFile; // not the current srcFile, the one when the original was made
+	return clone;
+}
+
+JSDOC.Symbol.prototype.__defineSetter__("name",
+	function(n) { n = n.replace(/^_global_[.#-]/, ""); n = n.replace(/\.prototype\.?/g, '#'); this._name = n; }
+);
+JSDOC.Symbol.prototype.__defineGetter__("name",
+	function() { return this._name; }
+);
+JSDOC.Symbol.prototype.__defineSetter__("params", 
+	function(v) {
+		for (var i = 0, l = v.length; i < l; i++) {
+			if (v[i].constructor != JSDOC.DocTag) { // may be a generic object parsed from signature, like {type:..., name:...}
+				this._params[i] = new JSDOC.DocTag("param"+((v[i].type)?" {"+v[i].type+"}":"")+" "+v[i].name);
+			}
+			else {
+				this._params[i] = v[i];
+			}
+		}
+	}
+);
+JSDOC.Symbol.prototype.__defineGetter__("params",
+	function() { return this._params; }
+);
+
+JSDOC.Symbol.prototype.getEvents = function() {
+	var events = [];
+	for (var i = 0, l = this.methods.length; i < l; i++) {
+		if (this.methods[i].isEvent) {
+			this.methods[i].name = this.methods[i].name.replace("event:", "");
+			events.push(this.methods[i]);
+		}
+	}
+	return events;
+}
+
+JSDOC.Symbol.prototype.getMethods = function() {
+	var nonEvents = [];
+	for (var i = 0, l = this.methods.length; i < l; i++) {
+		if (!this.methods[i].isEvent) {
+			nonEvents.push(this.methods[i]);
+		}
+	}
+	return nonEvents;
+}
+
+
+JSDOC.Symbol.prototype.populate = function(
+		/** String */ name,
+		/** Object[] */ params,
+		/** String */ isa,
+		/** JSDOC.DocComment */ comment
+) {
+	this.$args = arguments;
+	
+	this.name = name;
+	this.alias = this.name;
+	
+	this.params = params;
+	this.isa = (isa == "VIRTUAL")? "OBJECT":isa;
+	this.comment = comment || new JSDOC.DocComment("");
+	this.srcFile = JSDOC.Symbol.srcFile;
+	
+	if (this.is("FILE") && !this.alias) this.alias = this.srcFile;
+
+	this.setTags();
+	
+	if (typeof JSDOC.PluginManager != "undefined") {
+		JSDOC.PluginManager.run("onSymbol", this);
+	}
+}
+
+JSDOC.Symbol.prototype.setTags = function() {
+	// @author
+	var authors = this.comment.getTag("author");
+	if (authors.length) {
+		this.author = authors.map(function($){return $.desc;}).join(", ");
+	}
+	
+	/*t:
+		plan(34, "testing JSDOC.Symbol");
+		
+		requires("../lib/JSDOC/DocComment.js");
+		requires("../frame/String.js");
+		requires("../lib/JSDOC/DocTag.js");
+
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@author Joe Smith*"+"/"));
+		is(sym.author, "Joe Smith", "@author tag, author is found.");
+	*/
+	
+	// @desc
+	var descs = this.comment.getTag("desc");
+	if (descs.length) {
+		this.desc = descs.map(function($){return $.desc;}).join("\n"); // multiple descriptions are concatenated into one
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@desc This is a description.*"+"/"));
+		is(sym.desc, "This is a description.", "@desc tag, description is found.");
+	*/
+	
+	// @overview
+	if (this.is("FILE")) {
+		if (!this.alias) this.alias = this.srcFile;
+		
+		var overviews = this.comment.getTag("overview");
+		if (overviews.length) {
+			this.desc = [this.desc].concat(overviews.map(function($){return $.desc;})).join("\n");
+		}
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@overview This is an overview.*"+"/"));
+		is(sym.desc, "\nThis is an overview.", "@overview tag, description is found.");
+	*/
+	
+	// @since
+	var sinces = this.comment.getTag("since");
+	if (sinces.length) {
+		this.since = sinces.map(function($){return $.desc;}).join(", ");
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@since 1.01*"+"/"));
+		is(sym.since, "1.01", "@since tag, description is found.");
+	*/
+	
+	// @constant
+	if (this.comment.getTag("constant").length) {
+		this.isConstant = true;
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@constant*"+"/"));
+		is(sym.isConstant, true, "@constant tag, isConstant set.");
+	*/
+	
+	// @version
+	var versions = this.comment.getTag("version");
+	if (versions.length) {
+		this.version = versions.map(function($){return $.desc;}).join(", ");
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@version 2.0x*"+"/"));
+		is(sym.version, "2.0x", "@version tag, version is found.");
+	*/
+	
+	// @deprecated
+	var deprecateds = this.comment.getTag("deprecated");
+	if (deprecateds.length) {
+		this.deprecated = deprecateds.map(function($){return $.desc;}).join("\n");
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@deprecated Use other method.*"+"/"));
+		is(sym.deprecated, "Use other method.", "@deprecated tag, desc is found.");
+	*/
+	
+	// @example
+	var examples = this.comment.getTag("example");
+	if (examples.length) {
+		this.example = examples.map(
+			// trim trailing whitespace
+			function($) {
+				$.desc = $.desc.replace(/\s+$/, "");
+				return $;
+			}
+		);
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@example This\n  is an example. \n*"+"/"));
+		isnt(typeof sym.example[0], "undefined", "@example tag, creates sym.example array.");
+		is(sym.example[0], "This\n  is an example.", "@example tag, desc is found.");
+	*/
+	
+	// @see
+	var sees = this.comment.getTag("see");
+	if (sees.length) {
+		var thisSee = this.see;
+		sees.map(function($){thisSee.push($.desc);});
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "FILE", new JSDOC.DocComment("/**@see The other thing.*"+"/"));
+		is(sym.see, "The other thing.", "@see tag, desc is found.");
+	*/
+	
+	// @class
+	var classes = this.comment.getTag("class");
+	if (classes.length) {
+		this.isa = "CONSTRUCTOR";
+		this.classDesc = classes[0].desc; // desc can't apply to the constructor as there is none.
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@class This describes the class.*"+"/"));
+		is(sym.isa, "CONSTRUCTOR", "@class tag, makes symbol a constructor.");
+		is(sym.classDesc, "This describes the class.", "@class tag, class description is found.");
+	*/
+	
+	// @namespace
+	var namespaces = this.comment.getTag("namespace");
+	if (namespaces.length) {
+		this.classDesc = namespaces[0].desc;
+		this.isNamespace = true;
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@namespace This describes the namespace.*"+"/"));
+		is(sym.classDesc, "This describes the namespace.", "@namespace tag, class description is found.");
+	*/
+	
+	// @param
+	var params = this.comment.getTag("param");
+	if (params.length) {
+		// user-defined params overwrite those with same name defined by the parser
+		var thisParams = this.params;
+
+		if (thisParams.length == 0) { // none exist yet, so just bung all these user-defined params straight in
+			this.params = params;
+		}
+		else { // need to overlay these user-defined params on to existing parser-defined params
+			for (var i = 0, l = params.length; i < l; i++) {
+				if (thisParams[i]) {
+					if (params[i].type) thisParams[i].type = params[i].type;
+					thisParams[i].name = params[i].name;
+					thisParams[i].desc = params[i].desc;
+					thisParams[i].isOptional = params[i].isOptional;
+					thisParams[i].defaultValue = params[i].defaultValue;
+				}
+				else thisParams[i] = params[i];
+			}
+		}
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [{type: "array", name: "pages"}], "FUNCTION", new JSDOC.DocComment("/**Description.*"+"/"));
+		is(sym.params.length, 1, "parser defined param is found.");
+		
+		sym = new JSDOC.Symbol("foo", [], "FUNCTION", new JSDOC.DocComment("/**Description.\n@param {array} pages*"+"/"));
+		is(sym.params.length, 1, "user defined param is found.");
+		is(sym.params[0].type, "array", "user defined param type is found.");
+		is(sym.params[0].name, "pages", "user defined param name is found.");
+		
+		sym = new JSDOC.Symbol("foo", [{type: "array", name: "pages"}], "FUNCTION", new JSDOC.DocComment("/**Description.\n@param {string} uid*"+"/"));
+		is(sym.params.length, 1, "user defined param overwrites parser defined param.");
+		is(sym.params[0].type, "string", "user defined param type overwrites parser defined param type.");
+		is(sym.params[0].name, "uid", "user defined param name overwrites parser defined param name.");
+	
+		sym = new JSDOC.Symbol("foo", [{type: "array", name: "pages"}, {type: "number", name: "count"}], "FUNCTION", new JSDOC.DocComment("/**Description.\n@param {string} uid*"+"/"));
+		is(sym.params.length, 2, "user defined params  overlay parser defined params.");
+		is(sym.params[1].type, "number", "user defined param type overlays parser defined param type.");
+		is(sym.params[1].name, "count", "user defined param name overlays parser defined param name.");
+
+		sym = new JSDOC.Symbol("foo", [], "FUNCTION", new JSDOC.DocComment("/**Description.\n@param {array} pages The pages description.*"+"/"));
+		is(sym.params.length, 1, "user defined param with description is found.");
+		is(sym.params[0].desc, "The pages description.", "user defined param description is found.");
+	*/
+	
+	// @constructor
+	if (this.comment.getTag("constructor").length) {
+		this.isa = "CONSTRUCTOR";
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@constructor*"+"/"));
+		is(sym.isa, "CONSTRUCTOR", "@constructor tag, makes symbol a constructor.");
+	*/
+	
+	// @static
+	if (this.comment.getTag("static").length) {
+		this.isStatic = true;
+		if (this.isa == "CONSTRUCTOR") {
+			this.isNamespace = true;
+		}
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@static\n@constructor*"+"/"));
+		is(sym.isStatic, true, "@static tag, makes isStatic true.");
+		is(sym.isNamespace, true, "@static and @constructor tag, makes isNamespace true.");
+	*/
+	
+	// @inner
+	if (this.comment.getTag("inner").length) {
+		this.isInner = true;
+		this.isStatic = false;
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@inner*"+"/"));
+		is(sym.isStatic, false, "@inner tag, makes isStatic false.");
+		is(sym.isInner, true, "@inner makes isInner true.");
+	*/
+	
+	// @name
+	var names = this.comment.getTag("name");
+	if (names.length) {
+		this.name = names[0].desc;
+	}
+	
+	/*t:
+		// todo
+	*/
+	
+	// @field
+	if (this.comment.getTag("field").length) {
+		this.isa = "OBJECT";
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "FUNCTION", new JSDOC.DocComment("/**@field*"+"/"));
+		is(sym.isa, "OBJECT", "@field tag, makes symbol an object.");
+	*/
+	
+	// @function
+	if (this.comment.getTag("function").length) {
+		this.isa = "FUNCTION";
+		if (/event:/.test(this.alias)) this.isEvent = true;
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@function*"+"/"));
+		is(sym.isa, "FUNCTION", "@function tag, makes symbol a function.");
+	*/
+	
+	// @event
+	var events = this.comment.getTag("event");
+	if (events.length) {
+		this.isa = "FUNCTION";
+		this.isEvent = true;
+		if (!/event:/.test(this.alias))
+			this.alias = this.alias.replace(/^(.*[.#-])([^.#-]+)$/, "$1event:$2");
+	}
+	
+	/*t:
+		var sym = new JSDOC.Symbol("foo", [], "OBJECT", new JSDOC.DocComment("/**@event*"+"/"));
+		is(sym.isa, "FUNCTION", "@event tag, makes symbol a function.");
+		is(sym.isEvent, true, "@event makes isEvent true.");
+	*/
+	
+	// @fires
+	var fires = this.comment.getTag("fires");
+	if (fires.length) {
+		for (var i = 0; i < fires.length; i++) {
+			this.fires.push(fires[i].desc);
+		}
+	}
+	
+	/*t:
+		// todo
+	*/
+	
+	// @property
+	var properties = this.comment.getTag("property");
+	if (properties.length) {
+		thisProperties = this.properties;
+		for (var i = 0; i < properties.length; i++) {
+			var property = new JSDOC.Symbol(this.alias+"#"+properties[i].name, [], "OBJECT", new JSDOC.DocComment("/**"+properties[i].desc+"*/"));
+			// TODO: shouldn't the following happen in the addProperty method of Symbol?
+			if (properties[i].type) property.type = properties[i].type;
+			if (properties[i].defaultValue) property.defaultValue = properties[i].defaultValue;
+			this.addProperty(property);
+			if (!JSDOC.Parser.symbols.getSymbolByName(property.name))
+				JSDOC.Parser.addSymbol(property);
+		}
+	}
+	
+	/*t:
+		// todo
+	*/
+
+	// @return
+	var returns = this.comment.getTag("return");
+	if (returns.length) { // there can be many return tags in a single doclet
+		this.returns = returns;
+		this.type = returns.map(function($){return $.type}).join(", ");
+	}
+	
+	/*t:
+		// todo
+	*/
+	
+	// @exception
+	this.exceptions = this.comment.getTag("throws");
+	
+	/*t:
+		// todo
+	*/
+	
+	// @requires
+	var requires = this.comment.getTag("requires");
+	if (requires.length) {
+		this.requires = requires.map(function($){return $.desc});
+	}
+	
+	/*t:
+		// todo
+	*/
+	
+	// @type
+	var types = this.comment.getTag("type");
+	if (types.length) {
+		this.type = types[0].desc; //multiple type tags are ignored
+	}
+	
+	/*t:
+		// todo
+	*/
+	
+	// @private
+	if (this.comment.getTag("private").length || this.isInner) {
+		this.isPrivate = true;
+	}
+	
+	// @ignore
+	if (this.comment.getTag("ignore").length) {
+		this.isIgnored = true;
+	}
+	
+	/*t:
+		// todo
+	*/
+	
+	// @inherits ... as ...
+	var inherits = this.comment.getTag("inherits");
+	if (inherits.length) {
+		for (var i = 0; i < inherits.length; i++) {
+			if (/^\s*([a-z$0-9_.#:-]+)(?:\s+as\s+([a-z$0-9_.#:-]+))?/i.test(inherits[i].desc)) {
+				var inAlias = RegExp.$1;
+				var inAs = RegExp.$2 || inAlias;
+
+				if (inAlias) inAlias = inAlias.replace(/\.prototype\.?/g, "#");
+				
+				if (inAs) {
+					inAs = inAs.replace(/\.prototype\.?/g, "#");
+					inAs = inAs.replace(/^this\.?/, "#");
+				}
+
+				if (inAs.indexOf(inAlias) != 0) { //not a full namepath
+					var joiner = ".";
+					if (this.alias.charAt(this.alias.length-1) == "#" || inAs.charAt(0) == "#") {
+						joiner = "";
+					}
+					inAs = this.alias + joiner + inAs;
+				}
+			}
+			this.inherits.push({alias: inAlias, as: inAs});
+		}
+	}
+	
+	/*t:
+		// todo
+	*/
+	
+	// @augments
+	this.augments = this.comment.getTag("augments");
+	
+	// @default
+	var defaults = this.comment.getTag("default");
+	if (defaults.length) {
+		if (this.is("OBJECT")) {
+			this.defaultValue = defaults[0].desc;
+		}
+	}
+	
+	/*t:
+		// todo
+	*/
+	
+	// @memberOf
+	var memberOfs = this.comment.getTag("memberOf");
+	if (memberOfs.length) {
+		this.memberOf = memberOfs[0].desc;
+		this.memberOf = this.memberOf.replace(/\.prototype\.?/g, "#");
+	}
+
+	/*t:
+		// todo
+	*/
+	
+	// @public
+	if (this.comment.getTag("public").length) {
+		this.isPrivate = false;
+	}
+	
+	/*t:
+		// todo
+	*/
+		
+	if (JSDOC.PluginManager) {
+		JSDOC.PluginManager.run("onSetTags", this);
+	}
+}
+
+JSDOC.Symbol.prototype.is = function(what) {
+	return this.isa === what;
+}
+
+JSDOC.Symbol.prototype.isBuiltin = function() {
+	return JSDOC.Lang.isBuiltin(this.alias);
+}
+
+JSDOC.Symbol.prototype.setType = function(/**String*/comment, /**Boolean*/overwrite) {
+	if (!overwrite && this.type) return;
+	var typeComment = JSDOC.DocComment.unwrapComment(comment);
+	this.type = typeComment;
+}
+
+JSDOC.Symbol.prototype.inherit = function(symbol) {
+	if (!this.hasMember(symbol.name) && !symbol.isInner) {
+		if (symbol.is("FUNCTION"))
+			this.methods.push(symbol);
+		else if (symbol.is("OBJECT"))
+			this.properties.push(symbol);
+	}
+}
+
+JSDOC.Symbol.prototype.hasMember = function(name) {
+	return (this.hasMethod(name) || this.hasProperty(name));
+}
+
+JSDOC.Symbol.prototype.addMember = function(symbol) {
+	if (symbol.is("FUNCTION")) { this.addMethod(symbol); }
+	else if (symbol.is("OBJECT")) { this.addProperty(symbol); }
+}
+
+JSDOC.Symbol.prototype.hasMethod = function(name) {
+	var thisMethods = this.methods;
+	for (var i = 0, l = thisMethods.length; i < l; i++) {
+		if (thisMethods[i].name == name) return true;
+		if (thisMethods[i].alias == name) return true;
+	}
+	return false;
+}
+
+JSDOC.Symbol.prototype.addMethod = function(symbol) {
+	var methodAlias = symbol.alias;
+	var thisMethods = this.methods;
+	for (var i = 0, l = thisMethods.length; i < l; i++) {
+		if (thisMethods[i].alias == methodAlias) {
+			thisMethods[i] = symbol; // overwriting previous method
+			return;
+		}
+	}
+	thisMethods.push(symbol); // new method with this alias
+}
+
+JSDOC.Symbol.prototype.hasProperty = function(name) {
+	var thisProperties = this.properties;
+	for (var i = 0, l = thisProperties.length; i < l; i++) {
+		if (thisProperties[i].name == name) return true;
+		if (thisProperties[i].alias == name) return true;
+	}
+	return false;
+}
+
+JSDOC.Symbol.prototype.addProperty = function(symbol) {
+	var propertyAlias = symbol.alias;
+	var thisProperties = this.properties;
+	for (var i = 0, l = thisProperties.length; i < l; i++) {
+		if (thisProperties[i].alias == propertyAlias) {
+			thisProperties[i] = symbol; // overwriting previous property
+			return;
+		}
+	}
+
+	thisProperties.push(symbol); // new property with this alias
+}
+
+JSDOC.Symbol.srcFile = ""; //running reference to the current file being parsed

+ 242 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/SymbolSet.js

@@ -0,0 +1,242 @@
+/** @constructor */
+JSDOC.SymbolSet = function() {
+	this.init();
+}
+
+JSDOC.SymbolSet.prototype.init = function() {
+	this._index = new Hash();
+}
+
+JSDOC.SymbolSet.prototype.keys = function() {
+	return this._index.keys();
+}
+
+JSDOC.SymbolSet.prototype.hasSymbol = function(alias) {
+	return this._index.hasKey(alias);
+}
+
+JSDOC.SymbolSet.prototype.addSymbol = function(symbol) {
+	if (this.hasSymbol(symbol.alias)) {
+		LOG.warn("Overwriting symbol documentation for: "+symbol.alias + ".");
+	}
+	this._index.set(symbol.alias, symbol);
+}
+
+JSDOC.SymbolSet.prototype.getSymbol = function(alias) {
+	if (this.hasSymbol(alias)) return this._index.get(alias);
+}
+
+JSDOC.SymbolSet.prototype.getSymbolByName = function(name) {
+	for (var p = this._index.first(); p; p = this._index.next()) {
+		var symbol = p.value;
+		if (symbol.name == name) return symbol;
+	}
+}
+
+JSDOC.SymbolSet.prototype.toArray = function() {
+	return this._index.values();
+}
+
+JSDOC.SymbolSet.prototype.deleteSymbol = function(alias) {
+	if (!this.hasSymbol(alias)) return;
+	this._index.drop(alias);
+}
+
+JSDOC.SymbolSet.prototype.renameSymbol = function(oldName, newName) {
+	// todo: should check if oldname or newname already exist
+	this._index.replace(oldName, newName);
+	this._index.get(newName).alias = newName;
+	return newName;
+}
+
+JSDOC.SymbolSet.prototype.relate = function() {
+	this.resolveBorrows();
+	this.resolveMemberOf();
+	this.resolveAugments();
+}
+
+JSDOC.SymbolSet.prototype.resolveBorrows = function() {
+	for (var p = this._index.first(); p; p = this._index.next()) {
+		var symbol = p.value;
+		if (symbol.is("FILE") || symbol.is("GLOBAL")) continue;
+		
+		var borrows = symbol.inherits;
+		for (var i = 0; i < borrows.length; i++) {
+		
+if (/#$/.test(borrows[i].alias)) {
+	LOG.warn("Attempted to borrow entire instance of "+borrows[i].alias+" but that feature is not yet implemented.");
+	return;
+}
+			var borrowed = this.getSymbol(borrows[i].alias);
+			
+			if (!borrowed) {
+				LOG.warn("Can't borrow undocumented "+borrows[i].alias+".");
+				continue;
+			}
+
+			if (borrows[i].as == borrowed.alias) {
+				var assumedName = borrowed.name.split(/([#.-])/).pop();
+				borrows[i].as = symbol.name+RegExp.$1+assumedName;
+				LOG.inform("Assuming borrowed as name is "+borrows[i].as+" but that feature is experimental.");
+			}
+			
+			var borrowAsName = borrows[i].as;
+			var borrowAsAlias = borrowAsName;
+			if (!borrowAsName) {
+				LOG.warn("Malformed @borrow, 'as' is required.");
+				continue;
+			}
+			
+			if (borrowAsName.length > symbol.alias.length && borrowAsName.indexOf(symbol.alias) == 0) {
+				borrowAsName = borrowAsName.replace(borrowed.alias, "")
+			}
+			else {
+				var joiner = "";
+				if (borrowAsName.charAt(0) != "#") joiner = ".";
+				borrowAsAlias = borrowed.alias + joiner + borrowAsName;
+			}
+			
+			borrowAsName = borrowAsName.replace(/^[#.]/, "");
+					
+			if (this.hasSymbol(borrowAsAlias)) continue;
+
+			var clone = borrowed.clone();
+			clone.name = borrowAsName;
+			clone.alias = borrowAsAlias;
+			this.addSymbol(clone);
+		}
+	}
+}
+
+JSDOC.SymbolSet.prototype.resolveMemberOf = function() {
+	for (var p = this._index.first(); p; p = this._index.next()) {
+		var symbol = p.value;
+
+		if (symbol.is("FILE") || symbol.is("GLOBAL")) continue;
+		
+		// the memberOf value was provided in the @memberOf tag
+		else if (symbol.memberOf) {			
+			// like foo.bar is a memberOf foo
+			if (symbol.alias.indexOf(symbol.memberOf) == 0) {
+				var memberMatch = new RegExp("^("+symbol.memberOf+")[.#-]?(.+)$");
+				var aliasParts = symbol.alias.match(memberMatch);
+				
+				if (aliasParts) {
+					symbol.memberOf = aliasParts[1];
+					symbol.name = aliasParts[2];
+				}
+				
+				var nameParts = symbol.name.match(memberMatch);
+
+				if (nameParts) {
+					symbol.name = nameParts[2];
+				}
+			}
+			// like bar is a memberOf foo
+			else {
+				var joiner = symbol.memberOf.charAt(symbol.memberOf.length-1);
+				if (!/[.#-]/.test(joiner)) symbol.memberOf += ".";
+				this.renameSymbol(symbol.alias, symbol.memberOf + symbol.name);
+			}
+		}
+		// the memberOf must be calculated
+		else {
+			var parts = symbol.alias.match(/^(.*[.#-])([^.#-]+)$/);
+
+			if (parts) {
+				symbol.memberOf = parts[1];
+				symbol.name = parts[2];				
+			}
+		}
+
+		// set isStatic, isInner
+		if (symbol.memberOf) {
+			switch (symbol.memberOf.charAt(symbol.memberOf.length-1)) {
+				case '#' :
+					symbol.isStatic = false;
+					symbol.isInner = false;
+				break;
+				case '.' :
+					symbol.isStatic = true;
+					symbol.isInner = false;
+				break;
+				case '-' :
+					symbol.isStatic = false;
+					symbol.isInner = true;
+				break;
+				default: // memberOf ends in none of the above
+					symbol.isStatic = true;
+				break;
+			}
+		}
+		
+		// unowned methods and fields belong to the global object
+		if (!symbol.is("CONSTRUCTOR") && !symbol.isNamespace && symbol.memberOf == "") {
+			symbol.memberOf = "_global_";
+		}
+
+		// clean up
+		if (symbol.memberOf.match(/[.#-]$/)) {
+			symbol.memberOf = symbol.memberOf.substr(0, symbol.memberOf.length-1);
+		}
+		// add to parent's methods or properties list
+		if (symbol.memberOf) {
+
+			var container = this.getSymbol(symbol.memberOf);
+			if (!container) {
+				if (JSDOC.Lang.isBuiltin(symbol.memberOf)) container = JSDOC.Parser.addBuiltin(symbol.memberOf);
+				else {
+					LOG.warn("Trying to document "+symbol.name +" as a member of undocumented symbol "+symbol.memberOf+".");
+				}
+			}
+			
+			if (container) container.addMember(symbol);
+		}
+	}
+}
+
+JSDOC.SymbolSet.prototype.resolveAugments = function() {
+	for (var p = this._index.first(); p; p = this._index.next()) {
+		var symbol = p.value;
+		
+		if (symbol.alias == "_global_" || symbol.is("FILE")) continue;
+		JSDOC.SymbolSet.prototype.walk.apply(this, [symbol]);
+	}
+}
+
+JSDOC.SymbolSet.prototype.walk = function(symbol) {
+	var augments = symbol.augments;
+	for(var i = 0; i < augments.length; i++) {
+		var contributer = this.getSymbol(augments[i]);
+		if (!contributer && JSDOC.Lang.isBuiltin(''+augments[i])) {
+			contributer = new JSDOC.Symbol("_global_."+augments[i], [], augments[i], new JSDOC.DocComment("Built in."));
+			contributer.isNamespace = true;
+			contributer.srcFile = "";
+			contributer.isPrivate = false;
+			JSDOC.Parser.addSymbol(contributer);
+		}
+		
+		if (contributer) {			
+			if (contributer.augments.length) {
+				JSDOC.SymbolSet.prototype.walk.apply(this, [contributer]);
+			}
+			
+			symbol.inheritsFrom.push(contributer.alias);
+			//if (!isUnique(symbol.inheritsFrom)) {
+			//	LOG.warn("Can't resolve augments: Circular reference: "+symbol.alias+" inherits from "+contributer.alias+" more than once.");
+			//}
+			//else {
+				var cmethods = contributer.methods;
+				var cproperties = contributer.properties;
+				
+				for (var ci = 0, cl = cmethods.length; ci < cl; ci++) {
+					if (!cmethods[ci].isStatic) symbol.inherit(cmethods[ci]);
+				}
+				for (var ci = 0, cl = cproperties.length; ci < cl; ci++) {
+					if (!cproperties[ci].isStatic) symbol.inherit(cproperties[ci]);
+				}	
+			//}
+		}
+		else LOG.warn("Can't augment contributer: "+augments[i]+", not found.");
+	}
+}

+ 41 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/TextStream.js

@@ -0,0 +1,41 @@
+
+/**
+	@constructor
+*/
+JSDOC.TextStream = function(text) {
+	if (typeof(text) == "undefined") text = "";
+	text = ""+text;
+	this.text = text;
+	this.cursor = 0;
+}
+
+JSDOC.TextStream.prototype.look = function(n) {
+	if (typeof n == "undefined") n = 0;
+	
+	if (this.cursor+n < 0 || this.cursor+n >= this.text.length) {
+		var result = new String("");
+		result.eof = true;
+		return result;
+	}
+	return this.text.charAt(this.cursor+n);
+}
+
+JSDOC.TextStream.prototype.next = function(n) {
+	if (typeof n == "undefined") n = 1;
+	if (n < 1) return null;
+	
+	var pulled = "";
+	for (var i = 0; i < n; i++) {
+		if (this.cursor+i < this.text.length) {
+			pulled += this.text.charAt(this.cursor+i);
+		}
+		else {
+			var result = new String("");
+			result.eof = true;
+			return result;
+		}
+	}
+
+	this.cursor += n;
+	return pulled;
+}

+ 18 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Token.js

@@ -0,0 +1,18 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/**
+	@constructor
+*/
+JSDOC.Token = function(data, type, name) {
+	this.data = data;
+	this.type = type;
+	this.name = name;
+}
+
+JSDOC.Token.prototype.toString = function() { 
+	return "<"+this.type+" name=\""+this.name+"\">"+this.data+"</"+this.type+">";
+}
+
+JSDOC.Token.prototype.is = function(what) {
+	return this.name === what || this.type === what;
+}

+ 332 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/TokenReader.js

@@ -0,0 +1,332 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/**
+	@class Search a {@link JSDOC.TextStream} for language tokens.
+*/
+JSDOC.TokenReader = function() {
+	this.keepDocs = true;
+	this.keepWhite = false;
+	this.keepComments = false;
+}
+
+/**
+	@type {JSDOC.Token[]}
+ */
+JSDOC.TokenReader.prototype.tokenize = function(/**JSDOC.TextStream*/stream) {
+	var tokens = [];
+	/**@ignore*/ tokens.last    = function() { return tokens[tokens.length-1]; }
+	/**@ignore*/ tokens.lastSym = function() {
+		for (var i = tokens.length-1; i >= 0; i--) {
+			if (!(tokens[i].is("WHIT") || tokens[i].is("COMM"))) return tokens[i];
+		}
+	}
+
+	while (!stream.look().eof) {
+		if (this.read_mlcomment(stream, tokens)) continue;
+		if (this.read_slcomment(stream, tokens)) continue;
+		if (this.read_dbquote(stream, tokens))   continue;
+		if (this.read_snquote(stream, tokens))   continue;
+		if (this.read_regx(stream, tokens))      continue;
+		if (this.read_numb(stream, tokens))      continue;
+		if (this.read_punc(stream, tokens))      continue;
+		if (this.read_newline(stream, tokens))   continue;
+		if (this.read_space(stream, tokens))     continue;
+		if (this.read_word(stream, tokens))      continue;
+		
+		// if execution reaches here then an error has happened
+		tokens.push(new JSDOC.Token(stream.next(), "TOKN", "UNKNOWN_TOKEN"));
+	}
+	return tokens;
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_word = function(/**JSDOC.TokenStream*/stream, tokens) {
+	var found = "";
+	while (!stream.look().eof && JSDOC.Lang.isWordChar(stream.look())) {
+		found += stream.next();
+	}
+	
+	if (found === "") {
+		return false;
+	}
+	else {
+		var name;
+		if ((name = JSDOC.Lang.keyword(found))) tokens.push(new JSDOC.Token(found, "KEYW", name));
+		else tokens.push(new JSDOC.Token(found, "NAME", "NAME"));
+		return true;
+	}
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_punc = function(/**JSDOC.TokenStream*/stream, tokens) {
+	var found = "";
+	var name;
+	while (!stream.look().eof && JSDOC.Lang.punc(found+stream.look())) {
+		found += stream.next();
+	}
+	
+	if (found === "") {
+		return false;
+	}
+	else {
+		tokens.push(new JSDOC.Token(found, "PUNC", JSDOC.Lang.punc(found)));
+		return true;
+	}
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_space = function(/**JSDOC.TokenStream*/stream, tokens) {
+	var found = "";
+	
+	while (!stream.look().eof && JSDOC.Lang.isSpace(stream.look())) {
+		found += stream.next();
+	}
+	
+	if (found === "") {
+		return false;
+	}
+	else {
+		if (this.collapseWhite) found = " ";
+		if (this.keepWhite) tokens.push(new JSDOC.Token(found, "WHIT", "SPACE"));
+		return true;
+	}
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_newline = function(/**JSDOC.TokenStream*/stream, tokens) {
+	var found = "";
+	
+	while (!stream.look().eof && JSDOC.Lang.isNewline(stream.look())) {
+		found += stream.next();
+	}
+	
+	if (found === "") {
+		return false;
+	}
+	else {
+		if (this.collapseWhite) found = "\n";
+		if (this.keepWhite) tokens.push(new JSDOC.Token(found, "WHIT", "NEWLINE"));
+		return true;
+	}
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_mlcomment = function(/**JSDOC.TokenStream*/stream, tokens) {
+	if (stream.look() == "/" && stream.look(1) == "*") {
+		var found = stream.next(2);
+		
+		while (!stream.look().eof && !(stream.look(-1) == "/" && stream.look(-2) == "*")) {
+			found += stream.next();
+		}
+		
+		// to start doclet we allow /** or /*** but not /**/ or /****
+		if (/^\/\*\*([^\/]|\*[^*])/.test(found) && this.keepDocs) tokens.push(new JSDOC.Token(found, "COMM", "JSDOC"));
+		else if (this.keepComments) tokens.push(new JSDOC.Token(found, "COMM", "MULTI_LINE_COMM"));
+		return true;
+	}
+	return false;
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_slcomment = function(/**JSDOC.TokenStream*/stream, tokens) {
+	var found;
+	if (
+		(stream.look() == "/" && stream.look(1) == "/" && (found=stream.next(2)))
+		|| 
+		(stream.look() == "<" && stream.look(1) == "!" && stream.look(2) == "-" && stream.look(3) == "-" && (found=stream.next(4)))
+	) {
+		
+		while (!stream.look().eof && !JSDOC.Lang.isNewline(stream.look())) {
+			found += stream.next();
+		}
+		
+		if (this.keepComments) {
+			tokens.push(new JSDOC.Token(found, "COMM", "SINGLE_LINE_COMM"));
+		}
+		return true;
+	}
+	return false;
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_dbquote = function(/**JSDOC.TokenStream*/stream, tokens) {
+	if (stream.look() == "\"") {
+		// find terminator
+		var string = stream.next();
+		
+		while (!stream.look().eof) {
+			if (stream.look() == "\\") {
+				if (JSDOC.Lang.isNewline(stream.look(1))) {
+					do {
+						stream.next();
+					} while (!stream.look().eof && JSDOC.Lang.isNewline(stream.look()));
+					string += "\\\n";
+				}
+				else {
+					string += stream.next(2);
+				}
+			}
+			else if (stream.look() == "\"") {
+				string += stream.next();
+				tokens.push(new JSDOC.Token(string, "STRN", "DOUBLE_QUOTE"));
+				return true;
+			}
+			else {
+				string += stream.next();
+			}
+		}
+	}
+	return false; // error! unterminated string
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_snquote = function(/**JSDOC.TokenStream*/stream, tokens) {
+	if (stream.look() == "'") {
+		// find terminator
+		var string = stream.next();
+		
+		while (!stream.look().eof) {
+			if (stream.look() == "\\") { // escape sequence
+				string += stream.next(2);
+			}
+			else if (stream.look() == "'") {
+				string += stream.next();
+				tokens.push(new JSDOC.Token(string, "STRN", "SINGLE_QUOTE"));
+				return true;
+			}
+			else {
+				string += stream.next();
+			}
+		}
+	}
+	return false; // error! unterminated string
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_numb = function(/**JSDOC.TokenStream*/stream, tokens) {
+	if (stream.look() === "0" && stream.look(1) == "x") {
+		return this.read_hex(stream, tokens);
+	}
+	
+	var found = "";
+	
+	while (!stream.look().eof && JSDOC.Lang.isNumber(found+stream.look())){
+		found += stream.next();
+	}
+	
+	if (found === "") {
+		return false;
+	}
+	else {
+		if (/^0[0-7]/.test(found)) tokens.push(new JSDOC.Token(found, "NUMB", "OCTAL"));
+		else tokens.push(new JSDOC.Token(found, "NUMB", "DECIMAL"));
+		return true;
+	}
+}
+/*t:
+	requires("../lib/JSDOC/TextStream.js");
+	requires("../lib/JSDOC/Token.js");
+	requires("../lib/JSDOC/Lang.js");
+	
+	plan(3, "testing JSDOC.TokenReader.prototype.read_numb");
+	
+	//// setup
+	var src = "function foo(num){while (num+8.0 >= 0x20 && num < 0777){}}";
+	var tr = new JSDOC.TokenReader();
+	var tokens = tr.tokenize(new JSDOC.TextStream(src));
+	
+	var hexToken, octToken, decToken;
+	for (var i = 0; i < tokens.length; i++) {
+		if (tokens[i].name == "HEX_DEC") hexToken = tokens[i];
+		if (tokens[i].name == "OCTAL") octToken = tokens[i];
+		if (tokens[i].name == "DECIMAL") decToken = tokens[i];
+	}
+	////
+	
+	is(decToken.data, "8.0", "decimal number is found in source.");
+	is(hexToken.data, "0x20", "hexdec number is found in source (issue #99).");
+	is(octToken.data, "0777", "octal number is found in source.");
+*/
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_hex = function(/**JSDOC.TokenStream*/stream, tokens) {
+	var found = stream.next(2);
+	
+	while (!stream.look().eof) {
+		if (JSDOC.Lang.isHexDec(found) && !JSDOC.Lang.isHexDec(found+stream.look())) { // done
+			tokens.push(new JSDOC.Token(found, "NUMB", "HEX_DEC"));
+			return true;
+		}
+		else {
+			found += stream.next();
+		}
+	}
+	return false;
+}
+
+/**
+	@returns {Boolean} Was the token found?
+ */
+JSDOC.TokenReader.prototype.read_regx = function(/**JSDOC.TokenStream*/stream, tokens) {
+	var last;
+	if (
+		stream.look() == "/"
+		&& 
+		(
+			
+			(
+				!(last = tokens.lastSym()) // there is no last, the regex is the first symbol
+				|| 
+				(
+					   !last.is("NUMB")
+					&& !last.is("NAME")
+					&& !last.is("RIGHT_PAREN")
+					&& !last.is("RIGHT_BRACKET")
+				)
+			)
+		)
+	) {
+		var regex = stream.next();
+		
+		while (!stream.look().eof) {
+			if (stream.look() == "\\") { // escape sequence
+				regex += stream.next(2);
+			}
+			else if (stream.look() == "/") {
+				regex += stream.next();
+				
+				while (/[gmi]/.test(stream.look())) {
+					regex += stream.next();
+				}
+				
+				tokens.push(new JSDOC.Token(regex, "REGX", "REGX"));
+				return true;
+			}
+			else {
+				regex += stream.next();
+			}
+		}
+		// error: unterminated regex
+	}
+	return false;
+}

+ 133 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/TokenStream.js

@@ -0,0 +1,133 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/**
+	@constructor
+*/
+JSDOC.TokenStream = function(tokens) {
+	this.tokens = (tokens || []);
+	this.rewind();
+}
+
+/**
+	@constructor
+	@private
+*/
+function VoidToken(/**String*/type) {
+	this.toString = function() {return "<VOID type=\""+type+"\">"};
+	this.is = function(){return false;}
+}
+
+JSDOC.TokenStream.prototype.rewind = function() {
+	this.cursor = -1;
+}
+
+/**
+	@type JSDOC.Token
+*/
+JSDOC.TokenStream.prototype.look = function(/**Number*/n, /**Boolean*/considerWhitespace) {
+	if (typeof n == "undefined") n = 0;
+
+	if (considerWhitespace == true) {
+		if (this.cursor+n < 0 || this.cursor+n > this.tokens.length) return {};
+		return this.tokens[this.cursor+n];
+	}
+	else {
+		var count = 0;
+		var i = this.cursor;
+
+		while (true) {
+			if (i < 0) return new JSDOC.Token("", "VOID", "START_OF_STREAM");
+			else if (i > this.tokens.length) return new JSDOC.Token("", "VOID", "END_OF_STREAM");
+
+			if (i != this.cursor && (this.tokens[i] === undefined || this.tokens[i].is("WHIT"))) {
+				if (n < 0) i--; else i++;
+				continue;
+			}
+			
+			if (count == Math.abs(n)) {
+				return this.tokens[i];
+			}
+			count++;
+			(n < 0)? i-- : i++;
+		}
+
+		return new JSDOC.Token("", "VOID", "STREAM_ERROR"); // because null isn't an object and caller always expects an object
+	}
+}
+
+/**
+	@type JSDOC.Token|JSDOC.Token[]
+*/
+JSDOC.TokenStream.prototype.next = function(/**Number*/howMany) {
+	if (typeof howMany == "undefined") howMany = 1;
+	if (howMany < 1) return null;
+	var got = [];
+
+	for (var i = 1; i <= howMany; i++) {
+		if (this.cursor+i >= this.tokens.length) {
+			return null;
+		}
+		got.push(this.tokens[this.cursor+i]);
+	}
+	this.cursor += howMany;
+
+	if (howMany == 1) {
+		return got[0];
+	}
+	else return got;
+}
+
+/**
+	@type JSDOC.Token[]
+*/
+JSDOC.TokenStream.prototype.balance = function(/**String*/start, /**String*/stop) {
+	if (!stop) stop = JSDOC.Lang.matching(start);
+	
+	var depth = 0;
+	var got = [];
+	var started = false;
+	
+	while ((token = this.look())) {
+		if (token.is(start)) {
+			depth++;
+			started = true;
+		}
+		
+		if (started) {
+			got.push(token);
+		}
+		
+		if (token.is(stop)) {
+			depth--;
+			if (depth == 0) return got;
+		}
+		if (!this.next()) break;
+	}
+}
+
+JSDOC.TokenStream.prototype.getMatchingToken = function(/**String*/start, /**String*/stop) {
+	var depth = 0;
+	var cursor = this.cursor;
+	
+	if (!start) {
+		start = JSDOC.Lang.matching(stop);
+		depth = 1;
+	}
+	if (!stop) stop = JSDOC.Lang.matching(start);
+	
+	while ((token = this.tokens[cursor])) {
+		if (token.is(start)) {
+			depth++;
+		}
+		
+		if (token.is(stop) && cursor) {
+			depth--;
+			if (depth == 0) return this.tokens[cursor];
+		}
+		cursor++;
+	}
+}
+
+JSDOC.TokenStream.prototype.insertAhead = function(/**JSDOC.Token*/token) {
+	this.tokens.splice(this.cursor+1, 0, token);
+}

+ 32 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Util.js

@@ -0,0 +1,32 @@
+/**
+ * @namespace
+ * @deprecated Use {@link FilePath} instead.
+ */
+JSDOC.Util = {
+}
+
+/**
+ * @deprecated Use {@link FilePath.fileName} instead.
+ */
+JSDOC.Util.fileName = function(path) {
+	LOG.warn("JSDOC.Util.fileName is deprecated. Use FilePath.fileName instead.");
+	var nameStart = Math.max(path.lastIndexOf("/")+1, path.lastIndexOf("\\")+1, 0);
+	return path.substring(nameStart);
+}
+
+/**
+ * @deprecated Use {@link FilePath.fileExtension} instead.
+ */
+JSDOC.Util.fileExtension = function(filename) {
+	LOG.warn("JSDOC.Util.fileExtension is deprecated. Use FilePath.fileExtension instead.");
+	return filename.split(".").pop().toLowerCase();
+};
+
+/**
+ * @deprecated Use {@link FilePath.dir} instead.
+ */
+JSDOC.Util.dir = function(path) {
+	LOG.warn("JSDOC.Util.dir is deprecated. Use FilePath.dir instead.");
+	var nameStart = Math.max(path.lastIndexOf("/")+1, path.lastIndexOf("\\")+1, 0);
+	return path.substring(0, nameStart-1);
+}

+ 507 - 0
jsdoc_toolkit-2.3.3-beta/app/lib/JSDOC/Walker.js

@@ -0,0 +1,507 @@
+if (typeof JSDOC == "undefined") JSDOC = {};
+
+/** @constructor */
+JSDOC.Walker = function(/**JSDOC.TokenStream*/ts) {
+	this.init();
+	if (typeof ts != "undefined") {
+		this.walk(ts);
+	}
+}
+
+JSDOC.Walker.prototype.init = function() {
+	this.ts = null;
+
+	var globalSymbol = new JSDOC.Symbol("_global_", [], "GLOBAL", new JSDOC.DocComment(""));
+	globalSymbol.isNamespace = true;
+	globalSymbol.srcFile = "";
+	globalSymbol.isPrivate = false;
+	JSDOC.Parser.addSymbol(globalSymbol);
+	this.lastDoc = null;
+	this.token = null;
+	
+	/**
+		The chain of symbols under which we are currently nested.
+		@type Array
+	*/
+	this.namescope = [globalSymbol];
+	this.namescope.last = function(n){ if (!n) n = 0; return this[this.length-(1+n)] || "" };
+}
+
+JSDOC.Walker.prototype.walk = function(/**JSDOC.TokenStream*/ts) {
+	this.ts = ts;
+	while (this.token = this.ts.look()) {
+		if (this.token.popNamescope) {
+			
+			var symbol = this.namescope.pop();
+			if (symbol.is("FUNCTION")) {
+				if (this.ts.look(1).is("LEFT_PAREN") && symbol.comment.getTag("function").length == 0) {
+					symbol.isa = "OBJECT";
+				}
+			}
+		}
+		this.step();
+		if (!this.ts.next()) break;
+	}
+}
+
+JSDOC.Walker.prototype.step = function() {
+	if (this.token.is("JSDOC")) { // it's a doc comment
+	
+		var doc = new JSDOC.DocComment(this.token.data);
+		
+				
+		if (doc.getTag("exports").length > 0) {
+			var exports = doc.getTag("exports")[0];
+
+			exports.desc.match(/(\S+) as (\S+)/i);
+			var n1 = RegExp.$1;
+			var n2 = RegExp.$2;
+			
+			if (!n1 && n2) throw "@exports tag requires a value like: 'name as ns.name'";
+			
+			JSDOC.Parser.rename = (JSDOC.Parser.rename || {});	
+			JSDOC.Parser.rename[n1] = n2
+		}
+		
+		if (doc.getTag("lends").length > 0) {
+			var lends = doc.getTag("lends")[0];
+
+			var name = lends.desc
+			if (!name) throw "@lends tag requires a value.";
+			
+			var symbol = new JSDOC.Symbol(name, [], "OBJECT", doc);
+			
+			this.namescope.push(symbol);
+			
+			var matching = this.ts.getMatchingToken("LEFT_CURLY");
+			if (matching) matching.popNamescope = name;
+			else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			
+			this.lastDoc = null;
+			return true;
+		}
+		else if (doc.getTag("name").length > 0 && doc.getTag("overview").length == 0) { // it's a virtual symbol
+			var virtualName = doc.getTag("name")[0].desc;
+			if (!virtualName) throw "@name tag requires a value.";
+			
+			if (doc.getTag("memberOf").length > 0) {
+				virtualName = (doc.getTag("memberOf")[0] + "." + virtualName)
+					.replace(/([#.])\./, "$1");
+				doc.deleteTag("memberOf");
+			}
+
+			var symbol = new JSDOC.Symbol(virtualName, [], "VIRTUAL", doc);
+			
+			JSDOC.Parser.addSymbol(symbol);
+			
+			this.lastDoc = null;
+			return true;
+		}
+		else if (doc.meta) { // it's a meta doclet
+			if (doc.meta == "@+") JSDOC.DocComment.shared = doc.src;
+			else if (doc.meta == "@-") JSDOC.DocComment.shared = "";
+			else if (doc.meta == "nocode+") JSDOC.Parser.conf.ignoreCode = true;
+			else if (doc.meta == "nocode-") JSDOC.Parser.conf.ignoreCode = JSDOC.opt.n;
+			else throw "Unrecognized meta comment: "+doc.meta;
+			
+			this.lastDoc = null;
+			return true;
+		}
+		else if (doc.getTag("overview").length > 0) { // it's a file overview
+			symbol = new JSDOC.Symbol("", [], "FILE", doc);
+			
+			JSDOC.Parser.addSymbol(symbol);
+			
+			this.lastDoc = null;
+			return true;
+		}
+		else {
+			this.lastDoc = doc;
+			return false;
+		}
+	}
+	else if (!JSDOC.Parser.conf.ignoreCode) { // it's code
+		if (this.token.is("NAME")) { // it's the name of something
+			var symbol;
+			var name = this.token.data;
+			var doc = null; if (this.lastDoc) doc = this.lastDoc;
+			var params = [];
+		
+			// it's inside an anonymous object
+			if (this.ts.look(1).is("COLON") && this.ts.look(-1).is("LEFT_CURLY") && !(this.ts.look(-2).is("JSDOC") || this.namescope.last().comment.getTag("lends").length || this.ts.look(-2).is("ASSIGN") || this.ts.look(-2).is("COLON"))) {
+				name = "$anonymous";
+				name = this.namescope.last().alias+"-"+name
+					
+				params = [];
+				
+				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+
+				JSDOC.Parser.addSymbol(symbol);
+				
+				this.namescope.push(symbol);
+				
+				var matching = this.ts.getMatchingToken(null, "RIGHT_CURLY");
+				if (matching) matching.popNamescope = name;
+				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			}
+			// function foo() {}
+			else if (this.ts.look(-1).is("FUNCTION") && this.ts.look(1).is("LEFT_PAREN")) {
+				var isInner;
+				
+				if (this.lastDoc) doc = this.lastDoc;
+				
+				if (doc && doc.getTag("memberOf").length > 0) {
+					name = (doc.getTag("memberOf")[0]+"."+name).replace("#.", "#");
+					doc.deleteTag("memberOf");
+				}
+				else {
+					name = this.namescope.last().alias+"-"+name;
+					if (!this.namescope.last().is("GLOBAL")) isInner = true;
+				}
+				
+				if (!this.namescope.last().is("GLOBAL")) isInner = true;
+				
+				params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+
+				symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
+				if (isInner) symbol.isInner = true;
+
+				if (this.ts.look(1).is("JSDOC")) {
+					var inlineReturn = ""+this.ts.look(1).data;
+					inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
+					symbol.type = inlineReturn;
+				}
+				
+				JSDOC.Parser.addSymbol(symbol);
+				
+				this.namescope.push(symbol);
+				
+				var matching = this.ts.getMatchingToken("LEFT_CURLY");
+				if (matching) matching.popNamescope = name;
+				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			}
+			// foo = function() {}
+			else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("FUNCTION")) {
+				var constructs;
+				var isConstructor = false;
+				if (doc && (constructs = doc.getTag("constructs")) && constructs.length) {
+					if (constructs[0].desc) {
+						name = constructs[0].desc;
+						isConstructor = true;
+					}
+				}
+					
+				var isInner;
+				if (this.ts.look(-1).is("VAR") || this.isInner) {
+					if (doc && doc.getTag("memberOf").length > 0) {
+						name = (doc.getTag("memberOf")[0]+"."+name).replace("#.", "#");
+						doc.deleteTag("memberOf");
+					}
+					else {
+						name = this.namescope.last().alias+"-"+name;
+						if (!this.namescope.last().is("GLOBAL")) isInner = true;
+					}
+					if (!this.namescope.last().is("GLOBAL")) isInner = true;
+				}
+				else if (name.indexOf("this.") == 0) {
+					name = this.resolveThis(name);
+				}
+
+				if (this.lastDoc) doc = this.lastDoc;
+				params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+				
+				symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
+
+				if (isInner) symbol.isInner = true;
+				if (isConstructor) symbol.isa = "CONSTRUCTOR";
+				
+				if (this.ts.look(1).is("JSDOC")) {
+					var inlineReturn = ""+this.ts.look(1).data;
+					inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
+					symbol.type = inlineReturn;
+				}
+
+				JSDOC.Parser.addSymbol(symbol);
+				
+				this.namescope.push(symbol);
+				
+				var matching = this.ts.getMatchingToken("LEFT_CURLY");
+				if (matching) matching.popNamescope = name;
+				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			}
+			// foo = new function() {} or foo = (function() {}
+			else if (this.ts.look(1).is("ASSIGN") && (this.ts.look(2).is("NEW") || this.ts.look(2).is("LEFT_PAREN")) && this.ts.look(3).is("FUNCTION")) {
+				var isInner;
+				if (this.ts.look(-1).is("VAR") || this.isInner) {
+					name = this.namescope.last().alias+"-"+name
+					if (!this.namescope.last().is("GLOBAL")) isInner = true;
+				}
+				else if (name.indexOf("this.") == 0) {
+					name = this.resolveThis(name);
+				}
+
+				this.ts.next(3); // advance past the "new" or "("
+				
+				if (this.lastDoc) doc = this.lastDoc;
+				params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+				
+				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+				if (isInner) symbol.isInner = true;
+				
+				if (this.ts.look(1).is("JSDOC")) {
+					var inlineReturn = ""+this.ts.look(1).data;
+					inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
+					symbol.type = inlineReturn;
+				}
+				
+				JSDOC.Parser.addSymbol(symbol);
+				
+				symbol.scopeType = "INSTANCE";
+				this.namescope.push(symbol);
+				
+				var matching = this.ts.getMatchingToken("LEFT_CURLY");
+				if (matching) matching.popNamescope = name;
+				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			}
+			// foo: function() {}
+			else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("FUNCTION")) {
+				name = (this.namescope.last().alias+"."+name).replace("#.", "#");
+				
+				if (this.lastDoc) doc = this.lastDoc;
+				params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+				
+				if (doc && doc.getTag("constructs").length) {
+					name = name.replace(/\.prototype(\.|$)/, "#");
+					
+					if (name.indexOf("#") > -1) name = name.match(/(^[^#]+)/)[0];
+					else name = this.namescope.last().alias;
+
+					symbol = new JSDOC.Symbol(name, params, "CONSTRUCTOR", doc);
+				}
+				else {
+					symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
+				}
+				
+				if (this.ts.look(1).is("JSDOC")) {
+					var inlineReturn = ""+this.ts.look(1).data;
+					inlineReturn = inlineReturn.replace(/(^\/\*\* *| *\*\/$)/g, "");
+					symbol.type = inlineReturn;
+				}
+				
+				JSDOC.Parser.addSymbol(symbol);
+				
+				this.namescope.push(symbol);
+				
+				var matching = this.ts.getMatchingToken("LEFT_CURLY");
+				if (matching) matching.popNamescope = name;
+				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			}
+			// foo = {}
+			else if (this.ts.look(1).is("ASSIGN") && this.ts.look(2).is("LEFT_CURLY")) {
+				var isInner;
+				if (this.ts.look(-1).is("VAR") || this.isInner) {
+					name = this.namescope.last().alias+"-"+name
+					if (!this.namescope.last().is("GLOBAL")) isInner = true;
+				}
+				else if (name.indexOf("this.") == 0) {
+					name = this.resolveThis(name);
+				}
+				
+				if (this.lastDoc) doc = this.lastDoc;
+				
+				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+				if (isInner) symbol.isInner = true;
+				
+			
+				if (doc) JSDOC.Parser.addSymbol(symbol);
+
+				this.namescope.push(symbol);
+				
+				var matching = this.ts.getMatchingToken("LEFT_CURLY");
+				if (matching) matching.popNamescope = name;
+				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			}
+			// var foo;
+			else if (this.ts.look(1).is("SEMICOLON")) {
+				var isInner;
+
+				if (this.ts.look(-1).is("VAR") || this.isInner) {
+					name = this.namescope.last().alias+"-"+name
+					if (!this.namescope.last().is("GLOBAL")) isInner = true;
+					
+					if (this.lastDoc) doc = this.lastDoc;
+				
+					symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+					if (isInner) symbol.isInner = true;
+					
+				
+					if (doc) JSDOC.Parser.addSymbol(symbol);
+				}
+			}
+			// foo = x
+			else if (this.ts.look(1).is("ASSIGN")) {				
+				var isInner;
+				if (this.ts.look(-1).is("VAR") || this.isInner) {
+					name = this.namescope.last().alias+"-"+name
+					if (!this.namescope.last().is("GLOBAL")) isInner = true;
+				}
+				else if (name.indexOf("this.") == 0) {
+					name = this.resolveThis(name);
+				}
+				
+				if (this.lastDoc) doc = this.lastDoc;
+				
+				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+				if (isInner) symbol.isInner = true;
+				
+			
+				if (doc) JSDOC.Parser.addSymbol(symbol);
+			}
+			// foo: {}
+			else if (this.ts.look(1).is("COLON") && this.ts.look(2).is("LEFT_CURLY")) {
+				name = (this.namescope.last().alias+"."+name).replace("#.", "#");
+				
+				if (this.lastDoc) doc = this.lastDoc;
+				
+				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+				
+			
+				if (doc) JSDOC.Parser.addSymbol(symbol);
+				
+				this.namescope.push(symbol);
+				
+				var matching = this.ts.getMatchingToken("LEFT_CURLY");
+				if (matching) matching.popNamescope = name;
+				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			}
+			// foo: x
+			else if (this.ts.look(1).is("COLON")) {
+				name = (this.namescope.last().alias+"."+name).replace("#.", "#");;
+				
+				if (this.lastDoc) doc = this.lastDoc;
+				
+				symbol = new JSDOC.Symbol(name, params, "OBJECT", doc);
+				
+			
+				if (doc) JSDOC.Parser.addSymbol(symbol);
+			}
+			// foo(...)
+			else if (this.ts.look(1).is("LEFT_PAREN")) {
+				if (typeof JSDOC.PluginManager != "undefined") {
+					var functionCall = {name: name};
+				
+					var cursor = this.ts.cursor;
+					params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+					this.ts.cursor = cursor;
+					
+					for (var i = 0; i < params.length; i++)
+						functionCall["arg" + (i + 1)] = params[i].name;
+				
+					JSDOC.PluginManager.run("onFunctionCall", functionCall);
+					if (functionCall.doc) {
+						this.ts.insertAhead(new JSDOC.Token(functionCall.doc, "COMM", "JSDOC"));
+					}
+				}
+			}
+			this.lastDoc = null;
+		}
+		else if (this.token.is("FUNCTION")) { // it's an anonymous function
+			if (
+				(!this.ts.look(-1).is("COLON") || !this.ts.look(-1).is("ASSIGN"))
+				&& !this.ts.look(1).is("NAME")
+			) {
+				if (this.lastDoc) doc = this.lastDoc;
+				
+				name = "$anonymous";
+				name = this.namescope.last().alias+"-"+name
+				
+				params = JSDOC.Walker.onParamList(this.ts.balance("LEFT_PAREN"));
+				
+				symbol = new JSDOC.Symbol(name, params, "FUNCTION", doc);
+				
+				JSDOC.Parser.addSymbol(symbol);
+				
+				this.namescope.push(symbol);
+				
+				var matching = this.ts.getMatchingToken("LEFT_CURLY");
+				if (matching) matching.popNamescope = name;
+				else LOG.warn("Mismatched } character. Can't parse code in file " + symbol.srcFile + ".");
+			}
+		}
+	}
+	return true;
+}
+
+/**
+	Resolves what "this." means when it appears in a name.
+	@param name The name that starts with "this.".
+	@returns The name with "this." resolved.
+ */
+JSDOC.Walker.prototype.resolveThis = function(name) {
+	name.match(/^this\.(.+)$/)
+	var nameFragment = RegExp.$1;
+	if (!nameFragment) return name;
+	
+	var symbol = this.namescope.last();
+	var scopeType = symbol.scopeType || symbol.isa;
+
+	// if we are in a constructor function, `this` means the instance
+	if (scopeType == "CONSTRUCTOR") {
+		name = symbol.alias+"#"+nameFragment;
+	}
+	
+	// if we are in an anonymous constructor function, `this` means the instance
+	else if (scopeType == "INSTANCE") {
+		name = symbol.alias+"."+nameFragment;
+	}
+	
+	// if we are in a function, `this` means the container (possibly the global)
+	else if (scopeType == "FUNCTION") {
+		// in a method of a prototype, so `this` means the constructor
+		if (symbol.alias.match(/(^.*)[#.-][^#.-]+/)) {
+			var parentName = RegExp.$1;
+			var parent = JSDOC.Parser.symbols.getSymbol(parentName);
+
+			if (!parent) {
+				if (JSDOC.Lang.isBuiltin(parentName)) parent = JSDOC.Parser.addBuiltin(parentName);
+				else {
+					if (symbol.alias.indexOf("$anonymous") < 0) // these will be ignored eventually
+						LOG.warn("Trying to document "+symbol.alias+" without first documenting "+parentName+".");
+				}
+			}
+			if (parent) name = parentName+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
+		}
+		else {
+			parent = this.namescope.last(1);
+			name = parent.alias+(parent.is("CONSTRUCTOR")?"#":".")+nameFragment;
+		}
+	}
+	// otherwise it means the global
+	else {
+		name = nameFragment;
+	}
+	
+	return name;
+}
+
+JSDOC.Walker.onParamList = function(/**Array*/paramTokens) {
+	if (!paramTokens) {
+		LOG.warn("Malformed parameter list. Can't parse code.");
+		return [];
+	}
+	var params = [];
+	for (var i = 0, l = paramTokens.length; i < l; i++) {
+		if (paramTokens[i].is("JSDOC")) {
+			var paramType = paramTokens[i].data.replace(/(^\/\*\* *| *\*\/$)/g, "");
+			
+			if (paramTokens[i+1] && paramTokens[i+1].is("NAME")) {
+				i++;
+				params.push({type: paramType, name: paramTokens[i].data});
+			}
+		}
+		else if (paramTokens[i].is("NAME")) {
+			params.push({name: paramTokens[i].data});
+		}
+	}
+	return params;
+}

+ 111 - 0
jsdoc_toolkit-2.3.3-beta/app/main.js

@@ -0,0 +1,111 @@
+/**
+ * @version $Id: main.js 818 2009-11-08 14:51:41Z micmath $
+ */
+
+function main() {
+	IO.include("lib/JSDOC.js");
+	IO.includeDir("plugins/");
+	
+	// process the options
+	
+	// the -c option: options are defined in a configuration file
+	if (JSDOC.opt.c) {
+		eval("JSDOC.conf = " + IO.readFile(JSDOC.opt.c));
+		
+		LOG.inform("Using configuration file at '"+JSDOC.opt.c+"'.");
+		
+		for (var c in JSDOC.conf) {
+			if (c !== "D" && !defined(JSDOC.opt[c])) { // commandline overrules config file
+				JSDOC.opt[c] = JSDOC.conf[c];
+			}
+		}
+		
+		if (typeof JSDOC.conf["_"] != "undefined") {
+			JSDOC.opt["_"] = JSDOC.opt["_"].concat(JSDOC.conf["_"]);
+		}
+		
+		LOG.inform("With configuration: ");
+		for (var o in JSDOC.opt) {
+			LOG.inform("    "+o+": "+JSDOC.opt[o]);
+		}
+	}
+	
+	// be verbose
+	if (JSDOC.opt.v) LOG.verbose = true;
+	
+	// send log messages to a file
+	if (JSDOC.opt.o) LOG.out = IO.open(JSDOC.opt.o);
+	
+	// run the unit tests
+	if (JSDOC.opt.T) {
+		LOG.inform("JsDoc Toolkit running in test mode at "+new Date()+".");
+		IO.include("frame/Testrun.js");
+		IO.include("test.js");
+	}
+	else {
+		// a template must be defined and must be a directory path
+		if (!JSDOC.opt.t && System.getProperty("jsdoc.template.dir")) {
+			JSDOC.opt.t = System.getProperty("jsdoc.template.dir");
+		}
+		if (JSDOC.opt.t && SYS.slash != JSDOC.opt.t.slice(-1)) {
+			JSDOC.opt.t += SYS.slash;
+		}
+		
+		// verbose messages about the options we were given
+		LOG.inform("JsDoc Toolkit main() running at "+new Date()+".");
+		LOG.inform("With options: ");
+		for (var o in JSDOC.opt) {
+			LOG.inform("    "+o+": "+JSDOC.opt[o]);
+		}
+		
+		// initialize and build a symbolSet from your code
+		JSDOC.JsDoc();
+		
+		// debugger's option: dump the entire symbolSet produced from your code
+		if (JSDOC.opt.Z) {
+			LOG.warn("So you want to see the data structure, eh? This might hang if you have circular refs...");
+			IO.include("frame/Dumper.js");
+			var symbols = JSDOC.JsDoc.symbolSet.toArray();
+			for (var i = 0, l = symbols.length; i < l; i++) {
+				var symbol = symbols[i];
+				print("// symbol: " + symbol.alias);
+				print(symbol.serialize());
+			}
+		}
+		else {
+			if (typeof JSDOC.opt.t != "undefined") {
+				try {
+					// a file named "publish.js" must exist in the template directory
+					load(JSDOC.opt.t+"publish.js");
+					
+					// and must define a function named "publish"
+					if (!publish) {
+						LOG.warn("No publish() function is defined in that template so nothing to do.");
+					}
+					else {
+						// which will be called with the symbolSet produced from your code
+						publish(JSDOC.JsDoc.symbolSet);
+					}
+				}
+				catch(e) {
+					LOG.warn("Sorry, that doesn't seem to be a valid template: "+JSDOC.opt.t+"publish.js : "+e);
+				}
+			}
+			else {
+				LOG.warn("No template given. Might as well read the usage notes.");
+				JSDOC.usage();
+			}
+		}
+	}
+	
+	// notify of any warnings
+	if (!JSDOC.opt.q && LOG.warnings.length) {
+		print(LOG.warnings.length+" warning"+(LOG.warnings.length != 1? "s":"")+".");
+	}
+	
+	// stop sending log messages to a file
+	if (LOG.out) {
+		LOG.out.flush();
+		LOG.out.close();
+	}
+}

+ 20 - 0
jsdoc_toolkit-2.3.3-beta/app/plugins/commentSrcJson.js

@@ -0,0 +1,20 @@
+JSDOC.PluginManager.registerPlugin(
+	"JSDOC.commentSrcJson",
+	{
+		onDocCommentSrc: function(comment) {
+			var json;
+			if (/^\s*@json\b/.test(comment)) {
+				comment.src = new String(comment.src).replace("@json", "");
+
+				eval("json = "+comment.src);
+				var tagged = "";
+				for (var i in json) {
+					var tag = json[i];
+					// todo handle cases where tag is an object
+					tagged += "@"+i+" "+tag+"\n";
+				}
+				comment.src = tagged;
+			}
+		}
+	}
+);

+ 16 - 0
jsdoc_toolkit-2.3.3-beta/app/plugins/frameworkPrototype.js

@@ -0,0 +1,16 @@
+JSDOC.PluginManager.registerPlugin(
+	"JSDOC.frameworkPrototype",
+	{
+		onPrototypeClassCreate: function(classCreator) {
+			var desc = "";
+			if (classCreator.comment) {
+				desc = classCreator.comment;
+			}
+			var insert = desc+"/** @name "+classCreator.name+"\n@constructor\n@scope "+classCreator.name+".prototype */"
+			
+			insert = insert.replace(/\*\/\/\*\*/g, "\n");
+			/*DEBUG*///print("insert is "+insert);
+			classCreator.addComment.data = insert;
+		}
+	}
+);

+ 10 - 0
jsdoc_toolkit-2.3.3-beta/app/plugins/functionCall.js

@@ -0,0 +1,10 @@
+JSDOC.PluginManager.registerPlugin(
+	"JSDOC.functionCall",
+	{
+		onFunctionCall: function(functionCall) {
+			if (functionCall.name == "dojo.define" && functionCall.arg1) {
+				functionCall.doc = "/** @lends "+eval(functionCall.arg1)+".prototype */";
+			}
+		}
+	}
+);

+ 62 - 0
jsdoc_toolkit-2.3.3-beta/app/plugins/publishSrcHilite.js

@@ -0,0 +1,62 @@
+JSDOC.PluginManager.registerPlugin(
+	"JSDOC.publishSrcHilite",
+	{
+		onPublishSrc: function(src) {
+			if (src.path in JsHilite.cache) {
+				return; // already generated src code
+			}
+			else JsHilite.cache[src.path] = true;
+		
+			try {
+				var sourceCode = IO.readFile(src.path);
+			}
+			catch(e) {
+				print(e.message);
+				quit();
+			}
+
+			var hiliter = new JsHilite(sourceCode, src.charset);
+			src.hilited = hiliter.hilite();
+		}
+	}
+);
+
+function JsHilite(src, charset) {
+
+	var tr = new JSDOC.TokenReader();
+	
+	tr.keepComments = true;
+	tr.keepDocs = true;
+	tr.keepWhite = true;
+	
+	this.tokens = tr.tokenize(new JSDOC.TextStream(src));
+	
+	// TODO is redefining toString() the best way?
+	JSDOC.Token.prototype.toString = function() { 
+		return "<span class=\""+this.type+"\">"+this.data.replace(/</g, "&lt;")+"</span>";
+	}
+	
+	if (!charset) charset = "utf-8";
+	
+	this.header = '<html><head><meta http-equiv="content-type" content="text/html; charset='+charset+'"> '+
+	"<style>\n\
+	.KEYW {color: #933;}\n\
+	.COMM {color: #bbb; font-style: italic;}\n\
+	.NUMB {color: #393;}\n\
+	.STRN {color: #393;}\n\
+	.REGX {color: #339;}\n\
+	.line {border-right: 1px dotted #666; color: #666; font-style: normal;}\n\
+	</style></head><body><pre>";
+	this.footer = "</pre></body></html>";
+	this.showLinenumbers = true;
+}
+
+JsHilite.cache = {};
+
+JsHilite.prototype.hilite = function() {
+	var hilited = this.tokens.join("");
+	var line = 1;
+	if (this.showLinenumbers) hilited = hilited.replace(/(^|\n)/g, function(m){return m+"<span class='line'>"+((line<10)? " ":"")+((line<100)? " ":"")+(line++)+"</span> "});
+	
+	return this.header+hilited+this.footer;
+}

+ 10 - 0
jsdoc_toolkit-2.3.3-beta/app/plugins/symbolLink.js

@@ -0,0 +1,10 @@
+JSDOC.PluginManager.registerPlugin(
+	"JSDOC.symbolLink",
+	{
+		onSymbolLink: function(link) {
+			// modify link.linkPath (the href part of the link)
+			// or link.linkText (the text displayed)
+			// or link.linkInner (the #name part of the link)
+		}
+	}
+);

+ 31 - 0
jsdoc_toolkit-2.3.3-beta/app/plugins/tagParamConfig.js

@@ -0,0 +1,31 @@
+JSDOC.PluginManager.registerPlugin(
+	"JSDOC.tagParamConfig",
+	{
+		onDocCommentTags: function(comment) {
+			var currentParam = null;
+			var tags = comment.tags;
+			for (var i = 0, l = tags.length; i < l; i++) {
+				
+				if (tags[i].title == "param") {
+					if (tags[i].name.indexOf(".") == -1) {
+						currentParam = i;
+					}
+				}
+				else if (tags[i].title == "config") {
+					tags[i].title = "param";
+					if (currentParam == null) {
+						tags[i].name = "arguments"+"."+tags[i].name;
+					}
+					else if (tags[i].name.indexOf(tags[currentParam].name+".") != 0) {
+						tags[i].name = tags[currentParam].name+"."+tags[i].name;
+					}
+					currentParam != null
+					//tags[currentParam].properties.push(tags[i]);
+				}
+				else {
+					currentParam = null;
+				}
+			}
+		}
+	}
+);

+ 43 - 0
jsdoc_toolkit-2.3.3-beta/app/plugins/tagSynonyms.js

@@ -0,0 +1,43 @@
+JSDOC.PluginManager.registerPlugin(
+	"JSDOC.tagSynonyms",
+	{
+		onDocCommentSrc: function(comment) {
+			comment.src = comment.src.replace(/@methodOf\b/i, "@function\n@memberOf");
+			comment.src = comment.src.replace(/@fieldOf\b/i, "@field\n@memberOf");
+		},
+		
+		onDocCommentTags: function(comment) {
+			for (var i = 0, l = comment.tags.length; i < l; i++) {
+				var title = comment.tags[i].title.toLowerCase();
+				var syn;
+				if ((syn = JSDOC.tagSynonyms.synonyms["="+title])) {
+					comment.tags[i].title = syn;
+				}
+			}
+		}
+	}
+);
+
+new Namespace(
+	"JSDOC.tagSynonyms",
+	function() {
+		JSDOC.tagSynonyms.synonyms = {
+			"=member":             "memberOf",
+			"=memberof":           "memberOf",
+			"=description":        "desc",
+			"=exception":          "throws",
+			"=argument":           "param",
+			"=returns":            "return",
+			"=classdescription":   "class",
+			"=fileoverview":       "overview",
+			"=extends":            "augments",
+			"=base":               "augments",
+			"=projectdescription": "overview",
+			"=classdescription":   "class",
+			"=link":               "see",
+			"=borrows":            "inherits",
+			"=scope":              "lends",
+			"=construct":          "constructor"
+		}
+	}
+);

+ 348 - 0
jsdoc_toolkit-2.3.3-beta/app/run.js

@@ -0,0 +1,348 @@
+/**
+ * @fileOverview
+ * A bootstrap script that creates some basic required objects
+ * for loading other scripts.
+ * @author Michael Mathews, micmath@gmail.com
+ * @version $Id: run.js 756 2009-01-07 21:32:58Z micmath $
+ */
+
+/**
+ * @namespace Keep track of any messages from the running script.
+ */
+LOG = {
+	warn: function(msg, e) {
+		if (JSDOC.opt.q) return;
+		if (e) msg = e.fileName+", line "+e.lineNumber+": "+msg;
+		
+		msg = ">> WARNING: "+msg;
+		LOG.warnings.push(msg);
+		if (LOG.out) LOG.out.write(msg+"\n");
+		else print(msg);
+	},
+
+	inform: function(msg) {
+		if (JSDOC.opt.q) return;
+		msg = " > "+msg;
+		if (LOG.out) LOG.out.write(msg+"\n");
+		else if (typeof LOG.verbose != "undefined" && LOG.verbose) print(msg);
+	}
+};
+LOG.warnings = [];
+LOG.verbose = false
+LOG.out = undefined;
+
+/**
+ *	@class Manipulate a filepath.
+ */
+function FilePath(absPath, separator) {
+	this.slash =  separator || "/"; 
+	this.root = this.slash;
+	this.path = [];
+	this.file = "";
+	
+	var parts = absPath.split(/[\\\/]/);
+	if (parts) {
+		if (parts.length) this.root = parts.shift() + this.slash;
+		if (parts.length) this.file =  parts.pop()
+		if (parts.length) this.path = parts;
+	}
+	
+	this.path = this.resolvePath();
+}
+
+/** Collapse any dot-dot or dot items in a filepath. */
+FilePath.prototype.resolvePath = function() {
+	var resolvedPath = [];
+	for (var i = 0; i < this.path.length; i++) {
+		if (this.path[i] == "..") resolvedPath.pop();
+		else if (this.path[i] != ".") resolvedPath.push(this.path[i]);
+	}
+	return resolvedPath;
+}
+
+/** Trim off the filename. */
+FilePath.prototype.toDir = function() {
+	if (this.file) this.file = "";
+	return this;
+}
+
+/** Go up a directory. */
+FilePath.prototype.upDir = function() {
+	this.toDir();
+	if (this.path.length) this.path.pop();
+	return this;
+}
+
+FilePath.prototype.toString = function() {
+	return this.root
+		+ this.path.join(this.slash)
+		+ ((this.path.length > 0)? this.slash : "")
+		+ this.file;
+}
+
+/**
+ * Turn a path into just the name of the file.
+ */
+FilePath.fileName = function(path) {
+	var nameStart = Math.max(path.lastIndexOf("/")+1, path.lastIndexOf("\\")+1, 0);
+	return path.substring(nameStart);
+}
+
+/**
+ * Get the extension of a filename
+ */
+FilePath.fileExtension = function(filename) {
+   return filename.split(".").pop().toLowerCase();
+};
+
+/**
+ * Turn a path into just the directory part.
+ */
+FilePath.dir = function(path) {
+	var nameStart = Math.max(path.lastIndexOf("/")+1, path.lastIndexOf("\\")+1, 0);
+	return path.substring(0, nameStart-1);
+}
+
+
+importClass(java.lang.System);
+
+/**
+ * @namespace A collection of information about your system.
+ */
+SYS = {
+	/**
+	 * Information about your operating system: arch, name, version.
+	 * @type string
+	 */
+	os: [
+		new String(System.getProperty("os.arch")),
+		new String(System.getProperty("os.name")),
+		new String(System.getProperty("os.version"))
+	].join(", "),
+	
+	/**
+	 * Which way does your slash lean.
+	 * @type string
+	 */
+	slash: System.getProperty("file.separator")||"/",
+	
+	/**
+	 * The path to the working directory where you ran java.
+	 * @type string
+	 */
+	userDir: new String(System.getProperty("user.dir")),
+	
+	/**
+	 * Where is Java's home folder.
+	 * @type string
+	 */
+	javaHome: new String(System.getProperty("java.home")),
+	
+	/**
+	 * The absolute path to the directory containing this script.
+	 * @type string
+	 */
+	pwd: undefined
+};
+
+// jsrun appends an argument, with the path to here.
+if (arguments[arguments.length-1].match(/^-j=(.+)/)) {
+	if (RegExp.$1.charAt(0) == SYS.slash || RegExp.$1.charAt(1) == ":") { // absolute path to here
+		SYS.pwd = new FilePath(RegExp.$1).toDir().toString();
+	}
+	else { // relative path to here
+		SYS.pwd = new FilePath(SYS.userDir + SYS.slash + RegExp.$1).toDir().toString();
+	}
+	arguments.pop();
+}
+else {
+	print("The run.js script requires you use jsrun.jar.");
+	quit();
+}
+
+// shortcut
+var File = Packages.java.io.File;
+
+/**
+ * @namespace A collection of functions that deal with reading a writing to disk.
+ */
+IO = {
+
+	/**
+	 * Create a new file in the given directory, with the given name and contents.
+	 */
+	saveFile: function(/**string*/ outDir, /**string*/ fileName, /**string*/ content) {
+		var out = new Packages.java.io.PrintWriter(
+			new Packages.java.io.OutputStreamWriter(
+				new Packages.java.io.FileOutputStream(outDir+SYS.slash+fileName),
+				IO.encoding
+			)
+		);
+		out.write(content);
+		out.flush();
+		out.close();
+	},
+	
+	/**
+	 * @type string
+	 */
+	readFile: function(/**string*/ path) {
+		if (!IO.exists(path)) {
+			throw "File doesn't exist there: "+path;
+		}
+		return readFile(path, IO.encoding);
+	},
+
+	/**
+	 * @param inFile 
+	 * @param outDir
+	 * @param [fileName=The original filename]
+	 */
+	copyFile: function(/**string*/ inFile, /**string*/ outDir, /**string*/ fileName) {
+		if (fileName == null) fileName = FilePath.fileName(inFile);
+	
+		var inFile = new File(inFile);
+		var outFile = new File(outDir+SYS.slash+fileName);
+		
+		var bis = new Packages.java.io.BufferedInputStream(new Packages.java.io.FileInputStream(inFile), 4096);
+		var bos = new Packages.java.io.BufferedOutputStream(new Packages.java.io.FileOutputStream(outFile), 4096);
+		var theChar;
+		while ((theChar = bis.read()) != -1) {
+			bos.write(theChar);
+		}
+		bos.close();
+		bis.close();
+	},
+
+	/**
+	 * Creates a series of nested directories.
+	 */
+	mkPath: function(/**Array*/ path) {
+		if (path.constructor != Array) path = path.split(/[\\\/]/);
+		var make = "";
+		for (var i = 0, l = path.length; i < l; i++) {
+			make += path[i] + SYS.slash;
+			if (! IO.exists(make)) {
+				IO.makeDir(make);
+			}
+		}
+	},
+	
+	/**
+	 * Creates a directory at the given path.
+	 */
+	makeDir: function(/**string*/ path) {
+		(new File(path)).mkdir();
+	},
+
+	/**
+	 * @type string[]
+	 * @param dir The starting directory to look in.
+	 * @param [recurse=1] How many levels deep to scan.
+	 * @returns An array of all the paths to files in the given dir.
+	 */
+	ls: function(/**string*/ dir, /**number*/ recurse, _allFiles, _path) {
+		if (_path === undefined) { // initially
+			var _allFiles = [];
+			var _path = [dir];
+		}
+		if (_path.length == 0) return _allFiles;
+		if (recurse === undefined) recurse = 1;
+		
+		dir = new File(dir);
+		if (!dir.directory) return [String(dir)];
+		var files = dir.list();
+		
+		for (var f = 0; f < files.length; f++) {
+			var file = String(files[f]);
+			if (file.match(/^\.[^\.\/\\]/)) continue; // skip dot files
+	
+			if ((new File(_path.join(SYS.slash)+SYS.slash+file)).list()) { // it's a directory
+				_path.push(file);
+				if (_path.length-1 < recurse) IO.ls(_path.join(SYS.slash), recurse, _allFiles, _path);
+				_path.pop();
+			}
+			else {
+				_allFiles.push((_path.join(SYS.slash)+SYS.slash+file).replace(SYS.slash+SYS.slash, SYS.slash));
+			}
+		}
+	
+		return _allFiles;
+	},
+
+	/**
+	 * @type boolean
+	 */
+	exists: function(/**string*/ path) {
+		file = new File(path);
+	
+		if (file.isDirectory()){
+			return true;
+		}
+		if (!file.exists()){
+			return false;
+		}
+		if (!file.canRead()){
+			return false;
+		}
+		return true;
+	},
+
+	/**
+	 * 
+	 */
+	open: function(/**string*/ path, /**string*/ append) {
+		var append = true;
+		var outFile = new File(path);
+		var out = new Packages.java.io.PrintWriter(
+			new Packages.java.io.OutputStreamWriter(
+				new Packages.java.io.FileOutputStream(outFile, append),
+				IO.encoding
+			)
+		);
+		return out;
+	},
+
+	/**
+	 * Sets {@link IO.encoding}.
+	 * Encoding is used when reading and writing text to files,
+	 * and in the meta tags of HTML output.
+	 */
+	setEncoding: function(/**string*/ encoding) {
+		if (/ISO-8859-([0-9]+)/i.test(encoding)) {
+			IO.encoding = "ISO8859_"+RegExp.$1;
+		}
+		else {
+			IO.encoding = encoding;
+		}
+	},
+
+	/**
+	 * @default "utf-8"
+	 * @private
+	 */
+	encoding: "utf-8",
+	
+	/**
+	 * Load the given script.
+	 */
+	include: function(relativePath) {
+		load(SYS.pwd+relativePath);
+	},
+	
+	/**
+	 * Loads all scripts from the given directory path.
+	 */
+	includeDir: function(path) {
+		if (!path) return;
+		
+		for (var lib = IO.ls(SYS.pwd+path), i = 0; i < lib.length; i++) 
+			if (/\.js$/i.test(lib[i])) load(lib[i]);
+	}
+}
+
+// now run the application
+IO.include("frame.js");
+IO.include("main.js");
+
+main();

+ 144 - 0
jsdoc_toolkit-2.3.3-beta/app/t/TestDoc.js

@@ -0,0 +1,144 @@
+var TestDoc = {
+	fails: 0,
+	plans: 0,
+	passes: 0,
+	results: []
+};
+
+TestDoc.record = function(result) {
+	TestDoc.results.push(result);
+	if (typeof result.verdict == "boolean") {
+		if (result.verdict === false) TestDoc.fails++;
+		if (result.verdict === true) TestDoc.passes++;
+	}
+}
+
+TestDoc.prove = function(filePath) {
+	if (typeof document != "undefined" && typeof document.write != "undefined") {
+		if (TestDoc.console) print = function(s) { TestDoc.console.appendChild(document.createTextNode(s+"\n")); }
+		else print = function(s) { document.write(s+"<br />"); }
+	}
+	TestDoc.run(TestDoc.readFile(filePath));
+}
+
+TestDoc.run = function(src) {
+	try { eval(src); } catch(e) { print("# ERROR! "+e); }
+	
+	var chunks = src.split(/\/\*t:/);
+	
+	var run = function(chunk) {
+		// local shortcuts
+		var is = TestDoc.assertEquals;
+		var isnt = TestDoc.assertNotEquals;
+		var plan = TestDoc.plan;
+		var requires = TestDoc.requires;
+		
+		try { eval(chunk); } catch(e) { print("# ERROR! "+e); }
+	}
+	for (var start = -1, end = 0; (start = src.indexOf("/*t:", end)) > end; start = end) {
+		run(
+			src.substring(
+				start+4,
+				(end = src.indexOf("*/", start))
+			)
+		);
+	}
+}
+
+TestDoc.Result = function(verdict, message) {
+	this.verdict = verdict;
+	this.message = message;
+}
+
+TestDoc.Result.prototype.toString = function() {
+	if (typeof this.verdict == "boolean") {
+		return (this.verdict? "ok" : "not ok") + " " + (++TestDoc.report.counter) + " - " + this.message;
+	}
+	
+	return "# " + this.message;
+}
+
+TestDoc.requires = function(file) {
+	if (!TestDoc.requires.loaded[file]) {
+		load(file);
+		TestDoc.requires.loaded[file] = true;
+	}
+}
+TestDoc.requires.loaded = {};
+
+TestDoc.report = function() {
+	TestDoc.report.counter = 0;
+	print("1.."+TestDoc.plans);
+	for (var i = 0; i < TestDoc.results.length; i++) {
+		print(TestDoc.results[i]);
+	}
+	print("----------------------------------------");
+	if (TestDoc.fails == 0 && TestDoc.passes == TestDoc.plans) {
+		print("All tests successful.");
+	}
+	else {
+		print("Failed " + TestDoc.fails + "/" + TestDoc.plans + " tests, "+((TestDoc.plans == 0)? 0 : Math.round(TestDoc.passes/(TestDoc.passes+TestDoc.fails)*10000)/100)+"% okay. Planned to run "+TestDoc.plans+", did run "+(TestDoc.passes+TestDoc.fails)+".")
+	}
+}
+
+TestDoc.plan = function(n, message) {
+	TestDoc.plans += n;
+	TestDoc.record(new TestDoc.Result(null, message+" ("+n+" tests)"));
+}
+
+TestDoc.assertEquals = function(a, b, message) {
+	var result = (a == b);
+	if (!result) message += "\n#\n# " + a + " does not equal " + b + "\n#";
+	TestDoc.record(new TestDoc.Result(result, message));
+}
+
+TestDoc.assertNotEquals = function(a, b, message) {
+	var result = (a != b);
+	if (!result) message += "\n#\n# " + a + " equals " + b + "\n#";
+	TestDoc.record(new TestDoc.Result(result, message));
+}
+
+TestDoc.readFile = (function(){
+	// rhino
+	if (typeof readFile == "function") {
+		return function(url) {
+			var text = readFile(url);
+			return text || "";
+		}
+	}
+
+	// a web browser
+	else {
+		return function(url) {
+			var httpRequest;
+		
+			if (window.XMLHttpRequest) { // Mozilla, Safari, etc
+				httpRequest = new XMLHttpRequest();
+			} 
+			else if (window.ActiveXObject) { // IE
+				try {
+					httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
+				} 
+				catch (e) {
+				   try {
+						httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
+					} 
+					catch (e) {
+					}
+				}
+			}
+		
+			if (!httpRequest) { throw "Cannot create HTTP Request."; }
+			
+			httpRequest.open('GET', url, false);
+			httpRequest.send('');
+			if (httpRequest.readyState == 4) {
+				if (httpRequest.status >= 400) {
+					throw "The HTTP Request returned an error code: "+httpRequest.status;
+				}
+			}
+			
+			return httpRequest.responseText || "";
+		}
+	}
+})();

+ 13 - 0
jsdoc_toolkit-2.3.3-beta/app/t/runner.js

@@ -0,0 +1,13 @@
+// try: java -jar ../../jsrun.jar runner.js
+
+load("TestDoc.js");
+
+TestDoc.prove("../frame/Opt.js");
+TestDoc.prove("../lib/JSDOC.js");
+TestDoc.prove("../frame/String.js");
+TestDoc.prove("../lib/JSDOC/DocTag.js");
+TestDoc.prove("../lib/JSDOC/DocComment.js");
+TestDoc.prove("../lib/JSDOC/TokenReader.js");
+TestDoc.prove("../lib/JSDOC/Symbol.js");
+
+TestDoc.report();

+ 342 - 0
jsdoc_toolkit-2.3.3-beta/app/test.js

@@ -0,0 +1,342 @@
+load("app/frame/Dumper.js");
+function symbolize(opt) {
+	symbols = null;
+	JSDOC.JsDoc(opt);
+	symbols = JSDOC.JsDoc.symbolSet;
+}
+
+var testCases = [
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/overview.js"]});
+		//print(Dumper.dump(symbols));	
+		is('symbols.getSymbolByName("My Cool Library").name', 'My Cool Library', 'File overview can be found by alias.');		
+	}
+	,
+	function() {
+		symbolize({_: [SYS.pwd+"test/name.js"]});
+
+		is('symbols.getSymbol("Response").name', "Response", 'Virtual class name is found.');
+		is('symbols.getSymbol("Response#text").alias', "Response#text", 'Virtual method name is found.');
+		is('symbols.getSymbol("Response#text").memberOf', "Response", 'Virtual method parent name is found.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/prototype.js"]});
+
+		is('symbols.getSymbol("Article").name', "Article", 'Function set to constructor prototype with inner constructor name is found.');
+		is('symbols.getSymbol("Article").hasMethod("init")', true, 'The initializer method name of prototype function is correct.');
+		is('symbols.getSymbol("Article").hasMember("counter")', true, 'A static property set in the prototype definition is found.');
+		is('symbols.getSymbol("Article").hasMember("title")', true, 'An instance property set in the prototype is found.');
+		is('symbols.getSymbol("Article#title").isStatic', false, 'An instance property has isStatic set to false.');
+		is('symbols.getSymbol("Article.counter").name', "counter", 'A static property set in the initializer has the name set correctly.');
+		is('symbols.getSymbol("Article.counter").memberOf', "Article", 'A static property set in the initializer has the memberOf set correctly.');
+		is('symbols.getSymbol("Article.counter").isStatic', true, 'A static property set in the initializer has isStatic set to true.');
+	}
+	,
+	function() {
+		symbolize({a:true, _: [SYS.pwd+"test/prototype_oblit.js"]});
+		
+		is('symbols.getSymbol("Article").name', "Article", 'Oblit set to constructor prototype name is found.');
+		is('typeof symbols.getSymbol("Article.prototype")', "undefined", 'The prototype oblit is not a symbol.');
+		is('symbols.getSymbol("Article#getTitle").name', "getTitle", 'The nonstatic method name of prototype oblit is correct.');
+		is('symbols.getSymbol("Article#getTitle").alias', "Article#getTitle", 'The alias of non-static method of prototype oblit is correct.');
+		is('symbols.getSymbol("Article#getTitle").isStatic', false, 'The isStatic of a nonstatic method of prototype oblit is correct.');
+		is('symbols.getSymbol("Article.getTitle").name', "getTitle", 'The static method name of prototype oblit is correct.');
+		is('symbols.getSymbol("Article.getTitle").isStatic', true, 'The isStatic of a static method of prototype oblit is correct.');
+		is('symbols.getSymbol("Article#getTitle").isa', "FUNCTION", 'The isa of non-static method of prototype oblit is correct.');
+		is('symbols.getSymbol("Article.getTitle").alias', "Article.getTitle", 'The alias of a static method of prototype oblit is correct.');
+		is('symbols.getSymbol("Article.getTitle").isa', "FUNCTION", 'The isa of static method of prototype oblit is correct.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/prototype_oblit_constructor.js"]});
+		
+		is('symbols.getSymbol("Article").name', "Article", 'Oblit set to constructor prototype with inner constructor name is found.');
+		is('symbols.getSymbol("Article#init").name', "init", 'The initializer method name of prototype oblit is correct.');
+		is('symbols.getSymbol("Article").hasMember("pages")', true, 'Property set by initializer method "this" is on the outer constructor.');
+		is('symbols.getSymbol("Article#Title").name', "Title", 'Name of the inner constructor name is found.');
+		is('symbols.getSymbol("Article#Title").memberOf', "Article", 'The memberOf of the inner constructor name is found.');
+		is('symbols.getSymbol("Article#Title").isa', "CONSTRUCTOR", 'The isa of the inner constructor name is constructor.');
+		is('symbols.getSymbol("Article#Title").hasMember("title")', true, 'A property set on the inner constructor "this"  is on the inner constructor.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/inner.js"]});
+		
+		is('symbols.getSymbol("Outer").name', "Outer", 'Outer constructor prototype name is found.');
+		is('symbols.getSymbol("Outer").methods.length', 1, 'Inner function doesnt appear as a method of the outer.');
+		is('symbols.getSymbol("Outer").hasMethod("open")', true, 'Outer constructors methods arent affected by inner function.');
+		is('symbols.getSymbol("Outer-Inner").alias', "Outer-Inner", 'Alias of inner function is found.');
+		is('symbols.getSymbol("Outer-Inner").isa', "CONSTRUCTOR", 'isa of inner function constructor is found.');
+		is('symbols.getSymbol("Outer-Inner").memberOf', "Outer", 'The memberOf of inner function is found.');
+		is('symbols.getSymbol("Outer-Inner").name', "Inner", 'The name of inner function is found.');
+		is('symbols.getSymbol("Outer-Inner#name").name', "name", 'A member of the inner function constructor, attached to "this" is found on inner.');
+		is('symbols.getSymbol("Outer-Inner#name").memberOf', "Outer-Inner", 'The memberOf of an inner function member is found.');		
+	}
+	,
+	function() {
+		symbolize({a:true, _: [SYS.pwd+"test/prototype_nested.js"]});
+		
+		is('symbols.getSymbol("Word").name', "Word", 'Base constructor name is found.');
+		is('symbols.getSymbol("Word").hasMethod("reverse")', true, 'Base constructor method is found.');
+		is('symbols.getSymbol("Word").methods.length', 1, 'Base constructor has only one method.');
+		is('symbols.getSymbol("Word").memberOf', "", 'Base constructor memberOf is empty.');
+		is('symbols.getSymbol("Word#reverse").name', "reverse", 'Member of constructor prototype name is found.');
+		is('symbols.getSymbol("Word#reverse").memberOf', "Word", 'Member of constructor prototype memberOf is found.');
+		is('symbols.getSymbol("Word#reverse.utf8").name', "utf8", 'Member of constructor prototype method name is found.');
+		is('symbols.getSymbol("Word#reverse.utf8").memberOf', "Word#reverse", 'Static nested member memberOf is found.');
+	}
+	,
+	function() {
+		symbolize({a:true, _: [SYS.pwd+"test/namespace_nested.js"]});
+		
+		is('symbols.getSymbol("ns1").name', "ns1", 'Base namespace name is found.');
+		is('symbols.getSymbol("ns1").memberOf', "", 'Base namespace memberOf is empty (its a constructor).');
+		is('symbols.getSymbol("ns1.ns2").name', "ns2", 'Nested namespace name is found.');
+ 		is('symbols.getSymbol("ns1.ns2").alias', "ns1.ns2", 'Nested namespace alias is found.');
+ 		is('symbols.getSymbol("ns1.ns2").memberOf', "ns1", 'Nested namespace memberOf is found.');
+ 		is('symbols.getSymbol("ns1.ns2.Function1").name', "Function1", 'Method of nested namespace name is found.');
+ 		is('symbols.getSymbol("ns1.ns2.Function1").memberOf', "ns1.ns2", 'Constructor of nested namespace memberOf is found.');			
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/functions_nested.js"]});
+		
+		is('symbols.getSymbol("Zop").name', "Zop", 'Any constructor name is found.');
+		is('symbols.getSymbol("Zop").isa', "CONSTRUCTOR", 'It isa constructor.');
+		is('symbols.getSymbol("Zop").hasMethod("zap")', true, 'Its method name, set later, is in methods array.');
+		is('symbols.getSymbol("Foo").name', "Foo", 'The containing constructor name is found.');
+		is('symbols.getSymbol("Foo").hasMethod("methodOne")', true, 'Its method name is found.');
+		is('symbols.getSymbol("Foo").hasMethod("methodTwo")', true, 'Its second method name is found.');
+		is('symbols.getSymbol("Foo#methodOne").alias', "Foo#methodOne", 'A methods alias is found.');
+		is('symbols.getSymbol("Foo#methodOne").isStatic', false, 'A methods is not static.');
+		is('symbols.getSymbol("Bar").name', "Bar", 'A global function declared inside another function is found.');
+		is('symbols.getSymbol("Bar").isa', "FUNCTION", 'It isa function.');
+		is('symbols.getSymbol("Bar").memberOf', "_global_", 'It is global.');
+		is('symbols.getSymbol("Foo-inner").name', "inner", 'An inner functions name is found.');
+		is('symbols.getSymbol("Foo-inner").memberOf', "Foo", 'It is member of the outer function.');
+		is('symbols.getSymbol("Foo-inner").isInner', true, 'It is an inner function.');
+	}
+	,
+	function() {
+		symbolize({a:true, _: [SYS.pwd+"test/memberof_constructor.js"]});
+		
+		is('symbols.getSymbol("Circle#Tangent").name', "Tangent", 'Constructor set on prototype using @member has correct name.');
+ 		is('symbols.getSymbol("Circle#Tangent").memberOf', "Circle", 'Constructor set on prototype using @member has correct memberOf.');
+ 		is('symbols.getSymbol("Circle#Tangent").alias', "Circle#Tangent", 'Constructor set on prototype using @member has correct alias.');
+ 		is('symbols.getSymbol("Circle#Tangent").isa', "CONSTRUCTOR", 'Constructor set on prototype using @member has correct isa.');
+		is('symbols.getSymbol("Circle#Tangent").isStatic', false, 'Constructor set on prototype using @member is not static.');
+		is('symbols.getSymbol("Circle#Tangent#getDiameter").name', "getDiameter", 'Method set on prototype using @member has correct name.');
+		is('symbols.getSymbol("Circle#Tangent#getDiameter").memberOf', "Circle#Tangent", 'Method set on prototype using @member has correct memberOf.');
+		is('symbols.getSymbol("Circle#Tangent#getDiameter").alias', "Circle#Tangent#getDiameter", 'Method set on prototype using @member has correct alias.');
+		is('symbols.getSymbol("Circle#Tangent#getDiameter").isa', "FUNCTION", 'Method set on prototype using @member has correct isa.');
+		is('symbols.getSymbol("Circle#Tangent#getDiameter").isStatic', false, 'Method set on prototype using @member is not static.');
+	}
+	,
+	function() {
+		symbolize({a:true, p: true,  _: [SYS.pwd+"test/memberof.js"]});
+		
+		is('symbols.getSymbol("pack.install").alias', "pack.install", 'Using @memberOf sets alias, when parent name is in memberOf tag.');
+		is('symbols.getSymbol("pack.install.overwrite").name', "install.overwrite", 'Using @memberOf sets name, even if the name is dotted.');
+		is('symbols.getSymbol("pack.install.overwrite").memberOf', "pack", 'Using @memberOf sets memberOf.');
+ 		is('symbols.getSymbol("pack.install.overwrite").isStatic', true, 'Using @memberOf with value not ending in octothorp sets isStatic to true.');
+	}
+	,
+	function() {
+		symbolize({a:true, p: true,  _: [SYS.pwd+"test/memberof2.js"]});
+		
+		is('symbols.getSymbol("Foo#bar").alias', "Foo#bar", 'An inner function can be documented as an instance method.');
+		is('symbols.getSymbol("Foo.zip").alias', "Foo.zip", 'An inner function can be documented as a static method.');
+		is('symbols.getSymbol("Foo.Fiz").alias', "Foo.Fiz", 'An inner function can be documented as a static constructor.');
+		is('symbols.getSymbol("Foo.Fiz#fipple").alias', "Foo.Fiz#fipple", 'An inner function can be documented as a static constructor with a method.');
+		is('symbols.getSymbol("Foo#blat").alias', "Foo#blat", 'An global function can be documented as an instance method.');
+	}
+	,
+	function() {
+		symbolize({a:true, p: true,  _: [SYS.pwd+"test/memberof3.js"]});
+		
+		is('symbols.getSymbol("Foo#bar").alias', "Foo#bar", 'A virtual field can be documented as an instance method.');
+		is('symbols.getSymbol("Foo2#bar").alias', "Foo2#bar", 'A virtual field with the same name can be documented as an instance method.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/borrows.js"]});
+
+		is('symbols.getSymbol("Layout").name', "Layout", 'Constructor can be found.');
+		is('symbols.getSymbol("Layout").hasMethod("init")', true, 'Constructor method name can be found.');
+		is('symbols.getSymbol("Layout").hasMember("orientation")', true, 'Constructor property name can be found.');
+		
+		is('symbols.getSymbol("Page").hasMethod("reset")', true, 'Second constructor method name can be found.');
+		is('symbols.getSymbol("Page").hasMember("orientation")', true, 'Second constructor borrowed property name can be found in properties.');
+		is('symbols.getSymbol("Page#orientation").memberOf', "Page", 'Second constructor borrowed property memberOf can be found.');
+		is('symbols.getSymbol("Page-getInnerElements").alias', "Page-getInnerElements", 'Can borrow an inner function and it is still inner.');
+		is('symbols.getSymbol("Page.units").alias', "Page.units", 'Can borrow a static function and it is still static.');
+		
+		is('symbols.getSymbol("ThreeColumnPage#init").alias', "ThreeColumnPage#init", 'Third constructor method can be found even though method with same name is borrowed.');
+		is('symbols.getSymbol("ThreeColumnPage#reset").alias', "ThreeColumnPage#reset", 'Borrowed method can be found.');
+		is('symbols.getSymbol("ThreeColumnPage#orientation").alias', "ThreeColumnPage#orientation", 'Twice borrowed method can be found.');
+	
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/borrows2.js"]});
+
+		is('symbols.getSymbol("Foo").hasMethod("my_zop")', true, 'Borrowed method can be found.');		
+		is('symbols.getSymbol("Bar").hasMethod("my_zip")', true, 'Second borrowed method can be found.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/constructs.js"]});
+
+		is('symbols.getSymbol("Person").hasMethod("say")', true, 'The constructs tag creates a class that lends can add a method to.');		
+	}
+	,
+	function() {
+		symbolize({a: true, _: [SYS.pwd+"test/augments.js", SYS.pwd+"test/augments2.js"]});
+		
+		is('symbols.getSymbol("Page").augments[0]', "Layout", 'An augmented class can be found.');
+		is('symbols.getSymbol("Page#reset").alias', "Page#reset", 'Method of augmenter can be found.');
+		is('symbols.getSymbol("Page").hasMethod("Layout#init")', true, 'Method from augmented can be found.');
+		is('symbols.getSymbol("Page").hasMember("Layout#orientation")', true, 'Property from augmented can be found.');
+		is('symbols.getSymbol("Page").methods.length', 3, 'Methods of augmented class are included in methods array.');
+	
+		is('symbols.getSymbol("ThreeColumnPage").augments[0]', "Page", 'The extends tag is a synonym for augments.');
+		is('symbols.getSymbol("ThreeColumnPage").hasMethod("ThreeColumnPage#init")', true, 'Local method overrides augmented method of same name.');
+		is('symbols.getSymbol("ThreeColumnPage").methods.length', 3, 'Local method count is right.');
+		
+		is('symbols.getSymbol("NewsletterPage").augments[0]', "ThreeColumnPage", 'Can augment across file boundaries.');
+		is('symbols.getSymbol("NewsletterPage").augments.length', 2, 'Multiple augments are supported.');
+		is('symbols.getSymbol("NewsletterPage").inherits[0].alias', "Junkmail#annoy", 'Inherited method with augments.');
+		is('symbols.getSymbol("NewsletterPage").methods.length', 6, 'Methods of augmented class are included in methods array across files.');
+		is('symbols.getSymbol("NewsletterPage").properties.length', 1, 'Properties of augmented class are included in properties array across files.');
+	}
+	,
+	function() {
+		symbolize({a:true, _: [SYS.pwd+"test/static_this.js"]});
+		
+		is('symbols.getSymbol("box.holder").name', "holder", 'Static namespace name can be found.');
+		is('symbols.getSymbol("box.holder.foo").name', "foo", 'Static namespace method name can be found.');
+		is('symbols.getSymbol("box.holder").isStatic', true, 'Static namespace method is static.');
+		
+		is('symbols.getSymbol("box.holder.counter").name', "counter", 'Instance namespace property name set on "this" can be found.');
+		is('symbols.getSymbol("box.holder.counter").alias', "box.holder.counter", 'Instance namespace property alias set on "this" can be found.');
+		is('symbols.getSymbol("box.holder.counter").memberOf', "box.holder", 'Static namespace property memberOf set on "this" can be found.');
+	}
+	,
+	function() {
+		symbolize({a:true, p: true, _: [SYS.pwd+"test/lend.js"]});
+
+		is('symbols.getSymbol("Person").name', "Person", 'Class defined in lend comment is found.');
+		is('symbols.getSymbol("Person").hasMethod("initialize")', true, 'Lent instance method name can be found.');
+		is('symbols.getSymbol("Person").hasMethod("say")', true, 'Second instance method can be found.');
+		is('symbols.getSymbol("Person#sing").isStatic', false, 'Instance method is known to be not static.');
+		
+		is('symbols.getSymbol("Person.getCount").name', "getCount", 'Static method name from second lend comment can be found.');
+		is('symbols.getSymbol("Person.getCount").isStatic', true, 'Static method from second lend comment is known to be static.');
+	
+		is('LOG.warnings.filter(function($){if($.indexOf("notok") > -1) return $}).length', 1, 'A warning is emitted when lending to an undocumented parent.');
+	}
+	,
+	function() {
+		symbolize({a:true, _: [SYS.pwd+"test/param_inline.js"]});
+	
+		is('symbols.getSymbol("Layout").params[0].type', "int", 'Inline param name is set.');
+		is('symbols.getSymbol("Layout").params[0].desc', "The number of columns.", 'Inline param desc is set from comment.');
+		is('symbols.getSymbol("Layout#getElement").params[0].name', "id", 'User defined param documentation takes precedence over parser defined.');
+		is('symbols.getSymbol("Layout#getElement").params[0].isOptional', true, 'Default for param is to not be optional.');
+		is('symbols.getSymbol("Layout#getElement").params[1].isOptional', false, 'Can mark a param as being optional.');
+		is('symbols.getSymbol("Layout#getElement").params[1].type', "number|string", 'Type of inline param doc can have multiple values.');
+		is('symbols.getSymbol("Layout#Canvas").params[0].type', "", 'Type can be not defined for some params.');
+		is('symbols.getSymbol("Layout#Canvas").params[2].type', "int", 'Type can be defined inline for only some params.');
+		is('symbols.getSymbol("Layout#rotate").params.length', 0, 'Docomments inside function sig is ignored without a param.');
+		is('symbols.getSymbol("Layout#init").params[2].type', "zoppler", 'Doc comment type overrides inline type for param with same name.');
+	}
+	,
+	function() {
+		symbolize({a: true, _: [SYS.pwd+"test/shared.js", SYS.pwd+"test/shared2.js"]});
+
+		is('symbols.getSymbol("Array#some").name', 'some', 'The name of a symbol in a shared section is found.');
+		is('symbols.getSymbol("Array#some").alias', 'Array#some', 'The alias of a symbol in a shared section is found.');
+		is('symbols.getSymbol("Array#some").desc', "Extension to builtin array.", 'A description can be shared.');
+		is('symbols.getSymbol("Array#filter").desc', "Extension to builtin array.\nChange every element of an array.", 'A shared description is appended.');
+		is('symbols.getSymbol("Queue").desc', "A first in, first out data structure.", 'A description is not shared when outside a shared section.');
+		is('symbols.getSymbol("Queue.rewind").alias', "Queue.rewind", 'Second shared tag can be started.');
+		is('symbols.getSymbol("startOver").alias', "startOver", 'Shared tag doesnt cross over files.');
+	}
+	,
+	function() {
+		symbolize({a: true, _: [SYS.pwd+"test/config.js"]});
+		is('symbols.getSymbol("Contact").params[0].name', 'person', 'The name of a param is found.');
+		is('symbols.getSymbol("Contact").params[1].name', 'person.name', 'The name of a param set with a dot name is found.');
+		is('symbols.getSymbol("Contact").params[2].name', 'person.age', 'The name of a second param set with a dot name is found.');
+		is('symbols.getSymbol("Contact").params[4].name', 'connection', 'The name of a param after config is found.');
+		
+		is('symbols.getSymbol("Family").params[0].name', 'persons', 'Another name of a param is found.');
+		is('symbols.getSymbol("Family").params[1].name', 'persons.Father', 'The name of a param+config is found.');
+		is('symbols.getSymbol("Family").params[2].name', 'persons.Mother', 'The name of a second param+config is found.');
+		is('symbols.getSymbol("Family").params[3].name', 'persons.Children', 'The name of a third param+config is found.');	
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/ignore.js"]});
+		is('LOG.warnings.filter(function($){if($.indexOf("undocumented symbol Ignored") > -1) return $}).length', 1, 'A warning is emitted when documenting members of an ignored parent.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/functions_anon.js"]});
+		is('symbols.getSymbol("a.b").alias', 'a.b', 'In anonymous constructor this is found to be the container object.');
+		is('symbols.getSymbol("a.f").alias', 'a.f', 'In anonymous constructor this can have a method.');
+		is('symbols.getSymbol("a.c").alias', 'a.c', 'In anonymous constructor method this is found to be the container object.');
+		is('symbols.getSymbol("g").alias', 'g', 'In anonymous function executed inline this is the global.');
+		is('symbols.getSymbol("bar2.p").alias', 'bar2.p', 'In named constructor executed inline this is the container object.');
+		is('symbols.getSymbol("module.pub").alias', 'module.pub', 'In parenthesized anonymous function executed inline function scoped variables arent documented.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/oblit_anon.js"]});
+		is('symbols.getSymbol("opt").name', 'opt', 'Anonymous object properties are created.');
+		is('symbols.getSymbol("opt.conf.keep").alias', 'opt.conf.keep', 'Anonymous object first property is assigned to $anonymous.');
+		is('symbols.getSymbol("opt.conf.base").alias', 'opt.conf.base', 'Anonymous object second property is assigned to $anonymous.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/params_optional.js"]});
+		is('symbols.getSymbol("Document").params.length', 3, 'Correct number of params are found when optional param syntax is used.');
+		is('symbols.getSymbol("Document").params[1].name', "id", 'Name of optional param is found.');
+		is('symbols.getSymbol("Document").params[1].isOptional', true, 'Optional param is marked isOptional.');
+		is('symbols.getSymbol("Document").params[2].name', "title", 'Name of optional param with default value is found.');
+		is('symbols.getSymbol("Document").params[2].isOptional', true, 'Optional param with default value is marked isOptional.');
+		is('symbols.getSymbol("Document").params[2].defaultValue', " This is untitled.", 'Optional param default value is found.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/synonyms.js"]});
+		is('symbols.getSymbol("myObject.myFunc").type', 'function', 'Type can be set to function.');
+	}
+	,
+	function() {
+		symbolize({a:true, p:true, _: [SYS.pwd+"test/event.js"]});
+		is('symbols.getSymbol("Kitchen#event:cakeEaten").isEvent', true, 'Function with event prefix is an event.');
+		is('symbols.getSymbol("Kitchen#cakeEaten").isa', "FUNCTION", 'Function with same name as event isa function.');
+	}
+	,
+	function() {
+		symbolize({x:"js", a:true, _: [SYS.pwd+"test/scripts/"]});
+		is('JSDOC.JsDoc.srcFiles.length', 1, 'Only js files are scanned when -x=js.');
+	}
+	,
+	function() {
+		symbolize({x:"js", a:true, _: [SYS.pwd+"test/exports.js"]});
+		is('symbols.getSymbol("mxn.Map#doThings").name', 'doThings', 'Exports creates a documentation alias that can have methods.');
+	}
+	,
+	function() {
+		symbolize({p:true, a:true, _: [SYS.pwd+"test/module.js"]});
+		is('symbols.getSymbol("myProject.myModule.myPublicMethod").name', 'myPublicMethod', 'A function wrapped in parens can be recognized.');
+		is('symbols.getSymbol("myProject.myModule-myPrivateMethod").name', 'myPrivateMethod', 'A private method in the scope of a function wrapped in parens can be recognized.');
+		is('symbols.getSymbol("myProject.myModule-myPrivateVar").name', 'myPrivateVar', 'A private member in the scope of a function wrapped in parens can be recognized.');
+	}
+];
+
+//// run and print results
+print(testrun(testCases));

+ 24 - 0
jsdoc_toolkit-2.3.3-beta/app/test/addon.js

@@ -0,0 +1,24 @@
+String.prototype.reverse = function() {
+}
+
+String.prototype.reverse.utf8 = function() {
+}
+
+Function.count = function() {
+}
+
+/** @memberOf Function */
+Function.count.reset = function() {
+}
+
+/** @memberOf Function */
+count.getValue = function() {
+}
+
+/** @memberOf Function.prototype */
+getSig = function() {
+}
+
+/** @memberOf Function.prototype */
+Function.prototype.getProps = function() {
+}

+ 14 - 0
jsdoc_toolkit-2.3.3-beta/app/test/anon_inner.js

@@ -0,0 +1,14 @@
+/**
+ * @name bar
+ * @namespace
+ */
+ 
+new function() {
+    /**
+     * @name bar-foo
+     * @function
+     * @param {number} x
+     */
+    function foo(x) {
+    }
+}

+ 31 - 0
jsdoc_toolkit-2.3.3-beta/app/test/augments.js

@@ -0,0 +1,31 @@
+/**
+@constructor
+*/
+function Layout(p) {
+	this.init = function(p) {
+	}
+	
+	this.getId = function() {
+	}
+	
+	/** @type Page */
+	this.orientation = "landscape";
+}
+
+/**
+@constructor
+@augments Layout
+*/
+function Page() {
+	this.reset = function(b) {
+	}
+}
+
+/**
+@extends Page
+@constructor
+*/
+function ThreeColumnPage() {
+	this.init = function(resetCode) {
+	}
+}

+ 26 - 0
jsdoc_toolkit-2.3.3-beta/app/test/augments2.js

@@ -0,0 +1,26 @@
+/**
+@constructor
+*/
+function LibraryItem() {
+	this.reserve = function() {
+	}
+}
+
+/**
+@constructor
+*/
+function Junkmail() {
+	this.annoy = function() {
+	}
+}
+
+/**
+@inherits Junkmail.prototype.annoy as pester
+@augments ThreeColumnPage
+@augments LibraryItem
+@constructor
+*/
+function NewsletterPage() {
+	this.getHeadline = function() {
+	}
+}

+ 46 - 0
jsdoc_toolkit-2.3.3-beta/app/test/borrows.js

@@ -0,0 +1,46 @@
+/**
+@constructor
+*/
+function Layout(p) {
+	/** initilize 1 */
+	this.init = function(p) {
+	}
+	
+	/** get the id */
+	this.getId = function() {
+	}
+	
+	/** @type string */
+	this.orientation = "landscape";
+	
+	function getInnerElements(elementSecretId){
+	}
+}
+
+/** A static method. */
+Layout.units = function() {
+}
+
+/**
+@constructor
+@borrows Layout#orientation
+@borrows Layout-getInnerElements
+@borrows Layout.units
+*/
+function Page() {
+	/** reset the page */
+	this.reset = function(b) {
+	}
+}
+
+/**
+@constructor
+@borrows Layout.prototype.orientation as this.orientation
+@borrows Layout.prototype.init as #init
+@inherits Page.prototype.reset as #reset
+*/
+function ThreeColumnPage() {
+	/** initilize 2 */
+	this.init = function(p) {
+	}
+}

+ 23 - 0
jsdoc_toolkit-2.3.3-beta/app/test/borrows2.js

@@ -0,0 +1,23 @@
+// testing circular borrows
+
+/**
+	@class
+	@borrows Bar#zop as this.my_zop
+*/
+function Foo() {
+	/** this is a zip. */
+	this.zip = function() {}
+	
+	this.my_zop = new Bar().zop;
+}
+
+/**
+	@class
+	@borrows Foo#zip as this.my_zip
+*/
+function Bar() {
+	/** this is a zop. */
+	this.zop = function() {}
+	
+	this.my_zip = new Foo().zip;
+}

+ 22 - 0
jsdoc_toolkit-2.3.3-beta/app/test/config.js

@@ -0,0 +1,22 @@
+/**
+ * @constructor
+ * @param person The person.
+ * @param {string} person.name The person's name.
+ * @config {integer} age The person's age.
+ * @config [id=1] Optional id number to use.
+ * @param connection
+ */
+function Contact(person, connection) {
+
+}
+
+/**
+ * @constructor
+ * @param persons
+ * @config {string} Father The paternal person.
+ * @config {string} Mother The maternal person.
+ * @config {string[]} Children And the rest.
+ */
+function Family(/**Object*/persons) {
+
+}

+ 18 - 0
jsdoc_toolkit-2.3.3-beta/app/test/constructs.js

@@ -0,0 +1,18 @@
+var Person = makeClass(
+    /**
+      @scope Person
+    */
+    {
+        /**
+        	This is just another way to define a constructor.
+        	@constructs
+        	@param {string} name The name of the person.
+         */
+        initialize: function(name) {
+            this.name = name;
+        },
+        say: function(message) {
+            return this.name + " says: " + message;
+        }
+    }
+);

+ 10 - 0
jsdoc_toolkit-2.3.3-beta/app/test/encoding.js

@@ -0,0 +1,10 @@
+
+/**
+ * @Constructor
+ * @desc 配置文件
+ * @class 什么也不返回
+ */
+function Test(conf) {
+    // do something;
+}
+

+ 12 - 0
jsdoc_toolkit-2.3.3-beta/app/test/encoding_other.js

@@ -0,0 +1,12 @@
+
+/**
+ * @Constructor
+ * @desc ðïîÛ
+ * @class ßàáâãäåæçèçìëêíîï °±²³´µ¡¶·¸¹
+ */
+function Test(conf) {
+    // do something;
+}
+
+// run with commanline option -e=iso-8859-5
+

+ 54 - 0
jsdoc_toolkit-2.3.3-beta/app/test/event.js

@@ -0,0 +1,54 @@
+/**
+ * @name Kitchen
+ * @constructor
+ * @fires Bakery#event:donutOrdered
+ */
+
+/**
+ * Fired when some cake is eaten.
+ * @name Kitchen#event:cakeEaten
+ * @function
+ * @param {Number} pieces The number of pieces eaten.
+ */
+
+/**
+ * Find out if cake was eaten.
+ * @name Kitchen#cakeEaten
+ * @function
+ * @param {Boolean} wasEaten
+ */
+
+/**
+ * @name getDesert
+ * @function
+ * @fires Kitchen#event:cakeEaten
+ */
+ 
+/**
+ * @name Bakery
+ * @constructor
+ * @extends Kitchen
+ */
+
+/**
+ * Fired when a donut order is made.
+ * @name Bakery#event:donutOrdered
+ * @event
+ * @param {Event} e The event object.
+ * @param {String} [e.topping] Optional sprinkles.
+ */
+
+/**
+ * @constructor
+ * @borrows Bakery#event:donutOrdered as this.event:cakeOrdered
+ */
+function CakeShop() {
+}
+
+/** @event */
+CakeShop.prototype.icingReady = function(isPink) {
+}
+
+/** @event */
+function amHungry(/**Boolean*/enoughToEatAHorse) {
+}

+ 14 - 0
jsdoc_toolkit-2.3.3-beta/app/test/exports.js

@@ -0,0 +1,14 @@
+/** @namespace */
+var mxn = {};
+
+(function(){ 
+    /** @exports Map as mxn.Map */
+    var Map =
+        /** @constructor */
+        mxn.Map = function() {
+        };
+    
+    /** A method. */
+    Map.prototype.doThings = function() {
+    };
+})();

+ 39 - 0
jsdoc_toolkit-2.3.3-beta/app/test/functions_anon.js

@@ -0,0 +1,39 @@
+/** an anonymous constructor executed inline */
+a = new function() {
+	/** a.b*/
+    this.b = 1;
+    /** a.f */
+    this.f = function() {
+    	/** a.c */
+    	this.c = 2;
+    }
+}
+
+
+/**
+	named function executed inline
+*/
+bar1 = function Zoola1() {
+	/** property of global */
+	this.g = 1;
+}();
+
+/**
+	named constructor executed inline
+*/
+bar2 = new function Zoola2() {
+	/** property of bar */
+	this.p = 1;
+};
+
+/** module pattern */
+module = (function () {
+	/** won't appear in documentation */
+	var priv = 1;
+	
+	/** @scope module */
+	return {
+		/** will appear as a property of module */
+		pub: 1
+	}
+})();

+ 33 - 0
jsdoc_toolkit-2.3.3-beta/app/test/functions_nested.js

@@ -0,0 +1,33 @@
+/** @constructor */
+function Zop() {
+}
+
+/**
+ @class
+*/
+Foo = function(id) {
+	// this is a bit twisted, but if you call Foo() you will then
+	// modify Foo(). This is kinda, sorta non-insane, because you
+	// would have to call Foo() 100% of the time to use Foo's methods
+	Foo.prototype.methodOne = function(bar) {
+	  alert(bar);
+	};
+	
+	// same again
+	Foo.prototype.methodTwo = function(bar2) {
+	  alert(bar2);
+	};
+	
+	// and these are only executed if the enclosing function is actually called
+	// and who knows if that will ever happen?
+	Bar = function(pez) {
+	  alert(pez);
+	};
+	Zop.prototype.zap = function(p){
+		alert(p);
+	};
+	
+	// but this is only visible inside Foo
+	function inner() {
+	}
+};

+ 13 - 0
jsdoc_toolkit-2.3.3-beta/app/test/global.js

@@ -0,0 +1,13 @@
+/** ecks */
+var x = [1, 2, 4];
+
+var y = {
+	foo: function(){
+	}
+}
+
+bar = function() {
+}
+
+function zop() {
+}

+ 25 - 0
jsdoc_toolkit-2.3.3-beta/app/test/globals.js

@@ -0,0 +1,25 @@
+function example(/**Circle*/a, b) {
+	/** a global defined in function  */
+	var number = a;
+	
+	var hideNumber = function(){
+	}
+	
+	setNumber = function(){
+	}
+	alert('You have chosen: ' + b);
+}
+
+function initPage() {
+	var supported = document.createElement && document.getElementsByTagName;
+	if (!supported) return;
+	// start of DOM script
+	var x = document.getElementById('writeroot');
+	// etc.
+}
+
+/** an example var */
+var document = new Document(x, y);
+
+var getNumber = function(){
+}

+ 10 - 0
jsdoc_toolkit-2.3.3-beta/app/test/ignore.js

@@ -0,0 +1,10 @@
+/**
+ * A test constructor.
+ * @constructor
+ * @ignore
+ */
+function Ignored() {
+	/** a method */
+    this.bar = function() {
+    }
+}

+ 16 - 0
jsdoc_toolkit-2.3.3-beta/app/test/inner.js

@@ -0,0 +1,16 @@
+/**
+ * @constructor
+ */
+function Outer() {
+  /**
+   * @constructor
+   */
+  function Inner(name) {
+    /** The name of this. */
+    this.name = name;
+  }
+
+  this.open = function(name) {
+    return (new Inner(name));
+  }
+}

+ 477 - 0
jsdoc_toolkit-2.3.3-beta/app/test/jsdoc_test.js

@@ -0,0 +1,477 @@
+/** 
+ * @fileoverview This file is to be used for testing the JSDoc parser
+ * It is not intended to be an example of good JavaScript OO-programming,
+ * nor is it intended to fulfill any specific purpose apart from 
+ * demonstrating the functionality of the 
+ * <a href='http://sourceforge.net/projects/jsdoc'>JSDoc</a> parser
+ *
+ * @author Gabriel Reid gab_reid@users.sourceforge.net
+ * @version 0.1 
+ */
+
+
+/**
+ * Construct a new Shape object.
+ * @class This is the basic Shape class.  
+ * It can be considered an abstract class, even though no such thing
+ * really existing in JavaScript
+ * @constructor
+ * @throws MemoryException if there is no more memory 
+ * @throws GeneralShapeException rarely (if ever)
+ * @return {Shape|Coordinate} A new shape.
+ */
+function Shape(){
+  
+   /**
+    * This is an example of a function that is not given as a property
+    * of a prototype, but instead it is assigned within a constructor.
+    * For inner functions like this to be picked up by the parser, the
+    * function that acts as a constructor <b>must</b> be denoted with
+    * the <b>&#64;constructor</b> tag in its comment.
+    * @type String
+    */
+   this.getClassName = function(){
+      return "Shape";
+   }
+
+   /** 
+    * This is an inner method, just used here as an example
+    * @since version 0.5
+    * @author Sue Smart
+    */
+   function addReference(){
+       // Do nothing...
+   }
+   
+}
+
+/**
+ * Create a new Hexagon instance.
+ * @extends Shape
+ * @class Hexagon is a class that is a <i>logical</i> sublcass of 
+ * {@link Shape} (thanks to the <code>&#64;extends</code> tag), but in 
+ * reality it is completely unrelated to Shape.
+ * @param {int} sideLength The length of one side for the new Hexagon
+ * @example
+ * var h = new Hexagon(2);
+ * @example
+ * if (hasHex) {
+ *     hex   = new Hexagon(5);
+ *     color = hex.getColor();
+ * }
+ */
+function Hexagon(sideLength) {
+}
+
+
+/**
+ * This is an unattached (static) function that adds two integers together.
+ * @param {int} One The first number to add 
+ * @param {int} Two The second number to add 
+ * @author Gabriel Reid
+ * @deprecated So you shouldn't use it anymore! Use {@link Shape#getClassName} instead.
+ */
+function Add(One, Two){
+    return One + Two;
+}
+
+
+/**
+ * The color of this shape
+ * @type Color
+ */
+Shape.prototype.color = null;
+
+/**
+ * The border of this shape. 
+ * @field
+ * @type int
+ */
+Shape.prototype.border = function(){return border;};
+
+/*
+ * These are all the instance method implementations for Shape
+ */
+
+/**
+ * Get the coordinates of this shape. It is assumed that we're always talking
+ * about shapes in a 2D location here.
+ * @requires The {@link Shape} class
+ * @returns A Coordinate object representing the location of this Shape
+ * @type Coordinate[]
+ */
+Shape.prototype.getCoords = function(){
+   return this.coords;
+}
+
+/**
+ * Get the color of this shape.
+ * @see #setColor
+ * @see The <a href="http://example.com">Color</a> library.
+ * @link Shape
+ * @type Color
+ */
+Shape.prototype.getColor = function(){
+   return this.color;
+}
+
+/**
+ * Set the coordinates for this Shape
+ * @param {Coordinate} coordinates The coordinates to set for this Shape
+ */
+Shape.prototype.setCoords = function(coordinates){
+   this.coords = coordinates;
+}
+
+/**
+ * Set the color for this Shape
+ * @param {Color} color The color to set for this Shape
+ * @param other There is no other param, but it can still be documented if
+ *              optional parameters are used
+ * @throws NonExistantColorException (no, not really!)
+ * @see #getColor
+ */
+Shape.prototype.setColor = function(color){
+   this.color = color;
+}
+
+/**
+ * Clone this shape
+ * @returns A copy of this shape
+ * @type Shape
+ * @author Gabriel Reid
+ */
+Shape.prototype.clone = function(){
+   return new Shape();
+}
+
+/**
+ * Create a new Rectangle instance. 
+ * @class A basic rectangle class, inherits from Shape.
+ * This class could be considered a concrete implementation class
+ * @constructor
+ * @param {int} width The optional width for this Rectangle
+ * @param {int} height Thie optional height for this Rectangle
+ * @author Gabriel Reid
+ * @see Shape is the base class for this
+ * @augments Shape
+ * @hilited
+ */
+function Rectangle(width, // This is the width 
+                  height // This is the height
+                  ){
+   if (width){
+      this.width = width;
+      if (height){
+	 this.height = height;
+      }
+   }
+}
+
+
+/* Inherit from Shape */
+Rectangle.prototype = new Shape();
+
+/**
+ * Value to represent the width of the Rectangle.
+ * <br>Text in <b>bold</b> and <i>italic</i> and a 
+ * link to <a href="http://sf.net">SourceForge</a>
+ * @private
+ * @type int
+ */
+Rectangle.prototype.width = 0;
+
+/**
+ * Value to represent the height of the Rectangle
+ * @private
+ * @type int
+ */
+Rectangle.prototype.height = 0;
+
+/**
+ * Get the type of this object. 
+ * @type String
+ */
+Rectangle.prototype.getClassName= function(){
+    return "Rectangle";
+}
+
+/**
+ * Get the value of the width for the Rectangle
+ * @type int
+ * @see Rectangle#setWidth
+ */
+Rectangle.prototype.getWidth = function(){
+   return this.width;
+}
+
+/**
+ * Get the value of the height for the Rectangle.
+ * Another getter is the {@link Shape#getColor} method in the 
+ * {@link Shape} base class.  
+ * @return The height of this Rectangle
+ * @type int
+ * @see Rectangle#setHeight
+ */
+Rectangle.prototype.getHeight = function(){
+    return this.height;
+}
+
+/**
+ * Set the width value for this Rectangle.
+ * @param {int} width The width value to be set
+ * @see #setWidth
+ */
+Rectangle.prototype.setWidth = function(width){
+   this.width = width;
+}
+
+/**
+ * Set the height value for this Rectangle.
+ * @param {int} height The height value to be set
+ * @see #getHeight
+ */
+Rectangle.prototype.setHeight = function(height){
+   this.height = height;
+}
+
+/**
+ * Get the value for the total area of this Rectangle
+ * @return total area of this Rectangle
+ * @type int
+ */
+Rectangle.prototype.getArea = function(){
+   return width * height;
+}
+
+
+/**
+ * Create a new Square instance.
+ * @class A Square is a subclass of {@link Rectangle}
+ * @param {int} width The optional width for this Rectangle
+ * @param {int} height The optional height for this Rectangle
+ * @augments Rectangle
+ */
+function Square(width, height){
+   if (width){
+      this.width = width;
+      if (height){
+	 this.height = height;
+      }
+   } 
+   
+}
+
+/* Square is a subclass of Rectangle */
+Square.prototype = new Rectangle();
+
+/**
+ * Set the width value for this Shape.
+ * @param {int} width The width value to be set
+ * @see #getWidth
+ */
+Square.prototype.setWidth = function(width){
+   this.width = this.height = width;
+}
+
+/**
+ * Set the height value for this Shape 
+ * Sets the {@link Rectangle#height} attribute in the Rectangle.
+ * @param {int} height The height value to be set
+ */
+Square.prototype.setHeight = function(height){
+   this.height = this.width = height;
+}
+
+
+/**
+ * Create a new Circle instance based on a radius.
+ * @class Circle class is another subclass of Shape
+ * @extends Shape
+ * @param {int} radius The optional radius of this {@link Circle }
+ * @mixin Square.prototype.setWidth as this.setDiameter
+ */
+function Circle(radius){
+   if (radius) {
+      /** The radius of the this Circle. */
+      this.radius = radius;
+   }
+}
+
+/* Circle inherits from {@link Shape} */
+Circle.prototype = new Shape();
+
+/** 
+ * The radius value for this Circle 
+ * @private
+ * @type int
+ */
+Circle.prototype.radius = 0;
+
+/** 
+ * A very simple class (static) field that is also a constant
+ * @final
+ * @type float
+ */
+Circle.PI = 3.14;
+
+/**
+ * Get the radius value for this Circle
+ * @type int
+ * @see #setRadius
+ */
+Circle.prototype.getRadius = function(){
+   return this.radius;
+}
+
+/** 
+ * Set the radius value for this Circle
+ * @param {int} radius The {@link Circle#radius} value to set
+ * @see #getRadius
+ */
+Circle.prototype.setRadius = function(radius){
+   this.radius = radius;
+}
+
+/** 
+ * An example of a  class (static) method that acts as a factory for Circle
+ * objects. Given a radius value, this method creates a new Circle.
+ * @param {int} radius The radius value to use for the new Circle.
+ * @type Circle
+ */
+Circle.createCircle = function(radius){
+    return new Circle(radius);
+}
+
+
+/**
+ * Create a new Coordinate instance based on x and y grid data.
+ * @class Coordinate is a class that can encapsulate location information.
+ * @param {int} [x=0] The optional x portion of the Coordinate
+ * @param {int} [y=0] The optinal y portion of the Coordinate
+ */
+function Coordinate(x, y){
+   if (x){
+      this.x = x;
+      if (y){
+	 this.y = y;
+      }
+   }
+}
+
+/** 
+ * The x portion of the Coordinate 
+ * @type int
+ * @see #getX
+ * @see #setX
+ */
+Coordinate.prototype.x = 0;
+
+/** 
+ * The y portion of the Coordinate 
+ * @type int
+ * @see #getY
+ * @see #setY
+ */
+Coordinate.prototype.y = 0;
+
+/**
+ * Gets the x portion of the Coordinate.
+ * @type int
+ * @see #setX
+ */
+Coordinate.prototype.getX = function(){
+   return this.x;
+}
+
+/** 
+ * Get the y portion of the Coordinate.
+ * @type int
+ * @see #setY
+ */
+Coordinate.prototype.getY = function(){
+   return this.y;
+}
+
+/**
+ * Sets the x portion of the Coordinate.
+ * @param {int} x The x value to set
+ * @see #getX
+ */
+Coordinate.prototype.setX = function(x){
+   this.x = x;
+}
+
+/** 
+ * Sets the y portion of the Coordinate.
+ * @param {int} y The y value to set
+ * @see #getY
+ */
+Coordinate.prototype.setY = function(y){
+   this.y = y;
+}
+
+/**
+ * @class This class exists to demonstrate the assignment of a class prototype
+ * as an anonymous block.
+ */
+function ShapeFactory(){
+}
+
+ShapeFactory.prototype = {
+   /** 
+    * Creates a new {@link Shape} instance.
+    * @return A new {@link Shape}
+    * @type Shape
+    */
+   createShape: function(){
+      return new Shape();
+   }
+}
+
+/**
+ * An example of a singleton class
+ * @param ... Arguments represent {@link coordinate}s in the shape.
+ * @constructor
+ */
+MySingletonShapeFactory = function(){
+
+   /**
+    * Get the next {@link Shape}
+    * @type Shape
+    * @return A new {@link Shape}
+    */
+   this.getShape = function(){ 
+      return null; 
+   }
+
+}
+
+
+/** 
+ * Create a new Foo instance.
+ * @class This is the Foo class. It exists to demonstrate 'nested' classes.
+ * @constructor 
+ * @see Foo.Bar
+ */
+function Foo(){}
+
+/** 
+ * Creates a new instance of Bar.
+ * @class This class exists to demonstrate 'nested' classes.
+ * @constructor 
+ * @see Foo.Bar
+ */
+function Bar(){}
+
+/** 
+ * Nested class
+ * @constructor 
+ */
+Foo.Bar = function(){
+	/** The x. */ this.x = 2;
+}
+
+Foo.Bar.prototype = new Bar();
+/** The y. */ 
+Foo.Bar.prototype.y = '3';

+ 33 - 0
jsdoc_toolkit-2.3.3-beta/app/test/lend.js

@@ -0,0 +1,33 @@
+ /** @class  */
+var Person = Class.create(
+    /**
+      @lends Person.prototype
+    */
+    {
+      initialize: function(name) {
+            this.name = name;
+        },
+        say: function(message) {
+            return this.name + ': ' + message;
+        }
+    }
+ );
+
+/** @lends Person.prototype */
+{
+	/** like say but more musical */
+	sing: function(song) {
+	}
+}
+
+/** @lends Person */
+{
+	getCount: function() {
+	}
+}
+
+/** @lends Unknown.prototype */
+{
+	notok: function() {
+	}
+}

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio