packages feed

template-toolkit (empty) → 0.1.0.1

raw patch · 10 files changed

+2696/−0 lines, 10 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, containers, directory, mtl, parsec, pcre-utils, regex-pcre-builtin, scientific, text, unordered-containers, uri-encode

Files

+ LICENSE view
@@ -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>.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple
+main = defaultMain
+ Text/TemplateToolkit.hs view
@@ -0,0 +1,997 @@+{-|
+Module      : Text.TemplateToolkit
+Description : Template Toolkit implementation for Haskell
+Copyright   : (c) Dzianis Kabanau, 2017
+Maintainer  : kobargh@gmail.com
+
+This is a Haskell implementation of <http://www.template-toolkit.org Template Toolkit> - the popular Perl template processing system.
+
+-}
+
+module Text.TemplateToolkit (
+    -- * Documentation
+    TName
+    ,TErr
+    ,TConfig(..)
+    ,evalTemplateFile
+    -- $conf
+
+    -- * Example
+    -- $example
+ ) where
+
+import Text.TemplateToolkitAST
+import Data.List
+import Data.Foldable (toList)
+import Control.Applicative ((<*))
+import Control.Monad.State
+import Control.Monad.Except
+import System.Directory
+import qualified Data.Map.Strict as Map
+import qualified Data.Sequence as Seq
+import qualified Data.HashMap.Lazy as HashMap (toList)
+
+import Data.Char (toUpper,toLower,isDigit)
+import qualified Data.Text as T
+import qualified Data.Text.IO as TIO (readFile)
+import qualified Data.Text.Encoding as TE (decodeUtf8)
+import qualified Data.Text.Lazy as TL (fromStrict)
+import qualified Data.Text.Lazy.Encoding as TLE (encodeUtf8)
+import qualified Data.ByteString.Char8 as BS
+
+import Text.Regex.PCRE
+import qualified Text.Regex.PCRE.ByteString.Utils as PCRE
+import qualified Network.URI.Encode as URI (encodeText)
+import qualified Data.Aeson as Aeson
+import Data.Scientific (floatingOrInteger)
+
+import Debug.Trace
+
+type TErr = String
+
+type EvalWithExcept = ExceptT TErr (StateT Env IO)
+    
+instance Eq Val where
+    (==) (VFloat x) (VFloat y) = x == y
+    (==) (VFloat x) (VInt y) = x == (fromInteger y)
+    (==) (VInt x) (VFloat y) = (fromInteger x) == y
+    (==) (VInt x) (VInt y) = x == y
+    (==) x@(VFloat _) y = x == (toNum y)
+    (==) x y@(VFloat _) = (toNum x) == y
+    (==) x@(VInt _) y = x == (toNum y)
+    (==) x y@(VInt _) = (toNum x) == y
+    (==) (VString a) (VString b) = a == b
+
+instance Ord Val where
+    compare (VFloat x) (VFloat y) = compare x y
+    compare (VFloat x) (VInt y) = compare x (fromInteger y)
+    compare (VInt x) (VFloat y) = compare (fromInteger x) y
+    compare (VInt x) (VInt y) = compare x y
+    compare (VString x) (VString y) = compare x y
+    compare x@(VString _) y@(VFloat _) = compare (toNum x) y
+    compare x@(VString _) y@(VInt _) = compare (toNum x) y
+    compare x@(VFloat _) y@(VString _) = compare x (toNum y)
+    compare x@(VInt _) y@(VString _) = compare x (toNum y)
+    
+instance Num Val where
+    (+) (VInt x) (VInt y) = VInt (x + y)
+    (+) (VInt x) (VFloat y) = VFloat (fromInteger x + y)
+    (+) (VFloat x) (VFloat y) = VFloat (x + y)
+    (+) (VFloat x) (VInt y) = VFloat (x + fromInteger y)
+    (+) x y = (toNum x) + (toNum y)
+    (*) (VInt x) (VInt y) = VInt (x * y)
+    (*) (VInt x) (VFloat y) = VFloat (fromInteger x * y)
+    (*) (VFloat x) (VFloat y) = VFloat (x * y)
+    (*) (VFloat x) (VInt y) = VFloat (x * fromInteger y)
+    (*) x y = (toNum x) * (toNum y)
+    abs (VInt x) = VInt (abs x)
+    abs (VFloat x) = VFloat (abs x)
+    abs x = abs (toNum x)
+    signum (VInt x) = VInt (signum x)
+    signum (VFloat x) = VFloat (signum x)
+    signum x = signum (toNum x)
+    fromInteger = VInt
+    negate (VInt x) = VInt (negate x)
+    negate (VFloat x) = VFloat (negate x)
+    negate x = negate (toNum x)
+    
+instance Fractional Val where
+    fromRational = VFloat . fromRational
+    (/) (VFloat x) (VFloat y) = VFloat (x / y)
+    (/) (VFloat x) (VInt y) = VFloat (x / fromInteger y)
+    (/) (VInt x) (VFloat y) = VFloat (fromInteger x / y)
+    (/) (VInt x) (VInt y) = VFloat (fromInteger x / fromInteger y)
+    (/) x y = (toNum x) / (toNum y)
+
+    
+fromAeson :: Aeson.Value -> Expr
+fromAeson Aeson.Null = EVal Undef
+fromAeson (Aeson.Bool True) = EVal $ VInt 1
+fromAeson (Aeson.Bool False) = EVal $ VInt 0
+fromAeson (Aeson.Number x) = case floatingOrInteger x of
+    (Left f) -> EVal . VFloat . realToFrac $ f
+    (Right i) -> EVal . VInt . fromInteger $ i
+fromAeson (Aeson.String s) = EVal $ VString s
+fromAeson (Aeson.Array a) = EVal . VArray . (map fromAeson) . toList $ a
+fromAeson (Aeson.Object o) = EVal . VHash . (map (\(k,v) -> (T.unpack k,fromAeson v))) . HashMap.toList $ o
+    
+refTableInsert :: Hash -> Val -> (String,Hash)
+refTableInsert refTableOrig v = (refId, refTableNew) where
+    refId = reftype ++ "ref#" ++ show (Map.size refTableOrig + 1)
+        where reftype = case v of
+                VHashV _ -> "hash"
+                VArrayV _ -> "array"
+                _ -> ""
+    refTableNew = Map.insert refId v refTableOrig
+    
+getFromHash :: String -> Hash -> Val
+getFromHash = Map.findWithDefault (Undef)
+
+getVal :: (String,Val) -> VarNode -> EvalWithExcept (String,Val)
+getVal (_,VRef r') k = do
+    (_,reft) <- get
+    getVal (r',getFromHash r' reft) k
+getVal (r,VHashV h) (VarKey k) = do
+    case Map.lookup k h of
+        Nothing -> case lookup k vmethods of
+            Nothing -> return (r,Undef)
+            Just m -> m (VHashV h) [] r
+        Just v -> return (r,v)
+getVal (r,VArrayV a) (VarIndex i) = return $ if Seq.length a <= (fromIntegral i) then (r,Undef) else (r,Seq.index a i)
+getVal (r,v) (VarMethod met pars) = case lookup met vmethods of
+    -- Nothing -> return (r,VString . T.pack $ "Invalid method '" ++ met ++ "'")
+    Nothing -> throwError ("Invalid method '" ++ met ++ "'")
+    Just m -> m v pars r
+getVal (r,v) (VarKey k) = do
+    case lookup k vmethods of
+        Nothing -> return (r,Undef)
+        Just m -> m v [] r
+getVal (r,_) _ = return (r,Undef)
+    
+getVar :: Var -> EvalWithExcept (String,Val)
+getVar (Var ((VarKey k0):vs)) = do
+    (vars,reft) <- get
+    getVar' ("",getFromHash k0 vars) vs
+    where getVar' (r,val) (v':vs') = do
+                k' <- getNode v'
+                (r',v') <- getVal (r,val) k'
+                getVar' (r',v') vs'
+          getVar' (r,val) [] = return (r,val)
+          getNode (VarRef r) = do
+                val <- getVar r
+                case val of
+                    (_,VInt i) -> return $ VarIndex (fromIntegral i)
+                    (_,v) -> return $ VarKey (toString v)
+          getNode n = return n
+
+getVarVal :: Var -> EvalWithExcept Val
+getVarVal var = do
+    (_,val) <- getVar var
+    case val of
+        (VRef r) -> do
+            (_,reft) <- get
+            return $ getFromHash r reft
+        _ -> return val
+          
+toNum :: Val -> Val
+toNum (VString s) = case reads (T.unpack s) :: [(Integer,String)] of
+    [] -> case reads (T.unpack s) :: [(Double,String)] of
+        [] -> VInt 0
+        (s',_):_ -> VFloat s'
+    (s',_):_ -> VInt s'
+toNum v@(VInt _) = v
+toNum v@(VFloat _) = v
+toNum _  = VInt 0
+
+toInt :: Val -> Int
+toInt (VFloat v) = fromIntegral . truncate $ v
+toInt (VInt v) = fromIntegral v
+toInt v = toInt . toNum $ v
+
+toString :: Val -> String
+toString (VString v) = T.unpack v
+toString (VInt v) = show v
+toString (VFloat v) = show v
+toString (VRef v) = v
+toString _ = ""
+
+toText :: Val -> T.Text
+toText (VString v) = v
+toText v = T.pack . toString $ v
+
+not' :: Val -> Val
+not' (VString s) = if (T.unpack s) == "" then VInt 1 else VInt 0
+not' (VInt 0) = VInt 1
+not' (VFloat 0.0) = VInt 1
+not' (Undef) = VInt 1
+not' _ = VInt 0
+
+evalUnOp :: UnOp -> Expr -> EvalWithExcept Val
+evalUnOp Pos e = liftM toNum (evalExpr e)
+evalUnOp Neg e = liftM (negate . toNum) (evalExpr e)
+evalUnOp Not e = liftM not' (evalExpr e)
+
+isValTrue :: Val -> Bool
+isValTrue v = not' v == VInt 0
+
+evalBinOp' :: (Val -> Val -> Val) -> Expr -> Expr -> EvalWithExcept Val
+evalBinOp' f e1 e2 = liftM2 f (evalExpr e1) (evalExpr e2)
+evalBinBoolOp' :: (Val -> Val -> Bool) -> Expr -> Expr -> EvalWithExcept Val
+evalBinBoolOp' f e1 e2 = do
+    x' <- evalExpr e1
+    y' <- evalExpr e2
+    case f x' y' of
+        True -> return $ VInt 1
+        False -> return $ VInt 0
+evalBinOp :: BinOp -> Expr -> Expr -> EvalWithExcept Val
+evalBinOp Add = evalBinOp' (+)
+evalBinOp Sub = evalBinOp' (-)
+evalBinOp Mul = evalBinOp' (*)
+evalBinOp Div = div'
+    where div' x y = do
+            x' <- evalExpr x
+            y' <- evalExpr y
+            if (toInt y') == 0 then throwError "Division by zero" else return (x' / y')
+evalBinOp Mod = mod'
+    where mod' x y = do
+                x' <- evalExpr x
+                y' <- evalExpr y
+                mod'' x' y'
+          mod'' :: Val -> Val -> EvalWithExcept Val
+          mod'' (VInt x) (VInt y) = if y /= 0 then (return $ VInt (x `mod` y)) else throwError "Division by zero"
+          mod'' _ _ = throwError "Not integer in 'mod' operation"
+evalBinOp Con = con'
+    where con' x y = do
+            x' <- evalExpr x
+            y' <- evalExpr y
+            return $ VString (toText x' `T.append` toText y')
+evalBinOp Gt = evalBinBoolOp' (>)
+evalBinOp Ge = evalBinBoolOp' (>=)
+evalBinOp Lt = evalBinBoolOp' (<)
+evalBinOp Le = evalBinBoolOp' (<=)
+evalBinOp Eq = evalBinBoolOp' (==)
+evalBinOp Ne = evalBinBoolOp' (/=)
+evalBinOp And = and'
+    where and' e1 e2 = do
+            v1' <- evalExpr e1
+            if isValTrue v1'
+            then do
+                v2' <- evalExpr e2
+                if isValTrue v2'
+                then return v2'
+                else return $ VInt 0
+            else return $ VInt 0
+evalBinOp Or = or'
+    where or' e1 e2 = do
+            v1' <- evalExpr e1
+            if isValTrue v1'
+            then return v1'
+            else do
+                v2' <- evalExpr e2
+                if isValTrue v2'
+                then return v2'
+                else return $ VInt 0
+
+evalExpr :: Expr -> EvalWithExcept Val
+evalExpr (EVal (VArray xs)) = do
+    xs' <- mapM evalExprWithRef xs
+    return $ VArrayV $ Seq.fromList xs'
+evalExpr (EVal (VArrayRange e1 e2)) = do
+    from' <- evalExpr e1
+    to' <- evalExpr e2
+    return $ VArrayV . Seq.fromList . (map (VInt . fromIntegral)) $ [(toInt from')..(toInt to')]
+evalExpr (EVal a@(VArrayV _)) = do
+    return a
+evalExpr (EVal (VHash xs)) = do
+    let (ks',es') = unzip xs
+    vs' <- mapM evalExprWithRef es'
+    return $ VHashV (Map.fromList (zip ks' vs'))
+evalExpr (EVal (VIString vs)) = do
+    let getVIString (IString s) = return s
+        getVIString (IVar v) = liftM (toText . snd) (getVar v)
+    vs' <- mapM getVIString vs
+    return $ VString (T.concat vs')
+    
+evalExpr (EVal v) = return v
+evalExpr (EVar var) = do
+    (_,val) <- getVar var
+    return val
+evalExpr (EUnOp op e) = evalUnOp op e
+evalExpr (EBinOp op e1 e2) = evalBinOp op e1 e2
+evalExpr (ETerOp e1 e2 e3) = do
+    v1' <- evalExpr e1
+    if isValTrue v1'
+    then evalExpr e2
+    else evalExpr e3
+
+evalExprWithRef :: Expr -> EvalWithExcept Val
+evalExprWithRef e = do
+    v <- evalExpr e
+    case v of
+        a@(VArrayV _) -> do
+            (vars,reft) <- get
+            let (id,reft') = refTableInsert reft a
+            put (vars,reft')
+            return $ VRef id
+        h@(VHashV _) -> do
+            (vars,reft) <- get
+            let (id,reft') = refTableInsert reft h
+            put (vars,reft')
+            return $ VRef id
+        x -> return x
+
+--
+----- statement evaluators
+--
+
+fst3 :: (a,b,c) -> a
+fst3 (x,_,_) = x
+
+snd3 :: (a,b,c) -> b
+snd3 (_,x,_) = x
+
+trd3 :: (a,b,c) -> c
+trd3 (_,_,x) = x
+
+evalOrSkipStmt :: Stmt -> EvalWithExcept T.Text
+evalOrSkipStmt stmt = do
+    (_,reft) <- get
+    let (VHashV loops) = Map.findWithDefault (VHashV Map.empty) "#loopcontrols" reft
+    let (VInt breakCode) = Map.findWithDefault (VInt 0) (show . fst3 . sId $ stmt) loops
+    if breakCode == 0
+    then evalStmt stmt
+    else return T.empty
+
+evalStmt :: Stmt -> EvalWithExcept T.Text
+
+evalStmt (Seq ss stmtId _) = do
+    ss' <- mapM evalOrSkipStmt ss
+    return $ T.concat ss'
+
+evalStmt (SComment _ _) = return T.empty
+    
+evalStmt (SText s stmtId _) = return s
+
+evalStmt (SExpr e stmtId _) = do
+    val <- evalExpr e
+    return $ toText val
+    
+evalStmt assign@(SAssign (Var varPath@((VarKey k0):vs)) e stmtId lineN) = do
+    val <- evalExprWithRef e
+    if null vs
+    then do
+        modify (\(vars,reft) -> (Map.insert k0 val vars,reft))
+        return T.empty
+    else do
+        var <- getVar (Var $ init varPath)
+        updateRefTable var (last varPath) val
+            where updateRefTable :: (String,Val) -> VarNode -> Val -> EvalWithExcept T.Text
+                  updateRefTable (r,VHashV h) (VarKey k) val = do
+                        modify (\(vars,reft) -> (vars,Map.insert r (VHashV $ Map.insert k val h) reft))
+                        return T.empty
+                  updateRefTable (r,VHashV h) (VarMethod "item" (e':_)) val = do
+                        k' <- evalExpr e'
+                        modify (\(vars,reft) -> (vars,Map.insert r (VHashV $ Map.insert (toString k') val h) reft))
+                        return T.empty
+                  updateRefTable (r,VArrayV a) (VarIndex i) val = do
+                        modify (\(vars,reft) -> (vars,Map.insert r (VArrayV $ updateArr' i val a) reft))
+                        return T.empty
+                        where updateArr' i val a = if (Seq.length a > i) then (Seq.update i val a)
+                                                   else (a Seq.>< (Seq.replicate (i - Seq.length a) (Undef))) Seq.|> val
+                  updateRefTable (_,VRef r) vark val = do
+                        (_,reft) <- get
+                        updateRefTable (r,getFromHash r reft) vark val
+                        return T.empty
+                  -- auto-vivification
+                  updateRefTable _ k@(VarKey _) _ = do
+                        evalStmt $ SAssign (Var $ init varPath) (EVal (VHashV Map.empty)) stmtId lineN
+                        evalStmt assign
+                  updateRefTable _ k@(VarMethod "item" _) _ = do
+                        evalStmt $ SAssign (Var $ init varPath) (EVal (VHashV Map.empty)) stmtId lineN
+                        evalStmt assign
+                  updateRefTable _ k@(VarIndex _) _ = do
+                        evalStmt $ SAssign (Var $ init varPath) (EVal (VArrayV Seq.empty)) stmtId lineN
+                        evalStmt assign
+                  -- otherwise
+                  updateRefTable v k val = throwError (show lineN ++ ": Variable assign error:\nvariable path: " ++ (show varPath) ++ "\nvalue: " ++ (show val) ++ "\n")
+    
+evalStmt (SIf cond body maybeElse stmtId _) = do
+    v' <- evalExpr cond
+    if isValTrue v'
+    then evalOrSkipStmt body
+    else evalElse' maybeElse
+        where evalElse' Nothing = return T.empty
+              evalElse' (Just (Elsif cond' body' maybeElse')) = do
+                    v'' <- evalExpr cond'
+                    if isValTrue v''
+                    then evalOrSkipStmt body'
+                    else evalElse' maybeElse'
+              evalElse' (Just (Else body'')) = evalOrSkipStmt body''
+          
+evalStmt whileStmt@(SWhile cond body stmtId _) = do
+    isSkip <- loopStart (snd3 . sId $ whileStmt)
+    val <- evalExpr cond
+    if isValTrue val && (not isSkip)
+    then do
+        v' <- evalOrSkipStmt body
+        v'' <- evalOrSkipStmt whileStmt
+        return $ T.append v' v''
+    else return T.empty
+    
+evalStmt foreachStmt@(SForeach var expr body stmtId lineN) = do
+    isSkip <- loopStart (snd3 . sId $ foreachStmt)
+    val <- evalExpr expr
+    foreach' val isSkip <* loopEnd (sId foreachStmt)
+        where foreach' :: Val -> Bool -> EvalWithExcept T.Text
+              foreach' (VRef r') False = do
+                    (_,reft) <- get
+                    foreach' (getFromHash r' reft) False
+              foreach' (VArrayV a') False = case toList a' of
+                    [] -> return T.empty
+                    (x:xs) -> do
+                        evalIterator (snd3 stmtId) a' (+1)
+                        evalStmt (SAssign var (EVal x) stmtId lineN)
+                        v' <- evalStmt body
+                        isSkip' <- loopStart (snd3 . sId $ foreachStmt)
+                        v'' <- foreach' (VArrayV $ Seq.fromList xs) isSkip'
+                        return $ T.append v' v''
+              foreach' x False = foreach' (VArrayV $ Seq.singleton x) False
+              foreach' _ True = return T.empty
+                    
+evalStmt (SBlock name body stmtId _) = do
+    (vars,reft) <- get
+    let (VHashV coderefs) = Map.findWithDefault (VHashV Map.empty) "#coderefs" reft
+    let coderefs' = Map.insert name (VCode body) coderefs
+    put (vars,Map.insert "#coderefs" (VHashV coderefs') reft)
+    return T.empty
+
+evalStmt (SProcess name assigns stmtId _) = do
+    mapM evalOrSkipStmt assigns
+    (VString name') <- evalExpr (EVal (VIString [name]))
+    evalNamedBlock (T.unpack name',stmtId)
+
+evalStmt (SWrapper name assigns body stmtId lineN) = do
+    (VString name') <- evalExpr (EVal (VIString [name]))
+    mapM evalOrSkipStmt assigns >> return T.empty
+    content <- evalOrSkipStmt body
+    evalStmt $ SAssign (Var [VarKey "content"]) (EVal (VString content)) (0,0,0) lineN
+    evalNamedBlock (T.unpack name',stmtId)
+
+evalStmt (SFilter name exprs body stmtId _) = do
+    v <- evalStmt body
+    case lookup name filters of
+        Nothing -> throwError ("Invalid filter '" ++ name ++ "'")
+        Just f -> f exprs v
+                          
+evalStmt (SLast (parent,_,_) _) = do
+    evalLastNext parent (-1)
+
+evalStmt (SNext (parent,_,_) _) = do
+    evalLastNext parent 1
+
+evalNamedBlock :: (String,StmtId) -> EvalWithExcept T.Text
+evalNamedBlock (name,stmtId) = do
+    env@(_,reft) <- get
+    let (VHashV coderefs) = Map.findWithDefault (VHashV Map.empty) "#coderefs" reft
+    case Map.lookup name coderefs of
+        (Just (VCode body)) -> evalOrSkipStmt body
+        Nothing -> do
+            t <- getVarVal (Var [VarKey "_CONFIG", VarKey "TEMPLATES", VarKey name])
+            case t of
+                Undef -> throwError ("\"" ++ name ++ "\" not found")
+                tn -> do
+                    tf <- liftIO $ TIO.readFile (toString tn)
+                    let stmtId' = (fst3 stmtId, (trd3 stmtId) + 1, (trd3 stmtId) + 1)
+                    case parseTemplateWithStmtId tf name stmtId' of
+                        Left err -> throwError (name ++ ": " ++ (show err))
+                        Right stmt -> do
+                            evalStmt (SBlock (toString tn) stmt stmtId' 0)
+                            catchError (evalOrSkipStmt stmt) (\e -> throwError (name ++ ": " ++ e))
+
+    
+evalLastNext :: Int -> Int -> EvalWithExcept T.Text
+evalLastNext parent breakCode = do
+    (vars,reft) <- get
+    let (VHashV loops) = Map.findWithDefault (VHashV Map.empty) "#loopcontrols" reft
+    let loops' = Map.insert (show parent) (VInt $ fromIntegral breakCode) loops
+    put (vars,Map.insert "#loopcontrols" (VHashV loops') reft)
+    return T.empty
+    
+loopStart :: Int -> EvalWithExcept Bool
+loopStart loopId = do
+    (vars,reft) <- get
+    let (VHashV loops) = Map.findWithDefault (VHashV Map.empty) "#loopcontrols" reft
+        (VInt breakCode) = Map.findWithDefault (VInt 0) (show loopId) loops
+        loops' = Map.insert (show loopId) (VInt $ if breakCode /= (-1) then 0 else (-1)) loops
+    put (vars,Map.insert "#loopcontrols" (VHashV loops') reft)
+    return $ if breakCode == (-1) then True else False
+    
+loopEnd :: StmtId -> EvalWithExcept ()
+loopEnd loopId = do
+    modify (\(vars,reft) -> (vars,Map.delete ("#foreacharr" ++ (show $ snd3 loopId)) reft))
+    modify (\(vars,reft) -> (vars,Map.delete ("#foreach" ++ (show $ snd3 loopId)) reft))
+    (vars,reft) <- get
+    let (VHashV loops) = Map.findWithDefault (VHashV Map.empty) "#loopcontrols" reft
+        loops' = Map.insert (show $ snd3 loopId) (VInt 0) loops
+        reft' = Map.insert "#loopcontrols" (VHashV loops') reft
+        (VArrayV outer) = Map.findWithDefault (VArrayV Seq.empty) ("#foreacharr" ++ (show $ fst3 loopId)) reft'
+    if Seq.null outer then put (Map.delete "loop" vars,reft') >> return ()
+    else put (vars,reft') >> evalIterator (fst3 loopId) outer id
+    
+evalIterator :: Int -> Array -> (Val -> Val) -> EvalWithExcept ()
+evalIterator loopId arr iterFunc = do
+    (vars,reft) <- get
+    let l' = fromIntegral $ Seq.length arr
+        start = [("size",VInt l')
+                ,("max",VInt $ l' - 1)
+                ,("index",VInt (-1))
+                ]
+        (VHashV h') = Map.findWithDefault (VHashV $ Map.fromList start) ("#foreach" ++ show loopId) reft
+        (VArrayV a') = Map.findWithDefault (VArrayV arr) ("#foreacharr" ++ show loopId) reft
+        (VInt i) = iterFunc (h' Map.! "index")
+        (VInt iMax) = h' Map.! "max"
+        h = flip Map.union h' (Map.fromList [("index",VInt i)
+                                           ,("count",VInt i+1)
+                                           ,("first",if i == 0 then VInt 1 else VInt 0)
+                                           ,("last",if i == iMax then VInt 1 else VInt 0)
+                                           ,("prev",if i == 0 then Undef else (a' `Seq.index` (fromIntegral i - 1)))
+                                           ,("next",if i == iMax then Undef else (a' `Seq.index` (fromIntegral i + 1)))
+                                           ,("odd",if (i + 1) `mod` 2 == 0 then VInt 0 else VInt 1)
+                                           ,("even",if (i + 1) `mod` 2 == 0 then VInt 1 else VInt 0)
+                                           ])
+        reft' = Map.insert ("#foreach" ++ show loopId) (VHashV h) reft
+        reft'' = Map.insert ("#foreacharr" ++ show loopId) (VArrayV a') reft'
+        vars'' = Map.insert "loop" (VHashV h) vars
+    put (vars'',reft'')
+    return ()
+
+--
+----- /statement evaluators
+--
+
+
+--
+----- common functions for vmethods and filters
+--
+
+_changefirst' s change = case T.unpack s of
+    x:xs -> T.pack $ (change x):xs
+    [] -> T.empty
+_Lcfirst' s = _changefirst' s toLower
+_Lower' = T.toLower
+_Ucfirst' s = _changefirst' s toUpper
+_Upper' = T.toUpper
+_Replace' s re su = case PCRE.substituteCompile' (BS.pack re) (BS.pack . T.unpack $ s) (BS.pack su) of
+                        Left err -> error err
+                        Right bs -> TE.decodeUtf8 $ bs
+_Trim' s = _Replace' s "(^\\s+|\\s+$)" ""
+_Collapse' s = _Trim' $ _Replace' s "\\s+" " "
+
+--
+----- /common functions for vmethods and filters
+--
+
+--
+----- vmethods
+--
+
+vmethods =
+    [("collapse",_collapse)
+    ,("defined",_defined)
+    ,("delete",_delete)
+    ,("each",_values)
+    ,("first",_first)
+    ,("grep",_grep)
+    ,("import",_import)
+    ,("item",_item)
+    ,("join",_join)
+    ,("keys",_keys)
+    ,("last",_last)
+    ,("lcfirst",_lcfirst)
+    ,("length",_length)
+    ,("lower",_lower)
+    ,("match",_match)
+    ,("nsort",_nsort)
+    ,("pairs",_pairs)
+    ,("pop",_pop)
+    ,("push",_push)
+    ,("remove",_remove)
+    ,("replace",_replace)
+    ,("reverse",_reverse)
+    ,("shift",_shift)
+    ,("size",_size)
+    ,("slice",_slice)
+    ,("sort",_sort)
+    ,("splice",_splice)
+    ,("split",_split)
+    ,("trim",_trim)
+    ,("ucfirst",_ucfirst)
+    ,("unique",_unique)
+    ,("unshift",_unshift)
+    ,("upper",_upper)
+    ,("values",_values)
+    
+    ] where
+    getter v _ r = return (r,v)
+    
+    _match' :: Val -> String -> [Val]
+    _match' v re = map (VString . TE.decodeUtf8) matches
+        where bs = (BS.pack . toString $ v) =~ re :: AllTextSubmatches [] BS.ByteString
+              matches = case getAllTextSubmatches bs of
+                    [] -> []
+                    (full:[]) -> [full]
+                    (_:subs) -> subs
+        
+    _matchGlobal' :: Val -> String -> [Val]
+    _matchGlobal' v re = case _match' (VString $ T.pack re) "\\(.+\\)" of
+        [] -> matches 
+        _ -> concat $ map (\m' -> _match' m' re) matches
+        where matches = map (VString . TE.decodeUtf8) $ getAllTextMatches ((BS.pack . toString $ v) =~ re :: AllTextMatches [] BS.ByteString)
+
+    _size' (VArrayV a) = fromIntegral $ Seq.length a
+    
+    _splice' a offs leng arr = (Seq.take offs'' a) Seq.>< arr Seq.>< (Seq.drop (offs'' + leng'') a)
+        where l = Seq.length a
+              offs' = if offs < 0 then l + offs + 1 else offs
+              offs'' = if offs' < 0 then 0 else offs'
+              leng' = if leng < 0 then (l - offs'' + leng) else leng
+              leng'' = if leng' < 0 then 0 else leng'
+ 
+    _collapse s _ r = return (r, VString . _Collapse' . toText $ s)
+    
+    _defined h@(VHashV _) (k:_) r = do
+        k' <- evalExpr k
+        (r',v) <- getVal (r,h) (VarKey . toString $ k')
+        _defined v [] r
+    _defined a@(VArrayV _) (i:_) r = do
+        i' <- evalExpr i
+        (r',v) <- getVal (r,a) (VarIndex . toInt $ i')
+        _defined v [] r
+    _defined Undef _ r = return (r,VInt 0)
+    _defined _ _ r = return (r,VInt 1)
+    
+    _delete _ [] r = return (r,Undef)
+    _delete (VHashV h) (e:es) r = do
+        k <- evalExpr e
+        let vh = VHashV $ Map.delete (toString k) h
+        modify (\(vars,reft) -> (vars,Map.insert r vh reft))
+        _delete vh es r
+    
+    _each (VHashV h) _ r = do
+        return (r,arr)
+            where arr = VArrayV . Seq.fromList . concat . map (\(k,v) -> [(VString . T.pack $ k),v]) $ (Map.toList h)
+    _each _ _ r = return (r,Undef)
+    
+    _first a@(VArrayV a') [] r = if (_size' a) > 0 then return (r,a' `Seq.index` 0) else return (r,Undef)
+    _first (VArrayV a) (e:_) r = do
+        i <- evalExpr e
+        return (r, VArrayV $ Seq.take (toInt i) a)
+    _first _ _ r = return (r,Undef)
+    
+    _grep (VArrayV a) (re:_) r = do
+        re' <- liftM toString (evalExpr re)
+        let a' = filter (\v -> not . null $ _match' v re') (toList a)
+        return (r, VArrayV . Seq.fromList $ a')
+    
+    _import (VHashV h) (e:es) r = do
+        v <- evalExpr e
+        case v of
+            (VRef r') -> do
+                (_,reft) <- get
+                let v' = reft Map.! r'
+                _import (VHashV h) ((EVal v'):es) r
+            (VHashV h') -> do
+                let vh = VHashV $ Map.union h' h
+                modify (\(vars,reft) -> (vars,Map.insert r vh reft))
+                _import vh es r
+            _ -> _import (VHashV h) es r
+    _import (VArrayV a) (e:es) r = do
+        v <- evalExpr e
+        case v of
+            (VRef r') -> do
+                (_,reft) <- get
+                let v' = reft Map.! r'
+                _import (VArrayV a) ((EVal v'):es) r
+            (VArrayV a') -> do
+                let va = VArrayV $ a Seq.>< a'
+                modify (\(vars,reft) -> (vars,Map.insert r va reft))
+                _import va es r
+            _ -> _import (VArrayV a) es r
+    _import _ _ r = return (r,Undef)
+            
+    _item h@(VHashV _) (e:_) r = do
+        k <- evalExpr e
+        (_,v) <- getVal (r,h) (VarKey .toString $ k)
+        return (r,v)
+    _item _ _ r = return (r,Undef)
+        
+    _join (VArrayV a) (e:_) r = do
+        delim <- evalExpr e
+        return(r,VString . T.pack . (intercalate $ toString delim) . (map toString) . toList $ a)
+    _join a@(VArrayV _) [] r = _join a [(EVal . VString . T.pack $ ",")] r
+    _join _ _ r = return (r,Undef)
+    
+    _keys (VHashV h) _ r = do
+        return (r,arr)
+            where arr = VArrayV . Seq.fromList . map (VString . T.pack) $ (Map.keys h)
+    _keys _ _ r = return (r,Undef)
+    
+    _last a@(VArrayV a') [] r = if (_size' a) > 0 then return (r,a' `Seq.index` ((_size' a) - 1)) else return (r,Undef)
+    _last (VArrayV a) (e:_) r = do
+        i <- evalExpr e
+        return (r, VArrayV . Seq.reverse $ Seq.take (toInt i) (Seq.reverse a))
+    _last _ _ r = return (r,Undef)
+    
+    _lcfirst s = getter $ VString (_Lcfirst' . toText $ s)
+
+    _length s = getter $ VInt (fromIntegral . length . toString $ s)
+
+    _lower s = getter $ VString (_Lower' . toText $ s)
+    
+    _match v (re:gl:_) r = do
+        re' <- liftM toString (evalExpr re)
+        gl' <- evalExpr gl
+        let matcher = if isValTrue gl' then _matchGlobal' else _match'
+        case matcher v re' of
+            [] -> return (r,Undef)
+            ms -> do
+                return (r,VArrayV . Seq.fromList $ ms)
+    _match v (re:[]) r = _match v [re, EVal . VInt $ 0] r
+    _match _ _ r = return (r,Undef)
+            
+    _nsort (VArrayV a) _ r = do
+        return (r,arr)
+            where arr = VArrayV . Seq.fromList . sort . (map toNum) . toList $ a
+    _nsort _ _ r = return (r,Undef)
+    
+    _pairs (VHashV h) _ r = do
+        return (r,arr)
+            where arr = VArrayV . Seq.fromList . map
+                        (\(k,v) -> VHashV . Map.fromList $ [("key",VString . T.pack $ k),("value",v)]) $ Map.toList h
+    _pairs _ _ r = return (r,Undef)
+    
+    _pop (VArrayV a) [] r = do
+        (_,v) <- _last (VArrayV a) [] r
+        let a' = VArrayV $ _splice' a (-2) 1 Seq.empty
+        modify (\(vars,reft) -> (vars,Map.insert r a' reft))
+        return (r,v)
+    _pop _ _ r = return (r,Undef)
+
+    _push (VArrayV a) exs r = do
+        vxs <- mapM evalExpr exs
+        let a' = VArrayV $ _splice' a (-1) 0 (Seq.fromList vxs)
+        modify (\(vars,reft) -> (vars,Map.insert r a' reft))
+        return (r,Undef)
+    _push _ _ r = return (r,Undef)
+
+    _remove v (re:_) r = _replace v [re] r
+    _remove _ _ r = return (r,Undef)
+    
+    _replace v (re:su:_) r = do
+        re' <- liftM toString (evalExpr re)
+        su' <- liftM toString (evalExpr su)
+        let v' = _Replace' (toText v) re' su'
+        return (r,VString v')
+    _replace v (re:_) r = _replace v [re, EVal . VString $ T.empty] r
+    _replace _ _ r = return (r,Undef)
+    
+    _reverse (VArrayV a) [] r = do
+        return (r,VArrayV . Seq.reverse $ a)
+    _reverse _ _ r = return (r,Undef)
+    
+    _shift (VArrayV a) [] r = do
+        (_,v) <- _first (VArrayV a) [] r
+        let a' = VArrayV $ _splice' a 0 1 Seq.empty
+        modify (\(vars,reft) -> (vars,Map.insert r a' reft))
+        return (r,v)
+    _shift _ _ r = return (r,Undef)
+    
+    _size (VHashV h) _ r = return (r,VInt . fromIntegral . length . toList $ h)
+    _size a@(VArrayV _) _ r = return (r,VInt (_size' a))
+    _size _ _ r = return (r,VInt 0)
+    
+    _slice (VArrayV a) (fr:to:_) r = do
+        fr' <- liftM toInt (evalExpr fr)
+        to' <- liftM toInt (evalExpr to)
+        let l = Seq.length a
+            fr'' = if fr' < 0 then l + fr' else fr'
+            to'' = if to' < 0 then l + to' else to'
+            a' = if fr'' < 0 || to'' < 0 || fr'' > to'' || fr'' > l || to'' > l then Seq.empty
+                 else (Seq.take (to'' - fr'' + 1)) . (Seq.drop fr'') $ a
+        return (r,VArrayV a')
+    _slice (VArrayV a) (fr:_) r = _slice (VArrayV a) [fr,EVal . VInt $ (-1)] r
+    _slice _ _ r = _slice (VArrayV Seq.empty) (map (EVal . VInt) [0,0]) r
+            
+        
+    _sort (VArrayV a) _ r = do
+        return (r,arr)
+            where arr = VArrayV . Seq.fromList . sort . (map (VString . T.pack . toString)) . toList $ a
+    _sort _ _ r = return (r,Undef)
+    
+    _splice (VArrayV a) (eo:el:exs) r = do
+        offs <- liftM toInt (evalExpr eo)
+        leng <- liftM toInt (evalExpr el)
+        vxs <- mapM evalExpr exs
+        arr <- mapM mkArrEl vxs
+        let arr' = VArrayV $ _splice' a offs leng (Seq.fromList $ concat arr)
+        modify (\(vars,reft) -> (vars,Map.insert r arr' reft))
+        return (r,Undef)
+            where mkArrEl :: Val -> EvalWithExcept [Val]
+                  mkArrEl (VRef r') = do
+                        (_,reft) <- get
+                        mkArrEl (reft Map.! r')
+                  mkArrEl (VArrayV a') = return (toList a')
+                  mkArrEl v = return [v]
+    _splice a@(VArrayV a') (eo:[]) r = _splice a [eo,(EVal . VInt . fromIntegral . Seq.length $ a')] r
+    _splice a@(VArrayV a') [] r = _splice a [EVal (VInt 0)] r
+    _splice _ _ r = return (r,Undef)
+    
+    _split v (re:_) r = do
+        re' <- liftM toString (evalExpr re)
+        let a = case PCRE.splitCompile' (BS.pack re') (BS.pack . toString $ v) of
+                    Left err -> error err
+                    Right vs -> VArrayV . Seq.fromList . (map (VString . TE.decodeUtf8)) $ vs
+        return (r,a)
+    _split v _ r = return (r,VArrayV . Seq.singleton . VString . toText $ v)
+    
+    _trim s _ r = return (r, VString . _Trim' . toText $ s)
+
+    _ucfirst s = getter $ VString (_Ucfirst' . toText $ s)
+    
+    _unique (VArrayV a) _ r = do
+        return (r,arr)
+            where arr = VArrayV . Seq.fromList . (nubBy (\x y -> toString x == toString y)) . toList $ a
+
+    _unshift (VArrayV a) exs r = do
+        vxs <- mapM evalExpr exs
+        let a' = VArrayV $ _splice' a 0 0 (Seq.fromList vxs)
+        modify (\(vars,reft) -> (vars,Map.insert r a' reft))
+        return (r,Undef)
+    _unshift _ _ r = return (r,Undef)
+
+    _upper s = getter $ VString (_Upper' . toText $ s)
+
+    _values (VHashV h) _ r = do
+        return (r, VArray . map EVal $ (Map.elems h))
+    _values _ _ r = return (r,Undef)
+
+
+--
+----- /vmethods
+--
+
+
+--
+----- filters
+--
+
+filters =
+    [("collapse",_collapse)
+    ,("html",_html)
+    ,("lcfirst",_lcfirst)
+    ,("lower",_lower)
+    ,("null",_null)
+    ,("replace",_replace)
+    ,("trim",_trim)
+    ,("ucfirst",_ucfirst)
+    ,("upper",_upper)
+    ,("uri",_uri)
+    ] where
+    _collapse _ v = return $ _Collapse' v
+    _html _ s = return $ foldl' (\s' (re,su) -> _Replace' s' re su) s [("&","&amp;"),("<","&lt;"),(">","&gt;"),("\"","&quot;")]
+    _lcfirst _ v = return $ _Lcfirst' v
+    _lower _ v = return $ _Lower' v
+    _null _ _ = return T.empty
+    _replace [] v = return v
+    _replace (re:[]) v = _replace [re, EVal . VString $ T.empty] v
+    _replace (re:su:_) v = do
+        re' <- liftM toString (evalExpr re)
+        su' <- liftM toString (evalExpr su)
+        let v' = _Replace' v re' su'
+        return v'
+    _trim _ v = return $ _Trim' v
+    _ucfirst _ v = return $ _Ucfirst' v
+    _upper _ v = return $ _Upper' v
+    _uri _ v = return $ URI.encodeText v
+
+--
+----- /filters
+--
+    
+data TConfig = AesonObject Aeson.Value | JSONstring T.Text
+
+evalTemplateFile ::     TName -- ^ Template filename to process
+                        -> TConfig -- ^ Template config and initial variables (either aeson object or JSON object string)
+                        -> IO (Either TErr T.Text) -- ^ Result of template evaluation - either error or text
+evalTemplateFile t cfg = do
+    case cfg of
+        (JSONstring json) -> case Aeson.decode (TLE.encodeUtf8 . TL.fromStrict $ json) of
+                                (Just aeson) -> evalTemplateFile t (AesonObject aeson)
+                                _ -> return $ Left "Invalid JSON string"
+        (AesonObject aeson) -> case fromAeson aeson of
+                                (EVal (VHash h)) -> evalStateT (runExceptT (evalTemplateFile' t h)) (Map.empty,Map.empty)
+                                _ -> return $ Left "Aeson 'object' expected as a second parameter"
+                                
+
+
+evalTemplateFile' :: TName -> [(String,Expr)] -> EvalWithExcept T.Text
+evalTemplateFile' t cfg = do
+    let assignVar (k,e) = evalStmt (SAssign (Var [VarKey k]) e (0,0,0) 0)
+    mapM_ assignVar cfg
+    vDirs <- getVarVal (Var [VarKey "_CONFIG", VarKey "INCLUDE_PATH"])
+    let dirs = case vDirs of
+                (VArrayV a) -> toList a
+                s@(VString _) -> [s]
+                _ -> []
+        getFilesPaths d = do
+                f' <- liftM (map (\f_ -> (f_,d ++ "/" ++ f_))) (getDirectoryContents d)
+                f'' <- filterM (doesFileExist . snd) f'
+                return f''
+    files <- mapM (liftIO . getFilesPaths) (map toString dirs)
+    let files' = map (\(k,v) -> (k, VString . T.pack $ v)) (concat files)
+    evalStmt $ SAssign (Var [VarKey "_CONFIG", VarKey "TEMPLATES"]) (EVal (VHashV . Map.fromList $ files')) (0,0,0) 0
+    evalStmt $ SProcess (IString $ T.pack t) [] (0,0,0) 0
+    
+{-$conf
+All variables in initial 'TConfig' object are passed to the parsed template.
+
+Special /__\_CONFIG__/ variable is an object that contains settings passed to the template evaluator. For now there is only one setting: /__INCLUDE_PATH__/ - list of folders where evaluator will look for template files.
+-}
+
+{-$example
+Template Toolkit language manual: "Text.TemplateToolkitLang".
+
+Below is a simple example of using this module:
+
+=== template-toolkit-example.hs
+@
+import Text.TemplateToolkit
+import Data.Text.IO as TIO (readFile)
+import qualified Data.Text as T
+
+main = do
+    cfg <- TIO.readFile "./conf.json"
+    s <- evalTemplateFile "template.tt" (JSONstring cfg)
+    case s of
+        (Right txt) -> putStr . T.unpack $ txt
+        (Left err) -> putStr ("ERROR! " ++ err)
+@
+
+=== conf.json
+@
+{
+  \"_CONFIG\":{
+    \"INCLUDE_PATH\":[\".\"]
+  },
+  \"users\":{
+    \"Foo\": 13,
+    \"Bar\": 3.14,
+    \"Baz\": \"bazzz\"
+  }
+}
+@
+
+=== template.tt
+@
+\<html>
+  \<body>
+    \<h1>Template Toolkit for Haskell\</h1>
+    \<h2>Count 1-10:\</h2>
+    [% FOREACH i = [1..10] -%]
+      [% i; (!loop.last) ? \', \' : \'.\' %]
+    [%- END %]
+    \<h2>Users hash:\</h2>
+    [% FOREACH user = users.pairs %]
+      \<p>[% user.key %]: [% user.value %]
+    [% END %]
+    \<h2>External template:\</h2>
+    [% PROCESS template2.tt words = [\'dog\',\'cat\',\'pig\'] %]
+  \</body>
+\</html>
+@
+
+=== template2.tt
+@
+\<p>[% words.sort.reverse.join(\'|\') %]
+@
+-}
+ Text/TemplateToolkitAST.hs view
@@ -0,0 +1,500 @@+module Text.TemplateToolkitAST where
+
+import Text.Parsec
+import Text.Parsec.Text
+import Text.Parsec.Expr
+import Control.Applicative ((<*))
+import Control.Monad
+import Numeric
+import Data.List (foldl')
+import Data.Char (toUpper,toLower,isDigit)
+import qualified Data.Text as T
+import qualified Data.Map.Strict as Map
+import qualified Data.Sequence as Seq
+
+--
+----- types
+--
+
+type Hash = Map.Map String Val
+type Array = Seq.Seq Val
+type Env = (Hash,Hash)
+type StmtId = (Int,Int,Int) -- (ParentID,SelfID,LastID)
+type LineN = Int
+type TName = String
+type Parser' = GenParser (TName,StmtId)
+
+data IString = IString T.Text | IVar Var deriving (Show)
+
+data Val = VString T.Text
+         | VIString [IString]
+         | VInt Integer
+         | VFloat Double
+         | VArray [Expr] -- array of Expressions (must be eval'ed)
+         | VArrayRange Expr Expr -- range from Expr1 to Expr2 (must be eval'ed)
+         | VArrayV Array -- array of Values (eval'ed and can be stored)
+         | VHash [(String,Expr)] -- hash of Expressions
+         | VHashV Hash  -- hash of Values
+         | VRef String -- reference to VArrayV or VHashV
+         | VCode Stmt
+         | Undef
+         deriving (Show)
+        
+data VarNode = VarKey String
+             | VarIndex Int
+             | VarMethod String [Expr]
+             | VarRef Var
+             deriving (Show)
+             
+data Var = Var [VarNode] deriving (Show)
+
+data Expr = EVal Val
+          | EVar Var
+          | EAssign Var Expr
+          | EUnOp UnOp Expr
+          | EBinOp BinOp Expr Expr
+          | ETerOp Expr Expr Expr
+          deriving (Show)
+        
+data UnOp = Pos | Neg | Not
+            deriving (Show)
+
+data BinOp = Add | Sub | Mul | Div | Mod | Con | Gt | Ge | Lt | Le | Eq | Ne | And | Or
+             deriving (Show)
+          
+data Else = Else Stmt | Elsif Expr Stmt (Maybe Else)
+    deriving (Show)
+
+data Stmt = Seq {sSeq :: [Stmt], sId :: StmtId, lineN :: LineN}
+          | SComment {sId :: StmtId, lineN :: LineN}
+          | SText {sText :: T.Text, sId :: StmtId, lineN :: LineN}
+          | SIf {sCond :: Expr, sBody :: Stmt, sElse :: (Maybe Else), sId :: StmtId, lineN :: LineN}
+          | SWhile {sCond :: Expr, sBody :: Stmt, sId :: StmtId, lineN :: LineN}
+          | SForeach {sForeachVar :: Var, sForeachArr :: Expr, sBody :: Stmt, sId :: StmtId, lineN :: LineN}
+          | SNext {sId :: StmtId, lineN :: LineN}
+          | SLast {sId :: StmtId, lineN :: LineN}
+          | SBlock {sBlockName :: String, sBody :: Stmt, sId :: StmtId, lineN :: LineN}
+          | SProcess {sProcessName :: IString, sProcessAssigns :: [Stmt], sId :: StmtId, lineN :: LineN}
+          | SWrapper {sWrapperName :: IString, sWrapperAssigns :: [Stmt], sBody :: Stmt, sId :: StmtId, lineN :: LineN}
+          | SAssign {sAssignVar :: Var, sAssignExpr :: Expr, sId :: StmtId, lineN :: LineN}
+          | SExpr {sExpr :: Expr, sId :: StmtId, lineN :: LineN}
+          | SFilter {sFilterName :: String, sFilterParams :: [Expr], sBody :: Stmt, sId :: StmtId, lineN :: LineN}
+          deriving (Show)
+--
+----- /types
+--
+
+--
+----- parsers
+--
+spaces1 :: Parser' ()
+spaces1 = skipMany1 space
+
+spacesAround :: Parser' a -> Parser' a
+spacesAround = between spaces spaces
+
+spaces1Around :: Parser' a -> Parser' a
+spaces1Around = between spaces1 spaces1
+
+parens :: Parser' a -> Parser' a
+parens = between (char '(' >> spaces) (spaces >> char ')')
+
+charCi :: Char -> Parser' Char
+charCi c = (char $ toUpper c) <|> (char $ toLower c)
+
+stringCi :: String -> Parser' String
+stringCi s = mapM charCi s
+
+escapedChar :: Parser' Char
+escapedChar = do
+    char '\\'
+    x <- oneOf "\\$\"'nt"
+    return $ case x of
+        '\\' -> x
+        '$' -> x
+        '"' -> x
+        '\'' -> x
+        'n' -> '\n'
+        't' -> '\t'
+
+parseString :: Parser' Val
+parseString = do
+    char '\''
+    x <- many $ noneOf "'\\" <|> escapedChar
+    char '\''
+    return $ VString (T.pack x)
+    
+parseInterpolatedVar :: Parser' Var
+parseInterpolatedVar = try $ (try $ between (string "${") (char '}') parseVar) <|> (char '$' >> parseVar)
+
+parseInterpolatedString :: Parser' Val
+parseInterpolatedString = do
+    char '"'
+    iStrings <- many $ ((many1 $ noneOf "\"$\\" <|> escapedChar) >>= return . IString . T.pack) <|> (parseInterpolatedVar >>= return . IVar)
+    char '"'
+    return $ VIString iStrings
+
+parseInt :: Parser' Val
+parseInt = do
+    num <- many1 digit
+    return $ VInt (read num)
+
+parseFloat :: Parser' Val
+parseFloat = try $ do
+    d1 <- many1 digit
+    char '.'
+    d2 <- many1 digit
+    return $ VFloat (fst $ (readFloat $ d1++"."++d2) !! 0)
+
+parseArray :: Parser' Val
+parseArray = do
+    char '[' >> spaces >> many (char ',' >> spaces)
+    list <- sepEndBy parseExpr (many $ spacesAround (char ','))
+    spaces >> char ']'
+    return $ VArray list
+    
+parseArrayRange :: Parser' Val
+parseArrayRange = try $ do
+    char '[' >> spaces
+    e1 <- parseExpr
+    spacesAround $ string ".."
+    e2 <- parseExpr
+    spaces >> char ']'
+    return $ VArrayRange e1 e2
+
+parseHashKeyVal :: Parser' (String,Expr)
+parseHashKeyVal = do
+    key <- (parseString >>= \(VString str) -> return $ T.unpack str) <|> many1 alphaNum
+    spaces >> string "=>" >> spaces
+    val <- parseExpr
+    return (key,val)
+    
+parseHash :: Parser' Val
+parseHash = do
+    char '{' >> spaces >> many (char ',' >> spaces)
+    list <- sepEndBy parseHashKeyVal (many $ spacesAround (char ','))
+    spaces >> char '}'
+    return $ VHash list
+
+parseVarKey :: Parser' VarNode
+parseVarKey = try $ do
+    let varletter' = ['A'..'Z']++['a'..'z']++['_']
+    c <- oneOf varletter'
+    cs <- many $ oneOf (varletter' ++ ['0'..'9'])
+    if reserved' (c:cs) then unexpected "reserved"
+    else return $ VarKey (c:cs)
+        where reserved' n = elem n ["BLOCK", "ELSE", "ELSIF", "END", "EXIT", "FILTER", "FOREACH", "IF", "LAST", "NEXT", "PROCESS", "WHILE", "WRAPPER"]
+    
+parseVarIndex :: Parser' VarNode
+parseVarIndex = liftM (VarIndex . read) $ many1 digit
+    
+parseVarMethod :: Parser' VarNode
+parseVarMethod = try $ do
+    VarKey methodName <- parseVarKey
+    exprs <- parens $ sepBy parseExpr (spacesAround (char ','))
+    return $ VarMethod methodName exprs
+    
+parseVarRef :: Parser' VarNode
+parseVarRef = do
+    char '$'
+    var <- parseVarKey
+    return $ VarRef (Var [var])
+
+parseVarRefDeep :: Parser' VarNode
+parseVarRefDeep = do
+    try $ string "${"
+    root <- parseVarKey
+    deeper <- many $ char '.' >> (parseVarKey <|> parseVarIndex)
+    char '}'
+    return $ VarRef (Var (root:deeper))
+    
+parseVar :: Parser' Var
+parseVar = do
+    root <- (parseVarMethod <|> parseVarKey)
+    deeper <- many $ char '.' >> (parseVarMethod <|> parseVarRefDeep <|> parseVarRef <|> parseVarKey <|> parseVarIndex)
+    return $ Var (root:deeper)
+
+parseMethodItem' :: Parser' VarNode
+parseMethodItem' = try $ do
+    string "item"
+    exprs <- parens $ sepBy parseExpr (spacesAround (char ','))
+    return $ VarMethod "item" exprs
+    
+parseStrictVar :: Parser' Var
+parseStrictVar = do
+    root <- parseVarKey
+    deeper <- many $ char '.' >> (parseMethodItem' <|> parseVarRefDeep <|> parseVarRef <|> parseVarKey <|> parseVarIndex)
+    return $ Var (root:deeper)
+    
+parseVal :: Parser' Expr
+parseVal = (liftM EVal $ parseHash <|> parseArrayRange <|> parseArray <|> parseString <|> parseInterpolatedString)
+            <|> (liftM EVar parseVar)
+            <|> (liftM EVal $ parseFloat <|> parseInt)
+
+parseTernary :: Parser' Expr
+parseTernary = try $ do
+    cond <- parseTerm
+    spacesAround $ char '?'
+    yes <- parseTerm
+    spacesAround $ char ':'
+    no <- parseTerm
+    return $ ETerOp cond yes no
+
+parseTerm :: Parser' Expr
+parseTerm = ((parens parseExpr) <|> parseVal) <* spaces
+    
+parseExpr = buildExpressionParser table (parseTernary <|> parseTerm)
+    where table =   [
+                    [Prefix $ try (char '-' >> notFollowedBy (char '%')) >> return (EUnOp Neg),
+                    Prefix $ char '+' >> return (EUnOp Pos),
+                    Prefix $ ((string "!" >> return ()) <|> try (stringCi "not" >> spaces1)) >> return (EUnOp Not)]
+                    ,[Infix (char '*' >> spaces >> return (EBinOp Mul)) AssocLeft,
+                    Infix (char '/' >> spaces >> return (EBinOp Div)) AssocLeft,
+                    Infix (try (char '%' >> notFollowedBy (char ']') >> spaces) >> return (EBinOp Mod)) AssocLeft,
+                    Infix (try (stringCi "mod" >> spaces1) >> return (EBinOp Mod)) AssocLeft]
+                    ,[Infix (char '+' >> spaces >> return (EBinOp Add)) AssocLeft,
+                    Infix (try (char '-' >> notFollowedBy (char '%') >> spaces) >> return (EBinOp Sub)) AssocLeft]
+                    ,[Infix (try (char '_' >> spaces1) >> return (EBinOp Con)) AssocLeft]
+                    ,[Infix (try (string ">=" >> spaces) >> return (EBinOp Ge)) AssocLeft,
+                    Infix (char '>' >> spaces >> return (EBinOp Gt)) AssocLeft,
+                    Infix (try (string "<=" >> spaces) >> return (EBinOp Le)) AssocLeft,
+                    Infix (char '<' >> spaces >> return (EBinOp Lt)) AssocLeft,
+                    Infix (try (string "==" >> spaces) >> return (EBinOp Eq)) AssocLeft,
+                    Infix (string "!=" >> spaces >> return (EBinOp Ne)) AssocLeft]
+                    ,[Infix (try (stringCi "gt" >> spaces1) >> return (EBinOp Gt)) AssocLeft,
+                    Infix (try (stringCi "ge" >> spaces1) >> return (EBinOp Ge)) AssocLeft,
+                    Infix (try (stringCi "lt" >> spaces1) >> return (EBinOp Lt)) AssocLeft,
+                    Infix (try (stringCi "le" >> spaces1) >> return (EBinOp Le)) AssocLeft,
+                    Infix (try (stringCi "eq" >> spaces1) >> return (EBinOp Eq)) AssocLeft,
+                    Infix (try (stringCi "ne" >> spaces1) >> return (EBinOp Ne)) AssocLeft]
+                    ,[Infix ((try (stringCi "and" >> spaces1) <|> (spacesAround (string "&&") >> return ())) >> return (EBinOp And)) AssocLeft,
+                    Infix ((try (stringCi "or" >> spaces1) <|> (try (spacesAround (string "||")) >> return ())) >> return (EBinOp Or)) AssocLeft]
+                    ]
+
+incrementSid :: Parser' StmtId
+incrementSid = do
+    (tName,(parent,prev,_)) <- getState
+    let newId = (parent,prev+1,prev+1)
+    putState (tName,newId)
+    return newId
+    
+getLineN :: Parser' LineN
+getLineN = liftM sourceLine getPosition
+
+parseComment :: Parser' Stmt
+parseComment = do
+    char '#'
+    lineN <- getLineN
+    manyTill anyChar (string "\n" <|> try (string "-%]" <|> string "%]"))
+    newId <- incrementSid
+    return $ SComment newId lineN
+    
+parseText :: Parser' Stmt
+parseText = do
+    spaces
+    try $ (string "-%]" <* spaces) <|> string "%]"
+    lineN <- getLineN
+    s <- manyTill anyChar (try ((try $ spaces >> string "[%-") <|> string "[%"))
+    spaces
+    newId <- incrementSid
+    return $ SText (T.pack s) newId lineN
+
+stmtEnd :: Parser' ()
+stmtEnd = do
+    spaces
+    eof <|> (char ';' >> spaces) <|> (lookAhead (string "-%]" <|> string "%]") >> return ())
+    
+parsePipeFilter :: Parser' (String,[Expr])
+parsePipeFilter = do
+    try $ spaces >> char '|' >> spaces1
+    (VarKey name) <- parseVarKey
+    exprs <- try (parens $ sepBy parseExpr (spacesAround (char ','))) <|> return []
+    return (name,exprs)
+    
+stmtEndFilter :: Parser' [(String,[Expr])]
+stmtEndFilter = do
+    spaces
+    fltrs <- many parsePipeFilter
+    eof <|> (char ';' >> spaces) <|> (lookAhead (string "-%]" <|> string "%]") >> return ())
+    return fltrs
+
+parseEnd :: Parser' ()
+parseEnd = (try $ string "END") >> stmtEnd
+
+parseMaybeElse :: Parser' (Maybe Else)
+parseMaybeElse = ((parseElsif <|> parseElse) >>= \els -> return $ Just els) <|> (parseEnd >> return Nothing)
+
+parseIf :: Parser' Stmt
+parseIf = do
+    try $ string "IF" >> spaces1
+    lineN <- getLineN
+    newId <- incrementSid
+    cond <- parseExpr
+    stmtEnd
+    body <- parseStmtSeq
+    maybeElse <- parseMaybeElse
+    return $ SIf cond body maybeElse newId lineN
+    
+parseElsif :: Parser' Else
+parseElsif = do
+    try $ string "ELSIF" >> spaces1
+    cond <- parseExpr
+    stmtEnd
+    body <- parseStmtSeq
+    maybeElse <- parseMaybeElse
+    return $ Elsif cond body maybeElse
+
+parseElse :: Parser' Else
+parseElse = do
+    try $ string "ELSE" >> stmtEnd
+    body <- parseStmtSeq
+    parseEnd
+    return $ Else body
+
+parseWhile :: Parser' Stmt
+parseWhile = do
+    try $ string "WHILE" >> spaces1
+    lineN <- getLineN
+    (tName,(parent,prev,_)) <- getState
+    putState (tName,(prev+1,prev+1,prev+1))
+    cond <- parseExpr
+    stmtEnd
+    body <- parseStmtSeq
+    parseEnd
+    return $ SWhile cond body (parent,prev+1,prev+1) lineN
+
+parseForeach :: Parser' Stmt
+parseForeach = do
+    try $ string "FOREACH" >> spaces1
+    lineN <- getLineN
+    (tName,(parent,prev,_)) <- getState
+    putState (tName,(prev+1,prev+1,prev+1))
+    (SAssign var expr _ _) <- parseAssign
+    stmtEnd
+    body <- parseStmtSeq
+    parseEnd
+    return $ SForeach var expr body (parent,prev+1,prev+1) lineN
+
+parseLast :: Parser' Stmt
+parseLast = do
+    try $ string "LAST" >> stmtEnd
+    lineN <- getLineN
+    newId <- incrementSid
+    return $ SLast newId lineN
+    
+parseNext :: Parser' Stmt
+parseNext = do
+    try $ string "NEXT" >> stmtEnd
+    lineN <- getLineN
+    newId <- incrementSid
+    return $ SNext newId lineN
+    
+parseBlock :: Parser' Stmt
+parseBlock = do
+    try $ string "BLOCK" >> spaces1
+    lineN <- getLineN
+    newId <- incrementSid
+    (VarKey name) <- parseVarKey
+    stmtEnd
+    body <- parseStmtSeq
+    parseEnd
+    return $ SBlock name body newId lineN
+
+parseTmplName :: Parser' IString
+parseTmplName = do
+    (liftM IVar parseInterpolatedVar) <|> do
+        (VarKey s) <- parseVarKey
+        ext <- option "" (many $ oneOf (['A'..'Z']++['a'..'z']++['_','.','-']))
+        return $ IString (T.pack (s ++ ext))
+
+parseProcess :: Parser' Stmt
+parseProcess = do
+    try $ string "PROCESS" >> spaces1
+    lineN <- getLineN
+    newId <- incrementSid
+    name <- parseTmplName
+    spaces
+    assigns <- many parseSimpleAssign
+    fltr <- stmtEndFilter
+    return $ case fltr of
+        [] -> SProcess name assigns newId lineN
+        fltrs -> foldr (\(fltrName,exprs) stmt -> SFilter fltrName exprs stmt newId lineN) (SProcess name assigns newId lineN) fltrs
+
+parseWrapper :: Parser' Stmt
+parseWrapper = do
+    try $ string "WRAPPER" >> spaces1
+    lineN <- getLineN
+    newId <- incrementSid
+    name <- parseTmplName
+    spaces
+    assigns <- many parseSimpleAssign
+    stmtEnd
+    body <- parseStmtSeq
+    parseEnd
+    return $ SWrapper name assigns body newId lineN
+        
+parseAssign :: Parser' Stmt
+parseAssign = do
+    lineN <- getLineN
+    var <- parseStrictVar
+    spacesAround $ char '='
+    expr <- parseExpr
+    newId <- incrementSid
+    return $ SAssign var expr newId lineN
+    
+parseSimpleAssign :: Parser' Stmt
+parseSimpleAssign = do
+    lineN <- getLineN
+    var <- parseStrictVar
+    spacesAround $ char '='
+    expr <- parseTernary <|> parseTerm
+    newId <- incrementSid
+    return $ SAssign var expr newId lineN
+
+parseStmtExpr :: Parser' Stmt
+parseStmtExpr = do
+    lineN <- getLineN
+    expr <- parseExpr
+    fltr <- stmtEndFilter
+    newId <- incrementSid
+    return $ case fltr of
+        [] -> SExpr expr newId lineN
+        fltrs -> foldl' (\stmt (fltrName,exprs) -> SFilter fltrName exprs stmt newId lineN) (SExpr expr newId lineN) fltrs
+        
+parseFilter :: Parser' Stmt
+parseFilter = do
+    try $ string "FILTER" >> spaces1
+    lineN <- getLineN
+    newId <- incrementSid
+    (VarKey name) <- parseVarKey
+    exprs <- try (parens $ sepBy parseExpr (spacesAround (char ','))) <|> return []
+    stmtEnd
+    body <- parseStmtSeq
+    parseEnd
+    return $ SFilter name exprs body newId lineN
+    
+parseStmtSeq :: Parser' Stmt
+parseStmtSeq = do
+    newId <- incrementSid
+    lineN <- getLineN
+    seq <- many1 (
+        parseComment <|>
+        parseText <|>
+        parseIf <|>
+        parseWhile <|>
+        parseForeach <|>
+        parseLast <|>
+        parseNext <|>
+        parseBlock <|>
+        parseProcess <|>
+        parseWrapper <|>
+        parseFilter <|>
+        (try $ parseAssign <* stmtEnd) <|>
+        parseStmtExpr
+        )
+    return $ Seq seq newId lineN
+    
+parseTemplateWithStmtId :: T.Text -> TName -> StmtId -> Either ParseError Stmt
+parseTemplateWithStmtId t tName sId = runParser parseStmtSeq (tName,sId) "" (T.concat [(T.pack "%]"), t, (T.pack "[%")])
+
+parseTemplate tName t = parseTemplateWithStmtId t tName (0,0,0)
+ Text/TemplateToolkitLang.hs view
@@ -0,0 +1,453 @@+{-|
+Module      : Text.TemplateToolkitLang
+Description : Template Toolkit language manual for Haskell
+Copyright   : (c) Dzianis Kabanau, 2017
+Maintainer  : kobargh@gmail.com
+
+Template Toolkit is a template processing system originally written in Perl by Andy Wardley.
+Below is the complete documentation of features implemented in Haskell version of the system. Original documentation is available <http://www.template-toolkit.org/docs/index.html here>, but be warned that not all features are realized in Haskell version.
+
+-}
+
+module Text.TemplateToolkitLang (
+    -- * Tags
+    -- $tags
+    
+    -- * Variables
+    -- $variables
+    
+    -- * Operators
+    -- $operators
+    
+    -- * Directives
+    -- $directives
+    
+    -- * Virtual methods
+    -- $vmethods
+    
+    -- * Filters
+    -- $filters
+    
+ ) where
+
+{-$tags
+
+Character sequences @__[% %]__@ are used to enclose directive tags within a template.
+
+@
+ Hello __[% user.name %]__!
+@
+
+If directive tag starts by @__[%-__@ then all spaces before the tag are stripped. With @__-%]__@ all spaces after the tag are removed.
+
+Multiple directives may be enclosed within one tag. In this case they must be separated by @__;__@
+
+Comments are started by @__#__@
+
+@
+ __[%__
+   __FOREACH__ __x__ = [0,15,100];
+     __x__;     /# Comments here/
+   __END__;
+ __%]__
+@
+-}
+
+{- $variables
+There are three types of variables implemented: SCALARS, ARRAYS and HASHES.
+
+Dots are used to traverse hashes and arrays: @__[% my_hash.key1 %]__@, @__[% my_array.0 %]__@.
+
+Arrays may be represented in range form: @__[% xs = [1..10] %]__@
+
+Actually arrays and hashes are kind-of Perl's arrayrefs and hashrefs.
+So, for example, if two variables refer to one hash, modifying one variable affects another too.
+Assignment to undefined hashes and arrays __autovivificates__ needed structures.
+
+@
+ __[%__
+   /# scalar/
+   __int__ = 100;
+   __flt__ = 0.2;
+   __str__ = \"Hello \\\"guest\\\"\"; /# backslash is used to escape characters \" \' \\ $ and to type newlines \\n and tabs \\t./
+   
+   /# array/
+   __arr__ = [1,2,\"three\"];
+   __rng__ = [1..10];       /# [1,2,3,4,5,6,7,8,9,10]/
+   
+   /# hash/
+   __foo__ = {
+     k1 => 1,
+     k2 => \"value 2\",
+     k3 => [1,2,3],
+     k4 => {
+        subk1 => 10,
+        subk2 => 20,
+     }
+   };
+   
+   __bar__ = __foo.k4;__
+   __bar.subk1__ = \"this is ten\";
+   __foo.k4.subk1;__        /# \'this is ten\'/
+   
+   __bazarray.10.k1__ = 1; /# autovivification of array \'bazarray\' and hash \'bazarray.10\'/
+   
+ __%]__
+@
+
+Variables may be __interpolated__ in double-quoted strings and in complex dotted variables (hashes or arrays).
+
+__$__ character is used to precede interpolated variable: @"__$var__"@. Complex variables may be enclosed in curly braces: @"__${myhash.k1.0}__"@. 
+
+Single-qouted strings do not interpolate variables.
+
+@
+ __[%__
+   __i__ = 2;
+   __kbar__ = \'bar\';
+   __arr__ = [\'one\',\'two\',\'three\',\'four\',\'five\'];
+   __hsh__ = {foo => 10, bar => 20};
+   __arr.$i__; /# \'three\'/
+   \"Foo is __${hsh.foo}__.0\";         /# \'Foo is 10.0\'/
+   __hsh.$kbar__; /# 20/
+ __%]__
+@
+-}
+
+{-$operators
+=== Arithmetic operators
+
+@
+ __[%__
+   15 + 3.14;    /# 18.14/
+   12 - 10;      /# 2/
+   2 * 2;        /# 4/
+   10 \/ 4;       /# 2.5/
+
+   5 % 2;        /# 1       modulus (remainder) of division/
+                 /#         \'mod\' - synonym of \'%\'/
+   13 mod 5;     /# 3/
+
+   +7;           /# 7       unary positive/
+   -7;           /# -7      unary negative/
+ __%]__
+@
+
+=== Logical operators
+__0__ and empty strings are /false/ values. Everything else is /true/. Internal representation of /true\/false/ is __1__\/__0__ respectively.
+
+If both operands of comparison are strings, then they are compared as strings. Otherwise string operand is parsed as number.
+
+@
+ __[%__
+   100 __>__ 99;            /# 1        true/
+   100 __>=__ 101;          /# 0        false/
+   100 __<__ 100;           /# 0        false/
+   100 __<=__ 100;          /# 1        true/
+   100 __==__ 100;          /# 1        true/
+   100 __!=__ 100;          /# 0        false/
+   __!__0;                  /# 1        unary negation/
+   
+   \"100\" > 2;           /# 1        true - numeric comparison/
+   \"100\" > \"2\";         /# 0        false - string comparison/
+   
+   0 __||__ 100;            /# 100/
+                        /# \'or\' - synonym of \'||\'/
+   30 __or__ \'\';            /# 30/
+   
+   \'fox\' __&&__ \'dog\';      /# \'dog\'/
+                        /# \'and\' - synonym of \'&&\'/
+   \'fox\' __and__ \"\";        /# 0        evaluates to false value/
+   
+   /# operations group with ( )/
+   (\"string\" && 0) __?__ \"snow\" __:__ \"rain\";      /# \'rain\'/
+   /# cond ? val_true : val_false          ternary condition operator/
+   /# complex operands of ternary condition operator must be enclosed in parentheses:/
+   (!loop.last) ? \'not last\' : \'last\' /# \'not last\' or \'last\'/
+   !loop.last ? \'not last\' : \'last\' /# always evals to 0 - same as !(loop.last ? \'not last\' : \'last\')/
+ __%]__
+@
+
+=== String concatenation
+Underscore @__\___@ separated by spaces is used to concatenate strings.
+
+@
+ __[%__
+   __name__ = \"Zork\";
+   \"Hello,\" _ __name__;        /# \'Hello, Zork\'/
+ __%]__
+@
+-}
+
+{-$directives
+=== Conditionals
+
+__IF__ ... [ELSIF...] [ELSE...] __END__
+
+@
+__[% IF user.name__ == \'root\' __%]__
+
+Hello, Admin!
+
+__[% ELSIF user.status__ > 1 __%]__
+
+Welcome, master __[% user.name %]__!
+
+__[% ELSE %]__
+
+Hi, __[% user.name %]__!
+
+__[% END %]__
+@
+
+=== Loops
+
+__FOREACH__ x = ... __END__
+
+Special variable @__loop__@ is used to save information about foreach loop and current iteration.
+
+@
+__[%__ __FOREACH__ __i__ = [10..15] __%]__
+
+ I is __[% i %]__
+
+ __[%__
+   __IF__ __i__ == 12;
+     __loop.index;__ /# 2       current index/
+     __loop.count;__ /# 3       iteration count - index+1/
+     __loop.first;__ /# 10      first loop element/
+     __loop.last;__  /# 15      last loop element/
+     __loop.prev;__  /# 11      previous loop element/
+     __loop.next;__  /# 13      next loop element/
+     __loop.odd;__   /# 1       true if iteration count is odd/
+     __loop.even;__  /# 0       true if iteration count is even/
+   __END;__
+ __%]__
+
+__[% END %]__
+@
+
+__WHILE__ ... __END__
+
+@
+__[%__
+  x = 1;
+  __WHILE__ x <= 20;
+
+    "| X$x ";
+    x = x + 1; __IF__ x > 3; __LAST__; __END__;
+
+    __FOREACH__ y = [1..10];
+      __IF__ y > 2 && y < 9; __NEXT__; __END__;
+      "y$y ";
+    __END__;
+    
+  __END__;
+  /# Output: | X1 y1 y2 y9 y10 | X2 y1 y2 y9 y10 | X3 y1 y2 y9 y10/
+__%]__
+@
+
+__LAST__ is used to immediately break current loop. __NEXT__ - to skip to the next iteration.
+
+=== Processing template files and blocks
+
+__PROCESS__ /blockname_or_filename/ [var1 = ... var2 = ... varN = ...]
+
+__BLOCK__ /blockname/ ... __END__
+
+@
+\<html>
+  __[%__ __PROCESS__ header.tt __title__ = \'Homepage\' __descr__ = \'Homepage description\' __keyw__ = \'homepage,home,firstpage\' __%]__
+
+  \<p>Hello, __[% user.name %]__!
+  
+  __[% BLOCK__ foobarbazzer __%]__
+    \<p>Foo__[% word %]__barbaz
+  __[% END %]__
+  
+  __[%__
+    __FOREACH w__ = [\'fox\', \'dog\', \'cat\'];
+      __PROCESS__ foobarbazzer __word__ = __w__; /# \<p>Foofoxbarbaz \<p>Foodogbarbaz \<p>Foocatbarbaz/
+    __END__;
+  __%]__
+  
+  __[%__ __PROCESS__ footer.tt __%]__
+\</html>
+@
+
+Variable may be passed to __PROCESS__ or __WRAPPER__ directive as a name using __$__: @__[% PROCESS $template %]__@
+
+=== Wrapper
+
+__WRAPPER__ /wrapper_block_or_filename/ [var1 = ... var2 = ... varN = ...] ... __END__
+
+The @__WRAPPER__@ block evaluates its content and passes the result in the variable @__content__@ to /wrapper_block_or_filename/. This is very useful for web templates with the same skeleton.
+
+@
+/FILE index.tt/
+__[% WRAPPER html_wrap.tt title = \'Barbaz homepage!\' %]__
+\<p>Welcome to Barbaz home!
+__[% END %]__
+
+
+/FILE html_wrap.tt/
+\<html>
+\<head>
+ \<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">
+ \<title>__[% title %]__\</title>
+\</head>
+\<body>
+__[% content %]__
+\</body>
+\</html>
+@
+
+-}
+
+{-$vmethods
+
+Virtual methods are applied to the variables to get new values or to modify array or hash variable.
+
+@__[% my_array.size %]__@ - Get the number of elements in @__my_array__@.
+
+@__[% karr = my_hash.keys %]__@ - Create array @__karr__@. The elements are the keys of @__my_hash__@.
+
+@__[% my_array.push(\"Baz\") %]__@ - Modify array @__my_array__@ appending scalar @__\"Baz\"__@ to the end of the array.
+
+Methods that use regular expressions are based on __pcre__ patterns, e.g. @__[% bar.match(\'(?i)^baz\\\\d$\') %]__@.
+
+Below is the list of implemented virtual methods. You can also take a look at the original documentation: <http://www.template-toolkit.org/docs/manual/VMethods.html Template Toolkit VMethods>.
+
+===Scalar vmethods
+[@.collapse@] Returns the string with trailing and leading spaces removed and internal multiple spaces converted to single space.
+
+[@.defined@] Returns true if the value is defined.
+
+[@.lcfirst@] Returns the string with first character converted to lower case.
+
+[@.length@] Returns the length of the string.
+
+[@.lower@] Returns the string converted to lower case.
+
+[@.match(/pattern/, /global/)@] Returns the array of captured substrings from regexing target string with @/pattern/@. If pattern-match fails, returns @/false/@. Optional @/global/@ is used for global pattern-match to return all captured substrings.
+
+[@.replace(/old/, /new/)@] Returns the string with all occurrences of @/old/@ replaced by @/new/@.
+
+[@.split(/pattern/)@] Returns the array produced by splitting the string by @/pattern/@. 
+
+[@.trim@] Returns the string with leading and trailing spaces removed.
+
+[@.ucfirst@] Returns the string with first character converted to upper case.
+
+[@.upper@] Returns the string converted to upper case.
+
+===Array vmethods
+
+[@.first@] Returns the first element of the array.
+
+[@.grep(/pattern/)@] Returns the array of elements that match @/pattern/@.
+
+[@.import(/arr1/, /arr2/, /arrN/)@] Imports to the target array elements of all passed arrays. Target array is modified.
+
+[@.join(/separator/)@] Returns the string created by joining all elements of the array separated by @/separator/@.
+
+[@.last@] Returns the last element of the array.
+
+[@.nsort@] Returns the array sorted numerically.
+
+[@.pop@] Removes the last element from the array. Target array is modified.
+
+[@.push(/el1/, /el2/, /elN/)@] Appends element(s) to the end of the array. Target array is modified.
+
+[@.reverse@] Returns reversed version of the array.
+
+[@.shift@] Removes the first element from the array. Target array is modified.
+
+[@.size@] Returns the number of elements in the array.
+
+[@.slice(/from/, /to/)@] Returns the array of elements idexing in the range @/from/../to/@ of the original array. If @/to/@ is omitted, then to the last element.
+
+[@.sort@] Returns the array sorted alphabetically.
+
+[@.splice(/offset/,/length/,/insertarray/)@] Powerful array-modification method that behaves just like <https://perldoc.perl.org/functions/splice.html Perl's splice>. Removes from the array @/length/@ elements starting from @/offset/@ index, replacing them with @/insertarray/@. If @/insertarray/@ is omitted - just removes elements. If @/length/@ is omitted removes everything up to the end of the array. @/length/@ may be @0@ - no elements are removed. @/offset/@ may be negative - counts index from the end of the array, e.g. @-2@ - penultimate element's index. Target array is modified.
+
+[@.unique@] Returns the array of only unique elements.
+
+[@.unshift(/el1/, /el2/, /elN/)@] Prepends element(s) to the beginning of the array. Target array is modified.
+
+===Hash vmethods
+
+[@.delete(/key1/, /key2/, /keyN/)@] Deletes items identified by the keys from the hash. Target hash is modified.
+
+[@.each@] Returns keys and values of the hash in one array - @[key1, val1, key2, val2, ... keyN, valN]@.
+
+[@.keys@] Returns the array of the hash keys.
+
+[@.import(/hash1/, /hash2/, /hashN/)@] Imports to the target hash items of all passed hashes. Existing items get new values, new items are added. Target hash is modified.
+
+[@.item(/key/)@] Returns the value of hash item identified by /key/. This is useful to get values with keys that contain any characters. E.g. @__[% hash.item('complex $ key.') %]__@ or @__[% hash.item('12') %]__@.
+
+[@.pairs@] Returns the array of key-value pairs of the hash. The pairs are hashes each with two keys: @__key__@ and @__value__@.
+
+[@.size@] Returns the number of items in the hash.
+
+[@.values@] Returns the array of the hash values.
+
+Sometimes a hash may have items whith keys that conflict with the above method names. In this situation parentheses may be used to force method call.
+
+@
+__[%__
+  __my_car__ = {
+    \'keys\' => \'lost\',
+    \'mileage\' => 150000,
+  };
+  __my_car.keys__; /# \'lost\'/
+  __my_car.keys()__; /# [\'keys\', \'mileage\']/
+__%]__
+@
+
+-}
+
+{-$filters
+
+Filters transform text of a block. Filter may be applied as a directive:
+
+__FILTER__ /filter_name/ ... __END__
+
+Or in piped form (multiple filters may be chained):
+
+/expr/ __|__ /filter_name [| /filter_name]/
+
+@
+__[% FILTER collapse %]__
+__[% PROCESS body.tt %]__
+__[% END %]__
+
+The same:
+
+__[% PROCESS body.tt | collapse %]__
+@
+
+Below is the list of implemented filters.
+
+[@collapse@] Removes leading and trailing spaces. Internal multiple spaces are converted to single space.
+
+[@html@] Replaces all @__\<__@, @__>__@, @__&__@, @__\"__@ with @__&lt;__@, @__&gt;__@, @__&amp;__@, @__&quot;__@ respectively.
+
+[@lcfirst@] Converts the first character to lower case.
+
+[@lcfirst@] Converts all characters to lower case.
+
+[@null@] Discards the text of block
+
+[@replace(/old/, /new/)@] Replaces all occurrences of @/old/@ with @/new/@.
+
+[@trim@] Removes leading and trailing spaces.
+
+[@ucfirst@] Converts the first character to upper case.
+
+[@upper@] Converts all characters to upper case.
+
+[@uri@] URI-encodes text.
+
+-}
+ example/conf.json view
@@ -0,0 +1,10 @@+{
+	"_CONFIG":{
+		"INCLUDE_PATH":["."]
+	},
+	"users":{
+		"Foo": 13,
+		"Bar": 3.14,
+		"Baz": "bazzz"
+	}
+}
+ example/template-toolkit-example.hs view
@@ -0,0 +1,11 @@+import Text.TemplateToolkit
+import qualified Data.Text.IO as TIO (readFile)
+import qualified Data.Text as T
+
+main = do
+    cfg <- TIO.readFile "./conf.json"
+    s <- evalTemplateFile "template.tt" (JSONstring cfg)
+    case s of
+        (Right txt) -> putStr . T.unpack $ txt
+        (Left err) -> putStr ("ERROR! " ++ err)
+        
+ example/template.tt view
@@ -0,0 +1,15 @@+<html>
+	<body>
+		<h1>Template Toolkit for Haskell</h1>
+		<h2>Count 1-10:</h2>
+		[% FOREACH i = [1..10] -%]
+			[% i; (!loop.last) ? ', ' : '.' %]
+		[%- END %]
+		<h2>Users hash:</h2>
+		[% FOREACH user = users.pairs %]
+			<p>[% user.key %]: [% user.value %]
+		[% END %]
+		<h2>External template:</h2>
+		[% PROCESS template2.tt words = ['dog','cat','pig'] %]
+	</body>
+</html>
+ example/template2.tt view
@@ -0,0 +1,1 @@+<p>[% words.sort.reverse.join('|') %]
+ template-toolkit.cabal view
@@ -0,0 +1,33 @@+-- Initial template-toolkit.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                template-toolkit
+version:             0.1.0.1
+synopsis:            Template Toolkit implementation for Haskell
+description:         Template Toolkit is a template processing system originally written in Perl by Andy Wardley.
+					 This haskell implementation includes such features as:
+					 scalar, array, hash variables, variable interpolation,
+					 conditional directives,
+					 loops and loop controls,
+					 external templates and internal blocks processing,
+					 lots of virtual methods and filters.
+					 
+homepage:            https://github.com/kobargh/template-toolkit
+license:             GPL-3
+license-file:        LICENSE
+author:              Dzianis Kabanau
+maintainer:          kobargh@gmail.com
+-- copyright:           
+category:            Text
+build-type:          Simple
+extra-source-files:  example/*.hs, example/*.json, example/*.tt
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     Text.TemplateToolkit, Text.TemplateToolkitLang
+  other-modules:       Text.TemplateToolkitAST
+  -- other-extensions:    
+  -- build-depends:       base >=4.8 && <4.9, parsec >=3.1 && <3.2, text >=1.2 && <1.3, containers >=0.5 && <0.6, mtl >=2.2 && <2.3, directory >=1.2 && <1.3, unordered-containers >=0.2 && <0.3, bytestring >=0.10 && <0.11, regex-pcre-builtin >=0.94 && <0.95, pcre-utils >=0.1 && <0.2, uri-encode >=1.5 && <1.6, aeson >=1.2 && <1.3, scientific >=0.3 && <0.4
+  build-depends:       base >=4.7 && <4.10, parsec, text, containers, mtl, directory, unordered-containers, bytestring, regex-pcre-builtin, pcre-utils, uri-encode, aeson, scientific
+  -- hs-source-dirs:      
+  default-language:    Haskell2010