Dao 0.0.0.0 → 0.1.0.2
raw patch · 27 files changed
+20532/−821 lines, 27 filesdep +Cryptodep +arraydep +binarydep ~base
Dependencies added: Crypto, array, binary, bytestring, containers, data-binary-ieee754, deepseq, directory, filepath, mtl, process, random, time, transformers, utf8-string
Dependency ranges changed: base
Files
- Dao.cabal +95/−26
- LICENSE +0/−674
- Main.hs +0/−73
- README +0/−48
- src/Dao/Binary.hs +640/−0
- src/Dao/Glob.hs +439/−0
- src/Dao/HashMap.hs +256/−0
- src/Dao/Interpreter.hs +8391/−0
- src/Dao/Interpreter/AST.hs +3135/−0
- src/Dao/Interpreter/Parser.hs +831/−0
- src/Dao/Interpreter/Tokenizer.hs +136/−0
- src/Dao/Interval.hs +910/−0
- src/Dao/Lib/Array.hs +165/−0
- src/Dao/Lib/File.hs +208/−0
- src/Dao/Lib/ListEditor.hs +222/−0
- src/Dao/PPrint.hs +358/−0
- src/Dao/Parser.hs +1927/−0
- src/Dao/Predicate.hs +253/−0
- src/Dao/Random.hs +483/−0
- src/Dao/RefTable.hs +75/−0
- src/Dao/Stack.hs +88/−0
- src/Dao/StepList.hs +414/−0
- src/Dao/String.hs +521/−0
- src/Dao/Token.hs +400/−0
- src/Dao/Tree.hs +437/−0
- src/dao-main.hs +122/−0
- tests/main.hs +26/−0
Dao.cabal view
@@ -1,29 +1,98 @@-Name: Dao-Version: 0.0.0.0-Cabal-Version: >= 1.6-Copyright: 2008-2009, Ramin Honary-License: GPL-License-File: LICENSE-Author: Ramin Honary <ramin.honary@gmail.com>-Maintainer: ramin.honary@gmail.com-Homepage: -Synopsis: An interactive knowledge base, natural language interpreter.-Description:- This program is still largely incomplete.- Dao is an artificial intelligence program which allows users to construct- a knowledge base for intepreting natural language input. The idea is to- let users interactively build their own knowledge base by adding rules at- runtime. The state of the knowledge base can be updated by enacting it's- own production rules, or by a user entering commands to alter the state- directly. Regular-expression-like patterns are associated with rules so- natural language input can be used to invoke rules at any time.-Category: AI-Build-Type: Simple+-- "dao.cabal" The metafile for building the Dao System using Cabal.+-- +-- Copyright (C) 2008-2012 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>. -Extra-Source-Files: README +Name: Dao+Version: 0.1.0.2+Cabal-Version: >= 1.10+License: GPL-3+Copyright: (C) 2008-2014 Ramin Honary, all rights reserved.+Author: Ramin Honary+Maintainer: ramin.honary@gmail.com+Build-Type: Simple+Synopsis: Dao is meta programming language with its own built-in+ interpreted language, designed with artificial+ intelligence applications in mind.+Description:+ The Dao modules and interactive program is a meta programming language+ intended for artificial intelligence uses. It is very much like the+ classic UNIX "AWK" scripting language, but instead of using POSIX-style+ regular epxressions, the patterns used in Dao are designed to more easily+ match natural language input.+Library+ HS-source-dirs: src+ Default-Language: Haskell2010+ GHC-options: -threaded -Wall+ -fno-warn-name-shadowing+ -fno-warn-unused-do-bind+ Build-Depends:+ base == 4.* , mtl >= 2.0.1.0, random >= 1.0.0.1,+ time >= 1.4.2 , directory >= 1.1.0.0, filepath >= 1.2.0.0,+ process >= 1.0.1.2, array >= 0.3.0.2, bytestring >= 0.9.1.2,+ utf8-string >= 0.3.2 , binary >= 0.5.0.2, Crypto >= 4.0.0.0,+ transformers >= 0.2.2.0, containers >= 0.4.0.0, deepseq >= 1.0.0.0,+ data-binary-ieee754 >= 0.4.4+ Default-Extensions:+ TemplateHaskell ScopedTypeVariables RankNTypes + MultiParamTypeClasses FunctionalDependencies FlexibleInstances + FlexibleContexts DeriveFunctor DeriveDataTypeable + GeneralizedNewtypeDeriving + Exposed-Modules:+ Dao.Interval, Dao.Tree , Dao.RefTable , Dao.Random ,+ Dao.StepList, Dao.Predicate, Dao.Binary , Dao.HashMap ,+ Dao.String , Dao.Stack , Dao.PPrint , Dao.Parser ,+ Dao.Token , Dao.Glob , Dao.Interpreter, Dao.Lib.File,+ Dao.Lib.ListEditor, Dao.Lib.Array,+ Dao.Interpreter.Tokenizer, Dao.Interpreter.AST, Dao.Interpreter.Parser+ Executable dao- Main-is: Main.hs- Build-Depends: base- Hs-Source-Dirs: .- GHC-Options: -Wall+ HS-source-dirs: src+ Main-is: dao-main.hs+ Default-language: Haskell2010+ Build-Depends:+ base >= 4.3.1.0, mtl >= 2.0.1.0, random >= 1.0.0.1,+ time >= 1.4.2 , directory >= 1.1.0.0, filepath >= 1.2.0.0,+ process >= 1.0.1.2, array >= 0.3.0.2, bytestring >= 0.9.1.2,+ utf8-string >= 0.3.2 , binary >= 0.5.0.2, Crypto >= 4.0.0.0,+ transformers >= 0.2.2.0, containers >= 0.4.0.0, deepseq >= 1.0.0.0,+ data-binary-ieee754 >= 0.4.4+ Default-Extensions:+ TemplateHaskell ScopedTypeVariables RankNTypes + MultiParamTypeClasses FunctionalDependencies FlexibleInstances + FlexibleContexts DeriveFunctor DeriveDataTypeable + GeneralizedNewtypeDeriving ++Test-suite main+ HS-source-dirs: tests+ Main-is: main.hs+ Default-language: Haskell2010+ Type: exitcode-stdio-1.0+ Build-Depends:+ base >= 4.3.1.0, mtl >= 2.0.1.0, random >= 1.0.0.1,+ time >= 1.4.2 , directory >= 1.1.0.0, filepath >= 1.2.0.0,+ process >= 1.0.1.2, array >= 0.3.0.2, bytestring >= 0.9.1.2,+ utf8-string >= 0.3.2 , binary >= 0.5.0.2, Crypto >= 4.0.0.0,+ transformers >= 0.2.2.0, containers >= 0.4.0.0, deepseq >= 1.0.0.0,+ data-binary-ieee754 >= 0.4.4+ Extensions:+ TemplateHaskell ScopedTypeVariables RankNTypes + MultiParamTypeClasses FunctionalDependencies FlexibleInstances + FlexibleContexts DeriveFunctor DeriveDataTypeable + GeneralizedNewtypeDeriving +
− LICENSE
@@ -1,674 +0,0 @@- 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>.
− Main.hs
@@ -1,73 +0,0 @@-{- Main -- The main source of the Dao program.- - Copyright (C) March 6, 2009, Ramin Honary- -- - The main source of the Dao program. This file contains little more than the main- - function, a function to manage input arguments and the environment, and then a- - run loop which calls the "readline" library and passes the string input to the Dao- - runtime interpreter in the "Dao.hs" source file.- -- - THIS PROGRAM IS INCOMPLETE AS OF: March 15, 2009- - ------------------------------------------------- -- - 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/>.- -- - Licensed under the GNU General Public License <http://www.gnu.org/licenses/gpl.html>- -}--module Dao where---- import qualified Data.Map as Map--- import qualified Data.Set as Set--import System-import System.Environment-import System.Console.Readline-import System.IO--- import System.IO.Error--- import System.Process--import Ramins_mod-import Errst-import Parser_st-import Dao_base_parser-import Dao_base-import Dao-----------1---------2---------3---------4---------5---------6---------7---------8---------9--------10--{- Take an input string from the "readline" library routine, run the "dao" state with that input- - string, then loop. If the "dao" state comes back with it's on-off switch set to False, return- - control to the main function and (presumably) quit the program. -}-run_loop :: Dao -> IO ()-run_loop dao = do- if (onoff_switch dao)- then do- instr <- readline ">> "- case instr of- Nothing -> return ()- (Just instr) -> do- dao1 <- apply_string_input instr dao- run_loop dao1- else return ()--manage_args :: [String] -> [(String, String)] -> IO Dao-manage_args args env = do- return (Dao True args env [Map.empty] Map.empty Map.empty [] [] [])--main = do- args <- getArgs- env <- getEnvironment- dao <- manage_args args env- run_loop dao-
− README
@@ -1,48 +0,0 @@-Dao -- An interactive knowledge base, natural language interpreter.-Copyright (C) March 25, 2009, Ramin Honary--This program is my first attempt to re-write my entire Master's thesis-as a Haskell program. It was initially written in C, and then when I-realized I couldn't finish my project in time making it in C, I switched-to Perl. I completed my thesis, but I was largely unsatisfied with my-work and continued to make improvements. Then I discovered Haskell and-decided it was the best way to make it work. I would like to publish the-work I have completed so far so I can continue to make improvements-publicly and with an open source license, the GPL.--Dao is an artificial intelligence program which allows users to construct-a knowledge base for intepreting natural language input. The idea is to-let users interactively build their own knowledge base by adding rules at-runtime. The state of the knowledge base can be updated by enacting it's-own production rules, or by a user entering commands to alter the state-directly. Regular-expression-like patterns are associated with rules so-natural language input can be used to invoke rules at any time.--Because the knowledge base is built interactively, it is very simple to-define a new regular expression production rule and test it out-immediately, to see if it interferes with other production rules in the-knowledge base, or to see if the regular expression was defined-correctly.--A future release of this program should be able to deduce regular-expressions and production rules without requiring the users to define-regular expressions directly.--THIS PROGRAM IS INCOMPLETE AS OF: March 15, 2009---------------------------------------------------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/>.--(See the file "LICENSE" included in the same directory with this file.)-
+ src/Dao/Binary.hs view
@@ -0,0 +1,640 @@+-- "src/Dao/Binary.hs" declares the binary serializing monad.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | This module provides an essential wrapper around the 'Data.Binary.Binary' monad which allows a+-- binary serializer to read data type tags in the byte stream and select the next parser to be used+-- by looking up the parser with data type tag.+-- +-- Dao's binary protocol is compact and efficient, with every fundamental data type prefixed with a+-- single byte of information. Integers of arbitrary length are stored using Variable Length Integer+-- (VLI) encoding. However the byte stream is not compressed, and there are no functions in this+-- module which facilitate this, it is up to you to do compression. Using algorithms like GZip or+-- BZip2 will almost certainly decrese the size of the byte stream as Dao's binary protocol makes+-- no attempt to reduce data entropy.+--+-- Arbitrary data types can be encoded as long as they instantiate 'Data.Typeable.Typeable' and+-- 'Dao.Interpreter.ObjectInterface' and have been made available to the 'Dao.Interpreter.Runtime' during+-- initialization of the Dao program. Each new type placed in the stream creates an integer tag in+-- an index with the 'Data.Typeable.TypeRep', and every item of the type that is placed after that+-- tag is prefixed with the integer index value. When decoding, the index of tags is constructed on+-- the fly as they are read from arbitrary points in the stream, and the index is used to select the+-- correct binary decoder from the 'Dao.Interpreter.ObjectInterface' stored in the 'Dao.Interpreter.Runtime'.+-- +-- Of course, this module is not a full re-writing of "Data.Binary", it relies heavily on the+-- "Data.Binary" module, and provides a Dao-friendly wrapper around it.+module Dao.Binary where++import Dao.String+import qualified Dao.Tree as T+import Dao.Token+import Dao.Predicate++import Control.Applicative+import Control.Monad+import Control.Monad.Error+import qualified Control.Monad.State as S++import Data.Monoid+import Data.Dynamic+import Data.Char+import Data.Int+import Data.Ratio+import Data.Complex+import Data.Word+import Data.Bits+import Data.Time+import Data.Array.IArray+import qualified Data.Map as M+import qualified Data.IntMap as Im+import qualified Data.Set as S+import qualified Data.IntSet as Is+import qualified Data.ByteString.Lazy as Z++import Data.Digest.SHA1 as SHA1+import qualified Data.Binary.IEEE754 as B+import qualified Data.ByteString as B+import qualified Data.Binary as B+import qualified Data.Binary.Get as B+import qualified Data.Binary.Put as B++----------------------------------------------------------------------------------------------------++type Byte = Word8+type InStreamID = Word32+type ByteOffset = B.ByteOffset++-- | A data type used to help instantiate the 'Dao.Binary.Binary' class. Refer to the+-- 'fromDataBinary' function for more details.+data Serializer mtab a = Serializer{ serializeGet :: GGet mtab a, serializePut :: a -> GPutM mtab () }++-- | Minimal complete definition is to either instantiate both 'get' and 'put', or to instnatiate+-- just 'serializer'. You can instantiate all three if you want but that may cause a lot of+-- confusion. Apart from 'serializer', it is identical to the 'Data.Binary.Binary' class, so please+-- refer to that module for more background information on how to use this one.+class Binary a mtab where+ get :: GGet mtab a+ get = serializeGet serializer+ put :: a -> GPutM mtab ()+ put = serializePut serializer+ serializer :: Serializer mtab a+ serializer = Serializer{serializeGet=Dao.Binary.get,serializePut=Dao.Binary.put}++class HasCoderTable mtab where+ getEncoderForType :: Name -> mtab -> Maybe (Dynamic -> GPut mtab)+ getDecoderForType :: Name -> mtab -> Maybe (GGet mtab Dynamic)++-- | To evaluate a 'GPut' or 'GGet' function without providing any coder table, simply pass @()@.+instance HasCoderTable () where+ getEncoderForType _ _ = Nothing+ getDecoderForType _ _ = Nothing++data EncodeIndex mtab+ = EncodeIndex+ { indexCounter :: InStreamID+ , encodeIndex :: M.Map Name InStreamID+ , encMTabRef :: mtab+ }++data DecodeIndex mtab+ = DecodeIndex+ { decodeIndex :: M.Map InStreamID Name+ , decMTabRef :: mtab+ }++newtype GPutM mtab a = PutM{ encoderToStateT :: S.StateT (EncodeIndex mtab) B.PutM a }+type GPut mtab = GPutM mtab ()++data GGetErr = GetErr { gGetErrOffset :: ByteOffset, gGetErrMsg :: UStr }+instance Show GGetErr where { show (GetErr ofst msg) = "(offset="++show ofst++") "++uchars msg }++newtype GGet mtab a = Get{ decoderToStateT :: PredicateT GGetErr (S.StateT (DecodeIndex mtab) B.Get) a }++instance Functor (GPutM mtab) where { fmap f (PutM a) = PutM (fmap f a) }+instance Monad (GPutM mtab) where+ return = PutM . return+ (PutM a) >>= fn = PutM (a >>= encoderToStateT . fn)+ fail = PutM . fail+instance Applicative (GPutM mtab) where { pure=return; (<*>)=ap; }+instance Monoid a => Monoid (GPutM mtab a) where+ mempty=return mempty+ mappend a b = a >>= \a -> b >>= \b -> return (mappend a b)+instance HasCoderTable mtab => S.MonadState (EncodeIndex mtab) (GPutM mtab) where+ state fn = PutM (S.state fn)++instance Functor (GGet mtab) where { fmap f (Get a) = Get (fmap f a) }+instance Monad (GGet mtab) where+ return = Get . return+ (Get a) >>= fn = Get (a >>= decoderToStateT . fn)+ Get a >> Get b = Get (a >> b)+ fail msg = bytesRead >>= \ofst -> Get (throwError (GetErr ofst (toUStr msg)))+instance MonadPlus (GGet mtab) where { mzero = Get mzero; mplus (Get a) (Get b) = Get (mplus a b); }+instance Applicative (GGet mtab) where { pure=return; (<*>)=ap; }+instance Alternative (GGet mtab) where { empty=mzero; (<|>)=mplus; }+instance Monoid a => Monoid (GGet mtab a) where+ mempty=return mempty+ mappend a b = a >>= \a -> b >>= \b -> return (mappend a b)+instance S.MonadState (DecodeIndex mtab) (GGet mtab) where+ state = Get . lift . S.state+instance MonadError GGetErr (GGet mtab) where+ throwError = Get . throwError+ catchError (Get fn) catch = Get (catchError fn (decoderToStateT . catch))++-- | This class only exists to provide the the function 'getCoderTable' with the exact same function+-- in both the 'GPutM' and 'GGet' monads, rather than having a separate function for each monad.+class HasCoderTable mtab => ProvidesCoderTable m mtab where { getCoderTable :: m mtab }+instance HasCoderTable mtab => ProvidesCoderTable (GPutM mtab) mtab where { getCoderTable = S.gets encMTabRef }+instance HasCoderTable mtab => ProvidesCoderTable (GGet mtab) mtab where { getCoderTable = S.gets decMTabRef }++data InStreamIndex = InStreamIndex{ inStreamIndexID :: InStreamID, inStreamIndexLabel :: Name }+ deriving (Eq, Ord, Show)+instance HasCoderTable mtab => Binary InStreamIndex mtab where+ put (InStreamIndex a b) = prefixByte 0x01 $ put a >> put b+ get = tryWord8 0x01 $ pure InStreamIndex <*> get <*> get++-- | Find the 'Dao.String.UStr' that was associated with this 'InStreamID' when the byte stream was+-- constructed when 'newInStreamID' was called.+decodeIndexLookup :: HasCoderTable mtab => InStreamID -> GGet mtab (Maybe Name)+decodeIndexLookup tid = M.lookup tid <$> S.gets decodeIndex++-- | Given an 'Data.Int.Int64' length value, compute how many VLI bytes of hash code should be+-- necessary to for a byte stream of that length, and return an 'Data.Word.Word64' value trimmed to+-- that byte length.+trimIntegerHash :: Int64 -> Integer -> (Int, Integer)+trimIntegerHash i h = snd $ head $ dropWhile ((i<) . fst) $+ map (\x -> (2^(8+4*(fromIntegral x :: Int)), (x, h .&. (2^(7*x)-1)))) [0..14::Int]++-- | A trimmed hash is an hash produced by SHA1 along with the length of the original data. However+-- it instantiates 'Prelude.Eq', and 'Binary' in such a way that only a maximum of 5 bytes of the+-- hash value are ever stored and used for verification. This is to conserve space in a byte stream+-- when writing smaller chunks of data. For example, it is not necessary to store all 20 bytes of+-- the hash code when the data you are storing or reading is itself 20 bytes long.+data TrimmedHash = TrimmedHash Int64 Integer deriving (Eq, Ord)+instance Binary TrimmedHash mtab where+ put (TrimmedHash i h0) =+ let (len, h) = trimIntegerHash i h0+ in if len>0 then putPosIntegral h else return ()+ get = TrimmedHash (error "TrimmedHash length not set") <$> getPosIntegral++-- | Create a 'TrimmedHash' a list of bytes as the second parameter and the maximum length of the+-- list as the first parameter.+trimmedHash :: Z.ByteString -> TrimmedHash+trimmedHash blk = mkTrimmedHash blk where+ len = Z.length blk+ mkTrimmedHash = TrimmedHash len . snd . trimIntegerHash len . SHA1.toInteger . hash .+ map snd . takeWhile ((>0) . fst) . zip (iterate (\x->x-1) len) . Z.unpack++-- not for export+setTrimmedHashLength :: Int64 -> TrimmedHash -> TrimmedHash+setTrimmedHashLength i (TrimmedHash _ h) = TrimmedHash i h++trimmedHashVerify :: Z.ByteString -> TrimmedHash -> Bool+trimmedHashVerify b = (trimmedHash b ==)++-- | A lazy block stream that wraps up a 'Data.ByteString.Lazy.ByteString' in a data type that+-- instantiates 'Binary' in such a way that the bytes are written lazily in chunks of 1 megabyte+-- blocks with checksums, providing a protocol that can encode and decode arbitrarily large data+-- without interfearing with the protocol used by this module.+newtype BlockStream1M = BlockStream1M { block1MStreamToByteString :: Z.ByteString }+instance Binary BlockStream1M mtab where+ put =+ mapM_ (\ blk -> put blk >> put (trimmedHash blk)+ ) . fix (\ loop blk ->+ let (a,b) = Z.splitAt (2^(20::Int)) blk in a : if Z.null b then [] else loop b+ ) . block1MStreamToByteString+ get = (BlockStream1M . Z.concat) <$> loop [] where+ loop bx = get >>= \b -> get >>= \cksum ->+ if trimmedHashVerify b (setTrimmedHashLength (Z.length b) cksum)+ then loop (bx++[b])+ else fail "bad checksum"++-- | If the type signature in the given 'Dao.String.UStr' already has an associated type ID in the+-- encoder table, the existing ID is returned rather than creating a new one, and nothing changes.+-- If a new ID is created, the 'Dao.String.UStr' is paired with the new ID and written to the byte+-- stream.+newInStreamID :: HasCoderTable mtab => Name -> GPutM mtab InStreamID+newInStreamID typ = S.get >>= \st -> let idx = encodeIndex st in case M.lookup typ idx of+ Nothing -> do+ let nextID = indexCounter st + 1+ S.put $ st{indexCounter=nextID, encodeIndex=M.insert typ nextID idx}+ put $ InStreamIndex{inStreamIndexID=nextID, inStreamIndexLabel=typ}+ return nextID+ Just tid -> return tid++-- | When decoding a byte stream, it is up to you to check for the 'InStreamIndex'ies that are+-- scattered throughout. To do this, 'newInStreamID' is only called after a special escape byte+-- prefix is seen, for example, the byte prefix used when the 'Dao.Interpreter.OHaskell' constructor is+-- to be encoded. Once this prefix is decoded you should call 'newInStreamID'. That way, when you+-- are decoding the byte stream, you will know that 'updateTypes' must be called whenever you decode+-- the byte prefix for 'Dao.Interpreter.OHaskell'.+-- +-- This function simply checks if a 'InStreamIndex' exists at the current location in the byte+-- stream. If it does not exist, this function simply returns and does nothing. If it does exist,+-- the data is pulled out of the stream and the index is updated.+updateTypes :: HasCoderTable mtab => GGet mtab ()+updateTypes = tryWord8 0x01 $ do+ (InStreamIndex tid label) <- get+ S.modify $ \st -> st{decodeIndex = M.insert tid label (decodeIndex st)}++runPut :: HasCoderTable mtab => mtab -> GPut mtab -> Z.ByteString+runPut mtab fn = B.runPut $ S.evalStateT (encoderToStateT fn) $+ EncodeIndex{indexCounter=1, encodeIndex=mempty, encMTabRef=mtab}++runGet :: HasCoderTable mtab => mtab -> GGet mtab a -> Z.ByteString -> Predicate GGetErr a+runGet mtab fn = B.runGet $ S.evalStateT (runPredicateT $ decoderToStateT fn) $+ DecodeIndex{decodeIndex=mempty, decMTabRef=mtab}++encode :: (HasCoderTable mtab, Binary a mtab) => mtab -> a -> Z.ByteString+encode mtab = runPut mtab . put++decode :: (HasCoderTable mtab, Binary a mtab) => mtab -> Z.ByteString -> Predicate GGetErr a+decode mtab = runGet mtab get++encodeFile :: (HasCoderTable mtab, Binary a mtab) => mtab -> FilePath -> a -> IO ()+encodeFile mtab path = Z.writeFile path . encode mtab++decodeFile :: (HasCoderTable mtab, Binary a mtab) => mtab -> FilePath -> IO (Predicate GGetErr a)+decodeFile mtab path = decode mtab <$> Z.readFile path++putWithBlockStream1M :: HasCoderTable mtab => GPut mtab -> GPut mtab+putWithBlockStream1M fn = getCoderTable >>= \mtab -> put $ BlockStream1M $ runPut mtab fn++getWithBlockStream1M :: HasCoderTable mtab => GGet mtab a -> GGet mtab a+getWithBlockStream1M fn = do+ mtab <- getCoderTable+ (BlockStream1M bs1m) <- get+ Get (predicate (runGet mtab fn bs1m))++----------------------------------------------------------------------------------------------------++class (Ix i, Binary i mtab, Binary a mtab) => HasPrefixTable a i mtab where { prefixTable :: PrefixTable mtab i a }++-- | For data types with many constructors, especially enumerated types, it is effiecient if your+-- decoder performs a single look-ahead to retrieve an index, then use the index to lookup the next+-- parser in a table. This is a lookup table using 'Data.Array.IArray.Array' as the table which does+-- exactly that.+data PrefixTable mtab i a = PrefixTable String (Maybe (GGet mtab i)) (Maybe (Array i (GGet mtab a)))++instance Ix i => Functor (PrefixTable mtab i) where+ fmap f (PrefixTable msg getIdx t) = PrefixTable msg getIdx (fmap (amap (fmap f)) t)+instance (Integral i, Show i, Ix i, Binary a mtab) => Monoid (PrefixTable mtab i a) where+ mempty = PrefixTable "" Nothing Nothing+ mappend (PrefixTable msgA getIdxA a) (PrefixTable msgB getIdxB b) =+ PrefixTable (msgA<>"<>"<>msgB) (msum [getIdxA >> getIdxB, getIdxB, getIdxA]) $ msum $+ [ a >>= \a -> b >>= \b -> do+ let ((loA, hiA), (loB, hiB)) = (bounds a, bounds b)+ let ( lo , hi ) = (min loA loB, max hiA hiB)+ Just $ accumArray (flip mplus) mzero (lo, hi) (assocs a ++ assocs b)+ , a, b+ ]++-- | For each 'GGet' function stored in the 'PrefixTable', bind it to the function provided here and+-- store the bound functions back into the table. It works similar to the 'Control.Monad.>>='+-- function.+bindPrefixTable :: Ix i => PrefixTable mtab i a -> (a -> GGet mtab b) -> PrefixTable mtab i b+bindPrefixTable (PrefixTable msg getIdx arr) fn = PrefixTable msg getIdx (fmap (amap (>>=fn)) arr)++-- | Construct a 'Serializer' from a list of serializers, and each list item will be prefixed with a+-- byte in the range given. It is necesary for the data type to instantiate 'Data.Typeable.Typeable'+-- in order to +mkPrefixTable+ :: (Integral i, Show i, Ix i, Num i)+ => String -> GGet mtab i -> i -> i -> [GGet mtab a] -> PrefixTable mtab i a+mkPrefixTable msg getIdx lo' hi' ser =+ let len = fromIntegral (length ser)+ lo = min lo' hi'+ hi = max lo' hi'+ idxs = takeWhile (<=hi) (iterate (+1) lo)+ table = PrefixTable msg (Just getIdx) $ Just $+ accumArray (flip const) (fail ("in "++msg++" table")) (lo, hi) (zip idxs ser)+ in if null ser+ then PrefixTable msg (Just getIdx) Nothing+ else+ if 0<len && len<=hi-lo+1+ then table+ else error ("too many prefix table items for mkPrefixTable for "++msg)++mkPrefixTableWord8 :: String -> Byte -> Byte -> [GGet mtab a] -> PrefixTable mtab Byte a+mkPrefixTableWord8 msg = mkPrefixTable msg getWord8++runPrefixTable :: (Integral i, Show i, Ix i, Binary i mtab) => PrefixTable mtab i a -> GGet mtab a+runPrefixTable (PrefixTable _msg getIdx t) = flip (maybe mzero) t $ \decoderArray -> do+ prefix <- lookAhead (maybe get id getIdx)+ guard $ inRange (bounds decoderArray) prefix+ prefix <- maybe get id getIdx+ decoderArray!prefix++word8PrefixTable :: HasPrefixTable a Byte mtab => GGet mtab a+word8PrefixTable = runPrefixTable (prefixTable :: HasPrefixTable a Byte mtab => PrefixTable mtab Byte a)++prefixByte :: Byte -> GPut mtab -> GPut mtab+prefixByte w fn = putWord8 w >> fn++-- | This is a polymorphic object that has been constructed using the instances of the canonical+-- 'Data.Binary.Binary'. This makes it possible to write your binary instances like so:+-- > import Data.Binary+-- > import Dao.Binary+-- > import MyObject -- exports data type MyObject which instantiates the canonical 'Data.Binary.Binary'.+-- > instance Dao.Binary.Binary MyObject where { serializer = fromDataBinary }+--+-- I cannot just use GHC's @UndecidableInstances@ feature to declare all type which instantiate the+-- canonical 'Data.Binary.Binary' to also instantiate my own 'Dao.Binary.Binary' because+-- I need my own versions of 'Data.Binary.get' and 'Data.Binary.put' for certain types like+-- 'Data.Maybe' and list types. So unfortunately, we are stuck declaring a new instance for every+-- data type that needs serialization.+fromDataBinary :: B.Binary a => Serializer mtab a+fromDataBinary =+ Serializer+ { serializeGet = dataBinaryGet B.get+ , serializePut = dataBinaryPut . B.put+ }++dataBinaryPut :: B.PutM a -> GPutM mtab a+dataBinaryPut = PutM . lift++dataBinaryGet :: B.Get a -> GGet mtab a+dataBinaryGet = Get . lift . lift++lookAhead :: GGet mtab a -> GGet mtab a+lookAhead (Get fn) = S.get >>= Get . lift . lift . B.lookAhead . S.evalStateT (runPredicateT fn) >>= Get . predicate++bytesRead :: GGet mtab ByteOffset+bytesRead = Get $ lift $ lift B.bytesRead++isEmpty :: GGet mtab Bool+isEmpty = dataBinaryGet B.isEmpty++putWord8 :: Word8 -> GPut mtab+putWord8 = PutM . lift . B.putWord8++putWord16be :: Word16 -> GPut mtab+putWord16be = PutM . lift . B.putWord16be++putWord16le :: Word16 -> GPut mtab+putWord16le = PutM . lift . B.putWord16le++putWord32be :: Word32 -> GPut mtab+putWord32be = PutM . lift . B.putWord32be++putWord32le :: Word32 -> GPut mtab+putWord32le = PutM . lift . B.putWord32le++putWord64be :: Word64 -> GPut mtab+putWord64be = PutM . lift . B.putWord64be++putWord64le :: Word64 -> GPut mtab+putWord64le = PutM . lift . B.putWord64le++getWord8 :: GGet mtab Word8+getWord8 = Get $ lift $ lift $ B.getWord8++getWord16be :: GGet mtab Word16+getWord16be = Get $ lift $ lift $ B.getWord16be++getWord16le :: GGet mtab Word16+getWord16le = Get $ lift $ lift $ B.getWord16le++getWord32be :: GGet mtab Word32+getWord32be = Get $ lift $ lift $ B.getWord32be++getWord32le :: GGet mtab Word32+getWord32le = Get $ lift $ lift $ B.getWord32le++getWord64be :: GGet mtab Word64+getWord64be = Get $ lift $ lift $ B.getWord64be++getWord64le :: GGet mtab Word64+getWord64le = Get $ lift $ lift $ B.getWord64le++putIntegral :: (Integral a, Bits a) => a -> GPut mtab+putIntegral = putInteger . fromIntegral++getIntegral :: (Integral a, Bits a) => GGet mtab a+getIntegral = fromIntegral <$> getInteger++putPosIntegral :: (Integral a, Bits a) => a -> GPut mtab+putPosIntegral = putPosInteger . fromIntegral++getPosIntegral :: (Integral a, Bits a) => GGet mtab a+getPosIntegral = fromIntegral <$> getPosInteger++putInteger :: Integer -> GPut mtab+putInteger = dataBinaryPut . vlPutInteger++getInteger :: GGet mtab Integer+getInteger = fmap fromIntegral $ dataBinaryGet vlGetInteger++putPosInteger :: Integer -> GPut mtab+putPosInteger = dataBinaryPut . vlPutPosInteger++getPosInteger :: GGet mtab Integer+getPosInteger = dataBinaryGet vlGetPosInteger++putByteString :: B.ByteString -> GPut mtab+putByteString = dataBinaryPut . B.putByteString++getByteString :: Int -> GGet mtab B.ByteString+getByteString = dataBinaryGet . B.getByteString++putLazyByteString :: Z.ByteString -> GPut mtab+putLazyByteString = dataBinaryPut . B.putLazyByteString++getLazyByteString :: Int64 -> GGet mtab Z.ByteString+getLazyByteString = dataBinaryGet . B.getLazyByteString++-- | Look ahead one byte, if the byte is the number you are expecting drop the byte and evaluate the+-- given 'GGet' function, otherwise backtrack.+tryWord8 :: Word8 -> GGet mtab a -> GGet mtab a+tryWord8 w fn = lookAhead getWord8 >>= guard . (w==) >> getWord8 >> fn++instance Binary Int8 mtab where+ put = putWord8 . fromIntegral+ get = fmap fromIntegral getWord8+instance Binary Int16 mtab where { put = putIntegral; get = Dao.Binary.getIntegral }+instance Binary Int32 mtab where { put = putIntegral; get = Dao.Binary.getIntegral }+instance Binary Int64 mtab where { put = putIntegral; get = Dao.Binary.getIntegral }+instance Binary Int mtab where { put = putIntegral; get = Dao.Binary.getIntegral }+instance Binary Word8 mtab where { put = putPosIntegral; get = Dao.Binary.getPosIntegral }+instance Binary Word16 mtab where { put = putPosIntegral; get = Dao.Binary.getPosIntegral }+instance Binary Word32 mtab where { put = putPosIntegral; get = Dao.Binary.getPosIntegral }+instance Binary Word64 mtab where { put = putPosIntegral; get = Dao.Binary.getPosIntegral }+instance Binary Word mtab where { put = putPosIntegral; get = Dao.Binary.getPosIntegral }+instance Binary Float mtab where+ put = dataBinaryPut . B.putFloat32be+ get = dataBinaryGet B.getFloat32be+instance Binary Double mtab where+ put = dataBinaryPut . B.putFloat64be+ get = dataBinaryGet B.getFloat64be+instance (RealFloat a, Binary a mtab) => Binary (Complex a) mtab where+ put (a :+ b) = put a >> put b+ get = pure (:+) <*> get <*> get++instance (Num a, Bits a, Integral a, Binary a mtab) => Binary (Ratio a) mtab where+ put o = putIntegral (numerator o) >> putPosIntegral (denominator o)+ get = pure (%) <*> getIntegral <*> getPosIntegral++instance Binary Integer mtab where { put = putInteger; get = getInteger; }+instance Binary Char mtab where { put = putPosIntegral . ord; get = chr <$> getPosIntegral; }++instance Binary UTCTime mtab where+ put t = do+ put (toModifiedJulianDay (utctDay t))+ put (toRational (utctDayTime t))+ get = do+ d <- fmap ModifiedJulianDay get+ t <- fmap fromRational get+ return (UTCTime{ utctDay=d, utctDayTime=t })++instance Binary NominalDiffTime mtab where+ put t = put (toRational t)+ get = fmap fromRational get++instance Binary B.ByteString mtab where+ put o = putPosIntegral (B.length o) >> Dao.Binary.putByteString o+ get = getPosIntegral >>= Dao.Binary.getByteString++instance Binary Z.ByteString mtab where+ put o = putPosIntegral (Z.length o) >> Dao.Binary.putLazyByteString o+ get = getPosIntegral >>= Dao.Binary.getLazyByteString++instance (Binary a t, Binary b t) => Binary (a, b) t where+ put (a, b) = put a >> put b+ get = pure (,) <*> get <*> get+instance (Binary a t, Binary b t, Binary c t) => Binary (a, b, c) t where+ put (a, b, c) = put a >> put b >> put c+ get = pure (,,) <*> get <*> get <*> get+instance (Binary a t, Binary b t, Binary c t, Binary d t) => Binary (a, b, c, d) t where+ put (a, b, c, d) = put a >> put b >> put c >> put d+ get = pure (,,,) <*> get <*> get <*> get <*> get+instance (Binary a t, Binary b t, Binary c t, Binary d t, Binary e t) => Binary (a, b, c, d, e) t where+ put (a, b, c, d, e) = put a >> put b >> put c >> put d >> put e+ get = pure (,,,,) <*> get <*> get <*> get <*> get <*> get+instance (Binary a t, Binary b t, Binary c t, Binary d t, Binary e t, Binary f t) => Binary (a, b, c, d, e, f) t where+ put (a, b, c, d, e, f) = put a >> put b >> put c >> put d >> put e >> put f+ get = pure (,,,,,) <*> get <*> get <*> get <*> get <*> get <*> get+instance (Binary a t, Binary b t, Binary c t, Binary d t, Binary e t, Binary f t, Binary g t) => Binary (a, b, c, d, e, f, g) t where+ put (a, b, c, d, e, f, g) = put a >> put b >> put c >> put d >> put e >> put f >> put g+ get = pure (,,,,,,) <*> get <*> get <*> get <*> get <*> get <*> get <*> get+instance (Binary a t, Binary b t, Binary c t, Binary d t, Binary e t, Binary f t, Binary g t, Binary h t) => Binary (a, b, c, d, e, f, g, h) t where+ put (a, b, c, d, e, f, g, h) = put a >> put b >> put c >> put d >> put e >> put f >> put g >> put h+ get = pure (,,,,,,,) <*> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get++instance (Ord i, Binary i mtab, Binary a mtab) => Binary (M.Map i a) mtab where { put = put . M.assocs ; get = M.fromList <$> get; }+instance ( Binary a mtab) => Binary (Im.IntMap a) mtab where { put = put . Im.assocs; get = Im.fromList <$> get; }+instance (Ord a, Binary a mtab) => Binary (S.Set a) mtab where { put = put . S.elems ; get = S.fromList <$> get; }+instance Binary (Is.IntSet ) mtab where { put = put . Is.elems ; get = Is.fromList <$> get; }++instance (Eq p, Ord p, Binary p mtab, Binary a mtab) => Binary (T.Tree p a) mtab where+ put t = case t of+ T.Void -> prefixByte 0x00 $ return ()+ T.Leaf a -> prefixByte 0x01 $ put a+ T.Branch t -> prefixByte 0x02 $ put t+ T.LeafBranch a t -> prefixByte 0x03 $ put a >> put t where+ get = word8PrefixTable++instance (Eq p, Ord p, Binary p mtab, Binary a mtab) => HasPrefixTable (T.Tree p a) Byte mtab where+ prefixTable = mkPrefixTableWord8 "Tree" 0x00 0x03 $+ [ return T.Void+ , T.Leaf <$> get+ , T.Branch <$> get+ , pure T.LeafBranch <*> get <*> get+ ]++instance Binary () mtab where { get = return (); put () = return (); }++putNullTerm :: GPut mtab+putNullTerm = putWord8 0x00++getNullTerm :: GGet mtab ()+getNullTerm = tryWord8 0x00 $ return ()++instance Binary Bool mtab where+ put o = putWord8 (if o then 0x04 else 0x05)+ get = word8PrefixTable+instance HasPrefixTable Bool Byte mtab where+ prefixTable = mkPrefixTableWord8 "Bool" 0x04 0x05 [return False, return True]++instance Binary a mtab => Binary (Maybe a) mtab where+ put = maybe (putWord8 0x00) (\o -> putWord8 0x01 >> put o) + get = word8PrefixTable <|> fail "expecting Data.Maybe.Maybe"+instance Binary a mtab => HasPrefixTable (Maybe a) Byte mtab where+ prefixTable = mkPrefixTableWord8 "Maybe" 0x00 0x01 [return Nothing, Just <$> get]++instance Binary a mtab => Binary [a] mtab where+ put o = mapM_ (put . Just) o >> putNullTerm+ get = concatMap (maybe [] return) <$> loop [] where+ loop ox = msum $+ [ getNullTerm >> return ox+ -- It is important to check for the null terminator first, then try to parse, that way the+ -- parser dose not backtrack (which may cause it to fail) if we are at a null terminator.+ , optional get >>= maybe (fail "expecting list element") (loop . (ox++) . (:[]))+ ]++-- | Like 'putUnwrapped' but takes an arbitrary binary encoder for encoding individual list+-- parameters.+putUnwrappedWith :: (a -> GPut mtab) -> [a] -> GPut mtab+putUnwrappedWith put list = mapM_ put list >> putNullTerm++-- | The inverse of 'getUnwrapped', this function is simply defined as:+-- > \list -> 'Control.Monad.mapM_' 'put' list >> 'putNullterm'+-- This is useful when you want to place a list of items, but you dont want to waste space on a+-- prefix byte for each list element. In lists of millions of elements, this can save you megabytes+-- of space, but placing any elements which are prefixed with a null byte will result in undefined+-- behavior when decoding.+putUnwrapped :: Binary a mtab => [a] -> GPut mtab+putUnwrapped = putUnwrappedWith put++-- | Like 'getUnwrapped' but takes an arbitrary binary decoder for encoding individual list+-- parameters.+getUnwrappedWith :: GGet mtab a -> GGet mtab [a]+getUnwrappedWith get = fix (\loop ox -> (getNullTerm >> return ox) <|> (get >>= \o -> loop (ox++[o]))) []++-- | The inverse of 'putUnwrapped', this function is simply defined as: 'Control.Applicative.many'+-- 'get' >>= \list -> 'getNullTerm' >> 'Control.Monad.return' list This assumes that+-- every element placed by 'get' has a non-null prefixed encoding. If any elements in the list might+-- be encoded such that they start with a 0x00 byte, the @'Control.Applicative.many'@ 'get'+-- expression will parse the null terminator of the list as though it were an element and continue+-- looping which results in undefined behavior. Examples of elements that may start with null @0x00@+-- bytes are 'Dao.String.UStr', 'Dao.String.Name', 'Prelude.Integer', or any 'Prelude.Integral' type.+getUnwrapped :: Binary a mtab => GGet mtab [a]+getUnwrapped = getUnwrappedWith get++instance Binary UStr mtab where+ put o = dataBinaryPut (B.put o)+ get = dataBinaryGet B.get++instance Binary Name mtab where+ put o = dataBinaryPut (B.put o)+ get = dataBinaryGet B.get++instance Binary Location mtab where+ put o = case o of+ LocationUnknown -> return ()+ Location a b c d -> prefixByte 0x7F $ put a >> put b >> put c >> put d+ get = msum $+ [ isEmpty >>= guard >> return LocationUnknown+ , tryWord8 0x7F $ pure Location <*> get <*> get <*> get <*> get+ , return LocationUnknown+ ]+
+ src/Dao/Glob.hs view
@@ -0,0 +1,439 @@+-- "src/Dao/Glob.hs" functions and data types related to the Glob+-- data type, for matching unix-like glob patterns to strings.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.+++{-# LANGUAGE DeriveDataTypeable #-}++-- | The 'Glob' expression data type is constructed by parsing a string containing a 'Glob'+-- expression. Reminscent of old-fashioned POSIX glob expressions that you would use in UNIX or+-- Linux systems on the command line (@ls *.hs@).+-- +-- Also of use is the 'PatternTree' type. This 'Dao.Tree.Tree' data type allows you to associate+-- arbitrary object values with 'Glob' expressions. You can insert 'Glob' expressions into a+-- 'PatternTree' with 'insertMultiPattern' and then use 'matchTree' to match a string expression.+-- Every pattern that matches will return the object value associated with it along with a+-- @'Dao.Tree.Tree' 'Dao.String.Name'@ mapping which substrings matched which wildcards.+-- +-- The syntax for a glob expression is just an arbitrary string with @'$'@ characters indicating+-- variables. A @'$'@ must be followed by at least one alphabetic or underscore character, and then+-- zero or more alphanumeric characters or underschore characters. These characters may then be+-- followed by a @'?'@. For example:+-- > "some text $wildcard more text"+-- > "some text $wildcard* more text"+-- > "some text $anyone? more text"+-- The first and second forms are identical, you may choose to follow a wildcard with a @'*'@ if you+-- want the 'Wildcard' variable to be followed by text with no space or punctuation in between.+-- 'Wildcard's match arbitrary-length sequences of string constants. For example, the above 'Glob'+-- containing the variable called @wildcard@ will match the following strings:+-- > "some text more text" -> a variable called "wildcard" is assigned an empty list+-- > "some text a more text" -> a variable called "wildcard" is assigned the list [a]+-- > "some text a b more text" -> a variable called "wildcard" is assigned the list [a b]+-- > "some text a b c more text" -> a variable called "wildcard" is assigned the list [a b c]+-- An 'AnyOne' variable matches any string constant, but one and only one. The following strings+-- will match the above example:+-- > "some text then more text" -> a variable called "anyone" is assigned the list [then]+-- > "some text with more text" -> a variable called "anyone" is assigned the list [with]+-- But the above exaple will not match:+-- > "some text more text"+-- > "some text then with more text"+-- Variable matched are stored in @('Dao.Tree.Tree' 'Dao.String.Name')@ structures.+-- +-- Glob expressions are wrappers around lists of 'GlobUnit's. Each 'GlobUnit' is a 'Wildcard',+-- 'AnyOne' variable, or a string constant called a 'Single'. It is called 'Single' rather than+-- 'Control.Applicative.Const' to avoid conflicting with the data type defined in the+-- "Control.Applicative" module.+-- +-- The data type used to store 'Single' string constants is polymorphic. So you can construct a+-- 'Glob' containing 'Prelude.String's, 'Dao.String.UStr's, or anything that can be constructed from+-- a 'Prelude.String'.+-- +-- /NOTE:/ that when a 'Glob' is parsed using 'Prelude.read', the string constant is the substring+-- of all characters between the variables. If there are no variables, the whole string will be+-- stored into a list of just one 'Single' string constant. However this behavior may not be useful.+-- It may be useful to break down string constants into smaller 'Single' string constants. To do+-- this, use the 'parseOverSingles' function.+-- +-- The following is a simple program you can use from the command line in GHCi to observe how to+-- construct 'Glob' expressions and try matching strings to these 'Glob's to see the result.+-- > import System.IO.Unsafe+-- > import Data.IORef+-- > +-- > -- Establish a global variable for GHCi.+-- > testref :: IORef (PatternTree String String)+-- > testref = unsafePerformIO (newIORef T.Void)+-- > +-- > -- A function to break-up a string into clusters of spaces, numbers, or letters.+-- > breakstr :: String -> [String]+-- > breakstr cx = loop cx where+-- > check cx func = case cx of+-- > c:cx | func c -> Just $ span func (c:cx)+-- > _ -> Nothing+-- > loop cx =+-- > if null cx+-- > then []+-- > else maybe ([head cx] : loop (tail cx)) (\ (cx, rem) -> cx : loop rem) $+-- > foldl (\a -> mplus a . check cx) Nothing [isSpace, isAlpha, isDigit]+-- > +-- > -- Use 'Prelude.read' to parse a 'Glob' expression with 'Prelude.String's as the constant+-- values. Also, use 'parseOverSignles' to break-down the string constants using breakstr above.+-- > parsepat :: String -> Glob String+-- > parsepat = flip parseOverSingles breakstr . read+-- > +-- > newpat :: String -> String -> IO ()+-- > newpat pat act = do+-- > let glob = parsepat pat+-- > modifyIORef testref (insertMultiPattern (flip const) [glob] act)+-- > putStrLn $ "added pattern: "++show glob+-- > +-- > delpat :: String -> IO ()+-- > delpat str = modifyIORef testref (T.delete (getPatUnits $ parsepat str))+-- > +-- > ls :: IO ()+-- > ls = readIORef testref >>= putStrLn . disp "" where+-- > disp ind t = case t of+-- > T.Void -> "()"+-- > T.Leaf o -> show o+-- > T.Branch m -> dispMap ind m+-- > T.LeafBranch o m -> " = " ++ show o ++ " ..." ++ dispMap ind m+-- > dispMap ind m = (++(ind++"}")) $ ("{\n"++) $+-- > if M.null m+-- > then "(empty map)"+-- > else unlines $ do+-- > (g, tree) <- M.assocs m+-- > ['\t':ind ++ unwords ['"':show g++"\"", "=", disp ('\t':ind) tree]]+-- > +-- > trypat :: String -> IO ()+-- > trypat instr = do+-- > tree <- readIORef testref+-- > forM_ (matchTree True tree (breakstr instr)) $ \ (glob, vars, o) -> do+-- > putStrLn $ "pattern: "++show glob+-- > putStrLn $ "action: "++show o+-- > putStrLn $ ("vars assigned:\n"++) $ unlines $ flip map (T.assocs vars) $ \ (nm, o) -> unwords $+-- > ['\t':show nm, "=", show (unwords o)]+module Dao.Glob where++import Dao.String+import qualified Dao.Tree as T+import Dao.PPrint+import Dao.Random++import Control.Applicative+import Control.Monad.Identity+import Control.DeepSeq++import Data.Typeable+import Data.Monoid+import Data.List+import Data.Char+import qualified Data.Map as M++----------------------------------------------------------------------------------------------------++-- | Tokenize a 'Prelude.String' grouping together whitespace, numbers, letters, and punctuation+-- makrs, except for brackets and quote markers which will all be tokenized as single character+-- strings.+simpleTokenize :: String -> [UStr]+simpleTokenize ax = map ustr (loop ax) where+ loop ax = case ax of+ [] -> []+ a:ax | elem a "([{}])\"'`" -> [a] : loop ax+ a:ax -> case msum (map (check a ax) kinds) of+ Nothing -> [a] : loop ax+ Just (got, ax) -> got : loop ax+ check a ax fn = if fn a then let (got, ax') = span fn ax in Just (a:got, ax') else Nothing+ kinds = [isSpace, isAlpha, isNumber, isPunctuation, isAscii, not . isAscii]++----------------------------------------------------------------------------------------------------++-- | A 'GlobUnit' is a single unit of a 'Glob' pattern, which is either a constant token value (a+-- 'Single'), a wildcard matching a single token (an 'AnyOne') or a 'Wildcard' matching zero or more+-- tokens. This is a very glob data type, remeniscent of the good old-fashioned Unix glob expression+-- but not restricted to single-character tokens. The unit token type need not be a string, but most the+-- instances of 'GlobUnit' into 'Prelude.Show' and 'Prelude.Read' are only defined for 'GlobUnit's+-- of 'Dao.String.UStr's.+data GlobUnit tok+ = Wildcard Name (Maybe Name)+ | AnyOne Name (Maybe Name)+ | Single tok+ deriving (Eq, Typeable)++-- Order such that sorting will group 'Wildcards' first, 'AnyOne's second, and 'Single's third.+instance Ord tok => Ord (GlobUnit tok) where+ compare a b = case a of+ Wildcard a a1 -> case b of+ Wildcard b b1 -> compare a b <> compare a1 b1+ _ -> LT+ AnyOne a a1 -> case b of+ Wildcard{} -> GT+ AnyOne b b1 -> compare a b <> compare a1 b1+ Single{} -> LT+ Single a -> case b of+ Single b -> compare a b+ _ -> GT++instance Functor GlobUnit where+ fmap f o = case o of+ Single o -> Single (f o)+ Wildcard n t -> Wildcard n t+ AnyOne n t -> AnyOne n t++isSingle :: GlobUnit o -> Bool+isSingle o = case o of { Single _ -> True; _ -> False }++isVariable :: GlobUnit o -> Bool+isVariable = not . isSingle++-- not for export -- strips the leadnig and trailing quote @'"'@ characters.+toStringWithoutQuotes :: String -> String+toStringWithoutQuotes cx = loop $ case cx of { '"':cx -> cx ; cx -> cx ; } where+ loop cx = case cx of { '"':"" -> ""; "" -> ""; c:cx -> c : loop cx; }++-- | Use this function to instantiate your version of 'GlobUnit' into the 'Prelude.Show' class. This+-- function assumes your data type is a string-like type where evaluating 'Prelude.show' on your+-- type produces a string of characters with a leading and trailing quote @'"'@ character.+showGlobUnitOfStrings :: (tok -> String) -> GlobUnit tok -> String+showGlobUnitOfStrings gshow tok = let printyp = maybe "" (\n -> "::"++uchars n) in case tok of+ Wildcard nm t -> '$':uchars (toUStr nm)++printyp t+ AnyOne nm t -> '$':uchars (toUStr nm)++printyp t++"?"+ Single tok -> toStringWithoutQuotes (gshow tok)++instance Show (GlobUnit UStr) where { show = showGlobUnitOfStrings uchars }+instance Show (GlobUnit String) where { show = showGlobUnitOfStrings id }++-- | Use this function to instantiate your version of 'Glob' into the 'Prelude.Show' class. The+-- function you pass to convert the 'Single' type to a string is passed to 'showGlobUnitOfStrings'.+showGlobUnitList :: (tok -> String) -> [GlobUnit tok] -> String+showGlobUnitList gshow gx = show $ concatMap (showGlobUnitOfStrings gshow) gx++instance Read (GlobUnit String) where+ readsPrec _prec str = let init c = c=='_' || isAlpha c in case str of+ '$':c:str | init c -> do+ (cx, str) <- [span isAlphaNum str]+ (typfunc, str) <- case str of+ ':':':':str -> return $ head $ concat $+ [ case str of+ c:str | init c -> do+ (cx, str) <- [span isAlphaNum str]+ [(Just $ ustr $ c:cx, str)]+ _ -> []+ , [(Nothing, str)]+ ]+ str -> [(Nothing, str)]+ case str of+ '?':str -> [(AnyOne (ustr $ c:cx) typfunc, str)]+ _ -> [(Wildcard (ustr $ c:cx) typfunc, str)]+ '$':str -> [span (/='$') str] >>= \ (cx, str) -> [(Single ('$':cx), str)]+ _ -> []++instance Read (GlobUnit UStr) where+ readsPrec prec str = readsPrec prec str >>= \ (tok, str) -> return (fmap ustr tok, str)++instance UStrType (GlobUnit UStr) where+ maybeFromUStr str = case readsPrec 0 (uchars str) of { [(o, "")] -> Just o; _ -> Nothing; }+ toUStr = ustr . show++instance NFData o => NFData (GlobUnit o) where+ rnf (Wildcard a b) = deepseq a $! deepseq b ()+ rnf (AnyOne a b) = deepseq a $! deepseq b ()+ rnf (Single a ) = deepseq a ()++instance HasRandGen o => HasRandGen (GlobUnit o) where+ randO = countNode $ runRandChoice+ randChoice = randChoiceList $+ [ Single <$> randO+ , return Wildcard <*> randO <*> randO+ , return AnyOne <*> randO <*> randO+ ]++----------------------------------------------------------------------------------------------------++-- | A 'Glob' is a kind of pattern that can be matched against tokens. A 'Glob' pattern contains a+-- list of 'GlobUnit's, and a 'GlobUnit' is either a constant (a 'Single') token, or variable (a+-- 'Wildcard' or 'AnyOne') that can be matched against a list constant tokens using 'matchPattern'.+-- When you have a large number of 'Glob' patterns and you would like to match any of them to a list+-- of tokens, merge the 'Glob' patterns together into a 'PatternTree' using the 'globTree' function,+-- and match them all at once using the 'matchTree' function.+data Glob tok = Glob { getPatUnits :: [GlobUnit tok], getGlobLength :: Int }+ deriving (Eq, Ord, Typeable)++makeGlob :: [GlobUnit tok] -> Glob tok+makeGlob ox = Glob{ getPatUnits=ox, getGlobLength=length ox }++instance Functor Glob where+ fmap f g = g{ getPatUnits = fmap (fmap f) (getPatUnits g) }++instance Show (Glob UStr) where { show = showGlobUnitList uchars . getPatUnits }+instance Show (Glob String) where { show = showGlobUnitList id . getPatUnits }++instance Read (Glob String) where+ readsPrec prec str = if null str then return mempty else do+ (units, str) <- loop [] str+ return (Glob{ getPatUnits=units, getGlobLength=length units }, str)+ where+ loop units str = case break (=='$') str of+ ("", "" ) -> return (units, "")+ ("", str) -> readsPrec prec str >>= \ (unit, str) -> loop (units++[unit]) str+ (cx, str) -> loop (units++[Single cx]) str++instance Read (Glob UStr) where+ readsPrec prec str = readsPrec prec str >>= \ (g, str) -> return (fmap ustr g, str)++instance Monoid (Glob o) where+ mempty = nullValue+ mappend (Glob{ getPatUnits=a, getGlobLength=lenA }) (Glob{ getPatUnits=b, getGlobLength=lenB }) =+ Glob{ getPatUnits=a++b, getGlobLength=lenA+lenB }++instance NFData o => NFData (Glob o) where { rnf (Glob a b) = deepseq a $! deepseq b () }++instance HasNullValue (Glob o) where+ nullValue = Glob{ getPatUnits=[], getGlobLength=0 }+ testNull (Glob{ getPatUnits=ax }) = null ax++instance UStrType (Glob UStr) where+ maybeFromUStr str = case readsPrec 0 (uchars str) of { [(o, "")] -> Just o; _ -> Nothing; }+ toUStr = ustr . show++instance PPrintable (Glob UStr) where { pPrint = pShow }++instance HasRandGen o => HasRandGen (Glob o) where+ randO = randList 1 6 >>= \o -> return $ Glob{ getPatUnits=o, getGlobLength=length o }++----------------------------------------------------------------------------------------------------++-- | A pattern is a list of tokens/variables that can be compared to a token list using+-- 'matchPattern' or 'matchTree'. A 'PatternTree' contains many patterns which have been merged into+-- a tree structure, which can match N patterns of maximum length M to a token list of L tokens in+-- O(L*log(M*N)) time, making it a much more efficient data structure for matching against a large+-- database of patterns. Every 'Glob' pattern in the tree is mapped to result value called an+-- "action", which is the polymorphic type @act@. Every pattern in the tree that matches a list of+-- tokens produces an "action" and also contains a list of associations of which labeled wildcards+-- matched which substring of tokens.+type PatternTree tok act = T.Tree (GlobUnit tok) act++-- | When a 'Glob' is constructed with a function of the 'Prelude.Read' class, the 'Single' items+-- produced are all contiguous characters in between 'Wildcard' and 'AnyOne' markers. For example+-- the string:+-- > read "$X will do $Y? too" :: 'Glob' 'Prelude.String'+-- will parse to a 'Glob' where the 'getPatUnits' is the following list of items:+-- > ['Wildcard' "X", 'Single' " will do ", 'AnyOne' "Y", 'Single' " too"]+-- Notice how the 'Single' items contain spaces. This may or may not be desirable.+--+-- In the case that you would like to further parse the 'Single' strings, you can use the+-- 'parseOverSingles' function, breaking a 'Single' down into a list of 'Single's.+parseOverSinglesM :: Monad m => Glob tokA -> (tokA -> m [tokB]) -> m (Glob tokB)+parseOverSinglesM g convert =+ forM (getPatUnits g)+ (\u -> case u of+ Single u -> convert u >>= mapM (return . Single)+ AnyOne nm t -> return [AnyOne nm t]+ Wildcard nm t -> return [Wildcard nm t]+ ) >>= return . makeGlob . concat++-- | Like 'parseOverSinglesM' but is a pure function.+parseOverSingles :: Glob tokA -> (tokA -> [tokB]) -> Glob tokB+parseOverSingles g = runIdentity . parseOverSinglesM g . (return.)++-- | Insert an item at multiple points in the 'PatternTree'+insertMultiPattern :: (Eq tok, Ord tok) => (act -> act -> act) -> [Glob tok] -> act -> PatternTree tok act -> PatternTree tok act+insertMultiPattern plus pats act tree =+ foldl (\tree pat -> T.update (getPatUnits pat) (maybe (Just act) (Just . flip plus act)) tree) tree pats++-- | By converting an ordinary 'Glob' to a pattern tree, you are able to use all of the methods+-- in the "Dao.Tree" module to modify the patterns in it.+globTree :: (Eq tok, Ord tok) => Glob tok -> act -> PatternTree tok act+globTree pat a = T.insert (getPatUnits pat) a T.Void++-- | Calls 'matchTree' with the 'PatternTree' stored within the given 'Glob' object, and returns+-- only the matching results.+matchPattern :: (Eq tok, Ord tok) => Bool -> Glob tok -> [tok] -> [M.Map Name (Maybe Name, [tok])]+matchPattern greedy pat tokx = matchTree greedy (globTree pat ()) tokx >>= \ (_, m, ()) -> [m]++-- | Match a list of token items to a set of 'Glob' expressions that have been combined into a+-- single 'PatternTree', matching every possible pattern in the 'PatternTree' to the list of token+-- items in depth-first order. The first boolean parameter indicates whether 'Wildcard's should be+-- matched greedily (pass 'Prelude.False' for non-greedy matching). Be aware that greedy matching is+-- /not lazy/ which could cause freezes if you are working with infinitely recursive data types.+-- Non-greedy matching is lazy and works fine with everything.+--+-- Each match is returned as a triple indicating 1. the 'Glob' that matched the token list, 2. the+-- token list items that were bound to the 'Dao.String.Name's in the 'Wildcard' and 'AnyOne'+-- 'GlobUnit's, and 3. the item associated with the 'Glob' expression that matched.+--+-- The 'Data.Map.Map' objects returned map which variable names matched to pairs containing in the+-- 'Prelude.fst' slot the type of the token that the variable expects (the type is the part of the+-- pattern variable after the "::" symbol), and in the 'Prelude.snd' slot contains the tokens that+-- matched in that variable position.+matchTree+ :: (Eq tok, Ord tok)+ => Bool -> PatternTree tok act -> [tok] -> [(Glob tok, M.Map Name (Maybe Name, [tok]), act)]+matchTree greedy tree tokx = loop M.empty 0 [] tree tokx where+ loop vars p path tree tokx = case tree of+ T.Void -> []+ T.Leaf a -> guard (null tokx) >> done vars p path a+ T.Branch b -> branch vars p path [] b tokx+ T.LeafBranch a b -> branch vars p path [a] b tokx+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ partStep bind tokx = (if greedy then reverse else id) $ (bind, tokx) :+ fix (\loop bind tokx -> if null tokx then [] else do+ bind <- [bind++[head tokx]]+ tokx <- [tail tokx]+ ((bind, tokx) : loop bind tokx)+ ) bind tokx+ -- partStep takes a list of tokens, like [a,b,c] and returns a list for every possible+ -- 2-way partition: [([],[a,b,c]), ([a],[b,c]), ([a,b],[c]), ([a,b,c],[])]+ -- This forms a list of (bind, tokx) pairs where 'bind' will be assigned to a variable and 'tokx'+ -- is the remaining tokens to be matched. So when a 'Wildcard' variable is matched, it tries every+ -- possible ('bind', 'tokx') pair, binding the 'bind' to a variable and looping on 'tokx'.+ done vars p path a = [(Glob{ getPatUnits=path, getGlobLength=p }, vars, a)]+ branch vars p path a b tokx = case tokx of+ [] -> msum $+ [a >>= \a -> done vars p path a+ ,do (pat, tree) <- M.assocs b+ a <- case tree of+ T.Void -> []+ T.Branch _ -> []+ T.Leaf a -> [a]+ T.LeafBranch a _ -> [a]+ case pat of+ Wildcard nm t -> case M.lookup nm vars of+ Nothing -> done (M.insert nm (t, []) vars) p path a+ Just (_, pfx) -> guard (null pfx) >> done vars p path a+ AnyOne{} -> []+ Single{} -> []+ ]+ tok:tokx -> let next pat vars tree = loop vars (p+1) (path++[pat]) tree in msum $+ [do tree <- maybe [] (:[]) $ M.lookup (Single tok) b+ next (Single tok) vars tree tokx+ ,do -- Next we use 'takeWhile' because of how the 'Ord' instance of 'GlobUnit' is defined,+ -- 'Wildcard's and 'AnyOne's are always first in the list of 'assocs'.+ (pat, tree) <- takeWhile (isVariable . fst) (M.assocs b)+ let defVar nm t mkAssoc = case M.lookup nm vars of+ Just (_, pfx) -> maybe [] (:[]) (stripPrefix pfx (tok:tokx)) >>= next pat vars tree+ Nothing -> do+ (bind, tokx) <- mkAssoc+ next pat (M.insert nm (t, bind) vars) tree tokx+ case pat of+ Wildcard nm t -> defVar nm t (partStep [] (tok:tokx))+ AnyOne nm t -> defVar nm t [([tok], tokx)]+ Single{} -> error "undefined behavior in Dao.Glob.matchTree:branch: case Single"+ -- 'Single' cases must not occur, they should have been filtered out by the code:+ -- > takeWhile (isVariable . fst)+ ]+
+ src/Dao/HashMap.hs view
@@ -0,0 +1,256 @@+-- "src/Dao/HashMap.hs" a simple hash map using Data.IntMap+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++module Dao.HashMap+ ( Hash128, Int128Hashable(hash128),+ hash128_md5, deriveHash128_Show, deriveHash128_UStr,+ deriveHash128_DataBinary, deriveHash128_DaoBinary,+ Index, indexToPair, hashNewIndex, newIndex, indexHash, indexKey,+ HashMap, hashLookup, hashAlter, hashInsert, hashDelete, hashAdjust, hashModify,+ Dao.HashMap.lookup, alter, Dao.HashMap.insert, Dao.HashMap.delete, adjust, modify,+ unionWith, unionsWith, Dao.HashMap.union, unions, differenceWith, difference, intersectionWith, intersection,+ assocs, elems, keys, fromListWith, fromList, size, empty+ ) where++import Dao.String+import Dao.PPrint+import Dao.Binary++import Control.Applicative hiding (empty)+import Control.Monad++import Data.Typeable+import Data.Monoid+import Data.List+import Data.Char+import Data.Word+import qualified Data.Binary as B+import qualified Data.Binary.Get as B+import qualified Data.Binary.Put as B+import qualified Data.ByteString.Lazy as B+import qualified Data.Map as M+import qualified Data.IntMap as I+import qualified Data.Digest.MD5 as MD5++----------------------------------------------------------------------------------------------------++type Hash128 = (Word64, Word64)+type Int128Map a = I.IntMap (I.IntMap a)++_lookup :: Hash128 -> Int128Map a -> Maybe a+_lookup (h1, h2) = I.lookup (fromIntegral h1) >=> I.lookup (fromIntegral h2)++_alter :: (Maybe a -> Maybe a) -> Hash128 -> Int128Map a -> Int128Map a+_alter alt (h1, h2) =+ I.alter+ ((\m -> if I.null m then Nothing else Just m) . I.alter alt (fromIntegral h2) . maybe mempty id)+ (fromIntegral h1)++_unionWith :: (a -> a -> a) -> Int128Map a -> Int128Map a -> Int128Map a+_unionWith f = I.unionWith (I.unionWith f)++_differenceWith :: (a -> b -> Maybe a) -> Int128Map a -> Int128Map b -> Int128Map a+_differenceWith f = I.differenceWith (\a b -> let m = I.differenceWith f a b in guard (not $ I.null m) >> return m)++_intersectionWith :: (a -> b -> c) -> Int128Map a -> Int128Map b -> Int128Map c+_intersectionWith f = I.intersectionWith (I.intersectionWith f)++_assocs :: Int128Map a -> [(Hash128, a)]+_assocs m = I.assocs m >>= \ (h1, m) -> I.assocs m >>= \ (h2, a) -> [((fromIntegral h1, fromIntegral h2), a)]++_fromListWith :: (a -> a -> a) -> [(Hash128, a)] -> Int128Map a+_fromListWith f = fmap (I.fromListWith f) . I.fromListWith (++) . fmap (\ ((h1, h2), a) -> (fromIntegral h1, [(fromIntegral h2, a)]))++----------------------------------------------------------------------------------------------------++class Ord key => Int128Hashable key where { hash128 :: key -> Hash128 }++hash128_md5 :: [Word8] -> Hash128+hash128_md5 = B.runGet (pure (,) <*> B.getWord64be <*> B.getWord64be) . B.pack . MD5.hash++deriveHash128_Show :: Show a => a -> Hash128+deriveHash128_Show = deriveHash128_UStr . show++deriveHash128_UStr :: UStrType a => a -> Hash128+deriveHash128_UStr = hash128_md5 . utf8bytes++deriveHash128_DataBinary :: B.Binary a => a -> Hash128+deriveHash128_DataBinary = hash128_md5 . B.unpack . B.encode++deriveHash128_DaoBinary :: (HasCoderTable mtab, Binary a mtab) => mtab -> a -> Hash128+deriveHash128_DaoBinary mtab = hash128_md5 . B.unpack . encode mtab++----------------------------------------------------------------------------------------------------++data Index key = Index { indexHash :: Hash128, indexKey :: key } deriving (Eq, Ord, Typeable)++indexToPair :: Index key -> (B.ByteString, key)+indexToPair (Index{ indexHash=(h1, h2), indexKey=key }) =+ (B.runPut (B.putWord64be h1 >> B.putWord64be h2), key)++instance Show key => Show (Index key) where+ show (Index{ indexKey=key }) = concat ["Index (", show key, ")"]++instance (Read key, Int128Hashable key) => Read (Index key) where+ readsPrec p s = do+ s <- maybe [] return $ stripPrefix "Index" $ dropWhile isSpace s+ (key, s) <- case dropWhile isSpace s of { '(':s -> readsPrec p s; _ -> [] }+ case dropWhile isSpace s of+ ')':s -> [(Index{ indexHash=hash128 key, indexKey=key }, s)]+ _ -> []++instance Binary key mtab => Binary (Index key) mtab where+ put (Index{ indexHash=(h1, h2), indexKey=key }) = putWord64be h1 >> putWord64be h2 >> put key+ get = do+ h1 <- getWord64be+ h2 <- getWord64be+ key <- get+ return $ Index{ indexHash=(h1, h2), indexKey=key }++instance PPrintable key => PPrintable (Index key) where { pPrint i = pPrint (indexKey i) }++hashNewIndex :: (key -> Hash128) -> key -> Index key+hashNewIndex hash key = Index{ indexHash = hash key, indexKey = key }++newIndex :: Int128Hashable key => key -> Index key+newIndex = hashNewIndex hash128++----------------------------------------------------------------------------------------------------++newtype HashMap key a = HashMap (Int128Map (M.Map key a)) deriving (Eq, Ord, Typeable)++instance (Show key, Show a) => Show (HashMap key a) where+ show = ("fromList "++) . show . assocs++instance (Read key, Int128Hashable key, Read a) => Read (HashMap key a) where+ readsPrec p s = maybe [] return (stripPrefix "fromList" $ dropWhile isSpace s) >>=+ readsPrec p . dropWhile isSpace >>= \ (hmap, s) -> return (fromList hmap, s)++instance Functor (HashMap key) where+ fmap f (HashMap m) = HashMap $ fmap (fmap (fmap f)) m++instance (Ord key, Monoid a) => Monoid (HashMap key a) where+ mempty = HashMap mempty+ mappend a b = unionWith mappend a b++instance HasNullValue (HashMap key a) where+ nullValue = HashMap mempty+ testNull = Dao.HashMap.null++instance (Ord key, Binary key mtab, Binary a mtab) => Binary (HashMap key a) mtab where+ put = put . assocs+ get = fromList <$> get++instance (PPrintable key, PPrintable a) => PPrintable (HashMap key a) where+ pPrint = pList (pString "HashMap") "{" ", " "}" .+ map (\ (a, b) -> pInline [pPrint a, pString " = ", pPrint b]) . assocs++null :: HashMap key a -> Bool+null (HashMap a) = I.null a++hashLookup :: Ord key => Index key -> HashMap key a -> Maybe a+hashLookup key (HashMap intmap) = _lookup (indexHash key) intmap >>= M.lookup (indexKey key)++lookup :: Int128Hashable key => key -> HashMap key a -> (Index key, Maybe a)+lookup key hmap = let i = Index{ indexHash=hash128 key, indexKey=key } in (i, hashLookup i hmap)++hashAlter :: Ord key => (Maybe a -> Maybe a) -> Index key -> HashMap key a -> HashMap key a+hashAlter alt key (HashMap intmap) = HashMap $+ _alter+ ((\m -> guard (not $ M.null m) >> Just m) . M.alter alt (indexKey key) . maybe mempty id)+ (indexHash key)+ intmap++alter :: Int128Hashable key => (Maybe a -> Maybe a) -> key -> HashMap key a -> HashMap key a+alter f key = hashAlter f (Index{ indexHash=hash128 key, indexKey=key })++hashInsert :: Ord key => Index key -> a -> HashMap key a -> HashMap key a+hashInsert key a = hashAlter (const $ Just a) key++insert :: Int128Hashable key => key -> a -> HashMap key a -> HashMap key a+insert key = hashInsert (Index{ indexHash=hash128 key, indexKey=key})++hashDelete :: Ord key => Index key -> HashMap key a -> HashMap key a+hashDelete = hashAlter (const Nothing)++delete :: Int128Hashable key => key -> HashMap key a -> HashMap key a+delete key = hashDelete (Index{ indexHash=hash128 key, indexKey=key })++hashAdjust :: Ord key => (a -> a) -> Index key -> HashMap key a -> HashMap key a+hashAdjust f = hashAlter (fmap f)++adjust :: Int128Hashable key => (a -> a) -> key -> HashMap key a -> HashMap key a+adjust f key = hashAdjust f (Index{ indexHash=hash128 key, indexKey=key })++hashModify :: Ord key => (a -> Maybe a) -> Index key -> HashMap key a -> HashMap key a+hashModify f = hashAlter (>>=f)++modify :: Int128Hashable key => (a -> Maybe a) -> key -> HashMap key a -> HashMap key a+modify f key = hashModify f (Index{ indexHash=hash128 key, indexKey=key })++unionWith :: Ord key => (a -> a -> a) -> HashMap key a -> HashMap key a -> HashMap key a+unionWith f (HashMap a) (HashMap b) = HashMap $ _unionWith (M.unionWith f) a b++union :: Ord key => HashMap key a -> HashMap key a -> HashMap key a+union = unionWith const++unionsWith :: Ord key => (a -> a -> a) -> [HashMap key a] -> HashMap key a+unionsWith f = foldl (unionWith f) empty++unions :: Ord key => [HashMap key a] -> HashMap key a+unions = unionsWith const++differenceWith :: Ord key => (a -> b -> Maybe a) -> HashMap key a -> HashMap key b -> HashMap key a+differenceWith f (HashMap a) (HashMap b) = HashMap $+ _differenceWith (\a b -> let m = M.differenceWith f a b in guard (not $ M.null m) >> return m) a b++difference :: Ord key => HashMap key a -> HashMap key b -> HashMap key a+difference = differenceWith (\ _ _ -> Nothing)++intersectionWith :: Ord key => (a -> b -> c) -> HashMap key a -> HashMap key b -> HashMap key c+intersectionWith f (HashMap a) (HashMap b) = HashMap $ _intersectionWith (M.intersectionWith f) a b++intersection :: Ord key => HashMap key a -> HashMap key a -> HashMap key a+intersection = intersectionWith const++assocs :: HashMap key a -> [(Index key, a)]+assocs (HashMap m) = do+ (hash, m) <- _assocs m+ (key , a) <- M.assocs m+ [(Index{ indexHash=hash, indexKey=key }, a)]++elems :: HashMap key a -> [a]+elems = fmap snd . assocs++keys :: HashMap key a -> [Index key]+keys = fmap fst . assocs++fromListWith :: Ord key => (a -> a -> a) -> [(Index key, a)] -> HashMap key a+fromListWith f = HashMap . fmap (fmap $ M.fromListWith f) . _fromListWith (++) .+ fmap (\ (Index{ indexHash=hash, indexKey=key }, a) -> (hash, [(key, a)]))++fromList :: Ord key => [(Index key, a)] -> HashMap key a+fromList = fromListWith (flip const)++size :: HashMap key a -> Integer+size (HashMap m) = I.foldl (I.foldl (\i -> (i+) . toInteger . M.size)) 0 m++empty :: HashMap key a+empty = HashMap I.empty+
+ src/Dao/Interpreter.hs view
@@ -0,0 +1,8391 @@+-- "src/Dao/Interpreter.hs" defines the Dao programming language semantics.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++{-# LANGUAGE CPP #-}++module Dao.Interpreter(+ Action(Action), actionTokens, actionPattern, actionMatch, actionCodeBlock,+ makeActionsForQuery, betweenBeginAndEnd, daoShutdown, getLocalRuleSet,+ getGlobalRuleSet, defaultTokenizer, constructPatternWith, constructPattern,+ DaoSetupM(), DaoSetup, haskellType, daoProvides, daoClass, daoConstant, daoFunction,+ daoFunction0, daoInitialize, setupDao, evalDao, DaoFunc, daoFunc, funcAutoDerefParams,+ daoForeignFunc, executeDaoFunc,+ Sizeable(getSizeOf),+ ObjectClass(obj, fromObj, castToCoreType),+ execCastToCoreType, listToObj, listFromObj, new, opaque, objFromHata,+ Struct(Nullary, Struct), structLookup,+ ToDaoStructClass(toDaoStruct), toDaoStructExec, pPrintStructForm,+ FromDaoStructClass(fromDaoStruct), withFromDaoStructExec, fromDaoStructExec,+ StructError(StructError),+ structErrName, structErrField, structErrValue, structErrExtras,+ mkLabel, mkStructName, mkFieldName,+ ToDaoStruct(),+ fromData, innerToStruct, innerToStructWith, renameConstructor, makeNullary, putNullaryUsingShow,+ define, optionalField, setField, defObjField, (.=), putObjField, (.=@), defMaybeObjField, (.=?),+ FromDaoStruct(),+ toData, constructor, innerFromStruct, nullary, getNullaryWithRead, structCurrentField,+ tryCopyField, tryField, copyField, field,+ convertFieldData, req, opt, reqList, optList,+ ObjectUpdate, ObjectTraverse,+ ObjectLens(updateIndex), ObjectFunctor(objectFMap),+ ObjectFocus, ObjFocusState(), getFocalReference,+ execToFocusUpdater, withInnerLens, runObjectFocus, focusObjectClass, focusStructAsDict,+ focusLiftExec, focalPathSuffix, focusGuardStructName, updateHataAsStruct, callMethod,+ innerDataUpdateIndex, referenceUpdateName, referenceLookupName,+ Object(+ ONull, OTrue, OType, OInt, OWord, OLong,+ OFloat, ORatio, OComplex, OAbsTime, ORelTime,+ OChar, OString, ORef, OList, ODict, OTree, OBytes, OHaskell+ ),+ T_type, T_int, T_word, T_long, T_ratio, T_complex, T_float, T_time, T_diffTime,+ T_char, T_string, T_ref, T_bytes, T_list, T_dict, T_struct,+ isNumeric, typeMismatchError,+ initializeGlobalKey, destroyGlobalKey, evalTopLevelAST,+ Reference(Reference, RefObject), reference, refObject, referenceHead, refUnwrap,+ refNames, referenceFromUStr, fmapReference, setQualifier, modRefObject,+ refAppendSuffix, referenceLookup, referenceUpdate,+ CoreType(+ NullType, TrueType, TypeType, IntType, WordType, DiffTimeType, FloatType,+ LongType, RatioType, ComplexType, TimeType, CharType, StringType, RefType,+ ListType, DictType, TreeType, BytesType, HaskellType+ ),+ typeOfObj, coreType, hataType, objTypeFromCoreType, objTypeFromName,+ TypeSym(CoreType, TypeSym, TypeVar), TypeStruct(TypeStruct), ObjType(ObjType), typeChoices,+ RefSuffix(NullRef, DotRef, Subscript, FuncCall),+ refSuffixHead, refSuffixHasFuncCall, refSuffixToList, dotRef, subscript, funcCall,+ Complex(Complex),+ realPart, imagPart, mkPolar, cis, polar, magnitude, phase, conjugate, complex,+ minAccumArray, minArray,+ FuzzyStr(FuzzyStr),+ ExecUnit(), ExecTokenizer(ExecTokenizer), runExecTokenizer,+ globalMethodTable, defaultTimeout, importGraph, currentWithRef, taskForExecUnits,+ currentQuery, currentPattern, currentBranch, providedAttributes, programModuleName,+ preExec, postExec, quittingTime, programTokenizer, currentCodeBlock, ruleSet,+ newExecUnit, inModule,+ Task(), initTask, throwToTask, killTask, taskLoop, taskLoop_,+ Executable(execute), DerefAssignExpr,+ ExecRef(execReadRef, execTakeRef, execPutRef, execSwapRef, execModifyRef, execModifyRef_),+ ExecControl(ExecReturn, ExecError), execReturnValue,+ execErrorMessage, execErrorInModule, execErrorLocation, execErrorSubtype, execErrorInfo,+ ExecErrorSubtype(+ ExecErrorUntyped, ExecThrow, ExecStructError, ExecUndefinedRef, ExecTypeError,+ ExecUpdateOpError, ExecInfixOpError, ExecIOException, ExecHaskellError+ ),+ errInFunc, errInConstr, errInInitzr, errOfReference, argNum, numArgsPassed,+ expectNumArgs, exectDimension, expectType, actualType, leftSideType, rightSideType,+ modifiedConst, assertFailed, returnedVoid, errorDict,+ newError, throwArityError, throwParseError, throwBadTypeError, errLocation, errModule,+ errCurrentModule, errInfo, updateExecErrorInfo, logUncaughtErrors, clearUncaughtErrorLog,+ execForM, execForM_,+ Exec(Exec), execToPredicate, XPure(XPure), xpureToState, runXPure, evalXPure, xpure, xobj,+ xnote, xonUTF8, xmaybe,+ ExecThrowable(toExecErrorInfo, execThrow), ioExec,+ ExecHandler(ExecHandler), execHandler,+ newExecIOHandler, execCatchIO, execHandleIO, execIOHandler,+ execErrorHandler, catchReturn, execNested, execNested_, execFuncPushStack,+ execFuncPushStack_, execWithStaticStore, execWithWithRefStore, withExecTokenizer,+ Subroutine(Subroutine), setupCodeBlock,+ origSourceCode, staticVars, staticRules, staticLambdas, executable, runCodeBlock, runCodeBlock_,+ RuleSet(), CallableCode(CallableCode), argsPattern, returnType, codeSubroutine, + PatternRule(PatternRule), rulePatterns, ruleAction, + asReference, asInteger, asRational, asPositive, asComplex, objConcat,+ objToBool, extractStringElems, requireAllStringArgs,+ shiftLeft, shiftRight,+ evalArithPrefixOp, evalInfixOp, evalUpdateOp, runTokenizerWith, runTokenizer, makePrintFunc,+ paramsToGlobExpr, matchFuncParams, execGuardBlock, objToCallable, callCallables,+ callObject, checkPredicate, checkVoid,+ evalConditional,+ localVarDefine, localVarUpdate, localVarLookup, maybeDerefObject, derefObjectGetReference, derefObject,+ updateExecError,+ assignUnqualifiedOnly,+ LimitedObject(LimitedObject, unlimitObject),+ MethodTable(), execGetObjTable, lookupMethodTable,+ ReadIterable(readForLoop), UpdateIterable(updateForLoop),+ HataClass(haskellDataInterface), toHata, fromHata,+ InitItem(InitSingle, InitAssign),+ Interface(),+ objCastFrom, objEquality, objOrdering, objBinaryFormat, objNullTest, objPPrinter,+ objSizer, objIndexer, objIndexUpdater, objToStruct, objFromStruct, objInitializer, objTraverse,+ objInfixOpTable, objArithPfxOpTable, objCallable, objDereferencer,+ interfaceAdapter, interfaceToDynamic,+ DaoClassDefM(), interface, DaoClassDef,+ defCastFrom, autoDefEquality, defEquality, autoDefOrdering, defOrdering, autoDefBinaryFmt,+ defBinaryFmt, autoDefNullTest, defNullTest, defPPrinter, autoDefPPrinter, defReadIterable,+ autoDefReadIterable, defUpdateIterable, autoDefUpdateIterable, defIndexer, defIndexUpdater,+ defSizer, autoDefSizeable, autoDefToStruct, defToStruct, autoDefFromStruct, defFromStruct,+ defInitializer, defTraverse, autoDefTraverse, defInfixOp, defPrefixOp, defCallable, defDeref,+ defMethod, defMethod0, defLeppard+ )+ where++import Dao.Glob+import Dao.PPrint+import Dao.Predicate+import Dao.Random+import Dao.Stack+import Dao.String+import Dao.Token+import Dao.RefTable+import qualified Dao.HashMap as H+import qualified Dao.Binary as B+import qualified Dao.Interval as Iv+import qualified Dao.Tree as T+import Dao.Interpreter.Tokenizer+import Dao.Interpreter.AST++import Data.Array.IArray+import Data.Binary (encode)+import Data.Bits+import Data.Char+import Data.Dynamic+import Data.IORef+import Data.List+import Data.Monoid+import Data.Ratio+import Data.Time.Clock+import Data.Word+import qualified Data.ByteString.Lazy.UTF8 as U+import qualified Data.ByteString.Lazy as B+import qualified Data.Binary as D+import qualified Data.Binary.Put as D+import qualified Data.Binary.Get as D+import qualified Data.Complex as C+import qualified Data.Map as M+import qualified Data.Set as S++import Control.Applicative+import Control.Concurrent+import Control.DeepSeq+import Control.Exception+import Control.Monad+import Control.Monad.Error+import Control.Monad.Reader+import Control.Monad.State++#if 0+import Debug.Trace+import System.IO+strace :: PPrintable s => String -> s -> s+strace msg s = trace (msg++": "++prettyShow s) s+dbg :: MonadIO m => String -> m ()+dbg = liftIO . hPutStrLn stderr . ("(DEBUG) "++) . (>>=(\c -> if c=='\n' then "\n(DEBUG) " else [c]))+dbg' :: MonadIO m => String -> m a -> m a+dbg' msg f = f >>= \a -> dbg msg >> return a+dbg0 :: (MonadPlus m, MonadIO m, MonadError e m, Show e) => String -> m a -> m a+dbg0 msg f = do+ dbg (msg++" (BEGIN)")+ catchError+ (mplus (f >>= \a -> dbg (msg++" (DONE)") >> return a) (dbg (msg++" (BACKTRACKED)") >> mzero))+ (\e -> dbg (msg++" (ERROR) "++show e) >> throwError e)+updbg :: MonadIO m => String -> (Maybe Object -> m (Maybe Object)) -> Maybe Object -> m (Maybe Object)+updbg msg f o = dbg ("(update with "++msg++")") >> f o >>= \o -> dbg ("(update complete "++show o++")") >> return o+#endif+#if 0+_randTrace :: String -> RandO a -> RandO a+_randTrace = Dao.Random.randTrace+#else+_randTrace :: String -> RandO a -> RandO a+_randTrace _ = id+#endif++----------------------------------------------------------------------------------------------------++-- A note on the binary format.+-- Most constructors have a unique prefix byte, and this allows more efficient encoding because+-- it is not necessary to place null terminators everywhere and you can determine exactly which+-- constructor is under the decoder cursor just from the byte prefix. This means there is an+-- address space for prefix bytes between 0x00 and 0xFF. This is an overview of that address space.+-- +-- 0x00..0x07 > The "Dao.Binary" module declares a few unique prefixes of its own for booleans,+-- variable-length integers, and maybe types, and of course the null terminator.+-- 0x08..0x1A > Each prefix here used alone indicates a 'CoreType's. But each prefix may be followed+-- by data which indicates that it is actuall one of the constructors for the 'Object'+-- data type.+-- +-- 0x25..0x26 'Struct'+-- 0x2E..0x2F 'TypeSym'+-- 0x33 'TypeStruct'+-- 0x37 'ObjType' (T_type)+-- +-- 0x42..0x45 > The 'RefSuffix' data type. These prefixes are re-used for the 'ReferenceExpr' data type+-- because there is a one-to-one mapping between these two data types.+-- 0x48..0x4F > The 'Reference' data type. These prefixes are re-used for the 'ReferenceExpr' data type+-- execpt for the 'RefWrapper' constructor which is mapped to @'RefPrefixExpr' 'REF'@.+-- +-- -- the abstract syntax tree -- --+-- +-- 0x52..0x53 'RefPrefixExpr'+-- 0x59 'ParenExpr'+-- 0x60..0x64 'ObjectExpr'+-- 0x6A 'ArithExpr'+-- 0x6F 'AssignExpr'+-- 0x73 'ObjTestExpr'+-- 0x74..0x76 'RuleFuncExpr'+-- 0x7A..0x7B 'RuleHeadExpr'+-- 0x81 'DotLabelExpr'+-- 0x82 'AttributeExpr'+-- 0x86 'ObjListExpr'+-- 0xBA..0xCD 'InfixOp'+-- 0x8D..0x9D 'UpdateOp' -- Partially overlaps with 'InfixOp'+-- 0x8E..0x9B 'ArithPfxOp' -- Partially overlaps with 'InfixOp'+-- 0xA8..0xAF 'ScriptExpr'+-- 0xB6 'ElseExpr'+-- 0xBA 'IfElseExpr'+-- 0xBE 'WhileExpr'+-- 0xC5..0xC7 'TyChkExpr'+-- 0xCF..0xD0 'ParamExpr'+-- 0xD6 'ParamListExpr'+-- 0xDD 'CodeBlock'+-- 0xE9..0xEE 'TopLevelExpr'++----------------------------------------------------------------------------------------------------++-- | An 'Action' is the result of a pattern match that occurs during an input string query. It is a+-- data structure that contains all the information necessary to run an 'Subroutine' assocaited with+-- a 'Glob', including the parent 'ExecUnit', the 'Dao.Glob.Glob' and the 'Dao.Glob.Match' objects,+-- and the 'Executables'. Use 'execute' to evaluate a 'Action' in the current thread.+-- +-- To execute an action in a separate thread, use 'forkExecAction'.+data Action+ = Action+ { actionTokens :: [Object]+ , actionPattern :: Glob Object+ , actionMatch :: M.Map Name Object+ , actionCodeBlock :: Subroutine+ }+ deriving (Eq, Typeable)++instance PPrintable Action where { pPrint = pPrintStructForm }++instance ToDaoStructClass Action where+ toDaoStruct = renameConstructor "Action" $ do+ "tokens" .=@ actionTokens+ "pattern" .=@ actionPattern+ "match" .=@ actionMatch+ "code" .=@ actionCodeBlock++instance FromDaoStructClass Action where+ fromDaoStruct = do+ constructor "Action"+ let err name o = flip (execThrow "match dictionary items must contain lists of tokens") [] $+ StructError+ { structErrName = Nothing+ , structErrField = Just (toUStr name)+ , structErrValue = Just $ obj (typeOfObj o)+ , structErrExtras = []+ }+ let fmtMatches = fmap M.fromList .+ mapM (\ (name, o) -> xmaybe (fromObj o) <|> err name o >>= return . (,) name) . M.assocs+ return Action <*> req "tokens" <*> req "pattern" <*> (req "match" >>= fmtMatches) <*> req "code"++instance ObjectClass Action where { obj=new; fromObj=objFromHata; }++instance HataClass Action where+ haskellDataInterface = interface "Action" $ do+ autoDefEquality >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++instance Executable Action (Maybe Object) where+ execute act = do+ cq <- gets currentQuery+ cp <- gets currentPattern+ ccb <- gets currentCodeBlock+ let setVar :: ObjectClass o => String -> (Action -> o) -> T_dict -> T_dict+ setVar name f = M.insert (ustr name) (obj $ f act)+ let setTokensVar = setVar "tokens" actionTokens+ let setSelfVar = M.union (M.singleton (ustr "self") (obj $ setTokensVar $ mempty))+ let localVars = setSelfVar $ actionMatch act+ modify $ \xunit -> + xunit+ { currentQuery = Just $ actionTokens act+ , currentPattern = Just $ actionPattern act+ , currentCodeBlock = Just $ actionCodeBlock act+ }+ success <- optional $ runCodeBlock_ localVars (actionCodeBlock act)+ modify (\xunit -> xunit{ currentQuery=cq, currentPattern=cp, currentCodeBlock=ccb })+ xmaybe success++instance Executable [Action] [Object] where+ execute = fmap concat .+ mapM (\act -> catchPredicate (execute act) >>= \p -> case p of+ OK (Just o) -> return [o]+ PFail (ExecReturn (Just o)) -> return [o]+ PFail err -> logUncaughtErrors [err] >> return []+ _ -> return []+ )++-- | Using an 'ExecTokenizer' function, break up a string into a list of tokens, returning them as a+-- 'TokenList' object. Input paramaters can be strings or lists of strings. If an input paramter is+-- a string, it is tokenized to a list of strings. If an input parameter is a list of strings, it is+-- considered to be already tokenized and simply appended to list of tokens produced by previous+-- parameters.+runTokenizerWith :: ExecTokenizer -> [Object] -> Exec [Object]+runTokenizerWith tok ox = fmap concat $ forM ox $ \o -> case o of+ OString o -> fmap obj <$> runExecTokenizer tok o+ OList ox -> return ox+ o -> return [o]++-- | Like 'runTokenizerWith', but uses the default tokenizer function set for this module.+runTokenizer :: [Object] -> Exec [Object]+runTokenizer ox = gets programTokenizer >>= \tok -> runTokenizerWith tok ox++-- | Match a given input string to the 'Dao.Evaluator.currentPattern' of the current 'ExecUnit'.+-- Return all patterns and associated match results and actions that matched the input string, but+-- do not execute the actions. This is done by tokenizing the input string and matching the tokens+-- to the program using 'Dao.Glob.matchTree'. NOTE: Rules that have multiple patterns may execute+-- more than once if the input matches more than one of the patterns associated with the rule. *This+-- is not a bug.* Each pattern may produce a different set of match results, it is up to the+-- programmer of the rule to handle situations where the action may execute many times for a single+-- input.+-- +-- Once you have created an action group, you can execute it with 'Dao.Evaluator.execute'.+makeActionsForQuery :: [PatternTree Object [Subroutine]] -> [Object] -> Exec [Action]+makeActionsForQuery tree tokens = do+ let match = matchTree False (T.unionsWith (++) tree) tokens+ fmap concat $ forM match $ \ (patn, match, execs) -> do+ match <- catchPredicate $ fmap M.fromList $ -- evaluate pattern type checkers+ forM (M.assocs match) $ \ (name, (vartyp, ox)) -> case vartyp of+ Nothing -> return (name, obj ox)+ Just vartyp -> do+ match <- catchPredicate $ referenceLookup $ Reference UNQUAL vartyp $ FuncCall [OList ox] NullRef+ case match of+ OK (_, Nothing) -> return (name, obj ox)+ OK (_, Just o) -> return (name, o)+ PFail (ExecReturn Nothing) -> return (name, obj ox)+ PFail (ExecReturn (Just o)) -> return (name, o)+ PFail err -> throwError err+ Backtrack -> mzero+ case match of+ Backtrack -> return []+ PFail err -> logUncaughtErrors [err] >> return []+ OK match -> return $+ flip fmap execs $ \exec -> deepseq exec $! deepseq tokens $! deepseq patn $! deepseq match $!+ Action+ { actionPattern = patn+ , actionTokens = fmap obj tokens+ , actionMatch = match+ , actionCodeBlock = exec+ }++-- | Evaluate an executable function between evaluating all of the "BEGIN{}" and "END{}" statements.+betweenBeginAndEnd :: Exec a -> Exec a+betweenBeginAndEnd runInBetween = get >>= \xunit -> do+ -- Run all "BEGIN{}" procedures.+ mapM_ execute (preExec xunit)+ clearUncaughtErrorLog+ -- Run the given function, presumably it performs a string execution.+ a <- runInBetween+ -- Update the "global this" pointer to include the uncaught exceptions.+ errs <- OList . map new <$> clearUncaughtErrorLog+ let upd = M.union (M.singleton (ustr "errors") errs)+ referenceUpdate (Dao.Interpreter.reference GLOBAL (ustr "self")) False $ \o ->+ return $ Just $ ODict $ upd $ case o of { Just (ODict o) -> o; _ -> mempty; }+ -- Run all "END{}" procedures.+ mapM_ execute (postExec xunit)+ return a++-- | Evaluates the @EXIT@ scripts for every presently loaded dao program, and then clears the+-- 'Dao.Interpreter.importGraph', effectively removing every loaded dao program and idea file from memory.+daoShutdown :: Exec ()+daoShutdown = (M.elems <$> gets importGraph) >>=+ mapM_ (\xunit -> inModule xunit $ gets quittingTime >>= mapM_ execute)++-- | Returns a list of @'PatternTree' 'Object' ['Subroutine']@ objects from the global rule set and+-- the local rule set.+getLocalRuleSet :: Exec [PatternTree Object [Subroutine]]+getLocalRuleSet = do+ sub <- gets currentCodeBlock+ return $ maybe [] (return . staticRules) sub++-- | Returns a list of @'PatternTree' 'Object' ['Subroutine']@ objects from the global rule set and+-- the local rule set.+getGlobalRuleSet :: Exec [PatternTree Object [Subroutine]]+getGlobalRuleSet = return <$> gets ruleSet++_mkDoFunc :: String -> [Exec [PatternTree Object [Subroutine]]] -> (DaoFunc (), DaoFunc (), DaoFunc ())+_mkDoFunc name selectors = (mkDo, mkDoAll, mkQuery) where+ run f () ox = do+ inTrees <- concat <$> sequence selectors+ flip (,) () <$> (runTokenizer ox >>= makeActionsForQuery inTrees >>= f)+ mkQuery = daoFunc{daoFuncName=ustr("query"++name), daoForeignFunc=run(return . Just . obj . fmap obj)}+ mkDo = daoFunc{daoFuncName=ustr("do" ++name), daoForeignFunc=run(betweenBeginAndEnd . msum . fmap execute)}+ mkDoAll = daoFunc{daoFuncName=ustr("doAll"++name), daoForeignFunc=run(betweenBeginAndEnd . fmap (Just . obj) . execute)}++builtin_do :: DaoFunc ()+builtin_doAll :: DaoFunc ()+builtin_query :: DaoFunc ()+(builtin_do, builtin_doAll, builtin_query) = _mkDoFunc "" [getLocalRuleSet, getGlobalRuleSet]++builtin_doLocal :: DaoFunc ()+builtin_doAllLocal :: DaoFunc ()+builtin_queryLocal :: DaoFunc ()+(builtin_doLocal, builtin_doAllLocal, builtin_queryLocal) = _mkDoFunc "Local" [getLocalRuleSet]++builtin_doGlobal :: DaoFunc ()+builtin_doAllGlobal :: DaoFunc ()+builtin_queryGlobal :: DaoFunc ()+(builtin_doGlobal, builtin_doAllGlobal, builtin_queryGlobal) = _mkDoFunc "Global" [getGlobalRuleSet]++----------------------------------------------------------------------------------------------------++-- | When a 'Dao.Interpreter.AST.RuleExpr' is evaluated to an 'Object', it takes this form.+-- 'PatternRule' instantiats 'Executable' such that 'execute' converts it to a 'PatternTree'.+data PatternRule+ = PatternRule{ rulePatterns :: [Object], ruleAction :: Subroutine }+ deriving (Show, Typeable)++instance NFData PatternRule where { rnf (PatternRule a b) = deepseq a $! deepseq b () }++instance HasNullValue PatternRule where+ nullValue = PatternRule{rulePatterns=[], ruleAction=nullValue}+ testNull (PatternRule a b) = null a && testNull b++instance PPrintable PatternRule where+ pPrint (PatternRule pats exe) = (\a -> ppCallableAction "rule" a nullValue exe) $ case pats of+ [] -> pString "()"+ [pat] -> pPrint pat+ pats -> pList (pString "rule") "(" ", " ")" (map pPrint pats)++instance ToDaoStructClass PatternRule where+ toDaoStruct = renameConstructor "PatternRule" $ do+ "patterns" .=@ rulePatterns+ "action" .=@ ruleAction++instance FromDaoStructClass PatternRule where+ fromDaoStruct = return PatternRule <*> req "patterns" <*> req "action"++instance Executable PatternRule (PatternTree Object [Subroutine]) where+ execute (PatternRule{ rulePatterns=pats, ruleAction=sub }) = do+ globs <- mapM (constructPattern . return) pats+ return $ insertMultiPattern (++) globs [sub] mempty++instance ObjectClass PatternRule where { obj=new; fromObj=objFromHata; }++instance HataClass PatternRule where+ haskellDataInterface = interface "PatternRule" $ do+ autoDefPPrinter >> autoDefToStruct >> autoDefFromStruct++defaultTokenizer :: ExecTokenizer+defaultTokenizer = ExecTokenizer $ return . fmap obj . simpleTokenizer . uchars++-- | This function takes a list of objects and constructs a list of @('Dao.Glob.Glob' 'Object')@s to+-- be inserted into a 'Dao.Glob.PatternTree' object. The input list of @['Object']@s will each form+-- a single pattern, then all of the patterns are unioned together to form the pattern tree. This+-- means token strings matched against the resulting @('Dao.Glob.Glob' 'Object')@ constructed by+-- this function will match any and all of the patterns. If any of the objects in the input list+-- are strings, the strings will be parsed into 'Dao.Glob.Glob' objects, and each string constant+-- within the 'Dao.Glob.Glob' object will be further tokenized with the 'programTokenizer'.+constructPatternWith :: ExecTokenizer -> [Object] -> Exec (Glob Object)+constructPatternWith tok = fmap (mconcat . mconcat) . mapM (derefObject>=>construct) where+ construct o = case o of+ OString o -> case readsPrec 0 (uchars o) of+ [(glob, "")] -> fmap return $ parseOverSinglesM glob $ \str -> case str of+ "" -> return []+ str -> fmap obj <$> runExecTokenizer tok (ustr str)+ _ -> execThrow "unable to parse pattern" ExecErrorUntyped []+ OList ox -> return [makeGlob $ fmap Single ox]+ OHaskell (Hata _ d) -> do+ let err = throwBadTypeError "could not use as pattern expression" o []+ maybe err return $ msum $+ [ return <$> fromDynamic d+ , fmap (makeGlob . fst) . T.assocs . ruleSetRules <$> fromDynamic d+ ]+ _ -> throwBadTypeError "could not create pattern from data of type" o []++-- | Like 'constructPatternWith' but uses the default 'ExecTokenizer' that has been set for the+-- current 'ExecUnit'.+constructPattern :: [Object] -> Exec (Glob Object)+constructPattern ox = gets programTokenizer >>= flip constructPatternWith ox++----------------------------------------------------------------------------------------------------++-- The stateful data for the 'DaoSetup' monad.+data SetupModState+ = SetupModState+ { daoSatisfies :: M.Map UStr ()+ -- ^ a set of references that can satisfy "required" statements in Dao scripts.+ , daoSetupConstants :: M.Map Name Object+ , daoClasses :: MethodTable+ , daoEntryPoint :: Exec ()+ }++-- | This monadic type allows you to define a built-in module using procedural+-- programming syntax. Simply define each addition to the module one line at a time. Functions that+-- you can use include 'modProvides', 'modFunction', 'daoClass', and 'daoInitalize'.+-- +-- Define clever names for every 'DaoSetup' you write, then +type DaoSetup = DaoSetupM ()+newtype DaoSetupM a = DaoSetup{ daoSetupM :: State SetupModState a }+ deriving (Functor, Applicative, Monad)++-- | This function is a placeholder used by the type system. The value of this function is+-- undefined, so strictly evaluating it will throw an exception. Fortunately, the only time you will+-- ever use this function is with the 'daoClass' function, which uses the type of this function but+-- never it's value. Refer to the documentation on 'daoClass' to see how to properly use this+-- function.+haskellType :: HataClass o => o+haskellType = error $ unwords $+ [ "the haskellType function is just a placeholder"+ , "used by the type system, it must not be evaluated."+ ]++_updateSetupModState :: (SetupModState -> SetupModState) -> DaoSetup+_updateSetupModState f = DaoSetup (modify f)++-- | Dao programs can declare "requires" statements along with it's imports. If your built-in module+-- provides what Dao programs might "required", then declare that this module provides that feature+-- using this function.+daoProvides :: UStrType s => s -> DaoSetup+daoProvides label = _updateSetupModState $ \st ->+ st{ daoSatisfies = M.insert (toUStr label) () $ daoSatisfies st }++-- | Associate an 'HataClass' with a 'Name'. This 'Name' will be callable from within Dao scripts.+-- > newtype MyClass = MyClass { ... } deriving (Eq, Ord)+-- >+-- > instance 'HataClass' MyClass where+-- > 'haskellDataInterface' = 'interface' $ do+-- > 'autoDefEquality'+-- > 'autoDefOrdering'+-- > ...+-- >+-- > setupDao :: 'DaoSetup'+-- > setupDao = do+-- > daoClass "myClass" (haskellType::MyClass)+-- > ...+daoClass :: (Typeable o, HataClass o) => o -> DaoSetup+daoClass ~o = _updateSetupModState $ \st ->+ st{ daoClasses = _insertMethodTable o haskellDataInterface (daoClasses st) }++-- | Define a built-in top-level function that is not a member method of any object. Examples of+-- built-in functions provided in this module are "println()" and "typeof()".+daoFunction :: (Show name, UStrType name) => name -> DaoFunc () -> DaoSetup+daoFunction name func = _updateSetupModState $ \st -> let nm = (fromUStr $ toUStr name) in+ st{ daoSetupConstants = M.insert nm (new $ func{ daoFuncName=nm }) (daoSetupConstants st) }++-- | Like 'daoFunction' but creates a function that takes no parameters.+daoFunction0 :: Name -> Exec (Maybe Object) -> DaoSetup+daoFunction0 name f = daoFunction name $+ DaoFunc+ { daoFuncClass = []+ , daoFuncName = nil+ , funcAutoDerefParams = False+ , daoForeignFunc = \ () ox -> case ox of+ [] -> flip (,) () <$> f+ _ -> throwArityError "" 0 ox [(errInFunc, obj $ reference UNQUAL name)]+ }++-- | Define a constant value for any arbitrary 'Object'.+daoConstant :: (Show name, UStrType name) => name -> Object -> DaoSetup+daoConstant name o = _updateSetupModState $ \st ->+ st{ daoSetupConstants = M.insert (fromUStr $ toUStr name) o (daoSetupConstants st) }++-- | Provide an 'Exec' monad to perform when 'setupDao' is evaluated. You may use this function as+-- many times as you wish, every 'Exec' monad will be executed in the order they are specified. This+-- is a good way to create a read-eval-print loop.+daoInitialize :: Exec () -> DaoSetup+daoInitialize f = _updateSetupModState $ \st -> st{ daoEntryPoint = daoEntryPoint st >> f }++-- | Use this function evaluate a 'DaoSetup' in the IO () monad. Use this to define the 'main'+-- function of your program.+setupDao :: DaoSetup -> IO (Predicate ExecControl ())+setupDao setup0 = do+ let setup = execState (daoSetupM $ loadEssentialFunctions >> setup0) $+ SetupModState+ { daoSatisfies = M.empty+ , daoSetupConstants = M.empty+ , daoClasses = mempty+ , daoEntryPoint = return ()+ }+ xunit <- _initExecUnit+ fmap fst $ ioExec (daoEntryPoint setup) $+ xunit+ { providedAttributes = daoSatisfies setup+ , builtinConstants = daoSetupConstants setup+ , globalMethodTable = daoClasses setup+ }++-- | Simply run a single 'Exec' function in a fresh environment with no setup, and delete the+-- envrionment when finished returning only the 'Dao.Predicate.Predicate' result of the 'Exec'+-- evaluation. If you want to have more control over the runtime in which the 'Exec' function runs,+-- use 'setupDao' with 'daoInitialize'.+evalDao :: Exec a -> IO (Predicate ExecControl a)+evalDao f = _initExecUnit >>= fmap fst . ioExec f++----------------------------------------------------------------------------------------------------++-- | All object methods that operate on object data types built-in to the Dao language, or built-in+-- to a library extending the Dao language, are stored in 'Data.Map.Map's from the functions name to+-- an object of this type.+--+-- The @this@ of this function is the data type of what languages like C++ or Java would call the+-- "self" variable. 'DaoFunc's where the @this@ is () are considered ordinary functions that do not+-- operate on any object apart from their input parameters.+data DaoFunc this+ = DaoFunc+ { daoFuncClass :: [Name]+ , daoFuncName :: Name+ , funcAutoDerefParams :: Bool+ , daoForeignFunc :: this -> [Object] -> Exec (Maybe Object, this)+ }+ deriving Typeable+instance Eq (DaoFunc this) where { a == b = daoFuncName a == daoFuncName b; }+instance Ord (DaoFunc this) where { compare a b = compare (daoFuncName a) (daoFuncName b) }+instance Show (DaoFunc this) where+ show func =+ if null (daoFuncClass func)+ then uchars (daoFuncName func)+ else foldr (\name str -> uchars name ++ "." ++ str) (uchars $ daoFuncName func) (daoFuncClass func)+instance PPrintable (DaoFunc this) where { pPrint = pShow }++-- | Use this as the constructor of a 'DaoFunc'. By default the @this@ type is (). To change the+-- @this@ type, simply supply a different function type for the 'daoForeignFunc' field. For example:+-- > daoFunc{ daoFuncName=ustr "add", daoForeignFunc = retrun . (+1) } :: DaoFunc Int+daoFunc :: DaoFunc typ+daoFunc =+ DaoFunc+ { daoFuncClass = []+ , daoFuncName = nil+ , funcAutoDerefParams = True+ , daoForeignFunc = \typ _ -> return (Nothing, typ)+ }++-- | Execute a 'DaoFunc' +executeDaoFunc :: DaoFunc this -> this -> [Object] -> Exec (Maybe Object, this)+executeDaoFunc fn this params = do+ args <- (if funcAutoDerefParams fn then mapM derefObject else return) params+ pval <- catchPredicate (daoForeignFunc fn this args)+ case pval of+ OK (o, this) -> return (o, this)+ PFail (ExecReturn o) -> return (o, this)+ PFail err -> throwError err+ Backtrack -> mzero++-- Evaluate this function as one of the instructions in the monadic function passed to the+-- 'setupDao' function in order to install the most fundamental functions into the Dao evaluator.+-- This function must be evaluated in order to have access to the following functions:+-- > print, join, defined, delete+loadEssentialFunctions :: DaoSetup+loadEssentialFunctions = do+ daoClass (haskellType :: H.HashMap Object Object)+ daoClass (haskellType :: RuleSet)+ daoClass (haskellType :: Pair)+ daoFunction "print" builtin_print+ daoFunction "println" builtin_println+ daoFunction "join" builtin_join+ daoFunction "str" builtin_str+ daoFunction "quote" builtin_quote+ daoFunction "concat" builtin_concat+ daoFunction "concat1" builtin_concat1+ daoFunction "reverse" builtin_reverse+ daoFunction "int" builtin_int+ daoFunction "long" builtin_long+ daoFunction "ratio" builtin_ratio+ daoFunction "float" builtin_float+ daoFunction "complex" builtin_complex+ daoFunction "imag" builtin_imag+ daoFunction "phase" builtin_phase+ daoFunction "conj" builtin_conj+ daoFunction "abs" builtin_abs+ daoFunction "time" builtin_time+ daoFunction "now" builtin_now+ daoFunction "ref" builtin_ref+ daoFunction "defined" builtin_check_if_defined+ daoFunction "delete" builtin_delete+ daoFunction "typeof" builtin_typeof+ daoFunction "sizeof" builtin_sizeof+ daoFunction "call" builtin_call+ daoFunction "toHash" builtin_toHash+ daoFunction "fromHash" builtin_fromHash+ daoFunction "tokenize" builtin_tokenize+ daoFunction "query" builtin_query+ daoFunction "doAll" builtin_doAll+ daoFunction "do" builtin_do+ daoFunction "fromStruct" builtin_fromStruct+ daoFunction "toStruct" builtin_toStruct+ daoFunction "queryGlobal" builtin_queryGlobal+ daoFunction "doAllGlobal" builtin_doAllGlobal+ daoFunction "doGlobal" builtin_doGlobal+ daoFunction "queryLocal" builtin_queryLocal+ daoFunction "doAllLocal" builtin_doAllLocal+ daoFunction "doLocal" builtin_doLocal+ daoFunction "HashMap" builtin_HashMap+ daoFunction "assocs" builtin_assocs+ daoFunction "Pair" builtin_Pair+ mapM_ (uncurry daoConstant) $ flip fmap [minBound..maxBound] $ \t ->+ (toUStr $ show t, OType $ objTypeFromCoreType t)++instance ObjectClass (DaoFunc ()) where { obj=new; fromObj=objFromHata; }+instance ObjectClass (DaoFunc Dynamic) where { obj=new; fromObj=objFromHata; }+instance ObjectClass (DaoFunc Hata) where { obj=new; fromObj=objFromHata; }+instance ObjectClass (DaoFunc Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (DaoFunc ()) where+ haskellDataInterface = interface "Builtin_Function" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter++instance HataClass (DaoFunc Dynamic) where+ haskellDataInterface = interface "Builtin_Dynamic_Method" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter++instance HataClass (DaoFunc Hata) where+ haskellDataInterface = interface "Builtin_Haskell_Data_Method" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter++instance HataClass (DaoFunc Object) where+ haskellDataInterface = interface "Builtin_Object_Method" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter++----------------------------------------------------------------------------------------------------++-- | This class provides a consistent interface, the 'obj' function, for converting a wide range of+-- types to an 'Object' type.+class ObjectClass o where+ obj :: o -> Object+ fromObj :: Object -> Maybe o+ castToCoreType :: CoreType -> o -> XPure Object+ castToCoreType _ _ = mzero++execCastToCoreType :: ObjectClass o => CoreType -> o -> Exec Object+execCastToCoreType t = execute . castToCoreType t++instance ObjectClass () where+ obj () = ONull+ fromObj o = case o of { ONull -> return (); _ -> mzero; }+ castToCoreType t () = case t of+ NullType -> return ONull+ CharType -> return $ OChar '\0'+ IntType -> return $ OInt 0+ WordType -> return $ OWord 0+ LongType -> return $ OLong 0+ DiffTimeType -> return $ ORelTime 0+ FloatType -> return $ OFloat 0+ RatioType -> return $ ORatio 0+ ComplexType -> return $ OComplex $ complex 0 0+ StringType -> return $ OString nil+ BytesType -> return $ OBytes mempty+ ListType -> return $ OList []+ DictType -> return $ ODict mempty+ _ -> mzero++instance ObjectClass Bool where+ obj true = if true then OTrue else ONull+ fromObj o = case o of { OTrue -> return True; ONull -> return False; _ -> mzero }+ castToCoreType t o = case t of+ NullType -> guard (not o) >> return ONull+ TrueType -> guard o >> return OTrue+ CharType -> return $ OChar $ if o then '1' else '0'+ IntType -> return $ OInt $ if o then 1 else 0+ WordType -> return $ OWord $ if o then 1 else 0+ LongType -> return $ OLong $ if o then 1 else 0+ DiffTimeType -> return $ ORelTime $ if o then 1 else 0+ FloatType -> return $ OFloat $ if o then 1 else 0+ RatioType -> return $ ORatio $ if o then 1 else 0+ ComplexType -> return $ OComplex $ if o then complex 1 0 else complex 0 0+ StringType -> return $ obj $ if o then "true" else "false"+ BytesType -> return $ OBytes $ B.pack $ return $ if o then 1 else 0+ _ -> mzero++instance ObjectClass Char where+ obj = OChar+ fromObj o = case o of { OChar o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (o=='\0') >> return ONull+ TrueType -> \o -> case o of+ '0' -> return ONull+ '1' -> return OTrue+ _ -> mzero+ CharType -> return . OChar+ IntType -> return . OInt . ord+ WordType -> return . OWord . fromIntegral . ord+ LongType -> return . OLong . fromIntegral . ord+ DiffTimeType -> return . ORelTime . fromRational . toRational . ord+ FloatType -> return . OFloat . fromRational . toRational . ord+ RatioType -> return . ORatio . toRational . ord+ ComplexType -> return . OComplex . flip complex 0 . fromRational . toRational . ord+ StringType -> return . obj . (:[])+ BytesType -> return . OBytes . D.runPut . D.putWord64le . fromIntegral . ord+ _ -> \ _ -> mzero++charFromIntegral :: (MonadPlus m, Integral i) => i -> m Char+charFromIntegral i0 =+ let i = fromIntegral i0+ in if ord(minBound::Char) <= i && i <= ord(maxBound::Char) then return (chr i) else mzero++instance ObjectClass Int where+ obj = OInt+ fromObj o = case o of { OInt o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (o==0) >> return ONull+ TrueType -> \o -> return $ if o==0 then ONull else OTrue+ CharType -> fmap OChar . charFromIntegral+ IntType -> return . OInt+ WordType -> return . OWord . fromIntegral+ LongType -> return . OLong . toInteger+ FloatType -> return . OFloat . fromRational . toRational+ RatioType -> return . ORatio . toRational+ ComplexType -> return . OComplex . flip complex 0 . fromRational . toRational+ DiffTimeType -> return . ORelTime . fromRational . toRational+ StringType -> return . obj . prettyShow . obj+ BytesType -> return . OBytes . D.runPut . D.putWord64le . fromIntegral+ _ -> \ _ -> mzero++instance ObjectClass Word where+ obj = OWord . fromIntegral+ fromObj o = case o of { OWord o -> return (fromIntegral o); _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (o==0) >> return ONull+ TrueType -> \o -> return $ if o==0 then ONull else OTrue+ CharType -> fmap OChar . charFromIntegral+ IntType -> return . OInt . fromIntegral+ WordType -> return . OWord . fromIntegral+ LongType -> return . OLong . toInteger+ FloatType -> return . OFloat . fromRational . toRational+ RatioType -> return . ORatio . toRational+ ComplexType -> return . OComplex . flip complex 0 . fromRational . toRational+ DiffTimeType -> return . ORelTime . fromRational . toRational+ StringType -> return . obj . prettyShow . obj+ BytesType -> return . OBytes . D.runPut . D.putWord64le . fromIntegral+ _ -> \ _ -> mzero++instance ObjectClass Word64 where+ obj = OWord+ fromObj o = case o of { OWord o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (o==0) >> return ONull+ TrueType -> \o -> return $ if o==0 then ONull else OTrue+ CharType -> fmap OChar . charFromIntegral+ IntType -> return . OInt . fromIntegral+ WordType -> return . OWord+ LongType -> return . OLong . toInteger+ FloatType -> return . OFloat . fromRational . toRational+ RatioType -> return . ORatio . toRational+ ComplexType -> return . OComplex . flip complex 0 . fromRational . toRational+ DiffTimeType -> return . ORelTime . fromRational . toRational+ StringType -> return . obj . prettyShow . obj+ BytesType -> return . OBytes . D.runPut . D.putWord64le . fromIntegral+ _ -> \ _ -> mzero++instance ObjectClass Integer where+ obj = OLong+ fromObj o = case o of { OLong o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (o==0) >> return ONull+ TrueType -> \o -> return $ if o==0 then ONull else OTrue+ CharType -> fmap OChar . charFromIntegral+ IntType -> return . OInt . fromInteger+ WordType -> return . OWord . fromInteger+ LongType -> return . OLong+ FloatType -> return . OFloat . fromRational . toRational+ RatioType -> return . ORatio . toRational+ ComplexType -> return . OComplex . flip complex 0 . fromRational . toRational+ DiffTimeType -> return . ORelTime . fromRational . toRational+ StringType -> return . obj . show+ BytesType -> return . OBytes . B.reverse . D.encode+ _ -> \ _ -> mzero++instance ObjectClass NominalDiffTime where+ obj = ORelTime+ fromObj o = case o of { ORelTime o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (toRational o == 0) >> return ONull+ TrueType -> \o -> return $ if toRational o == 0 then ONull else OTrue+ IntType -> return . OInt . round+ WordType -> return . OWord . round+ LongType -> return . OLong . round+ FloatType -> return . OFloat . fromRational . toRational+ DiffTimeType -> return . ORelTime+ ComplexType -> return . OComplex . flip complex 0 . fromRational . toRational+ _ -> \ _ -> mzero++instance ObjectClass Double where+ obj = OFloat+ fromObj o = case o of { OFloat o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (o==0) >> return ONull+ TrueType -> \o -> return $ if o==0 then ONull else OTrue+ CharType -> fmap OChar . charFromIntegral . (round :: Double -> Int)+ IntType -> return . OInt . round+ WordType -> return . OWord . round+ LongType -> return . OLong . round+ FloatType -> return . OFloat+ RatioType -> return . ORatio . toRational+ ComplexType -> return . OComplex . flip complex 0 . fromRational . toRational+ DiffTimeType -> return . ORelTime . fromRational . toRational+ StringType -> return . obj . show+ BytesType -> return . OBytes . D.encode+ _ -> \ _ -> mzero++instance ObjectClass Rational where+ obj = ORatio+ fromObj o = case o of { ORatio o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (o==0) >> return ONull+ TrueType -> \o -> return $ if o==0 then ONull else OTrue+ CharType -> fmap OChar . charFromIntegral . (round :: Rational -> Int)+ IntType -> return . OInt . round+ WordType -> return . OWord . round+ LongType -> return . OLong . round+ FloatType -> return . OFloat . fromRational+ RatioType -> return . ORatio+ ComplexType -> return . OComplex . flip complex 0 . fromRational+ DiffTimeType -> return . ORelTime . fromRational . toRational+ StringType -> return . obj . prettyShow . obj+ _ -> \ _ -> mzero++instance ObjectClass Complex where+ obj = OComplex+ fromObj o = case o of { OComplex o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> \o -> guard (complex 0 0 == o) >> return ONull+ TrueType -> \o -> return $ if complex 0 0 == o then ONull else OTrue+ IntType -> i OInt+ WordType -> return . OWord . round . magnitude+ LongType -> i OLong+ FloatType -> f OFloat+ RatioType -> f ORatio+ ComplexType -> return . OComplex+ DiffTimeType -> f ORelTime+ StringType -> return . obj . prettyShow+ _ -> \ _ -> mzero+ where+ f constr o = guard (imagPart o == 0) >> return (constr $ fromRational $ toRational $ realPart o)+ i constr = f (constr . fromInteger . (round :: Rational -> Integer))++instance ObjectClass UStr where+ obj = OString+ fromObj o = case o of { OString o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ StringType -> return . OString+ _ -> castToCoreType t . uchars++instance ObjectClass String where+ obj = obj . toUStr+ fromObj = fromObj >=> maybeFromUStr+ castToCoreType t = case t of+ NullType -> \o -> guard (o=="null") >> return ONull+ TrueType -> \o -> case map toLower o of+ "true" -> return OTrue+ "yes" -> return OTrue+ "no" -> return ONull+ "false" -> return ONull+ "null" -> return ONull+ _ -> mzero+ IntType -> pars OInt+ WordType -> pars OWord+ LongType -> pars OLong+ FloatType -> pars OFloat+ TimeType -> pars OAbsTime+ StringType -> return . OString . ustr+ RefType -> pars ORef+ _ -> \ _ -> mzero+ where+ nospc = dropWhile isSpace+ pars f str = case fmap (reverse . nospc . reverse) <$> readsPrec 0 (nospc str) of+ [(o, "")] -> return (f o)+ _ -> mzero++instance ObjectClass B.ByteString where+ obj = OBytes+ fromObj o = case o of { OBytes o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ NullType -> f (D.isEmpty >>= guard >> return ONull)+ TrueType ->+ f (D.getWord8 >>= \w ->+ return $ case w of { 0->Just ONull; 1->Just OTrue; _->mzero; }) >=> xmaybe+ CharType -> fmap OChar . (f D.getWord64le >=> charFromIntegral)+ IntType -> fmap (OInt . fromIntegral) . f D.getWord64le+ WordType -> fmap (OWord . fromIntegral) . f D.getWord64le+ LongType -> return . OLong . D.decode . B.reverse+ FloatType -> return . OFloat . D.decode+ _ -> \ _ -> mzero+ where+ f :: D.Get o -> B.ByteString -> XPure o+ f get = return . D.runGet get++instance ObjectClass [Object] where+ obj = OList+ fromObj o = case o of { OList o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ StringType -> fmap OList . loop return+ BytesType -> fmap (OBytes . D.runPut . mapM_ D.putLazyByteString) . loop (\ (OBytes o) -> [o])+ ListType -> return . OList+ _ -> \ _ -> mzero+ where+ loop f = fmap concat .+ mapM (\o -> (xmaybe (fromObj o) >>= loop f) <|> (f <$> castToCoreType t o))++instance ObjectClass (M.Map Name Object) where+ obj = ODict+ fromObj o = case o of { ODict o -> return o; _ -> mzero; }+ castToCoreType t o = case t of+ NullType -> guard (M.null o) >> return ONull+ DictType -> return $ ODict o+ _ -> mzero++instance ObjectClass Reference where+ obj = ORef+ fromObj o = case o of { ORef o -> return o; _ -> mzero; }+ castToCoreType t o = case t of+ StringType -> return $ obj $ '$':prettyShow o+ RefType -> return (ORef o)+ _ -> mzero++instance ObjectClass Name where+ obj n = ORef $ Reference UNQUAL n NullRef+ fromObj o = case o of+ OString o -> maybeFromUStr o+ ORef (Reference UNQUAL name NullRef) -> return name+ _ -> mzero++instance ObjectClass ObjType where+ obj = OType+ fromObj o = case o of { OType o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ TypeType -> return . OType+ StringType -> return . obj . prettyShow+ _ -> \ _ -> mzero++instance ObjectClass CoreType where+ obj = OType . objTypeFromCoreType+ fromObj o = case o of+ OType (ObjType [TypeStruct [CoreType o]]) -> return o+ _ -> mzero+ castToCoreType t = case t of+ IntType -> return . OInt . fromIntegral . fromEnum+ WordType -> return . OWord . fromIntegral . fromEnum+ LongType -> return . OLong . fromIntegral . fromEnum+ StringType -> return . obj . show+ TypeType -> return . obj+ _ -> \ _ -> mzero++instance ObjectClass Struct where+ obj = OTree+ fromObj o = case o of { OTree o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ TreeType -> return . OTree+ RefType -> return . obj . structName+ StringType -> return . obj . prettyShow+ _ -> \ _ -> mzero++instance ObjectClass UTCTime where+ obj = OAbsTime+ fromObj o = case o of { OAbsTime o -> return o; _ -> mzero; }+ castToCoreType t = case t of+ StringType -> return . obj . prettyShow . obj+ TimeType -> return . OAbsTime+ _ -> \ _ -> mzero++instance ObjectClass Hata where+ obj = OHaskell+ fromObj o = case o of { OHaskell o -> return o; _ -> mzero; }++instance ObjectClass Dynamic where+ obj = opaque+ fromObj o = case o of { OHaskell (Hata _ o) -> return o; _ -> mzero; }++instance ObjectClass Object where+ obj = id;+ fromObj = return;+ castToCoreType t o = case o of+ ONull -> f False+ OTrue -> f True+ OChar o -> f o+ OInt o -> f o+ OWord o -> f o+ OLong o -> f o+ OAbsTime o -> f o+ OFloat o -> f o+ ORatio o -> f o+ OComplex o -> f o+ OString o -> f o+ OBytes o -> f o+ OList o -> f o+ ODict o -> f o+ ORef o -> f o+ OType o -> f o+ OTree o -> f o+ ORelTime o -> f o+ OHaskell _ -> mzero+ where+ f :: ObjectClass o => o -> XPure Object+ f = castToCoreType t++instance ObjectClass Location where { obj=new; fromObj=objFromHata; }++instance ObjectClass Comment where { obj=new; fromObj=objFromHata; }++instance ObjectClass [Comment] where { obj=listToObj; fromObj=listFromObj; }++instance ObjectClass DotNameExpr where { obj=new; fromObj=objFromHata; }++instance ObjectClass AST_DotName where { obj=new; fromObj=objFromHata; }++instance ObjectClass DotLabelExpr where { obj=new; fromObj=objFromHata; }++instance ObjectClass AST_DotLabel where { obj=new; fromObj=objFromHata; }++listToObj :: ObjectClass o => [o] -> Object+listToObj = OList . map obj++listFromObj :: ObjectClass o => Object -> Maybe [o]+listFromObj o = case o of+ OList o -> mapM fromObj o+ _ -> mzero++-- | Create a new 'Object' containing the original value and a reference to the 'Interface'+-- retrieved by the instance of 'haskellDataInterface' for the data type.+new :: (HataClass typ, Typeable typ) => typ -> Object+new = OHaskell . toHata++-- | Create a completely opaque haskell data type that can be used stored to a Dao language+-- variable, but never inspected or modified in any way.+opaque :: forall typ . Typeable typ => typ -> Object+opaque o = OHaskell $ flip Hata (toDyn o) $+ interfaceToDynamic (interface (show $ typeOf o) (return ()) :: Interface typ)++-- | The inverse operation of 'new', uses 'fromObj' and 'fromHata' to extract the data type+-- wrapped up in the 'Object', assuming the 'Object' is the 'OHaskell' constructor holding a+-- 'Hata' container.+objFromHata :: (Typeable o, HataClass o) => Object -> Maybe o+objFromHata = fromObj >=> fromHata++----------------------------------------------------------------------------------------------------++-- | This is the "Haskell Data" data type used to wrap-up a Haskell data types into a+-- 'Data.Dynamic.Dynamic' data type and associate this dynamic data with the 'Interface' used by the+-- runtime to read and modify the data. Whenever an non-primitive 'Object' is created, the data is+-- converted to a 'Data.Dynamic.Dynamic' value and paired with a copy of the 'Interface'.+data Hata = Hata (Interface Dynamic) Dynamic deriving Typeable++instance Eq Hata where+ Hata ifcA a == Hata ifcB b =+ ((ifcA==ifcB)&&) $ maybe False id $ objEquality ifcA >>= \eq -> return (eq a b)++instance Ord Hata where+ compare (Hata ifcA a) (Hata ifcB b) = maybe err id $+ guard (ifcA==ifcB) >> objOrdering ifcA >>= \comp -> return (comp a b) where+ err = error $ unwords $+ [ "cannot compare object of type", show (objHaskellType ifcA)+ , "with obejct of type", show (objHaskellType ifcB)+ ]++instance Show Hata where { show (Hata o _) = show (objHaskellType o) }++instance NFData Hata where { rnf (Hata _ _) = () }++instance PPrintable Object where+ pPrint o = case o of+ ONull -> pString "null"+ OTrue -> pString "true"+ OChar o -> pShow o+ OInt o -> pShow o+ OWord o -> pString (show o++"U")+ OLong o -> pString (show o++"L")+ ORelTime o -> pShow o+ OFloat o -> pString (show o++"f")+ ORatio o ->+ if denominator o == 1+ then pString (show (numerator o)++"R")+ else pWrapIndent $+ [ pString "(", pString (show (numerator o)), pString "/"+ , pString (show (denominator o)++"R"), pString ")"+ ]+ OComplex o -> pPrint o+ OString o -> pShow o+ OBytes o ->+ if B.null o+ then pString "data{}"+ else pList (pString "data") "{" ", " "}" (map (pString . showHex) (B.unpack o))+ OList ox -> if null ox then pString "list{}" else pContainer "list " pPrint ox+ ODict o ->+ if M.null o+ then pString "dict{}"+ else pContainer "dict " (\ (a, b) -> pWrapIndent [pPrint a, pString " = ", pPrint b]) (M.assocs o)+ ORef o -> pPrint o+ OType o -> pPrint o+ OTree o -> pPrint o+ OAbsTime o -> pString ("date "++show o)+ OHaskell (Hata ifc o) -> case objPPrinter ifc of+ Nothing -> fail $ "cannot pretty print Haskell data type: "++show (objHaskellType ifc)+ Just pp -> pp o++instance B.Binary Hata MTab where+ put (Hata ifc o) = do+ let typeName = objInterfaceName ifc + mtab <- B.getCoderTable+ case B.getEncoderForType typeName mtab of+ Just fn -> do+ tid <- B.newInStreamID typeName+ B.put tid >> B.putWithBlockStream1M (fn o)+ Nothing -> fail $ unwords ["no binary format method defied for Haskell type", uchars (toUStr typeName)]+ get = do+ B.updateTypes+ mtab <- B.getCoderTable+ tid <- B.get >>= B.decodeIndexLookup+ maybe mzero id $ do+ tid <- tid+ fn <- B.getDecoderForType tid mtab+ tab <- lookupMethodTable tid mtab+ return (Hata tab <$> B.getWithBlockStream1M fn)++instance HasNullValue Hata where+ nullValue = toHata ()+ testNull (Hata ifc o) = case objNullTest ifc of+ Nothing -> error ("to check whether objects of type "++show (objHaskellType ifc)++" are null is undefined behavior")+ Just fn -> fn o++-- | This is a convenience function for calling 'OHaskell' using just an initial value of type+-- @typ@. The 'Interface' is retrieved automatically using the instance of 'haskellDataInterface' for+-- the @typ@.+toHata :: (HataClass typ, Typeable typ) => typ -> Hata+toHata t = flip Hata (toDyn t) (interfaceTo t haskellDataInterface) where+ interfaceTo :: Typeable typ => typ -> Interface typ -> Interface Dynamic+ interfaceTo _ ifc = interfaceToDynamic ifc++-- | Inverse operation of 'toHata', useful when instantiating 'ObjectClass', uses+-- 'Data.Dynamic.fromDynamic' to extract the value that has been wrapped in up the 'Hata'+-- constructor.+fromHata :: (HataClass typ, Typeable typ) => Hata -> Maybe typ+fromHata (Hata _ o) = fromDynamic o++----------------------------------------------------------------------------------------------------++class Sizeable o where { getSizeOf :: o -> Exec Object }++instance Sizeable Char where { getSizeOf = return . obj . ord }+instance Sizeable Word64 where { getSizeOf = return . obj }+instance Sizeable Int where { getSizeOf = return . obj . abs }+instance Sizeable Double where { getSizeOf = return . obj . abs }+instance Sizeable Integer where { getSizeOf = return . obj . abs }+instance Sizeable NominalDiffTime where { getSizeOf = return . obj . abs }+instance Sizeable Rational where { getSizeOf = return . obj . abs }+instance Sizeable Complex where { getSizeOf = return . obj . magnitude }+instance Sizeable UStr where { getSizeOf = return . obj . ulength }+instance Sizeable [Object] where { getSizeOf = return . obj . length }+instance Sizeable (M.Map Name Object) where { getSizeOf = return . obj . M.size }+instance Sizeable (H.HashMap Object Object) where { getSizeOf = return . obj . H.size }+instance Sizeable Hata where { getSizeOf (Hata ifc o) = maybe mzero ($ o) (objSizer ifc) }++instance Sizeable Object where+ getSizeOf o = case o of+ OChar o -> getSizeOf o+ OWord o -> getSizeOf o+ OInt o -> getSizeOf o+ OLong o -> getSizeOf o+ ORelTime o -> getSizeOf o+ OFloat o -> getSizeOf o+ ORatio o -> getSizeOf o+ OComplex o -> getSizeOf o+ OString o -> getSizeOf o+ OList o -> getSizeOf o+ ODict o -> getSizeOf o+ OHaskell o -> getSizeOf o+ _ -> mzero++----------------------------------------------------------------------------------------------------++-- $Building_structs+-- Here are all the basic functions for converting between Haskell language data types and Dao+-- language structures.+-- +-- Most 'FromDaoStruct' functions will backtrack when they fail to get the necessary data. This+-- function can make a backtracking function fail. For example:+-- > 'tryField' "x" >>= 'objType'+-- backtracks in any case+-- +-- > required (tryField "x" >>= objType)+-- > tryField "x" >>= required objType+-- These two forms do the same thing: fails if 'objType' backtracks, but not if the field doesn't+-- exist.+-- +-- > 'Control.Applicative.optional' ('tryField' "x" >>= 'objType')+-- returns 'Prelude.Nothing' if the field does not exist or if 'objType' backtracks+-- +-- > 'field' "x" >>= 'objType'+-- fails if the field does not exist, backtracks if it exists but is the wrong type+-- (you probably don't ever want to do this).+-- +-- > 'required' ('field' "x" >>= 'objType')+-- > 'field' "x" >>= 'required' 'objType'+-- These two forms are the same: fails if either the field does not exist or if 'objType'+-- backtracks.++-- | This is the data type used as the intermediary between Haskell objects and Dao objects. If you+-- would like your Haskell data type to be used as a non-opaque data type in a Dao language script,+-- the first step is to instantiate your data type into this class. The next step would be to+-- instantiate your object into the 'HataClass' class. Instantiating the+-- 'HataClass' class alone will make your object usable in Dao language scripts, but+-- it will be an opaque type. Instantiating 'Struct' and declaring 'autoDefStruct' in the+-- 'defObjectInterface' will allow functions in the Dao language script to read and write+-- information to your data structure, modifying it during runtime.+-- +-- 'Struct' values are used lazily, so your data types will only be converted to and from 'Struct's+-- when absolutely necessary. This helps to conserver memory usage.+data Struct+ = Nullary{ structName :: Name }+ -- ^ models a constructor with no fields, for example 'Prelude.EQ', 'Prelude.GT' and+ -- 'Prelude.LT'.+ | Struct+ { structName :: Name -- ^ provide the name for this constructor.+ , fieldMap :: M.Map Name Object+ }+ deriving (Eq, Ord, Show, Typeable)++structLookup :: Name -> Struct -> Maybe Object+structLookup name struct = case struct of+ Nullary{} -> Nothing+ Struct{ fieldMap=m } -> M.lookup name m++instance NFData Struct where+ rnf (Nullary a ) = deepseq a ()+ rnf (Struct a b) = deepseq a $! deepseq b ()++instance HasNullValue Struct where+ nullValue = Nullary{ structName=ustr "NULL" }+ testNull (Nullary{ structName=name }) = name == ustr "NULL"+ testNull _ = False++-- binary 0x25 0x26+instance B.Binary Struct MTab where+ put o = case o of+ Nullary o -> B.putWord8 0x25 >> B.put o+ Struct n o -> B.putWord8 0x26 >> B.put n >> B.put o+ get = B.word8PrefixTable <|> fail "expecting Struct"++instance B.HasPrefixTable Struct B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "Struct" 0x25 0x26 $+ [ Nullary <$> B.get+ , return Struct <*> B.get <*> B.get+ ]++instance PPrintable Struct where+ pPrint o = case o of+ Nullary{ structName=name } -> pString ('#' : uchars (toUStr name))+ Struct{ structName=name, fieldMap=dict } ->+ pList (pString ('#' : uchars (toUStr name))) "{" ", " "}" $+ flip map (M.assocs dict) $ \ (left, right) -> pInline $+ [pPrint left, pString " = ", pPrint right]++instance HasRandGen Struct where+ randO = _randTrace "Struct" $ countNode $ runRandChoice + randChoice = randChoiceList $+ [ scramble $+ return Struct <*> randO <*> (M.fromList <$> randListOf 1 4 (pure (,) <*> randO <*> randO))+ , Nullary <$> randO+ ]+ defaultO = _randTrace "D.Struct" $ Nullary <$> defaultO++instance ToDaoStructClass Struct where { toDaoStruct = return () }++instance FromDaoStructClass Struct where { fromDaoStruct = FromDaoStruct $ lift get }++instance HataClass Struct where+ haskellDataInterface = interface "Struct" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++-- | You can make your data type readable but not writable in the Dao runtime. That means a Dao+-- script can inspect elements of your data type, but not modify them As an example lets say you+-- have a 3D-point data type you would like to use in your Dao script.+-- > data Finite =+-- > Point2D{ get_x::'T_float', get_y::'T_float' }+-- > | Point3D{ get_x::'T_float', get_y::'T_float', get_z::'T_float' }+-- +-- Lets say you have already instantiated the 'HataClass' class and provided the Dao runtime with+-- a 'DaoFunc' (via 'setupDao') that constructs a Point3D at runtime:+-- > p = Point3D(1.9, 4.4, -2.1);+-- Now you would like to extend the 'HataClass' of your Point3D to also be readable at runtime.+-- If you instantiate 'ToDaoStructClass' your Dao language script could also read elements from the+-- point like so:+-- > distFromOrigin = sqrt(p.x*p.x + p.y*p.y + p.z*p.z);+-- However you cannot modify the point unless you also instantiate 'FromDaoStructClass'. So a statement+-- like this would result in an error:+-- > p.x /= distFromOrigin;+-- > p.y /= distFromOrigin;+-- > p.z /= distFromOrigin;+-- +-- You can convert this to a 'Struct' type using the 'fromData' function. There are many ways to+-- define fields in a 'Struct', here are a few:+-- > instance 'ToDaoStructClass' Point3D 'Object' where+-- > 'toDaoStruct' = 'fromData' "@Point2D@" $ do+-- > 'putPrimField' "x" get_x+-- > 'putPrimField' "y" get_y+-- > obj <- 'Control.Monad.Reader.Class.ask'+-- > case obj of+-- > Point3D _ _ z -> 'renameConstructor' "@Point3D@" $ do+-- > 'define' "z" ('obj' z)+-- > _ -> return ()+-- +-- Finally, you should define the instantiation of Point3D into the 'HataClass' class so it+-- includes the directive 'autoDefToStruct'.+class ToDaoStructClass haskData where { toDaoStruct :: ToDaoStruct haskData () }++-- | Continuing the example from above, if you do want your data type to be modifyable by functions+-- running in the Dao language runtime, you must instantiate this class, which is facilitated by the+-- 'toData' function.+-- > instance 'FromDaoStructClass' 'Point3D' where+-- > fromDaoStruct = 'toData' $ 'Control.Monad.msum' $+-- > [ do 'constructor' "@Point2D@"+-- > return Point3D 'Control.Applicative.<*>' 'req' "x" 'Control.Applicative.<*>' 'req' "y"+-- > , do 'constructor' "@Point3D@"+-- > return Point3D 'Control.Applicative.<*>' 'req' "x" 'Control.Applicative.<*>' 'req' "y" 'Control.Applicative.<*>' 'req' "z"+-- > ]+-- +-- Do not forget to define the instantiation of Point3D into the 'HataClass' class so it+-- includes the directive 'autoDefFromStruct'.+-- +-- Note that an instance of 'FromDaoStructClass' must also instantiate 'ToDaoStructClass'. I can see no+-- use for objects that are only writable, that is they can be created at runtime but never+-- inspected at runtime.+class ToDaoStructClass haskData => FromDaoStructClass haskData where+ fromDaoStruct :: FromDaoStruct haskData++-- | If there is ever an error converting to or from your Haskell data type, you can+-- 'Control.Monad.Error.throwError' a 'StructError'.+data StructError+ = StructError+ { structErrName :: Maybe UStr+ , structErrField :: Maybe UStr+ , structErrValue :: Maybe Object+ , structErrExtras :: [Name]+ }+ deriving (Eq, Ord, Typeable)++instance PPrintable StructError where+ pPrint err = do+ let pp p msg f = case f err of+ Nothing -> return ()+ Just o -> pString (msg++": ") >> p o >> pNewLine+ pp pUStr "on constructor" structErrName+ pp pUStr "on field" structErrField+ pp pPrint "with value" structErrValue+ let extras = structErrExtras err+ if null extras then return () else pString ("non-member fields: "++show extras)++instance HasNullValue StructError where+ nullValue =+ StructError+ { structErrName=Nothing+ , structErrField=Nothing+ , structErrValue=Nothing+ , structErrExtras=[]+ }+ testNull+ ( StructError+ { structErrName=Nothing+ , structErrField=Nothing+ , structErrValue=Nothing+ , structErrExtras=[]+ }+ ) = True+ testNull _ = False++pPrintStructForm :: ToDaoStructClass o => o -> PPrint+pPrintStructForm o = case fromData toDaoStruct o of+ PFail err -> pPrint err+ Backtrack -> pString "(### FAILED TO CONVERT OBJECT TO STRUCT ###)"+ OK struct -> pPrint struct++----------------------------------------------------------------------------------------------------++-- Used to instantiate 'MonadError.throwError' by both the 'ToDaoStruct' and 'FromDaoStruct' monads.+_structThrowError+ :: (MonadError ExecControl m)+ => (forall a . PredicateT ExecControl (State st) a -> m a) -> (st -> Struct) -> ExecControl -> m b+_structThrowError constr inside err =+ constr (lift $ gets inside) >>= \struct -> constr $ throwError $ case err of+ ExecError{execErrorSubtype = ExecStructError info} ->+ err { execErrorSubtype = ExecStructError $+ info{ structErrName = structErrName info <|> Just (toUStr $ structName struct) } }+ err -> err++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass StructError where+ toDaoStruct = renameConstructor "StructError" $ do+ asks structErrName >>= optionalField "structName" . fmap OString+ asks structErrField >>= optionalField "field" . fmap OString+ asks structErrValue >>= optionalField "value"+ asks structErrExtras >>= optionalField "extras" . fmap obj . refNames++instance ToDaoStructClass (ParseError () DaoTT) where+ toDaoStruct = renameConstructor "ParseError" $ do+ asks parseErrMsg >>= ("message" .=?)+ asks parseErrMsg >>= ("onToken" .=?) . fmap show+ asks parseErrLoc >>= putLocation++instance FromDaoStructClass StructError where+ fromDaoStruct = do+ constructor "StructError"+ let str o = case o of+ OString o -> return o+ _ -> fail "expecting string value"+ let ref o = case o of+ ORef o -> case o of+ Reference UNQUAL o NullRef -> return o+ _ -> fail "not an unqualified reference singleton"+ _ -> fail "not a reference type"+ let lst o = case o of+ OList o -> forM o ref+ _ -> fail "expecting list value"+ return StructError+ <*> optional (tryField "structName" $ str)+ <*> optional (tryField "field" $ str)+ <*> optional (tryField "value" return)+ <*> (tryField "extras" $ lst)++instance ToDaoStructClass Comment where+ toDaoStruct = ask >>= \co -> case co of+ InlineComment o -> renameConstructor "InlineComment" $ "comment" .= o+ EndlineComment o -> renameConstructor "EndlineComment" $ "comment" .= o++instance FromDaoStructClass Comment where+ fromDaoStruct = msum $+ [ constructor "InlineComment" >> InlineComment <$> req "comment"+ , constructor "EndlineComment" >> EndlineComment <$> req "comment"+ ]++instance ToDaoStructClass AST_DotName where+ toDaoStruct = renameConstructor "DotName" $ ask >>= \ (AST_DotName coms n) -> case coms of+ Com () -> "name" .= n+ coms -> "comments" .= coms >> "name" .= n++instance FromDaoStructClass AST_DotName where+ fromDaoStruct = constructor "DotName" >>+ return AST_DotName <*> (maybe (Com ()) id <$> opt "comments") <*> req "name"++instance ToDaoStructClass AST_DotLabel where+ toDaoStruct = renameConstructor "DotLabel" $ do+ (AST_DotLabel n nx loc) <- ask+ "head" .= n+ "tail" .= OList (map obj nx)+ putLocation loc++instance FromDaoStructClass AST_DotLabel where+ fromDaoStruct = do+ constructor "DotLabel"+ let convert o = case sequence (map fromObj o) of+ Nothing -> fail "\"tail\" field must contain a list of \"#DotName\" data types."+ Just ox -> return ox+ return AST_DotLabel <*> req "head" <*> (req "tail" >>= convert) <*> location++instance ObjectClass StructError where { obj=new; fromObj=objFromHata; }++instance HataClass StructError where+ haskellDataInterface = interface "StructError" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest+ autoDefToStruct >> autoDefFromStruct++-- | Used to convert a 'Prelude.String' to a 'Dao.String.Name' by functions like 'define' and+-- 'setField'. Usually you will not need to use it.+mkLabel :: (UStrType name, MonadPlus m) => name -> m Name+mkLabel name = xmaybe $ maybeFromUStr (toUStr name)++mkStructName :: (UStrType name, MonadPlus m) => name -> m Name+mkStructName name = mplus (mkLabel name) $ fail "invalid constructor name"++mkFieldName :: (UStrType name, MonadPlus m) => name -> m Name+mkFieldName name = mplus (mkLabel name) $ fail "invalid field name"++-- | This is a handy monadic and 'Data.Functor.Applicative' interface for instantiating+-- 'toDaoStruct' in the 'ToDaoStructClass' class.+newtype ToDaoStruct haskData a+ = ToDaoStruct+ { _runToDaoStruct :: PredicateT ExecControl (State (Struct, haskData)) a }+ deriving (Functor, Applicative, Alternative, MonadPlus)++instance Monad (ToDaoStruct haskData) where+ return = ToDaoStruct . return+ m >>= f = ToDaoStruct $ _runToDaoStruct m >>= _runToDaoStruct . f+ fail msg = execThrow msg (ExecStructError nullValue) []++instance MonadState Struct (ToDaoStruct haskData) where+ state f = ToDaoStruct $ lift $ state $ \ (struct, haskData) ->+ let (a, struct') = f struct in (a, (struct', haskData))++instance MonadReader haskData (ToDaoStruct haskData) where+ ask = ToDaoStruct $ lift $ fmap snd get+ local upd f = ToDaoStruct $ PredicateT $ do+ haskData <- gets snd+ modify (\ (struct, _) -> (struct, upd haskData))+ a <- runPredicateT $ _runToDaoStruct f+ modify (\ (struct, _) -> (struct, haskData))+ return a++instance MonadError ExecControl (ToDaoStruct haskData) where+ throwError = _structThrowError ToDaoStruct fst+ catchError f catch = ToDaoStruct $ catchError (_runToDaoStruct f) (_runToDaoStruct . catch)++instance MonadPlusError ExecControl (ToDaoStruct haskData) where+ catchPredicate = ToDaoStruct . catchPredicate . _runToDaoStruct+ predicate = ToDaoStruct . predicate++-- | This function is typically used to evaluate the instantiation of 'toDaoStruct'. It takes two+-- parameters: first a computation to convert your data type to the 'Struct' using the 'ToDaoStruct'+-- monad, and second the data type you want to convert. You can use functions like 'defineWith' and+-- 'setField' to build your 'ToDaoStruct' computation. For example, lets say you have a Haskell data+-- type called @mydat::MyData@ where @MyData@ instantiates 'ToDaoStruct', you can convert it to a+-- Dao 'Struct' like so:+-- > 'fromData' 'toDaoStruct' mydat+-- Notice how it reads similar to ordinary English, "convert from (Haskell) data to a Dao 'Struct'"+fromData+ :: ToDaoStruct haskData x+ -> haskData+ -> Predicate ExecControl Struct+fromData pred hask = evalState (runPredicateT $ _runToDaoStruct $ pred >> get) $+ (Struct{ structName=nil, fieldMap=M.empty }, hask)++toDaoStructExec :: ToDaoStruct typ x -> typ -> Exec Struct+toDaoStructExec toDaoStruct = (predicate :: Predicate ExecControl T_struct -> Exec T_struct) .+ fmapPFail ((\o -> newError{ execReturnValue=Just o}) . new) . fromData toDaoStruct++-- | Overwrite the current 'Struct' with a 'Struct' produced by a 'toDaoStruct' instance of a+-- different type. This is useful when instantiating a newtype or a data type constructor that+-- contains only one item (the "inner" item), and the data type of the inner item instantiates+-- 'ToDaoStructClass', you can simply use the instance of 'toDaoStruct' for that data type to+-- instantiate 'toDaoStruct' for the outer data type. Just be sure that the constructor name for the+-- inner type does not conflict with the constructor name for the outer data type. For example:+-- > data X = X1 { ... } | X2 { ... }+-- > instance 'DaoToStructClass' X 'Object' where { ... }+-- > data Y = Y1 { ... } | Y2 { ... }+-- > instance 'DaoToStructClass' Y 'Object' where { ... }+-- > +-- > newtype WrapX = WrapX { unwrapX :: X }+-- > instance 'DaoToStructClass' WrapX 'Object' where+-- > 'toDaoStruct' = 'Control.Monad.Reader.ask' >>= 'innerToStruct' . unwrapX+-- > +-- > data X_or_Y = Is_X { getX :: X } | Is_Y { getY :: Y }+-- > instance 'DaoToStructClass' X_or_Y 'Object' where+-- > 'toDaoStruct' = 'Control.Monad.Reader.ask' >>= \xy -> case xy of+-- > Is_X x -> 'innerToStruct' x+-- > Is_Y y -> 'innerToStruct' y+-- +-- The inverse of this operation in the 'FromDaoStructClass' is 'Prelude.fmap', or equivalently the+-- 'Control.Applicative.<$>' operator. Here is an example using 'Control.Applicative.<$>':+-- > instance 'FromDaoStructClass' WrapX 'Object' where+-- > 'fromDaoStruct' = WrapX <$> 'fromDaoStruct'+-- > +-- > instance 'FromDaoStructClass' X_or_Y 'Object' where+-- > 'fromDaoStruct' = Is_X <$> 'fromDaoStruct' <|> Is_Y <$> 'fromDaoStruct'+-- +-- Another way to do exactly the same thing as the example above is:+-- > instance 'FromDaoStructClass' WrapX 'Object' where+-- > 'fromDaoStruct' = 'Prelude.fmap' WrapX 'fromDaoStruct'+-- > +-- > instance 'FromDaoStructClass' X_or_Y 'Object' where+-- > 'fromDaoStruct' = 'Prelude.fmap' Is_X 'fromDaoStruct' `'Control.Monad.mplus'` 'Prelude.fmap' Is_Y 'fromDaoStruct'+-- +-- It is possible to use 'renameConstructor' after evaluating 'innerToStruct' to use a different+-- constructor name while keeping all of the fields set by the evaluation of 'innerToStruct',+-- however if this is done, 'Prelude.fmap' will backtrack, so you should use 'innerFromStruct'+-- instead.+innerToStruct :: ToDaoStructClass inner => inner -> ToDaoStruct haskData ()+innerToStruct = innerToStructWith toDaoStruct++-- | Like 'innerToStruct' but lets you supply a 'ToDaoStruct' function for an arbitrary data type,+-- not just one that instantiates 'ToDaoStructClass'.+innerToStructWith :: ToDaoStruct inner () -> inner -> ToDaoStruct haskData ()+innerToStructWith toDaoStruct o = ask >>= \haskData ->+ predicate (fromData toDaoStruct o) >>= ToDaoStruct . lift . put . flip (,) haskData++fmapHaskDataToStruct :: (haskData -> dyn) -> (dyn -> haskData) -> ToDaoStruct haskData a -> ToDaoStruct dyn a+fmapHaskDataToStruct to from (ToDaoStruct (PredicateT f)) =+ ToDaoStruct $ PredicateT $ state $ fmap (fmap to) . runState f . fmap from++-- | Use this function to set the 'structName' name of the constructor at some point, for example+-- when you observe some condition of the @haskData@ type that merits an alternative constructor+-- name.+renameConstructor :: UStrType name => name -> ToDaoStruct haskData ig -> ToDaoStruct haskData ()+renameConstructor name f = do+ name <- mkStructName name+ modify $ \struct -> struct{ structName=name }+ void f++-- | Like 'renameConstructor' but deletes everything and makes the 'Struct' being constructed into a+-- 'Nullary'. You would typically do this only when you are instantiating 'toDaoStruct' and you+-- only have one constructor to define.+makeNullary :: UStrType name => name -> ToDaoStruct haskData ()+makeNullary name = mkStructName name >>= \name -> put $ Nullary{ structName=name }++-- | Use this when you have derived the "Prelude.Show" class for a data type where every constructor+-- in that data type takes no parameters, for example, the 'Prelude.Ordering' data type.+putNullaryUsingShow :: Show haskData => ToDaoStruct haskData ()+putNullaryUsingShow = ask >>= makeNullary . show++define :: UStrType name => name -> Object -> ToDaoStruct haskData Object+define name value = do+ name <- mkFieldName name+ modify $ \struct -> struct{ fieldMap = M.insert name value (fieldMap struct) }+ return value++-- | Defines an optional field. If the value given is 'Prelude.Nothing', nothing happens. Otherwise+-- the value is placed into the 'Struct' at the given @name@d field. This is the inverse opreation+-- of using 'Control.Applicative.optional' in the 'FromDaoStruct' monad.+optionalField :: UStrType name => name -> Maybe Object -> ToDaoStruct haskData (Maybe Object)+optionalField name = maybe (return Nothing) (fmap Just . define name)++setField :: UStrType name => name -> (haskData -> Object) -> ToDaoStruct haskData Object+setField name f = ask >>= define name . f++-- | This is an important function for instantiating 'ToDaoStructClass'. It takes any+-- value instantiating 'HataClass', converts it to an 'Object' using the 'new'+-- function. It is the inverse of 'objType'.+--+-- It is recommended you use this function instead of 'defStructField', 'defPrimField', or+-- 'defDynField' whenever it is possible, i.e. whenever the data type you are putting instantiated+-- the 'HataClass' class.+defObjField+ :: (UStrType name, Typeable o, ObjectClass o)+ => name -> o -> ToDaoStruct haskData Object+defObjField name o = define name (obj o)++-- | Synonym for 'defObjField'+(.=) :: (UStrType name, Typeable o, ObjectClass o) => name -> o -> ToDaoStruct haskData Object+(.=) = defObjField+infixr 2 .=++-- | Like 'defObjField' but takes a field accessor to extract the data to be stored from the object+-- being converted. This function is defined as:+-- > \name accessor -> asks accessor >>= defObjField name+putObjField+ :: (UStrType name, Typeable o, ObjectClass o)+ => name -> (haskData -> o) -> ToDaoStruct haskData Object+putObjField name which = asks which >>= defObjField name++-- | Synonym for 'putObjField'+(.=@)+ :: (UStrType name, Typeable o, ObjectClass o)+ => name -> (haskData -> o) -> ToDaoStruct haskData Object+(.=@) = putObjField+infixr 2 .=@++-- | Like 'putObjField' but operates on an object wrapped in a 'Prelude.Maybe', not doing anything+-- in the case of 'Prelude.Nothing'.+defMaybeObjField+ :: (UStrType name, Typeable o, ObjectClass o)+ => name -> Maybe o -> ToDaoStruct haskData (Maybe Object)+defMaybeObjField name = maybe (return Nothing) (fmap Just . defObjField name)++(.=?) + :: (UStrType name, Typeable o, ObjectClass o)+ => name -> Maybe o -> ToDaoStruct haskData (Maybe Object)+(.=?) = defMaybeObjField++----------------------------------------------------------------------------------------------------++-- | This is a handy monadic and 'Data.Functor.Applicative' interface for instantiating+-- 'fromDaoStruct' in the 'FromDaoStructClass' class. It takes the form of a reader because what you+-- /read/ from the 'Struct' here in the Haskell language was /written/ by the Dao language+-- runtime. Think of it as "this is the data type used when the Dao runtime wants to write+-- information to my data structure."+-- +-- Because Dao is such a messy, fuzzy, not statically typed, interpreted language, the information+-- coming in from the Dao runtime requires a lot of sanitization. Therefore this monad provides+-- several functions for checking the type of information you are using to build your Haskell data+-- type.+--+-- Be sure to make ample use of the 'Control.Monad.guard', 'Control.Monad.Error.throwError', and+-- 'Control.Monad.fail' functions.+-- +-- /NOTE:/ refer to the documentation of the 'constructor' monad for an important note on reading+-- Haskell data types with multiple constructors.+newtype FromDaoStruct a =+ FromDaoStruct{ _runFromDaoStruct :: PredicateT ExecControl (State Struct) a }+ deriving (Functor, Applicative, Alternative, MonadPlus)++instance Monad FromDaoStruct where+ return = FromDaoStruct . return+ m >>= f = FromDaoStruct $ _runFromDaoStruct m >>= _runFromDaoStruct . f+ fail msg = FromDaoStruct (lift $ gets structName) >>= \name ->+ execThrow msg (ExecStructError $ nullValue{ structErrName = Just $ toUStr name }) []++instance MonadReader Struct FromDaoStruct where+ ask = FromDaoStruct $ lift get+ local upd f = FromDaoStruct $ PredicateT $ get >>= \st ->+ return $ evalState (runPredicateT $ _runFromDaoStruct f) (upd st)++instance MonadError ExecControl FromDaoStruct where+ throwError = _structThrowError FromDaoStruct id+ catchError (FromDaoStruct f) catch = FromDaoStruct $ catchError f (_runFromDaoStruct . catch)++instance MonadPlusError ExecControl FromDaoStruct where+ catchPredicate = FromDaoStruct . catchPredicate . _runFromDaoStruct+ predicate = FromDaoStruct . predicate++-- | This function is typically used to evaluate the instantiation of 'fromDaoStruct'. It takes two+-- parameters: first a computation to convert your data type to the Haskell data type from a+-- 'Struct' using the 'FromDaoStruct' monad, and second the 'Struct' you want to convert. For+-- example, if you have a Haskell data type 'MyData' which instantiates 'FromDaoStruct', you could+-- construct it from a properly formatted Dao 'Struct' using this statement:+-- > 'toData' 'fromDaoStruct' struct+-- Notice that this reads similar to ordinary English: "convert to (Haskell) data from a dao+-- struct."+toData :: FromDaoStruct haskData -> Struct -> Predicate ExecControl haskData+toData f = evalState (runPredicateT $ _runFromDaoStruct $ f >>= \o -> checkEmpty >> return o)++-- | Using a 'FromDaoStruct' monadic function, convert a given 'Struct' to a Haskell data type+-- @typ@.+withFromDaoStructExec :: FromDaoStruct typ -> Struct -> Exec typ+withFromDaoStructExec fromDaoStruct =+ predicate . fmapPFail ((\o -> newError{ execReturnValue=Just o }) . new) . toData fromDaoStruct++-- | Given a 'Struct', use the 'structName' to lookup a 'FromDaoStruct' monadic function in the+-- current 'ExecUnit' suitable for constructing a 'Hata' Haskell data type.+fromDaoStructExec :: Struct -> Exec Hata+fromDaoStructExec struct = do+ let name = structName struct+ (MethodTable mtab) <- gets globalMethodTable+ let badType msg = execThrow msg (ExecTypeError $ objTypeFromName name) []+ case M.lookup (structName struct) mtab of+ Nothing -> badType "no available built-in data type"+ Just ifc -> case objFromStruct ifc of+ Nothing -> badType "data type cannot be constructed from hashed structure"+ Just from -> Hata ifc <$> withFromDaoStructExec from struct++-- | Checks if the 'structName' is equal to the given name, and if not then backtracks. This is+-- important when constructing Haskell data types with multiple constructors.+--+-- A haskell data type with multiple constructors should be constructed with the+-- 'Control.Monad.msum' function like so:+-- > data MyData = A | B Int | C Int Int+-- > instance 'FromDaoStruct' ('Object) where+-- > 'fromDaoStruct' = 'toData' $ 'Control.Monad.msum' $+-- > [ 'constructor' "A" >> return a,+-- > do 'constructor' "B"+-- > B 'Control.Applicative.<$>' ('field' "b1" >>= 'primType')+-- > do 'constructor' "C"+-- > 'Control.Applicative.return' C+-- > 'Control.Applicative.<*>' 'required' ('field' "c1" >>= 'primType')+-- > 'Control.Applicative.<*>' 'required' ('field' "c2" >>= 'primType')+-- > ]+-- /NOTE/ that if all three 'constructor's backtrack (evaluate to 'Control.Monad.mzero') the whole+-- monad will backtrack. By convention, you should let the monad backtrack, rather than writing a+-- 'Control.Monad.fail' statement as the final item in the 'Control.Monad.msum' list.+constructor :: UStrType name => name -> FromDaoStruct ()+constructor name = (return (==) <*> mkStructName name <*> asks structName) >>= guard++-- | The inverse operation of 'innerToStruct', but looks for a constructor of a different name. This+-- is important because every 'toDaoStruct' should set it's own unique constructor name, and if you+-- set a different constructor name while using the same 'fromDaoStruct' function to read the fields+-- of the struct, the 'fromDaoStruct' function will backtrack seeing the wrong constructor name.+-- If you have not renamed the constructor with 'renameConstructor' after using 'innerToStruct', do+-- not use this function, simply use 'Prelude.fmap' or the 'Control.Applicative.<$>' operator+-- instead.+-- +-- This function temporarily changes the constructor name to the constructor set by the @inner@+-- type, that way the 'fromDaoStruct' instance of the @inner@ type will be fooled and read the+-- 'Struct' fields without backtracking. For example:+-- > newtype X = X{ getX :: Int }+-- > instance 'ToDataStruct' X where+-- > 'toDaoStruct' = do+-- > 'renameConstructor' "X"+-- > "getX" '.=@' getX+-- > +-- > newtype Y = Y{ innerX :: X }+-- > instance 'ToDataStruct' Y where+-- > 'toDaoStruct' = do+-- > -- the 'innerToStruct' function will use the 'toDaoStruct' for X+-- > 'Control.Monad.Reader.ask' >>= 'innerToStruct' . innerX+-- > -- then rename the constructor from "X" to "Y"+-- > 'renameConstructor' "Y"+-- > +-- Now when we want to define the accompanying 'FromDaoStructClass', we need to remember that we+-- used 'innerToStruct' and changed the 'structName' from "X" to "Y". Simply using 'Prelude.fmap'+-- (or equivalently 'Control.Applicative.<$>') will not work because the instance of 'fromDaoStruct'+-- for the @X@ data type will backtrack when it sees the 'structName' is "Y".+-- > instance 'FromDaoStructClass' Y where+-- > 'fromDaoStruct' = Y 'Control.Applicative.<$>' 'fromDaoStruct' -- /WRONG!/ This will always backtrack.+-- +-- The correct way to do it is to use 'innerFromStruct' like so:+-- > instance 'FromDaoStructClass' Y where+-- > 'fromDaoStruct' = Y 'Control.Applicative.<$> 'innerFromStruct' "X" -- CORRECT!+-- +innerFromStruct :: (UStrType name, FromDaoStructClass inner) => name -> FromDaoStruct inner+innerFromStruct tempName = do+ name <- asks structName+ tempName <- mkStructName tempName+ let setname name = FromDaoStruct $ lift $ modify $ \struct -> struct{ structName=name }+ o <- setname tempName >> mplus fromDaoStruct (setname name >> mzero)+ setname name >> return o++-- | Succeeds if the current 'Struct' is a 'Nullary' where the 'structName' is equal to the name+-- given to this function.+nullary :: UStrType name => name -> FromDaoStruct ()+nullary name = ask >>= \struct -> case struct of+ Nullary{} -> constructor name+ _ -> mzero++-- | Use the instantiation of 'Prelude.Read' derived for a type @haskData@ to construct the+-- @haskData from the 'structName' stored in a 'Nullary' 'Struct'.+getNullaryWithRead :: Read haskData => FromDaoStruct haskData+getNullaryWithRead = ask >>= \struct -> case struct of+ Nullary{ structName=name } -> case readsPrec 0 (uchars name) of+ [(haskData, "")] -> return haskData+ _ -> mzero+ _ -> mzero++-- | If an error is thrown using 'Control.Monad.Error.throwError' or 'Control.Monad.fail' within the+-- given 'FromDaoStruct' function, the 'structErrField' will automatically be set to the provided+-- 'Name' value.+structCurrentField :: Name -> FromDaoStruct o -> FromDaoStruct o+structCurrentField name (FromDaoStruct f) = FromDaoStruct $ catchPredicate f >>= \o -> case o of+ PFail (err@(ExecError{execErrorSubtype=ExecStructError info})) -> throwError $ + err{ execErrorSubtype = ExecStructError $ info{ structErrField = Just (toUStr name) } }+ PFail err -> throwError err+ OK o -> return o+ Backtrack -> mzero++-- | Retrieves an arbitrary 'Object' by it's field name, and backtraks if no such field is defined.+-- The value of the field is copied, and can be copied again after this operation. It is best not to+-- use this function, rather use 'tryField' to make sure each field is retrieved exactly once, then+-- use 'checkEmpty' to make sure there is no hidden extraneous data in the struct.+tryCopyField :: UStrType name => name -> (Object -> FromDaoStruct o) -> FromDaoStruct o+tryCopyField name f = (return M.lookup <*> mkFieldName name <*> asks fieldMap) >>=+ xmaybe >>= structCurrentField (fromUStr $ toUStr name) . f++-- | Like 'copyField', retrieves an arbitrary 'Object' by it's field name, and backtraks if no such+-- field is defined. However unlike 'tryCopyField', if the item is retrieved, it is deleted from the+-- inner 'Struct' so that it may not be used again. The reason for this is to use 'checkEmpty' and+-- 'requireEmpty', which can backtrack or fail if there are extraneous fields in the structure.+tryField :: UStrType name => name -> (Object -> FromDaoStruct o) -> FromDaoStruct o+tryField name f = do+ name <- mkFieldName name+ o <- tryCopyField name f+ FromDaoStruct $ lift $ modify $ \st ->+ case st of{ Struct{ fieldMap=m } -> st{ fieldMap=M.delete name m }; s -> s; }+ return o++_throwMissingFieldError :: Name -> FromDaoStruct o+_throwMissingFieldError name = throwError $+ newError{ execErrorSubtype = ExecStructError $ nullValue{ structErrField = Just $ toUStr name } }++-- | Like 'field' but evaluates 'Control.Monad.Error.throwError' if the 'FromDaoStruct' function+-- backtracks or throws it's own error. Internally, this function makes use of 'copyField' and /not/+-- 'tryField', so the field is preserved if it exists.+copyField :: UStrType name => name -> (Object -> FromDaoStruct o) -> FromDaoStruct o+copyField name f = mkFieldName name >>= \name ->+ mplus (tryCopyField name f) (_throwMissingFieldError name)++-- | Like 'field' but evaluates 'Control.Monad.Error.throwError' if the 'FromDaoStruct' function+-- backtracks or throws it's own error. Internally, this function makes use of 'tryField' and /not/+-- 'tryCopyField', so the field is removed if it exists -- two consecutive calls to this function+-- with the same key absolutely will fail.+field :: UStrType name => name -> (Object -> FromDaoStruct o) -> FromDaoStruct o+field name f = mkFieldName name >>= \name -> mplus (tryField name f) (_throwMissingFieldError name)++-- As you make calls to 'field' and 'tryField', the items in these fields in the 'Struct' are+-- being removed. Once you have all of the nata neccessary to construct the data 'Object', you can+-- check to make sure there are no extraneous unused data fields. If the 'Struct' is empty, this+-- function evaluates to @return ()@. If there are extranous fields in the 'Struct', 'throwError' is+-- evaluated. It is highly recommended that this function always be used as the last function+-- evaluated in the 'FromDaoStruct' monadic function.+checkEmpty :: FromDaoStruct ()+checkEmpty = FromDaoStruct (lift get) >>= \st -> case st of+ Struct{ fieldMap=m } -> when (not $ M.null m) $+ execThrow "assigned to non-member fields of structure"+ (ExecStructError $ nullValue{ structErrExtras = M.keys m }) []+ Nullary{} -> return ()++-- | Takes a conversion as the first parameter. The second parameter will be provided by 'field' or+-- 'tryField' when you pass it as a partial function application. If the conversion function+-- backtracks, 'Control.Monad.Error.throwError' is evaluated with the appropriate error data set.+-- This function should usually not be required, as it is called by functions like 'opt', 'req', and+-- 'reqList'.+convertFieldData :: (Object -> FromDaoStruct o) -> Object -> FromDaoStruct o+convertFieldData f o = mplus (f o) $ throwError $+ newError{ execErrorSubtype = ExecStructError $ nullValue{ structErrValue=Just o } }++-- | A required 'Struct' 'field'. This function is defined as+req :: (UStrType name, Typeable o, ObjectClass o) => name -> FromDaoStruct o+req name = field name (convertFieldData (xmaybe . fromObj))++-- | Check if a 'Struct' field exists using 'tryField', if it exists, convert it to the necessary+-- data type using 'fromObj' (which fails if an unexpected type is stored in that field).+opt :: (UStrType name, Typeable o, ObjectClass o) => name -> FromDaoStruct (Maybe o)+opt name = Just <$> tryField name (convertFieldData (xmaybe . fromObj)) <|> return Nothing++-- | Like 'req' but internally uses 'listFromObj' instead of 'fromObj'. The field must exist, if it+-- does not this function evaluates to 'Control.Monad.Error.throwError'. Use 'optList' instead if+-- you can accept an empty list when the field is not defined.+reqList :: (UStrType name, Typeable o, ObjectClass o) => name -> FromDaoStruct [o]+reqList name = field name $ convertFieldData (xmaybe . listFromObj)++-- | Like 'opt' but internally uses 'listFromObj' instead of 'fromObj'. The field may not exist, and+-- if it does not this function returns an empty list. Use 'reqList' to evaluate to+-- 'Control.Monad.Error.throwError' in the case the field does not exist.+optList :: (UStrType name, Typeable o, ObjectClass o) => name -> FromDaoStruct [o]+optList name = tryField name $ convertFieldData (maybe (return []) return . listFromObj)++----------------------------------------------------------------------------------------------------++builtin_toStruct :: DaoFunc ()+builtin_toStruct =+ daoFunc+ { daoForeignFunc = \ () ox -> do+ let qref = reference UNQUAL (ustr "toStruct")+ let wrongTypeErr o = throwBadTypeError "cannot convert to a struct from object of the given type" o []+ case ox of+ [o] -> case o of+ OTree _ -> return (Just o, ())+ OHaskell (Hata ifc d) -> case objToStruct ifc of+ Just to -> flip (,) () . Just . obj <$> toDaoStructExec to d+ Nothing -> wrongTypeErr o+ o -> wrongTypeErr o+ ox -> throwArityError "" 1 ox [(errInFunc, obj qref)]+ }++builtin_fromStruct :: DaoFunc ()+builtin_fromStruct =+ daoFunc+ { daoForeignFunc = \ () ox -> do+ let qref = reference UNQUAL (ustr "fromStruct")+ case ox of+ [o] -> case o of+ OTree o -> flip (,) () . Just . OHaskell <$> fromDaoStructExec o+ o -> throwBadTypeError "argument parameter is not a struct data type" o []+ ox -> throwArityError "" 1 ox [(errInFunc, obj qref)]+ }++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass Location where+ toDaoStruct = ask >>= \lo -> case lo of+ LocationUnknown -> makeNullary "NoLocation"+ Location{} -> renameConstructor "Location" $ do+ "startingLine" .=@ startingLine+ "startingColumn" .=@ startingColumn+ "endingLine" .=@ endingLine+ "endingColumn" .=@ endingColumn++instance FromDaoStructClass Location where+ fromDaoStruct = msum $+ [ nullary "NoLocation" >> return LocationUnknown+ , do constructor "Location"+ return Location+ <*> req "startingLine"+ <*> req "startingColumn"+ <*> req "endingLine"+ <*> req "endingColumn"+ ]++putLocation :: Location -> ToDaoStruct haskData ()+putLocation loc = case loc of+ LocationUnknown -> return ()+ Location{} -> void $ "location" .= loc++location :: FromDaoStruct Location+location = opt "location" >>= maybe (return LocationUnknown) return++putComments :: [Comment] -> ToDaoStruct haskData ()+putComments = void . defObjField "comments"++comments :: FromDaoStruct [Comment]+comments = req "comments"++optComments :: FromDaoStruct (Maybe [Comment])+optComments = opt "comments"++instance HasRandGen Object where+ randO = countNode $ recurse $ runRandChoice+ randChoice = mappend (fmap unlimitObject defaultChoice) $ randChoiceList $+ [ ORef <$> randO+ , depthLimitedInt 24 >>= \x ->+ scramble $ OList <$> randList 0 x+ , depthLimitedInt 24 >>= \x ->+ scramble $ ODict . M.fromList <$> randListOf 0 x (return (,) <*> randO <*> randO)+ , OType <$> randO+ , OTree <$> randO+ , ORatio <$> randO+ , OComplex <$> randO+ ]+ defaultO = _randTrace "D.Object" runDefaultChoice+ defaultChoice = randChoiceList $+ [ do i <- nextInt 10 -- OBytes+ fmap (OBytes . B.concat) $ replicateM i $+ fmap (encode . (\i -> fromIntegral i :: Word32)) randInt+ ]++-- | This is a newtype of 'Object' with a specially defined instance for 'HasRandGen' that+-- guarantees the 'Object' values generated randomly can be pretty-printed an re-parsed back to the+-- exact same value, unambiguously. For example, the instance of 'HasRandGen' for 'LimitedObject'+-- will not produce any values of:+-- > 'Dao.Interpreter.OList' ['Dao.Interpreter.OInt' 1, 'Dao.Interpreter.OInt' 2, 'Dao.Interpreter.OInt' 3]+-- because this will be pretty-printed to "list {1,2,3}" and parsing that pretty printed object will+-- yield the data type:+-- > ('Dao.Interpreter.AST_Init'+-- > ('Dao.Interpreter.AST_DotLabel' ('Dao.String.Name' "list") [] 'Dao.Token.LocationUnknown')+-- > ('Dao.Interpreter.AST_OptObjList' [] 'Prelude.Nothing')+-- > ('Dao.Interpreter.AST_ObjList' []+-- > [ 'Dao.Interpreter.Com' ('Dao.Interpreter.AST_Eval' ('Dao.Interpreter.AST_ObjArith' ('Dao.Interpreter.AST_Object' ('Dao.Interpreter.AST_ObjLiteral' (OInt 1 'Dao.Token.LocationUnknown')))))+-- > , 'Dao.Interpreter.Com' ('Dao.Interpreter.AST_Eval' ('Dao.Interpreter.AST_ObjArith' ('Dao.Interpreter.AST_Object' ('Dao.Interpreter.AST_ObjLiteral' (OInt 2 'Dao.Token.LocationUnknown')))))+-- > , 'Dao.Interpreter.Com' ('Dao.Interpreter.AST_Eval' ('Dao.Interpreter.AST_ObjArith' ('Dao.Interpreter.AST_Object' ('Dao.Interpreter.AST_ObjLiteral' (OInt 3 'Dao.Token.LocationUnknown')))))+-- > ]+-- > )+-- > )+-- Obviously this is a completely different data structure than the data originally randomly+-- generated. If one were to evaluate it using 'Dao.Interpreter.execute', it would evaluate to the+-- originally generated random object value. But for simplicity the test suit does not evaluate+-- anything, it only compares the original randomly generated test object value to the object value+-- that was constructed by parsing the pretty printed form.+--+-- Therefore, the only data structures that should be randomly generated for testing are the data+-- structures that pretty print to a form that can be parsed back to an identical value when+-- compared to the original. This limits the objects that can be generated to simple string and+-- integer literals, hence the name 'LimitedObject'.+newtype LimitedObject = LimitedObject { unlimitObject :: Object } deriving (Eq, Ord, Show)++instance HasNullValue LimitedObject where+ nullValue = LimitedObject nullValue+ testNull (LimitedObject o) = testNull o++instance HasRandGen LimitedObject where+ randO = _randTrace "LimitedObject" $ countNode $ runRandChoice+ randChoice = fmap LimitedObject $ randChoiceList $+ [ return ONull, return OTrue+ , OInt <$> defaultO+ , OWord <$> defaultO+ , OLong <$> defaultO+ , OFloat <$> defaultO+ , OString <$> defaultO+ , OAbsTime <$> defaultO+ , ORelTime <$> defaultO+ , OChar . chr . flip mod (ord(maxBound::Char)) <$> defaultO+ ]+ defaultO = randO++----------------------------------------------------------------------------------------------------++-- | The 'Object' type extends the 'Data.Dynamic.Dynamic' data type with a few more constructors for+-- data types that are fundamental to a programming language, like integers, strings, and lists.+data Object+ = ONull+ | OTrue+ | OChar T_char+ | OInt T_int+ | OWord T_word+ | OLong T_long+ | ORelTime T_diffTime+ | OFloat T_float+ | ORatio T_ratio+ | OComplex T_complex+ | OString T_string+ | OBytes T_bytes+ | OList T_list+ | ODict T_dict+ | ORef T_ref+ | OType T_type+ | OTree T_struct+ | OAbsTime T_time+ | OHaskell Hata+ deriving (Eq, Ord, Typeable, Show)++type T_char = Char+type T_int = Int+type T_word = Word64+type T_long = Integer+type T_diffTime = NominalDiffTime+type T_float = Double+type T_ratio = Rational+type T_complex = Complex+type T_string = UStr+type T_bytes = B.ByteString+type T_list = [Object]+type T_dict = M.Map Name Object+type T_ref = Reference+type T_type = ObjType+type T_struct = Struct+type T_time = UTCTime++instance NFData Object where+ rnf ONull = ()+ rnf OTrue = ()+ rnf (OChar a) = deepseq a ()+ rnf (OInt a) = deepseq a ()+ rnf (OWord a) = deepseq a ()+ rnf (OLong a) = deepseq a ()+ rnf (ORelTime a) = deepseq a ()+ rnf (OFloat a) = deepseq a ()+ rnf (ORatio a) = deepseq a ()+ rnf (OComplex a) = deepseq a ()+ rnf (OString a) = deepseq a ()+ rnf (OBytes a) = seq a ()+ rnf (OList a) = deepseq a ()+ rnf (ODict a) = deepseq a ()+ rnf (ORef a) = deepseq a ()+ rnf (OType a) = deepseq a ()+ rnf (OTree a) = deepseq a ()+ rnf (OAbsTime a) = deepseq a ()+ rnf (OHaskell a) = deepseq a ()++instance Monoid (XPure Object) where+ mempty = return ONull+ mappend a b = a >>= \a -> b >>= \b -> case a of+ ONull -> return b+ OTrue -> case b of+ OTrue -> return OTrue+ _ -> mzero+ a -> case b of+ ONull -> return a+ b -> xpure a + xpure b++instance HasNullValue Object where+ nullValue = ONull+ testNull a = case a of+ ONull -> True+ OChar c -> testNull c+ OInt i -> testNull i+ OWord i -> testNull i+ OLong i -> testNull i+ OFloat f -> testNull f+ ORelTime s -> testNull s+ ORatio r -> testNull r+ OComplex c -> testNull c+ OString s -> testNull s+ OBytes o -> testNull o+ OList s -> testNull s+ ODict m -> testNull m+ OTree t -> testNull t+ OHaskell o -> testNull o+ _ -> False++-- binary 0x08 0x1A Object-->CoreType+instance B.Binary Object MTab where+ put o = do+ let t = B.put (typeOfObj o)+ p o = t >> B.put o+ case o of+ ONull -> t+ OTrue -> t+ OChar o -> p o+ OInt o -> p o+ OWord o -> p o+ OLong o -> p o+ ORelTime o -> p o+ OFloat o -> p o+ ORatio o -> p o+ OComplex o -> p o+ OString o -> p o+ OBytes o -> p o+ OList o -> t >> B.putUnwrapped o+ ODict o -> p o+ ORef o -> p o+ OType o -> p o+ OTree o -> p o+ OAbsTime o -> p o+ OHaskell o -> B.put o+ get = B.word8PrefixTable <|> fail "expecting Object"++instance B.HasPrefixTable Object B.Byte MTab where+ prefixTable =+ let g f = fmap f B.get+ in mappend (OTree <$> B.prefixTable) $ B.mkPrefixTableWord8 "Object" 0x08 0x1A $+ [ return ONull+ , return OTrue+ , g OChar+ , g OInt+ , g OWord+ , g OLong+ , g ORelTime+ , g OFloat+ , g ORatio+ , g OComplex+ , g OString+ , g OBytes+ , OList <$> B.getUnwrapped+ , g ODict+ , g ORef+ , g OType+ , g OTree+ , g OAbsTime+ , mplus (OHaskell <$> B.get)+ (B.get >>= \ (B.BlockStream1M bs1m) -> return (OBytes bs1m))+ ]++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass RefQualifier where { toDaoStruct=putNullaryUsingShow; }++instance FromDaoStructClass RefQualifier where { fromDaoStruct=getNullaryWithRead; }++instance ObjectClass RefQualifier where { obj=new; fromObj=objFromHata; }++instance HataClass RefQualifier where+ haskellDataInterface = interface "RefQualifier" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++data Reference+ = Reference RefQualifier Name RefSuffix+ | RefObject Object RefSuffix+ | RefWrapper Reference+ deriving (Eq, Ord, Typeable, Show)++instance Monoid (XPure Reference) where+ mempty = mzero+ mappend a b = msum $+ [ a >>= \a -> b >>= \b -> case b of+ Reference UNQUAL name suf -> let suf2 = DotRef name suf in case a of+ Reference q name suf1 -> return $ Reference q name (suf1 <> suf2)+ RefObject o suf1 -> return $ RefObject o (suf1 <> suf2)+ RefWrapper a -> return a <> return b+ _ -> execThrow+ "only unqualified references can be appended to other references"+ ExecErrorUntyped [(assertFailed, obj b)]+ , a, b+ ]++instance Read Reference where+ readsPrec _ str = loop [] (sp str) where+ sp = dropWhile isSpace+ loop rx str = do+ (a, str) <- pure (span (\c -> isAlpha c || c=='_') str)+ guard (not $ null a)+ (ax, str) <- pure (span (\c -> isAlphaNum c || c=='_') str)+ ax <- pure (fromUStr $ toUStr $ a++ax)+ str <- pure (sp str)+ case str of+ '.':str -> loop (rx++[ax]) (sp str)+ "" | not $ null rx -> [(Reference UNQUAL (head rx) $ refSuffixFromNames (tail $ rx++[ax]), "")]+ "" -> [(Reference UNQUAL ax NullRef, "")]+ _ -> error $ concat ["a=", show a, "ax=", show ax, "str=", show str]++-- | Construct a 'Reference' with a 'RefQualifier' and a 'Name'.+reference :: RefQualifier -> Name -> Reference+reference q name = Reference q name NullRef++-- | Construct a 'Reference' with an object.+refObject :: Object -> Reference+refObject = flip RefObject NullRef++-- | Strip the 'RefSuffix' from the given 'Reference', changing it 'NullRef' and returning the+-- updated 'Referene' along with the 'RefSuffix' that was removed. If the 'Reference' is a+-- 'RefWrapper', nothing is changed.+referenceHead :: Reference -> (Reference, Maybe RefSuffix)+referenceHead qref = case qref of+ Reference q name suf -> (Reference q name NullRef, Just suf)+ RefObject o suf -> (RefObject o NullRef, Just suf)+ RefWrapper r -> (RefWrapper r , Nothing )++-- | The 'Reference' data type has a 'RefWrapper' constructor which wraps a 'Reference' value,+-- protecting it from being de-referenced. This function unwraps the inner 'Reference' if it is+-- within a 'RefWrapper', or else returns the 'Reference' unchanged.+refUnwrap :: Reference -> Reference+refUnwrap r = case r of { RefWrapper r -> r; r -> r; }++instance NFData Reference where+ rnf (Reference q n r) = deepseq q $! deepseq n $! deepseq r ()+ rnf (RefObject o r ) = deepseq o $! deepseq r ()+ rnf (RefWrapper r ) = deepseq r ()++instance PPrintable Reference where+ pPrint qref = case qref of+ Reference q n r -> case q of+ UNQUAL -> pInline [pPrint n, pPrint r]+ q -> pInline [pPrint q, pString " ", pPrint n, pPrint r]+ RefObject o r -> pInline [pString "(", pPrint o, pString ")", pPrint r]+ RefWrapper r -> pInline [pString "$", pPrint r]++-- binary 0x48 0x4E+instance B.Binary Reference MTab where+ put qref = case qref of+ Reference q n r -> prefix q $ B.put n >> B.put r where+ prefix q = B.prefixByte $ case q of+ { UNQUAL -> 0x48; LOCAL -> 0x49; CONST -> 0x4A; STATIC -> 0x4B; GLOBAL -> 0x4C; GLODOT -> 0x4D; }+ RefObject o r -> B.prefixByte 0x4E $ B.put o >> B.put r+ RefWrapper r -> B.prefixByte 0x4F $ B.put r+ get = B.word8PrefixTable <|> fail "expecting Reference"++instance B.HasPrefixTable Reference B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "Reference" 0x48 0x4F $+ [ f UNQUAL, f LOCAL, f CONST, f STATIC, f GLOBAL, f GLODOT+ , return RefObject <*> B.get <*> B.get+ , return RefWrapper <*> B.get+ ] where { f q = return (Reference q) <*> B.get <*> B.get }++instance HasRandGen Reference where+ randO = _randTrace "Reference" $ recurse $ countNode $ runRandChoice+ randChoice = randChoiceList $+ [ return Reference <*> randO <*> randO <*> randO+ , return RefObject <*> scrambO <*> randO+ , RefWrapper <$> scrambO+ ]+ defaultO = _randTrace "D.Reference" runDefaultChoice+ defaultChoice = randChoiceList $+ [ return Reference <*> defaultO <*> defaultO <*> defaultO+ , return RefObject <*> defaultO <*> defaultO+ ]++-- 'execute'-ing a 'Reference' will dereference it, essentially reading the value associated with+-- that reference from the 'ExecUnit'.+instance Executable Reference (Reference, Maybe Object) where { execute qref = referenceLookup qref }++refAppendSuffix :: Reference -> RefSuffix -> Reference+refAppendSuffix qref appref = case qref of+ Reference q name ref -> Reference q name (ref<>appref)+ RefObject o ref -> RefObject o (ref<>appref)+ RefWrapper qref -> RefWrapper $ refAppendSuffix qref appref++-- | This is an important function used throughout most of the intepreter to lookup 'Object's+-- associated with 'Reference's. It returns a a pair containing updated copy of the given+-- 'Reference' and the 'Object' that was looked-up. The 'Reference' returned is a copy of the+-- 'Reference' parameter given but updated with information about where the reference was looked up.+-- For example, if you pass an 'UNQUAL' (unqualified) reference, it may be looked up in the local,+-- global, or const variable tables. The reference returned will not be 'UNQUAL', it will be either+-- 'GLOBAL', 'LOCAL', or 'CONST', depending on where the 'Object' returned was found.+--+-- If the 'Reference' is a function call, the object returned will be the evaluation of the function+-- call, which may be void (a.k.a. 'Prelude.Nothing').+referenceLookup :: Reference -> Exec (Reference, Maybe Object)+referenceLookup qref = case qref of+ RefWrapper ref -> return $ (qref, Just (obj ref))+ qref -> do+ (a, (qref, _, _)) <- runObjectFocus (updateIndex qref get) True qref ()+ return (qref, a)++refNames :: [Name] -> Maybe Reference+refNames nx = case nx of+ [] -> Nothing+ n:nx -> Just $ Reference UNQUAL n $ refSuffixFromNames nx++referenceFromUStr :: UStr -> Maybe Reference+referenceFromUStr s = breakup [] $ uchars s where+ breakup refs s = case break (=='.') s of+ (n, '.':s) -> breakup (refs++[ustr n]) s+ (n, "" ) -> refNames $ refs++[ustr n]+ _ -> Nothing++fmapReference :: (RefSuffix -> RefSuffix) -> Reference -> Reference+fmapReference fn ref = case ref of+ Reference q nm ref -> Reference q nm (fn ref)+ RefObject o ref -> RefObject o (fn ref)+ RefWrapper qref -> RefWrapper $ fmapReference fn qref++setQualifier :: RefQualifier -> Reference -> Reference+setQualifier q ref = case ref of+ Reference _ name ref -> Reference q name ref+ RefObject o ref -> RefObject o ref+ RefWrapper qref -> RefWrapper $ setQualifier q qref++modRefObject :: (Object -> Object) -> Reference -> Reference+modRefObject mod ref = case ref of+ RefObject o ref -> RefObject (mod o) ref+ ref -> ref++-- | This function performs an update on a 'Reference', it is the complement to the 'referenceLookup'+-- function. Evaluating 'referenceUpdate' on a 'Reference' will write/update the value associated with+-- it. If the boolean parameter is 'Prelude.True' it indicates that the value updated must already+-- exist, and an undefined reference error will be thrown if it does not exist.+referenceUpdate :: Reference -> Bool -> (Maybe Object -> Exec (Maybe Object)) -> Exec (Reference, Maybe Object)+referenceUpdate qref mustExist upd = do+ -- The 'ExecUnit' is not actually modified in any way by 'updateIndex'. It is only used to+ -- instruct Haskell's type system to select the class instance of 'updateIndex' for the data type:+ -- > 'ObjectLens' 'ExecUnit' 'Reference'+ (result, (qref, _, _)) <-+ runObjectFocus (updateIndex qref $ execToFocusUpdater upd) mustExist (fst $ referenceHead qref) ()+ return (qref, result)++----------------------------------------------------------------------------------------------------++-- $Object_types+-- Here we have a lambda calculus for describing types. Computationally, it is very similar to the+-- Prolog programming language, however an 'ObjType' is written using a subset the Dao scripting+-- langauge.++data CoreType+ = NullType+ | TrueType+ | CharType+ | IntType+ | WordType+ | LongType+ | DiffTimeType+ | FloatType+ | RatioType+ | ComplexType+ | StringType+ | BytesType+ | ListType+ | DictType+ | RefType+ | TypeType+ | TreeType+ | TimeType+ | HaskellType+ deriving (Eq, Ord, Typeable, Enum, Bounded)++instance Show CoreType where+ show t = case t of+ NullType -> "Null"+ TrueType -> "True"+ CharType -> "Char"+ IntType -> "Int"+ WordType -> "Word"+ LongType -> "Long"+ DiffTimeType -> "Diff"+ FloatType -> "Float"+ RatioType -> "Ratio"+ ComplexType -> "Complex"+ StringType -> "String"+ BytesType -> "Bytes"+ ListType -> "List"+ DictType -> "Dict"+ RefType -> "Ref"+ TypeType -> "Type"+ TreeType -> "Tree"+ TimeType -> "Time"+ HaskellType -> "Haskell"++instance Read CoreType where+ readsPrec _ str = map (\a -> (a, "")) $ case str of+ "Null" -> [NullType]+ "True" -> [TrueType]+ "Char" -> [CharType]+ "Int" -> [IntType]+ "Word" -> [WordType]+ "Long" -> [LongType]+ "Diff" -> [DiffTimeType]+ "Float" -> [FloatType]+ "Ratio" -> [RatioType]+ "Complex" -> [ComplexType]+ "String" -> [StringType]+ "Bytes" -> [BytesType]+ "List" -> [ListType]+ "Dict" -> [DictType]+ "Ref" -> [RefType]+ "Type" -> [TypeType]+ "Tree" -> [TreeType]+ "Time" -> [TimeType]+ "Haskell" -> [HaskellType]+ _ -> []++instance NFData CoreType where { rnf a = seq a () }++instance UStrType CoreType where+ toUStr = derive_ustr+ maybeFromUStr a = case readsPrec 0 (uchars a) of+ [(o, "")] -> Just o+ _ -> Nothing+ fromUStr a = case maybeFromUStr a of+ Nothing -> error (show a++" is not a valid type identifier")+ Just a -> a++instance Iv.InfBound CoreType where+ minBoundInf = Iv.Finite minBound+ maxBoundInf = Iv.Finite maxBound++instance PPrintable CoreType where { pPrint = pShow }++-- binary 0x08 0x1A CoreType+instance B.Binary CoreType mtab where+ put t = B.putWord8 $ case t of+ NullType -> 0x08+ TrueType -> 0x09+ CharType -> 0x0A+ IntType -> 0x0B+ WordType -> 0x0C+ LongType -> 0x0D+ DiffTimeType -> 0x0E+ FloatType -> 0x0F+ RatioType -> 0x10+ ComplexType -> 0x11+ StringType -> 0x12+ BytesType -> 0x13+ ListType -> 0x14+ DictType -> 0x15+ RefType -> 0x16+ TypeType -> 0x17+ TreeType -> 0x18+ TimeType -> 0x19+ HaskellType -> 0x1A+ get = B.word8PrefixTable <|> fail "expecting CoreType"++instance B.HasPrefixTable CoreType B.Byte mtab where+ prefixTable = B.mkPrefixTableWord8 "CoreType" 0x08 0x1A $ map return $+ [ NullType+ , TrueType+ , CharType+ , IntType+ , WordType+ , LongType+ , DiffTimeType+ , FloatType+ , RatioType+ , ComplexType+ , StringType+ , BytesType+ , ListType+ , DictType+ , RefType+ , TypeType+ , TreeType+ , TimeType+ , HaskellType+ ]++instance HasRandGen CoreType where+ randO = toEnum <$> nextInt (fromEnum (maxBound::CoreType))+ defaultO = randO++-- | Get the 'CoreType' o an 'Object'.+coreType :: Object -> CoreType+coreType o = case o of+ ONull -> NullType+ OTrue -> TrueType+ OChar _ -> CharType+ OInt _ -> IntType+ OWord _ -> WordType+ OLong _ -> LongType+ ORelTime _ -> DiffTimeType+ OFloat _ -> FloatType+ ORatio _ -> RatioType+ OComplex _ -> ComplexType+ OString _ -> StringType+ OBytes _ -> BytesType+ OList _ -> ListType+ ODict _ -> DictType+ ORef _ -> RefType+ OType _ -> TypeType+ OTree _ -> TreeType+ OAbsTime _ -> TimeType+ OHaskell _ -> HaskellType++----------------------------------------------------------------------------------------------------++-- | A symbol in the type calculus.+data TypeSym+ = CoreType CoreType+ -- ^ used when the type of an object is equal to it's value, for example Null and True,+ -- or in situations where the type of an object has a value, for example the dimentions of a+ -- matrix.+ | TypeSym Name+ | TypeVar Name [ObjType]+ -- ^ a polymorphic type, like 'AnyType' but has a name.+ deriving (Eq, Ord, Show, Typeable)++instance NFData TypeSym where+ rnf (CoreType a ) = deepseq a ()+ rnf (TypeSym a ) = deepseq a ()+ rnf (TypeVar a b) = deepseq a $! deepseq b ()++instance HasRandGen TypeSym where+ randO = _randTrace "TypeSym" $ countNode $ runRandChoice+ randChoice = randChoiceList $+ [CoreType <$> randO, TypeSym <$> randO, scramble $ return TypeVar <*> randO <*> randList 1 4]+ defaultO = _randTrace "D.TypeSym" $ CoreType <$> defaultO++instance PPrintable TypeSym where+ pPrint t = case t of+ CoreType t -> pPrint t+ TypeSym t -> pPrint t+ TypeVar t ctx -> pInline $+ concat [[pPrint t], guard (not (null ctx)) >> [pList_ "[" ", " "]" (map pPrint ctx)]]++-- binary 0x2E 0x2F+instance B.Binary TypeSym mtab where+ put o = case o of+ CoreType o -> B.prefixByte 0x2D $ B.put o+ TypeSym o -> B.prefixByte 0x2E $ B.put o+ TypeVar ref ctx -> B.prefixByte 0x2F $ B.put ref >> B.put ctx+ get = B.word8PrefixTable <|> fail "expecting TypeSym"++instance B.HasPrefixTable TypeSym B.Byte mtab where+ prefixTable =+ B.mkPrefixTableWord8 "TypeSym" 0x2D 0x2F [CoreType <$> B.get, return TypeVar <*> B.get <*> B.get]++----------------------------------------------------------------------------------------------------++-- | Complex type structures can be programmed by combining 'ObjSimpleType's. An empty 'TypeStruct'+-- is the "any-type", which matches anything.+newtype TypeStruct = TypeStruct [TypeSym] deriving (Eq, Ord, Show, Typeable)++instance NFData TypeStruct where { rnf (TypeStruct a) = deepseq a () }++instance HasNullValue TypeStruct where { nullValue = TypeStruct []; testNull (TypeStruct a) = null a; }++instance PPrintable TypeStruct where+ pPrint (TypeStruct tx) = case tx of+ [] -> pString "AnyType"+ tx -> pList (pString "type") "(" ", " ")" (map pPrint tx)++-- binary 0x33 +instance B.Binary TypeStruct mtab where+ put (TypeStruct o) = B.prefixByte 0x33 $ B.put o+ get = B.word8PrefixTable <|> fail "expecting TypeStruct"++instance B.HasPrefixTable TypeStruct B.Byte mtab where+ prefixTable = B.mkPrefixTableWord8 "TypeStruct" 0x33 0x33 [TypeStruct <$> B.get]++instance HasRandGen TypeStruct where+ randO = _randTrace "TypeStruct" $ TypeStruct <$> randList 0 4+ defaultO = _randTrace "D.TypeStruct" $ TypeStruct <$> defaultList 0 4++----------------------------------------------------------------------------------------------------++-- | The fundamental 'Type' used to reason about whether an object is fit to be used for a+-- particular function. Any empty 'ObjType' is the "void-type" which matches nothing.+newtype ObjType = ObjType { typeChoices :: [TypeStruct] } deriving (Eq, Ord, Show, Typeable)++instance NFData ObjType where { rnf (ObjType a) = deepseq a () }++instance HasNullValue ObjType where { nullValue = ObjType []; testNull (ObjType a) = null a; }++instance PPrintable ObjType where+ pPrint t@(ObjType tx) = case fromObj (obj t) of+ Just t -> pString $ show (t::CoreType)+ Nothing -> case tx of+ [] -> pString "VoidType"+ tx -> pList (pString "anyOf") "(" ", " ")" (map pPrint tx)++-- binary 0x37 +instance B.Binary ObjType mtab where+ put (ObjType o) = B.prefixByte 0x37 $ B.put o+ get = B.word8PrefixTable <|> fail "expecting ObjType"++instance B.HasPrefixTable ObjType B.Byte mtab where+ prefixTable = B.mkPrefixTableWord8 "ObjType" 0x37 0x37 [ObjType <$> B.get]++instance HasRandGen ObjType where+ randO = _randTrace "ObjType" $ recurse $ ObjType <$> randList 0 3+ defaultO = _randTrace "D.ObjType" $ ObjType <$> defaultList 1 4++typeOfObj :: Object -> ObjType+typeOfObj o = case o of+ OHaskell o -> hataType o+ o -> ObjType [TypeStruct [CoreType $ coreType o]]++hataType :: Hata -> ObjType+hataType (Hata ifc _) = ObjType [TypeStruct [TypeSym $ objInterfaceName ifc]]++objTypeFromCoreType :: CoreType -> ObjType+objTypeFromCoreType = ObjType . return . TypeStruct . return . CoreType++objTypeFromName :: Name -> ObjType+objTypeFromName name = ObjType{ typeChoices = [TypeStruct [TypeSym name]] }++----------------------------------------------------------------------------------------------------++-- | This is actually a part of the 'Reference' constructor, and 'Reference' is one of the built-in+-- 'Object' data types. There is a one-to-one mapping from this type to the 'RefSuffixExpr' and+-- 'AST_Ref' data types produced by the parser.+data RefSuffix+ = NullRef+ | DotRef Name RefSuffix+ | Subscript [Object] RefSuffix+ | FuncCall [Object] RefSuffix+ deriving (Eq, Ord, Typeable, Show)++instance Monoid RefSuffix where+ mempty = NullRef+ mappend left right = case left of+ NullRef -> right+ DotRef nm left -> DotRef nm $ left<>right+ Subscript ox left -> Subscript ox $ left<>right+ FuncCall ox left -> FuncCall ox $ left<>right++-- | If the 'RefSuffix' is 'DotRef', 'Subscript', or 'FuncCall', the second parameter to these+-- constructors is overwritten with 'NullRef' so only the first parameter remains.+refSuffixHead :: RefSuffix -> RefSuffix+refSuffixHead suf = let lst = refSuffixToList suf in if null lst then NullRef else head lst++-- | Evaluates to 'Prelude.True' if ay of the constructors within the 'RefSuffix' are 'FuncCall'.+refSuffixHasFuncCall :: RefSuffix -> Bool+refSuffixHasFuncCall suf = case suf of+ NullRef -> False+ DotRef _ suf -> refSuffixHasFuncCall suf+ Subscript _ suf -> refSuffixHasFuncCall suf+ FuncCall _ _ -> True++-- | The 'RefSuffix' is a list-like data type, where most of the constructors may contain another+-- 'RefSuffix' structure as the "tail" of the list. This function "explodes" a 'RefSuffix' into a+-- list of 'RefSuffix's where "tail" is 'NullRef'. This is the inverse operation of+-- 'Data.Monoid.mconcat', so the following equality is always True:+-- > \r -> mconcat (refSuffixToList r) == r+refSuffixToList :: RefSuffix -> [RefSuffix]+refSuffixToList suf = case suf of+ NullRef -> []+ DotRef a suf -> DotRef a NullRef : refSuffixToList suf+ Subscript a suf -> Subscript a NullRef : refSuffixToList suf+ FuncCall a suf -> FuncCall a NullRef : refSuffixToList suf++-- | Construct a 'DotRef' with a 'NullRef' suffix.+dotRef :: Name -> RefSuffix+dotRef = flip DotRef NullRef++-- | Construct a 'Subscript' with a 'NullRef' suffix.+subscript :: [Object] -> RefSuffix+subscript = flip Subscript NullRef++-- | Construct a 'FuncCall' with a 'NullRef' suffix.+funcCall :: [Object] -> RefSuffix+funcCall = flip FuncCall NullRef++instance HasNullValue RefSuffix where+ nullValue = NullRef+ testNull r = case r of { NullRef -> True; _ -> False }++refSuffixFromNames :: [Name] -> RefSuffix+refSuffixFromNames nx = case nx of { [] -> NullRef; n:nx -> DotRef n $ refSuffixFromNames nx; }++instance Read RefSuffix where+ readsPrec _ str = case str of+ '.':c:str | isAlpha c ->+ case break (\c -> c=='.' || isAlphaNum c) (c:str) of+ (cx, str) ->+ maybe [] (return . (\ref -> (refSuffixFromNames ref, str))) $ sequence $+ fix (\loop str -> case break (=='.') str of+ (cx, str) -> case cx of+ [] -> []+ cx -> maybeFromUStr (ustr (dropWhile (=='.') cx)) : loop str+ ) cx+ str -> [(NullRef, str)]++instance NFData RefSuffix where+ rnf NullRef = ()+ rnf (DotRef a b) = deepseq a $! deepseq b ()+ rnf (Subscript a b) = deepseq a $! deepseq b ()+ rnf (FuncCall a b) = deepseq a $! deepseq b ()++instance PPrintable RefSuffix where+ pPrint = pWrapIndent . loop where + loop r = case r of+ NullRef -> []+ DotRef a b -> pString "." : pUStr (toUStr a) : loop b+ Subscript a b -> pList_ "[" ", " "]" (map pPrint a) : loop b+ FuncCall a b -> pList_ "(" ", " ")" (map pPrint a) : loop b++instance HasRandGen RefSuffix where+ randO = _randTrace "RefSuffix" $ recurse $ countNode $ runRandChoice+ randChoice = randChoiceList $+ [ return NullRef+ , scramble $ return DotRef <*> randO <*> randO+ , depthLimitedInt 8 >>= \x -> return Subscript <*> randList 0 x <*> scrambO+ , depthLimitedInt 8 >>= \x -> return FuncCall <*> randList 0 x <*> scrambO+ ]+ defaultO = _randTrace "D.RefSuffix" runDefaultChoice+ defaultChoice = randChoiceList $ + [ return NullRef+ , return Subscript <*> defaultList 0 1 <*> pure NullRef+ , return FuncCall <*> defaultList 0 1 <*> pure NullRef+ ]++-- binary 0x42 0x45+instance B.Binary RefSuffix MTab where+ put r = case r of+ NullRef -> B.putWord8 0x42+ DotRef a b -> B.prefixByte 0x43 $ B.put a >> B.put b+ Subscript a b -> B.prefixByte 0x44 $ B.put a >> B.put b+ FuncCall a b -> B.prefixByte 0x45 $ B.put a >> B.put b+ get = B.word8PrefixTable <|> fail "expecting RefSuffix"++instance B.HasPrefixTable RefSuffix B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "RefSuffix" 0x42 0x45 $+ [ return NullRef+ , return DotRef <*> B.get <*> B.get+ , return Subscript <*> B.get <*> B.get+ , return FuncCall <*> B.get <*> B.get+ ]++----------------------------------------------------------------------------------------------------++newtype Complex = Complex (C.Complex Double)+ deriving (Eq, Typeable, Floating, Fractional, Num)++-- | Since 'Object' requires all of it's types instantiate 'Prelude.Ord', I have defined+-- 'Prelude.Ord' of 'Data.Complex.Complex' numbers to be the distance from 0, that is, the radius of+-- the polar form of the 'Data.Complex.Complex' number, ignoring the angle argument.+instance Ord Complex where+ compare (Complex a) (Complex b) = compare (C.polar a) (C.polar b)++instance Show Complex where+ show (Complex a) = "("++show re++(if im<0 then "-" else "+")++show im++"i)" where+ re = C.realPart a+ im = C.imagPart a++instance NFData Complex where { rnf (Complex a) = deepseq a $! () }++instance HasNullValue Complex where+ nullValue = Complex (0 C.:+ 0)+ testNull (Complex c) = C.realPart c == 0 && C.imagPart c == 0++instance B.Binary Complex mtab where+ put o = B.put (realPart o) >> B.put (imagPart o)+ get = return complex <*> B.get <*> B.get++instance PPrintable Complex where+ pPrint (Complex (a C.:+ b))+ | a==0.0 && b==0.0 = pString "0i"+ | a==0.0 = pString (show b++"i")+ | b==0.0 = pShow a+ | otherwise = pInline [pShow a, pString (if b<0 then "-" else "+"), pString (show b++"i")]++instance HasRandGen Complex where { randO = return mkPolar <*> randO <*> randO; defaultO = randO; }++realPart :: Complex -> Double+realPart (Complex o) = C.realPart o++imagPart :: Complex -> Double+imagPart (Complex o) = C.imagPart o++mkPolar :: Double -> Double -> Complex+mkPolar a b = Complex (C.mkPolar a b)++cis :: Double -> Complex+cis = Complex . C.cis++polar :: Complex -> (Double, Double)+polar (Complex o) = C.polar o++magnitude :: Complex -> Double+magnitude (Complex o) = C.magnitude o++phase :: Complex -> Double+phase (Complex o) = C.phase o++conjugate :: Complex -> Complex+conjugate (Complex o) = Complex (C.conjugate o)++complex :: Double -> Double -> Complex+complex a b = Complex (a C.:+ b)++----------------------------------------------------------------------------------------------------++-- | Create the minimum-sized array that can store all of the indices in the given list, setting the+-- 'Data.Array.IArray.bounds' of the array automatically. Evaluates to 'Prelude.Nothing' if the+-- given list of elements is empty.+minAccumArray :: Ix i => (e -> e' -> e) -> e -> [(i, e')] -> Maybe (Array i e)+minAccumArray accfn deflt elems =+ if null elems then Nothing else Just (accumArray accfn deflt bnds elems) where+ idxs = map fst elems+ i0 = head idxs+ bnds = foldl (\ (lo, hi) i -> (min lo i, max hi i)) (i0, i0) (tail idxs)++-- | Create the minimum-sized array that can store all of the indices in the given list, and setting+-- the 'Data.Array.IArray.bounds' of the array automatically. Evaluates to 'Prelude.Nothing' if the+-- given list of elements is empty.+minArray :: Ix i => e -> [(i, e)] -> Maybe (Array i e)+minArray deflt elems = minAccumArray (flip const) deflt elems++----------------------------------------------------------------------------------------------------++-- | An alternative to 'Glob' expressions containing ordinary 'Dao.String.UStr's is a 'Glob'+-- expression containing 'FuzzyStr's. These strings approximately match the input string, ignoring+-- minor spelling errors and transposed characters.+newtype FuzzyStr = FuzzyStr UStr deriving (Ord, Typeable)++instance Eq FuzzyStr where+ a==b = + let ax = S.map toLower (S.fromList (uchars a))+ bx = S.map toLower (S.fromList (uchars b))+ in a == b+ || ax == bx+ || S.size (S.difference (S.union ax bx) (if S.size ax < S.size bx then ax else bx)) <= 1++instance Show FuzzyStr where { show (FuzzyStr str) = show str }++instance Read FuzzyStr where+ readsPrec p input = readsPrec p input >>= \ (s, rem) -> return (FuzzyStr (ustr s), rem)++instance Monoid FuzzyStr where+ mempty = FuzzyStr mempty+ mappend (FuzzyStr a) (FuzzyStr b) = FuzzyStr (a<>b)++instance HasNullValue FuzzyStr where+ nullValue = FuzzyStr nullValue+ testNull (FuzzyStr s) = testNull s++instance UStrType FuzzyStr where { fromUStr = FuzzyStr; toUStr (FuzzyStr u) = u; }++instance PPrintable FuzzyStr where { pPrint (FuzzyStr str) = pShow str }++instance ObjectClass FuzzyStr where { obj=new; fromObj=objFromHata; }++instance HataClass FuzzyStr where+ haskellDataInterface = interface "FuzzyStr" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter ++--instance Show (Glob FuzzyStr) where { show = show . fmap toUStr }++--instance Read (Glob FuzzyStr) where+-- readsPrec prec str = readsPrec prec str >>= \ (glob, str) -> [(fmap fromUStr glob, str)]++instance Show (GlobUnit Object) where+ show o = case o of+ Single o -> show o+ globunit -> show (fmap (const "") globunit)++instance PPrintable (GlobUnit Object) where { pPrint = pShow }++instance Show (Glob Object) where+ show glob = (++"\"") $ ('"':) $ do+ o <- getPatUnits glob+ let other o = "$("++prettyShow o++")"+ case o of+ Single o -> case o of+ OString o -> uchars o+ OHaskell (Hata _ifc dyn) -> case fromDynamic dyn of+ Nothing -> other o+ Just (FuzzyStr o) -> uchars o+ _ -> other o+ globunit -> show (fmap (const "") globunit)++instance PPrintable (Glob Object) where { pPrint = pShow }++instance ToDaoStructClass (GlobUnit Object) where+ toDaoStruct = ask >>= \o -> case o of+ Wildcard a t -> renameConstructor "Wildcard" $ "name" .= reference UNQUAL a >> "type" .=? t+ AnyOne a t -> renameConstructor "AnyOne" $ "name" .= reference UNQUAL a >> "type" .=? t+ Single a -> renameConstructor "Single" $ "item" .= a++instance FromDaoStructClass (GlobUnit Object) where+ fromDaoStruct = msum $+ [ constructor "Wildcard" >> return Wildcard <*> req "name" <*> opt "type"+ , constructor "AnyOne" >> return AnyOne <*> req "name" <*> opt "type"+ , constructor "Single" >> Single <$> req "item"+ ]++instance ObjectClass (GlobUnit Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (GlobUnit Object) where+ haskellDataInterface = interface "GlobUnit" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter+ autoDefFromStruct >> autoDefToStruct++instance ToDaoStructClass (Glob Object) where+ toDaoStruct = renameConstructor "GlobPattern" $ "items" .=@ obj . map obj . getPatUnits++instance FromDaoStructClass (Glob Object) where+ fromDaoStruct = do+ constructor "GlobPattern"+ items <- reqList "items"+ return (Glob{ getPatUnits=items, getGlobLength=length items })++instance ObjectClass (Glob Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (Glob Object) where+ haskellDataInterface = interface "GlobPattern" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct+ defMethod "match" $+ daoFunc+ { daoForeignFunc = \glob ox -> fmap (flip (,) glob . Just . obj) $+ forM (matchPattern False glob ox) $ \match -> fmap (obj . M.fromList . concat) $+ forM (M.assocs match) $ \ (name, (vartyp, ox)) -> case vartyp of+ Nothing -> return [(name, obj ox)]+ Just vartyp -> do+ match <- catchPredicate $ referenceLookup $ Reference UNQUAL vartyp $ FuncCall ox NullRef+ case match of+ Backtrack -> return []+ OK (_, Nothing) -> return [(name, obj ox)]+ OK (_, Just o) -> return [(name, obj o)]+ PFail err -> throwError err+ }++----------------------------------------------------------------------------------------------------++newtype Pair = Pair (Object, Object) deriving (Eq, Ord, Show, Typeable)++instance PPrintable Pair where+ pPrint (Pair (a,b)) = pList (pString "Pair") "(" ", " ")" [pPrint a, pPrint b]++instance ToDaoStructClass Pair where+ toDaoStruct = renameConstructor "Pair" $ ask >>= \ (Pair (a, b)) -> "fst" .= a >> "snd" .= b++instance FromDaoStructClass Pair where+ fromDaoStruct = constructor "Pair" >> Pair <$> (return (,) <*> req "fst" <*> req "snd")++instance ObjectClass Pair where { obj=new; fromObj=objFromHata; }++instance HataClass Pair where+ haskellDataInterface = interface "Pair" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct+ defIndexUpdater $ \ix upd -> do+ (Pair (a,b)) <- get+ let casti = extractXPure . castToCoreType IntType >=> fromObj+ let badindex = execThrow "index for Pair data type must be a either 0 or 1" ExecErrorUntyped+ let qref = reference UNQUAL (ustr "Pair")+ case ix of+ [i] -> do+ i <- focusLiftExec (derefObject i) >>=+ maybe (badindex [(actualType, obj (typeOfObj i))]) return . casti+ (o, setter) <- case i of+ 0 -> pure (a, \a -> (a, b))+ 1 -> pure (b, \b -> (a, b))+ i -> badindex [(assertFailed, OInt i)]+ (result, (changed, o)) <- withInnerLens (Just o) upd+ if changed+ then+ case o of+ Nothing ->+ execThrow "item in Pair object updated with void" ExecErrorUntyped [(ustr "index", obj i)]+ Just o -> put (Pair $ setter o) >> return result+ else return result+ ix -> throwArityError "for subscript to Pair data type" 1 ix [(errInConstr, obj qref)]++builtin_assocs :: DaoFunc ()+builtin_assocs =+ daoFunc+ { daoForeignFunc = \ () ox -> do+ let qref = reference UNQUAL (ustr "assocs")+ case ox of+ [o] -> do+ let fromDict o = return $+ (Just $ obj $ fmap (\ (a,b) -> obj $ Pair (obj a, obj b)) $ M.assocs o, ())+ let badtype = throwBadTypeError "object does not contain association Pairs" o []+ case o of+ ODict d -> fromDict d+ OTree (Struct{fieldMap=d}) -> fromDict d+ OHaskell _ -> maybe badtype (return . flip (,) () . Just) $ msum $+ [ fromObj o >>= return . obj . fmap (\ (ix, o) -> obj $ Pair (H.indexKey ix, o)) . H.assocs+ ]+ _ -> badtype+ ox -> throwArityError "" 1 ox [(errInFunc, obj qref)]+ }++builtin_Pair :: DaoFunc ()+builtin_Pair =+ daoFunc+ { daoForeignFunc = \ () ox -> case ox of+ [a, b] -> return $ (Just $ obj $ Pair(a, b), ())+ ox -> throwArityError "Pair() constructor requires exactly two arguments" 2 ox []+ }++----------------------------------------------------------------------------------------------------++-- | This is a newtype wrapper around a function that tokenizes a 'UStr' into smaller 'UStr's used+-- for constructing rule patterns, and also for tokenizing input strings into objects that can be+-- matched against rule patterns.+newtype ExecTokenizer = ExecTokenizer { runExecTokenizer :: UStr -> Exec [Object] }+ deriving Typeable++----------------------------------------------------------------------------------------------------++-- | This is the state that is used to run the evaluation algorithm. Every Dao program file that has+-- been loaded will have a single 'ExecUnit' assigned to it. Parameters that are stored in+-- 'Dao.Debug.DMVar's or 'Dao.Type.Resource's will be shared across all rules which are executed in+-- parallel, so for example 'execHeap' contains the variables global to all rules in a given+-- program. The remainder of the parameters, those not stored in 'Dao.Debug.DMVar's or+-- 'Dao.Type.Resource's, will have a unique copy of those values assigned to each rule as it+-- executes.+data ExecUnit+ = ExecUnit+ { globalMethodTable :: MethodTable+ -- ^ In this slot will be stored a read-only @'Data.Map.Lazy.Map' 'Dao.String.UStr'+ -- 'Interface'@ object that will allow any method with access to this+ -- 'GenRuntime' to retrieve a 'Interface' by it's name string. Specifically,+ -- this will be used by objects stored in the 'OHaskell' constructor.+ , importGraph :: M.Map UPath ExecUnit+ -- ^ every file opened, whether it is a data file or a program file, is registered here under+ -- it's file path (file paths map to 'File's).+ , defaultTimeout :: Maybe Int+ -- ^ the default time-out value to use when evaluating 'execInputString'+ , currentWithRef :: Maybe Object+ -- ^ the current document is set by the @with@ statement during execution of a Dao script.+ , taskForExecUnits :: Task+ , currentQuery :: Maybe [Object]+ , currentPattern :: Maybe (Glob Object)+ , currentCodeBlock :: Maybe Subroutine+ -- ^ when evaluating a 'Subroutine' selected by a string query, the action resulting from+ -- that query is defnied here. It is only 'Data.Maybe.Nothing' when the module is first being+ -- loaded from source code.+ , currentBranch :: [Name]+ -- ^ set by the @with@ statement during execution of a Dao script. It is used to prefix this+ -- to all global-dot references before reading from or writing to those references.+ , execStack :: Stack Name Object+ -- ^ stack of local variables used during evaluation+ , globalData :: T_dict+ , providedAttributes :: M.Map UStr ()+ , builtinConstants :: T_dict+ , execOpenFiles :: M.Map UPath ExecUnit+ , programModuleName :: Maybe UPath+ , preExec :: [Subroutine]+ -- ^ the "guard scripts" that are executed before every string execution.+ , postExec :: [Subroutine]+ -- ^ the "guard scripts" that are executed after every string execution.+ , quittingTime :: [Subroutine]+ , programTokenizer :: ExecTokenizer+ , ruleSet :: PatternTree Object [Subroutine]+ , lambdaSet :: [CallableCode]+ , uncaughtErrors :: [ExecControl]+ , runtimeRefTable :: RefTable Object Dynamic+ }++-- Initializes a completely empty 'ExecUnit'+_initExecUnit :: IO ExecUnit+_initExecUnit = do+ execTask <- initTask+ reftable <- newRefTable+ return $+ ExecUnit+ { globalMethodTable = mempty+ , defaultTimeout = Nothing+ , importGraph = mempty+ , currentWithRef = Nothing+ , currentQuery = Nothing+ , currentPattern = Nothing+ , currentCodeBlock = Nothing+ , currentBranch = []+ , globalData = mempty+ , providedAttributes = mempty+ , builtinConstants = mempty+ , taskForExecUnits = execTask+ , execStack = emptyStack+ , execOpenFiles = mempty+ , programModuleName = Nothing+ , preExec = []+ , quittingTime = mempty+ , programTokenizer = defaultTokenizer+ , postExec = []+ , ruleSet = T.Void+ , lambdaSet = []+ , uncaughtErrors = []+ , runtimeRefTable = reftable+ }++-- | Creates a new 'ExecUnit'. This is the only way to create a new 'ExecUnit', and it must be run+-- within the 'Exec' monad. The 'ExecUnit' produced by this function will have it's parent+-- 'ExecUnit' set to the value returned by the 'Control.Monad.Reader.Class.ask' instance of the+-- 'Exec' monad.+--+-- The parent of all other 'ExecUnit's, the root of the family tree, is initalized internally by the+-- 'startDao' function.+newExecUnit :: Maybe UPath -> Exec ExecUnit+newExecUnit modName = get >>= \parent -> liftIO _initExecUnit >>= \child -> return $+ child+ { programModuleName = modName+ , builtinConstants = builtinConstants parent+ , defaultTimeout = defaultTimeout parent+ , globalMethodTable = globalMethodTable parent+ , runtimeRefTable = runtimeRefTable parent+ }++-- | Execute an 'Exec' monadic function within a different 'ExecUnit' module. The result of the+-- 'Exec' monadic function is the first value in the tuple returned, any modifications to the given+-- 'ExecUnit' module are stored as the second value of the tuple returned.+inModule :: ExecUnit -> Exec a -> Exec (a, ExecUnit)+inModule subxunit exe = do+ xunit <- get+ result <- put subxunit >> catchPredicate exe+ subxunit <- get+ put xunit+ result <- predicate result+ return (result, subxunit)++----------------------------------------------------------------------------------------------------++-- | A 'Task' is simply a group of threads executing in parallel, but evaluating a task is still+-- synchronous, i.e. evaluating 'taskLoop' on a 'Task' will block until every thread in the task has+-- completed.+data Task+ = Task+ { taskWaitChan :: Chan (ThreadId, Int)+ , taskRunningThreads :: MVar (S.Set ThreadId)+ }++-- | Create a new 'Task'.+initTask :: IO Task+initTask = do+ wait <- newChan+ running <- newMVar S.empty+ return $ Task{ taskWaitChan=wait, taskRunningThreads=running }++-- | To halt a single thread in a 'Task', simply signal it with 'Control.Concurrent.killThread'. But+-- to halt everything the task is doing, use this function. Use of this function will never result in+-- deadlocks (I hope).+throwToTask :: Exception e => Task -> e -> IO ()+throwToTask task e = do+ let mvar = taskRunningThreads task+ ((S.elems <$> readMVar mvar) >>= mapM_ (flip throwTo e))+ `finally` getChanContents (taskWaitChan task) >> return ()++-- | Like 'throwToTask', but throws 'Control.Exception.ThreadKilled'.+killTask :: Task -> IO ()+killTask = flip throwToTask ThreadKilled++-- | This is a better way to manage a 'Task' because all tasks evaluated are waited for+-- synchronously, but you can provide a callback that is evaluated after each task completes. This+-- prevents exceptions from occurring, for example:+-- > "thread blocked indefinitely in an MVar operation"+-- +-- Provide a list of IO functions to be evaluated in parallel. Also provide a callback function+-- that will be evaluated after each thread completes. This function should take two parameters and+-- return a bool: the 'Control.Concurrent.ThreadId' of the thread that completed and a positive+-- integer value indicating the number of threads that are still running, and the bool returned+-- should indicate whether or not the loop should continue. If you should halt the loop by returning+-- 'Prelude.False', the threads in the task that are still running will continue running, and you+-- should call 'killTask' after 'taskLoop' to halt them if halting them should be necessary.+-- +-- This function is also exception safe. All tasks evaluated in parallel will not fail to singal the+-- callback, even if the thread halts with an exception or asynchronous signal from a function like+-- 'Control.Concurrent.killThread'. If the thread evaluating this function is halted by an+-- exception, all threads in the 'Task' are also killed.+taskLoop :: Task -> [IO ()] -> (ThreadId -> Int -> IO Bool) -> IO ()+taskLoop task parallelIO threadHaltedEvent = unless (null parallelIO) $+ (do mapM_ (forkInTask task) parallelIO+ fix $ \loop -> waitFirst task >>= \ (thread, remain) ->+ threadHaltedEvent thread remain >>= \contin -> unless (not contin || remain==0) loop+ ) `onException` killTask task+ where+ waitFirst :: Task -> IO (ThreadId, Int)+ waitFirst task = readChan (taskWaitChan task)+ forkInTask :: Task -> IO () -> IO ThreadId+ forkInTask task run = forkIO $ do+ self <- myThreadId+ bracket+ (modifyMVar (taskRunningThreads task) $ \s' -> do+ let s = S.delete self s'+ return (s, S.size s)+ )+ (\i -> writeChan (taskWaitChan task) (self, i))+ (\ _i -> run)++-- | Works exactly like 'taskLoop', except you do not need to provide a callback function to be+-- evaluated after every task completes. Essentially, every IO function is evaluated in the 'Task'+-- in parallel, and this function blocks until all tasks have completed.+taskLoop_ :: Task -> [IO ()] -> IO ()+taskLoop_ task inits = taskLoop task inits (\ _ _ -> return True)++----------------------------------------------------------------------------------------------------++-- | This simple, humble little class is one of the most important in the Dao program because it+-- defines the 'execute' function. Any data type that can result in procedural execution in the+-- 'Exec' monad can instantiate this class. This will allow the instnatiated data type to be used as+-- a kind of executable code that can be passed around and evaluated at arbitrary points in your Dao+-- program.+-- +-- Note that there the @result@ type parameter is functionally dependent on the @exec@ type+-- parameter. This guarantees there is a one-to-one mapping from independent @exec@ types to+-- dependent @result@ types, i.e. if you data type @MyDat@ maps to a data type @Rzlt@, then @Rzlt@+-- is the only possible data type that could ever be evaluated by 'execute'-ing the @MyDat@+-- function.+--+-- As a reminder, functional dependencies do not necessitate a one-to-one mapping from the+-- dependent type to the independent type, so the @result@ parameter may be the same for many+-- different @exec@ types. But once the compiler infers that the @exec@ parameter of the 'Executable'+-- class is @MyDat@, the @result@ type /must/ be @Rzlt@ and nothing else.+-- > instance Executable MyDat Rzlt+-- > instance Executable A () -- OK (different @exec@ parameters, same @result@ parameters)+-- > instance Executable B () -- OK+-- > instance Executable C () -- OK+-- > +-- > instance Executable D () -- COMPILER ERROR (same @exec@ parameters, different @result@ parameters)+-- > instance Executable D Int -- COMPILER ERROR+-- > instance Executable D Char -- COMPILER ERROR+-- In this example, should D instantiate () or Int or Char as it's result? You must choose only one.+class Executable exec result | exec -> result where { execute :: exec -> Exec result }++----------------------------------------------------------------------------------------------------++-- | Since the 'ExecUnit' deals with a few different kinds of pointer values, namely+-- 'Data.IORef.IORef' and 'MVar', which all have similar functions for reading and updating, I have+-- defined this class to provide a consistent set of functions for working with the various pointers+-- data types.+class ExecRef var where+ execReadRef :: var a -> Exec a+ execTakeRef :: var a -> Exec a+ execPutRef :: var a -> a -> Exec ()+ execSwapRef :: var a -> a -> Exec a+ execModifyRef :: var a -> (a -> Exec (a, b)) -> Exec b+ execModifyRef_ :: var a -> (a -> Exec a ) -> Exec ()+ execModifyRef_ var upd = execModifyRef var (\a -> upd a >>= \a -> return (a, ()))++instance ExecRef MVar where+ execModifyRef mvar upd =+ Exec $ PredicateT $ StateT $ \xunit -> modifyMVar mvar $ \var -> do+ (result, xunit) <- flip ioExec xunit $ execCatchIO (upd var) $+ [ newExecIOHandler $ flip (execThrow "") [] . ExecHaskellError+ , newExecIOHandler $ flip (execThrow "") [] . ExecIOException+ ]+ let x var p = return (var, (p, xunit))+ case result of+ Backtrack -> x var $ Backtrack+ OK (var, o) -> x var $ OK o+ PFail err -> x var $ PFail err+ execModifyRef_ mvar upd = execModifyRef mvar (\var -> upd var >>= \var -> return (var, ()))+ execReadRef = liftIO . readMVar+ execTakeRef = liftIO . takeMVar+ execPutRef mvar = liftIO . putMVar mvar+ execSwapRef mvar = liftIO . swapMVar mvar++instance ExecRef IORef where+ execModifyRef ref upd = liftIO (readIORef ref) >>= upd >>= \ (var, b) -> liftIO (writeIORef ref var) >> return b+ execModifyRef_ ref upd = liftIO (readIORef ref) >>= upd >>= liftIO . writeIORef ref+ execReadRef = liftIO . readIORef+ execTakeRef = execReadRef+ execPutRef ref = liftIO . writeIORef ref+ execSwapRef ref obj = liftIO (readIORef ref >>= \sw -> writeIORef ref obj >> return sw)++----------------------------------------------------------------------------------------------------++data ObjFocusState o+ = ObjFocusState+ { targetReference :: Reference+ -- ^ the whole reference we intend to use+ , focalReference :: Reference+ -- ^ the reference that is constructed piecewise as each part of the 'targetReference' is resolved.+ , objectInFocus :: o+ , objectInFocusChanged :: Bool+ -- ^ this value is automatically set whenever the 'Control.Monad.State.put' function, or+ -- 'Control.Monad.State.modify' functions are evaluated. This value is returned by functions+ -- like 'withInnerFocus' and 'runObjectFocus' to indicate when the item being modified was+ -- actually modified.+ , focusLookup :: Bool+ -- ^ when true, indicates that the current focus operation is a lookup. Lookups are different+ -- from updates in that an update can continue recursive searching through an object tree even+ -- if the reference to be updated is void. Assignment operations, for example, update void+ -- references by writing to the reference. Lookup operations, on the other hand, will always+ -- fail as soon as a void is encountered.+ }++-- Although the 'ObjectFocus' monad is a wrapper around a 'Dao.Predicate.PredicateT' monad+-- transformer that lifts the 'Exec' monad, backtracking (the instantiation of+-- 'Control.Monad.mzero') has a very different semantical meaning from the 'Exec' monad's semantical+-- meaning of backtracking. In the case of 'ObjectFocus', evaluating 'Control.Monad.mzero' indicates+-- a refernece being looked-up is undefined. To allow for 'Exec' to be lifted into 'ObjectFocus' and+-- also possibly evaluate to 'Control.Monad.mzero' without triggering a backtracking in the+-- 'ObjectFocus' monad, backtracking in the 'Exec' monad needs to be "caught" and re-thrown as an+-- error using 'Control.Monad.Error.throwError' wrapped up in the 'ObjFocusError' data type. However+-- this is all done "under the hood," the public API of the 'ObjectFocus' monad still has+-- 'Control.Monad.Error.throwError' instantiated to throw 'ExecControl' just like the 'Exec' monad:+-- > 'Control.Monad.Error.Class.MonadError' 'ExecControl' 'ObjectFocus'+-- and when evaluting the 'ObjectFocus' monad in the 'Exec' monad using 'runObjectFocus', this+-- error type is automatically caught again and converted back to 'Control.Monad.mzero', initiating+-- ordinary backtracking in the 'Exec' monad.+data ObjFocusError+ = InnerExecBacktrack -- ^ a lifted 'Exec' monad evaluated to 'Control.Monad.mzero'+ | InnerExecPFail ExecControl+ -- ^ a lifted 'Exec' monad evaluated to 'Control.Monad.Error.throwError'++-- | This is the stateful monad used by the 'ObjectLens' and 'ObjectFunctor' classes. It is called a+-- "focus" because the object we are focused on (on which we are looking up indicies or modifying+-- indicies) is stored in the state of the monad. The 'Control.Monad.State.modify' function modifies+-- the object in the focus.+newtype ObjectFocus o a+ = ObjectFocus{ mapObjectLensToPredicate :: PredicateT ObjFocusError (StateT (ObjFocusState o) Exec) a }+ deriving (Functor, Applicative, Alternative, MonadPlus)++instance Monad (ObjectFocus o) where+ return = ObjectFocus . return+ (ObjectFocus m) >>= f = ObjectFocus $ m >>= mapObjectLensToPredicate . f+ fail msg = _mapStructState (gets focalReference) >>= flip (execThrow msg) []++instance MonadError ExecControl (ObjectFocus o) where+ throwError = ObjectFocus . throwError . InnerExecPFail+ catchError (ObjectFocus f) catch = ObjectFocus $ catchError f $ \err -> case err of+ InnerExecPFail err -> mapObjectLensToPredicate (catch err)+ InnerExecBacktrack -> throwError InnerExecBacktrack++instance MonadPlusError ExecControl (ObjectFocus o) where+ catchPredicate (ObjectFocus f) = ObjectFocus $ catchPredicate f >>= \p -> case p of+ OK o -> return $ OK o+ PFail (InnerExecPFail e) -> return $ PFail e+ PFail InnerExecBacktrack -> throwError InnerExecBacktrack+ Backtrack -> return Backtrack+ predicate = ObjectFocus . predicate . fmapPFail InnerExecPFail++instance MonadIO (ObjectFocus o) where { liftIO = ObjectFocus . liftIO }++instance MonadState o (ObjectFocus o) where+ get = _mapStructState $ gets objectInFocus+ put o = _mapStructState $ modify $ \st -> st{objectInFocus=o, objectInFocusChanged=True}++-- | An 'ObjectFocus' function that updates a value within the data of type @o@ in focus at a given+-- @index@ using an inner 'ObjectFocus' function that focuses on the 'Object' value at the index if+-- it exists. The type of the inner 'ObjectFocus' is @('Prelude.Maybe' 'Object')@ because there may+-- be no value defined at the given index.+type ObjectUpdate o index = index -> ObjectFocus (Maybe Object) (Maybe Object) -> ObjectFocus o (Maybe Object)++-- | An 'ObjectFocus' that traverses the 'Object' in the focus by calling the provided traversal+-- function for every @index -> 'Object'@ relation defined within the data of type @o@. The+-- traversal function is an inner focus of type @[(index, 'Object')]@. The list in focus should+-- initially be empty when the inner traversal function is called. When the inner traversal function+-- is completed, it should contain every @(index, 'Object')@ that is intended to be stored back into+-- the data of type @o@. The outer 'ObjectFocus' should retrieve this list of pairs and determine+-- how to update the data of type @o@ accordingly.+type ObjectTraverse o index = (index -> Object -> ObjectFocus [(index, Object)] ()) -> ObjectFocus o ()++-- | This class provides functions that can be used to establish 'ObjectFocus' for various data+-- types. The class allows you to define an association between an index and and object type o, and+-- how the index is used to read and update the object o.+-- There are no functional dependencies between the object type and the index type, so using these+-- function may require type annotation.+class ObjectLens o index where { updateIndex :: ObjectUpdate o index }++-- | This class provides the 'objectFMap' function for evaluating a functor over every item in an+-- 'bject in the focus of an 'ObjectFocus'. The function that maps to the functor object takes a+-- polymorphic index type and the 'Object' associated with that index. For example, in the case of a+-- 'T_dict' type, the index would be a 'Dao.String.Name', in the case of a 'T_list' type, the index+-- would be a 'Prelude.Integer'.+class ObjectFunctor o index where { objectFMap :: ObjectTraverse o index }++_mapStructState :: StateT (ObjFocusState o) Exec a -> ObjectFocus o a+_mapStructState = ObjectFocus . lift++_getTargetRefInfo :: ObjectFocus o Reference+_getTargetRefInfo = _mapStructState $ gets targetReference++_setTargetRefInfo :: Reference -> ObjectFocus o ()+_setTargetRefInfo qref = _mapStructState $ modify $ \st -> st{targetReference=qref}++-- | This function will check if the 'focusLookup' boolean is set. If it is set, it checks if the+-- current 'objectInFocus' is 'Prelude.Nothing' and backtracks if it is. Otherwise it evaluates the+-- given function.+focusNext :: ObjectFocus (Maybe o) a -> ObjectFocus (Maybe o) a+focusNext f = do+ isLookup <- _mapStructState (gets focusLookup)+ if isLookup then get >>= maybe mzero (const f) else f++-- | When instantiating the 'ObjectLens' class with a 'RefSuffix' index type, it is useful to+-- record the head of the 'RefSuffix' that is being used to resolve the index. When an+-- 'Control.Monad.fail' is evaluated, the current path (the 'RefSuffix') to the part of the object+-- where the failure occurred will be used in the error report. Bracketing your 'ObjectFocus'+-- evaluation in this function will help create better error reports.+focalPathSuffix :: RefSuffix -> ObjectFocus o a -> ObjectFocus o a+focalPathSuffix suf f = do+ r <- _mapStructState $ get >>= \st -> do+ let r = focalReference st+ put (st{ focalReference=refAppendSuffix r suf }) >> return r+ f >>= \a -> _mapStructState (modify $ \st -> st{ focalReference=r }) >> return a++focusLiftExec :: Exec a -> ObjectFocus o a+focusLiftExec exec = ObjectFocus $ do+ p <- lift $ lift $ catchPredicate exec+ case p of+ Backtrack -> throwError InnerExecBacktrack+ PFail err -> throwError $ InnerExecPFail err+ OK o -> return o++execToFocusUpdater :: (Maybe Object -> Exec (Maybe Object)) -> ObjectFocus (Maybe Object) (Maybe Object)+execToFocusUpdater f = get >>= focusLiftExec . f >>= \o -> put o >> return o++getFocalReference :: ObjectFocus o Reference+getFocalReference = _mapStructState (gets focalReference)++-- | This is a kind of entry-point to the 'ObjectFocus' group of functions. First provide a boolean+-- value indicating whether this operation is a ('Prelude.True') lookup and should fail as soon as a+-- void address is encountered, or ('Prelude.False') an update that may insert a value at a void+-- address rather than failing. Second, provide a 'Reference' value for error reporting, to indicate+-- where a 'lookupIndex' or 'updateIndex' function failed. Note that using 'lookupIndex' and+-- 'updateIndex' functions instantiated for 'RefSuffix' indicies will append these indicies to the+-- 'Reference', so it might be better to pass the 'referenceHead' of the 'Reference'. Then supply an+-- object upon which the 'lookupIndex', 'updateIndex', or 'objectFMap' functions will be evaluating.+runObjectFocus :: ObjectFocus o a -> Bool -> Reference -> o -> Exec (a, (Reference, Bool, o))+runObjectFocus f isLookup qref o = _runObjectFocus f st >>= \p -> case p of+ OK o -> return o+ PFail (InnerExecPFail e) -> throwError e+ PFail InnerExecBacktrack -> mzero+ Backtrack -> execThrow "undefined reference" qref []+ where+ st= ObjFocusState+ { targetReference = qref+ , focalReference = fst (referenceHead qref)+ , objectInFocus = o+ , objectInFocusChanged = False+ , focusLookup = isLookup+ }++_runObjectFocus :: ObjectFocus o a -> ObjFocusState o -> Exec (Predicate ObjFocusError (a, (Reference, Bool, o)))+_runObjectFocus (ObjectFocus f) st = flip evalStateT st $ runPredicateT $ f >>= \a -> do+ o <- lift $ return (,,) <*> gets targetReference <*> gets objectInFocusChanged <*> gets objectInFocus+ return (a, o)++-- | This is a very important function because it allows you to evaluate an inner 'ObjectFocus'+-- monad that is focused on a different type from the type focus of the monad in the context in+-- which this function is evaluated. It allows you to select a sub-field of the current focus (for+-- example using 'Control.Monad.State.gets') and evaluate an updating function or lookup function+-- that uses value of the sub-field. This function returns the result of the evaluation, and the+-- updated value which can then be placed back into the sub-field if necessary. By composing+-- 'withInnerLens' functions, it is possible to construct a lens that can read and update any value+-- in arbitrarily complex data types.+withInnerLens :: sub -> ObjectFocus sub a -> ObjectFocus o (a, (Bool, sub))+withInnerLens sub f = do+ st <- _mapStructState get+ o <- focusLiftExec $ _runObjectFocus f $ st{objectInFocus=sub}+ ObjectFocus $ predicate $ o >>= \ (a, (_ref, changed, o)) -> return (a, (changed, o))++-- Used in the 'Interface' table to convert between a @typ@ and 'Data.Dynamic.Dynamic' value.+convertFocus :: (a -> b) -> (b -> a) -> ObjectFocus a x -> ObjectFocus b x+convertFocus a2b b2a f = get >>= flip withInnerLens f . b2a >>= \ (x, (changed, a)) ->+ when changed (put $ a2b a) >> return x++focusObjectClass :: ObjectClass o => ObjectFocus o a -> ObjectFocus Object a+focusObjectClass f = do+ (a, (changed, o)) <- get >>= xmaybe . fromObj >>= flip withInnerLens f+ when changed (put $ obj o) >> return a++instance ObjectLens T_dict Name where+ updateIndex name f = do+ (result, (changed, o)) <- get >>= flip withInnerLens (focusNext f) . (M.lookup name)+ when changed (modify $ M.alter (const o) name)+ return result++instance ObjectFunctor T_dict Name where+ objectFMap f = get >>=+ mapM (\ (name, o) -> focalPathSuffix (DotRef name NullRef) $ withInnerLens [] $ f name o+ ) . M.assocs >>= put . M.fromList . concatMap (snd . snd)++focusGuardStructName :: Name -> ObjectFocus T_struct ()+focusGuardStructName name = get >>= guard . (==name) . structName++focusStructAsDict :: ObjectFocus T_dict a -> ObjectFocus T_struct a+focusStructAsDict f = get >>= \struct -> case struct of+ Nullary{ structName=name } -> do+ (a, (changed, sub)) <- withInnerLens M.empty f+ if M.null sub+ then return ()+ else when changed (put $ Struct{ structName=name, fieldMap=sub })+ return a+ Struct{ structName=name, fieldMap=sub } -> do+ (a, (changed, sub)) <- withInnerLens sub f+ when changed $ do+ if M.null sub+ then put (Nullary{ structName=name })+ else put (struct{ fieldMap=sub })+ return a++instance ObjectLens T_struct Name where+ updateIndex name f = focusStructAsDict $ updateIndex name f++instance ObjectFunctor T_struct Name where+ objectFMap f = focusStructAsDict $ objectFMap f++updateHataAsStruct :: ObjectFocus T_struct a -> ObjectFocus Hata a+updateHataAsStruct f = do+ (Hata ifc o) <- get+ (fromStruct, toStruct) <- xmaybe (return (,) <*> objFromStruct ifc <*> objToStruct ifc)+ -- here ^ evaluation backtracks if the field cannot be accessed+ struct <- predicate $ fromData toStruct o+ (a, (changed, struct)) <- withInnerLens struct f+ when changed (predicate (toData fromStruct struct) >>= put . Hata ifc)+ return a++lookupHataAsStruct :: ObjectFocus T_struct a -> ObjectFocus Hata a+lookupHataAsStruct f = do+ (Hata ifc o) <- get+ toStruct <- xmaybe (objToStruct ifc)+ struct <- predicate $ fromData toStruct o+ fst <$> withInnerLens struct f++instance ObjectLens Hata Name where+ updateIndex name f = updateHataAsStruct $ updateIndex name f++instance ObjectFunctor Hata Name where+ objectFMap = flip mplus (return ()) . updateHataAsStruct . focusStructAsDict . objectFMap++instance ObjectLens [Object] Integer where+ updateIndex idx f = get >>= \ox ->+ if idx == negate 1+ then do+ (result, (changed, o)) <- withInnerLens Nothing f+ when changed (put $ maybe ox (:ox) o)+ return result+ else do+ let splitlen i rx ox = case ox of+ [] -> (i, rx, [])+ o:ox -> if i<idx then splitlen (i+1) (rx++[o]) ox else (i, rx, o:ox)+ let (len, lo, hi) = splitlen 0 [] ox+ if 0<=idx && idx<=len+ then+ if null hi+ then do+ (result, (changed, o)) <- withInnerLens Nothing f+ when changed (put $ maybe ox ((ox++) . return) o)+ return result+ else do+ (result, (changed, o)) <- withInnerLens (Just $ head hi) f+ when changed (put $ lo ++ maybe [] return o ++ tail hi)+ return result+ else execThrow "index ouf of bounds" ExecErrorUntyped [(assertFailed, OLong idx)]++instance ObjectFunctor [Object] Integer where+ objectFMap f = get >>=+ mapM (\ (idx, o) -> focalPathSuffix (Subscript [obj idx] NullRef) $ withInnerLens [] $ f idx o+ ) . zip [0..] >>= put . map snd . sortBy (\a b -> compare (fst a) (fst b)) . concatMap (snd . snd)++_dictSubscriptUpdate+ :: ObjectLens o Name+ => String -> [Object]+ -> ObjectFocus (Maybe Object) (Maybe Object)+ -> ObjectFocus o (Maybe Object)+_dictSubscriptUpdate msg ix f = focusLiftExec (mapM derefObject ix) >>= \ix -> case ix of+ [] -> fail $ "void subscript used to index "++msg+ [ORef (Reference UNQUAL name suf)] -> updateIndex name $ updateIndex suf f+ [_] -> fail $ "non-reference subscript used to update index of "++msg+ _ -> fail $ "multi-dimensional subscript used to update index of "++msg++-- Converts the function @f@ that is passed to an 'objectFMap' which takes an index value of type+-- @i@ to a value suitable for invoking an 'objectFMap' function instantiated for a different type+-- @fi@.+objectFMapConvert+ :: (i -> ObjectFocus [(fi, Object)] fi) -> (fi -> ObjectFocus [(i, Object)] i)+ -> (fi -> Object -> ObjectFocus [(fi, Object)] ())+ -> i -> Object+ -> ObjectFocus [(i, Object)] ()+objectFMapConvert i2fi fi2i f i o = getFocalReference >>= \qref -> do+ (_, (_, changed, o)) <- focusLiftExec (runObjectFocus (i2fi i >>= flip f o) False qref [])+ o <- forM o (\ (fi, o) -> fi2i fi >>= \i -> return (i, o))+ _mapStructState $ modify $ \st -> st{objectInFocusChanged=changed, objectInFocus=o}++_dictSubscriptFMap+ :: ObjectFunctor o Name+ => String -> ([Object] -> Object -> ObjectFocus [([Object], Object)] ()) -> ObjectFocus o ()+_dictSubscriptFMap msg f = objectFMap $ objectFMapConvert i2fi fi2i f where+ i2fi :: Name -> ObjectFocus [([Object], Object)] [Object]+ i2fi name = return [ORef $ Reference UNQUAL name NullRef]+ fi2i :: [Object] -> ObjectFocus [(Name, Object)] Name+ fi2i ix = focusLiftExec (mapM derefObject ix) >>= \ix -> case ix of+ [ORef (Reference UNQUAL name NullRef)] -> return name+ _ -> fail $ "improper index value used to update field while traversing "++msg++_index1DIntegral :: Show a => String -> String -> [Object] -> (Integer -> ObjectFocus o a) -> ObjectFocus o a+_index1DIntegral msg1 msg2 ix f = focusLiftExec (mapM derefObject ix) >>= \ix -> case ix of+ [] -> fail $ "void subscript used to "++msg1++" list"++msg2+ [i] -> case extractXPure (castToCoreType LongType i) >>= fromObj of+ Nothing -> fail $ "non-integer subscript used to "++msg1++" list"++msg2+ Just i -> f i+ _ -> fail $ "multi-dimensional subscript used to "++msg1++" list"++msg2++instance ObjectLens [Object] [Object] where+ updateIndex ix f = _index1DIntegral "update" "" ix $ flip updateIndex f++instance ObjectFunctor [Object] [Object] where+ objectFMap = objectFMap .+ objectFMapConvert (\i -> return [OLong i]) (\ix -> _index1DIntegral "traverse" "" ix return)++instance ObjectLens T_dict [Object] where+ updateIndex = _dictSubscriptUpdate "dictionary"++instance ObjectFunctor T_dict [Object] where+ objectFMap = _dictSubscriptFMap "dictionary"++instance ObjectLens T_struct [Object] where+ updateIndex = _dictSubscriptUpdate "struct"++instance ObjectFunctor T_struct [Object] where+ objectFMap = _dictSubscriptFMap "struct"++_hataUpdateSubscript+ :: (String -> ObjectFocus T_struct (Maybe Object))+ -> ObjectFocus Hata (Maybe Object)+_hataUpdateSubscript f = do+ (Hata ifc _) <- get+ updateHataAsStruct (f $ show $ objHaskellType ifc) <|> fail "cannot update field"++_hataLookupSubscript :: (String -> ObjectFocus T_struct Object) -> ObjectFocus Hata Object+_hataLookupSubscript f = do+ (Hata ifc _) <- get+ lookupHataAsStruct $ f $ show $ objHaskellType ifc++instance ObjectLens Hata [Object] where+ updateIndex ix f = get >>= \ (Hata ifc o) -> case objIndexUpdater ifc of+ Nothing -> _hataUpdateSubscript $ \msg -> _dictSubscriptUpdate msg ix f+ Just update -> do+ (result, (changed, o)) <- withInnerLens o $ update ix f+ when changed (put $ Hata ifc o) >> return result++instance ObjectFunctor Hata [Object] where+ objectFMap f = get >>= \ (Hata ifc o) -> case objTraverse ifc of+ Nothing -> updateHataAsStruct (_dictSubscriptFMap (show $ objHaskellType ifc) f) <|>+ fail "cannot update field"+ Just traverse -> do+ ((), (changed, o)) <- withInnerLens o (traverse f)+ when changed (put $ Hata ifc o)++_tryFuncCall+ :: Maybe Object -> [Object] -> RefSuffix+ -> ObjectFocus (Maybe Object) (Maybe Object)+ -> ObjectFocus (Maybe Object) (Maybe Object)+ -> ObjectFocus (Maybe Object) (Maybe Object)+_tryFuncCall func args suf f els = maybe els id $ do+ (Hata ifc d) <- func >>= fromObj+ calls <- objCallable ifc+ return $ do+ -- Get the result of the function call, the result of the operation will become the focus.+ this <- get+ (result, this) <- focusLiftExec (calls d >>= flip (callCallables this) args)+ put this+ -- Focus on the result of the function call and evaluate an update on it.+ -- The updated function result is ignored, any changes made to it are lost.+ (result, _) <- case suf of+ NullRef -> withInnerLens result f+ _ -> withInnerLens result (updateIndex suf f)+ -- The function call may have updated the "this" value, place this updated value back into the focus.+ return result++_refSuffixUpdate+ :: (ObjectClass o, ObjectLens o i)+ => o -> i -> RefSuffix+ -> ObjectFocus (Maybe Object) (Maybe Object)+ -> ObjectFocus (Maybe Object) (Maybe Object)+_refSuffixUpdate o i suf f = do+ (result, (changed, o)) <- withInnerLens o $ updateIndex i $ updateIndex suf f+ when changed (put $ Just $ obj o) >> return result++-- | This is an 'ObjectUpdate' function which operates in the monad+-- > 'ObjectFocus' (Maybe 'Object') (Maybe 'Object')+-- If the object in the focus is an object constructed with 'ODict', 'OTree', or 'OHaskell', then+-- the 'Name' parameter passed to this function is used to lookup a function stored in the object in+-- focus. This function sets the object in focus to the "this" variable in the Dao runtime and then+-- calls the function with the given @['Object']@ arguments. The object in focus is updated by the+-- function call and the result of the function call is placed in the focus and updated by the next+-- update function with the next 'RefSuffix' provided (as the arguments to the 'ObjectUpdate'). This+-- is the semantics for Dao language expressions of the kind:+-- a.b(c).d(e)+-- that is, an objet stored in the variable "a" has a method "b" called with a parameter "(c)" and+-- the result of this call is an object with a method "d" that is called with the parameter "(e)".+-- Of course if the 'RefSuffix' is 'NullRef', the result of the method call is simply returned. The+-- result of "a.b(c)" is stored on the stack and used to select and evaluate the method "d(e)",+-- however if "d(e)" modified the value on the stack, this modified value is lost when it is popped+-- off of the stack after evaluation completes. There is currently no way to update objects in this+-- way, as the Dao runtime does not have a way to update arbitrary points in it's working memory.+-- This will hopefully be improved in future versions.+callMethod :: Name -> [Object] -> ObjectUpdate (Maybe Object) RefSuffix+callMethod name args suf f = do+ qref <- _getTargetRefInfo+ let err msg = execThrow msg qref []+ o <- get+ case o of+ Nothing -> err "method call on undefined reference"+ Just o -> case o of+ ODict d -> _tryFuncCall (M.lookup name d) args suf f (_refSuffixUpdate d name suf f)+ OTree d -> _tryFuncCall (structLookup name d) args suf f (_refSuffixUpdate d name suf f)+ OHaskell (Hata ifc d) -> case M.lookup name (objMethodTable ifc) of+ Just func -> do+ (result, d) <- focusLiftExec $ executeDaoFunc func d args+ put (Just $ OHaskell $ Hata ifc d)+ -- Next we take the result of this method call and let any 'RefSuffix's that may exist to+ -- operate on it. Like _tryFuncCall, this will ignore changes made to the result of the+ -- function call.+ (result, _) <- case suf of+ NullRef -> withInnerLens result f+ _ -> withInnerLens result (updateIndex suf f)+ return result+ Nothing -> case objToStruct ifc of+ Just toStruct -> do+ struct <- predicate (fromData toStruct d)+ _tryFuncCall (structLookup name struct) args suf f (_refSuffixUpdate (Hata ifc d) name suf f)+ Nothing -> err "not a callable method function"+ _ -> err "method call on atomic object"+ -- TODO: provide a set of built-in methods available to every object.++instance ObjectLens (Maybe Object) RefSuffix where+ updateIndex suf f = _getTargetRefInfo >>= \qref -> focalPathSuffix suf $ get >>= \o -> case suf of+ NullRef -> focusNext $ get >>= flip withInnerLens f >>= \ (result, (changed, o)) ->+ when changed (put o) >> return result+ DotRef name (FuncCall args suf) -> focusNext $ callMethod name args suf f+ DotRef name suf -> case o of+ Nothing -> mzero+ Just o -> case o of+ ODict o -> _refSuffixUpdate o name suf f+ OTree o -> _refSuffixUpdate o name suf f+ OHaskell o -> _refSuffixUpdate o name suf f+ _ -> throwBadTypeError "referenced element of non-container object" o []+ Subscript ix suf -> focusNext $ case o of+ Nothing -> mzero+ Just o -> case o of+ OList o -> _refSuffixUpdate o ix suf f+ ODict o -> _refSuffixUpdate o ix suf f+ OTree o -> _refSuffixUpdate o ix suf f+ OHaskell o -> _refSuffixUpdate o ix suf f+ _ -> throwBadTypeError "cannot subscript of non-indexed object" o []+ FuncCall args suf -> focusNext $ case o of+ Nothing -> mzero+ Just o -> do+ (result, o) <- focusLiftExec (callObject qref o args)+ _mapStructState $ modify $ \st -> st{ objectInFocus=o }+ -- -^ Here we put the updated function back (it may have had it's static var table updated),+ -- but we use '_mapStructState' to do it. This is because using 'modify' or 'put' will+ -- automatically set the 'objectInFocusChanged' flag. Since many functions 'CONST' and we+ -- would like to update the function's static table without modifying the function itself+ -- without also triggering the exception thrown when a const variable is modified, we must+ -- make sure we update it in a way that would not indicate that the function object itself+ -- has been modified.+ (result, _) <- case suf of+ NullRef -> withInnerLens result f+ suf -> withInnerLens result (updateIndex suf f)+ return result++instance ObjectFunctor Object RefSuffix where+ objectFMap f = get >>= \o -> case o of+ OList o -> travers o int2subs subs2int OList+ ODict o -> travers o ref2subs subs2ref ODict+ OTree o -> travers o ref2subs subs2ref OTree+ OHaskell o -> travers o ref2subs subs2ref OHaskell+ _ -> return ()+ where+ int2subs i = return $ Subscript [OLong i] NullRef+ subs2int i = case i of+ Subscript [o] NullRef -> case extractXPure (castToCoreType LongType o) >>= fromObj of+ Nothing -> fail "while traversing list, updating function returned non-integer index value"+ Just i -> return i+ _ -> fail "while traversing list, updating function returned non-subscript reference as index"+ ref2subs i = return $ DotRef i NullRef+ subs2ref i = case i of+ DotRef name NullRef -> return name+ _ -> fail "while traversing structure, updating function returned invalid reference"+ travers o to from constr = do+ (_, (changed, o)) <- withInnerLens o (objectFMap $ objectFMapConvert to from f)+ when changed (put $ constr o)++instance ObjectFunctor (Maybe Object) RefSuffix where+ objectFMap f = get >>= maybe (return ()) (fmap fst . flip withInnerLens (objectFMap f))++-- | If you have a data type @o@ instantiating @'ObjectLens' o index@ with a given index type, and+-- this data type @o@ is a field of another @data@ type, you can instantiate 'updateIndex' for this+-- data type by providing functions to unwrap and wrap the data type @o@ inside of it. For+-- @newtype@s, the wrapper function can be given as @('Prelude.const' MyNewtype)@ where+-- @MyNewtype@ is the newtype constructor.+innerDataUpdateIndex+ :: (Show o, ObjectLens o index)+ => (dt -> o) -> (dt -> o -> dt)+ -> index -> ObjectFocus (Maybe Object) (Maybe Object) -> ObjectFocus dt (Maybe Object)+innerDataUpdateIndex unwrap wrap i upd = get >>= \dt -> do+ (result, (changed, o)) <- withInnerLens (unwrap dt) (updateIndex i upd)+ when changed (modify $ flip wrap o) >> return result++----------------------------------------------------------------------------------------------------++instance ObjectLens (Stack Name Object) Name where+ updateIndex name upd = do+ ((result, changed), stack) <- get >>=+ stackUpdateM (flip withInnerLens upd >=> \ (result, (changed, o)) -> return ((result, changed), o)) name+ when changed (put stack) >> return result++-- | This function can be used to automatically instantiate 'updateIndex' for any type @o@ that also+-- instantiates @'ObjectLens' o 'Dao.String.Name'@.+referenceUpdateName :: ObjectLens o Name => Reference -> ObjectFocus (Maybe Object) (Maybe Object) -> ObjectFocus o (Maybe Object)+referenceUpdateName qref f = case qref of+ Reference _ name suf -> updateIndex name $ updateIndex suf f+ RefObject o suf -> case o of+ ORef o -> referenceUpdateName (refAppendSuffix o suf) f+ _ -> fail "cannot update reference"+ RefWrapper _ -> fail "cannot update reference"++-- | This function can be used to automatically instantiate 'lookupIndex' for any type @o@ that also+-- instantiates @'ObjectLens' o 'Dao.String.Name'@. This function may also performs updates on variables+-- in place if the variable contains an object and the reference is a method call which updates the+-- object.+referenceLookupName :: ObjectLens o Name => Reference -> ObjectFocus o Object+referenceLookupName qref = case qref of+ Reference _ name suf -> updateIndex name get >>=+ fmap fst . flip withInnerLens (updateIndex suf get) >>= maybe (execThrow "" qref []) return+ RefObject o suf -> case o of+ ORef o -> referenceLookupName (refAppendSuffix o suf)+ _ -> fail "cannot update reference"+ RefWrapper _ -> fail "cannot update reference"++instance ObjectLens (Stack Name Object) Reference where { updateIndex = referenceUpdateName }++----------------------------------------------------------------------------------------------------++updateLocal :: Name -> RefSuffix -> ObjectFocus (Maybe Object) (Maybe Object) -> ObjectFocus () (Reference, Maybe Object)+updateLocal name suf f = do+ stack <- focusLiftExec (gets execStack)+ (result, (changed, o)) <- withInnerLens (stackLookup name stack) (updateIndex suf f)+ -- The updateIndex function may have performed a function call that updated the local stack so we+ -- need to get the (possibly) updated stack from the 'ExecUnit' once again and operate on that.+ when changed $ focusLiftExec (gets execStack) >>= \stack -> focusLiftExec $ modify $ \xunit ->+ xunit{ execStack = snd $ stackUpdate (const ((), o)) name stack }+ return (Reference LOCAL name suf, result)++updateConst :: Name -> RefSuffix -> ObjectFocus (Maybe Object) (Maybe Object) -> ObjectFocus () (Reference, Maybe Object)+updateConst name suf f = do+ let qref = Reference CONST name suf+ consts <- focusLiftExec (gets builtinConstants)+ (result, (changed, _)) <- withInnerLens (M.lookup name consts) (updateIndex suf f)+ when changed $ case suf of -- Modifying a const variable directly fails.+ NullRef -> execThrow "attempted modification of immutable value" ExecErrorUntyped [(modifiedConst, obj qref)]+ _ -> return () -- modifying a member of a const value is OK but the updated value is disgarded.+ return (qref, result)++updateStatic :: Name -> RefSuffix -> ObjectFocus (Maybe Object) (Maybe Object) -> ObjectFocus () (Reference, Maybe Object)+updateStatic name suf f = do+ let qref = Reference STATIC name suf+ store <- focusLiftExec (gets currentCodeBlock) >>= maybe mzero return+ (result, (changed, o)) <- withInnerLens (M.lookup name $ staticVars store) (updateIndex suf f)+ when changed $ focusLiftExec (gets currentCodeBlock) >>= \store -> case store of+ Nothing -> return ()+ Just store -> focusLiftExec $ modify $ \xunit ->+ xunit+ { currentCodeBlock = Just $+ store{ staticVars = M.alter (const o) name (staticVars store) }+ }+ return (qref, result)++updateGlobal :: Name -> RefSuffix -> ObjectFocus (Maybe Object) (Maybe Object) -> ObjectFocus () (Reference, Maybe Object)+updateGlobal name suf f = do+ let qref = Reference GLOBAL name suf+ store <- focusLiftExec (gets globalData)+ (result, (changed, o)) <- withInnerLens (M.lookup name store) (updateIndex suf f)+ when changed $ focusLiftExec (gets globalData) >>= \store -> focusLiftExec $ modify $ \xunit ->+ xunit{ globalData = M.alter (const o) name store }+ return (qref, result)++updateWithRef :: Name -> RefSuffix -> ObjectFocus (Maybe Object) (Maybe Object) -> ObjectFocus () (Reference, Maybe Object)+updateWithRef name suf f = do+ let qref = Reference GLODOT name suf+ store <- focusLiftExec (gets currentWithRef)+ case store of+ Nothing -> updateGlobal name suf f+ Just o -> do+ (result, (changed, o)) <- withInnerLens (Just o) (updateIndex suf f)+ when changed $ focusLiftExec $ modify $ \xunit -> xunit{ currentWithRef=o }+ return (qref, result)++instance ObjectLens () Name where+ updateIndex name = updateIndex (Reference UNQUAL name NullRef)++instance ObjectLens () Reference where+ updateIndex qref f = do+ (qref, result) <- case qref of+ Reference q name suf -> case q of+ UNQUAL -> fmap fst $ withInnerLens () $ msum $+ [ updateLocal name suf f+ , updateConst name suf f+ , updateStatic name suf f+ , updateGlobal name suf f+ , updateWithRef name suf f+ ]+ LOCAL -> updateLocal name suf f+ CONST -> updateConst name suf f+ STATIC -> updateStatic name suf f+ GLOBAL -> updateGlobal name suf f+ GLODOT -> updateWithRef name suf f+ _ -> execThrow "cannot update reference" qref []+ _setTargetRefInfo qref >> return result++----------------------------------------------------------------------------------------------------++-- | Error report indicating which function was being evaluated when the error occurred.+errInFunc :: Name+errInFunc = ustr "errInFunc"++-- | Error report indicating which function was being evaluated when the error occurred.+errInConstr :: Name+errInConstr = ustr "errInConstr"++-- | Error report indicating which function was being evaluated when the error occurred.+errInInitzr :: Name+errInInitzr = ustr "errInInitzr"++-- | Error report indicating which function was being evaluated when the error occurred.+errOfReference :: Name+errOfReference = ustr "errOfReference"++-- | Error report for function calls indicating which argument to the function was incorrect.+argNum :: Name+argNum = ustr "argNum"++-- | Error report for function calls indicating the number of arguments passed to the function.+numArgsPassed :: Name+numArgsPassed = ustr "numArgsPassed"++-- | Error report for subscript expressions indicating the number of dimensions expected+expectNumArgs :: Name+expectNumArgs = ustr "expectNumArgs"++-- | Error report for subscript expressions indicating the number of dimensions expected+exectDimension :: Name+exectDimension = ustr "expectDimension"++-- | Error report used any place an object value of an incorrect data type was given.+expectType :: Name+expectType = ustr "expectType"++-- | Error report indicating that the data type of the 'Object' given was incorrect.+actualType :: Name+actualType = ustr "actualType"++-- | Error report indicating the data type of the right-hand side of an infix operator.+leftSideType :: Name+leftSideType = ustr "leftSideType"++-- | Error report indicating the data type of the right-hand side of an infix operator.+rightSideType :: Name+rightSideType = ustr "rightSideType"++-- | Error report indicating an attempt to modify a 'CONST' 'Reference'.+modifiedConst :: Name+modifiedConst = ustr "modifiedConst"++-- | Error report indicating an object value was of the correct type but was out of bounds or was+-- otherwise not correct.+assertFailed :: Name+assertFailed = ustr "assertFailed"++-- | Error report indicating a function call evalauted to void.+returnedVoid :: Name+returnedVoid = ustr "returnedVoid"++-- | This is a simple dictionary of strings that can translate the keys of the 'execErrorInfo'+-- dictionary to more meaningful explanatory strings when reporting error messages.+errorDict :: M.Map Name UStr+errorDict = M.fromList $ fmap (fmap ustr) $+ [(errInFunc , "in function call")+ ,(errInConstr , "in constructor")+ ,(errInInitzr , "in initializer list")+ ,(errOfReference, "of reference")+ ,(argNum , "argument number")+ ,(expectNumArgs , "number of arguments expected")+ ,(exectDimension, "dimensional data type")+ ,(numArgsPassed , "number of arguments given")+ ,(expectType , "expecting type")+ ,(actualType , "actual type used")+ ,(leftSideType , "data type of the left-hand operand")+ ,(rightSideType , "data type of the right-hand operand")+ ,(modifiedConst , "modification on constant reference")+ ,(assertFailed , "object value fails assertion test")+ ,(returnedVoid , "expression evaluated to void")+ ]++----------------------------------------------------------------------------------------------------++-- | This data type is use to halt normal evaluation and force the result of evaluating the code to+-- be a particular value of this type. The 'Exec' monad instantiates+-- 'Control.Monad.Error.Class.MonadError' such that 'Control.Monad.Error.Class.throwError' throws a+-- value of this type. However, it is not only used for exceptions. The Dao scripting language's+-- "return" statement throws an 'ExecReturn' value which is caught using+-- 'Control.Monad.Error.Class.catchError' when evaluating function calls.+data ExecControl+ = ExecReturn { execReturnValue :: Maybe Object }+ | ExecError+ { execErrorMessage :: UStr+ , execErrorInModule :: Maybe UStr+ , execErrorLocation :: Location+ , execErrorSubtype :: ExecErrorSubtype+ , execErrorInfo :: T_dict+ }+ deriving Typeable++instance Show ExecControl where { show=prettyShow }++instance HasNullValue ExecControl where+ nullValue = ExecReturn Nothing+ testNull (ExecReturn Nothing) = True+ testNull _ = False++instance PPrintable ExecControl where+ pPrint err = case err of + ExecError{ execErrorMessage=msg, execErrorLocation=loc } -> do+ maybe (return ()) (pString . (++":") . uchars) (execErrorInModule err)+ pShow loc >> pString (if testNull loc then " " else ": ")+ when (not $ msg==nil) (pUStr msg)+ pIndent $ do+ pEndLine >> pPrint (execErrorSubtype err)+ forM_ (M.assocs $ execErrorInfo err) $ \ (key, val) -> do+ pEndLine+ maybe (pPrint key >> pString ": ") (pString . (++" ") . uchars) (M.lookup key errorDict)+ pPrint val+ ExecReturn{ execReturnValue=o } -> maybe (return ()) pPrint o++instance ToDaoStructClass ExecControl where+ toDaoStruct = ask >>= \o -> case o of+ ExecReturn{} -> renameConstructor "ExecReturn" $ asks execReturnValue >>= ("value" .=?)+ ExecError {} -> renameConstructor "ExecError" $ do+ asks execErrorInModule >>= ("inModule" .=?)+ "message" .=@ execErrorMessage+ "location" .=@ execErrorLocation+ "subtype" .=@ execErrorSubtype+ "info" .=@ execErrorInfo++instance HataClass ExecControl where+ haskellDataInterface = interface "ExecControl" $ do+ autoDefPPrinter >> autoDefNullTest >> autoDefToStruct++instance HasNullValue ExecErrorSubtype where+ nullValue = ExecErrorUntyped+ testNull ExecErrorUntyped = True+ testNull _ = False++newError :: ExecControl+newError =+ ExecError+ { execErrorMessage = nil+ , execErrorInModule = Nothing+ , execErrorLocation = LocationUnknown+ , execErrorSubtype = ExecErrorUntyped+ , execErrorInfo = mempty+ }++throwArityError :: MonadError ExecControl m => String -> Int -> [o] -> [(Name, Object)] -> m ig+throwArityError msg i ox info = execThrow fullmsg ExecErrorUntyped moreInfo where+ (before, after) = splitAt 100 ox+ fullmsg = (++(if null msg then "" else ", "++msg)) $+ if null after then "incorrect number of arguments given" else "over 100 arguments given"+ moreInfo = concat $+ [ if null after then [(numArgsPassed, OInt $ length before)] else []+ , [(expectNumArgs, OInt i)], info+ ]++throwBadTypeError :: MonadError ExecControl m => String -> Object -> [(Name, Object)] -> m ig+throwBadTypeError msg o info = execThrow msg (ExecTypeError $ typeOfObj o) info++throwParseError :: MonadError ExecControl m => String -> Maybe UPath -> ParseError () DaoTT -> [(Name, Object)] -> m ig+throwParseError msg mod err info = throwError $+ newError+ { execErrorMessage = ustr msg+ , execErrorInfo = M.fromList info+ , execErrorLocation = parseErrLoc err+ -- -^ set the error location in the 'ExecControl' structure+ , execErrorSubtype = ExecParseError $ err{ parseErrLoc = LocationUnknown }+ -- -^ delete the location to prevent it from being displayed twice+ , execErrorInModule = mod+ }++-- | Evaluate a monadic function which may throw an 'ExecControl' 'Dao.Predicate.PFail' predicate+-- value. If the monadic function does fail, the 'Doa.Predicate.PFail' value will be updated with+-- the 'Dao.Token.Location' value retrieved by evaluating 'Dao.Token.getLocation' on the object+-- provided. If the evaluation failed and has already set an error location, the location is not+-- modified by this function, which guarantees the inner-most call to 'errLocation' will set the+-- location of the error.+errLocation :: (MonadError ExecControl m, HasLocation o) => o -> m a -> m a+errLocation o f = catchError f $ \err -> case err of+ ExecError{execErrorLocation=LocationUnknown} ->+ throwError $ err{execErrorLocation=getLocation o}+ err -> throwError err++-- | Evaluate a monadic function which may throw an 'ExecControl' 'Dao.Predicate.PFail' predicate+-- value. If the monadic function does fail, the 'Doa.Predicate.PFail' value will be updated with+-- the given module name. If the evaluation failed and has already set an error module, the module+-- is not modified by this function, which guarantees the inner-most call to 'errModule' will set+-- the module of the error.+errModule :: MonadError ExecControl m => UPath -> m a -> m a+errModule path f = catchError f $ \err -> case err of+ ExecError{execErrorInModule=mod} -> throwError $ err{execErrorInModule=mplus mod (Just path)}+ err -> throwError err++-- | Like 'errModule' but sets the module reported by the error to be the current module of the+-- 'ExecUnit' if it is defined. If it is not defined, this function is equivalent to 'Prelude.id'.+errCurrentModule :: Exec a -> Exec a+errCurrentModule f = gets programModuleName >>= \mod -> maybe id errModule mod $ f++-- | Evaluate a monadic function which may throw an 'ExecControl' 'Dao.Predicate.PFail' predicate+-- value. If the monadic function does fail, the 'execErrorInfo' field of the 'ExecError' value in+-- the 'Dao.Predicate.PFail' predicate will be updated with the field 'Name' and 'Object' value+-- provided here.+errInfo :: MonadError ExecControl m => Name -> Object -> m a -> m a+errInfo name o f = catchError f $ \err -> case err of+ ExecError{execErrorInfo=info} -> throwError $ err{execErrorInfo=M.insert name o info}+ err -> throwError err++-- | Evaluate an 'Exec', but if it throws an exception, set record an 'ObjectExpr' where+-- the exception occurred in the exception information.+updateExecErrorInfo :: Name -> Object -> Exec a -> Exec a+updateExecErrorInfo name o fn = catchError fn $ \err -> case err of+ ExecReturn{} -> throwError err+ ExecError{execErrorInfo=info} -> throwError $ err{ execErrorInfo = M.insert name o info }++-- | If an error has not been caught, log it in the module where it can be retrieved later. This+-- function only stores errors constructed with 'ExecError', the 'ExecReturn' constructed objects+-- are ignored.+logUncaughtErrors :: [ExecControl] -> Exec ()+logUncaughtErrors errs = modify $ \xunit ->+ xunit{ uncaughtErrors = uncaughtErrors xunit +++ (errs >>= \e -> case e of { ExecReturn{} -> []; ExecError{} -> [e]; }) }++-- | Clear the log of uncaught 'ExecError' values stored by 'logUncaughtErrors'.+clearUncaughtErrorLog :: Exec [ExecControl]+clearUncaughtErrorLog = do+ errs <- gets uncaughtErrors+ modify $ \xunit -> xunit{ uncaughtErrors = [] }+ return errs++----------------------------------------------------------------------------------------------------++data ExecErrorSubtype+ = ExecErrorUntyped+ | ExecThrow Object+ -- ^ thrown when evaluating a "throw" statement, that is 'Dao.Interpreter.AST.ReturnExpr'+ | ExecStructError StructError -- ^ thrown by 'toDaoStruct' or 'fromDaoStruct'+ | ExecUndefinedRef Reference -- ^ signals reference lookup failed+ | ExecTypeError ObjType -- ^ catch-all exception thrown when wrong data type is used.+ | ExecUpdateOpError UpdateOp -- ^ thrown when an update operator fails+ | ExecIOException IOException -- ^ re-thrown when caught from the IO monad+ | ExecHaskellError ErrorCall -- ^ re-thrown when caught from the IO monad+ | ExecParseError (ParseError () DaoTT)+ | ExecInfixOpError ObjType InfixOp ObjType -- ^ thrown when an infix operator fails+ | ExecLoopCtrl LoopCtrl+ -- ^ inspired by the Python language, break and continue loop control statements are exceptions.+ deriving (Eq, Typeable)++instance Show ExecErrorSubtype where { show=prettyShow }++instance ToDaoStructClass ExecErrorSubtype where+ toDaoStruct = ask >>= \o -> case o of+ ExecErrorUntyped -> makeNullary "Error"+ ExecThrow o -> renameConstructor "Exception" $ "threw" .= o+ ExecStructError o -> innerToStruct o+ ExecUndefinedRef o -> renameConstructor "UndefinedRef" $ "reference" .= o+ ExecTypeError o -> renameConstructor "TypeMismatch" $ "usedType" .= o+ ExecUpdateOpError o -> renameConstructor "UpdateOpError" $ "operator" .= o+ ExecIOException o -> renameConstructor "HaskellIOException" $ "message" .= obj (show o)+ ExecHaskellError o -> renameConstructor "HaskellError" $ "message" .= obj (show o)+ ExecParseError o -> innerToStruct o+ ExecInfixOpError a o b -> renameConstructor "InfixOpError" $+ "operator" .= o >> "left" .= a >> "right" .= b+ ExecLoopCtrl o -> innerToStruct o++instance PPrintable ExecErrorSubtype where+ pPrint o = case o of+ ExecErrorUntyped -> return ()+ ExecThrow o -> pString "threw exception " >> pPrint o+ ExecStructError o -> pPrint o+ ExecUndefinedRef o -> pString "undefined reference " >> pPrint o+ ExecTypeError o -> pString "cannot use value of type " >> pPrint o+ ExecUpdateOpError o -> pString "cannot apply update with operator " >> pPrint o+ ExecIOException o -> pString (show o)+ ExecHaskellError o -> pString (show o)+ ExecParseError o -> pPrint o+ ExecInfixOpError a o b -> do+ pString "incompatible types on either side of operator " >> pPrint o+ pIndent $ do+ pEndLine >> pString "left-hand side of operator is value of type: " >> pPrint a+ pEndLine >> pString "right-hand side of operator is value of type: " >> pPrint b+ ExecLoopCtrl o -> pPrint o++instance ObjectClass ExecErrorSubtype where { obj=new; fromObj=objFromHata; }++instance HataClass ExecErrorSubtype where+ haskellDataInterface = interface "ErrorSubtype" $ do+ autoDefEquality >> autoDefPPrinter >> autoDefToStruct++----------------------------------------------------------------------------------------------------++data LoopCtrl+ = LoopCtrl+ { loopCtrlEscaped :: Bool+ -- ^ Has this exception gone past a function call context boundary?+ , loopCtrlContinue :: Bool+ -- ^ Is this control expression a continue statement? (if not it is a break statement)+ }+ deriving (Eq, Typeable)++loopCtrl :: Bool -> LoopCtrl+loopCtrl contin = LoopCtrl{ loopCtrlEscaped=False, loopCtrlContinue=contin }++instance PPrintable LoopCtrl where + pPrint ctrl = pString $ concat $+ [ if loopCtrlContinue ctrl then "continue" else "break"+ , " statement evaluated is not within a loop"+ ]++instance ToDaoStructClass LoopCtrl where+ toDaoStruct = renameConstructor "LoopCtrl" $ "isContinue" .=@ loopCtrlContinue++catchLoopCtrl :: Exec a -> (Bool -> Exec a) -> Exec a+catchLoopCtrl try catch = catchError try $ \err -> case err of+ ExecError{ execErrorSubtype =+ ExecLoopCtrl (LoopCtrl{ loopCtrlEscaped=False, loopCtrlContinue=contin }) } -> catch contin+ _ -> throwError err++-- | When defining an iterator, you should catch break and continue statements. This function is a+-- replacement for 'Control.Monad.forM' that handles break and continue statements correctly.+execForM :: [o] -> (o -> Exec a) -> Exec [a]+execForM ox f = loop ox [] where+ loop ox rev = case ox of+ [] -> return rev+ o:ox -> do+ (contin, r) <- catchLoopCtrl ((,) True . return <$> f o) (return . flip (,) [])+ (if contin then loop ox else return) (rev++r)++-- | Like 'execForM' but ignores the values returned by the iterating function.+execForM_ :: [o] -> (o -> Exec a) -> Exec ()+execForM_ ox f = loop ox where+ loop ox = case ox of+ [] -> return ()+ o:ox -> catchLoopCtrl (void $ f o) (flip when (loop ox))++----------------------------------------------------------------------------------------------------++-- | All evaluation of the Dao language takes place in the 'Exec' monad. It instantiates+-- 'Control.Monad.MonadIO.MonadIO' to allow @IO@ functions to be lifeted into it. It instantiates+-- 'Control.Monad.Error.MonadError' and provides it's own exception handling mechanism completely+-- different from the Haskell runtime, so as to allow for more control over exception handling in+-- the Dao runtime.+newtype Exec a = Exec{ execToPredicate :: PredicateT ExecControl (StateT ExecUnit IO) a }+ deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO)++instance MonadState ExecUnit Exec where { state = Exec . lift . state }++instance MonadError ExecControl Exec where+ throwError = Exec . throwError+ catchError (Exec try) catch = Exec (catchError try (execToPredicate . catch))++instance MonadPlusError ExecControl Exec where+ catchPredicate (Exec f) = Exec (catchPredicate f)+ predicate = Exec . predicate++----------------------------------------------------------------------------------------------------++-- | The 'XPure' type is like 'Exec' but does not lift IO or contain any reference to any+-- 'ExecUnit', so it is guaranteed to work without side-effects, but it also instantiates the+-- 'Control.Monad.MonadPlus', 'Control.Applicative.Alternative', 'Control.Monad.Error.MonadError'+-- and 'Dao.Predicate.MonadPlusError' classes so you can do computation with backtracking and+-- exceptions. Although this monad evaluates to a pure function, it does have stateful data: a+-- 'Dao.String.UStr' that will call a "print stream", which is provided for general purpose; a place+-- to print information throughout evaluation like a "print()" statement. The 'xnote' function+-- serves as the "print()" function for this monad. Use the ordinary 'Control.Monad.State.get' and+-- 'Control.Monad.State.modify' APIs for working with the print stream data.+-- +-- You can also evaluate an 'XPure' monad within an 'Exec' monad by simply using the 'execute'+-- function. This will automatically convert the internal 'Dao.Predicate.Predicate' of the 'XPure'+-- monad to the 'Dao.Predicate.Predicate' of the 'Exec' monad, meaning if you 'execute' an 'XPure'+-- monad that backtracks or throws an error, the 'Exec' monad will backtrack or throw the same+-- error.+newtype XPure a = XPure { xpureToState :: PredicateT ExecControl (State UStr) a }+ deriving (Functor, Applicative, Alternative, MonadPlus)++instance Show a => Show (XPure a) where+ show (XPure p) = case evalState (runPredicateT p) nil of+ Backtrack -> "(BACKTRACK)"+ PFail err -> "(PFAIL "++prettyShow err++")"+ OK o -> "(OK "++show o++")"++instance Monad XPure where+ return = XPure . return+ (XPure a) >>= f = XPure $ a >>= xpureToState . f+ fail msg = execThrow msg ExecErrorUntyped []++-- | Convert a value wrapped in an XPure monad to a pair containing the internal state 'Dao.String.UStr' and+-- 'Dao.Predicate.Predicate' value.+runXPure :: XPure a -> (Predicate ExecControl a, UStr)+runXPure = flip runState nil . runPredicateT . xpureToState++-- | Like 'Control.Monad.State.evalState', but works on the 'XPure' monad, i.e. it is defined as+-- > 'Prelude.fst' . 'runXPure'+evalXPure :: XPure a -> Predicate ExecControl a+evalXPure = fst . runXPure++-- | Like 'evalXPure' but evaluates to 'Prelude.Maybe' instead of a 'Dao.Predicate.Predicate'.+-- 'Dao.Predicate.Backtrack' and 'Dao.Predicate.PFail' both map to 'Prelude.Nothing',+-- 'Dao.Predicate.OK' maps to 'Prelude.Just'.+extractXPure :: XPure a -> Maybe a+extractXPure = okToJust . evalXPure++instance MonadError ExecControl XPure where+ throwError = XPure . throwError+ catchError (XPure f) catch = XPure $ catchError f (xpureToState . catch)++instance MonadPlusError ExecControl XPure where+ predicate = XPure . predicate+ catchPredicate (XPure f) = XPure $ catchPredicate f++instance MonadState UStr XPure where { state = XPure . lift . state }++instance Executable (XPure a) a where+ execute (XPure f) = predicate $ evalState (runPredicateT f) mempty++-- | Like 'Control.Applicative.pure' or 'Control.Monad.return' but the type is not polymorphic so+-- there is no need to annotate the monad to which you are 'Control.Monad.return'ing, which is+-- helpful when using functions like 'exceute' to convert the 'XPure' monad to the 'Exec' monad.+xpure :: a -> XPure a+xpure = pure++-- | Like 'xpure' but wraps any data type that instantiates the 'ObjectClass' class.+xobj :: ObjectClass a => a -> XPure Object+xobj = xpure . obj++-- | Append a string of any 'UStrType' to the general-purpose print stream contained within the+-- 'XPure' monad.+xnote :: UStrType s => s -> XPure ()+xnote = modify . flip mappend . toUStr++-- | Like 'xnote' but lets you operate on the 'Data.ByteString.Lazy.UTF8.ByteString'.+xonUTF8 :: (U.ByteString -> U.ByteString) -> XPure ()+xonUTF8 = modify . fmapUTF8String++-- | Works on any 'Control.Monad.MonadPlus' type, including 'Prelude.Maybe', 'Exec' and 'XPure', is+-- defined as: > 'Prelude.maybe' 'Control.Monad.mzero' 'Control.Monad.return' which is useful+-- shorthand for converting a value wrapped in a 'Prelude.Maybe' data type to a value wrapped in the+-- 'Control.Monad.MonadPlus' type.+xmaybe :: MonadPlus m => Maybe a -> m a+xmaybe = maybe mzero return++----------------------------------------------------------------------------------------------------++class ExecThrowable o where+ toExecErrorInfo :: o -> ExecErrorSubtype+ -- | Like 'Prelude.error' but works for the 'Exec' monad, throws an 'ExecControl' using+ -- 'Control.Monad.Error.throwError' constructed using the given 'Object' value as the+ -- 'execReturnValue'.+ execThrow+ :: (Monad m, MonadError ExecControl m, ExecThrowable o, UStrType msg)+ => msg -> o -> [(Name, Object)] -> m ig+ execThrow msg o info = throwError $+ newError+ { execErrorMessage = toUStr msg+ , execErrorSubtype = toExecErrorInfo o+ , execErrorInfo = M.fromList info+ }++instance ExecThrowable ExecErrorSubtype where { toExecErrorInfo = id }+instance ExecThrowable Object where { toExecErrorInfo = ExecThrow }+instance ExecThrowable StructError where { toExecErrorInfo = ExecStructError }+instance ExecThrowable Reference where { toExecErrorInfo = ExecUndefinedRef }+instance ExecThrowable IOException where { toExecErrorInfo = ExecIOException }+instance ExecThrowable ErrorCall where { toExecErrorInfo = ExecHaskellError }+instance ExecThrowable UpdateOp where { toExecErrorInfo = ExecUpdateOpError }+instance ExecThrowable (ParseError () DaoTT) where { toExecErrorInfo = ExecParseError }+instance ExecThrowable LoopCtrl where { toExecErrorInfo = ExecLoopCtrl }++ioExec :: Exec a -> ExecUnit -> IO (Predicate ExecControl a, ExecUnit)+ioExec func xunit = runStateT (runPredicateT (execToPredicate func)) xunit++----------------------------------------------------------------------------------------------------++-- | This is the data type analogous to the 'Exec' monad what 'Control.Exception.Handler' is to the+-- @IO@ monad.+newtype ExecHandler a =+ ExecHandler { execHandler :: ExecUnit -> Handler (Predicate ExecControl a, ExecUnit) }++instance Functor ExecHandler where+ fmap f (ExecHandler h) = ExecHandler (fmap (fmap (\ (p, xunit) -> (fmap f p, xunit))) h)++-- | Create an 'ExecHandler'.+newExecIOHandler :: Exception e => (e -> Exec a) -> ExecHandler a+newExecIOHandler h = ExecHandler (\xunit -> Handler (\e -> ioExec (h e) xunit))++-- | Using an 'ExecHandler' like 'execIOHandler', catch any exceptions thrown by the Haskell+-- language runtime and wrap them up in the 'Exec' monad.+execCatchIO :: Exec a -> [ExecHandler a] -> Exec a+execCatchIO tryFunc handlers = Exec $ PredicateT $ StateT $ \xunit ->+ liftIO $ catches (ioExec tryFunc xunit) (fmap (\h -> execHandler h xunit) handlers)++-- | Like 'execCatchIO' but with the arguments 'Prelude.flip'ped.+execHandleIO :: [ExecHandler a] -> Exec a -> Exec a+execHandleIO = flip execCatchIO++-- | An 'ExecHandler' for catching 'Control.Exception.ErrorCall's and re-throwing them to the+-- 'Procedural' monad using 'Control.Monad.Error.throwError', allowing the exception to be caught+-- and handled by Dao script code.+execIOHandler :: ExecHandler ()+execIOHandler = newExecIOHandler $ flip (execThrow "") [] . ExecIOException++-- | An 'ExecHandler' for catching 'Control.Exception.ErrorCall's and re-throwing them to the+-- 'Procedural' monad using 'Control.Monad.Error.throwError', allowing the exception to be caught+-- and handled by Dao script code.+execErrorHandler :: ExecHandler ()+execErrorHandler = newExecIOHandler $ flip (execThrow "") [] . ExecHaskellError++-- | This will catch an 'ExecControl' thrown by 'Control.Monad.Error.throwError', but re-throw+-- 'ExecError's.+catchReturn :: (Maybe Object -> Exec a) -> Exec a -> Exec a+catchReturn catch f = catchPredicate f >>= \pval -> case pval of+ PFail (ExecReturn a) -> catch a+ pval -> predicate pval++----------------------------------------------------------------------------------------------------+-- $StackOperations+-- Operating on the local stack.++-- | Push a new empty local-variable context onto the stack. Does NOT 'catchReturnObj', so it can be+-- used to push a new context for every level of nested if/else/for/try/catch statement, or to+-- evaluate a macro, but not a function call. Use 'execFuncPushStack' to perform a function call within+-- a function call. The stack is always poped when this function is done evaluating, even if the+-- given 'Exec' function evaluates to 'Control.Monad.mzero' or 'Control.Monad.Error.throwError'.+execNested :: T_dict -> Exec a -> Exec (a, T_dict)+execNested init exe = do+ store <- gets execStack+ modify $ \xunit -> xunit{ execStack = stackPush init store }+ result <- catchPredicate exe+ store <- gets execStack+ (store, dict) <- pure (stackPop store)+ modify $ \xunit -> xunit{ execStack = store }+ result <- predicate result+ return (result, dict)++-- | Like 'execNested' but immediately disgards the local variables when the inner 'Exec' function+-- has completed evaluation.+execNested_ :: T_dict -> Exec a -> Exec a+execNested_ init = fmap fst . execNested init++-- | Keep the current 'execStack', but replace it with a new empty stack before executing the given+-- function. This function is different from 'nestedExecStak' in that it acually removes the current+-- execution stack so a function call cannot modify the local variables of the function which called+-- it. Furthermore it catches evaluation of a "return" statement allowing the function which called+-- it to procede with execution after this call has returned.+execFuncPushStack :: T_dict -> Exec (Maybe Object) -> Exec (Maybe Object, T_dict)+execFuncPushStack dict exe = execNested dict (catchPredicate exe) >>= \ (pval, dict) -> case pval of+ OK o -> return (o, dict)+ Backtrack -> mzero+ PFail err -> case err of+ ExecReturn o -> return (o, dict)+ ExecError{execErrorSubtype=ExecLoopCtrl ctrl} -> throwError $+ err{execErrorSubtype=ExecLoopCtrl $ ctrl{loopCtrlEscaped=False}}+ err -> throwError err++execFuncPushStack_ :: T_dict -> Exec (Maybe Object) -> Exec (Maybe Object)+execFuncPushStack_ dict = fmap fst . execFuncPushStack dict++execWithStaticStore :: Subroutine -> Exec a -> Exec a+execWithStaticStore sub exe = do+ store <- gets currentCodeBlock+ modify (\st -> st{ currentCodeBlock=Just sub })+ result <- catchPredicate exe+ modify (\st -> st{ currentCodeBlock=store })+ predicate result++execWithWithRefStore :: Object -> Exec a -> Exec a+execWithWithRefStore o exe = do+ store <- gets currentWithRef+ modify (\st -> st{ currentWithRef=Just o })+ result <- catchPredicate exe+ modify (\st -> st{ currentWithRef=store })+ predicate result++withExecTokenizer :: ExecTokenizer -> Exec a -> Exec a+withExecTokenizer newtokzer f = do+ oldtokzer <- gets programTokenizer+ modify $ \xunit -> xunit{ programTokenizer=newtokzer }+ p <- catchPredicate f+ modify $ \xunit -> xunit{ programTokenizer=oldtokzer }+ predicate p++----------------------------------------------------------------------------------------------------++instance (Typeable a, ObjectClass a) => ToDaoStructClass (Com a) where+ toDaoStruct = renameConstructor "Com" $ do+ co <- ask+ let put o = "com" .= obj o+ case co of+ Com o -> put o >> return ()+ ComBefore c1 o -> "before" .= c1 >> put o >> return ()+ ComAfter o c2 -> put o >> "after" .= c2 >> return ()+ ComAround c1 o c2 -> "before" .= c1 >> put o >> "after" .= c2 >> return ()++instance (Typeable a, ObjectClass a) => FromDaoStructClass (Com a) where+ fromDaoStruct = do+ constructor "Com"+ let f name = tryField name (maybe (fail name) return . fromObj)+ before <- optional $ f "before"+ after <- optional $ f "after"+ o <- req "com"+ return $ maybe (Com o) id $ msum $+ [ return ComAround <*> before <*> pure o <*> after+ , return ComBefore <*> before <*> pure o+ , return ComAfter <*> pure o <*> after+ ]++instance (Typeable a, ObjectClass a) =>+ ObjectClass (Com a) where { obj=new; fromObj=objFromHata; }+instance (Typeable a, ObjectClass a) => HataClass (Com a) where+ haskellDataInterface = interface "Com" $ do+ autoDefToStruct >> autoDefFromStruct++instance (Typeable a, ObjectClass a) =>+ ObjectClass [Com a] where { obj=listToObj; fromObj=listFromObj; }++----------------------------------------------------------------------------------------------------++setupCodeBlock :: CodeBlock Object -> Subroutine+setupCodeBlock scrp =+ Subroutine+ { origSourceCode = scrp+ , staticVars = mempty+ , staticRules = mempty+ , staticLambdas = []+ , executable = execute scrp >> return Nothing+ }++-- binary 0xDD +instance B.Binary (CodeBlock Object) MTab where+ put (CodeBlock o) = B.prefixByte 0xDD $ B.put o+ get = B.tryWord8 0xDD $ CodeBlock <$> B.get++instance Executable (CodeBlock Object) () where { execute (CodeBlock ox) = mapM_ execute ox }++instance ObjectClass (CodeBlock Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (CodeBlock Object) where+ haskellDataInterface = interface "CodeBlock" $ do+ autoDefNullTest >> autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt >> autoDefPPrinter+ defDeref $ \o -> catchError (execute o >> return Nothing) $ \e -> case e of+ ExecReturn o -> return o+ ExecError{} -> throwError e+ -- TODO: define autoDefIterator, defIndexer, autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- | A subroutine is contains a 'CodeBlock' and an 'Data.IORef.IORef' to it's own static data. It+-- also has a reference to the last evaluation of 'execute' over it's 'CodeBlock', which provides a+-- hint to the Haskell runtime system that this code can be cached rather than evaluating the+-- 'CodeBlock' fresh every time. In a sense, it is a "live" 'CodeBlock' that can actually be+-- executed.+data Subroutine+ = Subroutine+ { origSourceCode :: CodeBlock Object+ , staticVars :: T_dict+ , staticRules :: PatternTree Object [Subroutine]+ , staticLambdas :: [CallableCode]+ , executable :: Exec (Maybe Object)+ }+ deriving Typeable++instance Eq Subroutine where { a==b = origSourceCode a == origSourceCode b }++instance Ord Subroutine where { compare a b = compare (origSourceCode a) (origSourceCode b) }++instance Show Subroutine where { show o = "Subroutine "++show (codeBlock (origSourceCode o)) }++instance NFData Subroutine where { rnf (Subroutine a _ _ _ _) = deepseq a () }++instance HasNullValue Subroutine where+ nullValue =+ Subroutine+ { origSourceCode = nullValue+ , staticVars = mempty+ , staticRules = mempty+ , staticLambdas = []+ , executable = return Nothing+ }+ testNull (Subroutine a _ _ _ _) = testNull a++instance PPrintable Subroutine where+ pPrint = pPrint . flip MetaEvalExpr LocationUnknown . origSourceCode++instance ToDaoStructClass Subroutine where+ toDaoStruct = renameConstructor "Subroutine" $ do+ "code" .=@ origSourceCode+ "vars" .=@ staticVars+ "rules" .=@ (\rs -> RuleSet{ ruleSetRules=rs, ruleSetTokenizer=Nothing }) . staticRules+ "lambdas" .=@ staticLambdas++instance FromDaoStructClass Subroutine where+ fromDaoStruct = do+ constructor "Subroutine"+ sub <- setupCodeBlock <$> req "code"+ vars <- req "vars"+ (RuleSet{ ruleSetRules=rules }) <- req "rules"+ lambdas <- req "lambdas"+ return $ sub{ staticVars=vars, staticRules=rules, staticLambdas=lambdas }++instance Executable Subroutine (Maybe Object) where+ execute sub = execWithStaticStore sub $+ catchReturn return ((execute (origSourceCode sub) :: Exec ()) >> return Nothing) :: Exec (Maybe Object)++instance ObjectClass Subroutine where { obj=new; fromObj=objFromHata; }++instance HataClass Subroutine where+ haskellDataInterface = interface "Subroutine" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++-- | Although 'Subroutine' instantiates 'Executable', this function allows you to easily place a+-- group of defined local variables onto the call stack before and the have the 'Subroutine'+-- executed.+runCodeBlock :: T_dict -> Subroutine -> Exec (Maybe Object, T_dict)+runCodeBlock initStack sub = execWithStaticStore sub $+ execFuncPushStack initStack (executable sub >>= liftIO . evaluate)++runCodeBlock_ :: T_dict -> Subroutine -> Exec (Maybe Object)+runCodeBlock_ initStack = fmap fst . runCodeBlock initStack++----------------------------------------------------------------------------------------------------++data RuleSet+ = RuleSet+ { ruleSetRules :: PatternTree Object [Subroutine]+ , ruleSetTokenizer :: Maybe ExecTokenizer+ }+ deriving Typeable++instance HasNullValue RuleSet where+ nullValue = RuleSet{ ruleSetRules=nullValue, ruleSetTokenizer=Nothing }+ testNull (RuleSet{ruleSetRules=r, ruleSetTokenizer=tok}) =+ testNull r && maybe True (const False) tok++instance Monoid RuleSet where+ mempty = nullValue+ mappend (RuleSet{ruleSetRules=a, ruleSetTokenizer=tokA}) (RuleSet{ruleSetRules=b, ruleSetTokenizer=tokB}) =+ RuleSet{ ruleSetRules=mappend a b, ruleSetTokenizer=mplus tokA tokB }++instance Sizeable RuleSet where { getSizeOf = return . obj . T.size . ruleSetRules }++instance PPrintable RuleSet where+ pPrint (RuleSet{ ruleSetRules=tree }) = pList (pString "RuleSet") "{ " ", " "}" $+ T.assocs tree >>= \ (ix, subs) -> do+ let rule ix = case ix of+ [] -> ""+ Single (OString s) : ix -> uchars s ++ rule ix+ Single o : ix -> prettyShow o ++ rule ix+ i : ix -> show i ++ rule ix+ sub <- subs+ [ pClosure (pString "rule " >> pShow (rule ix)) "{" "}" $+ map pPrint $ codeBlock $ origSourceCode sub ]++instance ObjectClass RuleSet where { obj=new ; fromObj=objFromHata; }++instance HataClass RuleSet where+ haskellDataInterface = interface "RuleSet" $ do+ autoDefNullTest >> autoDefSizeable >> autoDefPPrinter+ let qrefRuleSet = reference UNQUAL (ustr "RuleSet")+ let initParams ox = case ox of+ [] -> return $ RuleSet{ ruleSetRules=mempty, ruleSetTokenizer=Nothing }+ [o] -> do+ let err :: Exec RuleSet+ err = throwBadTypeError "" o [(errInConstr, obj qrefRuleSet)]+ maybe err return $ do+ fromObj o >>= \ (Hata ifc _) -> objCallable ifc+ let tok = ExecTokenizer $ \ox -> do+ toks <- fst <$> callObject (RefObject o NullRef) o [obj ox]+ mplus (xmaybe $ toks >>= fromObj) $+ execThrow "tokenizer for rule function did not return a list of objects" ExecErrorUntyped []+ return $ RuleSet{ ruleSetRules=mempty, ruleSetTokenizer=Just tok }+ _ -> throwArityError "" 1 ox [(errInInitzr, obj qrefRuleSet)]+ -- TODO: ^ the constructor for a 'PatternTree' should take tokenizer function.+ let listParams tree =+ foldM (\ rs@(RuleSet{ruleSetRules=tree, ruleSetTokenizer=maybeTok}) (i, o) -> case o of+ InitSingle o -> case fromObj o >>= \ (Hata _ d) -> fromDynamic d of+ Nothing -> throwBadTypeError "expecting rule or RuleSet" o $+ [(errInFunc, obj qrefRuleSet), (errInConstr, OInt i)]+ Just p -> do+ newtree <- maybe id withExecTokenizer maybeTok $ execute (p::PatternRule)+ return $ rs{ ruleSetRules=T.unionWith (++) tree newtree }+ InitAssign{} -> fail "cannot use assignment expression in initializer of RuleSet"+ ) tree . zip [1..]+ defInitializer initParams listParams+ defInfixOp ORB $ \ _ rs o -> fmap (obj . mappend rs) $ mplus (xmaybe $ fromObj o) $+ (throwBadTypeError "when uninioning RuleSet values" o [])+ let run f =+ daoFunc+ { daoForeignFunc = \rs ->+ runTokenizer >=> makeActionsForQuery [ruleSetRules rs] >=> fmap (flip (,) rs) . f+ }+ defMethod "query" $ run $ return . Just . obj . fmap obj+ defMethod "do" $ run $ msum . fmap execute+ defMethod "doAll" $ run $ fmap (Just . obj . fmap obj) . execute+ defMethod "tokenize" $+ daoFunc+ { daoForeignFunc = \rs -> fmap (flip (,) rs . Just . obj) .+ maybe runTokenizer runTokenizerWith (ruleSetTokenizer rs)+ }++----------------------------------------------------------------------------------------------------++-- | A subroutine is specifically a callable function (but we don't use the name Function to avoid+-- confusion with Haskell's "Data.Function"). +data CallableCode+ = CallableCode+ { argsPattern :: ParamListExpr Object+ , returnType :: ObjType+ , codeSubroutine :: Subroutine+ }+ deriving (Show, Typeable)++-- Used by the instantiation of CallableCode and PatternRule into the PPrintable class.+ppCallableAction :: String -> PPrint -> ObjType -> Subroutine -> PPrint+ppCallableAction what pats typ exe =+ pClosure (pString what >> pats >> pPrint typ) "{" "}" (map pPrint (codeBlock (origSourceCode exe)))++-- | Interface used during evaluation of Dao scripts to determine whether or not an if or while+-- statement should continue. Also, turns out to be handy for plenty of other reasons.+instance HasNullValue CallableCode where+ nullValue =+ CallableCode{argsPattern=nullValue, returnType=nullValue, codeSubroutine=nullValue}+ testNull (CallableCode a b c) = testNull a && testNull b && testNull c++instance NFData CallableCode where { rnf (CallableCode a b _) = deepseq a $! deepseq b () }++instance PPrintable [CallableCode] where + pPrint = sequence_ . intersperse (pString " ^ ") .+ fmap (\ (CallableCode pats ty exe) -> ppCallableAction "function" (pPrint pats) ty exe >> pEndLine)++instance ObjectClass [CallableCode] where { obj=new; fromObj=objFromHata; }++instance HataClass [CallableCode] where+ haskellDataInterface = interface "Function" $ do+ autoDefNullTest >> autoDefPPrinter+ defCallable return+ defInfixOp XORB $ \ _ a o -> case fromObj o of+ Just b -> return $ obj (a++b)+ Nothing -> fail "left-hand side of bitwise-XOR operator (^) is a Function, right hand side is not"++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_CodeBlock Object) where+ toDaoStruct = renameConstructor "CodeBlock" $ "list" .=@ getAST_CodeBlock++instance FromDaoStructClass (AST_CodeBlock Object) where+ fromDaoStruct = constructor "CodeBlock" >> AST_CodeBlock <$> req "list"++instance ObjectClass (AST_CodeBlock Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_CodeBlock Object) where+ haskellDataInterface = interface "CodeBlockExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0xC5 0xC7+instance B.Binary a MTab => B.Binary (TyChkExpr a Object) MTab where+ put o = case o of+ NotTypeChecked a -> B.prefixByte 0xC5 $ B.put a+ TypeChecked a b c -> B.prefixByte 0xC6 $ B.put a >> B.put b >> B.put c+ DisableCheck a b c d -> B.prefixByte 0xC7 $ B.put a >> B.put b >> B.put c >> B.put d+ get = B.word8PrefixTable <|> fail "expecting TyChkExpr"++instance B.Binary a MTab => B.HasPrefixTable (TyChkExpr a Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "TyChkExpr" 0xC5 0xC7 $+ [ NotTypeChecked <$> B.get+ , return TypeChecked <*> B.get <*> B.get <*> B.get+ , return DisableCheck <*> B.get <*> B.get <*> B.get <*> B.get+ ]++instance (Eq a, Ord a, Typeable a, ObjectClass a) =>+ ObjectClass (TyChkExpr Object a) where { obj=new; fromObj=objFromHata; }++instance (Eq a, Ord a, Typeable a, ObjectClass a) =>+ HataClass (TyChkExpr Object a) where+ haskellDataInterface = interface "TypedExec" $ do+ autoDefEquality >> autoDefOrdering++----------------------------------------------------------------------------------------------------++instance ObjectClass a => ToDaoStructClass (AST_TyChk a Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_NotChecked o -> renameConstructor "UntypedExpression" $ "expr" .= obj o+ AST_Checked o coms typ loc -> renameConstructor "TypedExpression" $ do+ "expr" .= obj o+ "colon" .= coms+ "typeExpr" .= typ+ putLocation loc++instance (Typeable a, ObjectClass a) => FromDaoStructClass (AST_TyChk a Object) where+ fromDaoStruct = msum $+ [do constructor "UntypedExpression"+ AST_NotChecked <$> req "expr"+ ,do constructor "TypedExpression"+ return AST_Checked <*> req "expr" <*> req "colon" <*> req "typeExpr" <*> location+ ]++instance (Eq a, Ord a, PPrintable a, Typeable a, ObjectClass a) =>+ ObjectClass (AST_TyChk a Object) where { obj=new; fromObj=objFromHata; }++instance (Eq a, Ord a, PPrintable a, Typeable a) => HataClass (AST_TyChk a Object) where+ haskellDataInterface = interface "TypedExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter+ -- autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0xCF 0xD0+instance B.Binary (ParamExpr Object) MTab where+ put (ParamExpr True a b) = B.prefixByte 0xCF $ B.put a >> B.put b+ put (ParamExpr False a b) = B.prefixByte 0xD0 $ B.put a >> B.put b+ get = B.word8PrefixTable <|> fail "expecting ParamExpr"++instance B.HasPrefixTable (ParamExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "ParamExpr" 0xCF 0xD0 $+ [ return (ParamExpr True ) <*> B.get <*> B.get+ , return (ParamExpr False) <*> B.get <*> B.get+ ]++instance ObjectClass (ParamExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ParamExpr Object) where+ haskellDataInterface = interface "ParamExpr" $ do+ autoDefEquality >> autoDefOrdering >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Param Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_NoParams -> makeNullary "NoParameters"+ AST_Param coms tychk loc -> renameConstructor "Parameter" $ do+ maybe (return ()) putComments coms+ "typeCheck" .= tychk+ putLocation loc++instance FromDaoStructClass (AST_Param Object) where+ fromDaoStruct = msum $+ [ nullary "NoParameters" >> return AST_NoParams+ , constructor "Parameter" >> return AST_Param <*> optComments <*> req "typeCheck" <*> location+ ]++instance ObjectClass (AST_Param Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Param Object) where+ haskellDataInterface = interface "ParameterExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0xD6 +instance B.Binary (ParamListExpr Object) MTab where+ put (ParamListExpr tyChk loc) = B.prefixByte 0xD6 $ B.put tyChk >> B.put loc+ get = B.word8PrefixTable <|> fail "expecting ParamListExpr"++instance B.HasPrefixTable (ParamListExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "ParamListExpr" 0xD6 0xD6 $+ [return ParamListExpr <*> B.get <*> B.get]++instance ObjectClass (ParamListExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ParamListExpr Object) where+ haskellDataInterface = interface "ParameterList" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_ParamList Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_ParamList tychk loc -> renameConstructor "ParamList" $ do+ "typeCheck" .= tychk+ putLocation loc++instance FromDaoStructClass (AST_ParamList Object) where+ fromDaoStruct = constructor "ParamList" >> return AST_ParamList <*> req "typeCheck" <*> location++instance ObjectClass (AST_ParamList Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_ParamList Object) where+ haskellDataInterface = interface "ParameterListExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0x7A 0x7B+instance B.Binary (RuleHeadExpr Object) MTab where+ put o = case o of+ RuleStringExpr a b -> B.prefixByte 0x7A $ B.put a >> B.put b+ RuleHeadExpr a b -> B.prefixByte 0x7B $ B.put a >> B.put b+ get = B.word8PrefixTable <|> fail "expecting RuleHeadExpr"++instance B.HasPrefixTable (RuleHeadExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "RuleHeadExpr" 0x7A 0x7B+ [ return RuleStringExpr <*> B.get <*> B.get+ , return RuleHeadExpr <*> B.get <*> B.get+ ]++instance Executable (RuleHeadExpr Object) [Object] where+ execute o = case o of+ RuleStringExpr r _ -> return [obj r]+ RuleHeadExpr r _ -> forM r $+ execute . DerefAssignExpr >=> checkVoid (getLocation o) "item in rule header"++instance ObjectClass (RuleHeadExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (RuleHeadExpr Object) where+ haskellDataInterface = interface "RuleHeader" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_RuleHeader Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_NullRules coms loc -> renameConstructor "NoStrings" $ do+ putComments coms >> putLocation loc+ AST_RuleString itm loc -> renameConstructor "StringItem" $ do+ "items" .= itm >> putLocation loc+ AST_RuleHeader lst loc -> renameConstructor "ValuesList" $ do+ "items" .= lst >> putLocation loc++instance FromDaoStructClass (AST_RuleHeader Object) where+ fromDaoStruct = msum $+ [ constructor "NoStrings" >> return AST_NullRules <*> comments <*> location+ , constructor "StringItem" >> return AST_RuleString <*> req "items" <*> location+ , constructor "ValuesList" >> return AST_RuleHeader <*> reqList "items" <*> location+ ]++instance ObjectClass (AST_RuleHeader Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_RuleHeader Object) where+ haskellDataInterface = interface "RuleHeaderExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- $Exec_helpers+-- Functions for working with object values when building built-in functions callable from within a+-- dao script.++asReference :: Object -> XPure Reference+asReference = xmaybe . fromObj++_getRuleSetParam :: [Object] -> Exec (Maybe RuleSet, [Object])+_getRuleSetParam ox = return $ case ox of+ [] -> (Nothing, [])+ o:ox -> maybe ((Nothing, o:ox)) (\o -> (Just o, ox)) (fromObj o)++-- Checks the list of parameters, and if there are more than one, checks if the first parameter is+-- an 'OHaskell' object which can be converted to 'CallableCode' using 'objToCallable'. If so, the+-- callables are returned and the results of pattern matching can be used as parameters to these+-- functions.+_getFuncStringParams :: [Object] -> Exec (Maybe [CallableCode], [UStr])+_getFuncStringParams ox = case ox of+ [] -> fail "no parameters passed to function"+ [o] -> (,) Nothing <$> oneOrMoreStrings [o]+ o:lst -> do+ calls <- (Just <$> objToCallable o) <|> return Nothing+ (,) calls <$> oneOrMoreStrings lst+ where+ oneOrMoreStrings ox = case concatMap extractStringElems ox of+ [] -> fail "parameter arguments contain no string values"+ ox -> return ox++asInteger :: Object -> XPure Integer+asInteger o = case o of+ OWord o -> return (toInteger o)+ OInt o -> return (toInteger o)+ OLong o -> return o+ OFloat o -> return (round o)+ ORatio o -> return (round o)+ ORelTime o -> return (round (toRational o))+ _ -> mzero++asRational :: Object -> XPure Rational+asRational o = case o of+ OInt o -> return (toRational o)+ OWord o -> return (toRational o)+ OLong o -> return (toRational o)+ OFloat o -> return (toRational o)+ ORelTime o -> return (toRational o)+ ORatio o -> return o+ OComplex o | imagPart o == 0 -> return (toRational (realPart o))+ _ -> mzero++asComplex :: Object -> XPure T_complex+asComplex o = case o of+ OComplex o -> return o+ o -> asRational o >>= return . flip complex 0 . fromRational++-- | A function which is basically the absolute value function, except it also works on 'Complex'+-- numbers, returning the magnitude of the number if it is 'Complex'.+asPositive :: Object -> XPure Object+asPositive o = case o of+ OInt o -> return (OInt $ abs o)+ OWord o -> return (OWord o)+ OLong o -> return (OLong $ abs o)+ OFloat o -> return (OFloat $ abs o)+ ORelTime o -> return (ORelTime $ abs o)+ OComplex o -> return (OFloat $ magnitude o)+ _ -> mzero++-- | Remove one layer of 'OList' objects, i.e. any objects in the list that are 'OList' constructors+-- will have the contents of those lists concatenated, and all non-'OList' constructors are treated+-- as lists of single objects. Also returns the number of concatenations.+objConcat :: [Object] -> (Int, [Object])+objConcat ox = (sum a, concat b) where+ (a, b) = unzip (ox >>= \o -> maybe [(0, [o])] (return . (,) 1) (fromObj o))++-- | Checks if an 'Object' is a numerical type, returns the numeric 'CoreType' if so, evaluates to+-- 'Control.Monad.mzero' if not.+isNumeric :: Object -> XPure CoreType+isNumeric o = do+ let t = coreType o+ guard (CharType <= t && t <= ComplexType)+ return t++eval_Prefix_op :: ArithPfxOp -> Object -> XPure Object+eval_Prefix_op op o = join $ xmaybe $+ fromObj o >>= \ (Hata ifc o) -> objArithPfxOpTable ifc >>= (! op) >>= \f -> return (f op o)++-- Pass the 'InfixOp' associated with the 'Prelude.Num' function so it can check whether the+-- 'defInfixOp' for that operator has been defined for objects of 'HaskellType'.+eval_Infix_op :: InfixOp -> Object -> Object -> XPure Object+eval_Infix_op op a b = join $ xmaybe $ onhask a b <|> (guard isCommut >> onhask b a) where+ isCommut = infixOpCommutativity op+ onhask a b = fromObj a >>= \ (Hata ifc a) ->+ (\f -> f op a b) <$> (objInfixOpTable ifc >>= (! op))++_evalNumOp1 :: (forall a . Num a => a -> a) -> Object -> XPure Object+_evalNumOp1 f o = case o of+ OChar o -> return $ OChar $ chr $ mod (f $ ord o) (ord maxBound)+ OInt o -> return $ OInt $ f o+ OWord o -> return $ OWord $ f o+ OLong o -> return $ OLong $ f o+ ORelTime o -> return $ ORelTime $ f o+ OFloat o -> return $ OFloat $ f o+ ORatio o -> return $ ORatio $ f o+ OComplex o -> return $ OComplex $ f o+ _ -> mzero++-- Evaluate a 2-ary function for any core type that instantiates the 'Prelude.Num' class.+_evalNumOp2 :: (forall a . Num a => a -> a -> a) -> Object -> Object -> XPure Object+_evalNumOp2 f a b = do+ let t = max (coreType a) (coreType b)+ a <- castToCoreType t a+ b <- castToCoreType t b+ case (a, b) of+ (OChar a, OChar b) -> return $ OChar $ chr $ mod (f (ord a) (ord b)) (ord maxBound)+ (OInt a, OInt b) -> return $ OInt $ f a b+ (OWord a, OWord b) -> return $ OWord $ f a b+ (OLong a, OLong b) -> return $ OLong $ f a b+ (ORelTime a, ORelTime b) -> return $ ORelTime $ f a b+ (OFloat a, OFloat b) -> return $ OFloat $ f a b+ (ORatio a, ORatio b) -> return $ ORatio $ f a b+ (OComplex a, OComplex b) -> return $ OComplex $ f a b+ _ -> mzero++instance Num (XPure Object) where+ a + b = a >>= \a -> b >>= \b -> case (a, b) of+ (OString a, OString b) -> return $ OString $ a<>b+ (OBytes a, OBytes b) -> return $ OBytes $ a<>b+ (OList a, OList b) -> return $ OList $ a++b+ (ODict a, ODict b) -> return $ ODict $ M.union b a+ (a, b) -> _evalNumOp2 (+) a b <|> eval_Infix_op ADD a b+ a * b = a >>= \a -> case a of+ OList ax -> OList <$> mapM ((* b) . xpure) ax+ ODict ax -> (ODict . M.fromList) <$>+ mapM (\ (key, a) -> fmap ((,) key) (xpure a * b)) (M.assocs ax)+ a -> b >>= \b -> case b of+ OList bx -> OList <$> mapM (((xpure a) *) . xpure) bx+ ODict bx -> (ODict . M.fromList) <$>+ mapM (\ (key, b) -> fmap ((,) key) (xpure a * xpure b)) (M.assocs bx)+ b -> _evalNumOp2 (*) a b <|> eval_Infix_op MULT a b+ a - b = a >>= \a -> b >>= \b -> case a of+ -- on strings, the inverse operation of "join(b, a)", every occurence of b from a.+ OString a -> case b of+ OString b -> return $ OString $ mconcat $ splitString a b+ _ -> mzero+ -- on lists, removes the item b from the list a+ OList a -> return $ OList $ filter (/= b) a+ ODict a -> case b of+ ODict b -> return $ ODict $ M.difference a b+ ORef (Reference UNQUAL b NullRef) -> return $ ODict $ M.delete b a+ ORef _ ->+ execThrow "dictionary key must be a single unqualified name" ExecErrorUntyped [(assertFailed, b)]+ _ -> mzero+ a -> _evalNumOp2 (-) a b <|> eval_Infix_op SUB a b+ negate a = (a >>= _evalNumOp1 negate) <|> (a >>= eval_Prefix_op NEGTIV)+ abs a = (a >>= _evalNumOp1 abs ) <|> (a >>= eval_Prefix_op NEGTIV)+ signum a = a >>= _evalNumOp1 signum+ fromInteger = return . obj++_xpureApplyError :: String -> String -> a+_xpureApplyError name msg = error $ concat $ concat $+ [["cannot evaluate ", name], guard (not $ null msg) >> [": ", msg]]++_xpureApply2 :: String -> (Object -> Object -> a) -> XPure Object -> XPure Object -> a+_xpureApply2 name f a b = case evalXPure ab of+ PFail e -> _xpureApplyError name (prettyShow e)+ Backtrack -> _xpureApplyError name ""+ OK o -> o+ where+ ab = do+ ta <- coreType <$> a+ tb <- coreType <$> b+ let t = max ta tb+ a <- a >>= castToCoreType t+ b <- b >>= castToCoreType t+ return (f a b)++_xpureApply1 :: String -> (Object -> XPure a) -> XPure Object -> a+_xpureApply1 name f o = case evalXPure $ o >>= f of+ PFail e -> _xpureApplyError name (prettyShow e)+ Backtrack -> _xpureApplyError name ""+ OK o -> o++_xpureMaybeApply1 :: (Object -> XPure a) -> XPure Object -> Maybe a+_xpureMaybeApply1 f o = case evalXPure $ o >>= f of+ PFail _ -> Nothing+ Backtrack -> Nothing+ OK o -> Just o++instance Eq (XPure Object) where+ (==) = _xpureApply2 "(==)" (==)+ (/=) = _xpureApply2 "(/=)" (/=)++instance Ord (XPure Object) where+ compare = _xpureApply2 "compare" compare+ (<) = _xpureApply2 "(<)" (<)+ (<=) = _xpureApply2 "(<=)" (<=)+ (>) = _xpureApply2 "(>)" (>)+ (>=) = _xpureApply2 "(>=)" (>=)++instance Real (XPure Object) where+ toRational = _xpureApply1 "toRational" $ castToCoreType LongType >=> xmaybe . fromObj++eval_Int_op1 :: String -> (forall a . Integral a => a -> a) -> XPure Object -> XPure Object+eval_Int_op1 name f o = o >>= \o -> case o of+ OChar o -> return $ OChar (chr $ flip mod (ord maxBound) $ f $ ord o)+ OInt o -> return $ OInt (f o)+ OWord o -> return $ OWord (f o)+ OLong o -> return $ OLong (f o)+ _ -> throwBadTypeError "wrong data type for object passed to function" o $+ [(errInFunc, obj (ustr name :: Name))]++_xpureCastTo :: XPure CoreType -> XPure Object -> XPure Object+_xpureCastTo typ a = join $ xpure castToCoreType <*> typ <*> a++-- In order for @('XPure' 'Object')@ to be used with the 'Prelude.Div' and 'Prelude.mod' functions,+-- it must instantiate 'Integral', which means it must instantiate 'Prelude.Enum'. This+-- instantiation is an attempt at making the functions behave as they would for ordinary enumerated+-- data types; it is /NOT/ pretty, but it basically works.+instance Enum (XPure Object) where+ succ = eval_Int_op1 "succ" succ+ pred = eval_Int_op1 "pred" pred+ toEnum = return . obj+ fromEnum = _xpureApply1 "fromEnum" $ castToCoreType IntType >=> xmaybe . fromObj+ enumFrom = fix (\loop o -> o : loop (succ o))+ enumFromThen lo hi = fix (\loop o -> o : loop (hi-lo+o)) lo+ enumFromTo a b =+ if maybe False (const True) (extractXPure typ)+ then case compare aa bb of+ EQ -> repeat aa+ LT -> loop (<bb) inc aa+ GT -> loop (>bb) (negate inc) aa+ else []+ where+ loop ok inc a = a : let b = a+inc in if ok b then loop ok inc b else []+ typ = a >>= \a -> b >>= \b -> do+ let t = max (coreType a) (coreType b)+ guard (CharType <= t && t <= ComplexType) >> xpure t+ inc = typ >>= flip castToCoreType (OChar '\x01')+ aa = _xpureCastTo typ a+ bb = _xpureCastTo typ b+ enumFromThenTo a b c =+ if maybe False (const True) $ extractXPure typ+ then case compare aa bb of+ EQ -> repeat aa+ LT -> if aa<cc then loop (<cc) aa else []+ GT -> if aa>cc then loop (>cc) aa else []+ else []+ where+ loop ok a = a : let b = a+inc in if ok b then loop ok b else []+ typ = a >>= \a -> b >>= \b -> c >>= \c -> do+ let t = max (coreType a) $ max (coreType b) $ (coreType c)+ guard (CharType <= t && t <= RatioType) >> xpure t+ inc = bb-aa+ aa = _xpureCastTo typ a+ bb = _xpureCastTo typ b+ cc = _xpureCastTo typ c++_xpureDivFunc+ :: String+ -> (forall a . Integral a => a -> a -> (a, a))+ -> XPure Object -> XPure Object -> (XPure Object, XPure Object)+_xpureDivFunc name div a b = _xpureApply2 name f aa bb where+ f a b = case (a, b) of+ (OChar a, OChar b) -> pair (ord a) (ord b) (OChar . chr)+ (OInt a, OInt b) -> pair a b OInt+ (OWord a, OWord b) -> pair a b OWord+ (OLong a, OLong b) -> pair a b OLong+ _ -> (mzero, mzero)+ pair a b constr = let (c, d) = div a b in (xpure $ constr c, xpure $ constr d)+ typ = a >>= \a -> b >>= \b -> do+ let t = max (coreType a) (coreType b)+ guard (CharType <= t && t <= LongType) >> xpure t+ aa = _xpureCastTo typ a+ bb = _xpureCastTo typ b++instance Integral (XPure Object) where+ toInteger = _xpureApply1 "toInteger" (castToCoreType LongType >=> xmaybe . fromObj)+ quotRem a b = _xpureDivFunc "quoteRem" quotRem a b+ divMod a b = _xpureDivFunc "divMod" divMod a b+ div a b = a >>= \a -> b >>= \b -> case (a, b) of+ (OString a, OString b) -> return $ OWord $ fromIntegral $ length $ splitString a b+ _ -> fst (_xpureDivFunc "(/)" divMod (xpure a) (xpure b)) <|> eval_Infix_op DIV a b+ mod a b = a >>= \a -> b >>= \b -> case (a, b) of+ (OString a, OString b) -> return $ OList $ map OString $ splitString a b+ _ -> snd (_xpureDivFunc "(%)" divMod (xpure a) (xpure b)) <|> eval_Infix_op MOD a b++_xpureFrac :: (forall a . Floating a => a -> a) -> XPure Object -> XPure Object+_xpureFrac f a = a >>= \a -> case a of+ ORelTime a -> xpure $ ORelTime $ fromRational $ toRational $+ f (fromRational (toRational a) :: Double)+ OFloat a -> xpure $ OFloat $ f a+ ORatio a -> xpure $ ORatio $ toRational $ f $ (fromRational a :: Double)+ OComplex a -> xpure $ OComplex $ f a+ _ -> mzero++_xpureFrac2 :: (forall a . Floating a => a -> a -> a) -> XPure Object -> XPure Object -> XPure Object+_xpureFrac2 f a b = a >>= \a -> b >>= \b -> do+ let t = max (coreType a) (coreType b)+ a <- castToCoreType t a+ b <- castToCoreType t b+ case (a, b) of+ (ORelTime a, ORelTime b) -> xpure $ ORelTime $ fromRational $ toRational $+ f (fromRational (toRational a) :: Double) (fromRational (toRational b) :: Double)+ (OFloat a, OFloat b) -> xpure $ OFloat $ f a b+ (ORatio a, ORatio b) -> xpure $ ORatio $ toRational $+ f (fromRational a :: Double) (fromRational b :: Double)+ (OComplex a, OComplex b) -> xpure $ OComplex $ f a b+ _ -> mzero++instance Fractional (XPure Object) where+ a / b = _xpureFrac2 (/) a b+ recip = _xpureFrac recip+ fromRational = xpure . ORatio++instance Floating (XPure Object) where+ pi = xpure $ OFloat pi+ logBase = _xpureFrac2 logBase+ (**) a b = _xpureFrac2 (**) a b+ exp = _xpureFrac exp+ sqrt = _xpureFrac sqrt+ log = _xpureFrac log+ sin = _xpureFrac sin+ tan = _xpureFrac tan+ cos = _xpureFrac cos+ asin = _xpureFrac asin+ atan = _xpureFrac atan+ acos = _xpureFrac acos+ sinh = _xpureFrac sinh+ tanh = _xpureFrac tanh+ cosh = _xpureFrac cosh+ asinh = _xpureFrac asinh+ atanh = _xpureFrac atanh+ acosh = _xpureFrac acosh++_xpureRealFrac :: Integral b => String -> (forall a . RealFrac a => a -> b) -> XPure Object -> b+_xpureRealFrac name f = _xpureApply1 name $ \o -> case o of+ ORelTime a -> xpure $ f a+ OFloat a -> xpure $ f a+ ORatio a -> xpure $ f a+ _ -> mzero++instance RealFrac (XPure Object) where+ properFraction = let name = "properFraction" in _xpureApply1 name $ \o -> case o of+ ORelTime o -> f ORelTime o+ OFloat o -> f OFloat o+ ORatio o -> f ORatio o+ _ -> xpure (error $ "cannot evaluate properFraction on object "++prettyShow o, mzero)+ where { f constr o = let (i, b) = properFraction o in xpure (i, xpure $ constr b) }+ truncate = _xpureRealFrac "truncate" truncate+ round = _xpureRealFrac "round" round+ ceiling = _xpureRealFrac "ceiling" ceiling+ floor = _xpureRealFrac "floor" floor++_xpureBits :: (forall a . Bits a => a -> a) -> (B.ByteString -> B.ByteString) -> XPure Object -> XPure Object+_xpureBits f g o = o >>= \o -> case o of+ OChar o -> return $ OChar $ chr $ mod (f $ ord o) (ord maxBound)+ OInt o -> return $ OInt $ f o+ OWord o -> return $ OWord $ f o+ OLong o -> return $ OLong $ f o+ OBytes o -> return $ OBytes $ g o+ _ -> mzero++_xpureBits2 :: InfixOp -> (forall a . Bits a => a -> a -> a) -> (T_dict -> T_dict -> T_dict) -> (B.ByteString -> B.ByteString -> B.ByteString) -> XPure Object -> XPure Object -> XPure Object+_xpureBits2 op bits dict bytes a b = a >>= \a -> b >>= \b -> do+ let t = max (coreType a) (coreType b)+ a <- castToCoreType t a+ b <- castToCoreType t b+ case (a, b) of+ (OChar a, OChar b) -> return $ OChar $ chr $ mod (bits (ord a) (ord b)) (ord maxBound)+ (OInt a, OInt b) -> return $ OInt $ bits a b+ (OWord a, OWord b) -> return $ OWord $ bits a b+ (OLong a, OLong b) -> return $ OLong $ bits a b+ (ODict a, ODict b) -> return $ ODict $ dict a b+ (OTree a, OTree b) -> case (a, b) of+ (Struct{ structName=na, fieldMap=ma }, Struct{ structName=nb, fieldMap=mb }) | na==nb ->+ xpure $ OTree $ a{ fieldMap = dict ma mb }+ _ -> throwBadTypeError "cannot operate on dissimilar struct types" (OTree b) $+ [(expectType, obj (typeOfObj (OTree a)))]+ (OBytes a, OBytes b) -> return $ OBytes $ bytes a b+ _ -> eval_Infix_op op a b++_dict_XOR :: (Object -> Object -> Object) -> T_dict -> T_dict -> T_dict+_dict_XOR f a b = M.difference (M.unionWith f a b) (M.intersectionWith f a b)++instance Bits (XPure Object) where+ a .&. b = _xpureBits2 AND (.&.) (M.intersectionWith (flip const)) (bytesBitArith (.&.)) a b <|>+ (a >>= \a -> b >>= \b -> eval_Infix_op ANDB a b)+ a .|. b = _xpureBits2 ORB (.|.) (M.unionWith (flip const)) (bytesBitArith (.|.)) a b <|>+ (a >>= \a -> b >>= \b -> eval_Infix_op ORB a b)+ xor a b = _xpureBits2 XORB xor (_dict_XOR (flip const)) (bytesBitArith xor ) a b <|>+ (a >>= \a -> b >>= \b -> eval_Infix_op XORB a b)+ complement = _xpureBits complement (B.map complement)+ shift o i = o >>= \o -> case o of+ OList o -> xpure $ OList $ case compare i 0 of+ EQ -> o+ LT -> reverse $ drop (negate i) $ reverse o+ GT -> drop i o+ _ -> _xpureBits (flip shift i) (flip bytesShift (fromIntegral i)) (xpure o)+ rotate o i = _xpureBits (flip shift i) (flip bytesRotate (fromIntegral i)) o+ bit i = xpure $ if i<64 then OWord (bit i) else OBytes (bytesBit (fromIntegral i))+ testBit o i = _xpureApply1 "testBit" testbit o where+ testbit o = case o of+ OChar o -> xpure $ testBit (ord o) i+ OInt o -> xpure $ testBit o i+ OWord o -> xpure $ testBit o i+ OLong o -> xpure $ testBit o i+ OBytes o -> xpure $ bytesTestBit o (fromIntegral i)+ _ -> mzero+ bitSize = _xpureApply1 "bitSize" $ \o -> case o of+ OInt o -> xmaybe $ bitSizeMaybe o+ OWord o -> xmaybe $ bitSizeMaybe o+ OBytes o -> xpure $ fromIntegral $ bytesBitSize o+ _ -> mzero+ bitSizeMaybe = _xpureMaybeApply1 $ \o -> case o of+ OInt o -> xmaybe $ bitSizeMaybe o+ OWord o -> xmaybe $ bitSizeMaybe o+ OBytes o -> xpure $ fromIntegral $ bytesBitSize o+ _ -> mzero+ isSigned = _xpureApply1 "isSigned" $ \o -> case o of+ OChar _ -> xpure False+ OInt _ -> xpure True+ OWord _ -> xpure False+ OLong _ -> xpure True+ OBytes _ -> xpure False+ _ -> mzero+ popCount = _xpureApply1 "popCount" $ \o -> case o of+ OChar o -> xpure $ popCount (ord o)+ OInt o -> xpure $ popCount o+ OWord o -> xpure $ popCount o+ OLong o -> xpure $ popCount o+ OBytes o -> xpure $ fromIntegral $ bytesPopCount o+ _ -> mzero++_shiftOp :: (Int -> Int) -> Object -> Object -> XPure Object+_shiftOp neg a b = case b of+ OInt b -> shift (xpure a) (neg b)+ OWord b -> shift (xpure a) (neg $ fromIntegral b)+ OLong b -> shift (xpure a) (neg $ fromIntegral b)+ _ -> mzero++-- | Evaluate the shift-left operator in the 'XPure' monad.+shiftLeft :: Object -> Object -> XPure Object+shiftLeft a b = _shiftOp id a b <|> eval_Infix_op SHL a b++-- | Evaluate the shift-right operator in the 'XPure' monad.+shiftRight :: Object -> Object -> XPure Object+shiftRight a b = _shiftOp negate a b <|> eval_Infix_op SHR a b++-- | Throw an error declaring that the two types cannot be used together because their types are+-- incompatible. Provide the a string describing the /what/ could not be done as a result of the+-- type mismatch, it will be placed in the message string:+-- > "could not <WHAT> the item <A> of type <A-TYPE> with the item <B> of type <B-TYPE>"+typeMismatchError :: InfixOp -> Object -> Object -> XPure ig+typeMismatchError op a b = throwError $+ newError{ execErrorSubtype = ExecInfixOpError (typeOfObj a) op (typeOfObj b) }++eval_ADD :: Object -> Object -> XPure Object+eval_ADD a b = (xpure a + xpure b) <|> typeMismatchError ADD a b++eval_SUB :: Object -> Object -> XPure Object+eval_SUB a b = (xpure a - xpure b) <|> typeMismatchError SUB a b++eval_MULT :: Object -> Object -> XPure Object+eval_MULT a b = (xpure a * xpure b) <|> typeMismatchError MULT a b++eval_DIV :: Object -> Object -> XPure Object+eval_DIV a b = do+ let { xa = xpure a; xb = xpure b; }+ (div xa xb <|> xa/xb) <|> typeMismatchError DIV a b++eval_MOD :: Object -> Object -> XPure Object+eval_MOD a b = do+ let { xa = xpure a; xb = xpure b; }+ (mod xa xb) <|> typeMismatchError MOD a b++eval_POW :: Object -> Object -> XPure Object+eval_POW a b = do+ let { xa = xpure a; xb = xpure b; }+ xa^^xb <|> xa**xb <|> typeMismatchError POW a b++eval_ORB :: Object -> Object -> XPure Object+eval_ORB a b = do+ let { xa = xpure a; xb = xpure b; }+ (xa.|.xb) <|> typeMismatchError ORB a b++eval_ANDB :: Object -> Object -> XPure Object+eval_ANDB a b = do+ let { xa = xpure a; xb = xpure b; }+ (xa.&.xb) <|> typeMismatchError ANDB a b++eval_XORB :: Object -> Object -> XPure Object+eval_XORB a b = do+ let { xa = xpure a; xb = xpure b; }+ (xor xa xb) <|> typeMismatchError XORB a b++eval_EQUL :: Object -> Object -> XPure Object+eval_EQUL a b = return $ obj $ xpure a == xpure b++eval_NEQUL :: Object -> Object -> XPure Object+eval_NEQUL a b = return $ obj $ xpure a /= xpure b++eval_GTN :: Object -> Object -> XPure Object+eval_GTN a b = return $ obj $ xpure a > xpure b++eval_LTN :: Object -> Object -> XPure Object+eval_LTN a b = return $ obj $ xpure a < xpure b++eval_GTEQ :: Object -> Object -> XPure Object+eval_GTEQ a b = return $ obj $ xpure a >= xpure b++eval_LTEQ :: Object -> Object -> XPure Object+eval_LTEQ a b = return $ obj $ xpure a <= xpure b++eval_SHR :: Object -> Object -> XPure Object+eval_SHR a b = shiftRight a b++eval_SHL :: Object -> Object -> XPure Object+eval_SHL a b = shiftLeft a b++eval_NEG :: Object -> XPure Object+eval_NEG = _evalNumOp1 negate++eval_INVB :: Object -> XPure Object+eval_INVB = complement . xpure++eval_NOT :: Object -> XPure Object+eval_NOT = fmap (obj . not) . objToBool++objToBool :: Object -> XPure Bool+objToBool o = case o of+ OHaskell (Hata ifc d) -> case objNullTest ifc of+ Nothing -> throwBadTypeError "cannot be used as a boolean value" o [(assertFailed, o)]+ Just test -> return (test d)+ o -> return $ not $ testNull o++-- | Traverse the entire object, returning a list of all 'OString' elements.+extractStringElems :: Object -> [UStr]+extractStringElems o = case o of+ OString o -> [o]+ OList o -> concatMap extractStringElems o+ _ -> []++-- | Useful for building 'DaoFunc' objects, checks every parameter in a list of 'Object's to be a+-- string, and throws an exception if one of the 'Object's is not a string.+requireAllStringArgs :: String -> [Object] -> Exec [UStr]+requireAllStringArgs msg ox = case mapM check (zip [1..] ox) of+ OK obj -> return obj+ Backtrack -> fail msg+ PFail err -> throwError err+ where+ check (i, o) = case o of+ OString o -> return o+ _ -> throwBadTypeError msg o [(argNum, OInt i)]++----------------------------------------------------------------------------------------------------++_updateToInfixOp :: UpdateOp -> InfixOp+_updateToInfixOp = (arr!) where+ arr :: Array UpdateOp InfixOp+ arr = array (UADD, maxBound) $+ [ (UADD , ADD )+ , (USUB , SUB )+ , (UMULT , MULT)+ , (UDIV , DIV )+ , (UMOD , MOD )+ , (UPOW , POW )+ , (UORB , ORB )+ , (UANDB , ANDB)+ , (UXORB , XORB)+ , (USHL , SHL )+ , (USHR , SHR )+ ]++instance ToDaoStructClass UpdateOp where { toDaoStruct = putNullaryUsingShow }++instance FromDaoStructClass UpdateOp where { fromDaoStruct = getNullaryWithRead }++instance ObjectClass UpdateOp where { obj=new; fromObj=objFromHata; }++instance HataClass UpdateOp where+ haskellDataInterface = interface "UpdateOperator" $ do+ autoDefEquality >> autoDefOrdering >> autoDefBinaryFmt+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass RefPfxOp where { toDaoStruct = putNullaryUsingShow }++instance FromDaoStructClass RefPfxOp where { fromDaoStruct = getNullaryWithRead }++instance ObjectClass RefPfxOp where { obj=new; fromObj=objFromHata; }++instance HataClass RefPfxOp where+ haskellDataInterface = interface "ReferenceOperator" $ do+ autoDefEquality >> autoDefOrdering+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass ArithPfxOp where { toDaoStruct = putNullaryUsingShow }++instance FromDaoStructClass ArithPfxOp where { fromDaoStruct = getNullaryWithRead }++instance ObjectClass ArithPfxOp where { obj=new; fromObj=objFromHata; }++instance HataClass ArithPfxOp where+ haskellDataInterface = interface "ArithmeticPrefixOperator" $ do+ autoDefEquality >> autoDefOrdering >> autoDefBinaryFmt+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass InfixOp where { toDaoStruct=putNullaryUsingShow; }++instance FromDaoStructClass InfixOp where { fromDaoStruct = getNullaryWithRead }++instance ObjectClass InfixOp where { obj=new; fromObj=objFromHata; }++instance HataClass InfixOp where+ haskellDataInterface = interface "ArithmeticInfixOperator" $ do+ autoDefEquality >> autoDefOrdering >> autoDefBinaryFmt+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass TopLevelEventType where { toDaoStruct = putNullaryUsingShow }++instance FromDaoStructClass TopLevelEventType where { fromDaoStruct = getNullaryWithRead }++instance ObjectClass TopLevelEventType where { obj=new; fromObj=objFromHata; }++instance HataClass TopLevelEventType where+ haskellDataInterface = interface "TopLevelEventType" $ do+ autoDefEquality >> autoDefOrdering+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++evalArithPrefixOp :: ArithPfxOp -> Object -> XPure Object+evalArithPrefixOp = (_arithPrefixOps!)++_arithPrefixOps :: Array ArithPfxOp (Object -> XPure Object)+_arithPrefixOps = array (minBound, maxBound) $ defaults +++ [ o NEGTIV eval_NEG+ , o POSTIV return+ , o INVB eval_INVB+ , o NOT eval_NOT+ ]+ where+ o = (,)+ defaults = flip map [minBound..maxBound] $ \op ->+ (op, \_ -> error $ "no builtin function for prefix "++show op++" operator")++evalInfixOp :: InfixOp -> Object -> Object -> XPure Object+evalInfixOp op a b = msum $ f a ++ f b ++ [(_infixOps!op) a b] where+ f = maybe [] return . (fromObj >=> getOp)+ getOp (Hata ifc a) = (\f -> f op a b) <$> (objInfixOpTable ifc >>= (! op))++_infixOps :: Array InfixOp (Object -> Object -> XPure Object)+_infixOps = array (minBound, maxBound) $ defaults +++ [ o ADD eval_ADD+ , o SUB eval_SUB+ , o MULT eval_MULT+ , o DIV eval_DIV+ , o MOD eval_MOD+ , o POW eval_POW+ , o SHL eval_SHL+ , o SHR eval_SHR+ , o ORB eval_ORB+ , o ANDB eval_ANDB+ , o XORB eval_XORB+ , o OR (error (e "logical-OR" )) -- These probably wont be evaluated. Locgical and/or is a+ , o AND (error (e "logical-AND")) -- special case to be evaluated in 'evalObjectExprWithLoc'.+ , o EQUL eval_EQUL+ , o NEQUL eval_NEQUL+ , o GTN eval_GTN+ , o LTN eval_LTN+ , o GTEQ eval_GTEQ+ , o LTEQ eval_LTEQ+ , o ARROW (error (e "ARROW"))+ ]+ where+ o = (,)+ defaults = flip map [minBound..maxBound] $ \op ->+ (op, \_ _ -> error $ "no builtin function for infix "++show op++" operator")+ e msg = msg +++ " operator should have been evaluated within the 'execute' function."++-- | Evaluate an 'UpdateOp' operator. Provide an optional 'Reference' indicating the reference+-- location of the value being updated, then the 'UpdateOp' operator, the right-hand side 'Object'+-- value, and finally the current value stored at the 'Reference' location that needs to be updated.+-- If the 'UpdateOp' is 'UCONST', the current value may be 'Prelude.Nothing' as any current value+-- will be overwritten. If the 'UpdateOp' is not 'UCONST' and the current value is+-- 'Prelude.Nothing', this is an error. Otherwise the current value is removed from the+-- 'Prelude.Just' constructor and used as the left-hand operand with the right-hand operand of the+-- appropriate arithmetic function associated with the 'UpdateOp'.+evalUpdateOp :: Maybe Reference -> UpdateOp -> Object -> Maybe Object -> Exec (Maybe Object)+evalUpdateOp qref op newObj oldObj = case op of+ UCONST -> return $ Just newObj+ op -> case oldObj of+ Nothing ->+ execThrow "performed update on void value" (ExecUpdateOpError op)+ (maybe [] (\qref -> [(errOfReference, obj qref)]) qref)+ Just oldObj -> Just <$> execute (evalInfixOp (_updateToInfixOp op) oldObj newObj)++_updatingOps :: Array UpdateOp (Object -> Object -> XPure Object)+_updatingOps = let o = (,) in array (minBound, maxBound) $ defaults +++ [ o UCONST (\_ b -> return b)+ , o UADD eval_ADD+ , o USUB eval_SUB+ , o UMULT eval_MULT+ , o UDIV eval_DIV+ , o UMOD eval_MOD+ , o UORB eval_ORB+ , o UANDB eval_ANDB+ , o UXORB eval_XORB+ , o USHL eval_SHL+ , o USHR eval_SHR+ ]+ where+ defaults = flip map [minBound..maxBound] $ \op ->+ (op, \_ _ -> error $ "no builtin function for update operator "++show op)++----------------------------------------------------------------------------------------------------++_strObjConcat :: [Object] -> String+_strObjConcat ox = ox >>= \o -> maybe [toUStr $ prettyShow o] return (fromObj o) >>= uchars++makePrintFunc :: (typ -> String -> Exec ()) -> DaoFunc typ+makePrintFunc print =+ daoFunc+ { funcAutoDerefParams = True+ , daoForeignFunc = \typ ox -> print typ (_strObjConcat ox) >> return (Nothing, typ)+ }++builtin_print :: DaoFunc ()+builtin_print = makePrintFunc (\ () -> liftIO . (putStr >=> evaluate))++builtin_println :: DaoFunc ()+builtin_println = makePrintFunc (\ () -> liftIO . (putStrLn >=> evaluate))++-- join string elements of a container, pretty prints non-strings and joins those as well.+builtin_join :: DaoFunc ()+builtin_join =+ daoFunc+ { funcAutoDerefParams = True+ , daoForeignFunc = \ () ox -> return $ flip (,) () $ Just $ obj $ case ox of+ OString j : ox -> (>>=uchars) $+ intersperse j $ snd (objConcat ox) >>= \o ->+ [maybe (ustr $ prettyShow o) id (fromObj o :: Maybe UStr)]+ ox -> _strObjConcat ox+ }++builtin_str :: DaoFunc ()+builtin_str =+ daoFunc+ { funcAutoDerefParams = True+ , daoForeignFunc = \ () -> return . flip (,) () . Just . obj . _strObjConcat+ }++builtin_quote :: DaoFunc ()+builtin_quote =+ daoFunc+ { funcAutoDerefParams = True+ , daoForeignFunc = \ () -> return . flip (,) () . Just . obj . show . _strObjConcat+ }++builtin_concat :: DaoFunc ()+builtin_concat =+ daoFunc+ { funcAutoDerefParams = True+ , daoForeignFunc = \ () -> return . flip (,) () . Just . obj . fix (\loop ox -> ox >>= \o -> maybe [o] loop (fromObj o))+ }++builtin_concat1 :: DaoFunc ()+builtin_concat1 =+ daoFunc+ { funcAutoDerefParams = True+ , daoForeignFunc = \ () -> return . flip (,) () . Just . obj . snd . objConcat+ }++builtin_reverse :: DaoFunc ()+builtin_reverse =+ daoFunc+ { funcAutoDerefParams = True+ , daoForeignFunc = \ () -> return . flip (,) () . Just . obj . reverse . snd . objConcat+ }++_castNumerical :: String -> (Object -> Exec Object) -> DaoFunc ()+_castNumerical name f = let n = ustr name :: Name in+ daoFunc+ { funcAutoDerefParams = True+ , daoForeignFunc = \ () ox -> case ox of+ [o] -> (flip (,) () . Just <$> f o) <|> throwBadTypeError "cannot cast to numerical type" o []+ ox -> throwArityError "" 1 ox [(errInFunc, obj $ reference UNQUAL n)]+ }++builtin_int :: DaoFunc ()+builtin_int = _castNumerical "int" $+ fmap OInt . (execute . castToCoreType IntType >=> xmaybe . fromObj)++builtin_long :: DaoFunc ()+builtin_long = _castNumerical "long" $+ fmap OLong . (execute . castToCoreType LongType >=> xmaybe . fromObj)++builtin_ratio :: DaoFunc ()+builtin_ratio = _castNumerical "ratio" $+ fmap ORatio . (execute . castToCoreType RatioType >=> xmaybe . fromObj)++builtin_float :: DaoFunc ()+builtin_float = _castNumerical "float" $+ fmap OFloat . (execute . castToCoreType FloatType >=> xmaybe . fromObj)++builtin_complex :: DaoFunc ()+builtin_complex = _castNumerical "complex" $+ fmap OComplex . (execute . castToCoreType ComplexType >=> xmaybe . fromObj)++builtin_imag :: DaoFunc ()+builtin_imag = _castNumerical "imag" $+ fmap (OFloat . imagPart) . (execute . castToCoreType ComplexType >=> xmaybe . fromObj)++builtin_phase :: DaoFunc ()+builtin_phase = _castNumerical "phase" $+ fmap (OFloat . phase) . (execute . castToCoreType ComplexType >=> xmaybe . fromObj)++builtin_conj :: DaoFunc ()+builtin_conj = _castNumerical "conj" $+ fmap (OComplex . conjugate) . (execute . castToCoreType ComplexType >=> xmaybe . fromObj)++builtin_abs :: DaoFunc ()+builtin_abs = _castNumerical "abs" $ execute . asPositive++builtin_time :: DaoFunc ()+builtin_time = _castNumerical "time" $ \o -> case o of+ ORelTime _ -> return o+ o -> (ORelTime . fromRational) <$> execute (asRational o)++_funcWithoutParams :: String -> Exec (Maybe Object) -> DaoFunc ()+_funcWithoutParams name f =+ daoFunc+ { daoForeignFunc = \ () ox -> case ox of+ [] -> flip (,) () <$> f+ ox -> throwArityError "function takes no parameters" 0 ox $+ [(errInFunc, obj $ reference UNQUAL (ustr name))]+ }++builtin_now :: DaoFunc ()+builtin_now = _funcWithoutParams "now" $ (Just . obj) <$> liftIO getCurrentTime++builtin_ref :: DaoFunc ()+builtin_ref =+ daoFunc+ { daoForeignFunc = \ () -> fmap (flip (,) () . Just . ORef) . execute . mconcat .+ fmap (\o -> (castToCoreType RefType o) <|>+ (throwBadTypeError "could not convert to reference" o []) >>=+ (castToCoreType RefType >=> xmaybe . fromObj)+ )+ }++builtin_check_if_defined :: DaoFunc ()+builtin_check_if_defined =+ daoFunc+ { funcAutoDerefParams = False+ , daoForeignFunc = \ () args -> fmap (flip (,) () . Just . obj . and) $ forM args $ \arg -> case arg of+ ORef o -> catchError (referenceLookup o >> return True) $ \err -> case err of+ ExecError{ execErrorSubtype=ExecUndefinedRef _ } -> return False+ err -> throwError err+ _ -> return True+ }++builtin_delete :: DaoFunc ()+builtin_delete =+ daoFunc+ { funcAutoDerefParams = False+ , daoForeignFunc = \ () args -> do+ forM_ args $ \arg -> case arg of+ ORef o -> void $ referenceUpdate o True (const $ return Nothing)+ _ -> return ()+ return (Nothing, ())+ }++builtin_typeof :: DaoFunc ()+builtin_typeof =+ daoFunc+ { daoForeignFunc = \ () ox -> return $ flip (,) () $ case ox of+ [] -> Nothing+ [o] -> Just $ OType $ typeOfObj o+ ox -> Just $ OList $ map (OType . typeOfObj) ox+ }++builtin_sizeof :: DaoFunc ()+builtin_sizeof =+ daoFunc+ { daoForeignFunc = \ () ox -> case ox of+ [o] -> flip (,) () . Just <$> getSizeOf o+ ox -> throwArityError "" 1 ox [(errInFunc, obj $ reference UNQUAL (ustr "sizeof"))]+ }++builtin_call :: DaoFunc ()+builtin_call =+ daoFunc+ { funcAutoDerefParams = False+ , daoForeignFunc = \ () ox -> case ox of+ [func, params] -> do+ let nonlist_err = fail "second parameter to \"call()\" function is not a list of arguments"+ params <- case params of+ OList params -> return params+ ORef params -> referenceLookup params >>= \ (_, params) -> case params of+ Nothing -> fail "second parameter parameter to \"call()\" function evaluated to null"+ Just params -> xmaybe (fromObj params) <|> nonlist_err+ _ -> nonlist_err+ qref <- xmaybe (fromObj func)+ <|> fail "first parameter to \"call()\" function is not a reference to a function"+ (qref, func) <- referenceLookup qref+ case func of+ Nothing -> fail "first parameter to \"call()\" function evaluated to null"+ Just func -> fmap (const ()) <$> callObject qref func params+ _ -> fail $ unwords $+ [ "the \"call()\" function was evaluated with incorrect arguments."+ , "Expecting a reference to function as first parameter"+ , "and a list of arguments as the second parameter."+ ]+ }++builtin_toHash :: DaoFunc ()+builtin_toHash =+ daoFunc+ { daoForeignFunc = \ () ox -> do+ let qref = reference UNQUAL (ustr "toHash")+ let err = throwArityError "" 1 ox [(errInFunc, obj qref)]+ case ox of+ [o] -> case o of+ OTree _ -> return (Just o, ())+ OHaskell (Hata ifc d) -> case objToStruct ifc of+ Just to -> flip (,) () . Just . OTree <$> toDaoStructExec to d+ Nothing -> throwBadTypeError "data type is opaque, cannot do binary conversion for hash" o $+ [(errInFunc, obj qref)]+ _ -> err+ _ -> err+ }++builtin_fromHash :: DaoFunc ()+builtin_fromHash =+ daoFunc+ { daoForeignFunc = \ () ox -> do+ let qref = reference UNQUAL (ustr "fromHash")+ case ox of+ [o] -> do+ let err = throwBadTypeError "hashed Struct parameter required" o [(errInFunc, obj qref)]+ xmaybe (fromObj o) <|> err >>= fmap (flip (,) () . Just . obj) . fromDaoStructExec + ox -> throwArityError "" 1 ox [(errInFunc, obj qref)]+ }++builtin_tokenize :: DaoFunc ()+builtin_tokenize =+ daoFunc{ daoForeignFunc = \ () -> fmap (flip (,) () . Just . obj . map obj) . runTokenizer }++----------------------------------------------------------------------------------------------------++-- binary 0x42 0x45 RefSuffixExpr-->RefSuffix+instance B.Binary (RefSuffixExpr Object) MTab where+ put o = case o of+ NullRefExpr -> B.putWord8 0x42+ DotRefExpr a b c -> B.prefixByte 0x43 $ B.put a >> B.put b >> B.put c+ SubscriptExpr a b -> B.prefixByte 0x44 $ B.put a >> B.put b+ FuncCallExpr a b -> B.prefixByte 0x45 $ B.put a >> B.put b+ get = B.word8PrefixTable <|> fail "expecting RefSuffixExpr"++instance B.HasPrefixTable (RefSuffixExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "RefSuffixExpr" 0x42 0x45 $+ [ return NullRefExpr+ , return DotRefExpr <*> B.get <*> B.get <*> B.get+ , return SubscriptExpr <*> B.get <*> B.get+ , return FuncCallExpr <*> B.get <*> B.get+ ]++instance Executable (RefSuffixExpr Object) RefSuffix where+ execute o = errLocation o $ case o of+ NullRefExpr -> return NullRef+ DotRefExpr name ref _ -> DotRef name <$> execute ref+ SubscriptExpr args ref -> return Subscript <*> execute args <*> execute ref+ FuncCallExpr args ref -> return FuncCall <*> execute args <*> execute ref++----------------------------------------------------------------------------------------------------++-- | To evaluate an 'Object' value against a type expression, you can store the+-- 'Object' into a 'TyChkExpr' and 'execute' it. This instance of+-- 'execute' evaluates a type checking monad computing over the 'tyChkExpr' in+-- the 'TyChkExpr'. If the type check determines the 'Object' value does not match, this+-- function backtracks. If the type check is successful, the most general type value for the object+-- if that type value is less-general or as-general as the 'TyChkExpr' provided.+instance Executable (TyChkExpr Object Object) Object where+ execute tc = case tc of+ NotTypeChecked _ -> return OTrue -- TODO: this needs to return the 'AnyType', not 'OTrue'.+ TypeChecked _ _ _ -> return OTrue -- TODO: evaluate the actual type checking algorithm here+ DisableCheck _ _ rslt _ -> return rslt++-- | Convert an 'ObjectExpr' to an 'Dao.Glob.Glob'.+paramsToGlobExpr :: ObjectExpr Object -> Exec (Glob UStr)+paramsToGlobExpr o = case o of+ ObjLiteralExpr (LiteralExpr (OString str) _) -> return (read (uchars str))+ _ -> fail "does not evaluate to a \"glob\" pattern"++-- | Called by 'callFunction' to match the list of 'Object's passed as arguments to the function.+-- Returns two 'T_dict's: the first is the 'T_dict' to be passed to 'execFuncPushStack', the second+-- is the dictionary of local variables passed by reference. Backtracks if any types do not match,+-- or if there are an incorrect number of parameters. Backtracking is important because of function+-- overloading.+matchFuncParams :: ParamListExpr Object -> [Object] -> Exec T_dict+matchFuncParams (ParamListExpr params _) ox = loop (0::Int) M.empty (tyChkItem params) ox where+ loop i dict params ox = case ox of+ [] | null params -> return dict+ [] -> mzero -- not enough parameters passed to function+ o:ox -> case params of+ [] -> mzero -- too many parameters passed to function+ ParamExpr passByRef tychk _ : params -> do+ let name = tyChkItem tychk+ execute $ fmapCheckedValueExpr (const o) tychk -- execute (TyChkExpr Object)+ o <- if passByRef then (case o of { ORef _ -> return o; _ -> mzero }) else derefObject o+ loop (i+1) (M.insert name o dict) params ox++-- | A guard script is some Dao script that is executed before or after some event, for example, the+-- code found in the @BEGIN@ and @END@ blocks.+execGuardBlock :: [ScriptExpr Object] -> Exec ()+execGuardBlock block = void $+ execFuncPushStack M.empty (mapM_ execute block >> return Nothing) >> return ()++-- | Takes two parameters: first is an error message parameter, the second is the 'Object' to be+-- called. The 'Object' to be called should be an 'OHaskell' constructed value containing a+-- 'Hata' where the 'interface' has defined 'defCallable'. If so, the 'CallableCode' objects+-- returned by 'objCallable' will be returned by this function. If not, +objToCallable :: Object -> Exec [CallableCode]+objToCallable o = case fromObj o >>= \ (Hata ifc o) -> fmap ($ o) (objCallable ifc) of+ Nothing -> mzero+ Just f -> f++-- | 'CallableCode' objects are usually stored in lists because of function overloading: e.g. a+-- function with a single name but is defined with multiple parameter lists would have several+-- 'CallableCode' objects associated with that function mame. This function tries to perform a+-- function call with a list of parameters. The parameters are matched to each 'CallableCode'+-- object's 'argsPattern', the first 'argsPattern' that matches without backtracking will evaluate+-- the function body. The value returned is a pair containing the result of the function call as the+-- 'Prelude.fst', and update "this" value as 'Prelude.snd'+callCallables :: Maybe Object -> [CallableCode] -> [Object] -> Exec (Maybe Object, Maybe Object)+callCallables this funcs params = fmap (fmap (M.lookup (ustr "this"))) $ join $+ msum $ flip fmap funcs $ \call -> matchFuncParams (argsPattern call) params >>=+ return . flip execFuncPushStack (execute $ codeSubroutine call) . M.alter (const this) (ustr "this")++-- | This function assumes you have retrieved a callable function-like 'Object' using a 'Reference'.+-- This function evaluates 'callCallables', and extracts the 'CallableCode' from the 'Object'+-- provided as the second parameter using 'objToCallable'. The 'Reference' passed as the first+-- parameter should be the reference used to retrieve the function 'Object'. If the given object+-- provides a 'defCallable' callback, the object can be called with parameters as if it were a+-- function.+callObject :: Reference -> Object -> [Object] -> Exec (Maybe Object, Maybe Object)+callObject qref o params = case o of+ OHaskell (Hata ifc d) -> case fromDynamic d of+ Just func -> fmap (const Nothing) <$> executeDaoFunc func () params -- try calling an ordinary function+ Nothing -> case objCallable ifc of+ Just getFuncs -> getFuncs d >>= \func -> callCallables (Just o) func params+ Nothing -> err+ _ -> err+ where { err = throwBadTypeError "not a callable object" o [(errInFunc, obj qref)] }++-- | Evaluate to 'procErr' if the given 'Predicate' is 'Backtrack' or 'PFail'. You must pass a+-- 'Prelude.String' as the message to be used when the given 'Predicate' is 'Backtrack'. You can also+-- pass a list of 'Object's that you are checking, these objects will be included in the+-- 'procErr' value.+-- This function should be used for cases when you have converted 'Object' to a+-- Haskell value, because 'Backtrack' values indicate type exceptions, and 'PFail' values indicate a+-- value error (e.g. out of bounds, or some kind of assert exception), and the messages passed to+-- 'procErr' will indicate this.+checkPredicate :: String -> [Object] -> Exec a -> Exec a+checkPredicate altmsg tried f = do+ pval <- catchPredicate f+ let err = fail (altmsg++" evaulated to void expression")+ case pval of+ OK a -> return a+ Backtrack -> err+ PFail (ExecReturn Nothing) -> err+ PFail err -> throwError $+ err{ execReturnValue = Just $ case execReturnValue err of+ Just (OList ox) -> obj $ tried ++ ox+ Just o -> obj $ tried ++ [o]+ Nothing -> obj tried+ }++-- | 'evalObjectExprExpr' can return 'Data.Maybe.Nothing', and usually this happens when something has+-- failed (e.g. reference lookups), but it is not always an error (e.g. a void list of argument to+-- functions). If you want 'Data.Maybe.Nothing' to cause an error, evaluate your+-- @'Exec' ('Data.Maybe.Maybe' 'Object')@ as a parameter to this function.+checkVoid :: Location -> String -> Maybe a -> Exec a+checkVoid loc msg fn = case fn of+ Nothing -> throwError $+ newError+ { execErrorMessage = ustr (msg++" evaluated to void")+ , execErrorLocation = loc+ }+ Just a -> return a++instance ToDaoStructClass (AST_RefSuffix Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_RefNull -> makeNullary "Null"+ AST_DotRef dot name ref loc -> renameConstructor "DotRef" $ do+ "dot" .= dot >> "head" .= name >> "tail" .= ref >> putLocation loc+ AST_Subscript args ref -> renameConstructor "Subscript" $ do+ "args" .= args >> "tail" .= ref >> return ()+ AST_FuncCall args ref -> renameConstructor "FuncCall" $ do+ "args" .= args >> "tail" .= ref >> return ()++instance FromDaoStructClass (AST_RefSuffix Object) where+ fromDaoStruct = msum $+ [ constructor "Null" >> return AST_RefNull+ , constructor "DotRef" >> return AST_DotRef <*> req "dot" <*> req "head" <*> req "tail" <*> location+ , constructor "Subscript" >> return AST_Subscript <*> req "args" <*> req "tail"+ , constructor "FuncCall" >> return AST_FuncCall <*> req "args" <*> req "tail"+ ]++instance ObjectClass (AST_RefSuffix Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_RefSuffix Object) where+ haskellDataInterface = interface "RefSuffixExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- | If an expression is inside of a 'ParenExpr', like it usually is after being parsed as part of+-- an @if@ or @while@ statement, this function evaluates the expression to a 'Prelude.Bool' value+-- used to determine if the conditional expression should be 'execute'd.+evalConditional :: ParenExpr Object -> Exec Bool+evalConditional o =+ (execute o :: Exec (Maybe Object)) >>=+ checkVoid (getLocation o) "conditional expression to if statement" >>= derefObject >>=+ execHandleIO [fmap (const False) execIOHandler] . return . not . testNull++-- binary 0x59 +instance B.Binary (ParenExpr Object) MTab where+ put (ParenExpr a b) = B.prefixByte 0x59 $ B.put a >> B.put b+ get = B.word8PrefixTable <|> fail "expecting ParenExpr"++instance B.HasPrefixTable (ParenExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "ParenExpr" 0x59 0x59 $+ [return ParenExpr <*> B.get <*> B.get]++instance Executable (ParenExpr Object) (Maybe Object) where { execute (ParenExpr a _) = execute a }++instance ObjectClass (ParenExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ParenExpr Object) where+ haskellDataInterface = interface "Parentheses" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt+ -- autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Paren Object) where+ toDaoStruct = renameConstructor "Paren" $ ask >>= \o -> case o of+ AST_Paren paren loc -> "inside" .= paren >> putLocation loc++instance FromDaoStructClass (AST_Paren Object) where+ fromDaoStruct = constructor "Paren" >> return AST_Paren <*> req "inside" <*> location++instance ObjectClass (AST_Paren Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Paren Object) where+ haskellDataInterface = interface "ParenthesesExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance B.Binary (IfExpr Object) MTab where+ put (IfExpr a b c) = B.put a >> B.put b >> B.put c+ get = return IfExpr <*> B.get <*> B.get <*> B.get++instance Executable (IfExpr Object) Bool where+ execute (IfExpr ifn thn _) = execNested_ M.empty $+ evalConditional ifn >>= \test -> when test (execute thn) >> return test++instance ObjectClass (IfExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (IfExpr Object) where+ haskellDataInterface = interface "Conditional" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_If Object) where+ toDaoStruct = renameConstructor "Conditional" $ ask >>= \o -> case o of+ AST_If ifn thn loc -> "condition" .= ifn >> "action" .= thn >> putLocation loc++instance FromDaoStructClass (AST_If Object) where+ fromDaoStruct = constructor "Conditional" >>+ return AST_If <*> req "condition" <*> req "action" <*> location++instance ObjectClass (AST_If Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_If Object) where+ haskellDataInterface = interface "ConditionalExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0xB6 +instance B.Binary (ElseExpr Object) MTab where+ put (ElseExpr a b) = B.prefixByte 0xB6 $ B.put a >> B.put b+ get = (B.tryWord8 0xB6 $ return ElseExpr <*> B.get <*> B.get) <|> fail "expecting ElseExpr"++instance Executable (ElseExpr Object) Bool where { execute (ElseExpr ifn _) = execute ifn }++instance ObjectClass (ElseExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ElseExpr Object) where+ haskellDataInterface = interface "Else" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Else Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_Else coms ifn loc -> renameConstructor "ElseIf" $ do+ "comments" .= coms >> "elseIf" .= ifn >> putLocation loc++instance FromDaoStructClass (AST_Else Object) where+ fromDaoStruct = constructor "ElseIf" >>+ return AST_Else <*> req "comments" <*> req "elseIf" <*> location++instance ObjectClass (AST_Else Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Else Object) where+ haskellDataInterface = interface "ElseExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0xBA +instance B.Binary (IfElseExpr Object) MTab where+ put (IfElseExpr a b c d) = B.prefixByte 0xBA $ B.put a >> B.put b >> B.put c >> B.put d+ get = B.word8PrefixTable <|> fail "expecting IfElseExpr"++instance B.HasPrefixTable (IfElseExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "IfElseExpr" 0xBA 0xBA $+ [return IfElseExpr <*> B.get <*> B.get <*> B.get <*> B.get]++instance Executable (IfElseExpr Object) () where+ execute (IfElseExpr ifn elsx final _loc) = do+ let tryEach elsx = case elsx of+ [] -> return False+ els:elsx -> execute els >>= \ok -> if ok then return ok else tryEach elsx+ (execute ifn >>= \ok ->+ if ok then return Nothing+ else tryEach elsx >>= \ok ->+ if ok then return Nothing+ else return final) >>= maybe (return ()) execute++instance ObjectClass (IfElseExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (IfElseExpr Object) where+ haskellDataInterface = interface "IfElse" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_IfElse Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_IfElse ifn els block loc -> renameConstructor "If" $ do+ "test" .= ifn >> "alt" .= listToObj els+ maybe (return ()) (void . defObjField "finalElse") block+ putLocation loc++instance FromDaoStructClass (AST_IfElse Object) where+ fromDaoStruct = constructor "If" >>+ return AST_IfElse+ <*> req "test"+ <*> reqList "alt"+ <*> opt "finalElse"+ <*> location++instance ObjectClass (AST_IfElse Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_IfElse Object) where+ haskellDataInterface = interface "IfElseExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary (not prefixed, always occurs within a list)+instance B.Binary (LastElseExpr Object) MTab where+ put (LastElseExpr a loc) = B.put a >> B.put loc+ get = (return LastElseExpr <*> B.get <*> B.get) <|> fail "expecting LastElseExpr"++instance Executable (LastElseExpr Object) () where { execute (LastElseExpr code _) = execute code }++instance ObjectClass (LastElseExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (LastElseExpr Object) where+ haskellDataInterface = interface "FinalElse" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_LastElse Object) where+ toDaoStruct = renameConstructor "LastElse" $ do+ (AST_LastElse coms code loc) <- ask+ "comments" .= coms >> "action" .= code >> putLocation loc++instance FromDaoStructClass (AST_LastElse Object) where+ fromDaoStruct = constructor "LastElse" >>+ return AST_LastElse <*> req "comments" <*> req "action" <*> location++instance ObjectClass (AST_LastElse Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_LastElse Object) where+ haskellDataInterface = interface "FinalElseExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary (not prefixed, always occurs within a list)+instance B.Binary (CatchExpr Object) MTab where+ put (CatchExpr a b loc) = B.put a >> B.put b >> B.put loc+ get = return CatchExpr <*> B.get <*> B.get <*> B.get++instance ObjectClass (CatchExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (CatchExpr Object) where+ haskellDataInterface = interface "CatchExpr" $ do+ autoDefEquality >> autoDefOrdering >> autoDefBinaryFmt++-- | Returns the 'Exec' function to be evaluated if the 'ExecControl' matches the 'CatchExpr' type+-- constraint. If the type constraint does not match, this function evaluates to+-- 'Control.Monad.mzero'.+executeCatchExpr :: ExecControl -> CatchExpr Object -> Exec (Exec ())+executeCatchExpr err (CatchExpr (ParamExpr _refd param _) catch _loc) = case param of -- TODO: do something with _refd+ NotTypeChecked name -> ex name catch+ TypeChecked name _check _loc -> ex name catch -- TODO: do something with _check+ DisableCheck name _ _ _ -> ex name catch+ where+ ex name catch = return $ execNested_ M.empty $ localVarDefine name (new err) >> execute catch++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Catch Object) where+ toDaoStruct = ask >>= \ (AST_Catch coms param action loc) -> renameConstructor "Catch" $ do+ "comments" .= coms >> "test" .= param >> "action" .= action >> putLocation loc++instance FromDaoStructClass (AST_Catch Object) where+ fromDaoStruct = constructor "Catch" >>+ return AST_Catch <*> req "comments" <*> req "test" <*> req "action" <*> location++instance ObjectClass (AST_Catch Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Catch Object) where+ haskellDataInterface = interface "CatchExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0x85 +instance B.Binary (WhileExpr Object) MTab where+ put (WhileExpr o) = B.prefixByte 0x85 $ B.put o+ get = B.word8PrefixTable <|> fail "expecting WhileExpr"++instance B.HasPrefixTable (WhileExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "WhileExpr" 0x85 0x85 [WhileExpr <$> B.get]++instance Executable (WhileExpr Object) () where+ execute (WhileExpr ifn) = fix $ \loop -> catchLoopCtrl (execute ifn) return >>= flip when loop++instance ObjectClass (WhileExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (WhileExpr Object) where+ haskellDataInterface = interface "While" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_While Object) where+ toDaoStruct = renameConstructor "While" $ ask >>= \ (AST_While o) -> innerToStruct o++instance FromDaoStructClass (AST_While Object) where+ fromDaoStruct = constructor "While" >> AST_While <$> innerFromStruct "Conditional"++instance ObjectClass (AST_While Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_While Object) where+ haskellDataInterface = interface "WhileExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0xA8 0xAF+instance B.Binary (ScriptExpr Object) MTab where+ put o = case o of+ IfThenElse a -> B.put a+ WhileLoop a -> B.put a+ RuleFuncExpr a -> B.put a+ EvalObject a z -> B.prefixByte 0xA8 $ B.put a >> B.put z+ TryCatch a b c z -> B.prefixByte 0xA9 $ B.put a >> B.put b >> B.put c >> B.put z+ ForLoop a b c z -> B.prefixByte 0xAA $ B.put a >> B.put b >> B.put c >> B.put z+ ContinueExpr True b z -> B.prefixByte 0xAB $ B.put b >> B.put z+ ContinueExpr False b z -> B.prefixByte 0xAC $ B.put b >> B.put z+ ReturnExpr True b z -> B.prefixByte 0xAD $ B.put b >> B.put z+ ReturnExpr False b z -> B.prefixByte 0xAE $ B.put b >> B.put z+ WithDoc a b z -> B.prefixByte 0xAF $ B.put a >> B.put b >> B.put z+ get = B.word8PrefixTable <|> fail "expecting ScriptExpr"++instance B.HasPrefixTable (ScriptExpr Object) B.Byte MTab where+ prefixTable = mconcat $+ [ fmap IfThenElse B.prefixTable+ , fmap WhileLoop B.prefixTable+ , fmap RuleFuncExpr B.prefixTable+ , B.mkPrefixTableWord8 "ScriptExpr" 0xA8 0xAF $ -- 0x89 0x8A 0x8B 0x8C 0x8D 0x8E 0x8F 0x90+ [ return EvalObject <*> B.get <*> B.get+ , return TryCatch <*> B.get <*> B.get <*> B.get <*> B.get+ , return ForLoop <*> B.get <*> B.get <*> B.get <*> B.get+ , return (ContinueExpr True ) <*> B.get <*> B.get+ , return (ContinueExpr False) <*> B.get <*> B.get+ , return (ReturnExpr True ) <*> B.get <*> B.get+ , return (ReturnExpr False) <*> B.get <*> B.get+ , return WithDoc <*> B.get <*> B.get <*> B.get+ ]+ ]++-- | Convert a single 'ScriptExpr' into a function of value @'Exec' 'Object'@.+instance Executable (ScriptExpr Object) () where+ execute script = errCurrentModule $ errLocation script $ case script of+ IfThenElse ifn -> execute ifn+ WhileLoop ifn -> execute ifn+ EvalObject o _loc -> execute (DerefAssignExpr o) >>= return . maybe () (`seq` ())+ RuleFuncExpr rulfn -> do+ o <- execute rulfn -- this 'execute' handles function expressions+ let dyn o = case o of+ OHaskell (Hata _ h) -> fromDynamic h+ _ -> Nothing+ let getObj :: (ObjectClass o, Typeable o) => Exec o+ getObj = mplus (xmaybe $ o >>= dyn) $ case o of+ Nothing -> fail "RuleFuncExpr evaluated to void"+ Just o -> throwBadTypeError "RuleFuncExpr evaluated to object of incorrect data type" o []+ let fsub sub = modify $ \xunit -> xunit{currentCodeBlock=Just sub}+ sub <- gets currentCodeBlock+ case sub of+ Nothing -> case rulfn of+ LambdaExpr{} -> getObj >>= \o ->+ modify $ \xunit -> xunit{ lambdaSet = lambdaSet xunit ++ o }+ RuleExpr{} -> do+ newtree <- getObj >>= \p -> execute (p::PatternRule)+ modify $ \xunit -> xunit{ ruleSet=T.unionWith (++) (ruleSet xunit) newtree }+ FuncExpr{} -> return ()+ -- function expressions are placed in the correct store by the above 'execute'+ Just sub -> case rulfn of+ LambdaExpr{} -> getObj >>= \o -> fsub $ sub{ staticLambdas = staticLambdas sub ++ o }+ RuleExpr{} -> do+ newtree <- getObj >>= \p -> execute (p::PatternRule)+ fsub $ sub{ staticRules=T.unionWith (++) (staticRules sub) newtree }+ FuncExpr{} -> return ()+ -- function expressions are placed in the correct store by the above 'execute'+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ TryCatch try els catchers _loc -> do+ ce <- catchPredicate $ execNested_ M.empty (execute try) <|> msum (fmap execute els)+ case ce of+ OK () -> return ()+ Backtrack -> mzero+ PFail err -> case err of+ ExecReturn{} -> predicate ce+ ExecError{execErrorSubtype=ExecLoopCtrl{}} -> predicate ce+ ExecError{} -> join $ msum $ fmap (executeCatchExpr err) catchers+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ ForLoop varName inObj thn _loc -> do+ let run = void $ execute thn+ let readIter o = execNested_ (M.singleton varName o) run+ let updateIter o = M.lookup varName . snd <$> execNested (maybe M.empty (M.singleton varName) o) run+ let readLoop o = void $ readForLoop o readIter+ let updateLoop qref o = void $ fmap snd $+ updateForLoop o updateIter >>= referenceUpdate qref False . const . return . Just+ errLocation inObj $ execute inObj >>= maybeDerefObject >>= \ (qref, iter) -> case iter of+ Nothing -> fail "iterator of for-loop expression evaluated to void"+ Just iter -> case qref of+ Nothing -> void $ readLoop iter+ Just qref -> case iter of+ OHaskell (Hata ifc _) -> case objUpdateIterable ifc of+ Just _ -> void $ updateLoop qref iter+ Nothing -> case objReadIterable ifc of+ Just _ -> void $ readLoop iter+ Nothing -> throwBadTypeError "data type not iterable" iter [(errOfReference, obj qref)]+ _ -> void $ updateLoop qref iter+ -- NOTE: the for loop iterator IS NOT passed to the 'referenceUpdate' function to be+ -- evaluated. This is to avoid introducing deadlocks in data types that may store their values+ -- in an MVar. The 'referenceLookup' function first evaluates the reference, creating a copy+ -- in the current thread, the for loop is evaluated, and THEN the 'referenceUpdate' function+ -- is evaluated, in those three discrete steps. It is not possible to evaluate a for loop in+ -- the dao programming language as an atomic action, so race conditions may occur when+ -- updating MVars -- but deadlocks will not occur.+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ ContinueExpr a test _loc -> do+ test <- execute test+ let signal = execThrow "" (loopCtrl a) []+ case test of+ Nothing -> signal+ Just o -> derefObject o >>= execute . objToBool >>= flip when signal+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ ReturnExpr returnStmt o _loc -> do+ o <- (execute o :: Exec (Maybe Object)) >>= maybe (return Nothing) (fmap Just . derefObject)+ if returnStmt then throwError (ExecReturn o) else maybe mzero (flip (execThrow "") [] . ExecThrow) o+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ WithDoc expr thn _loc -> execute expr >>=+ checkVoid (getLocation expr) "target of \"with\" statement" >>=+ flip execWithWithRefStore (execute thn)+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --++instance ObjectClass (ScriptExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ScriptExpr Object) where+ haskellDataInterface = interface "Script" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++localVarDefine :: Name -> Object -> Exec (Maybe Object)+localVarDefine name o = fmap (snd . fst) $+ runObjectFocus (updateLocal name NullRef $ modify (const $ Just o) >> get) False (reference UNQUAL name) ()++localVarUpdate :: Name -> (Maybe Object -> Maybe Object) -> Exec (Maybe Object)+localVarUpdate name f = fmap (snd . fst) $+ runObjectFocus (updateLocal name NullRef $ modify f >> get) False (reference UNQUAL name) ()++localVarLookup :: Name -> Exec Object+localVarLookup name = + (snd . fst) <$> runObjectFocus (updateLocal name NullRef get) True (reference UNQUAL name) () >>=+ maybe mzero return++-- | Like evaluating 'execute' on a value of 'Reference', except the you are evaluating an+-- 'Object' type. If the value of the 'Object' is not constructed with+-- 'ORef', the object value is returned unmodified.+derefObject :: Object -> Exec Object+derefObject = fmap snd . derefObjectGetReference++-- | Like 'derefObject' but also returns the 'Reference' value that was stored in the 'Object' that+-- was dereferenced, along with the dereferenced value. If the 'Object' is not constructed with+-- 'ORef', 'Prelude.Nothing' is returned instead of a 'Reference'.+derefObjectGetReference :: Object -> Exec (Maybe Reference, Object)+derefObjectGetReference o = maybeDerefObject (Just o) >>= \ (r, derefd) -> case derefd of+ Nothing -> case r of+ Nothing -> throwBadTypeError "dereferenced a non-reference value" o []+ Just r -> execThrow "reference evaluated to void" r [(errOfReference, o)]+ Just derefd -> return (r, derefd)++-- | Tries to dereference an 'Object'. If the 'Object' is an 'ORef' constructed 'Reference', the+-- reference is de-referenced, which may evaluate to 'Prelude.Nothing'. The dereferenced value is+-- returned in the 'Prelude.snd' of the pair. If the given 'Object' is an 'ORef', regardless of the+-- dereferenced value, the 'Reference' is returned in the 'Prelude.fst' of the pair. If the given+-- 'Object' is not an 'ORef' constructed 'Object', it is returned unmodified along with+-- 'Prelude.Nothing' in the 'Prelude.fst' of the pair.+maybeDerefObject :: Maybe Object -> Exec (Maybe Reference, Maybe Object)+maybeDerefObject = maybe (return (Nothing, Nothing)) $ \o -> case o of+ ORef r -> referenceLookup r >>= \ (r, o) -> case o of+ Nothing -> return (Just r, Nothing)+ Just o -> return (Just r, Just o)+ o -> return (Nothing, Just o)++----------------------------------------------------------------------------------------------------++-- | This data type instantates the 'execute' function for use in for-loop expressions.+data ForLoopBlock = ForLoopBlock Name Object (CodeBlock Object)++instance Executable ForLoopBlock (Bool, Maybe Object) where+ execute (ForLoopBlock name o block) = + execNested_ (M.singleton name o) $ loop (codeBlock block) where+ done cont = do+ ref <- gets execStack+ newValue <- return $ M.lookup name $ head $ mapList ref+ return (cont, newValue)+ loop ex = case ex of+ [] -> done True+ e:ex -> case e of+ ContinueExpr a cond _loc -> case cond of+ EvalExpr (ObjArithExpr (ObjectExpr VoidExpr)) -> done a+ cond -> execute cond >>= maybe err (execute . objToBool) >>= done . (if a then id else not) where+ err = fail "expression does not evaluate to boolean"+ e -> execute e >> loop ex++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Script Object) where+ toDaoStruct = let nm = renameConstructor in ask >>= \o -> case o of+ AST_Comment a -> nm "Comment" $ putComments a+ AST_IfThenElse a -> innerToStruct a+ AST_WhileLoop a -> innerToStruct a+ AST_RuleFunc a -> innerToStruct a+ AST_EvalObject a b loc -> nm "ObjectExpr" $ "expr" .= a >> putComments b >> putLocation loc+ AST_TryCatch a b c d loc -> nm "TryCatch" $ do+ "comments" .= a >> "tryBlock" .= b >> "elseBlocks" .= listToObj c+ "catchBlocks" .= listToObj d >> putLocation loc+ AST_ForLoop a b c loc -> nm "ForLoop" $ do+ "varName" .= a >> "iterate" .= b >> "block" .= c >> putLocation loc+ AST_ContinueExpr a b c loc -> nm (if a then "Continue" else "Break") $ do+ putComments b >> "condition" .= c >> putLocation loc+ AST_ReturnExpr a b loc -> nm (if a then "Return" else "Throw") $ do+ "expr" .= b >> putLocation loc+ AST_WithDoc a b loc -> nm "WithDoc" $ "expr" .= a >> "block" .= b >> putLocation loc++instance FromDaoStructClass (AST_Script Object) where+ fromDaoStruct = msum $+ [ constructor "Comment" >> AST_Comment <$> comments+ , AST_IfThenElse <$> fromDaoStruct+ , AST_WhileLoop <$> fromDaoStruct+ , AST_RuleFunc <$> fromDaoStruct+ , constructor "ObjectExpr" >> return AST_EvalObject <*> req "expr" <*> comments <*> location+ , constructor "TryCatch" >>+ return AST_TryCatch+ <*> comments+ <*> req "tryBlock"+ <*> reqList "elseBlocks"+ <*> reqList "catchBlocks"+ <*> location+ , constructor "ForLoop" >>+ return AST_ForLoop <*> req "varName" <*> req "iterate" <*> req "block" <*> location+ , constructor "Continue" >>+ return (AST_ContinueExpr True ) <*> comments <*> req "condition" <*> location+ , constructor "Break" >>+ return (AST_ContinueExpr False) <*> comments <*> req "condition" <*> location+ , constructor "Return" >> return (AST_ReturnExpr True ) <*> req "expr" <*> location+ , constructor "Throw" >> return (AST_ReturnExpr False) <*> req "expr" <*> location+ , constructor "WithDoc" >> return AST_WithDoc <*> req "expr" <*> req "block" <*> location+ ]++instance ObjectClass [AST_Script Object] where { obj=listToObj; fromObj=listFromObj; }++instance ObjectClass (AST_Script Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Script Object) where+ haskellDataInterface = interface "ScriptExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0x86 +instance B.Binary (ObjListExpr Object) MTab where+ put (ObjListExpr lst loc) = B.prefixByte 0x86 $ B.putUnwrapped lst >> B.put loc+ get = (B.tryWord8 0x86 $ return ObjListExpr <*> B.getUnwrapped <*> B.get) <|> fail "expecting ObjListExpr"++instance Executable (ObjListExpr Object) [Object] where+ execute (ObjListExpr exprs _) = forM (zip exprs [1..]) $ \ (a, i) -> execute a >>= maybe (err i) return where+ err i = execThrow "item in list literal expression evaluates to void" ExecErrorUntyped [(argNum, obj (i::Int))]++instance PPrintable (ObjListExpr Object) where { pPrint = pPrintInterm }++instance ObjectClass (ObjListExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ObjListExpr Object) where+ haskellDataInterface = interface "ListLiteral" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_ObjList Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_ObjList coms lst loc -> renameConstructor "ListLiteralExpression" $ do+ putComments coms >> defObjField "items" (listToObj lst) >> putLocation loc++instance FromDaoStructClass (AST_ObjList Object) where+ fromDaoStruct = constructor "ListLiteralExpression" >>+ return AST_ObjList <*> comments <*> reqList "items" <*> location++instance ObjectClass (AST_ObjList Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_ObjList Object) where+ haskellDataInterface = interface "ListLiteralExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance B.Binary (OptObjListExpr Object) MTab where+ put (OptObjListExpr o) = B.put o+ get = OptObjListExpr <$> B.get++instance Executable (OptObjListExpr Object) [Object] where+ execute (OptObjListExpr lst) = maybe (return []) execute lst++instance ObjectClass (OptObjListExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (OptObjListExpr Object) where+ haskellDataInterface = interface "OptionalListLiteral" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++-- | Evaluate an 'Exec', but if it throws an exception, set record an 'ObjectExpr' where+-- the exception occurred in the exception information.+updateExecError :: (ExecControl -> ExecControl) -> Exec a -> Exec a+updateExecError upd fn = catchError fn (\err -> throwError (upd err))++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_OptObjList Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_OptObjList coms o -> renameConstructor "OptObjList" $ "params" .=? o >> putComments coms++instance FromDaoStructClass (AST_OptObjList Object) where+ fromDaoStruct = constructor "OptObjList" >> return AST_OptObjList <*> comments <*> opt "params"++instance ObjectClass (AST_OptObjList Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_OptObjList Object) where+ haskellDataInterface = interface "OptionalListLiteralExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance B.Binary (LiteralExpr Object) MTab where+ put (LiteralExpr a loc) = B.put a >> B.put loc+ get = B.word8PrefixTable <|> fail "expecting LiteralExpr"++instance B.HasPrefixTable (LiteralExpr Object) B.Byte MTab where+ prefixTable = B.bindPrefixTable B.prefixTable $ \o -> LiteralExpr o <$> B.get++instance Executable (LiteralExpr Object) (Maybe Object) where { execute (LiteralExpr o _) = return (Just o) }++instance ObjectClass (LiteralExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (LiteralExpr Object) where+ haskellDataInterface = interface "Literal" $ do+ autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt >> defDeref execute++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Literal Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_Literal o loc -> renameConstructor "Literal" $ "obj" .= o >> putLocation loc++instance FromDaoStructClass (AST_Literal Object) where+ fromDaoStruct = constructor "Literal" >> return AST_Literal <*> req "obj" <*> location++instance ObjectClass (AST_Literal Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Literal Object) where+ haskellDataInterface = interface "LiteralExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0x3C 0x42 ReferenceExpr-->Reference+instance B.Binary (ReferenceExpr Object) MTab where+ put qref = case qref of+ ReferenceExpr q n r loc -> prefix q $ B.put n >> B.put r >> B.put loc where+ prefix q = B.prefixByte $ case q of+ { UNQUAL -> 0x48; LOCAL -> 0x49; CONST -> 0x4A; STATIC -> 0x4B; GLOBAL -> 0x4C; GLODOT -> 0x4D; }+ RefObjectExpr o r loc -> B.putWord8 0x4E >> B.put o >> B.put r >> B.put loc+ get = B.word8PrefixTable <|> fail "expecting Reference"++instance B.HasPrefixTable (ReferenceExpr Object) Word8 MTab where+ prefixTable = B.mkPrefixTableWord8 "ReferenceExpr" 0x48 0x4E $+ [ f UNQUAL, f LOCAL, f CONST, f STATIC, f GLOBAL, f GLODOT+ , return RefObjectExpr <*> B.get <*> B.get <*> B.get+ ] where { f q = return (ReferenceExpr q) <*> B.get <*> B.get <*> B.get }++instance Executable (ReferenceExpr Object) (Maybe Object) where+ execute qref = errLocation qref $ case qref of+ RefObjectExpr o NullRefExpr _ -> execute o+ RefObjectExpr o suf _ -> do+ o <- execute o >>=+ checkVoid (getLocation o) "function call on item in parentheses which evaluated to a void value"+ suf <- execute suf+ return $ Just $ obj $ RefObject o suf+ ReferenceExpr q ref suf _loc -> execute suf >>= return . Just . ORef . Reference q ref++instance ObjectClass (ReferenceExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ReferenceExpr Object) where+ haskellDataInterface = interface "ReferenceLiteral" $ do+ autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt+ defDeref (execute >=> fmap snd . maybeDerefObject)++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Reference Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_RefObject o ref loc -> renameConstructor "ParenExpr" $ do+ "paren" .= o >> "suffix" .= ref >> putLocation loc+ AST_Reference q coms name ref loc -> renameConstructor "Reference" $ do+ "qualifier" .= q >> putComments coms >> "name" .= name >> "suffix" .= ref >> putLocation loc++instance FromDaoStructClass (AST_Reference Object) where+ fromDaoStruct = msum $+ [ constructor "ParenExpr" >>+ return AST_RefObject <*> req "paren" <*> req "suffix" <*> location+ , constructor "Reference" >>+ return AST_Reference <*> req "qualifier"+ <*> comments <*> req "name" <*> req "suffix" <*> location+ ]++instance ObjectClass (AST_Reference Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Reference Object) where+ haskellDataInterface = interface "ReferenceExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct+ defDeref (msum . map (execute >=> fmap snd . maybeDerefObject) . toInterm)++----------------------------------------------------------------------------------------------------++-- binary 0x52 0x53+instance B.Binary (RefPrefixExpr Object) MTab where+ put o = case o of+ PlainRefExpr a -> B.put a+ RefPrefixExpr a b z -> let f = B.put b >> B.put z in case a of+ REF -> B.prefixByte 0x52 f+ DEREF -> B.prefixByte 0x53 f+ get = B.word8PrefixTable <|> fail "expecting RefPrefixExpr"++instance B.HasPrefixTable (RefPrefixExpr Object) B.Byte MTab where+ prefixTable = fmap PlainRefExpr B.prefixTable <>+ (B.mkPrefixTableWord8 "RefPrefixExpr" 0x52 0x53 $+ let f q = return (RefPrefixExpr q) <*> B.get <*> B.get in [f REF, f DEREF])++instance Executable (RefPrefixExpr Object) (Maybe Object) where+ execute ref = errLocation ref $ case ref of+ PlainRefExpr ref -> execute ref+ RefPrefixExpr op ref loc -> case op of+ REF -> do+ ref <- execute ref >>= checkVoid loc "operand of referencing operator ($)"+ case ref of+ ORef ref -> return $ Just $ ORef $ RefWrapper ref+ ref -> return $ Just $ ORef $ RefObject ref NullRef+ DEREF -> execute ref >>= checkVoid loc "operand of dereferencing operator (@)" >>=+ fmap Just . derefObject++instance ObjectClass (RefPrefixExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (RefPrefixExpr Object) where+ haskellDataInterface = interface "RefPrefixLiteral" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_RefPrefix Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_PlainRef a -> renameConstructor "PlainRef" $ "ref" .= a >> return ()+ AST_RefPrefix a b c loc -> renameConstructor "RefPrefix" $ do+ "op" .= a >> putComments b >> "expr" .= c >> putLocation loc++instance FromDaoStructClass (AST_RefPrefix Object) where+ fromDaoStruct = msum $+ [ constructor "RefPrefix" >>+ return AST_RefPrefix <*> req "op" <*> req "expr" <*> req "expr" <*> location+ , constructor "PlainRef" >> AST_PlainRef <$> req "ref"+ ]++instance ObjectClass (AST_RefPrefix Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_RefPrefix Object) where+ haskellDataInterface = interface "ReferencePrefixExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0x74 0x76+instance B.Binary (RuleFuncExpr Object) MTab where+ put o = case o of+ LambdaExpr a b z -> B.prefixByte 0x74 $ B.put a >> B.put b >> B.put z+ FuncExpr a b c z -> B.prefixByte 0x75 $ B.put a >> B.put b >> B.put c >> B.put z+ RuleExpr a b z -> B.prefixByte 0x76 $ B.put a >> B.put b >> B.put z+ get = B.word8PrefixTable <|> fail "expecting RuleFuncExpr"++instance B.HasPrefixTable (RuleFuncExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "RuleFuncExpr" 0x74 0x76 $+ [ return LambdaExpr <*> B.get <*> B.get <*> B.get+ , return FuncExpr <*> B.get <*> B.get <*> B.get <*> B.get+ , return RuleExpr <*> B.get <*> B.get <*> B.get+ ]++instance Executable (RuleFuncExpr Object) (Maybe Object) where+ execute o = case o of+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ LambdaExpr params script _ -> do+ let exec = setupCodeBlock script+ return $ Just $ new $+ [CallableCode{argsPattern=params, codeSubroutine=exec, returnType=nullValue}]+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ FuncExpr name params script _ -> do+ let exec = setupCodeBlock script+ let callableCode = CallableCode{argsPattern=params, codeSubroutine=exec, returnType=nullValue}+ localVarUpdate name $ \o -> case o>>=fromObj of+ Nothing -> Just $ obj [callableCode]+ Just cc -> Just $ obj $ cc++[callableCode]+ return (Just $ obj [callableCode])+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ RuleExpr rs script _ -> do+ let sub = setupCodeBlock script+ pats <- execute rs+ return $ Just $ obj $ PatternRule{ rulePatterns=pats, ruleAction=sub }+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --++instance ObjectClass (RuleFuncExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (RuleFuncExpr Object) where+ haskellDataInterface = interface "FunctionLiteral" $ do+ autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt >> autoDefPPrinter++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_RuleFunc Object) where+ toDaoStruct = let nm = renameConstructor in ask >>= \o -> case o of+ AST_Lambda a b loc -> nm "Lambda" $ "params" .= a >> "block" .= b >> putLocation loc+ AST_Func a b c d loc -> nm "Function" $+ putComments a >> "name" .= b >> "params" .= c >> "block" .= d >> putLocation loc+ AST_Rule a b loc -> nm "Rule" $ "params" .= a >> "block" .= b >> putLocation loc++instance FromDaoStructClass (AST_RuleFunc Object) where+ fromDaoStruct = msum $+ [ constructor "Lambda" >> return AST_Lambda <*> req "params" <*> req "block" <*> location+ , constructor "Function" >>+ return AST_Func <*> comments <*> req "name" <*> req "params" <*> req "block" <*> location+ , constructor "Rule" >> return AST_Rule <*> req "params" <*> req "block" <*> location+ ]++instance ObjectClass (AST_RuleFunc Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_RuleFunc Object) where+ haskellDataInterface = interface "FunctionLiteralExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0x60 0x65+instance B.Binary (ObjectExpr Object) MTab where+ put o = case o of+ ObjSingleExpr a -> B.put a+ ObjLiteralExpr a -> B.put a+ VoidExpr -> B.putWord8 0x60+ ArithPfxExpr a b z -> B.prefixByte 0x61 $ B.put a >> B.put b >> B.put z+ InitExpr a b c z -> B.prefixByte 0x62 $ B.put a >> B.put b >> B.put c >> B.put z+ StructExpr a b z -> B.prefixByte 0x63 $ B.put a >> B.put b >> B.put z+ MetaEvalExpr a z -> B.prefixByte 0x64 $ B.put a >> B.put z+ get = B.word8PrefixTable <|> fail "expecting ObjectExpr"++instance B.HasPrefixTable (ObjectExpr Object) B.Byte MTab where+ prefixTable = mconcat $+ [ ObjLiteralExpr <$> B.prefixTable+ , ObjSingleExpr <$> B.prefixTable+ , B.mkPrefixTableWord8 "ObjectExpr" 0x60 0x64 $+ [ return VoidExpr+ , return ArithPfxExpr <*> B.get <*> B.get <*> B.get+ , return InitExpr <*> B.get <*> B.get <*> B.get <*> B.get+ , return StructExpr <*> B.get <*> B.get <*> B.get+ , return MetaEvalExpr <*> B.get <*> B.get+ ]+ ]++instance Executable (ObjectExpr Object) (Maybe Object) where+ execute o = errLocation o $ case o of+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ VoidExpr -> return Nothing+ -- 'VoidExpr's only occur in return statements. Returning 'ONull' where nothing exists is+ -- probably the most intuitive thing to do on an empty return statement.+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ ObjLiteralExpr o -> execute o+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ ObjSingleExpr o -> execute o+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ ArithPfxExpr op expr loc -> do+ expr <- execute expr >>= fmap snd . maybeDerefObject >>=+ checkVoid loc ("operand to prefix operator "++show op)+ execute $ fmap Just (evalArithPrefixOp op expr)+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ InitExpr ref bnds initMap _ -> Just <$> _evalInit (dotLabelToRefExpr ref) bnds initMap+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ StructExpr name (OptObjListExpr items) _ -> case items of+ Nothing -> return (Just $ OTree $ Nullary{ structName=name })+ Just (ObjListExpr items _) -> execNested_ M.empty $ do+ forM_ items $ \item -> case item of + AssignExpr{} -> execute item -- fill the local stack by executing each assignment+ _ -> fail "struct initializer is not an assignment expression"+ stack <- gets execStack+ let items = head $ mapList stack+ return $ Just $ OTree $+ if M.null items+ then Nullary{ structName=name }+ else Struct{ fieldMap=items, structName=name }+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ MetaEvalExpr expr _ -> return $ Just $ new expr+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --++_evalInit :: ReferenceExpr Object -> OptObjListExpr Object -> ObjListExpr Object -> Exec Object+_evalInit ref bnds initMap = do+ ref <- execute ref >>= checkVoid (getLocation ref) "initializer label"+ ref <- case ref of+ ORef (Reference UNQUAL name NullRef) -> pure name+ ref -> throwBadTypeError "cannot use reference as initalizer" ref []+ bnds <- execute bnds >>= mapM derefObject+ let cantUseBounds msg =+ execThrow ("initializer "++msg++" must be defined without bounding parameters")+ ExecErrorUntyped [(assertFailed, OList bnds)]+ let list = case bnds of+ [] -> execNested_ M.empty $ fmap OList $ execute initMap >>= mapM derefObject+ _ -> cantUseBounds "for list constructor"+ let (ObjListExpr items _) = initMap+ let dict = case bnds of+ [] -> (ODict . snd) <$> execNested M.empty (mapM_ assignUnqualifiedOnly items)+ _ -> cantUseBounds "for dict constructor"+ case uchars ref of+ "list" -> list+ "List" -> list+ "dict" -> dict+ "Dict" -> dict+ "Dictionary" -> dict+ _ -> do+ tab <- execGetObjTable ref+ let qref = Reference UNQUAL ref NullRef+ case tab of+ Nothing -> execThrow "unknown object constructor" qref []+ Just tab -> execNested_ M.empty $ case objInitializer tab of+ Nothing -> execThrow "cannot declare constant object of type" qref []+ Just (init, fold) -> do+ o <- init bnds+ items <- forM items $ \item -> case item of+ AssignExpr a op b _ -> do+ a <- execute a >>= checkVoid (getLocation a) "left-hand side of initializer assignemt"+ b <- execute b >>= checkVoid (getLocation b) "right-hand side of initializer assignment" >>= derefObject+ return $ InitAssign a op b+ EvalExpr arith -> fmap InitSingle $+ execute arith >>= checkVoid (getLocation arith) "initializer item"+ OHaskell . Hata tab <$> fold o items++instance ObjectClass (ObjectExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ObjectExpr Object) where+ haskellDataInterface = interface "ObjectLiteral" $ do+ autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt+ defDeref execute >> autoDefPPrinter++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Object Object) where+ toDaoStruct = let nm = renameConstructor in ask >>= \o -> case o of+ AST_Void -> makeNullary "Void"+ AST_ObjLiteral a -> innerToStruct a+ AST_ObjSingle a -> innerToStruct a+ AST_ArithPfx a b c loc -> nm "ArithPrefix" $ do+ "op" .= a >> putComments b >> "expr" .= c >> putLocation loc+ AST_Init a b c loc -> nm "Init" $ do+ "name" .= a >> "params" .= b >> "initList" .= c >> putLocation loc+ AST_Struct a b loc -> nm "Struct" $ "name" .= a >> "initList" .= b >> putLocation loc+ AST_MetaEval a loc -> nm "MetaEval" $ "block" .= a >> putLocation loc++instance FromDaoStructClass (AST_Object Object) where+ fromDaoStruct = msum $+ [ nullary "Void" >> return AST_Void+ , AST_ObjLiteral <$> fromDaoStruct+ , AST_ObjSingle <$> fromDaoStruct+ , constructor "ArithPrefix" >>+ pure AST_ArithPfx <*> req "op" <*> comments <*> req "expr" <*> location+ , constructor "Init" >>+ pure AST_Init <*> req "name" <*> req "params" <*> req "initList" <*> location+ , constructor "Struct" >> pure AST_Struct <*> req "name" <*> req "initList" <*> location+ , constructor "MetaEval" >> pure AST_MetaEval <*> req "block" <*> location+ ]++instance ObjectClass (AST_Object Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Object Object) where+ haskellDataInterface = interface "ObjectLiteralExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0x6A +instance B.Binary (ArithExpr Object) MTab where+ put o = case o of+ ObjectExpr a -> B.put a+ ArithExpr a b c z -> B.prefixByte 0x6A $ B.put a >> B.put b >> B.put c >> B.put z+ get = B.word8PrefixTable <|> fail "expecting arithmetic expression"++instance B.HasPrefixTable (ArithExpr Object) B.Byte MTab where+ prefixTable = mappend (ObjectExpr <$> B.prefixTable) $+ B.mkPrefixTableWord8 "ArithExpr" 0x6A 0x6A $+ [pure ArithExpr <*> B.get <*> B.get <*> B.get <*> B.get]++instance Executable (ArithExpr Object) (Maybe Object) where+ execute o = case o of+ ObjectExpr o -> execute o+ ArithExpr left' op right' loc -> do+ let err1 msg = msg++"-hand operand of "++show op++ "operator "+ evalLeft = execute left' >>= checkVoid loc (err1 "left" )+ evalRight = execute right' >>= checkVoid loc (err1 "right")+ derefLeft = evalLeft >>= derefObject+ derefRight = evalRight >>= derefObject+ logical isAndOp = fmap Just $ do+ left <- derefLeft >>= execute . objToBool+ if left+ then if isAndOp then derefRight else return OTrue+ else if isAndOp then return ONull else derefRight+ case op of+ AND -> logical True+ OR -> logical False+ op -> do+ (left, right) <- case op of+ ARROW -> liftM2 (,) derefLeft evalRight+ _ -> liftM2 (,) derefLeft derefRight+ execute (fmap Just $ evalInfixOp op left right)++instance ObjectClass (ArithExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ArithExpr Object) where+ haskellDataInterface = interface "Arithmetic" $ do+ autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt+ defDeref execute >> autoDefPPrinter++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Arith Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_Object a -> innerToStruct a+ AST_Arith a b c loc -> renameConstructor "Arithmetic" $ do+ "left" .= a >> "op" .= b >> "right" .= c >> putLocation loc++instance FromDaoStructClass (AST_Arith Object) where+ fromDaoStruct = msum $+ [ AST_Object <$> fromDaoStruct+ , constructor "Arithmetic" >>+ pure AST_Arith <*> req "left" <*> req "op" <*> req "right" <*> location+ ]++instance ObjectClass (AST_Arith Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Arith Object) where+ haskellDataInterface = interface "ArithmeticExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++newtype DerefAssignExpr = DerefAssignExpr (AssignExpr Object)+-- ^ This data type instantiates 'Executable' such that the result is always dereference once. This+-- is necessary because an 'AssignExpr' always evaluates to an 'Object' literal expression, meaning+-- if the 'AssignExpr' contains 'ReferenceExpr', it will evaluate to an 'Object' constructing a+-- literal 'Reference' (using the 'ORef' constructor). Sometimes this is desirable, sometimes it is+-- not. It is desirable when evaluating arguments for a function call that requests it's arguments+-- not be dereferenced. It is not desirable when evaluating an arithmetic equation and the integer+-- value stored at a 'Reference' variable is required, and not the 'Reference' value itself.+-- The calling context cannot know what the result will be, or whether or not it is necessary to+-- call 'derefObject' on the result unless the calling context inspects the 'AssignExpr' value with+-- a case statement. By wrapping the 'AssignExpr' in this data type first and then evaluating+-- 'execute', you are guranteed that any 'ReferenceExpr' will evaluate to the value stored at the+-- resulting 'Reference' literal, and not the 'Reference' literal itself.++instance Executable DerefAssignExpr (Maybe Object) where+ execute (DerefAssignExpr o) = case o of+ EvalExpr{} -> execute o >>= fmap snd . maybeDerefObject+ AssignExpr{} -> execute o++_executeAssignExpr+ :: (Reference -> UpdateOp -> Object -> Exec (Maybe Object))+ -> AssignExpr Object -> Exec (Maybe Object)+_executeAssignExpr update o = case o of+ EvalExpr expr -> execute expr+ AssignExpr qref op expr loc -> do+ qref <- execute qref >>=+ checkVoid (getLocation qref) "left-hand side of assignment expression evaluated to void"+ case qref of+ ORef qref -> do+ newObj <- execute expr >>= checkVoid loc "right-hand side of assignment" >>= derefObject + update qref op newObj+ _ -> fail "left-hand side of assignment expression is not a reference value"++-- binary 0x6F +instance B.Binary (AssignExpr Object) MTab where+ put o = case o of+ EvalExpr a -> B.put a+ AssignExpr a b c z -> B.prefixByte 0x6F $ B.put a >> B.put b >> B.put c >> B.put z+ get = B.word8PrefixTable <|> fail "expecting AssignExpr"++instance B.HasPrefixTable (AssignExpr Object) B.Byte MTab where+ prefixTable = mappend (EvalExpr <$> B.prefixTable) $+ B.mkPrefixTableWord8 "AssignExpr" 0x6F 0x6F $+ [pure AssignExpr <*> B.get <*> B.get <*> B.get <*> B.get]++instance Executable (AssignExpr Object) (Maybe Object) where+ execute = _executeAssignExpr $ \qref op newObj ->+ snd <$> referenceUpdate qref (op/=UCONST) (evalUpdateOp (Just qref) op newObj)++-- | This function works a bit like how 'execute' works on an 'AssignExpr' data type, but every+-- assignment is checked to make sure it is local or unqualified. Furthurmore, all assignments are+-- forced into the top of the local variable stack, already-defined vairables at higher points in+-- the local variable stack are not updated in place. This function is used to define items in+-- is one important difference: it is specifically modified to work for evaluation of 'InitExpr'+-- data types, for example in the Dao language expression: @a = dict {a=1, b=2};@ Using this+-- function instead of 'execute' will always assign variables in the top of the local variable+-- stack, regardless of whether the variable has been defined before. This makes it possible to+-- write Dao language statements like this: @a=1; a = dict {a=a, b=2};@ which would create a+-- dictionary @a = dict {a=1, b=2};@, because before the "dict{}" expression, "a" had a value of 1.+assignUnqualifiedOnly :: AssignExpr Object -> Exec (Maybe Object)+assignUnqualifiedOnly = _executeAssignExpr $ \qref op newObj -> case qref of+ Reference UNQUAL r NullRef -> do+ store <- gets execStack+ let oldObj = stackLookup r store+ newObj <- evalUpdateOp (Just qref) op newObj oldObj+ (result, store) <- pure $ stackUpdateTop (const (newObj, newObj)) r store+ modify $ \xunit -> xunit{ execStack = store }+ return result+ _ -> execThrow "assignment must be unqualified" ExecErrorUntyped [(errOfReference, obj qref)]++instance ObjectClass (AssignExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AssignExpr Object) where+ haskellDataInterface = interface "Assignment" $ do+ autoDefNullTest >> autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt+ defDeref execute >> autoDefPPrinter++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_Assign Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_Eval o -> innerToStruct o+ AST_Assign to op from loc -> renameConstructor "Assign" $ do+ "to" .= to >> "op" .= op >> "from" .= from >> putLocation loc++instance FromDaoStructClass (AST_Assign Object) where+ fromDaoStruct = msum $+ [ AST_Eval <$> fromDaoStruct+ , pure AST_Assign <*> req "to" <*> req "op" <*> req "from" <*> location+ ]++instance ObjectClass (AST_Assign Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_Assign Object) where+ haskellDataInterface = interface "AssignmentExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance B.Binary (ObjTestExpr Object) MTab where+ put o = case o of+ ObjArithExpr a -> B.put a+ ObjTestExpr a b c d -> B.prefixByte 0x73 $ B.put a >> B.put b >> B.put c >> B.put d+ ObjRuleFuncExpr a -> B.put a+ get = B.word8PrefixTable <|> fail "expecting ObjTestExpr"++instance B.HasPrefixTable (ObjTestExpr Object) Word8 MTab where+ prefixTable = mconcat $ + [ ObjArithExpr <$> B.prefixTable+ , ObjRuleFuncExpr <$> B.prefixTable+ , B.mkPrefixTableWord8 "ObjTestExpr" 0x73 0x73 $+ [return ObjTestExpr <*> B.get <*> B.get <*> B.get <*> B.get]+ ]++instance Executable (ObjTestExpr Object) (Maybe Object) where+ execute o = errCurrentModule $ case o of+ ObjArithExpr a -> execute a+ ObjTestExpr a b c _ ->+ execute a >>= checkVoid (getLocation a) "conditional expression evaluates to void" >>= derefObject >>=+ execute . objToBool >>= \ok -> if ok then execute b else execute c+ ObjRuleFuncExpr o -> execute o++instance ObjectClass (ObjTestExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (ObjTestExpr Object) where+ haskellDataInterface = interface "ObjectTest" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_ObjTest Object) where+ toDaoStruct = ask >>= \o -> case o of+ AST_ObjArith a -> innerToStruct a+ AST_ObjTest a b c d e f -> renameConstructor "ObjTest" $ do+ "condition" .= a+ "quesMarkComs" .= b >> "action" .= c+ "colonComs" .= d >> "alt" .= e+ putLocation f+ AST_ObjRuleFunc a -> innerToStruct a++instance FromDaoStructClass (AST_ObjTest Object) where+ fromDaoStruct = msum $+ [ AST_ObjArith <$> fromDaoStruct+ , do constructor "ObjTest"+ return AST_ObjTest+ <*> req "condition" + <*> (maybe (Com ()) id <$> opt "quesMarkComs") <*> req "action"+ <*> (maybe (Com ()) id <$> opt "colonComs" ) <*> req "alt"+ <*> location+ , AST_ObjRuleFunc <$> fromDaoStruct+ ]++instance ObjectClass (AST_ObjTest Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_ObjTest Object) where+ haskellDataInterface = interface "ObjectTestExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass AST_Namespace where+ toDaoStruct = ask >>= \a -> case a of+ AST_NoNamespace -> makeNullary "NoNamespace"+ AST_Namespace n loc -> renameConstructor "Namespace" $ "name" .= n >> putLocation loc++instance FromDaoStructClass AST_Namespace where+ fromDaoStruct = msum $+ [ nullary "NoNamespace" >> return AST_NoNamespace+ , constructor "Namespace" >> return AST_Namespace <*> req "name" <*> location+ ]++instance ObjectClass AST_Namespace where { obj=new; fromObj=objFromHata; }++instance HataClass AST_Namespace where+ haskellDataInterface = interface "NamespaceExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- binary 0x81 0x82 -- placed next to with 'DotNameExpr'+instance B.Binary AttributeExpr MTab where+ put o = case o of+ AttribDotNameExpr a -> B.put a+ AttribStringExpr a loc -> B.prefixByte 0x82 $ B.put a >> B.put loc+ get = B.word8PrefixTable <|> fail "expecting AttributeExpr"++instance B.HasPrefixTable AttributeExpr Word8 MTab where+ prefixTable = (AttribDotNameExpr <$> B.prefixTable) <>+ B.mkPrefixTableWord8 "AttributeExpr" 0x82 0x82 [return AttribStringExpr <*> B.get <*> B.get]++-- binary 0xE9 0xEE+instance B.Binary (TopLevelExpr Object) MTab where+ put o = case o of+ RequireExpr a z -> B.prefixByte 0xE9 $ B.put a >> B.put z+ ImportExpr a b z -> B.prefixByte 0xEA $ B.put a >> B.put b >> B.put z+ TopScript a z -> B.prefixByte 0xEB $ B.put a >> B.put z+ EventExpr BeginExprType b z -> B.prefixByte 0xEC $ B.put b >> B.put z+ EventExpr ExitExprType b z -> B.prefixByte 0xED $ B.put b >> B.put z+ EventExpr EndExprType b z -> B.prefixByte 0xEE $ B.put b >> B.put z+ get = B.word8PrefixTable <|> fail "expecting TopLevelExpr"++instance B.HasPrefixTable (TopLevelExpr Object) B.Byte MTab where+ prefixTable = B.mkPrefixTableWord8 "TopLevelExpr" 0xE9 0xEE $+ [ return RequireExpr <*> B.get <*> B.get+ , return ImportExpr <*> B.get <*> B.get <*> B.get+ , return TopScript <*> B.get <*> B.get+ , return (EventExpr BeginExprType) <*> B.get <*> B.get+ , return (EventExpr ExitExprType ) <*> B.get <*> B.get+ , return (EventExpr EndExprType ) <*> B.get <*> B.get+ ]++instance Executable (TopLevelExpr Object) () where+ execute o = errCurrentModule $ case o of+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ RequireExpr{} -> attrib "require"+ ImportExpr{} -> attrib "import"+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ TopScript script _ -> do+ ((), dict) <- execNested mempty $ catchPredicate (execute script) >>= \pval -> case pval of+ OK _ -> return ()+ PFail (ExecReturn _) -> return ()+ PFail err -> throwError err+ Backtrack -> return () -- do not backtrack at the top-level+ let addIfFuncs a b = maybe b id $ do -- overwrite previously declared variables...+ (a, b) <- Just (,) <*> fromObj a <*> fromObj b+ Just $ obj ((a++b)::[CallableCode]) -- ...unless both variables are [CallableCode]+ modify $ \xunit -> xunit{ globalData = M.unionWith addIfFuncs (globalData xunit) dict }+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ EventExpr typ script _ -> do+ let exec = setupCodeBlock script+ let f = (++[exec])+ modify $ \xunit -> case typ of+ BeginExprType -> xunit{ preExec = f (preExec xunit) }+ EndExprType -> xunit{ postExec = f (postExec xunit) }+ ExitExprType -> xunit{ quittingTime = f (quittingTime xunit) }+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --+ where+ attrib a = fail $ a++" expression must occur only at the top of a dao script file"++instance ObjectClass (TopLevelExpr Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (TopLevelExpr Object) where+ haskellDataInterface = interface "TopLevel" $ do+ autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt >> autoDefPPrinter++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass AST_Attribute where+ toDaoStruct = ask >>= \o -> case o of+ AST_AttribDotName str -> renameConstructor "AttributeDotName" $ innerToStruct str+ AST_AttribString str loc ->+ renameConstructor "AttributeString" $ "value" .= str >> putLocation loc++instance FromDaoStructClass AST_Attribute where+ fromDaoStruct = msum $+ [ constructor "AttributeDotName" >> AST_AttribDotName <$> innerFromStruct "DotLabel"+ , constructor "AttributeString" >> return AST_AttribString <*> req "value" <*> location+ ]++instance ObjectClass AST_Attribute where { obj=new; fromObj=objFromHata; }++instance HataClass AST_Attribute where+ haskellDataInterface = interface "AttributeExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++instance ToDaoStructClass (AST_TopLevel Object) where+ toDaoStruct = let nm = renameConstructor in ask >>= \o -> case o of+ AST_Require a loc -> nm "Require" $ "attribute" .= a >> putLocation loc+ AST_Import a b loc -> nm "Import" $ "attribute" .= a >> "namespace" .= b >> putLocation loc+ AST_TopScript a loc -> nm "TopLevel" $ "script" .= a >> putLocation loc+ AST_TopComment a -> nm "Comment" $ putComments a+ AST_Event a b c loc ->+ nm "Event" $ "type" .= a >> "block" .= c >> putComments b >> putLocation loc++instance FromDaoStructClass (AST_TopLevel Object) where+ fromDaoStruct = msum $+ [ constructor "Import" >> return AST_Import <*> req "attribute" <*> req "namespace" <*> location+ , constructor "Require" >> return AST_Require <*> req "attribute" <*> location+ , constructor "TopLevel" >> return AST_TopScript <*> req "script" <*> location+ , constructor "Event" >> return AST_Event <*> req "type" <*> comments <*> req "block" <*> location+ , constructor "Comment" >> AST_TopComment <$> comments+ ]++instance ObjectClass (AST_TopLevel Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_TopLevel Object) where+ haskellDataInterface = interface "TopLevelExpression" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++-- the number is encoded by the ASCII encoded string "DaoProg\0"+program_magic_number :: Word64+program_magic_number = 0x44616f50726f6700++instance B.Binary (Program Object) MTab where+ put o = do+ -- place a magic number first, + B.putWord64be program_magic_number+ mapM_ B.put $ topLevelExprs o+ get = do+ magic <- B.lookAhead B.getWord64be+ guard (magic == program_magic_number)+ B.getWord64be >> fmap Program B.get++-- | Initialized the current 'ExecUnit' by evaluating all of the 'TopLevel' data in a+-- 'AST.AST_SourceCode'.+instance Executable (Program Object) () where+ execute (Program ast) = do+ ((), localVars) <- execNested mempty $ mapM_ execute (dropWhile isAttribute ast)+ -- Now, the local variables that were defined in the top level need to be moved to the global+ -- variable store.+ modify $ \xunit -> xunit{ globalData = M.union localVars (globalData xunit) }++instance ObjectClass (Program Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (Program Object) where+ haskellDataInterface = interface "ProgramData" $ do+ autoDefEquality >> autoDefNullTest >> autoDefBinaryFmt++----------------------------------------------------------------------------------------------------++_withGlobalKey :: Object -> (H.Index Object -> RefMonad Object Dynamic a) -> Exec a+_withGlobalKey idx f = gets globalMethodTable >>= \mt -> + gets runtimeRefTable >>= liftIO . runReaderT (f $ H.hashNewIndex (H.deriveHash128_DaoBinary mt) idx)++-- | Some objects may refer to an object that serves as a unique identifier created by the system,+-- for example objects refereing to file handles. These unique identifying objects should always be+-- stored in this table. The Dao 'Object' wrapper should be used as the index to retrieve the Object+-- in the table. This function takes the object to be stored, a destructor function to be called on+-- releasing the object, and an indexing object used to identify the stored object in the table.+initializeGlobalKey :: Typeable o => o -> (o -> IO ()) -> Object -> Exec (H.Index Object)+initializeGlobalKey o destructor idx = _withGlobalKey idx $ \key ->+ initializeWithKey (toDyn o) (destructor o) key >> return key++-- | Destroy an object that was stored into the global key table using 'initializeGlobalKey'. The+-- destructor function passed to the 'initializeGlobalKey' will be evaluated, and the object will+-- be removed from the table. This function takes an indexing object used to select the stored+-- object from the table.+destroyGlobalKey :: Object -> Exec ()+destroyGlobalKey = flip _withGlobalKey destroyWithKey++----------------------------------------------------------------------------------------------------++instance ToDaoStructClass (AST_SourceCode Object) where+ toDaoStruct = renameConstructor "SourceCode" $ do+ "modified" .=@ sourceModified+ "path" .=@ sourceFullPath+ asks directives >>= define "code" . listToObj++instance FromDaoStructClass (AST_SourceCode Object) where+ fromDaoStruct = constructor "SourceCode" >>+ return AST_SourceCode <*> req "modified" <*> req "path" <*> reqList "code"++instance ObjectClass (AST_SourceCode Object) where { obj=new; fromObj=objFromHata; }++instance HataClass (AST_SourceCode Object) where+ haskellDataInterface = interface "SourceCode" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++-- | Simply converts an 'Dao.Interpreter.AST_SourceCode' directly to a list of+-- 'Dao.Interpreter.TopLevelExpr's.+evalTopLevelAST :: AST_SourceCode Object -> Exec (Program Object)+evalTopLevelAST ast = case toInterm ast of+ [o] -> return o+ [] -> fail "converting AST_SourceCode to Program by 'toInterm' returned null value"+ _ -> fail "convertnig AST_SourceCode to Program by 'toInterm' returned ambiguous value"++----------------------------------------------------------------------------------------------------+-- $Builtin_object_interfaces+-- The following functions provide object interfaces for essential data types.++instance HataClass () where { haskellDataInterface = interface "HaskellNullValue" (return ()) }++type Get a = B.GGet MethodTable a+type Put = B.GPut MethodTable++-- This is only necessary to shorten the name 'MethodTable' because it is used throughout so many+-- instance declarations and type contexts.+type MTab = MethodTable++----------------------------------------------------------------------------------------------------++newtype MethodTable = MethodTable (M.Map Name (Interface Dynamic))++instance Monoid MethodTable where+ mempty = MethodTable mempty+ mappend (MethodTable a) (MethodTable b) = let dups = M.intersection a b in+ if M.null dups+ then MethodTable (M.union b a)+ else error ("Namespace conflict when installing built-in data type interfaces: "++show (M.keys dups))++-- | Lookup an 'Interface' by it's name from within the 'Exec' monad.+execGetObjTable :: Name -> Exec (Maybe (Interface Dynamic))+execGetObjTable nm = gets (lookupMethodTable nm . globalMethodTable)++lookupMethodTable :: Name -> MethodTable -> Maybe (Interface Dynamic)+lookupMethodTable nm (MethodTable tab) = M.lookup nm tab++-- not for export, use 'daoClass'+_insertMethodTable :: (Typeable o, HataClass o) => o -> Interface o -> MethodTable -> MethodTable+_insertMethodTable _ ifc = flip mappend $+ MethodTable (M.singleton (objInterfaceName ifc) (interfaceToDynamic ifc))++instance B.HasCoderTable MethodTable where+ getEncoderForType nm mtab = fmap fst $ lookupMethodTable nm mtab >>= objBinaryFormat+ getDecoderForType nm mtab = fmap snd $ lookupMethodTable nm mtab >>= objBinaryFormat++----------------------------------------------------------------------------------------------------++-- | Implements a "for" loop using a 'ReadIterable' item. This function should receive the+-- 'iter' object produced by 'initReadIter' that can be used by this function to extract each+-- 'val' and a function that is evaluated using a 'val' on every iteration of the loop.+-- 'readForLoop' should be defined call the given function as many times as necessary to+-- exaust the 'val's in the 'iter'.+--+-- /NOTE:/ When defining iterators, it is important to use 'execForM' or 'execForM_' to properly+-- handle "break" and "catch" statements.+class ReadIterable iter val | iter -> val where+ readForLoop :: iter -> (val -> Exec ()) -> Exec ()++instance ReadIterable [Object] Object where { readForLoop iter = execForM_ iter }++instance ReadIterable Hata Object where+ readForLoop h@(Hata ifc d) f = case objReadIterable ifc of+ Nothing -> throwBadTypeError "cannot iterate over object" (obj h) []+ Just for -> for d f++instance ReadIterable Object Object where+ readForLoop o f = case o of+ OList o -> readForLoop o f+ OHaskell o -> readForLoop o f+ _ -> throwBadTypeError "cannot iterate over object" o []++----------------------------------------------------------------------------------------------------++-- | A class that provides the 'updateForLoop' function, which is a function that will iterate over+-- types which can be read sequentially and modified as they are read.+--+-- /NOTE:/ When defining iterators, it is important to use 'execForM' or 'execForM_' to properly+-- handle "break" and "catch" statements.+class UpdateIterable iter val | iter -> val where+ updateForLoop :: iter -> (val -> Exec val) -> Exec iter++instance UpdateIterable [Object] (Maybe Object) where+ updateForLoop iter f =+ fmap (concatMap $ \o -> maybe [] id $ (o>>=fromObj) <|> fmap return o) (execForM iter $ f . Just)++instance UpdateIterable Hata (Maybe Object) where+ updateForLoop h@(Hata ifc d) f = case objUpdateIterable ifc of+ Nothing -> throwBadTypeError "cannot iterate over object" (obj h) []+ Just for -> Hata ifc <$> for d f++instance UpdateIterable T_dict (Maybe Object) where+ updateForLoop m f = fmap (M.fromList . concat) $ execForM (M.assocs m) $ \ (i, o) -> do+ p <- f (Just $ obj $ Pair (obj i, o))+ case p of+ Nothing -> return []+ Just p -> case fromObj p of+ Just (Pair (ref, o)) -> case fromObj ref of+ Just ref -> return [(ref, o)]+ Nothing -> throwBadTypeError "iterator cannot updte dictionary item" ref []+ Nothing -> throwBadTypeError "a dictionary iterator must store Pair objects" p []++instance UpdateIterable Object (Maybe Object) where+ updateForLoop o f = case o of+ OList o -> OList <$> updateForLoop o f+ ODict o -> ODict <$> updateForLoop o f+ OHaskell o -> OHaskell <$> updateForLoop o f+ o -> throwBadTypeError "cannot iterae over object" o []++----------------------------------------------------------------------------------------------------++-- | This class only exists to allow many different Haskell data types to declare their+-- 'Interface' under the same funcion name: 'haskellDataInterface'. Instantiate this function with+-- the help of the 'interface' function.+class HataClass typ where { haskellDataInterface :: Interface typ }++instance HataClass Location where+ haskellDataInterface = interface "Location" $ do+ autoDefEquality >> autoDefOrdering+ autoDefToStruct >> autoDefFromStruct+ autoDefPPrinter++instance HataClass Comment where+ haskellDataInterface = interface "Comment" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest >> autoDefPPrinter+ autoDefToStruct >> autoDefFromStruct++instance HataClass DotNameExpr where+ haskellDataInterface = interface "DotName" $ do+ autoDefEquality >> autoDefBinaryFmt >> autoDefPPrinter++instance HataClass AST_DotName where+ haskellDataInterface = interface "DotNameExpression" $ do+ autoDefEquality >> autoDefPPrinter >> autoDefToStruct >> autoDefFromStruct++instance HataClass DotLabelExpr where+ haskellDataInterface = interface "DotLabel" $ do+ autoDefEquality >> autoDefBinaryFmt >> autoDefPPrinter++instance HataClass AST_DotLabel where+ haskellDataInterface = interface "DotLabelExpression" $ do+ autoDefEquality >> autoDefPPrinter >> autoDefToStruct >> autoDefFromStruct++----------------------------------------------------------------------------------------------------++instance UpdateIterable (H.HashMap Object Object) (Maybe Object) where+ updateForLoop hm f = fmap (H.fromList . concat) $ execForM (H.assocs hm) $ \ (ix, o) -> do+ hash128 <- getObjectHash128+ p <- f (Just $ obj $ Pair (H.indexKey ix, o))+ case p of+ Nothing -> return []+ Just p -> do+ let badtype = throwBadTypeError "cannot update hash map in iterator with item" p []+ maybe badtype (\ (Pair(a,b)) -> return [(H.hashNewIndex hash128 a, b)]) (fromObj p)++instance ObjectClass (H.HashMap Object Object) where { obj=new; fromObj=objFromHata; }++-- | The hash function for 'Object's relies on the binary serialization of the object, which+-- requires access to the 'MethodTable' of the current 'ExecUnit'. Therefore the hash function must+-- be derived from the 'Exec' monad, the 'Object' data type unfortunately cannot simply derive the+-- 'Data.HashMap.Int128Hashable' class.+getObjectHash128 :: Exec (Object -> H.Hash128)+getObjectHash128 = gets globalMethodTable >>= \mt -> return (H.deriveHash128_DaoBinary mt)++instance HataClass (H.HashMap Object Object) where+ haskellDataInterface = interface "HashMap" $ do+ autoDefEquality >> autoDefOrdering >> autoDefBinaryFmt >> autoDefPPrinter+ autoDefSizeable >> autoDefUpdateIterable+ let un _ a b = xmaybe (fromObj b) >>= \b -> return $ new $ H.union b a+ defInfixOp ADD un+ defInfixOp ORB un+ defInfixOp ANDB $ \ _ a b -> xmaybe (fromObj b) >>= \b -> return $ new $ (H.intersection b a :: H.HashMap Object Object)+ defInfixOp SUB $ \ _ a b -> xmaybe (fromObj b) >>= \b -> return $ new $ (H.difference b a :: H.HashMap Object Object)+ let initItems hmap ox = do+ hash128 <- getObjectHash128+ let f hmap o = case o of+ InitSingle o -> do+ let idx = H.hashNewIndex hash128 o+ return $ H.hashInsert idx o hmap+ InitAssign i op o -> do+ i <- derefObject i+ let idx = H.hashNewIndex hash128 i+ let ref = Just (RefObject i NullRef) <|> fromObj i+ o <- evalUpdateOp ref op o (H.hashLookup idx hmap)+ return $ case o of+ Nothing -> H.hashDelete idx hmap+ Just o -> H.hashInsert idx o hmap+ foldM f hmap ox+ defInitializer hashMapFromList initItems+ let single_index :: Monad m => (Object -> m a) -> [Object] -> m a+ single_index f ix = case ix of+ [i] -> f i+ [] -> fail "no index value provided in subscript to HashMap data type"+ _ -> fail "HashMap is a one-dimensional data type, indexed with multi-dimesional subscript"+ defIndexer $ \hm -> single_index $ \i -> do+ hash128 <- getObjectHash128+ i <- derefObject i+ xmaybe (H.hashLookup (H.hashNewIndex hash128 i) hm)+ defIndexUpdater $ \ix upd -> flip single_index ix $ \i -> do+ hash128 <- focusLiftExec getObjectHash128+ i <- focusLiftExec $ derefObject i+ i <- pure (H.hashNewIndex hash128 i)+ hm <- get+ (result, (changed, o)) <- withInnerLens (H.hashLookup i hm) upd+ when changed (put $ H.hashAlter (const o) i hm)+ return result++hashMapFromList :: [Object] -> Exec (H.HashMap Object Object)+hashMapFromList ox = do+ hash128 <- getObjectHash128+ let fromDict = fmap (\ (i, o) -> (H.hashNewIndex hash128 (obj i), o)) . M.assocs+ f (a, o) = case o of+ OList ox -> case mapM (\ (i, o) -> maybe (Left (i, o)) Right (fromObj o)) (zip [0..] ox) of+ Left (i, t) -> throwBadTypeError "list item in hash map initializer" t $+ [(errInInitzr, OInt a), (ustr "listIndex", OInt i)]+ Right ox -> return $ fmap (\ (Pair(a,b)) -> (H.hashNewIndex hash128 a, b)) ox+ OTree (Struct{fieldMap=ox}) -> return $ fromDict ox+ ODict ox -> return $ fromDict ox+ o -> do+ let badtype = throwBadTypeError "hash map initializer from list" o [(errInInitzr, OInt a)]+ maybe badtype return $ msum $+ [ fromObj o >>= \ (Pair(a,b)) -> Just [(H.hashNewIndex hash128 a, b)]+ , H.assocs <$> fromObj o+ ]+ H.fromList . concat <$> mapM f (zip [1..] ox)++builtin_HashMap :: DaoFunc ()+builtin_HashMap =+ daoFunc{ daoForeignFunc = \ () -> fmap (flip (,) () . Just . obj) . hashMapFromList }++----------------------------------------------------------------------------------------------------++-- | When defining the function used by the Dao interpreter to construct your object from an+-- initializer statement, a statement which looks like the following code:+-- > MyObj(0, a) { item = 1, item += 3, x, y };+-- you will need to receive the list of items expressed in curly-brackets, which could be an+-- assignment operation, or a single object value expression. This data type provides the necessary+-- data to your initializer function.+data InitItem+ = InitSingle Object+ | InitAssign Object UpdateOp Object+ deriving (Eq, Ord, Typeable)++----------------------------------------------------------------------------------------------------++-- | This is all of the functions used by the "Dao.Evaluator" when manipulating objects in a Dao+-- program. Behavior of objects when they are used in "for" statements or "with" statements, or when+-- they are dereferenced using the "@" operator, or when they are used in equations are all defined+-- here.+-- +-- So this table is the reason you instantiate 'HataClass'.+-- +-- @obj@ specifies the container type that will wrap-up data of type @typ@. @obj@ is the type used+-- throughout the runtime system to symbolize the basic unit of information operated on by+-- computations.+-- +-- @typ@ specifies the type that you want to wrap-up into an @obj@ constructor. When you want to,+-- for example, check for equality between object of type @typ@, you can define a function for+-- 'objEquality'. All of the other polymorphic types are bound to the @typ@ types by the functional+-- dependencies mechanism of the Haskell language.+-- +-- @exec@ specifies a monad in which to evaluate functions which may need to cause side-effects.+-- This should usually be a 'Control.Monad.Monad'ic type like @IO@ or 'Exec'.+data Interface typ =+ Interface+ { objInterfaceName :: Name+ , objHaskellType :: TypeRep -- ^ this type is deduced from the initial value provided to the 'interface'.+ , objCastFrom :: Maybe (Object -> typ) -- ^ defined by 'defCastFrom'+ , objEquality :: Maybe (typ -> typ -> Bool) -- ^ defined by 'defEquality'+ , objOrdering :: Maybe (typ -> typ -> Ordering) -- ^ defined by 'defOrdering'+ , objBinaryFormat :: Maybe (typ -> Put, Get typ) -- ^ defined by 'defBinaryFmt'+ , objNullTest :: Maybe (typ -> Bool) -- ^ defined by 'defNullTest'+ , objPPrinter :: Maybe (typ -> PPrint) -- ^ defined by 'defPPrinter'+ , objReadIterable :: Maybe (typ -> (Object -> Exec ()) -> Exec ()) -- ^ defined by 'defReadIterator'+ , objUpdateIterable :: Maybe (typ -> (Maybe Object -> Exec (Maybe Object)) -> Exec typ) -- ^ defined by 'defUpdateIterator'+ , objIndexer :: Maybe (typ -> [Object] -> Exec Object) -- ^ defined by 'defIndexer'+ , objIndexUpdater :: Maybe (ObjectUpdate typ [Object]) -- ^ defined by 'defIndexUpdater'+ , objSizer :: Maybe (typ -> Exec Object) -- ^ defined by 'defSizer'+ , objToStruct :: Maybe (ToDaoStruct typ ()) -- ^ defined by 'defStructFormat'+ , objFromStruct :: Maybe (FromDaoStruct typ) -- ^ defined by 'defStructFormat'+ , objInitializer :: Maybe ([Object] -> Exec typ, typ -> [InitItem] -> Exec typ) -- ^ defined by 'defDictInit'+ , objTraverse :: Maybe (ObjectTraverse typ [Object]) -- ^ defined by 'defTraverse'+ , objInfixOpTable :: Maybe (Array InfixOp (Maybe (InfixOp -> typ -> Object -> XPure Object))) -- ^ defined by 'defInfixOp'+ , objArithPfxOpTable :: Maybe (Array ArithPfxOp (Maybe (ArithPfxOp -> typ -> XPure Object))) -- ^ defined by 'defPrefixOp'+ , objCallable :: Maybe (typ -> Exec [CallableCode]) -- ^ defined by 'defCallable'+ , objDereferencer :: Maybe (typ -> Exec (Maybe Object))+ , objMethodTable :: M.Map Name (DaoFunc typ)+ }+ deriving Typeable++instance Eq (Interface typ) where { a==b = objHaskellType a == objHaskellType b }++instance Ord (Interface typ) where { compare a b = compare (objHaskellType a) (objHaskellType b) }++-- | This function works a bit like 'Data.Functor.fmap', but maps an 'Interface' from one type+-- to another. This requires two functions: one that can cast from the given type to the adapted+-- type (to convert outputs of functions), and one that can cast back from the adapted type to the+-- original type (to convert inputs of functions). Each coversion function takes a string as it's+-- first parameter, this is a string containing the name of the function that is currently making+-- use of the conversion operation. Should you need to use 'Prelude.error' or 'newError', this+-- string will allow you to throw more informative error messages. WARNING: this function leaves+-- 'objHaskellType' unchanged because the original type value should usually be preserved.+interfaceAdapter+ :: (Typeable typ_a, Typeable typ_b)+ => (String -> typ_a -> typ_b)+ -> (String -> typ_b -> typ_a)+ -> Interface typ_a+ -> Interface typ_b+interfaceAdapter a2b b2a ifc = + ifc+ { objCastFrom = let n="objCastFrom" in fmap (fmap (a2b n)) (objCastFrom ifc)+ , objEquality = let n="objEquality" in fmap (\eq a b -> eq (b2a n a) (b2a n b)) (objEquality ifc)+ , objOrdering = let n="objOrdering" in fmap (\ord a b -> ord (b2a n a) (b2a n b)) (objOrdering ifc)+ , objBinaryFormat = let n="objBinaryFormat" in fmap (\ (toBin , fromBin) -> (toBin . b2a n, fmap (a2b n) fromBin)) (objBinaryFormat ifc)+ , objNullTest = let n="objNullTest" in fmap (\null b -> null (b2a n b)) (objNullTest ifc)+ , objPPrinter = let n="objPPrinter" in fmap (\eval -> eval . b2a n) (objPPrinter ifc)+ , objReadIterable = let n="objReadIterable" in fmap (\for t -> for (b2a n t)) (objReadIterable ifc)+ , objUpdateIterable = let n="objUpdateIterable" in fmap (\for t -> fmap (a2b n) . for (b2a n t)) (objUpdateIterable ifc)+ , objIndexer = let n="objIndexer" in fmap (\f i -> f (b2a n i)) (objIndexer ifc)+ , objIndexUpdater = let n="objIndexUpdater" in fmap (\upd i f -> convertFocus (a2b n) (b2a n) (upd i f)) (objIndexUpdater ifc)+ , objSizer = let n="objSizer" in fmap (\f o -> f (b2a n o)) (objSizer ifc)+ , objToStruct = let n="objToStruct" in fmap (fmapHaskDataToStruct (a2b n) (b2a n)) (objToStruct ifc)+ , objFromStruct = let n="objFromStruct" in fmap (fmap (a2b n)) (objFromStruct ifc)+ , objInitializer = let n="objInitializer" in fmap (\ (init, eval) -> (\ox -> fmap (a2b n) (init ox), \typ ox -> fmap (a2b n) (eval (b2a n typ) ox))) (objInitializer ifc)+ , objTraverse = let n="objTraverse" in fmap (\focus f -> convertFocus (a2b n) (b2a n) (focus f)) (objTraverse ifc)+ , objInfixOpTable = let n="objInfixOpTable" in fmap (fmap (fmap (\infx op b -> infx op (b2a n b)))) (objInfixOpTable ifc)+ , objArithPfxOpTable = let n="objPrefixOpTable" in fmap (fmap (fmap (\prfx op b -> prfx op (b2a n b)))) (objArithPfxOpTable ifc)+ , objMethodTable = let n="objMethodTable" in fmap (\func -> func{ daoForeignFunc = \t -> fmap (fmap (a2b n)) . daoForeignFunc func (b2a n t) }) (objMethodTable ifc)+ , objCallable = let n="objCallable" in fmap (\eval -> eval . b2a n) (objCallable ifc)+ , objDereferencer = let n="objDerferencer" in fmap (\eval -> eval . b2a n) (objDereferencer ifc)+ }++interfaceToDynamic :: Typeable typ => Interface typ -> Interface Dynamic+interfaceToDynamic oi = interfaceAdapter (\ _ -> toDyn) (from oi) oi where+ from :: Typeable typ => Interface typ -> String -> Dynamic -> typ+ from oi msg dyn = fromDyn dyn (dynErr oi msg dyn)+ dynErr :: Typeable typ => Interface typ -> String -> Dynamic -> typ+ dynErr oi msg dyn = error $ concat $+ [ "The '", msg+ , "' function defined for objects of type ", show (objHaskellType oi)+ , " was evaluated on an object of type ", show (dynTypeRep dyn)+ ]++-- Used to construct an 'Interface' in a "Control.Monad.State"-ful way. Instantiates+-- 'Data.Monoid.Monoid' to provide 'Data.Monoid.mempty' an allows multiple inheritence by use of the+-- 'Data.Monoid.mappend' function in the same way as+data HDIfcBuilder typ =+ HDIfcBuilder+ { objIfcHaskellType :: TypeRep+ , objIfcCastFrom :: Maybe (Object -> typ)+ , objIfcEquality :: Maybe (typ -> typ -> Bool)+ , objIfcOrdering :: Maybe (typ -> typ -> Ordering)+ , objIfcBinaryFormat :: Maybe (typ -> Put, Get typ)+ , objIfcNullTest :: Maybe (typ -> Bool)+ , objIfcPPrinter :: Maybe (typ -> PPrint)+ , objIfcReadIterable :: Maybe (typ -> (Object -> Exec ()) -> Exec ())+ , objIfcUpdateIterable :: Maybe (typ -> (Maybe Object -> Exec (Maybe Object)) -> Exec typ)+ , objIfcIndexer :: Maybe (typ -> [Object] -> Exec Object)+ , objIfcIndexUpdater :: Maybe (ObjectUpdate typ [Object])+ , objIfcSizer :: Maybe (typ -> Exec Object)+ , objIfcToStruct :: Maybe (ToDaoStruct typ ())+ , objIfcFromStruct :: Maybe (FromDaoStruct typ)+ , objIfcInitializer :: Maybe ([Object] -> Exec typ, typ -> [InitItem] -> Exec typ)+ , objIfcTraverse :: Maybe (ObjectTraverse typ [Object])+ , objIfcInfixOpTable :: [(InfixOp , InfixOp -> typ -> Object -> XPure Object)]+ , objIfcPrefixOpTable :: [(ArithPfxOp, ArithPfxOp -> typ -> XPure Object)]+ , objIfcMethodTable :: M.Map Name (DaoFunc typ)+ , objIfcCallable :: Maybe (typ -> Exec [CallableCode])+ , objIfcDerefer :: Maybe (typ -> Exec (Maybe Object))+ }++initHDIfcBuilder :: TypeRep -> HDIfcBuilder typ+initHDIfcBuilder typ =+ HDIfcBuilder+ { objIfcHaskellType = typ+ , objIfcCastFrom = Nothing+ , objIfcEquality = Nothing+ , objIfcOrdering = Nothing+ , objIfcBinaryFormat = Nothing+ , objIfcNullTest = Nothing+ , objIfcPPrinter = Nothing+ , objIfcReadIterable = Nothing+ , objIfcUpdateIterable = Nothing+ , objIfcIndexer = Nothing+ , objIfcIndexUpdater = Nothing+ , objIfcSizer = Nothing+ , objIfcToStruct = Nothing+ , objIfcFromStruct = Nothing+ , objIfcInitializer = Nothing+ , objIfcTraverse = Nothing+ , objIfcInfixOpTable = []+ , objIfcPrefixOpTable = []+ , objIfcMethodTable = mempty+ , objIfcCallable = Nothing+ , objIfcDerefer = Nothing+ }++-- | A handy monadic interface for defining an 'Interface' using nice, clean procedural+-- syntax.+type DaoClassDef typ = DaoClassDefM typ ()+newtype DaoClassDefM typ a = DaoClassDefM { daoClassDefState :: State (HDIfcBuilder typ) a }+instance Typeable typ => Functor (DaoClassDefM typ) where+ fmap f (DaoClassDefM m) = DaoClassDefM (fmap f m)+instance Typeable typ => Monad (DaoClassDefM typ) where+ return = DaoClassDefM . return+ (DaoClassDefM m) >>= f = DaoClassDefM (m >>= daoClassDefState . f)+instance Typeable typ => Applicative (DaoClassDefM typ) where { pure=return; (<*>)=ap; }++_updHDIfcBuilder :: Typeable typ => (HDIfcBuilder typ -> HDIfcBuilder typ) -> DaoClassDefM typ ()+_updHDIfcBuilder = DaoClassDefM . modify++-- | The callback function defined here is used when objects of your @typ@ can be constructed from+-- some other 'Object'. This function is used to convert an 'Object' of another types to an data+-- type of your @typ@ when it is necessary to do so (for example, evaluating the @==@ or @!=@+-- operator).+defCastFrom :: Typeable typ => (Object -> typ) -> DaoClassDefM typ ()+defCastFrom fn = _updHDIfcBuilder(\st->st{objIfcCastFrom=Just fn})++-- | The callback function defined here is used where objects of your @typ@ might be compared to+-- other objects using the @==@ and @!=@ operators in Dao programs. However using this is slightly+-- different than simply overriding the @==@ or @!=@ operators. Defining an equality reliation with+-- this function also allows Haskell language programs to compare your object to other objects+-- without unwrapping them from the 'Object' wrapper.+--+-- This function automatically define an equality operation over your @typ@ using the+-- instantiation of 'Prelude.Eq' and the function you have provided to the 'defCastFrom' function.+-- The 'defCastFrom' function is used to cast 'Object's to a value of your @typ@, and then the+-- @Prelude.==@ function is evaluated. If you eventually never define a type casting funcion using+-- 'defCastFrom', this function will fail, but it will fail lazily and at runtime, perhaps when you+-- least expect it, so be sure to define 'defCastFrom' at some point.+autoDefEquality :: (Typeable typ, Eq typ) => DaoClassDefM typ ()+autoDefEquality = defEquality (==)++-- | The callback function defined here is used where objects of your @typ@ might be compared to+-- other objects using the @==@ and @!=@ operators in Dao programs. However using this is slightly+-- different than simply overriding the @==@ or @!=@ operators. Defining an equality relation with+-- this function also allows Haskell language programs to compare your object to other objects+-- without unwrapping them from the 'Object' wrapper.+--+-- This function differs from 'autoDefEquality' because you must provide a customized equality+-- relation for your @typ@, if the 'autoDefEquality' and 'defCastFrom' functions are to be avoided+-- for some reason.+defEquality :: (Typeable typ, Eq typ) => (typ -> typ -> Bool) -> DaoClassDefM typ ()+defEquality fn = _updHDIfcBuilder(\st->st{objIfcEquality=Just fn})++-- | The callback function defined here is used where objects of your @typ@ might be compared to+-- other objects using the @<@, @>@, @<=@, and @>=@ operators in Dao programs. However using this is+-- slightly different than simply overriding the @<@, @>@, @<=@, or @>=@ operators. Defining an+-- equality relation with this function also allows Haskell language programs to compare your obejct+-- to other objects without unwrapping them from the 'Object' wrapper.+-- +-- Automatically define an ordering for your @typ@ using the instantiation of+-- 'Prelude.Eq' and the function you have provided to the 'defCastFrom' function. The 'defCastFrom'+-- function is used to cast 'Object's to a value of your @typ@, and then the @Prelude.==@ function+-- is evaluated. If you eventually never define a type casting funcion using 'defCastFrom', this+-- function will fail, but it will fail lazily and at runtime, perhaps when you least expect it, so+-- be sure to define 'defCastFrom' at some point.+autoDefOrdering :: (Typeable typ, Ord typ) => DaoClassDefM typ ()+autoDefOrdering = defOrdering compare++-- | The callback function defined here is used where objects of your @typ@ might be compared to+-- other objects using the @<@, @>@, @<=@, and @>=@ operators in Dao programs. However using this is+-- slightly different than simply overriding the @<@, @>@, @<=@, or @>=@ operators. Defining an+-- equality relation with this function also allows Haskell language programs to compare your obejct+-- to other objects without unwrapping them from the 'Object' wrapper.+-- +-- Define a customized ordering for your @typ@, if the 'autoDefEquality' and 'defCastFrom'+-- functions are to be avoided for some reason.+defOrdering :: (Typeable typ) => (typ -> typ -> Ordering) -> DaoClassDefM typ ()+defOrdering fn = _updHDIfcBuilder(\st->st{objIfcOrdering=Just fn})++-- | The callback function defined here is used if an object of your @typ@ should ever need to be+-- stored into a binary file in persistent storage (like your filesystem) or sent across a channel+-- (like a UNIX pipe or a socket).+-- +-- It automatically define the binary encoder and decoder using the 'Data.Binary.Binary' class+-- instantiation for this @typ@.+autoDefBinaryFmt :: (Typeable typ, B.Binary typ MethodTable) => DaoClassDefM typ ()+autoDefBinaryFmt = defBinaryFmt B.put B.get++-- | This function is used if an object of your @typ@ should ever need to be stored into a binary+-- file in persistent storage (like your filesystem) or sent across a channel (like a UNIX pipe or a+-- socket).+-- +-- If you have binary coding and decoding methods for your @typ@ but for some silly reason not+-- instantiated your @typ@ into the 'Data.Binary.Binary' class, your @typ@ can still be used as a+-- binary formatted object by the Dao system if you define the encoder and decoder using this+-- function. However, it would be better if you instantiated 'Data.Binary.Binary' and used+-- 'autoDefBinaryFmt' instead.+defBinaryFmt :: (Typeable typ) => (typ -> Put) -> Get typ -> DaoClassDefM typ ()+defBinaryFmt put get = _updHDIfcBuilder(\st->st{objIfcBinaryFormat=Just(put,get)})++autoDefNullTest :: (Typeable typ, HasNullValue typ) => DaoClassDefM typ ()+autoDefNullTest = defNullTest testNull++-- | The callback function defined here is used if an object of your @typ@ is ever used in an @if@+-- or @while@ statement in a Dao program. This function will return @Prelude.True@ if the object is+-- of a null value, which will cause the @if@ or @while@ test to fail and execution of the Dao+-- program will branch accordingly. There is no default method for this function so it must be+-- defined by this function, otherwise your object cannot be tested by @if@ or @while@ statements.+defNullTest :: Typeable typ => (typ -> Bool) -> DaoClassDefM typ ()+defNullTest fn = _updHDIfcBuilder(\st->st{objIfcNullTest=Just fn})++-- | The callback function to be called when the "print" built-in function is used.+defPPrinter :: Typeable typ => (typ -> PPrint) -> DaoClassDefM typ ()+defPPrinter fn = _updHDIfcBuilder(\st->st{objIfcPPrinter=Just fn})++-- | The callback function to be called when the "print" built-in function is used.+autoDefPPrinter :: (Typeable typ, PPrintable typ) => DaoClassDefM typ ()+autoDefPPrinter = defPPrinter pPrint++-- | The callback function defined here is used if an object of your @typ@ is ever used in a @for@+-- statement in a Dao program. However it is much better to instantiate your @typ@ into the+-- 'ReadIterable' class and use 'autoDefIterator' instead. If 'defUpdateIterator' is also defined,+-- the function defined here will never be used.+--+-- /NOTE:/ When defining iterators, it is important to use 'execForM' or 'execForM_' to properly+-- handle "break" and "catch" statements.+defReadIterable :: Typeable typ => (typ -> (Object -> Exec ()) -> Exec ()) -> DaoClassDefM typ ()+defReadIterable iter = _updHDIfcBuilder $ \st -> st{ objIfcReadIterable=Just iter }++-- | Define 'defReadIterable' automatically using the instance of @typ@ in the 'ReadIterable' class.+--+-- /NOTE:/ When defining iterators, it is important to use 'execForM' or 'execForM_' to properly+-- handle "break" and "catch" statements.+autoDefReadIterable :: (Typeable typ, ReadIterable typ Object) => DaoClassDefM typ ()+autoDefReadIterable = defReadIterable readForLoop++-- | The callback function defined here is used if an object of your @typ@ is ever used in a @for@+-- statement in a Dao program. However it is much better to instantiate your @typ@ into the+-- 'UpdateIterable' class and use 'autoDefIterator' instead. If 'defReadIterator' is also defined,+-- the read iterator is always ignored in favor of this function.+--+-- /NOTE:/ When defining iterators, it is important to use 'execForM' or 'execForM_' to properly+-- handle "break" and "catch" statements.+defUpdateIterable :: Typeable typ => (typ -> (Maybe Object -> Exec (Maybe Object)) -> Exec typ) -> DaoClassDefM typ ()+defUpdateIterable iter = _updHDIfcBuilder(\st->st{objIfcUpdateIterable=Just iter})++-- | Define 'defUpdateIterable' automatically using the instance of @typ@ in the 'ReadIterable'+-- class.+--+-- /NOTE:/ When defining iterators, it is important to use 'execForM' or 'execForM_' to properly+-- handle "break" and "catch" statements.+autoDefUpdateIterable :: (Typeable typ, UpdateIterable typ (Maybe Object)) => DaoClassDefM typ ()+autoDefUpdateIterable = defUpdateIterable updateForLoop++-- | The callback function defined here is used at any point in a Dao program where an expression+-- containing your object typ is subscripted with square brackets, for example in the statement:+-- @x = t[0][A][B];@ The object passed to your callback function is the object containing the+-- subscript value. So in the above example, if the local variable @t@ is a value of+-- your @typ@, this callback function will be evaluated three times:+-- 1. with the given 'Object' parameter being @('OInt' 0)@ and the @typ@ parameter as the value+-- stored in the local variable @y@.+-- 2. once with the 'Object' parameter being the result of dereferencing the local varaible @A@ and+-- the @typ@ parameter as the value stored in the local variable @y@.+-- 3. once the given 'Object' parameter being the result of dereferencing the local variable @B@ and+-- the @typ@ parameter as the value stored in the local variable @y@.+-- +-- Statements like this:+-- > ... = a[0,1,2]+-- access a single multi-dimensional index, in this case 3-dimensions with the tuple [0,1,2].+-- > ... = a[0][1][2]+-- accesses a sequence of single-dimensional elements, each element being accessed by the next+-- snigle-dimensional index in the sequence. Although this is one method of programming+-- multi-dimensional data types, it is evaluated differently than an index expressed as a tuple.+defIndexer :: Typeable typ => (typ -> [Object] -> Exec Object) -> DaoClassDefM typ ()+defIndexer fn = _updHDIfcBuilder(\st->st{objIfcIndexer=Just fn})++-- | The callback function defined here is used at any point in a Dao program where an expression+-- containing your object typ is subscripted with square brackets on the left-hand side of an+-- assignment expression:+-- @x[0][A][B] = t;@+-- This function must take the original object of your @typ@ and return the updated object along+-- with the value used to updated it. The object passed to your callback function is the object+-- containing the subscript value. So in the above example, if the local variables @x@ is a value of+-- your @typ@, this callback function will be evaluated three times:+-- 1. with the given 'Object' parameter being @('OInt' 0)@ and the @typ@ parameter as the value+-- stored in the local variable @y@.+-- 2. once with the 'Object' parameter being the result of dereferencing the local varaible @A@ and+-- the @typ@ parameter as the value stored in the local variable @y@.+-- 3. once the given 'Object' parameter being the result of dereferencing the local variable @B@ and+-- the @typ@ parameter as the value stored in the local variable @y@.+-- +-- Statements like this:+-- > a[0,1,2] = ...+-- access a single multi-dimensional index, in this case 3-dimensions with the tuple [0,1,2].+-- > a[0][1][2] = ...+-- accesses a sequence of single-dimensional elements, each element being accessed by the next+-- snigle-dimensional index in the sequence. Although this is one method of programming+-- multi-dimensional data types, it is evaluated differently than an index expressed as a tuple.+defIndexUpdater :: Typeable typ => ObjectUpdate typ [Object] -> DaoClassDefM typ ()+defIndexUpdater fn = _updHDIfcBuilder(\st->st{ objIfcIndexUpdater=Just fn })++-- | Define a function used by the built-in "size()" function to return an value indicating the size+-- of your @typ@ object.+defSizer :: Typeable typ => (typ -> Exec Object) -> DaoClassDefM typ ()+defSizer fn = _updHDIfcBuilder(\st->st{objIfcSizer=Just fn})++autoDefSizeable :: (Typeable typ, Sizeable typ) => DaoClassDefM typ ()+autoDefSizeable = defSizer getSizeOf++-- | Use your data type's instantiation of 'ToDaoStructClass' to call 'defToStruct'.+autoDefToStruct :: forall typ . (Typeable typ, ToDaoStructClass typ) => DaoClassDefM typ ()+autoDefToStruct = defToStruct toDaoStruct++-- | When a label referencing your object has a field record accessed, for example:+-- > c = a.b;+-- if your object is referenced by @a@ and the script expression wants to access a record called @b@+-- from within it, then function defined here will be used.+defToStruct :: Typeable typ => ToDaoStruct typ () -> DaoClassDefM typ ()+defToStruct encode = _updHDIfcBuilder (\st -> st{ objIfcToStruct=Just encode })++-- | When a label referencing your object has a field record updated, for example:+-- > a.b = c;+-- if your object is referenced by @a@ and the script expression wants to update a record called @b@+-- within it by assigning it the value referenced by @c@, then the function defined here will be+-- used.+autoDefFromStruct :: (Typeable typ, FromDaoStructClass typ) => DaoClassDefM typ ()+autoDefFromStruct = defFromStruct fromDaoStruct++-- | If for some reason you need to define a tree encoder and decoder for the 'Interface' of your+-- @typ@ without instnatiating 'ToDaoStructClass' or 'FromDaoStructClass', use+-- this function to define the tree encoder an decoder directly+defFromStruct :: Typeable typ => FromDaoStruct typ -> DaoClassDefM typ ()+defFromStruct decode = _updHDIfcBuilder (\st -> st{ objIfcFromStruct=Just decode })++-- | The callback defined here is used when a Dao program makes use of the static initialization+-- syntax of the Dao programming language, which are expression of this form:+-- > a = MyType { paramA=initA, paramB=initB, .... };+-- > a = MyType(param1, param2, ...., paramN) { paramA=initA, paramB=initB, .... };+-- When the interpreter sees this form of expression, it looks up the 'Interface' for your+-- @typ@ and checks if a callback has been defined by 'defDictInit'. If so, then the callback is+-- evaluated with a list of object values passed as the first parameter which contain the object+-- values written in the parentheses, and a list of 'InitItem's as the second parameter containing+-- the contents of the curly-brackets.+defInitializer :: Typeable typ => ([Object] -> Exec typ) -> (typ -> [InitItem] -> Exec typ) -> DaoClassDefM typ ()+defInitializer fa fb = _updHDIfcBuilder(\st->st{objIfcInitializer=Just (fa, fb)})++-- | Data structures in the Dao programming language can be traversed if you provide a function that+-- can update every 'Object' contained wihtin the data structure.+defTraverse :: Typeable typ => (([Object] -> Object -> ObjectFocus [([Object], Object)] ()) -> ObjectFocus typ ()) -> DaoClassDefM typ ()+defTraverse f = _updHDIfcBuilder(\st->st{objIfcTraverse=Just f})++-- | Define the 'defTraverse' function using the instance of 'objectFMap' for your @typ@ in the+-- @('ObjectFunctor' ['Object'] typ)@ class.+autoDefTraverse :: (Typeable typ, ObjectFunctor typ [Object]) => DaoClassDefM typ ()+autoDefTraverse = defTraverse objectFMap++-- | Overload infix operators in the Dao programming language, for example @+@, @*@, or @<<@.+-- +-- Like with C++, the operator prescedence and associativity is permanently defined by the parser+-- and cannot be changed by the overloading mechanism. You can only change how the operator behaves+-- based on the type of it's left and right hand parameters.+--+-- If you define two callbacks for the same 'UpdateOp', this will result in a runtime error,+-- hopefully the error will occur during the Dao runtime's object loading phase, and not while+-- actually executing a program.+defInfixOp :: Typeable typ => InfixOp -> (InfixOp -> typ -> Object -> XPure Object) -> DaoClassDefM typ ()+defInfixOp op fn = _updHDIfcBuilder $ \st -> st{objIfcInfixOpTable = objIfcInfixOpTable st ++ [(op, fn)] }++-- | Overload prefix operators in the Dao programming language, for example @!@, @~@, @-@, and @+@.+-- +-- Like with C++, the operator prescedence and associativity is permanently defined by the parser+-- and cannot be changed by the overloading mechanism. You can only change how the operator behaves+-- based on the type of it's left and right hand parameters.+-- +-- If you define two callbacks for the same 'UpdateOp', this will result in a runtime error,+-- hopefully the error will occur during the Dao runtime's object loading phase, and not while+-- actually executing a program.+defPrefixOp :: Typeable typ => ArithPfxOp -> (ArithPfxOp -> typ -> XPure Object) -> DaoClassDefM typ ()+defPrefixOp op fn = _updHDIfcBuilder $ \st -> st{objIfcPrefixOpTable = objIfcPrefixOpTable st ++ [(op, fn)] }++defCallable :: Typeable typ => (typ -> Exec [CallableCode]) -> DaoClassDefM typ ()+defCallable fn = _updHDIfcBuilder (\st -> st{objIfcCallable=Just fn})++defDeref :: Typeable typ => (typ -> Exec (Maybe Object)) -> DaoClassDefM typ ()+defDeref fn = _updHDIfcBuilder (\st -> st{objIfcDerefer=Just fn})++defMethod :: (UStrType name, Typeable typ) => name -> DaoFunc typ -> DaoClassDefM typ ()+defMethod inname infn = do+ let name = fromUStr $ toUStr inname+ let fn = infn{ daoFuncName=name }+ let dupname st _ = error $ concat $ + [ "Internal error: duplicate method name \"", show name+ , "\" for data type ", show (objIfcHaskellType st)+ ] + _updHDIfcBuilder $ \st ->+ st{ objIfcMethodTable = M.alter (maybe (Just fn) (dupname st)) name $ objIfcMethodTable st }++-- | Like 'detMethod' but creates a function that takes no parameters.+defMethod0 :: (UStrType name, Typeable this) => name -> (this -> Exec (Maybe Object, this)) -> DaoClassDefM this ()+defMethod0 name f = defMethod name $+ daoFunc+ { funcAutoDerefParams = False+ , daoForeignFunc = \this ox -> case ox of+ [] -> f this+ ox -> throwArityError "" 0 ox [(errInFunc, obj $ reference UNQUAL (fromUStr $ toUStr name))]+ }++-- | Rocket. Yeah. Sail away with you.+defLeppard :: Typeable typ => rocket -> yeah -> DaoClassDefM typ ()+defLeppard _ _ = return ()++-- | This is the Dao 'Object' interface to the Haskell language. Every function in this data type+-- allows you to customize the behavior of the Dao evaluator for a particular Haskell data type+-- @typ@. In order for your type to be useful, it must be possible to pass your data type to the+-- 'OHaskell' constructor, which requires a data type of 'Data.Dynamic.Dynamic', which means your+-- @typ@ must derive a class instance for 'Data.Typeable.Typeable'. The first parameter of type+-- @typ@ is not used except to retrieve it's 'Data.Typeable.TypeRep' using the+-- 'Data.Typealble.typeOf' function, it is safe to pass any data constructor with all of it's fields+-- 'Prelude.undefined', just the constructor itself must not be 'Prelude.undefined'.+-- +-- The @'DaoClassDefM'@ parameter you pass to this function is a monadic function so you can simply+-- declare the functionality you would like to include in this object one line at a time using+-- the procedural coding style. Each line in the "procedure" will be one of the @def*@ functions,+-- for example 'autoDefEquality' or 'autoDefOrdering'.+interface :: (UStrType name, Typeable typ) => name -> DaoClassDefM typ ig -> Interface typ+interface nm defIfc = let name = toUStr nm in case maybeFromUStr name of+ Nothing -> error $+ "Failed to install built-in data type interface, invalid type name provided: "++uchars nm+ Just name ->+ Interface+ { objHaskellType = typ+ , objInterfaceName = name+ , objCastFrom = objIfcCastFrom ifc+ , objEquality = objIfcEquality ifc+ , objOrdering = objIfcOrdering ifc+ , objBinaryFormat = objIfcBinaryFormat ifc+ , objNullTest = objIfcNullTest ifc+ , objPPrinter = objIfcPPrinter ifc+ , objReadIterable = objIfcReadIterable ifc+ , objUpdateIterable = objIfcUpdateIterable ifc+ , objIndexer = objIfcIndexer ifc+ , objIndexUpdater = objIfcIndexUpdater ifc+ , objSizer = objIfcSizer ifc+ , objToStruct = objIfcToStruct ifc+ , objFromStruct = objIfcFromStruct ifc+ , objInitializer = objIfcInitializer ifc+ , objTraverse = objIfcTraverse ifc+ , objCallable = objIfcCallable ifc+ , objDereferencer = objIfcDerefer ifc+ , objInfixOpTable = mkArray "defInfixOp" $ objIfcInfixOpTable ifc+ , objArithPfxOpTable = mkArray "defPrefixOp" $ objIfcPrefixOpTable ifc+ , objMethodTable = objIfcMethodTable ifc+ }+ where+ mktyp :: Typeable typ => DaoClassDefM typ ig -> typ -> TypeRep+ mktyp _ undefd = typeOf undefd+ typ = mktyp defIfc $+ error "'Dao.Interpreter.interface' evaluated 'Data.Typeable.typeOf' on undefined value"+ ifc = execState (daoClassDefState defIfc) (initHDIfcBuilder typ)+ mkArray oiName elems =+ minAccumArray (onlyOnce oiName) Nothing $ map (\ (i, e) -> (i, (i, Just e))) elems+ onlyOnce oiName a (i, b) = case a of+ Nothing -> b+ Just _ -> conflict oiName ("the "++show i++" operator")+ conflict oiName funcName = error $ concat $+ [ "'", oiName+ , "' has conflicting functions for ", funcName+ , " for the 'HataClass' instantiation of the '", show typ+ , "' Haskell data type."+ ]+
+ src/Dao/Interpreter/AST.hs view
@@ -0,0 +1,3135 @@+-- "src/Dao/Interpreter/AST.hs" defines the data types for the Dao+-- programming language abstract syntax tree and related type classes.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++module Dao.Interpreter.AST+ ( Intermediate(toInterm, fromInterm), Canonical(canonicalize),+ Comment(InlineComment, EndlineComment),+ Com(Com, ComBefore, ComAfter, ComAround),+ NamespaceExpr(NamespaceExpr), AST_Namespace(AST_NoNamespace, AST_Namespace),+ setCommentBefore, setCommentAfter, unComment, getComment, + pPrintInterm, putAST, getAST, commentString, pPrintComWith,+ pListOfComsWith, pListOfComs, randComWith, appendComments, com,+ DotNameExpr(DotNameExpr), AST_DotName(AST_DotName), getDotNameAST, undotNameExpr,+ DotLabelExpr(DotLabelExpr), AST_DotLabel(AST_DotLabel), + dotLabelToNameList, dotLabelToRefExpr, refToDotLabelExpr, dotLabelToRefAST, refToDotLabelAST,+ RefSuffixExpr(NullRefExpr, DotRefExpr, SubscriptExpr, FuncCallExpr),+ ReferenceExpr(ReferenceExpr, RefObjectExpr),+ RefPfxOp(REF, DEREF), + RefQualifier(UNQUAL, LOCAL, CONST, STATIC, GLOBAL, GLODOT),+ UpdateOp(UCONST, UADD, USUB, UMULT, UDIV, UMOD, UPOW, UORB, UANDB, UXORB, USHL, USHR), + ArithPfxOp(INVB, NOT, NEGTIV, POSTIV), + InfixOp(+ ADD, SUB, MULT, DIV, MOD, POW, ORB, ANDB, XORB, SHL, SHR, OR, AND,+ EQUL, NEQUL, GTN, LTN, GTEQ, LTEQ, ARROW+ ), infixOpCommutativity,+ allUpdateOpStrs, allPrefixOpChars, allPrefixOpStrs, allInfixOpChars, allInfixOpStrs,+ AST_RefSuffix(AST_RefNull, AST_DotRef, AST_Subscript, AST_FuncCall),+ AST_Reference(AST_Reference, AST_RefObject),+ ObjListExpr(ObjListExpr), AST_ObjList(AST_ObjList),+ OptObjListExpr(OptObjListExpr), AST_OptObjList(AST_OptObjList),+ LiteralExpr(LiteralExpr), AST_Literal(AST_Literal),+ ParenExpr(ParenExpr), AST_Paren(AST_Paren), + AssignExpr(EvalExpr, AssignExpr), AST_Assign(AST_Eval, AST_Assign),+ ObjTestExpr(ObjArithExpr, ObjTestExpr, ObjRuleFuncExpr),+ AST_ObjTest(AST_ObjArith, AST_ObjTest, AST_ObjRuleFunc),+ ArithExpr(ObjectExpr, ArithExpr), AST_Arith(AST_Object, AST_Arith), + RuleFuncExpr(LambdaExpr, FuncExpr, RuleExpr), AST_RuleFunc(AST_Lambda, AST_Func, AST_Rule),+ TyChkExpr(NotTypeChecked, TypeChecked, DisableCheck), fmapCheckedValueExpr,+ tyChkItem, tyChkExpr, tyChkLoc, typChkResult, checkedExpr,+ AST_TyChk(AST_NotChecked, AST_Checked), checkedAST, fmapCheckedValueAST,+ ParamExpr(ParamExpr), AST_Param(AST_NoParams, AST_Param), + ParamListExpr(ParamListExpr), getTypeCheckList,+ AST_ParamList(AST_ParamList), + RuleHeadExpr(RuleStringExpr, RuleHeadExpr), + AST_RuleHeader(AST_NullRules, AST_RuleString, AST_RuleHeader), + CodeBlock(CodeBlock), codeBlock,+ AST_CodeBlock(AST_CodeBlock), getAST_CodeBlock, + RefPrefixExpr(PlainRefExpr, RefPrefixExpr), cleanupRefPrefixExpr,+ AST_RefPrefix(AST_RefPrefix, AST_PlainRef),+ ObjectExpr(+ VoidExpr, ObjLiteralExpr, ObjSingleExpr,+ ArithPfxExpr, InitExpr, StructExpr, MetaEvalExpr+ ),+ AST_Object(+ AST_Void, AST_ObjLiteral, AST_ObjSingle,+ AST_ArithPfx, AST_Init, AST_Struct, AST_MetaEval+ ),+ ScriptExpr(+ IfThenElse, WhileLoop, RuleFuncExpr, EvalObject,+ TryCatch, ForLoop, ContinueExpr, ReturnExpr, WithDoc+ ),+ IfExpr(IfExpr), AST_If(AST_If), + ElseExpr(ElseExpr), AST_Else(AST_Else), + IfElseExpr(IfElseExpr), AST_IfElse(AST_IfElse), + LastElseExpr(LastElseExpr), AST_LastElse(AST_LastElse),+ CatchExpr(CatchExpr), AST_Catch(AST_Catch),+ WhileExpr(WhileExpr), AST_While(AST_While), + AST_Script(+ AST_Comment, AST_IfThenElse, AST_WhileLoop, AST_RuleFunc, AST_EvalObject,+ AST_TryCatch, AST_ForLoop, AST_ContinueExpr, AST_ReturnExpr, AST_WithDoc+ ),+ TopLevelEventType(BeginExprType, EndExprType, ExitExprType), + TopLevelExpr(RequireExpr, ImportExpr, TopScript, EventExpr), isAttribute, + AST_TopLevel(AST_Require, AST_Import, AST_TopScript, AST_Event, AST_TopComment),+ isAST_Attribute, getRequiresAndImports,+ AttributeExpr(AttribDotNameExpr, AttribStringExpr),+ AST_Attribute(AST_AttribDotName, AST_AttribString),+ Program(Program), topLevelExprs,+ AST_SourceCode(AST_SourceCode), sourceModified, sourceFullPath, directives, + )+ where++import qualified Dao.Binary as B+import Dao.String+import Dao.PPrint+import Dao.Random+import Dao.Token++import Data.Array.IArray+import Data.List (intersperse)+import Data.Monoid+import Data.Typeable+import Data.Word++import Control.Applicative+import Control.DeepSeq+import Control.Monad++----------------------------------------------------------------------------------------------------++-- | Elements of the symantic data structures that instantiate 'Executable' and do not instantiate+-- 'Dao.PPrint.PPrintable', 'Dao.Struct.Structured', or any parsers. Elements of the abstract syntax+-- tree (AST) instantiate 'Dao.PPrint.PPrintable', 'Dao.Struct.Structured', and all of the parsers,+-- but are not executable and do not instantiate 'Executable'. This separates concerns pretty well,+-- but leaves us with the problem of having to convert back and forth between these various data+-- types.+--+-- The 'Intermediate' class allows us to declare a one-to-one relationship between AST types and+-- executable types. For example, 'ObjectExpr' is the intermediate representation of+-- 'AST_Object', so our instance for this relationship is @instane 'Intermediate'+-- 'ObjectExpr' 'AST_Object'@.+class Intermediate obj ast | obj -> ast, ast -> obj where+ toInterm :: ast -> [obj]+ fromInterm :: obj -> [ast]++-- | This class is used to classify data types that can be generated at random by+-- 'Dao.Random.HasRandGen' and parsed from source code, which might generate data types with+-- identical functionality, identical pretty-printed forms, identical 'Executable' semantics but may+-- have differences in the recursive structure that the 'Prelude.Eq' class would compute as not+-- identical. The job of the 'canonicalize' function is to eliminate these discrepancies by reducing+-- the data structure to a canonical form.+class Canonical a where { canonicalize :: a -> a }++instance Intermediate Name Name where { toInterm = return; fromInterm = return; }++-- Not for export: here are a bunch of shortcuts to converting the AST to the intermediate data+-- type. Sinec 'toInterm' returns a single item in a list to indicate success and an empty list to+-- indicate failure, all of these items have their evaluated type wrapped in a list type. This is to+-- allow the 'toInterm' instances use the 'Control.Monad.liftM' family of functions.+ti :: Intermediate obj ast => ast -> [obj]+ti = toInterm+uc :: Com a -> [a]+uc = return . unComment+uc0 :: Intermediate obj ast => Com ast -> [obj]+uc0 = toInterm . unComment+um1 :: Intermediate obj ast => Maybe ast -> [Maybe obj]+um1 = maybe [Nothing] (fmap Just . toInterm)++fi :: Intermediate obj ast => obj -> [ast]+fi = fromInterm+nc :: a -> [Com a]+nc = return . Com+nc0 :: Intermediate obj ast => obj -> [Com ast]+nc0 = fmap Com . fromInterm+nm1 :: Intermediate obj ast => Maybe obj -> [Maybe ast]+nm1 = maybe [Nothing] (fmap Just . fromInterm)++-- not for export+no :: RandO Location+no = return LocationUnknown++-- | If there is a type that instantiates 'Intermediate', it can be converted to and from a type+-- that is pretty-printable ('Dao.PPrint.PPrintable').+pPrintInterm :: (Intermediate o ast, PPrintable ast) => o -> PPrint+pPrintInterm = mapM_ pPrint . fromInterm++-- | If there is a type that instantiates 'Intermediate', it can be converted to and from a type+-- that is 'Dao.Binary.GPut'.+putAST :: (Intermediate obj ast, B.Binary obj mtab) => ast -> B.GPut mtab+putAST ast = case toInterm ast of+ [obj] -> B.put obj+ _ -> fail "binary encoder could not convert AST to intermediate expression"++-- | If there is a type that instantiates 'Intermediate', it can be converted to and from a type+-- that is 'Dao.Binary.GGet'.+getAST :: (Intermediate obj ast, B.Binary obj mtab) => B.GGet mtab ast+getAST = B.get >>= \obj -> case fromInterm obj of+ [ast] -> return ast+ _ -> fail "binary decoder constructed object that could not be converted to an AST representation"++----------------------------------------------------------------------------------------------------++-- | Comments in the Dao language are not interpreted, but they are not disgarded either. Dao is+-- intended to manipulate natural language, and itself, so that it can "learn" new semantic+-- structures. Dao scripts can manipulate the syntax tree of other Dao scripts, and so it might be+-- helpful if the syntax tree included comments.+data Comment+ = InlineComment UStr+ | EndlineComment UStr+ deriving (Eq, Ord, Typeable, Show)++commentString :: Comment -> UStr+commentString com = case com of+ InlineComment a -> a+ EndlineComment a -> a++instance NFData Comment where+ rnf (InlineComment a) = seq a ()+ rnf (EndlineComment a) = seq a ()++instance HasNullValue Comment where+ nullValue = EndlineComment nil+ testNull (EndlineComment c) = c==nil+ testNull (InlineComment c) = c==nil++instance PPrintable Comment where+ pPrint com = do+ case com of+ EndlineComment c -> pString ("//"++uchars c) >> pForceNewLine+ InlineComment c -> pGroup True $ pInline $+ concat [[pString " /*"], map pString (lines (uchars c)), [pString "*/ "]]++instance PrecedeWithSpace a => PrecedeWithSpace (Com a) where+ precedeWithSpace o = case o of+ Com b -> precedeWithSpace b+ ComBefore a b -> precedeWithSpace a || precedeWithSpace b+ ComAfter b _ -> precedeWithSpace b+ ComAround a b _ -> precedeWithSpace a || precedeWithSpace b+ -- there should always be a space before a comment.++instance PPrintable [Comment] where { pPrint = mapM_ pPrint }++instance PrecedeWithSpace [Comment] where { precedeWithSpace = not . null }++instance HasRandGen [Comment] where { randO = return []; defaultO = return []; }+-- randO = do+-- i0 <- randInt+-- let (i1, many) = divMod i0 4+-- (i2, typn) = divMod i1 16+-- typx = take many (randToBase 2 typn ++ replicate 4 0)+-- lenx = map (+1) (randToBase 29 i2)+-- com typ = if typ==0 then EndlineComment else InlineComment+-- forM (zip typx lenx) $ \ (typ, len) ->+-- fmap (com typ . ustr . unwords . map (B.unpack . getRandomWord)) (replicateM len randInt)++----------------------------------------------------------------------------------------------------++-- | Symbols in the Dao syntax tree that can actually be manipulated can be surrounded by comments.+-- The 'Com' structure represents a space-efficient means to surround each syntactic element with+-- comments that can be ignored without disgarding them.+data Com a = Com a | ComBefore [Comment] a | ComAfter a [Comment] | ComAround [Comment] a [Comment]+ deriving (Eq, Ord, Typeable, Show)++instance Functor Com where+ fmap fn c = case c of+ Com a -> Com (fn a)+ ComBefore c1 a -> ComBefore c1 (fn a)+ ComAfter a c2 -> ComAfter (fn a) c2+ ComAround c1 a c2 -> ComAround c1 (fn a) c2++instance NFData a => NFData (Com a) where+ rnf (Com a ) = deepseq a ()+ rnf (ComBefore a b ) = deepseq a $! deepseq b ()+ rnf (ComAfter a b) = deepseq a $! deepseq b ()+ rnf (ComAround a b c) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue a => HasNullValue (Com a) where+ nullValue = Com nullValue+ testNull (Com a) = testNull a+ testNull _ = False++instance HasLocation a => HasLocation (Com a) where+ getLocation = getLocation . unComment+ setLocation com loc = fmap (\a -> setLocation a loc) com+ delLocation = fmap delLocation++instance HasRandGen a => HasRandGen (Com a) where+ randO = Com <$> randO+ defaultO = Com <$> defaultO++instance PPrintable a => PPrintable (Com a) where { pPrint = pPrintComWith pPrint }++pPrintComWith :: (a -> PPrint) -> Com a -> PPrint+pPrintComWith prin com = case com of+ Com c -> prin c+ ComBefore ax c -> pcom ax >> prin c+ ComAfter c bx -> prin c >> pcom bx+ ComAround ax c bx -> pcom ax >> prin c >> pcom bx+ where { pcom = pInline . map pPrint }++pListOfComsWith :: (a -> PPrint) -> [Com a] -> PPrint+pListOfComsWith prin = sequence_ . map (pPrintComWith prin)++pListOfComs :: PPrintable a => [Com a] -> PPrint+pListOfComs = pListOfComsWith pPrint++randComWith :: RandO a -> RandO (Com a)+randComWith rand = fmap Com rand+-- randComWith :: RandO a -> RandO (Com a)+-- randComWith rand = do+-- typ <- fmap (flip mod 24 . unsign) randInt+-- a <- rand+-- case typ of+-- 0 -> do+-- before <- randO+-- after <- randO+-- return (ComAround before a after)+-- 1 -> do+-- before <- randO+-- return (ComBefore before a)+-- 2 -> do+-- after <- randO+-- return (ComAfter a after)+-- _ -> return (Com a)++appendComments :: Com a -> [Comment] -> Com a+appendComments com cx = case com of+ Com a -> ComAfter a cx+ ComAfter a ax -> ComAfter a (ax++cx)+ ComBefore ax a -> ComAround ax a cx+ ComAround ax a bx -> ComAround ax a (bx++cx)++com :: [Comment] -> a -> [Comment] -> Com a+com before a after = case before of+ [] -> case after of+ [] -> Com a+ dx -> ComAfter a dx+ cx -> case after of+ [] -> ComBefore cx a+ dx -> ComAround cx a dx++setCommentBefore :: [Comment] -> Com a -> Com a+setCommentBefore cx com = case com of+ Com a -> ComBefore cx a+ ComBefore _ a -> ComBefore cx a+ ComAfter a dx -> ComAround cx a dx+ ComAround _ a dx -> ComAround cx a dx++setCommentAfter :: [Comment] -> Com a -> Com a+setCommentAfter cx com = case com of+ Com a -> ComAfter a cx+ ComBefore dx a -> ComAround dx a cx+ ComAfter a _ -> ComAfter a cx+ ComAround dx a _ -> ComAround dx a cx++unComment :: Com a -> a+unComment com = case com of+ Com a -> a+ ComBefore _ a -> a+ ComAfter a _ -> a+ ComAround _ a _ -> a++getComment :: Com a -> ([Comment], [Comment])+getComment com = case com of+ Com _ -> ([], [])+ ComBefore a _ -> (a, [])+ ComAfter _ b -> ([], b)+ ComAround a _ b -> (a, b)++----------------------------------------------------------------------------------------------------++-- | Direct a reference at a particular tree in the runtime.+data RefQualifier+ = UNQUAL -- ^ unqualified+ | LOCAL -- ^ refers to the current local variable stack+ | CONST -- ^ refers to a built-in constant+ | STATIC -- ^ a local variable stack specific to a 'Subroutine' that lives on even after the+ -- subroutine has completed.+ | GLOBAL -- ^ the global variable space for the current module.+ | GLODOT -- ^ a relative reference, gets it's name because it begins with a dot (".") character.+ -- Similar to the "this" keyword in C++ and Java, refers to the object of the current+ -- context set by the "with" statement, but defaults to the global variable space when+ -- not within a "with" statement. This is necessary to differentiate between local+ -- variables and references to the "with" context.+ deriving (Eq, Ord, Typeable, Enum, Ix, Bounded, Show, Read)++instance NFData RefQualifier where { rnf a = seq a () }++instance PPrintable RefQualifier where { pPrint = pUStr . toUStr }++instance PrecedeWithSpace RefQualifier where+ precedeWithSpace o = case o of+ LOCAL -> True+ CONST -> True+ STATIC -> True+ GLOBAL -> True+ _ -> False++instance UStrType RefQualifier where+ toUStr a = ustr $ case a of+ UNQUAL -> ""+ LOCAL -> "local"+ CONST -> "const"+ STATIC -> "static"+ GLOBAL -> "global"+ GLODOT -> "."+ maybeFromUStr str = case uchars str of+ "local" -> Just LOCAL+ "const" -> Just CONST+ "static" -> Just STATIC+ "global" -> Just GLOBAL+ "." -> Just GLODOT+ "" -> Just UNQUAL+ _ -> Nothing+ fromUStr str = maybe (error (show str++" is not a reference qualifier")) id (maybeFromUStr str)++instance HasRandGen RefQualifier where+ randO = fmap toEnum (nextInt (1+fromEnum (minBound::RefQualifier)))+ defaultO = randO++----------------------------------------------------------------------------------------------------++-- | Binary operators.+data InfixOp+ = ADD | SUB | MULT+ | DIV | MOD | POW+ | ORB | ANDB | XORB+ | SHL | SHR+ | OR | AND+ | EQUL | NEQUL + | GTN | LTN+ | GTEQ | LTEQ+ | ARROW+ deriving (Eq, Ord, Typeable, Enum, Ix, Bounded, Show, Read)++instance UStrType InfixOp where+ toUStr a = ustr $ case a of+ { ADD -> "+" ; SUB -> "-" ; MULT -> "*"+ ; DIV -> "/" ; MOD -> "%" ; POW -> "**"+ ; ORB -> "|" ; ANDB -> "&" ; XORB -> "^"+ ; SHL -> "<<"; SHR -> ">>"+ ; OR -> "||"; AND -> "&&"+ ; EQUL -> "=="; NEQUL-> "!="+ ; LTN -> "<" ; GTN -> ">"+ ; LTEQ -> "<="; GTEQ -> ">="+ ; ARROW -> "->";+ }+ maybeFromUStr str = case uchars str of+ { "+" -> Just ADD ; "-" -> Just SUB ; "*" -> Just MULT + ; "/" -> Just DIV ; "%" -> Just MOD ; "**" -> Just POW + ; "|" -> Just ORB ; "&" -> Just ANDB ; "^" -> Just XORB + ; "<<" -> Just SHL ; ">>" -> Just SHR+ ; "||" -> Just OR ; "&&" -> Just AND+ ; "==" -> Just EQUL ; "!=" -> Just NEQUL+ ; "<" -> Just LTN ; ">" -> Just GTN+ ; "<=" -> Just LTEQ ; ">=" -> Just GTEQ + ; "->" -> Just ARROW;+ ; _ -> Nothing+ }+ fromUStr str = maybe (error (show str++" is not an infix operator")) id (maybeFromUStr str)++instance NFData InfixOp where { rnf a = seq a () }++instance PPrintable InfixOp where { pPrint = pUStr . toUStr }++infixOpCommutativity :: InfixOp -> Bool+infixOpCommutativity = (arr !) where+ arr :: Array InfixOp Bool+ arr = array (minBound, maxBound) $+ [ (ADD, True), (SUB, False), (MULT, True), (DIV, False), (MOD, False), (POW, False)+ , (ORB, True), (ANDB, True), (XORB, True), (SHL, False), (SHR, False), (OR, False), (AND, False)+ , (EQUL, True), (NEQUL, True), (LTN, False), (GTN, False), (LTEQ, False), (GTEQ, False)+ , (ARROW, False)+ ]++-- binary 0x8D 0xA0+instance B.Binary InfixOp mtab where+ put o = B.putWord8 $ case o of+ { EQUL -> 0x8D; NEQUL -> 0x8E; GTN -> 0x8F; LTN -> 0x90; GTEQ -> 0x91; LTEQ -> 0x92+ ; ADD -> 0x93; SUB -> 0x94; MULT -> 0x95; DIV -> 0x96+ ; MOD -> 0x97; POW -> 0x98; ORB -> 0x99; ANDB -> 0x9A+ ; XORB -> 0x9B; SHL -> 0x9C; SHR -> 0x9D; ARROW -> 0x9E+ ; OR -> 0x9F; AND -> 0xA0 } + get = B.word8PrefixTable <|> fail "expecting InfixOp"++-- The byte prefixes overlap with the update operators of similar function to+-- the operators, except for the comparison opeators (EQUL, NEQUL, GTN, LTN,+-- GTEQ, LTEQ) which overlap with the prefix operators (INVB, NOT, NEGTIV, POSTIV, REF, DEREF)+instance B.HasPrefixTable InfixOp B.Byte mtab where+ prefixTable = B.mkPrefixTableWord8 "InfixOp" 0x8D 0xA0 $ let {r=return} in+ [ r EQUL , r NEQUL, r GTN , r LTN, r GTEQ , r LTEQ -- 0x8D,0x8E,0x8F,0x90,0x91,0x92+ , r ADD , r SUB , r MULT, r DIV, r MOD , r POW , r ORB -- 0x93,0x94,0x95,0x96,0x97,0x98,0x99+ , r ANDB , r XORB , r SHL , r SHR, r ARROW, r OR , r AND -- 0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,0xA0+ ]++instance HasRandGen InfixOp where+ randO = fmap toEnum (nextInt (1+fromEnum (maxBound::InfixOp)))+ defaultO = randO++allPrefixOpChars :: String+allPrefixOpChars = "$@~!-+"++allPrefixOpStrs :: String+allPrefixOpStrs = " $ @ ~ - + ! "++----------------------------------------------------------------------------------------------------++-- | Unary operators.+data ArithPfxOp = INVB | NOT | NEGTIV | POSTIV+ deriving (Eq, Ord, Typeable, Enum, Ix, Bounded, Show, Read)++instance NFData ArithPfxOp where { rnf a = seq a () }++instance UStrType ArithPfxOp where+ toUStr op = ustr $ case op of+ INVB -> "~"+ NOT -> "!"+ NEGTIV -> "-"+ POSTIV -> "+"+ maybeFromUStr str = case uchars str of+ "~" -> Just INVB+ "!" -> Just NOT+ "-" -> Just NEGTIV+ "+" -> Just POSTIV+ _ -> Nothing+ fromUStr str = maybe (error (show str++" is not a prefix opretor")) id (maybeFromUStr str)++instance PPrintable ArithPfxOp where { pPrint = pUStr . toUStr }++-- binary 0x8E 0x9B+instance B.Binary ArithPfxOp mtab where+ put o = B.putWord8 $ case o of { INVB -> 0x9B; NOT -> 0x8E; NEGTIV -> 0x94; POSTIV -> 0x93 }+ get = B.word8PrefixTable <|> fail "expecting ArithPfxOp"++instance B.HasPrefixTable ArithPfxOp B.Byte mtab where+ prefixTable = B.mkPrefixTableWord8 "ArithPfxOp" 0x8E 0x9F $ let {r=return;z=mzero} in+ [ r NOT -- 0x8E+ , z, z, z, z -- 0x8F,0x90,0x91,0x92+ , r POSTIV, r NEGTIV -- 0x93,0x94+ , z, z, z, z, z, z -- 0x95,0x96,0x97,0x98,0x99,0x9A+ , r INVB -- 0x9B+ ]++instance HasRandGen ArithPfxOp where+ randO = fmap toEnum (nextInt (1+fromEnum (maxBound::ArithPfxOp)))+ defaultO = randO++allInfixOpChars :: String+allInfixOpChars = "+-*/%<>^&|.?:"++allInfixOpStrs :: String+allInfixOpStrs = " + - * / % ** -> . || && == != | & ^ << >> < > <= >= . -> <- ? : :: "++----------------------------------------------------------------------------------------------------++newtype DotNameExpr = DotNameExpr{ undotNameExpr :: Name } deriving (Eq, Ord, Show, Typeable)++instance NFData DotNameExpr where { rnf (DotNameExpr n) = deepseq n () }++instance B.Binary DotNameExpr mtab where { put (DotNameExpr n) = B.put n; get = DotNameExpr <$> B.get; }++instance PPrintable DotNameExpr where { pPrint (DotNameExpr n) = pPrint n }++-- | A 'DotName' is simply a ".name" expression in the Dao language. It is a component of the+-- 'DotLabelExpr' and 'AST_DotLabel' data types.+data AST_DotName = AST_DotName (Com ()) Name deriving (Eq, Ord, Show, Typeable)++getDotNameAST :: AST_DotName -> Name+getDotNameAST (AST_DotName _ n) = n++instance NFData AST_DotName where { rnf (AST_DotName a b) = deepseq a $! deepseq b () }++instance PPrintable AST_DotName where+ pPrint (AST_DotName c n) = pInline [pPrintComWith (\ () -> pString ".") c, pPrint n]++instance Intermediate DotNameExpr AST_DotName where+ toInterm (AST_DotName _ n) = [DotNameExpr n]+ fromInterm (DotNameExpr n) = [AST_DotName (Com ()) n]++----------------------------------------------------------------------------------------------------++data NamespaceExpr = NamespaceExpr (Maybe Name) Location deriving (Eq, Ord, Show, Typeable)++instance NFData NamespaceExpr where { rnf (NamespaceExpr a b) = deepseq a $! deepseq b () }++instance HasNullValue NamespaceExpr where+ nullValue = NamespaceExpr Nothing LocationUnknown+ testNull (NamespaceExpr Nothing _) = True+ testNull _ = False++instance HasLocation NamespaceExpr where+ getLocation (NamespaceExpr _ loc) = loc+ setLocation (NamespaceExpr a _ ) loc = NamespaceExpr a loc+ delLocation (NamespaceExpr a _ ) = NamespaceExpr a LocationUnknown++instance PPrintable NamespaceExpr where { pPrint = pPrintInterm }++instance B.Binary NamespaceExpr mtab where+ put (NamespaceExpr a loc) = B.put a >> B.put loc+ get = return NamespaceExpr <*> B.get <*> B.get++----------------------------------------------------------------------------------------------------++data AST_Namespace+ = AST_NoNamespace+ | AST_Namespace (Com Name) Location+ deriving (Eq, Ord, Show, Typeable)++instance NFData AST_Namespace where+ rnf AST_NoNamespace = ()+ rnf (AST_Namespace a b) = deepseq a $! deepseq b ()++instance HasNullValue AST_Namespace where+ nullValue = AST_NoNamespace+ testNull AST_NoNamespace = True+ testNull _ = False++instance HasLocation AST_Namespace where+ getLocation o = case o of+ AST_NoNamespace -> LocationUnknown+ AST_Namespace _ loc -> loc+ setLocation o loc = case o of+ AST_NoNamespace -> AST_NoNamespace+ AST_Namespace a _ -> AST_Namespace a loc+ delLocation o = case o of+ AST_NoNamespace -> AST_NoNamespace+ AST_Namespace a _ -> AST_Namespace a LocationUnknown++instance PPrintable AST_Namespace where+ pPrint o = case o of { AST_NoNamespace -> return (); AST_Namespace a _ -> pPrint a; }++instance HasRandGen AST_Namespace where+ randO = countNode $ runRandChoice+ randChoice = randChoiceList [return AST_NoNamespace, return AST_Namespace <*> randO <*> no]+ defaultO = randO+ defaultChoice = randChoiceList [defaultO]++instance Intermediate NamespaceExpr AST_Namespace where+ toInterm o = case o of+ AST_NoNamespace -> [nullValue]+ AST_Namespace n loc -> [NamespaceExpr (Just $ unComment n) loc]+ fromInterm (NamespaceExpr n loc) = case n of+ Nothing -> [nullValue]+ Just n -> [AST_Namespace (Com n) loc]++----------------------------------------------------------------------------------------------------++-- | The intermediate form of 'AST_DotLabel'.+data DotLabelExpr = DotLabelExpr DotNameExpr [DotNameExpr] Location + deriving (Eq, Ord, Show, Typeable)++instance NFData DotLabelExpr where+ rnf (DotLabelExpr n nm loc) = deepseq n $! deepseq nm $! deepseq loc ()++instance HasLocation DotLabelExpr where+ getLocation (DotLabelExpr _ _ loc) = loc+ setLocation (DotLabelExpr n nx _ ) loc = DotLabelExpr n nx loc+ delLocation (DotLabelExpr n nx _ ) = DotLabelExpr n nx LocationUnknown++instance B.Binary DotLabelExpr mtab where+ put (DotLabelExpr n nx loc) = B.prefixByte 0x81 $ B.put n >> B.put nx >> B.put loc+ get = B.word8PrefixTable <|> fail "expecting DotLabelExpr"++instance B.HasPrefixTable DotLabelExpr Word8 mtab where+ prefixTable = B.mkPrefixTableWord8 "DotLabelExpr" 0x81 0x81 $+ [return DotLabelExpr <*> B.get <*> B.get <*> B.get]++instance PPrintable DotLabelExpr where { pPrint = pPrintInterm }++dotLabelToNameList :: DotLabelExpr -> [Name]+dotLabelToNameList (DotLabelExpr n nx _) = map undotNameExpr (n:nx)++----------------------------------------------------------------------------------------------------++-- | This is a list of 'Dao.String.Name's separated by dots. It is a pseudo-reference used to denote+-- things like constructor names in 'InitExpr', or setting the logical names of "import" modules+-- statements. It is basically a list of 'Dao.String.Name's that always has at least one element.+data AST_DotLabel = AST_DotLabel Name [AST_DotName] Location deriving (Eq, Ord, Show, Typeable)++instance NFData AST_DotLabel where+ rnf (AST_DotLabel n nx loc) = deepseq n $! deepseq nx $! deepseq loc ()++instance HasLocation AST_DotLabel where+ getLocation (AST_DotLabel _ _ loc) = loc+ setLocation (AST_DotLabel n nx _ ) loc = AST_DotLabel n nx loc+ delLocation (AST_DotLabel n nx _ ) = AST_DotLabel n nx LocationUnknown++instance PPrintable AST_DotLabel where+ pPrint (AST_DotLabel n nx _) = pWrapIndent $ pPrint n : map pPrint nx++instance HasRandGen AST_DotLabel where+ randO = return AST_DotLabel <*> randO <*> randListOf 0 3 (return AST_DotName <*> randO <*> randO) <*> no+ defaultO = randO++instance Intermediate DotLabelExpr AST_DotLabel where+ toInterm (AST_DotLabel n nx loc) = [DotLabelExpr] <*> [DotNameExpr n] <*> [nx >>= ti] <*> [loc]+ fromInterm (DotLabelExpr (DotNameExpr n) nx loc) = [AST_DotLabel] <*> [n] <*> [nx >>= fi] <*> [loc]++dotLabelToRefExpr :: DotLabelExpr -> ReferenceExpr o+dotLabelToRefExpr (DotLabelExpr (DotNameExpr n) nx loc) =+ ReferenceExpr UNQUAL n (loop nx) loc where+ loop nx = case nx of+ [] -> NullRefExpr+ (DotNameExpr n):nx -> DotRefExpr n (loop nx) LocationUnknown++refToDotLabelExpr :: ReferenceExpr o -> Maybe (DotLabelExpr, Maybe (ObjListExpr o))+refToDotLabelExpr o = case o of+ ReferenceExpr UNQUAL n suf loc ->+ loop (\nx loc ol -> (DotLabelExpr (DotNameExpr n) nx loc, ol)) [] loc suf+ _ -> mzero+ where+ loop f nx loc suf = case suf of+ NullRefExpr -> return (f nx loc Nothing)+ DotRefExpr n suf loc' -> loop f (nx++[DotNameExpr n]) (loc<>loc') suf+ FuncCallExpr ol NullRefExpr -> return (f nx loc (Just ol))+ _ -> mzero++dotLabelToRefAST :: AST_DotLabel -> AST_Reference o+dotLabelToRefAST (AST_DotLabel n nx loc) = AST_Reference UNQUAL [] n (loop nx) loc where+ loop nx = case nx of+ [] -> AST_RefNull+ (AST_DotName c n):nx -> AST_DotRef c n (loop nx) LocationUnknown++refToDotLabelAST :: AST_Reference o -> Maybe (AST_DotLabel, Maybe (AST_ObjList o))+refToDotLabelAST o = case o of+ AST_Reference UNQUAL _ n suf loc -> loop (\nx loc ol -> (AST_DotLabel n nx loc, ol)) [] loc suf+ _ -> mzero+ where+ loop f nx loc suf = case suf of+ AST_RefNull -> return (f nx loc Nothing)+ AST_DotRef c n suf loc' -> loop f (nx++[AST_DotName c n]) (loc<>loc') suf+ AST_FuncCall ol AST_RefNull -> return (f nx loc (Just ol))+ _ -> mzero++----------------------------------------------------------------------------------------------------++data RefSuffixExpr o+ = NullRefExpr+ | DotRefExpr Name (RefSuffixExpr o) Location+ | SubscriptExpr (ObjListExpr o) (RefSuffixExpr o)+ | FuncCallExpr (ObjListExpr o) (RefSuffixExpr o)+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (RefSuffixExpr o) where+ rnf NullRefExpr = ()+ rnf (DotRefExpr a b c) = deepseq a $! deepseq b $! deepseq c ()+ rnf (SubscriptExpr a b ) = deepseq a $! deepseq b ()+ rnf (FuncCallExpr a b ) = deepseq a $! deepseq b ()++instance HasNullValue (RefSuffixExpr o) where+ nullValue = NullRefExpr+ testNull NullRefExpr = True+ testNull _ = False++instance HasLocation (RefSuffixExpr o) where+ getLocation o = case o of+ NullRefExpr -> LocationUnknown+ DotRefExpr _ _ loc -> loc+ SubscriptExpr a _ -> getLocation a+ FuncCallExpr a _ -> getLocation a+ setLocation o loc = case o of+ NullRefExpr -> NullRefExpr+ DotRefExpr a b _ -> DotRefExpr a b loc+ SubscriptExpr a b -> SubscriptExpr (setLocation a loc) b+ FuncCallExpr a b -> FuncCallExpr (setLocation a loc) b+ delLocation o = case o of+ NullRefExpr -> NullRefExpr+ DotRefExpr a b _ -> DotRefExpr a (delLocation b) LocationUnknown+ SubscriptExpr a b -> SubscriptExpr a (delLocation b)+ FuncCallExpr a b -> FuncCallExpr a (delLocation b)++----------------------------------------------------------------------------------------------------++-- | Anything that follows a reference, which could be square-bracketed indecies, function+-- parameters, or a dot and another reference. This is actually only a partial reference. The+-- 'Reference' is the full reference. The item selected by the 'Reference' is then further inspected+-- using a 'RefSuffix'; a 'RefSuffix' may be null, but a 'Reference' is never null.+data AST_RefSuffix o+ = AST_RefNull+ | AST_DotRef (Com ()) Name (AST_RefSuffix o) Location+ | AST_Subscript (AST_ObjList o) (AST_RefSuffix o)+ | AST_FuncCall (AST_ObjList o) (AST_RefSuffix o)+ deriving (Eq, Ord, Typeable, Show, Functor)++instance HasNullValue (AST_RefSuffix o) where+ nullValue = AST_RefNull+ testNull AST_RefNull = True+ testNull _ = False++instance HasLocation (AST_RefSuffix o) where+ getLocation o = case o of+ AST_RefNull -> LocationUnknown+ AST_DotRef _ _ _ loc -> loc+ AST_Subscript a _ -> getLocation a+ AST_FuncCall a _ -> getLocation a+ setLocation o loc = case o of+ AST_RefNull -> AST_RefNull+ AST_DotRef a b c _ -> AST_DotRef a b c loc+ AST_Subscript a b -> AST_Subscript (setLocation a loc) b+ AST_FuncCall a b -> AST_FuncCall (setLocation a loc) b+ delLocation o = case o of+ AST_RefNull -> AST_RefNull+ AST_DotRef a b c _ -> AST_DotRef a b (delLocation c) LocationUnknown+ AST_Subscript a b -> AST_Subscript (delLocation a) (delLocation b)+ AST_FuncCall a b -> AST_FuncCall (delLocation a) (delLocation b)++instance PPrintable o => PPrintable (AST_RefSuffix o) where+ pPrint = pWrapIndent . loop where+ loop ref = case ref of+ AST_RefNull -> []+ AST_DotRef dot name ref _ -> pPrintComWith (\ () -> pString ".") dot : pUStr (toUStr name) : loop ref+ AST_Subscript args ref -> pArgs "[]" args ++ loop ref+ AST_FuncCall args ref -> pArgs "()" args ++ loop ref+ where+ pArgs str args = case str of+ (open:close:_) -> [pString [open], pPrint args, pString [close]]+ _ -> error "INTERNAL: bad instance definition of PPrintable AST_RefSuffix"++instance NFData o => NFData (AST_RefSuffix o) where+ rnf AST_RefNull = ()+ rnf (AST_DotRef a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (AST_Subscript a b ) = deepseq a $! deepseq b ()+ rnf (AST_FuncCall a b ) = deepseq a $! deepseq b ()++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_RefSuffix o) where+ randO = recurse $ countNode $ runRandChoice+ randChoice = randChoiceList $+ [ return AST_RefNull+ , scramble $ return AST_DotRef <*> randO <*> randO <*> randO <*> no+ , scramble $ return AST_Subscript <*> randO <*> randO+ , scramble $ return AST_FuncCall <*> randO <*> randO+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ return AST_RefNull+ , return AST_DotRef <*> defaultO <*> randO <*> pure AST_RefNull <*> no+ , return AST_Subscript <*> pure nullValue <*> pure AST_RefNull+ , return AST_FuncCall <*> pure nullValue <*> pure AST_RefNull+ ]++instance Intermediate (RefSuffixExpr o) (AST_RefSuffix o) where+ toInterm ast = case ast of+ AST_RefNull -> [NullRefExpr]+ AST_DotRef _ name ref loc -> [DotRefExpr] <*> [name] <*> toInterm ref <*> [loc]+ AST_Subscript args ref -> [SubscriptExpr] <*> toInterm args <*> toInterm ref+ AST_FuncCall args ref -> [FuncCallExpr] <*> toInterm args <*> toInterm ref+ fromInterm obj = case obj of+ NullRefExpr -> [AST_RefNull]+ DotRefExpr name ref loc -> [AST_DotRef] <*> [Com ()] <*> [name] <*> fromInterm ref <*> [loc]+ SubscriptExpr args ref -> [AST_Subscript] <*> fromInterm args <*> fromInterm ref+ FuncCallExpr args ref -> [AST_FuncCall] <*> fromInterm args <*> fromInterm ref++----------------------------------------------------------------------------------------------------++data ReferenceExpr o+ = RefObjectExpr (ParenExpr o) (RefSuffixExpr o) Location+ | ReferenceExpr RefQualifier Name (RefSuffixExpr o) Location+ -- ^ reference suffixed by square brackets or round brackets. If the 3rd parameter is False, it+ -- is suffixed by square brackets, and True means suffixed by round brackets. Square brackets+ -- indicates an indexing expression, round brackets indicates a function call.+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (ReferenceExpr o) where+ rnf (RefObjectExpr a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (ReferenceExpr a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasNullValue (ReferenceExpr o) where+ nullValue = RefObjectExpr nullValue NullRefExpr LocationUnknown+ testNull (RefObjectExpr a NullRefExpr LocationUnknown) = testNull a+ testNull _ = False++instance HasLocation (ReferenceExpr o) where+ getLocation o = case o of+ RefObjectExpr _ _ loc -> loc+ ReferenceExpr _ _ _ loc -> loc+ setLocation o loc = case o of+ RefObjectExpr a b _ -> RefObjectExpr a b loc+ ReferenceExpr a b c loc -> ReferenceExpr a b c loc+ delLocation o = case o of+ RefObjectExpr a b _ -> RefObjectExpr (delLocation a) b LocationUnknown+ ReferenceExpr a b c _ -> ReferenceExpr a b c LocationUnknown++instance PPrintable o => PPrintable (ReferenceExpr o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++data AST_Reference o+ = AST_RefObject (AST_Paren o) (AST_RefSuffix o) Location+ | AST_Reference RefQualifier [Comment] Name (AST_RefSuffix o) Location+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (AST_Reference o) where+ rnf (AST_RefObject a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (AST_Reference a b c d e) = deepseq a $! deepseq b $! deepseq c $! deepseq d $! deepseq e ()++instance HasLocation (AST_Reference o) where+ getLocation o = case o of+ AST_RefObject _ _ loc -> loc+ AST_Reference _ _ _ _ loc -> loc+ setLocation o loc = case o of+ AST_RefObject a b _ -> AST_RefObject a b loc+ AST_Reference a b c d _ -> AST_Reference a b c d loc+ delLocation o = case o of+ AST_RefObject a b _ -> AST_RefObject (delLocation a) (delLocation b) LocationUnknown+ AST_Reference a b c d _ -> AST_Reference a b c (delLocation d) LocationUnknown++instance PPrintable o => PPrintable (AST_Reference o) where+ pPrint o = pInline $ case o of+ AST_RefObject o ref _ -> [pPrint o, pPrint ref]+ AST_Reference q coms name ref _ -> concat $+ [ if q==UNQUAL then [] else [pPrint q, pString " "]+ , [pPrint coms, pUStr (toUStr name), pPrint ref]+ ]++instance PrecedeWithSpace (AST_Reference o) where { precedeWithSpace _ = True }++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_Reference o) where+ randO = countNode $ runRandChoice+ randChoice = randChoiceList $+ [ scramble $ return AST_Reference <*> randO <*> randO <*> randO <*> randO <*> no+ , scramble $ return AST_RefObject <*> randO <*> randO <*> no+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ return AST_Reference <*> defaultO <*> defaultO <*> defaultO <*> defaultO <*> no+ , return AST_RefObject <*> defaultO <*> defaultO <*> no+ ]++instance Intermediate (ReferenceExpr o) (AST_Reference o) where+ toInterm ast = case ast of+ AST_RefObject paren ref loc -> [RefObjectExpr] <*> ti paren <*> ti ref <*> [loc]+ AST_Reference q _ name ref loc -> [ReferenceExpr] <*> [q] <*> [name] <*> ti ref <*> [loc]+ fromInterm o = case o of+ RefObjectExpr paren ref loc -> [AST_RefObject] <*> fi paren <*> fi ref <*> [loc]+ ReferenceExpr q name ref loc ->+ [AST_Reference] <*> [q] <*> [[]] <*> [name] <*> fi ref <*> [loc]++----------------------------------------------------------------------------------------------------++data RefPrefixExpr o+ = PlainRefExpr (ReferenceExpr o)+ | RefPrefixExpr RefPfxOp (RefPrefixExpr o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++-- | Eliminate composed DEREF-REF operations+-- > @$a == a+cleanupRefPrefixExpr :: RefPrefixExpr o -> RefPrefixExpr o+cleanupRefPrefixExpr o =+ case o of { RefPrefixExpr DEREF (RefPrefixExpr REF o _) _ -> cleanupRefPrefixExpr o; _ -> o; }++instance NFData o => NFData (RefPrefixExpr o) where+ rnf (RefPrefixExpr a b c) = deepseq a $! deepseq b $! deepseq c ()+ rnf (PlainRefExpr a ) = deepseq a ()++instance HasNullValue (RefPrefixExpr o) where+ nullValue = PlainRefExpr nullValue+ testNull (PlainRefExpr a) = testNull a+ testNull _ = False++instance HasLocation (RefPrefixExpr o) where+ getLocation o = case o of+ PlainRefExpr o -> getLocation o+ RefPrefixExpr _ _ o -> o+ setLocation o loc = case o of+ PlainRefExpr a -> PlainRefExpr (setLocation a loc)+ RefPrefixExpr a b _ -> RefPrefixExpr a b loc+ delLocation o = case o of+ PlainRefExpr a -> PlainRefExpr (delLocation a)+ RefPrefixExpr a b _ -> RefPrefixExpr a (delLocation b) LocationUnknown++----------------------------------------------------------------------------------------------------++data AST_RefPrefix o+ = AST_PlainRef (AST_Reference o)+ | AST_RefPrefix RefPfxOp [Comment] (AST_RefPrefix o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_RefPrefix o) where+ rnf (AST_PlainRef a ) = deepseq a ()+ rnf (AST_RefPrefix a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasLocation (AST_RefPrefix o) where+ getLocation o = case o of+ AST_PlainRef a -> getLocation a+ AST_RefPrefix _ _ _ loc -> loc+ setLocation o loc = case o of+ AST_PlainRef a -> AST_PlainRef $ setLocation a loc+ AST_RefPrefix a b c _ -> AST_RefPrefix a b c loc+ delLocation o = case o of+ AST_PlainRef a -> AST_PlainRef $ delLocation a+ AST_RefPrefix a b c _ -> AST_RefPrefix a b (delLocation c) LocationUnknown++instance PPrintable o => PPrintable (AST_RefPrefix o) where+ pPrint o = case o of+ AST_PlainRef o -> pPrint o+ AST_RefPrefix ariOp coms objXp _ -> pWrapIndent [pPrint ariOp, pPrint coms, pPrint objXp]++instance PrecedeWithSpace (AST_RefPrefix o) where+ precedeWithSpace o = case o of+ AST_PlainRef _ -> True+ _ -> False++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_RefPrefix o) where+ randO = recurse $ countNode $ runRandChoice+ randChoice = randChoiceList $+ [ return AST_PlainRef <*> randO+ , return AST_RefPrefix <*> randO <*> randO <*> randO <*> no+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ AST_PlainRef <$> defaultO+ , return AST_RefPrefix <*> defaultO <*> defaultO <*> (AST_PlainRef <$> defaultO) <*> no+ ]++instance Intermediate (RefPrefixExpr o) (AST_RefPrefix o) where+ toInterm ast = case ast of+ AST_PlainRef a -> PlainRefExpr <$> toInterm a+ AST_RefPrefix a _ c loc -> [RefPrefixExpr] <*> [a] <*> toInterm c <*> [loc]+ fromInterm o = case o of+ PlainRefExpr a -> AST_PlainRef <$> fromInterm a+ RefPrefixExpr a c loc -> [AST_RefPrefix] <*> [a] <*> [[]] <*> fromInterm c <*> [loc]++----------------------------------------------------------------------------------------------------++-- | Contains a list of 'ObjectExpr's, which are used to encode parameters to function calls, and+-- intialization lists.+data ObjListExpr o = ObjListExpr [AssignExpr o] Location deriving (Eq, Ord, Typeable, Show, Functor)++instance Monoid (ObjListExpr o) where+ mempty = ObjListExpr [] LocationUnknown+ mappend (ObjListExpr a locA) (ObjListExpr b locB) = ObjListExpr (a++b) (locA<>locB)++instance NFData o => NFData (ObjListExpr o) where { rnf (ObjListExpr a b) = deepseq a $! deepseq b () }++instance HasNullValue (ObjListExpr o) where+ nullValue = mempty+ testNull (ObjListExpr a _) = null a++instance HasLocation (ObjListExpr o) where+ getLocation (ObjListExpr _ loc) = loc+ setLocation (ObjListExpr a _ ) loc = ObjListExpr (fmap delLocation a) loc+ delLocation (ObjListExpr a _ ) = ObjListExpr (fmap delLocation a) LocationUnknown++----------------------------------------------------------------------------------------------------++data AST_ObjList o = AST_ObjList [Comment] [Com (AST_Assign o)] Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance Monoid (AST_ObjList o) where+ mempty = AST_ObjList [] [] LocationUnknown+ mappend (AST_ObjList a1 a2 aloc) (AST_ObjList b1 b2 bloc) = AST_ObjList (a1++b1) (a2++b2) (aloc<>bloc)++instance HasNullValue (AST_ObjList o) where+ nullValue = mempty+ testNull (AST_ObjList [] [] _) = True+ testNull _ = False++instance HasLocation (AST_ObjList o) where+ getLocation (AST_ObjList _ _ loc) = loc+ setLocation (AST_ObjList a b _ ) loc = AST_ObjList a b loc+ delLocation (AST_ObjList a b _ ) = AST_ObjList a (fmap delLocation b) LocationUnknown++instance PPrintable o => PPrintable (AST_ObjList o) where+ pPrint (AST_ObjList coms lst _) = pPrint coms >>+ pInline (intersperse (pString ", ") (map pPrint lst))++instance NFData o => NFData (AST_ObjList o) where { rnf (AST_ObjList a b c) = deepseq a $! deepseq b $! deepseq c () }++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_ObjList o) where+ randO = recurse $ depthLimitedInt 8 >>= \x -> AST_ObjList <$> randO <*> randList 0 x <*> no+ defaultO = return AST_ObjList <*> defaultO <*> pure [] <*> no++instance Intermediate (ObjListExpr o) (AST_ObjList o) where+ toInterm (AST_ObjList _ lst loc) = [ObjListExpr] <*> [lst>>=uc0] <*> [loc]+ fromInterm (ObjListExpr lst loc) = [AST_ObjList] <*> [[]] <*> [lst>>=nc0] <*> [loc]++----------------------------------------------------------------------------------------------------++newtype OptObjListExpr o = OptObjListExpr (Maybe (ObjListExpr o))+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (OptObjListExpr o) where { rnf (OptObjListExpr a) = deepseq a () }++instance HasLocation (OptObjListExpr o) where+ getLocation (OptObjListExpr o) = maybe LocationUnknown getLocation o+ setLocation (OptObjListExpr o) loc = OptObjListExpr (setLocation o loc)+ delLocation (OptObjListExpr o) = OptObjListExpr (delLocation o )++instance HasNullValue (OptObjListExpr o) where+ nullValue = OptObjListExpr Nothing+ testNull (OptObjListExpr Nothing) = True+ testNull _ = False++----------------------------------------------------------------------------------------------------++data AST_OptObjList o = AST_OptObjList [Comment] (Maybe (AST_ObjList o))+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_OptObjList o) where+ rnf (AST_OptObjList a b) = deepseq a $! deepseq b ()++instance HasNullValue (AST_OptObjList o) where+ nullValue = AST_OptObjList [] Nothing+ testNull (AST_OptObjList _ a) = maybe True testNull a++pPrintObjList :: PPrintable o => String -> String -> String -> AST_ObjList o -> PPrint+pPrintObjList open comma close (AST_ObjList coms lst _) = pList (pPrint coms) open comma close (map pPrint lst)++pPrintOptObjList :: PPrintable o => String -> String -> String -> AST_OptObjList o -> PPrint+pPrintOptObjList open comma close (AST_OptObjList coms o) =+ maybe (return ()) (\o -> pPrint coms >> pPrintObjList open comma close o) o++instance HasLocation (AST_OptObjList o) where+ getLocation (AST_OptObjList _ o) = maybe LocationUnknown getLocation o+ setLocation (AST_OptObjList c o) loc = AST_OptObjList c (fmap (flip setLocation loc) o)+ delLocation (AST_OptObjList c o) = AST_OptObjList c (fmap delLocation o)++instance PPrintable o => PPrintable (AST_OptObjList o) where { pPrint o = pPrintOptObjList "{" ", " "}" o }++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_OptObjList o) where+ randO = countNode $ return AST_OptObjList <*> randO <*> randO+ defaultO = return AST_OptObjList <*> defaultO <*> defaultO++instance Intermediate (OptObjListExpr o) (AST_OptObjList o) where+ toInterm (AST_OptObjList _ o) = OptObjListExpr <$> um1 o+ fromInterm (OptObjListExpr o) = AST_OptObjList [] <$> nm1 o++----------------------------------------------------------------------------------------------------++data LiteralExpr o = LiteralExpr o Location deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (LiteralExpr o) where+ rnf (LiteralExpr a b) = deepseq a $! deepseq b ()++instance {- KEEP -} HasNullValue o => HasNullValue (LiteralExpr o) where+ nullValue = LiteralExpr nullValue LocationUnknown+ testNull (LiteralExpr o _) = testNull o++instance HasLocation (LiteralExpr o) where+ getLocation (LiteralExpr _ loc) = loc+ setLocation (LiteralExpr o _ ) loc = LiteralExpr o loc+ delLocation (LiteralExpr o _ ) = LiteralExpr o LocationUnknown++----------------------------------------------------------------------------------------------------++data AST_Literal o = AST_Literal o Location deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_Literal o) where+ rnf (AST_Literal a b) = deepseq a $! deepseq b ()++instance {- KEEP -} HasNullValue o => HasNullValue (AST_Literal o) where+ nullValue = AST_Literal nullValue LocationUnknown+ testNull (AST_Literal o _) = testNull o++instance HasLocation (AST_Literal o) where+ getLocation (AST_Literal _ loc) = loc+ setLocation (AST_Literal a _ ) loc = AST_Literal a loc+ delLocation (AST_Literal a _ ) = AST_Literal a LocationUnknown++instance PPrintable o => PPrintable (AST_Literal o) where+ pPrint (AST_Literal o _ ) = pPrint o++instance PrecedeWithSpace (AST_Literal o) where+ precedeWithSpace (AST_Literal _ _) = True++instance HasRandGen o => HasRandGen (AST_Literal o) where+ randO = scramble $ return AST_Literal <*> defaultO <*> no+ defaultO = randO++instance Intermediate (LiteralExpr o) (AST_Literal o) where+ toInterm (AST_Literal a loc) = [LiteralExpr] <*> [a] <*> [loc]+ fromInterm (LiteralExpr a loc) = [AST_Literal] <*> [a] <*> [loc]++----------------------------------------------------------------------------------------------------++-- | Required parenthesese.+data ParenExpr o = ParenExpr (AssignExpr o) Location deriving (Eq, Ord, Typeable, Show, Functor)++instance HasLocation (ParenExpr o) where+ getLocation (ParenExpr _ loc) = loc+ setLocation (ParenExpr o _ ) loc = ParenExpr o loc+ delLocation (ParenExpr o _ ) = ParenExpr (delLocation o) LocationUnknown++instance HasNullValue (ParenExpr o) where+ nullValue = ParenExpr nullValue LocationUnknown+ testNull (ParenExpr a _) = testNull a++instance NFData o => NFData (ParenExpr o) where { rnf (ParenExpr a b) = deepseq a $! deepseq b () }++instance PPrintable o => PPrintable (ParenExpr o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++data AST_Paren o = AST_Paren (Com (AST_Assign o)) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance HasLocation (AST_Paren o) where+ getLocation (AST_Paren _ loc) = loc+ setLocation (AST_Paren o _ ) loc = AST_Paren o loc+ delLocation (AST_Paren o _ ) = AST_Paren (delLocation o) LocationUnknown++instance HasNullValue (AST_Paren o) where+ nullValue = AST_Paren nullValue LocationUnknown+ testNull (AST_Paren a _) = testNull a++instance NFData o => NFData (AST_Paren o) where { rnf (AST_Paren a b) = deepseq a $! deepseq b () }++instance PPrintable o => PPrintable (AST_Paren o) where+ pPrint (AST_Paren o _) = pInline [pString "(", pPrint o, pString ")"]++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_Paren o) where+ randO = recurse $ return AST_Paren <*> randO <*> no+ defaultO = return AST_Paren <*> defaultO <*> no++instance Intermediate (ParenExpr o) (AST_Paren o) where+ toInterm (AST_Paren o loc) = [ParenExpr] <*> uc0 o <*> [loc]+ fromInterm (ParenExpr o loc) = [AST_Paren] <*> nc0 o <*> [loc]++----------------------------------------------------------------------------------------------------++data AssignExpr o+ = EvalExpr (ObjTestExpr o)+ | AssignExpr (ObjTestExpr o) UpdateOp (AssignExpr o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AssignExpr o) where+ rnf (EvalExpr a ) = deepseq a ()+ rnf (AssignExpr a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasNullValue (AssignExpr o) where+ nullValue = EvalExpr nullValue+ testNull (EvalExpr a) = testNull a+ testNull _ = False++instance HasLocation (AssignExpr o) where+ getLocation o = case o of+ EvalExpr o -> getLocation o+ AssignExpr _ _ _ o -> o+ setLocation o loc = case o of+ EvalExpr a -> EvalExpr (setLocation a loc)+ AssignExpr a b c _ -> AssignExpr a b c loc+ delLocation o = case o of+ EvalExpr a -> EvalExpr (delLocation a)+ AssignExpr a b c _ -> AssignExpr (delLocation a) b (delLocation c) LocationUnknown++instance PPrintable o => PPrintable (AssignExpr o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++data AST_Assign o+ = AST_Eval (AST_ObjTest o)+ | AST_Assign (AST_ObjTest o) (Com UpdateOp) (AST_Assign o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_Assign o) where+ rnf (AST_Eval a ) = deepseq a ()+ rnf (AST_Assign a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasNullValue (AST_Assign o) where+ nullValue = AST_Eval nullValue+ testNull (AST_Eval a) = testNull a+ testNull _ = False++instance HasLocation (AST_Assign o) where+ getLocation o = case o of+ AST_Eval o -> getLocation o+ AST_Assign _ _ _ o -> o+ setLocation o loc = case o of+ AST_Eval o -> AST_Eval (setLocation o loc)+ AST_Assign a b c _ -> AST_Assign a b c loc+ delLocation o = case o of + AST_Eval o -> AST_Eval (delLocation o)+ AST_Assign a b c _ -> AST_Assign (delLocation a) b (delLocation c) LocationUnknown++instance PPrintable o => PPrintable (AST_Assign o) where+ pPrint expr = case expr of+ AST_Eval eq -> pPrint eq+ AST_Assign objXp1 comUpdOp objXp2 _ -> pWrapIndent $+ [pPrint objXp1, pPrint comUpdOp, pPrint objXp2]++instance PrecedeWithSpace (AST_Assign o) where+ precedeWithSpace o = case o of+ AST_Eval o -> precedeWithSpace o+ AST_Assign o _ _ _ -> precedeWithSpace o++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_Assign o) where+ randO = countNode $ recurse $ runRandChoice+ randChoice = randChoiceList $+ [ AST_Eval <$> randO+ , do ox <- randListOf 0 3 (pure (,) <*> randO <*> randO)+ o <- randO+ return (foldr (\ (left, op) right -> AST_Assign left op right LocationUnknown) o ox)+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ AST_Eval <$> defaultO+ , return AST_Assign <*> defaultO <*> defaultO <*> (AST_Eval <$> defaultO) <*> no+ ]++instance Intermediate (AssignExpr o) (AST_Assign o) where+ toInterm ast = case ast of+ AST_Eval a -> EvalExpr <$> ti a+ AST_Assign a b c loc -> [AssignExpr] <*> ti a <*> uc b <*> ti c <*> [loc]+ fromInterm o = case o of+ EvalExpr a -> AST_Eval <$> fi a+ AssignExpr a b c loc -> [AST_Assign] <*> fi a <*> nc b <*> fi c <*> [loc]++----------------------------------------------------------------------------------------------------++-- | A conditional expression of the form @a==b ? "yes" : "no"@+data ObjTestExpr o+ = ObjArithExpr (ArithExpr o)+ | ObjTestExpr (ArithExpr o) (ArithExpr o) (ArithExpr o) Location+ | ObjRuleFuncExpr (RuleFuncExpr o)+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (ObjTestExpr o) where+ rnf (ObjArithExpr a ) = deepseq a ()+ rnf (ObjTestExpr a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (ObjRuleFuncExpr a ) = deepseq a $! ()++instance HasNullValue (ObjTestExpr o) where+ nullValue = ObjArithExpr nullValue+ testNull (ObjArithExpr a) = testNull a+ testNull _ = False++instance HasLocation (ObjTestExpr o) where+ getLocation o = case o of+ ObjArithExpr a -> getLocation a+ ObjTestExpr _ _ _ loc -> loc+ ObjRuleFuncExpr o -> getLocation o+ setLocation o loc = case o of+ ObjArithExpr a -> ObjArithExpr (setLocation a loc)+ ObjTestExpr a b c _ -> ObjTestExpr a b c loc+ ObjRuleFuncExpr a -> ObjRuleFuncExpr (setLocation a loc)+ delLocation o = case o of+ ObjArithExpr a -> ObjArithExpr (delLocation a)+ ObjTestExpr a b c _ ->+ ObjTestExpr (delLocation a) (delLocation b) (delLocation c) LocationUnknown+ ObjRuleFuncExpr a -> ObjRuleFuncExpr (delLocation a)++instance PPrintable o => PPrintable (ObjTestExpr o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++-- | A conditional expression of the form @a==b ? "yes" : "no"@+data AST_ObjTest o+ = AST_ObjArith (AST_Arith o)+ | AST_ObjTest (AST_Arith o) (Com ()) (AST_Arith o) (Com ()) (AST_Arith o) Location+ | AST_ObjRuleFunc (AST_RuleFunc o)+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (AST_ObjTest o) where+ rnf (AST_ObjArith a ) = deepseq a ()+ rnf (AST_ObjTest a b c d e f) =+ deepseq a $! deepseq b $! deepseq c $! deepseq d $! deepseq e $! deepseq f ()+ rnf (AST_ObjRuleFunc a ) = deepseq a ()++instance HasNullValue (AST_ObjTest o) where+ nullValue = AST_ObjArith nullValue+ testNull (AST_ObjArith a) = testNull a+ testNull _ = False++instance HasLocation (AST_ObjTest o) where+ getLocation o = case o of+ AST_ObjArith a -> getLocation a+ AST_ObjTest _ _ _ _ _ loc -> loc+ AST_ObjRuleFunc o -> getLocation o+ setLocation o loc = case o of+ AST_ObjArith a -> AST_ObjArith (setLocation a loc)+ AST_ObjTest a b c d e _ -> AST_ObjTest a b c d e loc+ AST_ObjRuleFunc a -> AST_ObjRuleFunc (setLocation a loc)+ delLocation o = case o of+ AST_ObjArith a -> AST_ObjArith (delLocation a)+ AST_ObjTest a b c d e _ -> + AST_ObjTest (delLocation a) b (delLocation c) d (delLocation e) LocationUnknown+ AST_ObjRuleFunc a -> AST_ObjRuleFunc (delLocation a)++instance PPrintable o => PPrintable (AST_ObjTest o) where+ pPrint o = case o of+ AST_ObjArith a -> pPrint a+ AST_ObjTest a b c d e _ -> pWrapIndent $+ [ pPrint a+ , pPrintComWith (\ () -> pString " ? ") b, pPrint c+ , pPrintComWith (\ () -> pString " : ") d, pPrint e+ ]+ AST_ObjRuleFunc o -> pPrint o++instance PrecedeWithSpace (AST_ObjTest o) where+ precedeWithSpace o = case o of+ AST_ObjArith o -> precedeWithSpace o+ AST_ObjTest o _ _ _ _ _ -> precedeWithSpace o+ AST_ObjRuleFunc{} -> True++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_ObjTest o) where+ randO = countNode $ runRandChoice+ randChoice = randChoiceList $+ [ AST_ObjArith <$> randO+ , let p = pure (Com ()) in+ recurse $ scramble $ return AST_ObjTest <*> randO <*> p <*> randO <*> p <*> randO <*> no+ , AST_ObjRuleFunc <$> randO+ ]+ defaultO = AST_ObjArith <$> defaultO+ defaultChoice = randChoiceList [defaultO]++instance Intermediate (ObjTestExpr o) (AST_ObjTest o) where+ toInterm o = case o of+ AST_ObjArith o -> ObjArithExpr <$> ti o+ AST_ObjTest a _ b _ c loc -> [ObjTestExpr] <*> ti a <*> ti b <*> ti c <*> [loc]+ AST_ObjRuleFunc a -> [ObjRuleFuncExpr] <*> ti a+ fromInterm o = case o of+ ObjArithExpr o -> AST_ObjArith <$> fi o+ ObjTestExpr a b c loc ->+ [AST_ObjTest] <*> fi a <*> [Com ()] <*> fi b <*> [Com ()] <*> fi c <*> [loc]+ ObjRuleFuncExpr a -> AST_ObjRuleFunc <$> fi a++----------------------------------------------------------------------------------------------------++data RefPfxOp = REF | DEREF deriving (Eq, Ord, Typeable, Enum, Ix, Bounded, Show, Read)++instance NFData RefPfxOp where { rnf a = seq a () }++instance UStrType RefPfxOp where+ toUStr op = ustr $ case op of+ REF -> "$"+ DEREF -> "@"+ maybeFromUStr str = case uchars str of+ "$" -> Just REF+ "@" -> Just DEREF+ _ -> Nothing+ fromUStr str = maybe (error (show str++" is not a prefix opretor")) id (maybeFromUStr str)++instance PPrintable RefPfxOp where { pPrint = pUStr . toUStr }++instance HasRandGen RefPfxOp where+ randO = fmap toEnum (nextInt (1+fromEnum (maxBound::RefPfxOp)))+ defaultO = randO++----------------------------------------------------------------------------------------------------++data UpdateOp+ = UCONST | UADD | USUB | UMULT | UDIV | UMOD | UPOW | UORB | UANDB | UXORB | USHL | USHR+ deriving (Eq, Ord, Typeable, Enum, Ix, Bounded, Show, Read)+instance NFData UpdateOp where { rnf a = seq a () }++allUpdateOpStrs :: String+allUpdateOpStrs = " = += -= *= /= %= **= |= &= ^= <<= >>= "++instance UStrType UpdateOp where+ toUStr a = ustr $ case a of+ UCONST -> "="+ UADD -> "+="+ USUB -> "-="+ UMULT -> "*="+ UDIV -> "/="+ UMOD -> "%="+ UPOW -> "**="+ UORB -> "|="+ UANDB -> "&="+ UXORB -> "^="+ USHL -> "<<="+ USHR -> ">>="+ maybeFromUStr str = case uchars str of+ "=" -> Just UCONST+ "+=" -> Just UADD + "-=" -> Just USUB + "*=" -> Just UMULT + "/=" -> Just UDIV + "%=" -> Just UMOD + "**=" -> Just UPOW+ "|=" -> Just UORB + "&=" -> Just UANDB + "^=" -> Just UXORB + "<<=" -> Just USHL + ">>=" -> Just USHR + _ -> Nothing+ fromUStr str =+ maybe (error (show str++" is not an assignment/update operator")) id (maybeFromUStr str)++instance PPrintable UpdateOp where { pPrint op = pString (' ':uchars op++" ") }++-- binary 0x8D 0x9D UpdateOp-->InfixOp+instance B.Binary UpdateOp mtab where+ put o = B.putWord8 $ case o of+ UCONST -> 0x8D+ UADD -> 0x93+ USUB -> 0x94+ UMULT -> 0x95+ UDIV -> 0x96+ UMOD -> 0x97+ UPOW -> 0x98+ UORB -> 0x99+ UANDB -> 0x9A+ UXORB -> 0x9B+ USHL -> 0x9C+ USHR -> 0x9D+ get = B.word8PrefixTable <|> fail "expecting UpdateOp"++instance B.HasPrefixTable UpdateOp B.Byte mtab where+ prefixTable = B.mkPrefixTableWord8 "UpdateOp" 0x8D 0x9D $ let {r=return;z=mzero} in+ [ r UCONST -- 0x8D+ , z, z, z, z, z -- 0x8E,0x8F,0x90,0x91,0x92+ , r UADD, r USUB, r UMULT, r UDIV, r UMOD, r UPOW, r UORB -- 0x93,0x94,0x95,0x96,0x97,0x98,0x99+ , r UANDB, r UXORB, r USHL, r USHR -- 0x9A,0x9B,0x9C,0x9D+ ]++instance HasRandGen UpdateOp where+ randO = fmap toEnum (nextInt (1+fromEnum (maxBound::UpdateOp)))+ defaultO = randO++----------------------------------------------------------------------------------------------------++data ArithExpr o+ = ObjectExpr (ObjectExpr o)+ | ArithExpr (ArithExpr o) InfixOp (ArithExpr o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (ArithExpr o) where+ rnf (ObjectExpr a ) = deepseq a ()+ rnf (ArithExpr a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasNullValue (ArithExpr o) where+ nullValue = ObjectExpr nullValue+ testNull (ObjectExpr a) = testNull a+ testNull _ = False++instance HasLocation (ArithExpr o) where+ getLocation o = case o of+ ObjectExpr o -> getLocation o+ ArithExpr _ _ _ o -> o+ setLocation o loc = case o of+ ObjectExpr a -> ObjectExpr (setLocation a loc)+ ArithExpr a b c _ -> ArithExpr a b c loc+ delLocation o = case o of+ ObjectExpr a -> ObjectExpr (delLocation a)+ ArithExpr a b c _ -> ArithExpr (delLocation a) b (delLocation c) LocationUnknown++instance PPrintable o => PPrintable (ArithExpr o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++data AST_Arith o+ = AST_Object (AST_Object o)+ | AST_Arith (AST_Arith o) (Com InfixOp) (AST_Arith o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_Arith o) where+ rnf (AST_Object a ) = deepseq a ()+ rnf (AST_Arith a b c d ) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasNullValue (AST_Arith o) where+ nullValue = AST_Object nullValue+ testNull (AST_Object a) = testNull a+ testNull _ = False++instance HasLocation (AST_Arith o) where+ getLocation o = case o of+ AST_Object o -> getLocation o+ AST_Arith _ _ _ o -> o+ setLocation o loc = case o of+ AST_Object a -> AST_Object (setLocation a loc)+ AST_Arith a b c _ -> AST_Arith a b c loc+ delLocation o = case o of+ AST_Object a -> AST_Object (delLocation a)+ AST_Arith a b c _ -> AST_Arith (delLocation a) b (delLocation c) LocationUnknown++instance PPrintable o => PPrintable (AST_Arith o) where+ pPrint o = case o of+ AST_Object o -> pPrint o+ AST_Arith objXp1 comAriOp objXp2 _ -> pWrapIndent [pPrint objXp1, pPrint comAriOp, pPrint objXp2]++instance PrecedeWithSpace (AST_Arith o) where+ precedeWithSpace o = case o of+ AST_Object o -> precedeWithSpace o+ AST_Arith o _ _ _ -> precedeWithSpace o++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_Arith o) where+ randO = countNode $ runRandChoice+ defaultChoice = randChoiceList $+ [ AST_Object <$> defaultO+ , return AST_Arith <*> (AST_Object <$> defaultO) <*> defaultO <*> (AST_Object <$> defaultO) <*> no+ ]+ defaultO = runDefaultChoice+ randChoice = randChoiceList $+ [ AST_Object <$> randO+ , do left <- AST_Object <$> randO+ x <- getCurrentDepth+ ops <- randListOf 0 (max 1 (4-x)) $+ pure (,) <*> randInfixOp <*> (AST_Object <$> randO)+ return $ foldPrec left ops+ ] where+ randInfixOp :: RandO (Com InfixOp, Int, Bool)+ randInfixOp = do+ (op, prec, assoc) <- runRandChoiceOf opGroups+ op <- randComWith (return op)+ return (op, prec, assoc)+ left op = (True , op)+ right op = (False, op)+ opGroups :: RandChoice (InfixOp, Int, Bool)+ opGroups = randChoiceList $ map return $ do+ (precedence, (associativity, operators)) <- zip [1..] $ concat $+ [ map right [[OR], [AND], [EQUL, NEQUL]]+ , map left $+ [ [GTN, LTN, GTEQ, LTEQ], [SHL, SHR]+ , [ORB], [XORB], [ANDB]+ , [ADD, SUB], [MULT, DIV, MOD]+ ]+ , map right [[POW], [ARROW]]+ ]+ operator <- operators+ return (operator, precedence, associativity)+ bind left op right = AST_Arith left op right LocationUnknown+ foldPrec left ops = case ops of+ [] -> left+ ((op, prec, _), right):ops -> case scanRight prec right ops of+ (right, ops) -> foldPrec (bind left op right) ops+ scanRight prevPrec left ops = case ops of+ [] -> (left, [])+ ((op, prec, assoc), right):next -> + if prevPrec<prec || (prevPrec==prec && not assoc)+ then case scanRight prec right next of+ (right, next) -> scanRight prevPrec (bind left op right) next+ else (left, ops)++instance Intermediate (ArithExpr o) (AST_Arith o) where+ toInterm o = case o of+ AST_Object a -> ObjectExpr <$> ti a+ AST_Arith a b c loc -> [ArithExpr ] <*> ti a <*> uc b <*> ti c <*> [loc]+ fromInterm o = case o of+ ObjectExpr a -> AST_Object <$> fi a+ ArithExpr a b c loc -> [AST_Arith ] <*> fi a <*> nc b <*> fi c <*> [loc]++----------------------------------------------------------------------------------------------------++data ObjectExpr o+ = VoidExpr+ | ObjLiteralExpr (LiteralExpr o)+ | ObjSingleExpr (RefPrefixExpr o)+ | ArithPfxExpr ArithPfxOp (ObjectExpr o) Location+ | InitExpr DotLabelExpr (OptObjListExpr o) (ObjListExpr o) Location+ | StructExpr Name (OptObjListExpr o) Location+ | MetaEvalExpr (CodeBlock o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (ObjectExpr o) where+ rnf VoidExpr = ()+ rnf (ObjLiteralExpr a ) = deepseq a ()+ rnf (ObjSingleExpr a ) = deepseq a $! ()+ rnf (ArithPfxExpr a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (InitExpr a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (StructExpr a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (MetaEvalExpr a b ) = deepseq a $! deepseq b ()++instance HasNullValue (ObjectExpr o) where+ nullValue = VoidExpr+ testNull VoidExpr = True+ testNull _ = False++instance HasLocation (ObjectExpr o) where+ getLocation o = case o of+ VoidExpr -> LocationUnknown+ ObjLiteralExpr o -> getLocation o+ ObjSingleExpr o -> getLocation o+ ArithPfxExpr _ _ o -> o+ InitExpr _ _ _ o -> o+ StructExpr _ _ o -> o+ MetaEvalExpr _ o -> o+ setLocation o loc = case o of+ VoidExpr -> VoidExpr+ ObjLiteralExpr a -> ObjLiteralExpr (setLocation a loc)+ ObjSingleExpr a -> ObjSingleExpr (setLocation a loc)+ ArithPfxExpr a b _ -> ArithPfxExpr a b loc+ InitExpr a b c _ -> InitExpr a b c loc+ StructExpr a b _ -> StructExpr a b loc+ MetaEvalExpr a _ -> MetaEvalExpr a loc+ delLocation o = case o of+ VoidExpr -> VoidExpr+ ObjLiteralExpr a -> ObjLiteralExpr (fd a)+ ObjSingleExpr a -> ObjSingleExpr (fd a)+ ArithPfxExpr a b _ -> ArithPfxExpr a (fd b) lu+ InitExpr a b c _ -> InitExpr (fd a) (fd b) (fd c) lu+ StructExpr a b _ -> StructExpr (fd a) (fd b) lu+ MetaEvalExpr a _ -> MetaEvalExpr (fd a) lu+ where+ lu = LocationUnknown+ fd :: HasLocation a => a -> a+ fd = delLocation++instance PPrintable o => PPrintable (ObjectExpr o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++-- | Part of the Dao language abstract syntax tree: any expression that evaluates to an Object.+data AST_Object o+ = AST_Void -- ^ Not a language construct, but used where an object expression is optional.+ | AST_ObjLiteral (AST_Literal o)+ | AST_ObjSingle (AST_RefPrefix o)+ | AST_ArithPfx ArithPfxOp [Comment] (AST_Object o) Location+ | AST_Init AST_DotLabel (AST_OptObjList o) (AST_ObjList o) Location+ | AST_Struct Name (AST_OptObjList o) Location+ | AST_MetaEval (AST_CodeBlock o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_Object o) where+ rnf AST_Void = ()+ rnf (AST_ObjLiteral a ) = deepseq a ()+ rnf (AST_ObjSingle a ) = deepseq a ()+ rnf (AST_ArithPfx a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (AST_Init a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (AST_Struct a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (AST_MetaEval a b ) = deepseq a $! deepseq b ()++instance HasNullValue (AST_Object o) where+ nullValue = AST_Void+ testNull AST_Void = True+ testNull _ = False++instance HasLocation (AST_Object o) where+ getLocation o = case o of+ AST_Void -> LocationUnknown+ AST_ObjLiteral o -> getLocation o+ AST_ObjSingle o -> getLocation o+ AST_ArithPfx _ _ _ o -> o+ AST_Init _ _ _ o -> o+ AST_Struct _ _ o -> o+ AST_MetaEval _ o -> o+ setLocation o loc = case o of+ AST_Void -> AST_Void+ AST_ObjLiteral a -> AST_ObjLiteral (setLocation a loc)+ AST_ObjSingle a -> AST_ObjSingle (setLocation a loc)+ AST_ArithPfx a b c _ -> AST_ArithPfx a b c loc+ AST_Init a b c _ -> AST_Init a b c loc+ AST_Struct a b _ -> AST_Struct a b loc+ AST_MetaEval a _ -> AST_MetaEval a loc+ delLocation o = case o of + AST_Void -> AST_Void+ AST_ObjLiteral a -> AST_ObjLiteral (delLocation a)+ AST_ObjSingle a -> AST_ObjSingle (delLocation a)+ AST_ArithPfx a b c _ -> AST_ArithPfx a b (delLocation c) LocationUnknown+ AST_Init a b c _ -> AST_Init (delLocation a) (delLocation b) (delLocation c) LocationUnknown+ AST_Struct a b _ -> AST_Struct a (delLocation b) LocationUnknown+ AST_MetaEval a _ -> AST_MetaEval (delLocation a) LocationUnknown++instance PPrintable o => PPrintable (AST_Object o) where+ pPrint expr = case expr of+ AST_Void -> return ()+ AST_ObjLiteral o -> pPrint o+ AST_ObjSingle o -> pPrint o+ AST_ArithPfx op coms objXp _ -> pWrapIndent $+ [pPrint op, pPrint coms, pPrint objXp]+ AST_Init ref objs elems _ ->+ pInline [pPrint ref, pPrintOptObjList "(" ", " ")" objs, pPrintObjList "{" ", " "}" elems]+ AST_Struct nm itms _ -> case itms of+ AST_OptObjList coms items -> do+ let name = pString $ '#' : uchars (toUStr nm)+ pPrint coms+ case items of+ Nothing -> name+ Just (AST_ObjList coms items _) -> do+ pPrint coms+ pList name "{" ", " "}" $ map pPrint items+ AST_MetaEval cObjXp _ -> pInline [pString "${", pPrint cObjXp, pString "}"]++instance PrecedeWithSpace (AST_Object o) where+ precedeWithSpace o = case o of+ AST_Void -> False+ AST_MetaEval{} -> False+ AST_ObjSingle o -> precedeWithSpace o+ _ -> True++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_Object o) where+ randO = countNode $ runRandChoice+ randChoice = randChoiceList $+ [ AST_ObjLiteral <$> randO+ , AST_ObjSingle <$> randO+ , pure AST_ArithPfx <*> randO <*> randO <*> randO <*> no+ , pure AST_Init <*> randO <*> randO <*> randO <*> no+ , pure AST_MetaEval <*> randO <*> no+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ AST_ObjLiteral <$> defaultO+ , AST_ObjSingle <$> defaultO+ , return AST_ArithPfx <*> defaultO <*> defaultO <*> (AST_ObjLiteral <$> defaultO) <*> no+ , return AST_Init <*> defaultO <*> defaultO <*> defaultO <*> no+ , return AST_Struct <*> defaultO <*> defaultO <*> no+ ]++instance (HasNullValue o, HasRandGen o) => HasRandGen [Com (AST_Object o)] where+ randO = depthLimitedInt 24 >>= \x -> countNode $ randList 1 x++instance Intermediate (ObjectExpr o) (AST_Object o) where+ toInterm ast = case ast of+ AST_Void -> [VoidExpr ]+ AST_ObjLiteral a -> ObjLiteralExpr <$> ti a+ AST_ObjSingle a -> [ObjSingleExpr ] <*> ti a+ AST_ArithPfx a _ c loc -> [ArithPfxExpr ] <*> [a] <*> ti c <*> [loc]+ AST_Init a b c loc -> [InitExpr ] <*> ti a <*> ti b <*> ti c <*> [loc]+ AST_Struct a b loc -> [StructExpr ] <*> [a] <*> ti b <*> [loc]+ AST_MetaEval a loc -> [MetaEvalExpr ] <*> ti a <*> [loc]+ fromInterm o = case o of+ VoidExpr -> [AST_Void ]+ ObjLiteralExpr a -> AST_ObjLiteral <$> fi a+ ObjSingleExpr a -> AST_ObjSingle <$> fi a+ ArithPfxExpr a b loc -> [AST_ArithPfx ] <*> [a] <*> [[]] <*> fi b <*> [loc]+ InitExpr a b c loc -> [AST_Init ] <*> fi a <*> fi b <*> fi c <*> [loc]+ StructExpr a b loc -> [AST_Struct ] <*> [a] <*> fi b <*> [loc]+ MetaEvalExpr a loc -> [AST_MetaEval ] <*> fi a <*> [loc]++----------------------------------------------------------------------------------------------------++-- | Functions and function parameters can specify optional type-checking expressions. This is a+-- data type that wraps a dao-typeable expression with type information.+data TyChkExpr a o+ = NotTypeChecked{tyChkItem::a}+ -- ^ no type information was specified for this item+ | TypeChecked {tyChkItem::a, tyChkExpr::ArithExpr o, tyChkLoc::Location}+ -- ^ type check information was specified and should be checked every time it is evaluated.+ | DisableCheck {tyChkItem::a, tyChkExpr::ArithExpr o, typChkResult::o, tyChkLoc::Location}+ -- ^ type check information was specified but has been disabled for efficiency reasons because+ -- we have verified that the item will always return a succesfull type-check.+ deriving (Eq, Ord, Typeable, Show)++checkedExpr :: TyChkExpr a o -> a+checkedExpr o = case o of+ NotTypeChecked o -> o+ TypeChecked o _ _ -> o+ DisableCheck o _ _ _ -> o++instance Functor (TyChkExpr a) where+ fmap _ (NotTypeChecked a ) = NotTypeChecked a+ fmap f (TypeChecked a b c ) = TypeChecked a (fmap f b) c+ fmap f (DisableCheck a b c d) = DisableCheck a (fmap f b) (f c) d++fmapCheckedValueExpr :: (a -> b) -> TyChkExpr a o -> TyChkExpr b o+fmapCheckedValueExpr f a = case a of+ NotTypeChecked a -> NotTypeChecked (f a)+ TypeChecked a b c -> TypeChecked (f a) b c+ DisableCheck a b c d -> DisableCheck (f a) b c d++instance (NFData o, NFData a) => NFData (TyChkExpr a o) where+ rnf (NotTypeChecked a ) = deepseq a ()+ rnf (TypeChecked a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (DisableCheck a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance (HasNullValue o, HasNullValue a) => HasNullValue (TyChkExpr a o) where+ nullValue = NotTypeChecked nullValue+ testNull (NotTypeChecked a) = testNull a+ testNull _ = False++instance HasLocation a => HasLocation (TyChkExpr a o) where+ getLocation a = case a of+ NotTypeChecked a -> getLocation a+ TypeChecked a _ loc -> getLocation a <> loc+ DisableCheck a _ _ loc -> getLocation a <> loc+ setLocation a loc = case a of+ NotTypeChecked a -> NotTypeChecked (setLocation a loc)+ TypeChecked a b _ -> TypeChecked a b loc+ DisableCheck a b c _ -> DisableCheck a b c loc+ delLocation a = case a of+ NotTypeChecked a -> NotTypeChecked (delLocation a)+ TypeChecked a b _ -> TypeChecked (delLocation a) (delLocation b) LocationUnknown+ DisableCheck a b c _ -> DisableCheck a b c LocationUnknown++instance (PPrintable o, PPrintable a) => PPrintable (TyChkExpr a o) where+ pPrint a = case a of+ NotTypeChecked a -> pPrint a+ TypeChecked a expr _ -> pInline [pPrint a, pString ": ", pPrint expr]+ DisableCheck a _ _ _ -> pInline [pPrint a]++----------------------------------------------------------------------------------------------------++-- | This node can be found in a few different syntactic structures. When a name or function or+-- expression is followed by a colon and some type checking information, this node is used for that+-- purpose.+data AST_TyChk a o+ = AST_NotChecked a+ | AST_Checked a (Com ()) (AST_Arith o) Location+ deriving (Eq, Ord, Typeable, Show)++checkedAST :: AST_TyChk a o -> a+checkedAST a = case a of { AST_NotChecked a -> a; AST_Checked a _ _ _ -> a; }++astTyChkDelLocWith :: (a -> a) -> AST_TyChk a o -> AST_TyChk a o+astTyChkDelLocWith del a = case a of+ AST_NotChecked a -> AST_NotChecked (del a)+ AST_Checked a b c _ -> AST_Checked (del a) b (delLocation c) LocationUnknown++instance Functor (AST_TyChk o) where+ fmap _ (AST_NotChecked a ) = AST_NotChecked a+ fmap f (AST_Checked a b c d) = AST_Checked a b (fmap f c) d++fmapCheckedValueAST :: (a -> b) -> AST_TyChk a o -> AST_TyChk b o+fmapCheckedValueAST f a = case a of+ AST_NotChecked a -> AST_NotChecked (f a)+ AST_Checked a b c d -> AST_Checked (f a) b c d++instance (NFData o, NFData a) => NFData (AST_TyChk a o) where+ rnf (AST_NotChecked a) = deepseq a ()+ rnf (AST_Checked a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance (HasNullValue o, HasNullValue a) => HasNullValue (AST_TyChk a o) where+ nullValue = AST_NotChecked nullValue+ testNull (AST_NotChecked a ) = testNull a+ testNull (AST_Checked _ _ a _) = testNull a++instance (PPrintable o, PPrintable a) => PPrintable (AST_TyChk a o) where+ pPrint a = case a of+ AST_NotChecked a -> pPrint a+ AST_Checked a coms expr _ -> pInline $+ [ pPrint a+ , pPrintComWith (\ () -> pString "::") coms+ , pPrint expr+ ]++instance HasLocation a => HasLocation (AST_TyChk a o) where+ getLocation a = case a of+ AST_NotChecked a -> getLocation a+ AST_Checked a _ _ loc -> getLocation a <> loc+ setLocation a loc = case a of+ AST_NotChecked a -> AST_NotChecked (setLocation a loc)+ AST_Checked a b c _ -> AST_Checked a b c loc+ delLocation = astTyChkDelLocWith delLocation++instance (HasRandGen o, HasRandGen a) => HasRandGen (AST_TyChk a o) where+ randO = countNode $ AST_NotChecked <$> randO+ --randChoice = randChoiceList [AST_NotChecked <$> randO, return AST_Checked <*> randO <*> randO <*> randO <*> no]+ defaultO = AST_NotChecked <$> defaultO++tyChkToInterm :: (b -> [a]) -> AST_TyChk b o -> [TyChkExpr a o]+tyChkToInterm ti a = case a of+ AST_NotChecked a -> NotTypeChecked <$> ti a+ AST_Checked a _ b loc -> [TypeChecked] <*> ti a <*> toInterm b <*> [loc]++tyChkFromInterm :: (a -> [b]) -> TyChkExpr a o -> [AST_TyChk b o]+tyChkFromInterm fi a = case a of+ NotTypeChecked a -> AST_NotChecked <$> fi a+ TypeChecked a b loc -> [AST_Checked] <*> fi a <*> [Com ()] <*> fromInterm b <*> [loc]+ DisableCheck a b _ loc -> [AST_Checked] <*> fi a <*> [Com ()] <*> fromInterm b <*> [loc]++----------------------------------------------------------------------------------------------------++-- | A list of function parameters (arguments) to a function in an object representing a function+-- expression.+data ParamListExpr o = ParamListExpr (TyChkExpr [ParamExpr o] o) Location+ deriving (Eq, Ord, Typeable, Show)++instance Functor ParamListExpr where+ fmap f (ParamListExpr a loc) =+ ParamListExpr (fmapCheckedValueExpr (fmap (fmap f)) $ fmap f a) loc++instance NFData o => NFData (ParamListExpr o) where { rnf (ParamListExpr a b) = deepseq a $! deepseq b () }++instance HasNullValue (ParamListExpr o) where+ nullValue = ParamListExpr (NotTypeChecked []) LocationUnknown+ testNull (ParamListExpr (NotTypeChecked []) _) = True+ testNull _ = False++instance HasLocation (ParamListExpr o) where+ getLocation (ParamListExpr _ loc) = loc+ setLocation (ParamListExpr a _ ) loc = ParamListExpr a loc+ delLocation (ParamListExpr a _ ) = ParamListExpr a LocationUnknown++instance PPrintable o => PPrintable (ParamListExpr o) where { pPrint (ParamListExpr lst _) = pPrint lst }++getTypeCheckList :: ParamListExpr o -> [ParamExpr o]+getTypeCheckList (ParamListExpr tychk _) = tyChkItem tychk ++----------------------------------------------------------------------------------------------------++-- | 'ParamExpr' is a part of the Dao language semantics, and is also used in the the 'CallableCode'+-- data type when evaluating parameters to be passed to the callable code function execution. The+-- boolean parameter here indicates whether or not the parameter should be passed by reference.+data ParamExpr o = ParamExpr Bool (TyChkExpr Name o) Location deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (ParamExpr o) where+ rnf (ParamExpr a b c) = deepseq a $! deepseq b $! deepseq c ()++instance HasLocation (ParamExpr o) where+ getLocation (ParamExpr _ _ loc) = loc+ setLocation (ParamExpr a b _ ) loc = ParamExpr a b loc+ delLocation (ParamExpr a b _ ) = ParamExpr a b LocationUnknown++instance PPrintable o => PPrintable (ParamExpr o) where+ pPrint (ParamExpr byRef tychk _) = when byRef (pString "$") >> pPrint tychk++instance PPrintable o => PPrintable [ParamExpr o] where { pPrint lst = pList_ "(" ", " ")" (fmap pPrint lst) }++----------------------------------------------------------------------------------------------------++data AST_Param o+ = AST_NoParams+ | AST_Param (Maybe [Comment]) (AST_TyChk Name o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_Param o) where+ rnf AST_NoParams = ()+ rnf (AST_Param a b c) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue (AST_Param o) where+ nullValue = AST_NoParams+ testNull AST_NoParams = True+ testNull _ = False++instance HasLocation (AST_Param o) where+ getLocation a = case a of+ AST_NoParams -> LocationUnknown+ AST_Param _ _ loc -> loc+ setLocation a loc = case a of+ AST_NoParams -> AST_NoParams+ AST_Param a b _ -> AST_Param a b loc+ delLocation a = case a of+ AST_NoParams -> AST_NoParams+ AST_Param a b _ -> AST_Param a (astTyChkDelLocWith delLocation b) LocationUnknown++instance PPrintable o => PPrintable (AST_Param o) where+ pPrint o = case o of+ AST_NoParams -> return ()+ AST_Param mcoms tychk _ -> pInline $+ [ maybe (return ()) (\coms -> pString "$" >> pPrint coms) mcoms+ , pPrint tychk+ ]++instance PPrintable o => PPrintable [Com (AST_Param o)] where+ pPrint lst = pList_ "(" ", " ")" (fmap pPrint lst)++----------------------------------------------------------------------------------------------------++instance HasRandGen o => HasRandGen (AST_Param o) where+ randO = countNode $ return AST_Param <*> randO <*> randO <*> no+ defaultO = return AST_Param <*> defaultO <*> defaultO <*> no++instance HasRandGen o => HasRandGen [Com (AST_Param o)] where+ randO = recurse $ depthLimitedInt 8 >>= \x -> randListOf 0 x scrambO+ defaultO = defaultList 0 1++instance Intermediate (ParamExpr o) (AST_Param o) where+ toInterm a = case a of+ AST_NoParams -> []+ AST_Param a b loc ->+ [ParamExpr] <*> [maybe False (const True) a] <*> tyChkToInterm return b <*> [loc]+ fromInterm o = case o of+ ParamExpr a b loc ->+ [AST_Param] <*> [if a then Just [] else Nothing] <*> tyChkFromInterm return b <*> [loc]++instance Intermediate [ParamExpr o] [Com (AST_Param o)] where+ toInterm ax = [ax >>= toInterm . unComment]+ fromInterm ax = [ax >>= fmap Com . fromInterm]++----------------------------------------------------------------------------------------------------++data AST_ParamList o+ = AST_ParamList (AST_TyChk [Com (AST_Param o)] o) Location+ deriving (Eq, Ord, Typeable, Show)++instance Functor AST_ParamList where+ fmap f (AST_ParamList a loc) =+ AST_ParamList (fmapCheckedValueAST (fmap (fmap (fmap f))) $ fmap f a) loc++instance NFData o => NFData (AST_ParamList o) where { rnf (AST_ParamList a b) = deepseq a $! deepseq b () }++instance HasNullValue o => HasNullValue (AST_ParamList o) where+ nullValue = AST_ParamList nullValue LocationUnknown+ testNull (AST_ParamList a _) = testNull a++instance HasLocation (AST_ParamList o) where+ getLocation (AST_ParamList _ loc) = loc+ setLocation (AST_ParamList a _ ) loc = AST_ParamList a loc+ delLocation (AST_ParamList a _ ) = AST_ParamList (astTyChkDelLocWith (fmap delLocation) a) LocationUnknown++instance PPrintable o => PPrintable (AST_ParamList o) where+ pPrint (AST_ParamList lst _) = pInline [pPrint lst]++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_ParamList o) where+ randO = countNode $ return AST_ParamList <*> randO <*> no+ defaultO = return $ AST_ParamList nullValue LocationUnknown++instance Intermediate (ParamListExpr o) (AST_ParamList o) where+ toInterm (AST_ParamList ox loc) = [ParamListExpr] <*> tyChkToInterm toInterm ox <*> [loc]+ fromInterm (ParamListExpr ox loc) = [AST_ParamList] <*> tyChkFromInterm fromInterm ox <*> [loc]++----------------------------------------------------------------------------------------------------++data RuleFuncExpr o+ = LambdaExpr (ParamListExpr o) (CodeBlock o) Location+ | FuncExpr Name (ParamListExpr o) (CodeBlock o) Location+ | RuleExpr (RuleHeadExpr o) (CodeBlock o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (RuleFuncExpr o) where+ rnf (LambdaExpr a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (FuncExpr a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (RuleExpr a b c ) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue (RuleFuncExpr o) where+ nullValue = LambdaExpr nullValue nullValue LocationUnknown+ testNull (RuleExpr a b _) = testNull a && testNull b+ testNull _ = False++instance HasLocation (RuleFuncExpr o) where+ getLocation o = case o of+ LambdaExpr _ _ o -> o+ FuncExpr _ _ _ o -> o+ RuleExpr _ _ o -> o+ setLocation o loc = case o of+ LambdaExpr a b _ -> LambdaExpr a b loc+ FuncExpr a b c _ -> FuncExpr a b c loc+ RuleExpr a b _ -> RuleExpr a b loc+ delLocation o = case o of+ LambdaExpr a b _ -> LambdaExpr (delLocation a) (delLocation b) LocationUnknown+ FuncExpr a b c _ -> FuncExpr a (delLocation b) (delLocation c) LocationUnknown+ RuleExpr a b _ -> RuleExpr a (delLocation b) LocationUnknown++instance PPrintable o => PPrintable (RuleFuncExpr o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++data AST_RuleFunc o+ = AST_Lambda (Com (AST_ParamList o)) (AST_CodeBlock o) Location+ | AST_Func [Comment] Name (Com (AST_ParamList o)) (AST_CodeBlock o) Location+ | AST_Rule (Com (AST_RuleHeader o)) (AST_CodeBlock o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_RuleFunc o) where+ rnf (AST_Lambda a b c ) = deepseq a $! deepseq b $! deepseq c () + rnf (AST_Func a b c d e) = deepseq a $! deepseq b $! deepseq c $! deepseq d $! deepseq e ()+ rnf (AST_Rule a b c ) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue o => HasNullValue (AST_RuleFunc o) where+ nullValue = AST_Lambda nullValue nullValue LocationUnknown+ testNull (AST_Lambda a b _) = testNull a && testNull b+ testNull _ = False++instance HasLocation (AST_RuleFunc o) where+ getLocation o = case o of+ AST_Lambda _ _ o -> o+ AST_Func _ _ _ _ o -> o+ AST_Rule _ _ o -> o+ setLocation o loc = case o of+ AST_Lambda a b _ -> AST_Lambda a b loc+ AST_Func a b c d _ -> AST_Func a b c d loc+ AST_Rule a b _ -> AST_Rule a b loc+ delLocation o = case o of + AST_Lambda a b _ -> AST_Lambda (delLocation a) (delLocation b) LocationUnknown+ AST_Func a b c d _ -> AST_Func a b (delLocation c) (delLocation d) LocationUnknown+ AST_Rule a b _ -> AST_Rule (delLocation a) (delLocation b) LocationUnknown++instance PPrintable o => PPrintable (AST_RuleFunc o) where+ pPrint expr = case expr of+ AST_Lambda ccNmx xcObjXp _ ->+ pPrintSubBlock (pInline [pString "function", pPrintComWith pPrint ccNmx]) xcObjXp+ AST_Func co nm ccNmx xcObjXp _ ->+ pClosure (pInline [pString "function ", pPrint co, pPrint nm, pPrint ccNmx]) "{" "}" [pPrint xcObjXp]+ AST_Rule ccNmx xcObjXp _ -> pClosure (pPrint ccNmx) "{" "}" [pPrint xcObjXp]++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_RuleFunc o) where+ randO = recurse $ countNode $ runRandChoice+ randChoice = randChoiceList $+ [ scramble $ return AST_Lambda <*> randO <*> randO <*> no+ , scramble $ return AST_Func <*> randO <*> randO <*> randO <*> randO <*> no+ , scramble $ return AST_Rule <*> randO <*> randO <*> no+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ scramble $ return AST_Lambda <*> defaultO <*> defaultO <*> no+ , scramble $ return AST_Func <*> defaultO <*> defaultO <*> defaultO <*> defaultO <*> no+ , scramble $ return AST_Rule <*> defaultO <*> defaultO <*> no+ ]++instance Intermediate (RuleFuncExpr o) (AST_RuleFunc o) where+ toInterm ast = case ast of+ AST_Lambda a b loc -> [LambdaExpr] <*> uc0 a <*> ti b <*> [loc]+ AST_Func _ a b c loc -> [FuncExpr] <*> [a] <*> uc0 b <*> ti c <*> [loc]+ AST_Rule a b loc -> [RuleExpr] <*> uc0 a <*> ti b <*> [loc]+ fromInterm o = case o of+ LambdaExpr a b loc -> [AST_Lambda] <*> nc0 a <*> fi b <*> [loc]+ FuncExpr a b c loc -> [AST_Func] <*> [[]] <*> [a] <*> nc0 b <*> fi c <*> [loc]+ RuleExpr a b loc -> [AST_Rule] <*> nc0 a <*> fi b <*> [loc]++----------------------------------------------------------------------------------------------------++data RuleHeadExpr o+ = RuleStringExpr UStr Location+ | RuleHeadExpr [AssignExpr o] Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance HasNullValue (RuleHeadExpr o) where+ nullValue = RuleStringExpr nil LocationUnknown+ testNull (RuleStringExpr a _) = a==nil+ testNull _ = False++instance HasLocation (RuleHeadExpr o) where+ getLocation o = case o of+ RuleStringExpr _ o -> o+ RuleHeadExpr _ o -> o+ setLocation o loc = case o of+ RuleStringExpr o _ -> RuleStringExpr o loc+ RuleHeadExpr o _ -> RuleHeadExpr o loc+ delLocation o = case o of+ RuleStringExpr o _ -> RuleStringExpr o LocationUnknown+ RuleHeadExpr o _ -> RuleHeadExpr (fmap delLocation o) LocationUnknown++instance NFData o => NFData (RuleHeadExpr o) where+ rnf (RuleStringExpr a b) = deepseq a $! deepseq b ()+ rnf (RuleHeadExpr a b) = deepseq a $! deepseq b ()++----------------------------------------------------------------------------------------------------++data AST_RuleHeader o+ = AST_NullRules [Comment] Location+ | AST_RuleString (Com UStr) Location+ | AST_RuleHeader [Com (AST_Assign o)] Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_RuleHeader o) where+ rnf (AST_NullRules a b) = deepseq a $! deepseq b ()+ rnf (AST_RuleString a b) = deepseq a $! deepseq b ()+ rnf (AST_RuleHeader a b) = deepseq a $! deepseq b ()++instance HasNullValue (AST_RuleHeader o) where+ nullValue = AST_NullRules [] LocationUnknown+ testNull (AST_NullRules _ _) = True+ testNull _ = False++instance HasLocation (AST_RuleHeader o) where+ getLocation o = case o of+ AST_NullRules _ o -> o+ AST_RuleString _ o -> o+ AST_RuleHeader _ o -> o+ setLocation o loc = case o of+ AST_NullRules a _ -> AST_NullRules a loc+ AST_RuleString a _ -> AST_RuleString a loc+ AST_RuleHeader a _ -> AST_RuleHeader a loc+ delLocation o = case o of+ AST_NullRules a _ -> AST_NullRules a LocationUnknown+ AST_RuleString a _ -> AST_RuleString a LocationUnknown+ AST_RuleHeader a _ -> AST_RuleHeader (fmap delLocation a) LocationUnknown++instance PPrintable o => PPrintable (AST_RuleHeader o) where+ pPrint o = case o of+ AST_NullRules coms _ -> pInline [pString "rule(", pPrint coms, pString ")"]+ AST_RuleString r _ -> pInline [pString "rule ", pPrintComWith pShow r, pString " "]+ AST_RuleHeader ruls _ -> pList (pString "rule") "(" ", " ")" (fmap pPrint ruls)++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_RuleHeader o) where+ randO = countNode $ runRandChoice+ randChoice = randChoiceList $+ [ return AST_RuleHeader <*> randList 0 3 <*> no+ , return AST_RuleString <*> randO <*> no+ , return AST_NullRules <*> scrambO <*> no+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ return AST_NullRules <*> defaultO <*> no+ , return AST_RuleString <*> defaultO <*> no+ ]++instance Intermediate (RuleHeadExpr o) (AST_RuleHeader o) where+ toInterm o = case o of+ AST_NullRules _ loc -> [RuleHeadExpr [] loc]+ AST_RuleString o loc -> [RuleStringExpr (unComment o) loc]+ AST_RuleHeader o loc -> [RuleHeadExpr] <*> [o>>=uc0] <*> [loc]+ fromInterm o = case o of+ RuleHeadExpr [] loc -> [AST_NullRules [] loc]+ RuleStringExpr o loc -> [AST_RuleString (Com o) loc]+ RuleHeadExpr o loc -> [AST_RuleHeader] <*> [o>>=nc0] <*> [loc]++----------------------------------------------------------------------------------------------------++-- | Defined such that the instantiation of 'CodeBlock' into the 'Executable' class executes each+-- 'ScriptExpr' in the 'CodeBlock', one after the other. Execution does not+-- occur within a 'execNested' because many other expressions which execute 'CodeBlock's,+-- especially 'TryCatch' expressions and 'ForLoop's need to be able to choose+-- when the stack is pushed so they can define temporary local variables.+newtype CodeBlock o = CodeBlock { codeBlock :: [ScriptExpr o] }+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (CodeBlock o) where { rnf (CodeBlock a) = deepseq a () }++instance Monoid (CodeBlock o) where+ mempty = CodeBlock []+ mappend a b = CodeBlock (mappend (codeBlock a) (codeBlock b))++instance HasNullValue (CodeBlock o) where+ nullValue = mempty+ testNull (CodeBlock []) = True+ testNull _ = False++instance HasLocation (CodeBlock o) where+ getLocation o = case codeBlock o of+ [] -> LocationUnknown+ [o] -> getLocation o+ o:ox -> mappend (getLocation o) (getLocation (foldl (flip const) o ox))+ setLocation o _ = o+ delLocation o = CodeBlock (fmap delLocation (codeBlock o))++instance PPrintable o => PPrintable (CodeBlock o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++-- | This node in the AST typically represents the list of 'AST_Script' expressions found between+-- curly-brackets in expressions like "if" and "else" statement, "for" statements and "while"+-- statements, "with" satements, "try" and "catch" statements and function declrataions.+newtype AST_CodeBlock o = AST_CodeBlock{ getAST_CodeBlock :: [AST_Script o] }+ deriving (Eq, Ord, Typeable, Show, Functor)+ -- A code block is never standing on it's own, it is always part of a larger expression, so there+ -- is no 'Dao.Token.Location' parameter for 'AST_CodeBlock'.++instance Monoid (AST_CodeBlock o) where+ mempty = AST_CodeBlock []+ mappend a b = AST_CodeBlock (mappend (getAST_CodeBlock a) (getAST_CodeBlock b))++instance NFData o => NFData (AST_CodeBlock o) where { rnf (AST_CodeBlock a) = deepseq a () }++instance HasNullValue (AST_CodeBlock o) where+ nullValue = AST_CodeBlock []+ testNull (AST_CodeBlock a) = null a++instance HasLocation (AST_CodeBlock o) where + getLocation o = case getAST_CodeBlock o of+ [] -> LocationUnknown+ [o] -> getLocation o+ o:ox -> mappend (getLocation o) (getLocation (foldl (flip const) o ox))+ setLocation o _ = o+ delLocation o = AST_CodeBlock (fmap delLocation (getAST_CodeBlock o))++-- 'pPrintComWith' wasn't good enough for this, because the comments might occur after the header+-- but before the opening bracket.+pPrintComCodeBlock :: PPrintable o => PPrint -> Com (AST_CodeBlock o) -> PPrint+pPrintComCodeBlock header c = case c of+ Com c -> run [] c []+ ComBefore bx c -> run bx c []+ ComAfter c ax -> run [] c ax+ ComAround bx c ax -> run bx c ax+ where+ run :: PPrintable o => [Comment] -> (AST_CodeBlock o) -> [Comment] -> PPrint+ run before cx after = case getAST_CodeBlock cx of+ [] -> header >> pInline (map pPrint before) >> pString " {}" >> pInline (map pPrint after)+ cx -> do+ pClosure (header >> pInline (map pPrint before)) " { " " }" (map (pGroup True . pPrint) cx)+ pInline (map pPrint after)++pPrintSubBlock :: PPrintable o => PPrint -> (AST_CodeBlock o) -> PPrint+pPrintSubBlock header px = pPrintComCodeBlock header (Com px)++instance PPrintable o => PPrintable (AST_CodeBlock o) where { pPrint o = mapM_ pPrint (getAST_CodeBlock o) }++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_CodeBlock o) where+ randO = countNode $ AST_CodeBlock . concat <$> sequence [return <$> scrambO, depthLimitedInt 16 >>= \x -> randList 0 x]+ defaultO = return $ AST_CodeBlock []++instance Intermediate (CodeBlock o) (AST_CodeBlock o) where+ toInterm (AST_CodeBlock ast) = [CodeBlock $ ast >>= toInterm ]+ fromInterm (CodeBlock obj) = [AST_CodeBlock $ obj >>= fromInterm]++----------------------------------------------------------------------------------------------------++data IfExpr o = IfExpr (ParenExpr o) (CodeBlock o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (IfExpr o) where { rnf (IfExpr a b c) = deepseq a $! deepseq b $! deepseq c () }++instance HasNullValue (IfExpr o) where+ nullValue = IfExpr nullValue nullValue LocationUnknown+ testNull (IfExpr a b _) = testNull a && testNull b++instance HasLocation (IfExpr o) where+ getLocation (IfExpr _ _ loc) = loc+ setLocation (IfExpr a b _ ) loc = IfExpr a b loc+ delLocation (IfExpr a b _ ) = IfExpr (delLocation a) (delLocation b) LocationUnknown++----------------------------------------------------------------------------------------------------++data AST_If o = AST_If (Com (AST_Paren o)) (AST_CodeBlock o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_If o) where { rnf (AST_If a b c) = deepseq a $! deepseq b $! deepseq c () }++instance HasLocation (AST_If o) where+ getLocation (AST_If _ _ loc) = loc+ setLocation (AST_If a b _ ) loc = AST_If a b loc+ delLocation (AST_If a b _ ) = AST_If (delLocation a) (delLocation b) LocationUnknown++instance HasNullValue (AST_If o) where+ nullValue = AST_If nullValue nullValue LocationUnknown+ testNull (AST_If a b _) = testNull a && testNull b++instance PPrintable o => PPrintable (AST_If o) where+ pPrint (AST_If ifn thn _) =+ pClosure (pString "if" >> pPrint ifn) "{" "}" [pPrint thn]++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_If o) where+ randO = countNode $ return AST_If <*> randO <*> randO <*> no+ defaultO = return AST_If <*> defaultO <*> defaultO <*> no++instance Intermediate (IfExpr o) (AST_If o) where+ toInterm (AST_If a b loc) = [IfExpr] <*> uc0 a <*> ti b <*> [loc]+ fromInterm (IfExpr a b loc) = [AST_If] <*> nc0 a <*> fi b <*> [loc]++----------------------------------------------------------------------------------------------------++data ElseExpr o = ElseExpr (IfExpr o) Location deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (ElseExpr o) where { rnf (ElseExpr a b ) = deepseq a $! deepseq b $! () }++instance HasNullValue (ElseExpr o) where+ nullValue = ElseExpr nullValue LocationUnknown+ testNull (ElseExpr a _) = testNull a++instance HasLocation (ElseExpr o) where+ getLocation (ElseExpr _ loc) = loc+ setLocation (ElseExpr a _ ) loc = ElseExpr a loc+ delLocation (ElseExpr a _ ) = ElseExpr (delLocation a) LocationUnknown++----------------------------------------------------------------------------------------------------++data AST_Else o = AST_Else (Com ()) (AST_If o) Location deriving (Eq, Ord, Typeable, Show, Functor)+ -- ^ @/**/ else /**/ if /**/ obj /**/ {}@++instance NFData o => NFData (AST_Else o) where { rnf (AST_Else a b c) = deepseq a $! deepseq b $! deepseq c () }++instance HasNullValue (AST_Else o) where+ nullValue = AST_Else nullValue nullValue LocationUnknown+ testNull (AST_Else a b _) = testNull a && testNull b++instance HasLocation (AST_Else o) where+ getLocation (AST_Else _ _ loc) = loc+ setLocation (AST_Else a b _ ) loc = AST_Else a b loc+ delLocation (AST_Else a b _ ) = AST_Else a (delLocation b) LocationUnknown++instance PPrintable o => PPrintable (AST_Else o) where+ pPrint (AST_Else coms (AST_If ifn thn _) _) =+ pClosure (pPrintComWith (\ () -> pString "else ") coms >> pString "if" >> pPrint ifn) "{" "}" [pPrint thn]++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_Else o) where+ randO = countNode $ return AST_Else <*> randO <*> randO <*> no+ defaultO = return AST_Else <*> defaultO <*> defaultO <*> no++instance Intermediate (ElseExpr o) (AST_Else o) where+ toInterm (AST_Else _ a loc) = [ElseExpr] <*> ti a <*> [loc]+ fromInterm (ElseExpr a loc) = [AST_Else] <*> [Com ()] <*> fi a <*> [loc]++----------------------------------------------------------------------------------------------------++data IfElseExpr o = IfElseExpr (IfExpr o) [ElseExpr o] (Maybe (LastElseExpr o)) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (IfElseExpr o) where+ rnf (IfElseExpr a b c d ) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasNullValue (IfElseExpr o) where+ nullValue = IfElseExpr nullValue [] Nothing LocationUnknown+ testNull (IfElseExpr a [] Nothing _) = testNull a+ testNull _ = False++instance HasLocation (IfElseExpr o) where+ getLocation (IfElseExpr _ _ _ loc) = loc+ setLocation (IfElseExpr a b c _ ) loc = IfElseExpr a b c loc+ delLocation (IfElseExpr a b c _ ) =+ IfElseExpr (delLocation a) (fmap delLocation b) (fmap delLocation c) LocationUnknown++----------------------------------------------------------------------------------------------------++data AST_IfElse o = AST_IfElse (AST_If o) [AST_Else o] (Maybe (AST_LastElse o)) Location+ -- ^ @if /**/ obj /**/ {} /**/ else /**/ if /**/ obj /**/ {} /**/ else {}@+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_IfElse o) where+ rnf (AST_IfElse a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasNullValue (AST_IfElse o) where+ nullValue = AST_IfElse nullValue [] Nothing LocationUnknown+ testNull (AST_IfElse a [] Nothing _) = testNull a+ testNull _ = False++instance HasLocation (AST_IfElse o) where+ getLocation (AST_IfElse _ _ _ loc) = loc+ setLocation (AST_IfElse a b c _ ) loc = AST_IfElse a b c loc+ delLocation (AST_IfElse a b c _ ) = AST_IfElse (delLocation a) (fmap delLocation b) (fmap delLocation c) LocationUnknown++instance PPrintable o => PPrintable (AST_IfElse o) where+ pPrint (AST_IfElse ifn els deflt _) = do+ pPrint ifn >> pNewLine+ mapM_ pPrint els >> pNewLine+ maybe (return ()) pPrint deflt++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_IfElse o) where+ randO = countNode $ depthLimitedInt 8 >>= \x ->+ return AST_IfElse <*> randO <*> randList 0 x <*> randO <*> no+ defaultO = return AST_IfElse <*> defaultO <*> defaultList 0 1 <*> randO <*> no++instance Intermediate (IfElseExpr o) (AST_IfElse o) where+ toInterm (AST_IfElse a b c loc) =+ [IfElseExpr] <*> ti a <*> [b>>=ti] <*> um1 c <*> [loc]+ fromInterm (IfElseExpr a b c loc) =+ [AST_IfElse] <*> fi a <*> [b>>=fi] <*> nm1 c <*> [loc]++----------------------------------------------------------------------------------------------------++data LastElseExpr o = LastElseExpr (CodeBlock o) Location+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (LastElseExpr o) where+ rnf (LastElseExpr a b) = deepseq a $! deepseq b ()++instance HasNullValue (LastElseExpr o) where+ nullValue = LastElseExpr nullValue LocationUnknown+ testNull (LastElseExpr o _) = testNull o++instance HasLocation (LastElseExpr o) where+ getLocation (LastElseExpr _ loc) = loc+ setLocation (LastElseExpr a _ ) loc = LastElseExpr a loc+ delLocation (LastElseExpr a _ ) = LastElseExpr (delLocation a) LocationUnknown++----------------------------------------------------------------------------------------------------++data AST_LastElse o = AST_LastElse (Com ()) (AST_CodeBlock o) Location+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (AST_LastElse o) where+ rnf (AST_LastElse a b c) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue (AST_LastElse o) where+ nullValue = AST_LastElse (Com ()) nullValue LocationUnknown+ testNull (AST_LastElse a b _) = testNull a && testNull b++instance HasLocation (AST_LastElse o) where+ getLocation (AST_LastElse _ _ loc) = loc+ setLocation (AST_LastElse a b _ ) loc = AST_LastElse a b loc+ delLocation (AST_LastElse a b _ ) = AST_LastElse a (delLocation b) LocationUnknown++instance PPrintable o => PPrintable (AST_LastElse o) where+ pPrint (AST_LastElse coms code _) =+ pClosure (pPrintComWith (\ () -> pString "else") coms) "{" "}" [pPrint code]++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_LastElse o) where+ randO = countNode $ return AST_LastElse <*> randO <*> randO <*> no+ randChoice = randChoiceList [randO]+ defaultO = return AST_LastElse <*> defaultO <*> defaultO <*> no+ defaultChoice = randChoiceList [defaultO]++instance Intermediate (LastElseExpr o) (AST_LastElse o) where+ toInterm (AST_LastElse _ o loc) = [LastElseExpr] <*> toInterm o <*> [loc]+ fromInterm (LastElseExpr o loc) = [AST_LastElse] <*> [Com ()] <*> fromInterm o <*> [loc]++----------------------------------------------------------------------------------------------------++data CatchExpr o = CatchExpr (ParamExpr o) (CodeBlock o) Location+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (CatchExpr o) where+ rnf (CatchExpr a b c) = deepseq a $! deepseq b $! deepseq c ()++instance HasLocation (CatchExpr o) where+ getLocation (CatchExpr _ _ loc) = loc+ setLocation (CatchExpr a b _ ) loc = CatchExpr a b loc+ delLocation (CatchExpr a b _ ) = CatchExpr (delLocation a) (delLocation b) LocationUnknown++----------------------------------------------------------------------------------------------------++data AST_Catch o = AST_Catch [Comment] (Com (AST_Param o)) (AST_CodeBlock o) Location+ deriving (Eq, Ord, Show, Typeable, Functor)++instance NFData o => NFData (AST_Catch o) where+ rnf (AST_Catch a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++instance HasLocation (AST_Catch o) where+ getLocation (AST_Catch _ _ _ loc) = loc+ setLocation (AST_Catch a b c _ ) loc = AST_Catch a b c loc+ delLocation (AST_Catch a b c _ ) = AST_Catch a (delLocation b) (delLocation c) LocationUnknown++instance PPrintable o => PPrintable (AST_Catch o) where+ pPrint (AST_Catch coms param code _) = pPrint coms >>+ pClosure (pString "catch " >> pPrint param) "{" "}" [pPrint code]++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_Catch o) where+ randO = countNode $ return AST_Catch <*> randO <*> randO <*> randO <*> no+ randChoice = randChoiceList [randO]+ defaultO = return AST_Catch <*> defaultO <*> defaultO <*> defaultO <*> no+ defaultChoice = randChoiceList [defaultO]++instance Intermediate (CatchExpr o) (AST_Catch o) where+ toInterm (AST_Catch _ a b loc) = [CatchExpr] <*> uc0 a <*> ti b <*> [loc]+ fromInterm (CatchExpr a b loc) = [AST_Catch] <*> [[]] <*> nc0 a <*> fi b <*> [loc]++----------------------------------------------------------------------------------------------------++newtype WhileExpr o = WhileExpr (IfExpr o) deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (WhileExpr o) where { rnf (WhileExpr (IfExpr a b c)) = deepseq a $! deepseq b $! deepseq c () }++instance HasNullValue (WhileExpr o) where+ nullValue = WhileExpr nullValue+ testNull (WhileExpr a) = testNull a++instance HasLocation (WhileExpr o) where+ getLocation (WhileExpr a) = getLocation a+ setLocation (WhileExpr a) loc = WhileExpr (setLocation a loc)+ delLocation (WhileExpr a) = WhileExpr (delLocation a)++----------------------------------------------------------------------------------------------------++newtype AST_While o = AST_While (AST_If o) deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_While o) where { rnf (AST_While (AST_If a b c)) = deepseq a $! deepseq b $! deepseq c () }++instance HasNullValue (AST_While o) where+ nullValue = AST_While nullValue+ testNull (AST_While a) = testNull a++instance HasLocation (AST_While o) where+ getLocation (AST_While a) = getLocation a+ setLocation (AST_While a) loc = AST_While (setLocation a loc)+ delLocation (AST_While a) = AST_While (delLocation a)++instance PPrintable o => PPrintable (AST_While o) where+ pPrint (AST_While (AST_If ifn thn _)) =+ pClosure (pInline [pString "while", pPrint ifn]) "{" "}" [pPrint thn]++instance Intermediate (WhileExpr o) (AST_While o) where+ toInterm (AST_While a) = WhileExpr <$> ti a+ fromInterm (WhileExpr a) = AST_While <$> fi a++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_While o) where+ randO = AST_While <$> randO+ defaultO = AST_While <$> defaultO++----------------------------------------------------------------------------------------------------++-- | Part of the Dao language abstract syntax tree: any expression that controls the flow of script+-- exectuion.+data ScriptExpr o+ = IfThenElse (IfElseExpr o)+ | WhileLoop (WhileExpr o)+ | RuleFuncExpr (RuleFuncExpr o)+ | EvalObject (AssignExpr o) Location -- location of the semicolon+ | TryCatch (CodeBlock o) [LastElseExpr o] [CatchExpr o] Location+ | ForLoop Name (RefPrefixExpr o) (CodeBlock o) Location+ | ContinueExpr Bool (AssignExpr o) Location+ | ReturnExpr Bool (AssignExpr o) Location+ | WithDoc (ParenExpr o) (CodeBlock o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (ScriptExpr o) where+ rnf (IfThenElse a ) = deepseq a ()+ rnf (WhileLoop a ) = deepseq a ()+ rnf (RuleFuncExpr a ) = deepseq a ()+ rnf (EvalObject a b ) = deepseq a $! deepseq b ()+ rnf (TryCatch a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (ForLoop a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (ContinueExpr a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (ReturnExpr a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (WithDoc a b c ) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue o => HasNullValue (ScriptExpr o) where+ nullValue = EvalObject nullValue LocationUnknown+ testNull (EvalObject a _) = testNull a+ testNull _ = False++instance HasLocation (ScriptExpr o) where+ getLocation o = case o of+ EvalObject _ o -> o+ IfThenElse o -> getLocation o+ RuleFuncExpr o -> getLocation o+ WhileLoop o -> getLocation o+ TryCatch _ _ _ o -> o+ ForLoop _ _ _ o -> o+ ContinueExpr _ _ o -> o+ ReturnExpr _ _ o -> o+ WithDoc _ _ o -> o+ setLocation o loc = case o of+ EvalObject a _ -> EvalObject a loc+ IfThenElse a -> IfThenElse (setLocation a loc)+ WhileLoop a -> WhileLoop (setLocation a loc)+ RuleFuncExpr a -> RuleFuncExpr (setLocation a loc)+ TryCatch a b c _ -> TryCatch a b c loc+ ForLoop a b c _ -> ForLoop a b c loc+ ContinueExpr a b _ -> ContinueExpr a b loc+ ReturnExpr a b _ -> ReturnExpr a b loc+ WithDoc a b _ -> WithDoc a b loc+ delLocation o = case o of+ EvalObject a _ -> EvalObject (delLocation a) LocationUnknown+ IfThenElse a -> IfThenElse (delLocation a)+ WhileLoop a -> WhileLoop (delLocation a)+ RuleFuncExpr a -> RuleFuncExpr (delLocation a)+ TryCatch a b c _ -> TryCatch (delLocation a) (fmap delLocation b) (fmap delLocation c) LocationUnknown+ ForLoop a b c _ -> ForLoop a (delLocation b) (delLocation c) LocationUnknown+ ContinueExpr a b _ -> ContinueExpr a (delLocation b) LocationUnknown+ ReturnExpr a b _ -> ReturnExpr a (delLocation b) LocationUnknown+ WithDoc a b _ -> WithDoc (delLocation a) (delLocation b) LocationUnknown++----------------------------------------------------------------------------------------------------++-- | Part of the Dao language abstract syntax tree: any expression that controls the flow of script+-- exectuion.+data AST_Script o+ = AST_Comment [Comment] + | AST_IfThenElse (AST_IfElse o)+ | AST_WhileLoop (AST_While o)+ | AST_RuleFunc (AST_RuleFunc o)+ | AST_EvalObject (AST_Assign o) [Comment] Location+ -- ^ @some.object.expression = for.example - equations || function(calls) /**/ ;@+ | AST_TryCatch [Comment] (AST_CodeBlock o) [AST_LastElse o] [AST_Catch o] Location+ -- ^ @try /**/ {} /**/ else /**/ {} /**/ catch /**/ errVar /**/ {}@ + | AST_ForLoop (Com Name) (Com (AST_RefPrefix o)) (AST_CodeBlock o) Location+ -- ^ @for /**/ var /**/ in /**/ objExpr /**/ {}@+ | AST_ContinueExpr Bool [Comment] (Com (AST_Assign o)) Location+ -- ^ The boolean parameter is True for a "continue" statement, False for a "break" statement.+ -- @continue /**/ ;@ or @continue /**/ if /**/ objExpr /**/ ;@+ | AST_ReturnExpr Bool (Com (AST_Assign o)) Location+ -- ^ The boolean parameter is True for a "return" statement, False for a "throw" statement.+ -- ^ @return /**/ ;@ or @return /**/ objExpr /**/ ;@+ | AST_WithDoc (Com (AST_Paren o)) (AST_CodeBlock o) Location+ -- ^ @with /**/ objExpr /**/ {}@+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (AST_Script o) where+ rnf (AST_Comment a ) = deepseq a ()+ rnf (AST_IfThenElse a ) = deepseq a ()+ rnf (AST_WhileLoop a ) = deepseq a ()+ rnf (AST_RuleFunc a ) = deepseq a ()+ rnf (AST_EvalObject a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (AST_TryCatch a b c d e) = deepseq a $! deepseq b $! deepseq c $! deepseq d $! deepseq e ()+ rnf (AST_ForLoop a b c d ) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (AST_ContinueExpr a b c d ) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (AST_ReturnExpr a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (AST_WithDoc a b c ) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue (AST_Script o) where+ nullValue = AST_EvalObject nullValue [] LocationUnknown+ testNull (AST_EvalObject a _ _) = testNull a+ testNull _ = False++instance HasLocation (AST_Script o) where+ getLocation o = case o of+ AST_Comment _ -> LocationUnknown+ AST_EvalObject _ _ o -> o+ AST_IfThenElse o -> getLocation o+ AST_WhileLoop o -> getLocation o+ AST_RuleFunc o -> getLocation o+ AST_TryCatch _ _ _ _ o -> o+ AST_ForLoop _ _ _ o -> o+ AST_ContinueExpr _ _ _ o -> o+ AST_ReturnExpr _ _ o -> o+ AST_WithDoc _ _ o -> o+ setLocation o loc = case o of+ AST_Comment a -> AST_Comment a+ AST_EvalObject a b _ -> AST_EvalObject a b loc+ AST_IfThenElse a -> AST_IfThenElse (setLocation a loc)+ AST_WhileLoop a -> AST_WhileLoop (setLocation a loc)+ AST_RuleFunc a -> AST_RuleFunc (setLocation a loc)+ AST_TryCatch a b c d _ -> AST_TryCatch a b c d loc+ AST_ForLoop a b c _ -> AST_ForLoop a b c loc+ AST_ContinueExpr a b c _ -> AST_ContinueExpr a b c loc+ AST_ReturnExpr a b _ -> AST_ReturnExpr a b loc+ AST_WithDoc a b _ -> AST_WithDoc a b loc+ delLocation o = case o of+ AST_Comment a -> AST_Comment a+ AST_EvalObject a b _ -> AST_EvalObject (delLocation a) b LocationUnknown+ AST_IfThenElse a -> AST_IfThenElse (delLocation a)+ AST_WhileLoop a -> AST_WhileLoop (delLocation a)+ AST_RuleFunc a -> AST_RuleFunc (delLocation a)+ AST_TryCatch a b c d _ -> AST_TryCatch a (delLocation b) (fmap delLocation c) (fmap delLocation d) LocationUnknown+ AST_ForLoop a b c _ -> AST_ForLoop a (fmap delLocation b) (delLocation c) LocationUnknown+ AST_ContinueExpr a b c _ -> AST_ContinueExpr a b (fmap delLocation c) LocationUnknown+ AST_ReturnExpr a b _ -> AST_ReturnExpr a (fmap delLocation b) LocationUnknown+ AST_WithDoc a b _ -> AST_WithDoc (fmap delLocation a) (delLocation b) LocationUnknown++instance PPrintable o => PPrintable (AST_Script o) where+ pPrint expr = pGroup True $ case expr of+ AST_Comment coms -> mapM_ pPrint coms+ AST_EvalObject objXp coms _ ->+ pPrint objXp >> mapM_ pPrint coms >> pString ";"+ AST_IfThenElse ifXp -> pPrint ifXp+ AST_WhileLoop whileLoop -> pPrint whileLoop+ AST_RuleFunc ruleOrFunc -> pPrint ruleOrFunc+ AST_TryCatch coms scrpXp elsXp catchExpr _ -> do+ pClosure (pString "try" >> pPrint coms) "{" "}" [pPrint scrpXp]+ mapM_ (\o -> pPrint o >> pEndLine) elsXp+ mapM_ (\o -> pPrint o >> pEndLine) catchExpr+ AST_ForLoop cNm cObjXp xcScrpXp _ ->+ pPrintSubBlock (pString "for " >> pPrint cNm >> pString " in " >> pPrint cObjXp) xcScrpXp+ AST_ContinueExpr contin coms cObjXp _ -> pWrapIndent $+ [ pString (if contin then "continue" else "break")+ , pInline (map pPrint coms)+ , case unComment cObjXp of+ AST_Eval (AST_ObjArith (AST_Object AST_Void)) -> return ()+ _ -> pString " if" >> when (precedeWithSpace cObjXp) (pString " ") >> pPrint cObjXp+ , pString ";"+ ]+ AST_ReturnExpr retrn cObjXp _ -> pWrapIndent $+ [pString (if retrn then "return " else "throw "), pPrint cObjXp, pString ";"]+ AST_WithDoc cObjXp xcScrpXp _ ->+ pPrintSubBlock (pString "with " >> pPrint cObjXp) xcScrpXp++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_Script o) where+ randO = countNode $ recurse $ runRandChoice+ randChoice = randChoiceList $+ [ return AST_EvalObject <*> randO <*> randO <*> no+ , return AST_IfThenElse <*> randO+ , return AST_WhileLoop <*> randO+ , return AST_RuleFunc <*> randO+ , scramble $ depthLimitedInt 4 >>= \x -> depthLimitedInt 4 >>= \y -> + return AST_TryCatch <*> randO <*> randO <*> randList 0 x <*> randList 0 y <*> no+ , scramble $ return AST_ForLoop <*> randO <*> randO <*> randO <*> no+ , scramble $ return AST_ContinueExpr <*> randO <*> randO <*> randO <*> no+ , scramble $ return AST_ReturnExpr <*> randO <*> randO <*> no+ , scramble $ return AST_WithDoc <*> randO <*> randO <*> no+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ AST_IfThenElse <$> defaultO+ , AST_WhileLoop <$> defaultO+ , AST_RuleFunc <$> defaultO+ , return AST_TryCatch <*> pure [] <*> defaultO <*> defaultList 0 1 <*> defaultList 0 1 <*> no+ , return AST_ContinueExpr <*> defaultO <*> defaultO <*> defaultO <*> no+ , return AST_ReturnExpr <*> defaultO <*> defaultO <*> no+ ]++instance PPrintable o => PPrintable (ScriptExpr o) where { pPrint = pPrintInterm }++instance Intermediate (ScriptExpr o) (AST_Script o) where+ toInterm ast = case ast of+ AST_Comment _ -> mzero+ AST_EvalObject a _ loc -> [EvalObject ] <*> ti a <*> [loc]+ AST_IfThenElse a -> [IfThenElse ] <*> ti a+ AST_WhileLoop a -> [WhileLoop ] <*> ti a+ AST_RuleFunc a -> [RuleFuncExpr] <*> ti a+ AST_TryCatch _ a b c loc -> [TryCatch ] <*> ti a <*> mapM ti b <*> mapM ti c <*> [loc]+ AST_ForLoop a b c loc -> [ForLoop ] <*> uc a <*> uc0 b <*> ti c <*> [loc]+ AST_ContinueExpr a _ c loc -> [ContinueExpr] <*> [a] <*> uc0 c <*> [loc]+ AST_ReturnExpr a b loc -> [ReturnExpr ] <*> [a] <*> uc0 b <*> [loc]+ AST_WithDoc a b loc -> [WithDoc ] <*> uc0 a <*> ti b <*> [loc]+ fromInterm obj = case obj of+ EvalObject a loc -> [AST_EvalObject ] <*> fi a <*> [[]] <*> [loc]+ IfThenElse a -> AST_IfThenElse <$> fi a+ WhileLoop a -> AST_WhileLoop <$> fi a+ RuleFuncExpr a -> AST_RuleFunc <$> fi a+ TryCatch a b c loc -> [AST_TryCatch ] <*> [[]] <*> fi a <*> mapM fi b <*> mapM fi c <*> [loc]+ ForLoop a b c loc -> [AST_ForLoop ] <*> nc a <*> nc0 b <*> fi c <*> [loc]+ ContinueExpr a b loc -> [AST_ContinueExpr] <*> [a] <*> [[]] <*> nc0 b <*> [loc]+ ReturnExpr a b loc -> [AST_ReturnExpr ] <*> [a] <*> nc0 b <*> [loc]+ WithDoc a b loc -> [AST_WithDoc ] <*> nc0 a <*> fi b <*> [loc]+ +----------------------------------------------------------------------------------------------------++data AttributeExpr+ = AttribDotNameExpr DotLabelExpr+ | AttribStringExpr UStr Location+ deriving (Eq, Ord, Show, Typeable)++instance NFData AttributeExpr where+ rnf (AttribDotNameExpr a ) = deepseq a ()+ rnf (AttribStringExpr a b) = deepseq a $! deepseq b ()++instance HasNullValue AttributeExpr where+ nullValue = AttribStringExpr nil LocationUnknown+ testNull (AttribStringExpr a _) = a==nil+ testNull _ = False++instance HasLocation AttributeExpr where+ getLocation o = case o of+ AttribDotNameExpr o -> getLocation o+ AttribStringExpr _ loc -> loc+ setLocation o loc = case o of+ AttribDotNameExpr o -> AttribDotNameExpr (setLocation o loc)+ AttribStringExpr o _ -> AttribStringExpr o loc+ delLocation o = case o of+ AttribDotNameExpr o -> AttribDotNameExpr (delLocation o)+ AttribStringExpr o _ -> AttribStringExpr o LocationUnknown++instance PPrintable AttributeExpr where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++data AST_Attribute+ = AST_AttribDotName AST_DotLabel+ | AST_AttribString UStr Location+ deriving (Eq, Ord, Show, Typeable)++instance NFData AST_Attribute where+ rnf (AST_AttribDotName a ) = deepseq a ()+ rnf (AST_AttribString a b) = deepseq a $! deepseq b ()++instance HasNullValue AST_Attribute where+ nullValue = AST_AttribString nil LocationUnknown+ testNull (AST_AttribString a _) = a==nil+ testNull _ = False++instance HasLocation AST_Attribute where+ getLocation o = case o of+ AST_AttribDotName o -> getLocation o+ AST_AttribString _ loc -> loc+ setLocation o loc = case o of+ AST_AttribDotName o -> AST_AttribDotName (setLocation o loc)+ AST_AttribString o _ -> AST_AttribString o loc+ delLocation o = case o of+ AST_AttribDotName o -> AST_AttribDotName (delLocation o)+ AST_AttribString o _ -> AST_AttribString o LocationUnknown++instance PPrintable AST_Attribute where+ pPrint o = case o of+ AST_AttribDotName nm -> pPrint nm+ AST_AttribString str _ -> pPrint str++instance HasRandGen AST_Attribute where+ randChoice = randChoiceList $+ [ AST_AttribDotName <$> randO+ , return AST_AttribString <*> randO <*> no+ ]+ randO = countNode $ runRandChoice+ defaultO = randO+ defaultChoice = randChoiceList [defaultO]++instance Intermediate AttributeExpr AST_Attribute where+ toInterm o = case o of+ AST_AttribDotName a -> AttribDotNameExpr <$> ti a+ AST_AttribString a loc -> [AttribStringExpr a loc]+ fromInterm o = case o of+ AttribDotNameExpr a -> AST_AttribDotName <$> fi a+ AttribStringExpr a loc -> [AST_AttribString a loc]++----------------------------------------------------------------------------------------------------++data TopLevelEventType+ = BeginExprType | EndExprType | ExitExprType+ deriving (Eq, Ord, Typeable, Enum)++instance Show TopLevelEventType where+ show t = case t of+ BeginExprType -> "BEGIN"+ EndExprType -> "END"+ ExitExprType -> "EXIT"++instance Read TopLevelEventType where+ readsPrec _ str = map (\t -> (t, "")) $ case str of+ "BEGIN" -> [BeginExprType]+ "END" -> [EndExprType]+ "EXIT" -> [ExitExprType]+ _ -> []++instance NFData TopLevelEventType where { rnf a = seq a () }++instance HasRandGen TopLevelEventType where+ randO = fmap toEnum (nextInt 3)+ defaultO = randO++----------------------------------------------------------------------------------------------------++-- | A 'TopLevelExpr' is a single declaration for the top-level of the program file. A Dao 'SourceCode'+-- is a list of these directives.+data TopLevelExpr o+ = RequireExpr AttributeExpr Location+ | ImportExpr AttributeExpr NamespaceExpr Location+ | TopScript (ScriptExpr o) Location+ | EventExpr TopLevelEventType (CodeBlock o) Location+ deriving (Eq, Ord, Typeable, Show, Functor)++instance NFData o => NFData (TopLevelExpr o) where+ rnf (RequireExpr a b ) = deepseq a $! deepseq b ()+ rnf (ImportExpr a b c) = deepseq a $! deepseq b $! deepseq c ()+ rnf (TopScript a b ) = deepseq a $! deepseq b ()+ rnf (EventExpr a b c) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue o => HasNullValue (TopLevelExpr o) where+ nullValue = TopScript nullValue LocationUnknown+ testNull (TopScript a LocationUnknown) = testNull a+ testNull _ = False++isAttribute :: TopLevelExpr o -> Bool+isAttribute toplevel = case toplevel of { RequireExpr{} -> True; ImportExpr{} -> True; _ -> False; }++instance HasLocation (TopLevelExpr o) where+ getLocation o = case o of+ RequireExpr _ o -> o+ ImportExpr _ _ o -> o+ TopScript _ o -> o+ EventExpr _ _ o -> o+ setLocation o loc = case o of+ RequireExpr a _ -> RequireExpr a loc+ ImportExpr a b _ -> ImportExpr a b loc+ TopScript a _ -> TopScript a loc+ EventExpr a b _ -> EventExpr a b loc+ delLocation o = case o of+ RequireExpr a _ -> RequireExpr (delLocation a) LocationUnknown+ ImportExpr a b _ -> ImportExpr (delLocation a) (delLocation b) LocationUnknown+ TopScript a _ -> TopScript (delLocation a) LocationUnknown+ EventExpr a b _ -> EventExpr a (delLocation b) LocationUnknown++instance PPrintable o => PPrintable (TopLevelExpr o) where { pPrint = pPrintInterm }++----------------------------------------------------------------------------------------------------++-- | A 'AST_TopLevel' is a single declaration for the top-level of the program file. A Dao 'SourceCode'+-- is a list of these directives.+data AST_TopLevel o+ = AST_Require (Com AST_Attribute) Location+ | AST_Import (Com AST_Attribute) AST_Namespace Location+ | AST_TopScript (AST_Script o) Location+ | AST_Event TopLevelEventType [Comment] (AST_CodeBlock o) Location+ | AST_TopComment [Comment]+ deriving (Eq, Ord, Typeable, Show, Functor)++instance (HasNullValue o, HasRandGen o) => HasRandGen (AST_TopLevel o) where+ randO = countNode $ runRandChoice+ randChoice = randChoiceList $+ [ return AST_Require <*> randO <*> no+ , return AST_Import <*> randO <*> randO <*> no+ , return AST_TopScript <*> randO <*> no+ , return AST_Event <*> randO <*> randO <*> randO <*> no+ , AST_TopComment <$> defaultO+ ]+ defaultO = runDefaultChoice+ defaultChoice = randChoiceList $+ [ return AST_Import <*> defaultO <*> defaultO <*> no+ , return AST_Require <*> defaultO <*> no+ , return AST_TopScript <*> defaultO <*> no+ , return AST_Event <*> defaultO <*> defaultO <*> defaultO <*> no+ ]++instance Intermediate (TopLevelExpr o) (AST_TopLevel o) where+ toInterm ast = case ast of+ AST_Require a loc -> [RequireExpr] <*> uc0 a <*> [loc]+ AST_Import a b loc -> [ImportExpr ] <*> uc0 a <*> ti b <*> [loc]+ AST_TopScript a loc -> [TopScript ] <*> ti a <*> [loc]+ AST_Event a _ b loc -> [EventExpr ] <*> [a] <*> ti b <*> [loc]+ AST_TopComment _loc -> mzero+ fromInterm obj = case obj of+ RequireExpr a loc -> [AST_Require ] <*> nc0 a <*> [loc]+ ImportExpr a b loc -> [AST_Import ] <*> nc0 a <*> fi b <*> [loc]+ TopScript a loc -> [AST_TopScript] <*> fi a <*> [loc]+ EventExpr a b loc -> [AST_Event ] <*> [a] <*> [[]] <*> fi b <*> [loc]++isAST_Attribute :: AST_TopLevel o -> Bool+isAST_Attribute o = case o of { AST_Require{} -> True; AST_Import{} -> True; _ -> False; }++-- | Split a list of 'AST_TopLevel' items into a tripple, the "require" statements, the "import"+-- statements. This function scans the list lazily and returns as soon as an 'AST_TopLevel' item in+-- the list is found that is not one of 'AST_Require', 'AST_Import' or 'AST_TopComment'.+-- +-- Notice that this function takes an 'AST_TopLevel' and returns a list of 'AttributeExpr's, not a+-- list of 'AST_Attribute's. This is because this function is designed for assisting in evaluation+-- of the import statements of a Dao script file, specifically in order to generate a dependency+-- graph.+getRequiresAndImports :: [AST_TopLevel o] -> ([AttributeExpr], [(AttributeExpr, NamespaceExpr)])+getRequiresAndImports = loop [] [] where+ loop requires imports ox = case ox of+ AST_Require a _ : ox -> loop (requires ++ uc0 a) imports ox+ AST_Import a b _ : ox -> loop requires (imports ++ (pure (,) <*> uc0 a <*> toInterm b)) ox+ AST_TopComment{} : ox -> loop requires imports ox+ _ -> (requires, imports)++instance NFData o => NFData (AST_TopLevel o) where+ rnf (AST_Require a b ) = deepseq a $! deepseq b ()+ rnf (AST_Import a b c ) = deepseq a $! deepseq b $! deepseq c ()+ rnf (AST_TopScript a b ) = deepseq a $! deepseq b ()+ rnf (AST_Event a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()+ rnf (AST_TopComment a ) = deepseq a ()++instance HasNullValue (AST_TopLevel o) where+ nullValue = AST_TopScript nullValue LocationUnknown+ testNull (AST_TopScript a _) = testNull a+ testNull _ = False++instance HasLocation (AST_TopLevel o) where+ getLocation o = case o of+ AST_Require _ o -> o+ AST_Import _ _ o -> o+ AST_TopScript _ o -> o+ AST_Event _ _ _ o -> o+ AST_TopComment _ -> LocationUnknown+ setLocation o loc = case o of+ AST_Require a _ -> AST_Require a loc+ AST_Import a b _ -> AST_Import a b loc+ AST_TopScript a _ -> AST_TopScript a loc+ AST_Event a b c _ -> AST_Event a b c loc+ AST_TopComment a -> AST_TopComment a+ delLocation o = case o of+ AST_Require a _ -> AST_Require (delLocation a) LocationUnknown+ AST_Import a b _ -> AST_Import (delLocation a) (delLocation b) LocationUnknown+ AST_TopScript a _ -> AST_TopScript (delLocation a) LocationUnknown+ AST_Event a b c _ -> AST_Event a b (delLocation c) LocationUnknown+ AST_TopComment a -> AST_TopComment a ++instance PPrintable o => PPrintable (AST_TopLevel o) where+ pPrint o = case o of+ AST_Require a _ -> pWrapIndent [pString "require ", pPrint a, pString ";"]+ AST_Import a b _ -> pWrapIndent [pString "import ", pPrint a, pPrint b, pString ";"]+ AST_TopScript a _ -> pPrint a+ AST_Event a b c _ -> pClosure (pShow a >> mapM_ pPrint b) " { " " }" (map pPrint (getAST_CodeBlock c))+ AST_TopComment a -> mapM_ (\a -> pPrint a >> pNewLine) a++----------------------------------------------------------------------------------------------------++-- | A program is just a list of 'TopLevelExpr's. It serves as the 'Intermediate'+-- representation of a 'AST_SourceCode'.+newtype Program o = Program { topLevelExprs :: [TopLevelExpr o] } deriving (Eq, Ord, Typeable)++instance Show o => Show (Program o) where { show (Program o) = unlines (map show o) }++instance HasNullValue (Program o) where+ nullValue = Program []+ testNull (Program p) = null p++instance HasLocation (Program o) where+ getLocation o = case topLevelExprs o of+ [] -> LocationUnknown+ [o] -> getLocation o+ o:ox -> mappend (getLocation o) (getLocation (foldl (flip const) o ox))+ setLocation o _ = o+ delLocation o = Program (fmap delLocation (topLevelExprs o))++----------------------------------------------------------------------------------------------------++-- | A 'SourceCode' is the structure loaded from source code. An 'ExecUnit' object is constructed from+-- 'SourceCode'.+data AST_SourceCode o+ = AST_SourceCode+ { sourceModified :: Int+ , sourceFullPath :: UStr+ -- ^ the URL (full file path) from where this source code was received.+ , directives :: [AST_TopLevel o]+ }+ deriving (Eq, Ord, Typeable)++instance NFData o => NFData (AST_SourceCode o) where+ rnf (AST_SourceCode a b c) = deepseq a $! deepseq b $! deepseq c ()++instance HasNullValue (AST_SourceCode o) where+ nullValue = (AST_SourceCode 0 nil [])+ testNull (AST_SourceCode 0 a []) | a==nil = True+ testNull _ = False++instance PPrintable o => PPrintable (AST_SourceCode o) where+ pPrint sc = do+ let (attrs, dirs) = span isAST_Attribute (directives sc)+ mapM_ pPrint attrs+ pForceNewLine+ mapM_ (\dir -> pPrint dir >> pForceNewLine) dirs++instance Intermediate (Program o) (AST_SourceCode o) where+ toInterm ast = [Program $ directives ast >>= toInterm]+ fromInterm obj = return $+ AST_SourceCode+ { sourceModified = 0+ , sourceFullPath = nil+ , directives = topLevelExprs obj >>= fromInterm+ }+
+ src/Dao/Interpreter/Parser.hs view
@@ -0,0 +1,831 @@+-- "src/Dao/Interpreter/Parser.hs" makes use of "Dao.Parser" to parse+-- parse 'Dao.Interpreter.AST' expressions.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++{-# LANGUAGE MultiParamTypeClasses #-}++module Dao.Interpreter.Parser where++import Dao.String+import Dao.Token+import Dao.PPrint+import Dao.Interpreter hiding (opt)+import Dao.Interpreter.AST+import Dao.Interpreter.Tokenizer+import Dao.Predicate+import Dao.Parser++import Control.Applicative+import Control.Monad+import Control.Monad.Error+import Control.Monad.State++import Data.Monoid+import Data.Maybe+import Data.List+import Data.Char+import Data.Ratio+import Data.Complex++maxYears :: Integer+maxYears = 99999++type DaoParser a = Parser DaoParState DaoTT a+type DaoTableItem a = TableItem DaoTT (DaoParser a)+type DaoPTable a = PTable DaoTT (DaoParser a)+type DaoParseErr = ParseError DaoParState DaoTT++----------------------------------------------------------------------------------------------------++data DaoParState+ = DaoParState+ { bufferedComments :: Maybe [Comment]+ , nonHaltingErrors :: [DaoParseErr]+ , internalState :: Maybe (TokStreamState DaoParState DaoTT)+ }++instance Monoid DaoParState where+ mappend a b =+ b{ bufferedComments = bufferedComments a >>= \a -> bufferedComments b >>= \b -> return (a++b)+ , nonHaltingErrors = nonHaltingErrors a ++ nonHaltingErrors b+ , internalState = internalState b+ }+ mempty =+ DaoParState+ { bufferedComments = Nothing+ , nonHaltingErrors = []+ , internalState = Nothing+ }++instance PPrintable DaoParState where { pPrint _ = return () }++setCommentBuffer :: [Comment] -> DaoParser ()+setCommentBuffer coms = modify $ \st ->+ st{ bufferedComments = (if null coms then mzero else return coms) <> bufferedComments st }++failLater :: String -> Location -> DaoParser ()+failLater msg loc = catchError (fail msg) $ \err -> modify $ \st ->+ st{nonHaltingErrors =+ nonHaltingErrors st ++ [err{parseStateAtErr=Nothing, parseErrLoc=loc}]}++----------------------------------------------------------------------------------------------------++spaceComPTab :: DaoPTable [Comment]+spaceComPTab = table $+ [ tableItem SPACE (return . const [] . as0)+ , tableItem INLINECOM (\c -> return [InlineComment $ asUStr c])+ , tableItem ENDLINECOM (\c -> return [EndlineComment $ asUStr c])+ ]++-- | Parses an arbitrary number of space and comment tokens, comments are returned. Backtracks if+-- there are no comments.+space :: DaoParser [Comment]+space = do+ st <- Control.Monad.State.get+ case bufferedComments st of+ Just coms -> put (st{bufferedComments=mempty}) >> return coms+ Nothing -> fmap concat $ many (joinEvalPTable spaceComPTab)++-- The 'space' parser backtracks if there are no spaces, which is important to prevent infinite+-- recursion in some situations. The 'optSpace' evalautes 'space' but returns an empty list of+-- 'space' backtracks, so 'optSpace' never backtracks.+optSpace :: DaoParser [Comment]+optSpace = mplus space (return [])++-- | Evaluates a 'DaoParser' within a cluster of optional spaces and comments, returning the result+-- of the parser wrapped in a 'Dao.Interpreter.Com' constructor. If the given 'DaoParser' backtracks, the+-- comments that were parsed before the 'DaoParser' was evaluated are buffered so a second call two+-- successive calls to this function return immediately. For example in an expression like:+-- > 'Control.Monad.msum' ['commented' p1, 'commented' p2, ... , 'commented' pN]+-- The spaces and comments occurring before the parsers @p1@, @p2@, ... , @pN@ are only being parsed+-- once, no matter how many parser are tried.+commented :: DaoParser a -> DaoParser (Com a)+commented parser = do+ before <- mplus space (return [])+ flip mplus (setCommentBuffer before >> mzero) $ do+ result <- parser+ after <- mplus space (return [])+ return (com before result after)++-- Take comments of the stream, but do not return them, instead just buffer them. This is a good way+-- to do a look-ahead past comments without deleting comments. If the next parser evaluated+-- immediately after this one is 'commented', the comments buffered by this function will be+-- returned with the object parsed by 'commented'. This is necessary in parse tables where the table+-- needs an operator token to select the next parser in the table, but the returned operator token+-- must be preceeded by possible comments.+bufferComments :: DaoParser ()+bufferComments = mplus (space >>= setCommentBuffer) (return ())++----------------------------------------------------------------------------------------------------++rationalFromString :: Int -> Rational -> String -> Maybe Rational+rationalFromString maxValue base str =+ if b<1 then fmap (b*) (fol (reverse str)) else fol str where+ b = abs base+ maxVal = abs maxValue+ fol = foldl shiftAdd (return 0)+ shiftAdd result nextChar = do+ x <- result+ y <- convertChar nextChar+ if y>=maxVal then mzero else return (x*b + (toInteger y % 1))+ convertChar a = case a of+ a | isDigit a -> return (ord a - ord '0')+ a | isLower a -> return (ord a - ord 'a' + 10)+ a | isUpper a -> return (ord a - ord 'A' + 10)+ _ -> mzero++numberFromStrs :: Int -> String -> Maybe String -> Maybe String -> Maybe String -> DaoParser Object+numberFromStrs base int maybFrac maybPlusMinusExp maybTyp = do+ let frac = maybe "" id (maybFrac >>= stripPrefix ".")+ strprfx = foldl (\f s t -> f (maybe t id (stripPrefix s t))) id . words+ plusMinusExp = fromMaybe "" (fmap (strprfx ".e .E e E") maybPlusMinusExp)+ typ = fromMaybe "" maybTyp+ (exp, hasMinusSign) = case plusMinusExp of+ "" -> ("" , False)+ p:exp | p=='+' -> (exp, False)+ | p=='-' -> (exp, True )+ exp -> (exp, False)+ b = toInteger base % 1+ rational = do+ x <- rationalFromString base b int+ y <- rationalFromString base (recip b) frac+ exp <- fmap (round . abs) (rationalFromString base b exp) :: Maybe Integer+ let ibase = if hasMinusSign then recip b else b+ result = (x+y)*(ibase^^exp)+ return (round result % 1 == result, result)+ (_r_is_an_integer, r) <- case rational of+ Nothing -> fail ("incorrect digits used to form a base-"++show base++" number")+ Just r -> return r+ case typ of+ "U" -> return $ OWord (round r)+ "I" -> return $ OInt (round r)+ "L" -> return $ OLong (round r)+ "R" -> return $ ORatio r+ "F" -> return $ OFloat (fromRational r)+ "f" -> return $ OFloat (fromRational r)+ "i" -> return $ OComplex $ Complex $ 0 :+ fromRational r+ "j" -> return $ OComplex $ Complex $ 0 :+ fromRational r+ "s" -> return $ ORelTime (fromRational r)+ "" ->+ return (OInt (round r))+-- if r_is_an_integer && null frac+-- then+-- let i = round r+-- in if fromIntegral (minBound::T_int) <= i && i <= fromIntegral (maxBound::T_int)+-- then return $ OInt $ fromIntegral i+-- else return $ OLong i+-- else return (ORatio r)+ typ -> fail ("unknown numeric type "++show typ)++----------------------------------------------------------------------------------------------------++-- | Compute diff times from strings representing days, hours, minutes, and seconds. The seconds+-- value may have a decimal point.+diffTimeFromStrs :: String -> DaoParser T_diffTime+diffTimeFromStrs time = do+ let [hours,minutes,secMils] = split [] time+ (seconds, dot_mils) = break (=='.') secMils+ miliseconds = dropWhile (=='.') dot_mils+ hours <- check "hours" 24 hours+ minutes <- check "minutes" 60 minutes+ let sec = check "seconds" 60+ seconds <-+ if null miliseconds+ then sec seconds+ else do+ seconds <- sec seconds+ return (seconds + rint miliseconds % (10 ^ length miliseconds))+ return $ fromRational (60*60*hours + 60*minutes + seconds)+ where+ split buf str = case break (==':') str of+ (t, "" ) -> reverse $ take 3 $ (t:buf) ++ repeat ""+ (t, ':':str) -> split (t:buf) str+ (_, _ ) -> error "unexpected character while parsing time-literal expression"+ rint str = if null str then 0 else (read str :: Integer)+ integerToRational s = s % 1 :: Rational+ check :: String -> Integer -> String -> DaoParser Rational+ check typ maxVal s = do+ let i = rint s+ zero = return (0%1)+ ok = return (integerToRational i)+ err = fail $ concat ["time value expression with ", s, " ", typ, " is invalid"]+ if null s then zero else if i<maxVal then ok else err++----------------------------------------------------------------------------------------------------++numberPTabItems :: [DaoTableItem (AST_Literal Object)]+numberPTabItems = + [ base 16 BASE16+ , base 2 BASE2+ , tableItem BASE10 $ \tok -> do+ frac <- optional (token DOTBASE10 id)+ exp <- optional (token EXPONENT id)+ typ <- optional (token NUMTYPE id)+ done tok 10 (asString tok) (mstr frac) (mstr exp) (mstr typ) (return (asLocation tok) <> mloc frac <> mloc exp <> mloc typ)+ , tableItem DOTBASE10 $ \tok -> do+ exp <- optional (token EXPONENT id)+ typ <- optional (token NUMTYPE id)+ done tok 10 "" (Just (asString tok)) (mstr exp) (mstr typ) (return (asLocation tok) <> mloc exp <> mloc typ)+ , tableItemBy "date" $ \startTok ->+ expect "date/time value expression after \"date\" statement" $ do+ token SPACE as0+ date <- token DATE id+ let optsp tok = optional $+ token SPACE id >>= \s -> mplus (token tok id) (unshift s >> mzero)+ time <- optsp TIME+ zone <- optsp LABEL+ let loc = asLocation startTok <>+ maybe LocationUnknown id (fmap asLocation time <> fmap asLocation zone)+ astr = (' ':) . asString+ timeAndZone = maybe " 00:00:00" astr time ++ maybe "" astr zone+ case readsPrec 0 (asString date ++ timeAndZone) of+ [(o, "")] -> return (AST_Literal (OAbsTime o) loc)+ _ -> fail "invalid UTC-time expression"+ , tableItemBy "time" $ \startTok -> expect "UTC-time value after \"time\" statement" $ do+ token SPACE as0+ tok <- token TIME id+ time <- diffTimeFromStrs (asString tok)+ return (AST_Literal (ORelTime time) (asLocation startTok <> asLocation tok))+ ]+ where+ mloc = fmap asLocation+ mstr = fmap asString+ base b t = tableItem t $ \tok -> do+ typ <- optional (token NUMTYPE id)+ done tok b (drop 2 (asString tok)) Nothing Nothing (mstr typ) (return (asLocation tok) <> mloc typ)+ done tok base int frac exp typ loc = do+ num <- numberFromStrs base int frac exp typ+ let endLoc = asLocation tok+ return (AST_Literal num (maybe endLoc id loc))++numberPTab :: DaoPTable (AST_Literal Object)+numberPTab = table numberPTabItems++-- | Parsing numerical literals+number :: DaoParser (AST_Literal Object)+number = joinEvalPTable numberPTab++singletonPTab :: DaoPTable (AST_Literal Object)+singletonPTab = table singletonPTabItems++parenPTabItem :: DaoTableItem (AST_Paren Object)+parenPTabItem = tableItemBy "(" $ \tok -> do+ o <- commented assignment+ expect "closing parentheses" $ do+ endloc <- tokenBy ")" asLocation+ return (AST_Paren o (asLocation tok <> endloc))++paren :: DaoParser (AST_Paren Object)+paren = joinEvalPTableItem parenPTabItem++metaEvalPTabItem :: DaoTableItem (AST_Object Object)+metaEvalPTabItem = tableItemBy "${" $ \startTok -> expect "object expression after open ${ meta-eval brace" $ do+ scrp <- fmap (AST_CodeBlock . concat) (many script)+ expect "closing } for meta-eval brace" $ do+ endLoc <- tokenBy "}" asLocation+ return (AST_MetaEval scrp (asLocation startTok <> endLoc))++singletonPTabItems :: [DaoTableItem (AST_Literal Object)]+singletonPTabItems = numberPTabItems +++ [ tableItem STRINGLIT (literal $ OString . read . asString)+ , tableItem CHARLIT (literal $ OChar . read . asString)+ , trueFalse "null" ONull, trueFalse "false" ONull, trueFalse "true" OTrue+ , reserved "operator", reserved "public", reserved "private"+ ]+ where+ literal constr tok = return (AST_Literal (constr tok) (asLocation tok))+ trueFalse lbl obj = tableItemBy lbl $ \tok -> return (AST_Literal obj (asLocation tok))+ reserved key = tableItemBy key $ fail $+ "keyword "++show key++" is reserved for future use, not implemented in this version of Dao"++-- Objects that are parsed as a single value, which includes all literal expressions and equtions in+-- parentheses.+singleton :: DaoParser (AST_Literal Object)+singleton = joinEvalPTable singletonPTab++-- Returns an AST_ObjList, which is a constructor that contains leading whitespace/comments. However+-- this function is a 'DaoTableItem' parser, which means the first token parsed must be the opening+-- bracket. In order to correctly parse the leading whitespace/comments while also correctly+-- identifying the opening bracket token, it is expected that you have called 'bufferComments'+-- immediately before this function is evaluated.+commaSepdObjList :: String -> String -> String -> DaoTableItem (AST_ObjList Object)+commaSepdObjList msg open close = tableItemBy open $ \startTok -> do+ let startLoc = asLocation startTok+ coms <- optSpace -- the comments must have been buffered by this point, otherwise the parser behaves strangely.+ (lst, endLoc) <- commaSepd ("arguments to "++msg) close (return . com [] nullValue) assignment id+ return (AST_ObjList coms lst (startLoc<>endLoc))++ruleFuncPTab :: DaoPTable (AST_RuleFunc Object)+ruleFuncPTab = table $+ [ tableItemBy "rule" $ \startTok ->+ expect "list of strings after \"rule\" statement" $ do+ lst <- commented $ joinEvalPTable $ table $+ [ tableItem STRINGLIT $ \str -> return $ AST_RuleString (Com $ read $ uchars $ asUStr str) (asLocation str)+ , fmap (\ (AST_ObjList coms lst loc) ->+ if null lst then AST_NullRules coms loc else AST_RuleHeader lst loc+ ) <$> commaSepdObjList "rule header" "(" ")"+ ]+ expect "bracketed expression after rule header" $ do+ (scrpt, endLoc) <- bracketed ("script expression for \"rule\" statement")+ return $ AST_Rule lst scrpt (asLocation startTok <> endLoc)+ , lambdaFunc "func", lambdaFunc "function"+ ]+ where+ lambdaFunc lbl = tableItemBy lbl $ \startTok ->+ expect ("parameters and bracketed script after \""++lbl++"\" statement") $ do+ constr <- mplus (pure AST_Func <*> optSpace <*> token LABEL asName) (return AST_Lambda)+ params <- commented paramList+ (scrpt, endLoc) <- bracketed ("script expression after \""++lbl++"\" statement")+ return $ constr params scrpt (asLocation startTok <> endLoc)++ruleFunc :: DaoParser (AST_RuleFunc Object)+ruleFunc = joinEvalPTable ruleFuncPTab++-- Objects that are parsed as a single value but which are constructed from other object+-- expressions. This table excludes 'singletonPTab'.+containerPTab :: DaoPTable (AST_Object Object)+containerPTab = table [metaEvalPTabItem]++-- None of the functions related to parameters and type checking parse with tables because there is+-- simply no need for it according to the Dao language syntax.+typeCheckParser :: a -> DaoParser (AST_TyChk a Object)+typeCheckParser a = flip mplus (return (AST_NotChecked a)) $ do+ com1 <- commented (tokenBy "::" id)+ let startLoc = asLocation (unComment com1)+ expect "type expression after colon operator" $ arithmetic >>= \obj -> return $+ AST_Checked a (fmap as0 com1) obj (startLoc <> getLocation obj)++typeCheckedName :: DaoParser (AST_TyChk Name Object)+typeCheckedName = token LABEL id >>= \tok ->+ fmap (\tychk -> setLocation tychk (asLocation tok <> getLocation tychk)) $+ typeCheckParser (asName tok)++parameter :: DaoParser (AST_Param Object)+parameter = msum $+ [ do startLoc <- tokenBy "$" asLocation+ coms <- optional space+ item <- typeCheckedName+ return $ AST_Param coms item (startLoc <> getLocation item)+ , typeCheckedName >>= \nm -> return $ AST_Param Nothing nm (getLocation nm)+ ]++paramList :: DaoParser (AST_ParamList Object)+paramList = do+ startLoc <- tokenBy "(" asLocation+ (lst, loc) <- commaSepd "parameter value" ")" (return . com [] AST_NoParams) parameter id+ lst <- typeCheckParser lst+ return (AST_ParamList lst (startLoc <> loc))++singletonOrContainerPTab :: DaoPTable (AST_Object Object)+singletonOrContainerPTab = fmap (fmap AST_ObjLiteral) singletonPTab <> containerPTab++singletonOrContainer :: DaoParser (AST_Object Object)+singletonOrContainer = joinEvalPTable singletonOrContainerPTab++----------------------------------------------------------------------------------------------------++commaSepd :: (UStrType str, UStrType errmsg) =>+ errmsg -> str -> ([Comment] -> b) -> DaoParser a -> ([Com a] -> b) -> DaoParser (b, Location)+commaSepd errMsg close voidVal parser constr =+ msum [commented parser >>= loop . (:[]), parseComEmpty, parseClose [] [], err] where+ parseComEmpty = space >>= parseClose []+ parseClose stack c = do+ loc <- tokenBy close asLocation+ return (if null stack && null c then constr stack else if null c then constr stack else voidVal c, loc)+ loop stack = flip mplus (parseClose stack []) $ do+ token COMMA as0+ o <- commented (expect errMsg parser)+ loop (stack++[o])+ err = fail $ "unknown token while parsing list of items for "++uchars errMsg++-- More than one parser has need of 'commaSepd' as a parameter to 'commented', but passing+-- 'commaSped' to 'commented' will return a value of type:+-- > 'Dao.Interpreter.Com' (['Dao.Interpreter.Com'], 'Dao.Parser.Location')+-- which is not useful for constructors of the abstract syntax tree. This function takes the+-- comments around the pair and maps the first item of the pair to the comments, returning an+-- uncommented pair.+commentedInPair :: DaoParser (a, Location) -> DaoParser (Com a, Location)+commentedInPair parser = do+ comntd <- commented parser+ let (a, loc) = unComment comntd+ return (fmap (const a) comntd, loc)++-- You MUST have evaluated 'bufferComments' before evaluating any of the parsers in this table.+-- 'refSuffix' does this.+refSuffixPTabItems :: [DaoTableItem (AST_RefSuffix Object)]+refSuffixPTabItems =+ [ tableItemBy "." $ \tok -> do+ comBefore <- optSpace -- get comments before dot that were buffered by 'bufferComments'+ comAfter <- optSpace -- get comments after the dot+ expect "valid identifier after dot token" $ do+ name <- token LABEL id+ suf <- refSuffix+ let loc = asLocation tok <> getLocation suf+ return $ AST_DotRef (com comBefore () comAfter) (asName name) suf loc+ , p "subscript expression" "[" "]" AST_Subscript+ , p "function call expression" "(" ")" AST_FuncCall+ ]+ where+ p msg open close constr = bindPTableItem (commaSepdObjList msg open close) $ \olst ->+ refSuffix >>= \suf -> return $ constr olst suf++refSuffixPTab :: DaoPTable (AST_RefSuffix Object)+refSuffixPTab = table refSuffixPTabItems++refSuffix :: DaoParser (AST_RefSuffix Object)+refSuffix = bufferComments >> joinEvalPTable refSuffixPTab <|> return AST_RefNull++referencePTabItems :: [DaoTableItem (AST_Reference Object)]+referencePTabItems =+ [ p "local" LOCAL, p "const" CONST, p "static" STATIC, p "global" GLOBAL, p "." GLODOT+ , tableItem LABEL $ \name -> do+ suf <- refSuffix+ return $ AST_Reference UNQUAL [] (asName name) suf (asLocation name)+ , bindPTableItem parenPTabItem $ \o -> refSuffix >>= \suf -> return $+ AST_RefObject o suf (getLocation o <> getLocation suf)+ ]+ where+ p opstr op = tableItemBy opstr $ \tok ->+ expect ("reference expression after "++show opstr++" qualifier") $ do+ coms <- optSpace+ name <- token LABEL id+ suf <- refSuffix+ return $ AST_Reference op coms (asName name) suf (asLocation tok <> asLocation name)++referencePTab :: DaoPTable (AST_Reference Object)+referencePTab = table referencePTabItems++referenceParser :: DaoParser (AST_Reference Object)+referenceParser = joinEvalPTable referencePTab++refPrefixPTabItems :: [DaoTableItem (AST_RefPrefix Object)]+refPrefixPTabItems = [p "$" REF, p "@" DEREF] where+ p opstr op = tableItemBy opstr $ \tok ->+ expect ("reference expression after"++show opstr++" token") $ do+ coms <- optSpace+ ref <- refPrefixParser+ return $ AST_RefPrefix op coms ref (asLocation tok <> getLocation ref)++refPrefixPTab :: DaoPTable (AST_RefPrefix Object)+refPrefixPTab = table refPrefixPTabItems <> (fmap (\o -> AST_PlainRef o) <$> referencePTab)++refPrefixParser :: DaoParser (AST_RefPrefix Object)+refPrefixParser = joinEvalPTable refPrefixPTab++initPTab :: DaoPTable (AST_Object Object)+initPTab = bindPTable refPrefixPTab $ \o -> let single = return (AST_ObjSingle o) in case o of+ AST_PlainRef ref -> case refToDotLabelAST ref of+ Just (ref, inits) -> (bufferComments>>) $ flip mplus single $ do+ olst <- joinEvalPTableItem $ commaSepdObjList "initializing expression" "{" "}"+ coms <- optSpace+ return $ AST_Init ref (AST_OptObjList coms inits) olst (getLocation o <> getLocation olst)+ Nothing -> single+ AST_RefPrefix{} -> single++structPTabItems :: [DaoTableItem (AST_Object Object)]+structPTabItems = (:[]) $ tableItem HASHLABEL $ \nameTok -> do+ bufferComments+ let name = fromUStr $ ustr $ tail $ asString nameTok+ let startLoc = asLocation nameTok+ flip mplus (return $ AST_Struct name nullValue startLoc) $ do+ objList <- joinEvalPTableItem $ commaSepdObjList "data structure initializer" "{" "}"+ let objListLoc = getLocation objList+ return $ AST_Struct name (AST_OptObjList [] (Just objList)) (startLoc<>objListLoc)++structPTab :: DaoPTable (AST_Object Object)+structPTab = table structPTabItems++arithPrefixPTab :: DaoPTable (AST_Object Object)+arithPrefixPTab = table $ (logicalNOT:) $ flip fmap ["~", "-", "+"] $ \pfxOp ->+ tableItemBy pfxOp $ \tok -> optSpace >>= \coms -> object >>= \o ->+ return (AST_ArithPfx (fromUStr (tokTypeToUStr (asTokType tok))) coms o (asLocation tok))+ where+ logicalNOT = tableItemBy "!" $ \op -> do+ coms <- optSpace+ o <- object+ return $ AST_ArithPfx (fromUStr $ tokTypeToUStr $ asTokType op) coms o (asLocation op)++-- This table extends the 'funcCallArraySubPTab' table with the 'arithPrefixPTab' table. The+-- 'containerPTab' is also included at this level. It is the most complicated (and therefore lowest+-- prescedence) object expression that can be formed without making use of infix operators.+objectPTab :: DaoPTable (AST_Object Object)+objectPTab = mconcat [singletonOrContainerPTab, arithPrefixPTab, structPTab, initPTab]++-- Evaluates 'objectPTab' to a 'DaoParser'.+object :: DaoParser (AST_Object Object)+object = joinEvalPTable objectPTab++-- A constructor that basically re-arranges the arguments to the 'Dao.Interpreter.AST.AST_Eval'+-- constructor such that this function can be used as an argument to 'Dao.Parser.sinpleInfixed'+-- or 'Dao.Parser.newOpTableParser'.+arithConstr :: AST_Arith Object -> (Location, Com InfixOp) -> AST_Arith Object -> DaoParser (AST_Arith Object)+arithConstr left (loc, op) right = return $ AST_Arith left op right loc++-- Parses a sequence of 'object' expressions interspersed with arithmetic infix opreators.+-- All infixed logical operators are included, assignment operators are not. The only prefix logical+-- operator. Logical NOT @(!)@ is not parsed here but in the 'arithmetic' function.+arithOpTable :: OpTableParser DaoParState DaoTT (Location, Com InfixOp) (AST_Arith Object)+arithOpTable =+ newOpTableParser "arithmetic expression" False+ (\tok -> do+ op <- commented (shift (fromUStr . tokTypeToUStr . asTokType))+ return (asLocation tok, op)+ )+ (object >>= \o -> bufferComments >> return (AST_Object o))+ arithConstr+ ( opRight ["->"] arithConstr+ : opRight ["**"] arithConstr+ : fmap (\ops -> opLeft (words ops) arithConstr)+ ["* / %", "+ -", "<< >>", "&", "^", "|", "< <= >= >", "== !="]+ ++ [opRight ["&&"] arithConstr, opRight [ "||"] arithConstr]+ )++arithmeticPTab :: DaoPTable (AST_Arith Object)+arithmeticPTab = bindPTable objectPTab $ \o ->+ evalOpTableParserWithInit (bufferComments >> return (AST_Object o)) arithOpTable++-- Evalautes the 'arithOpTable' to a 'DaoParser'.+arithmetic :: DaoParser (AST_Arith Object)+arithmetic = joinEvalPTable arithmeticPTab++objTestPTab :: DaoPTable (AST_ObjTest Object)+objTestPTab = mappend (fmap AST_ObjRuleFunc <$> ruleFuncPTab) $ bindPTable arithmeticPTab $ \a -> do+ bufferComments+ flip mplus (return $ AST_ObjArith a) $ do+ qmark <- commented (tokenBy "?" as0)+ expect "arithmetic expression after (?) operator" $ do+ b <- arithmetic+ expect "(:) operator and arithmetic expression after (?) operator" $ do+ coln <- commented (tokenBy ":" as0)+ expect "arithmetic expression after (:) operator" $ do+ c <- arithmetic+ return $ AST_ObjTest a qmark b coln c (getLocation a <> getLocation c)++objTest :: DaoParser (AST_ObjTest Object)+objTest = joinEvalPTable objTestPTab++assignmentWithInit :: DaoParser (AST_Assign Object) -> DaoParser (AST_Assign Object)+assignmentWithInit init = + simpleInfixedWithInit "object expression for assignment operator" rightAssoc+ (\left (loc, op) right -> return $ case left of+ AST_Eval left -> AST_Assign left op right loc+ left -> left+ )+ (bufferComments >> init)+ (fmap AST_Eval objTest)+ (pure (,) <*> look1 asLocation <*> commented (joinEvalPTable opTab))+ where+ opTab :: DaoPTable UpdateOp+ opTab = table $+ fmap (flip tableItemBy (return . fromUStr . tokTypeToUStr . asTokType)) (words allUpdateOpStrs)++assignmentPTab :: DaoPTable (AST_Assign Object)+assignmentPTab = bindPTable objTestPTab (assignmentWithInit . return . AST_Eval)++-- | Evaluates a sequence arithmetic expressions interspersed with assignment operators.+assignment :: DaoParser (AST_Assign Object)+assignment = joinEvalPTable assignmentPTab++----------------------------------------------------------------------------------------------------++bracketed :: String -> DaoParser (AST_CodeBlock Object, Location)+bracketed msg = do+ startLoc <- tokenBy "{" asLocation+ scrps <- concat <$> (many script <|> return [])+ expect ("curly-bracket to close "++msg++" statement") $ do+ _ <- look1 id+ endLoc <- tokenBy "}" asLocation+ return (AST_CodeBlock scrps, startLoc<>endLoc)++script :: DaoParser [AST_Script Object]+script = joinEvalPTable scriptPTab++ifWhilePTabItem :: String -> (AST_If Object -> a) -> DaoTableItem a+ifWhilePTabItem keyword constr = tableItemBy keyword $ \tok -> do+ o <- commented paren+ (thn, loc) <- bracketed keyword+ return $ constr $ AST_If o thn (asLocation tok <> loc)++whilePTabItem :: DaoTableItem (AST_While Object)+whilePTabItem = ifWhilePTabItem "while" AST_While++ifPTabItem :: DaoTableItem (AST_If Object)+ifPTabItem = ifWhilePTabItem "if" id++ifStatement :: DaoParser (AST_If Object)+ifStatement = joinEvalPTableItem ifPTabItem++lastElseParser :: DaoParser (AST_LastElse Object)+lastElseParser = do+ comTok <- commented $ tokenBy "else" asLocation+ (els, endLoc) <- bracketed "else statement"+ return $ AST_LastElse (fmap (const ()) comTok) els (unComment comTok <> endLoc)++catchExprParser :: DaoParser (AST_Catch Object)+catchExprParser = do+ bufferComments+ startLoc <- tokenBy "catch" asLocation+ expect "parameter variable name/type after \"catch\" statement" $ do+ coms <- optSpace+ param <- commented parameter+ (scrpt, endLoc) <- bracketed "\"catch\" statement"+ return $ AST_Catch coms param scrpt (startLoc <> endLoc)++ifElsePTabItem :: DaoTableItem (AST_IfElse Object)+ifElsePTabItem = bindPTableItem ifPTabItem (loop []) where+ loop elsx ifExpr = msum $+ [ do com1 <- optSpace+ elsLoc <- tokenBy "else" asLocation+ expect "bracketed expression, or another \"if\" expression after \"else\" statement" $ do+ com2 <- optSpace+ msum $+ [ do nextIf <- ifStatement+ loop (elsx++[AST_Else (com com1 () com2) nextIf (elsLoc <> getLocation nextIf)]+ ) ifExpr+ , do (els, endLoc) <- bracketed "else statement"+ return $+ AST_IfElse ifExpr elsx+ (Just $ AST_LastElse (com com1 () com2) els (getLocation els <> endLoc))+ (getLocation ifExpr <> endLoc)+ ]+ , return (AST_IfElse ifExpr elsx Nothing (getLocation ifExpr))+ ]++scriptPTab :: DaoPTable [AST_Script Object]+scriptPTab = comments <> objExpr <> table exprs where+ -- Object expressions should end with a semi-colon. An exception to this rule is made for rule and+ -- function constant expressions.+ objExpr = bindPTable assignmentPTab $ \o -> case o of+ AST_Eval (AST_ObjRuleFunc o) ->+ flip mplus (return [AST_RuleFunc o]) $ do+ coms <- optSpace+ loc <- mappend (getLocation o) <$> tokenBy ";" asLocation+ return [AST_EvalObject (AST_Eval $ AST_ObjRuleFunc o) coms loc]+ o -> do+ coms <- optSpace+ expect "semicolon after object expression" $ do+ endLoc <- tokenBy ";" asLocation+ return [AST_EvalObject o coms (getLocation o <> endLoc)]+ comments = bindPTable spaceComPTab $ \c1 -> optSpace >>= \c2 ->+ let coms = c1++c2 in if null coms then return [] else return [AST_Comment coms]+ exprs =+ [ fmap (fmap (return . AST_WhileLoop)) whilePTabItem + , fmap (fmap (return . AST_IfThenElse)) ifElsePTabItem+ , returnExpr "return" True+ , returnExpr "throw" False+ , continExpr "continue" True+ , continExpr "break" False+ , tableItemBy "try" $ \tok -> expect "bracketed script after \"try\" statement" $ do+ coms <- optSpace+ (try, endLoc) <- bracketed "\"try\" statement"+ elsExprs <- many lastElseParser+ catchExprs <- many catchExprParser+ let finalLoc :: forall a . HasLocation a => [a] -> Location+ finalLoc = foldl (\_ a -> getLocation a) endLoc+ let loc = asLocation tok <> finalLoc elsExprs <> finalLoc catchExprs+ return [AST_TryCatch coms try elsExprs catchExprs loc]+ , tableItemBy "for" $ \tok -> expect "iterator label after \"for statement\"" $ do+ comName <- commented (token LABEL asName)+ expect "\"in\" statement after \"for\" statement" $ do+ tokenBy "in" as0+ expect "object expression over which to iterate of \"for-in\" statement" $ do+ o <- commented refPrefixParser+ expect "bracketed script after \"for-in\" statement" $ do+ (for, endLoc) <- bracketed "\"for\" statement"+ return [AST_ForLoop comName o for (asLocation tok <> endLoc)]+ , tableItemBy "with" $ \tok -> expect "reference expression after \"with\" statement" $ do+ o <- commented paren+ expect "bracketed script after \"with\" statement" $ do+ (with, endLoc) <- bracketed "\"with\" statement"+ return [AST_WithDoc o with (asLocation tok <> endLoc)]+ ]+ semicolon = tokenBy ";" asLocation+ returnExpr key isReturn = tableItemBy key $ \tok -> do+ o <- commented (assignment <|> return nullValue)+ expect ("semicolon after \""++key++"\" statement") $ do+ endLoc <- semicolon+ return [AST_ReturnExpr isReturn o (asLocation tok <> endLoc)]+ continExpr key isContin = tableItemBy key $ \tok -> do+ let startLoc = asLocation tok+ let msg e = concat [e, " after \"", key, "-if\" statement"]+ coms <- optSpace+ msum $+ [do endLoc <- semicolon+ return [AST_ContinueExpr isContin coms (Com nullValue) (startLoc<>endLoc)]+ ,do tokenBy "if" as0+ expect (msg "conditional expression") $ do+ o <- commented assignment+ expect (msg "semicolon") $ do+ endLoc <- semicolon+ return [AST_ContinueExpr isContin coms o (startLoc<>endLoc)]+ , fail (msg "expecting optional object expression followed by a semicolon")+ ]++----------------------------------------------------------------------------------------------------++dotName :: DaoParser AST_DotName+dotName = return AST_DotName <*> commented (tokenBy "." as0) <*> token LABEL asName++dotLabelTableItem :: DaoTableItem AST_DotLabel+dotLabelTableItem = tableItem LABEL $ \tok ->+ return (AST_DotLabel (asName tok)) <*> many dotName <*> pure (asLocation tok)++attributePTab :: DaoPTable AST_Attribute+attributePTab = table $+ [ fmap AST_AttribDotName <$> dotLabelTableItem+ , tableItem STRINGLIT $ \tok -> return $ AST_AttribString (asUStr tok) (asLocation tok)+ ]++attribute :: DaoParser AST_Attribute+attribute = joinEvalPTable attributePTab++namespace :: DaoParser AST_Namespace+namespace = do+ tokenBy "as" as0+ nm <- commented $ token LABEL id+ return $ AST_Namespace (fmap asName nm) (unComment $ fmap asLocation nm)++----------------------------------------------------------------------------------------------------++toplevelPTab :: DaoPTable [AST_TopLevel Object]+toplevelPTab = table expr <> comments <> scriptExpr where+ comments = bindPTable spaceComPTab $ \c1 -> optSpace >>= \c2 -> return $+ let coms = c1++c2 in if null coms then [] else [AST_TopComment (c1++c2)]+ scriptExpr = bindPTable scriptPTab $ return . map (\o -> AST_TopScript o (getLocation o))+ expr =+ [ event "BEGIN" , event "END" , event "EXIT"+ , tableItemBy "require" $ \startTok -> needAttrib "require" $ do+ attrib <- commented attribute+ endTok <- needSemicolon "require"+ return [AST_Require attrib $ asLocation startTok <> endTok]+ , tableItemBy "import" $ \startTok -> needAttrib "import" $ do+ attrib <- commented attribute+ mplus+ (do namesp <- namespace+ endTok <- needSemicolon "import"+ return [AST_Import attrib namesp $ asLocation startTok <> endTok]+ )+ (return . AST_Import attrib nullValue .+ mappend (asLocation startTok) <$> needSemicolon "import")+ ]+ needSemicolon msg =+ expect ("expecting semicolon after \""++msg++"\" statement") $ tokenBy ";" asLocation+ needAttrib msg =+ expect ("expect string literal or logical module name after \""++msg++"\" statement")+ event lbl = tableItemBy lbl $ \tok -> do+ let exprType = show (asTokType tok)+ coms <- optSpace+ expect ("bracketed script after \""++exprType++"\" statement") $ do+ (event, endLoc) <- bracketed ('"':exprType++"\" statement")+ return [AST_Event (read lbl) coms event (asLocation tok <> endLoc)]++toplevel :: DaoParser [AST_TopLevel Object]+toplevel = joinEvalPTable toplevelPTab++----------------------------------------------------------------------------------------------------++daoParser :: DaoParser (AST_SourceCode Object)+daoParser = do+ let loop dx = msum+ [ isEOF >>= guard >> return dx+ , toplevel >>= \d -> loop (dx++d)+ , fail "syntax error on token"+ ]+ src <- loop []+ return (AST_SourceCode{sourceModified=0, sourceFullPath=nil, directives=src})++daoGrammar :: Language DaoParState DaoTT (AST_SourceCode Object)+daoGrammar = newLanguage 4 $ mplus daoParser $ fail "Parser backtracked without taking all input."++----------------------------------------------------------------------------------------------------++testDaoLexer :: String -> IO ()+testDaoLexer = testLexicalAnalysis (tokenDBLexer daoTokenDB) 4++testDaoParser :: String -> IO ()+testDaoParser input = case parse daoGrammar mempty input of+ OK a -> putStrLn ("Parser succeeded:\n"++prettyShow a)+ Backtrack -> testDaoLexer input >> putStrLn "---- PARSER BACKTRACKED ----\n"+ PFail err -> do+ testDaoLexer input+ putStrLn ("---- PARSER FAILED ----\n" ++ show err)+ let st = parseStateAtErr err >>= internalState+ putStrLn ("recentTokens = "++show (tokenQueue <$> st))+ putStrLn ("getLines = "++show (getLines <$> st))+
+ src/Dao/Interpreter/Tokenizer.hs view
@@ -0,0 +1,136 @@+-- "src/Dao/Interpreter/Tokenizer.hs" defines the+-- tokenizer for the Dao programming language.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++{-# LANGUAGE MultiParamTypeClasses #-}++module Dao.Interpreter.Tokenizer where++import Dao.String+import Dao.Interpreter.AST+import Dao.Parser++import Control.Monad.Error hiding (Error)++import Data.Monoid+import Data.Ix++----------------------------------------------------------------------------------------------------++newtype DaoTT = DaoTT{ unwrapDaoTT :: TT } deriving (Eq, Ord, Ix)+instance TokenType DaoTT where { unwrapTT = unwrapDaoTT; wrapTT = DaoTT }+instance HasTokenDB DaoTT where { tokenDB = daoTokenDB }+instance MetaToken DaoTokenLabel DaoTT where { tokenDBFromMetaValue _ = tokenDB }+instance Show DaoTT where { show = deriveShowFromTokenDB daoTokenDB }++type DaoLexer = Lexer DaoTT ()++daoTokenDB :: TokenDB DaoTT+daoTokenDB = makeTokenDB daoTokenDef++data DaoTokenLabel+ = SPACE | INLINECOM | ENDLINECOM | COMMA | LABEL | HASHLABEL | STRINGLIT | CHARLIT | DATE | TIME+ | BASE10 | DOTBASE10 | NUMTYPE | EXPONENT | BASE16 | BASE2+ deriving (Eq, Enum, Show, Read)+instance UStrType DaoTokenLabel where { toUStr = derive_ustr; fromUStr = derive_fromUStr; }++daoTokenDef :: LexBuilder DaoTT+daoTokenDef = do+ ------------------------------------------ WHITESPACE -------------------------------------------+ let spaceRX = rxRepeat1(map ch "\t\n\r\f\v ")+ space <- emptyToken SPACE $ spaceRX+ + ------------------------------------------- COMMENTS --------------------------------------------+ closeInliner <- fullToken INLINECOM $ rxRepeat1(ch '*') . rx '/'+ inlineCom <- fullToken INLINECOM $ rx "/*" .+ fix (\loop -> closeInliner <> rxRepeat1(invert[ch '*']) . loop)+ endlineCom <- fullToken ENDLINECOM $ rx "//" . rxRepeat(invert[ch '\n'])+ + -------------------------------------------- LABELS ---------------------------------------------+ let alpha = [from 'A' to 'Z', from 'a' to 'z', ch '_']+ labelRX = rxRepeat1 alpha . rxRepeat(from '0' to '9' : alpha)+ label <- fullToken LABEL $ labelRX+ hashlabel <- fullToken HASHLABEL $ rx '#' . labelRX+ + ---------------------------------------- NUMERICAL TYPES ----------------------------------------+ let from0to9 = from '0' to '9'+ plusMinus = rx[ch '+', ch '-']+ dot = rx '.'+ number = rxRepeat1 from0to9+ base10 <- fullToken BASE10 $ number+ dotBase10 <- fullToken DOTBASE10 $ dot . base10+ exponent <- fullToken EXPONENT $ rx[ch 'e', ch 'E'] .+ cantFail "expecting exponent value after 'e' or 'E' character" . opt plusMinus . base10+ base2 <- fullToken BASE2 $ (rx "0b" <> rx "0B") . base10+ base16 <- fullToken BASE16 $ (rx "0x" <> rx "0X") .+ rxRepeat[from0to9, from 'A' to 'F', from 'a' to 'f']+ numType <- fullToken NUMTYPE $ rx (map ch "UILRFfijs")+ let base10Parser = mconcat $+ [ dotBase10 . opt exponent . opt numType+ , base10 . opt dotBase10 . opt exponent . opt numType+ , base10 . dot . exponent . opt numType+ , base10 . opt exponent . opt numType+ ]+ ---------------------------------------- STRING LITERAL ----------------------------------------+ let litExpr op = rx op . (fix $ \loop ->+ rxRepeat(invert [ch op, ch '\\']) . (rx "\\" . rx anyChar . loop <> rx op))+ stringLit <- fullToken STRINGLIT $ litExpr '"'+ charLit <- fullToken CHARLIT $ litExpr '\''+ -------------------------------------- KEYWORDS AND GROUPING ------------------------------------+ openers <- operatorTable $ words "( [ { ${"+ comma <- emptyToken COMMA (rx ',')+ daoKeywords <- keywordTable LABEL labelRX $ words $ unwords $+ [ "local const static global"+ , "null false true date time function func rule"+ , "if else for in while with try catch continue break return throw"+ , "BEGIN END EXIT import require"+ , "struct union operator public private" -- other reserved keywords, but they don't do anything yet.+ ]+ let withKeyword key func = do+ tok <- getTokID key :: LexBuilderM DaoTT+ return (rx key . (label <> rxEmptyToken tok . func))+ closers <- operatorTable $ words "} ] )"+ + ------------------------------------------- OPERATORS -------------------------------------------+ operators <- operatorTable $ words $ unwords $+ [allUpdateOpStrs, allPrefixOpStrs, allInfixOpStrs, ": ;"]+ + ------------------------------------ DATE/TIME SPECIAL SYNTAX -----------------------------------+ -- makes use of token types that have already been created above+ let year = rxLimitMinMax 4 5 from0to9+ dd = rxLimitMinMax 1 2 from0to9+ col = rx ':'+ hy = rx '-'+ timeRX = dd . col . dd . col . dd . opt(dot . number)+ timeExpr <- fullToken TIME timeRX+ -- time <- withKeyword "time" $ cantFail "time expression" . space . timeExpr+ time <- withKeyword "time" $ space . timeExpr+ dateExpr <- fullToken DATE $ year . hy . dd . hy . dd+ -- date <- withKeyword "date" $ cantFail "date expression" .+ date <- withKeyword "date" $ space . dateExpr . opt(space . timeExpr) . opt(space . label)+ + ------------------------------------------- ACTIVATE --------------------------------------------+ -- activate $+ return $ regexToTableLexer $+ [ space, inlineCom, endlineCom, comma+ , stringLit, charLit, base16, base2, base10Parser+ , openers, hashlabel, operators, closers+ , date, time, daoKeywords+ ]+
+ src/Dao/Interval.hs view
@@ -0,0 +1,910 @@+-- "src/Dao/SetM.hs" defines the Interval data type used to denote+-- a possibly infinite subset of contiguous elements of an Enum data type.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}++-- | This module provides what I believe to be a better implementation of mathematical intervals+-- than what is provided by the "interval" and "data-interval" packages, although more work has yet+-- to be done instantiating all of the classes in the "latices" package.+--+-- This module improves on the concept of intervals by making them more general, specifically in+-- that intervals need only instantiate 'Prelude.Enum' rather than 'Prelude.Num'. This means+-- 'Prelude.Char' can now be used to create intervals, which is highly useful for constructing and+-- reasoning about regular expressions and parsers.+--+-- Another improvement provided by this module over other interval modules is that non-contiguous+-- interval sets can be constructed. Thus there are two data types, 'Interval' which is never empty+-- and can be used to construct 'Set's, and 'Set's which may or may not be empty or infinite, and do+-- the work of what the @Data.Interval.Interval@ data type would otherwise do.+module Dao.Interval+ ( -- * The 'Inf' data type+ Inf(NegInf, PosInf, Finite)+ , stepDown, stepUp, toPoint, enumIsInf+ , InfBound, minBoundInf, maxBoundInf+ -- * the 'Interval' data type+ , Interval, startPoint, endPoint, segment, single, wholeInterval, negInfTo, toPosInf, enumInfSeg+ , toBounded, toBoundedPair, segmentMember, singular, plural, segmentNub, segmentInvert+ -- * Predicates on 'Interval's+ , containingSet, numElems, isWithin, segmentHasEnumInf, segmentIsInfinite+ -- * The 'SetM' monadic data type+ , SetM, infiniteM, fromListM, rangeM, pointM+ , toListM, defaultM, memberM, lookupM, nullM, isSingletonM+ -- * Set Operators for monadic 'SetM's+ , sieveM, invertM, setXUnionM, unionM, intersectM, deleteM+ , setToSetM, setMtoSet+ -- * The 'Set' non-monadic data type+ , Set, whole, fromList, fromPairs, range, point+ , toList, elems, member, Dao.Interval.null, isSingleton+ -- * Set Operators for non-monadic 'Set's+ , Dao.Interval.invert, setXUnion, Dao.Interval.union, Dao.Interval.intersect, Dao.Interval.delete+ -- * Miscelaneous+ , upperTriangular, nonAssociativeProduct+ )+ where++import Data.Monoid+import Data.List+import Data.Ratio++import Control.Monad+import Control.Applicative+import Control.DeepSeq++-- | Like 'Prelude.Bounded', except the bounds might be infiniteM, and return 'NegInf' or+-- 'PosInf' for the bounds. Using the GHC "flexible instances" and "undecidable instances"+-- feature, any data type that is an instance of 'Prelude.Bounded' is also a memberM of 'BoundInf'.+class InfBound c where+ minBoundInf :: Inf c+ maxBoundInf :: Inf c++instance InfBound () where { minBoundInf = Finite (); maxBoundInf = Finite (); }+instance InfBound Int where { minBoundInf = Finite minBound; maxBoundInf = Finite maxBound; }+instance InfBound Char where { minBoundInf = Finite minBound; maxBoundInf = Finite maxBound; }+instance InfBound Integer where { minBoundInf = NegInf; maxBoundInf = PosInf; }+instance InfBound (Ratio Integer) where { minBoundInf = NegInf; maxBoundInf = PosInf; }+instance InfBound Float where { minBoundInf = NegInf; maxBoundInf = PosInf; }+instance InfBound Double where { minBoundInf = NegInf; maxBoundInf = PosInf; }++-- | Enumerable elements with the possibility of infinity.+data Inf c+ = NegInf -- ^ negative infinity+ | PosInf -- ^ positive infinity+ | Finite c -- ^ a single pointM+ deriving Eq++enumIsInf :: Inf c -> Bool+enumIsInf c = case c of+ NegInf -> True+ PosInf -> True+ _ -> False++instance Ord c => Ord (Inf c) where+ compare a b = f a b where+ f a b | a == b = EQ+ f NegInf _ = LT+ f _ NegInf = GT+ f PosInf _ = GT+ f _ PosInf = LT+ f (Finite a) (Finite b) = compare a b++instance Show c => Show (Inf c) where+ show e = case e of+ Finite c -> show c+ NegInf -> "-infnt"+ PosInf -> "+infnt"++instance Functor Inf where+ fmap f e = case e of+ NegInf -> NegInf+ PosInf -> PosInf+ Finite e -> Finite (f e)++-- | Increment a given value, but if the value is 'Prelude.maxBound', return 'PosInf'. In some+-- circumstances this is better than incrementing with @'Data.Functor.fmap' 'Prelude.succ'@ because+-- 'Prelude.succ' evaluates to an error when passing 'Prelude.maxBound' as the argument. This+-- function will never evaluate to an error.+stepUp :: (Eq c, Enum c, InfBound c) => Inf c -> Inf c+stepUp x = if x==maxBoundInf then PosInf else fmap succ x++-- | Decrement a given value, but if the value is 'Prelude.minBound', returns 'NegInf'. In some+-- circumstances this is better than incrementing @'Data.Functor.fmap' 'Prelude.pred'@ because+-- 'Prelude.pred' evaluates to an error when passing 'Prelude.maxBound' as the argument. This+-- function will never evaluate to an error.+stepDown :: (Eq c, Enum c, InfBound c) => Inf c -> Inf c+stepDown x = if x==minBoundInf then NegInf else fmap pred x++-- | Retrieve the value contained in an 'Inf', if it exists.+toPoint :: Inf c -> Maybe c+toPoint c = case c of+ Finite c -> Just c+ _ -> Nothing++-- | A enumInfSeg of 'Inf' items is a subset of consectutive items in the set of all @c@ where @c@+-- is any type satisfying the 'Prelude.Ix' class. To construct a 'Interval' object, use 'enumInfSeg'.+data Interval c+ = Single { startPoint :: Inf c }+ | Interval { startPoint :: Inf c, endPoint :: Inf c }+ deriving Eq+ -- NOTE: the constructor for this data type is not exported because all of the functions in this+ -- module that operate on 'Interval's make the assumption that the first parameter *less than* the+ -- second parameter. To prevent anyone from screwing it up, the constructor is hidden and+ -- constructing a 'Interval' must be done with the 'enumInfSeg' function which checks the parameters.++instance Ord c => Ord (Interval c) where+ compare x y = case x of+ Single a -> case y of+ Single b -> compare a b+ Interval b _ -> if a==b then LT else compare a b+ Interval a b -> case y of+ Single a' -> if a==b then GT else compare a a'+ Interval a' b' -> if a==a' then compare b' b else compare a a'++-- not exported+mkSegment :: Eq c => Inf c -> Inf c -> Interval c+mkSegment a b+ | a==b = Single a+ | otherwise = Interval a b++-- | If the 'Interval' was constructed with 'single', return the pointM (possibly 'PosInf' or+-- 'NegInf') value used to construct it, otherwise return 'Data.Maybe.Nothing'.+singular :: Interval a -> Maybe (Inf a)+singular seg = case seg of+ Interval _ _ -> mzero+ Single a -> return a++-- | If the 'Interval' was constructed with 'segment', return a pair of points (possibly 'PosInf'+-- or 'NegInf') value used to construct it, otherwise return 'Data.Maybe.Nothing'.+plural :: Interval a -> Maybe (Inf a, Inf a)+plural a = case a of+ Interval a b -> return (a, b)+ Single _ -> mzero++showSegment :: Show c => Interval c -> String+showSegment seg = case seg of+ Single a -> "at "++show a+ Interval a b -> "from "++show a++" to "++show b++-- | This gets rid of as many infiniteM elements as possible. All @'Single' 'PosInf'@ and+-- @'Single' 'NegInf'@ points are eliminated, and if an 'NegInf' or 'PosInf' can be+-- replaced with a corresponding 'minBoundInf' or 'maxBoundInf', then it is. This function is+-- intended to be used as a list monadic function, so use it like so:+-- @let myListOfSegments = [...] in myListOfSegments >>= 'delInfPoints'@+canonicalSegment :: (Eq c, InfBound c) => Interval c -> [Interval c]+canonicalSegment seg = nonInf seg >>= \seg -> case seg of+ Single a -> [Single a]+ Interval a b -> nonInf (mkSegment (bounds a) (bounds b))+ where+ nonInf seg = case seg of+ Single NegInf -> []+ Single PosInf -> []+ Single a -> [Single a ]+ Interval a b -> [Interval a b]+ bounds x = case x of+ NegInf -> minBoundInf+ PosInf -> maxBoundInf+ x -> x++instance Show c =>+ Show (Interval c) where { show seg = showSegment seg }++-- | A predicate evaluating whether or not a segment includes an 'PosInf' or 'NegInf' value.+-- This should not be confused with a predicate evaluating whether the set of elements included by+-- the rangeM is infiniteM, because types that are instances of 'Prelude.Bounded' may also contain+-- 'PosInf' or 'NegInf' elements, values of these types may be evaluated as "infintie" by+-- this function, even though they are 'Prelude.Bounded'. To check if a segment is infiniteM, use+-- 'segmentIsInfinite' instead.+segmentHasEnumInf :: Interval c -> Bool+segmentHasEnumInf seg = case seg of+ Single c -> enumIsInf c+ Interval a b -> enumIsInf a || enumIsInf b++-- | A predicate evaluating whether or not a segment is infiniteM. Types that are 'Prelude.Bounded'+-- are always finite, and thus this function will always evaluate to 'Prelude.False' for these+-- types.+segmentIsInfinite :: InfBound c => Interval c -> Bool+segmentIsInfinite seg = case [Single minBoundInf, Single maxBoundInf, seg] of+ [Single a, Single b, c] | enumIsInf a || enumIsInf b -> case c of+ Single c -> enumIsInf c+ Interval a b -> enumIsInf a || enumIsInf b+ _ -> False++-- | Construct a 'Interval' from two 'Inf' items. /NOTE/ if the 'Inf' type you are+-- constructing is an instance of 'Prelude.Bounded', use the 'boundedSegment' constructor instead of+-- this function.+enumInfSeg :: (Ord c, Enum c, InfBound c) => Inf c -> Inf c -> Interval c+enumInfSeg a b = seg a b where+ seg a b = construct (ck minBoundInf NegInf a) (ck maxBoundInf PosInf b)+ ck infnt subst ab = if infnt==ab then subst else ab+ construct a b+ | a == b = Single a+ | a < b = Interval a b+ | otherwise = Interval b a++-- | Construct a 'Interval' from two values.+segment :: Ord c => c -> c -> Interval c+segment a b = mkSegment (Finite (min a b)) (Finite (max a b))++-- | Construct a 'Interval' that is only a single unit, i.e. it starts at X and ends at X.+single :: Ord c => c -> Interval c+single a = segment a a++-- | Construct a 'Interval' from negative infinity to a given value.+negInfTo :: InfBound c => c -> Interval c+negInfTo a = Interval minBoundInf (Finite a)++-- | Construct a 'Interval' from a given value to positive infinity.+toPosInf :: InfBound c => c -> Interval c+toPosInf a = Interval (Finite a) maxBoundInf++-- | Construct the infiniteM 'Interval'+wholeInterval :: Interval c+wholeInterval = Interval NegInf PosInf++-- | Tests whether an element is a memberM is enclosed by the 'Interval'.+segmentMember :: Ord c => Interval c -> c -> Bool+segmentMember seg c = case seg of+ Single (Finite d) -> c == d+ Interval lo hi -> let e = Finite c in lo <= e && e <= hi+ _ -> False++-- | Construct a 'Interval', like the 'enumInfSeg' constructor above, however does not require 'Inf'+-- parameters as inputs. This function performs the additional check of testing whether or not a+-- value is equivalent to 'Prelude.minBound' or 'Prelude.maxBound', and if it is, replaces that+-- value with 'NegInf' or 'PosInf' respectively. In other words, you can use+-- 'Prelude.minBound' in place of NegInf and 'Prelude.maxBound' in place of 'PosInf' without+-- changing the semantics of the data structure as it is used throughout the program.+-- boundedSegment :: (Ord c, Enum c, Bounded c) => c -> c -> Interval c+-- boundedSegment a b = if a>b then co b a else co a b where+-- co a b = enumInfSeg (f a minBound NegInf) (f b maxBound PosInf)+-- f x bound infnt = if x==bound then infnt else Finite x++-- | If an 'Inf' is also 'Prelude.Bounded' then you can convert it to some value in the set of+-- 'Prelude.Bounded' items. 'NegInf' translates to 'Prelude.minBound', 'PosInf' translates+-- to 'Prelude.maxBound', and 'Finite' translates to the value at that pointM.+toBounded :: Bounded c => Inf c -> c+toBounded r = case r of+ NegInf -> minBound+ PosInf -> maxBound+ Finite c -> c++-- | Like 'toBounded', but operates on a segment and returns a pair of values.+toBoundedPair :: (Enum c, Bounded c) => Interval c -> (c, c)+toBoundedPair r = case r of+ Single c -> (toBounded c, toBounded c)+ Interval c d -> (toBounded c, toBounded d)++enumBoundedPair :: (Enum c, Bounded c) => Interval c -> [c]+enumBoundedPair seg = let (lo, hi) = toBoundedPair seg in [lo..hi]++-- | Computes the minimum 'Interval' that can contain the list of all given 'EnumRanges'.+-- 'Data.Maybe.Nothing' indicates the empty set.+containingSet :: (Ord c, Enum c, InfBound c) => [Interval c] -> Maybe (Interval c)+containingSet ex = foldl fe Nothing ex where+ fe Nothing a = Just a+ fe (Just a) c = Just $ case a of+ Single a -> case c of+ Single c -> enumInfSeg (min a c) (max a c)+ Interval c d -> enumInfSeg (min a c) (max a d)+ Interval a b -> case c of+ Single c -> enumInfSeg (min a b) (max b c)+ Interval c d -> enumInfSeg (min a c) (max b d)++-- | Evaluates to the number of elements covered by this region. Returns 'Prelude.Nothing' if there+-- are an infiniteM number of elements. For data of a type that is not an instance of 'Prelude.Num',+-- for example @'Interval' 'Data.Char.Char'@, it is recommended you first convert to the type+-- @'Interval' 'Data.Int.Int'@ using @'Control.Functor.fmap' 'Prelude.fromEnum'@ before using this+-- function, then convert the result back using @'Control.Functor.fmap' 'Prelude.toEnum'@ if+-- necessary.+numElems :: (Integral c, Enum c) => Interval c -> Maybe Integer+numElems seg = case seg of+ Single (Finite _) -> Just 1+ Interval (Finite a) (Finite b) -> Just (fromIntegral a - fromIntegral b + 1)+ _ -> Nothing++-- | Tests whether an 'Inf' is within the enumInfSeg. It is handy when used with backquote noation:+-- @enumInf `isWithin` enumInfSeg@+isWithin :: (Ord c, Enum c) => Inf c -> Interval c -> Bool+isWithin pointM seg = case seg of+ Single x -> pointM == x+ Interval NegInf hi -> pointM <= hi+ Interval lo PosInf -> lo <= pointM+ Interval lo hi -> lo <= pointM && pointM <= hi++-- | Returns true if two 'Interval's are intersecting.+areIntersecting :: (Ord c, Enum c) => Interval c -> Interval c -> Bool+areIntersecting a b = case a of+ Single aa -> case b of+ Single bb -> aa == bb+ Interval _ _ -> aa `isWithin` b+ Interval x y -> case b of+ Single bb -> bb `isWithin` a+ Interval x' y' -> x' `isWithin` a || y' `isWithin` a || x `isWithin` b || y `isWithin` b++-- | Returns true if two 'Interval's are consecutive, that is, if the end is the 'Prelude.pred'essor+-- of the start of the other.+areConsecutive :: (Ord c, Enum c, InfBound c) => Interval c -> Interval c -> Bool+areConsecutive a b = case a of+ Single a -> case b of+ Single b+ | a < b -> consec a b+ | otherwise -> consec b a+ Interval x y+ | a < x -> consec a x+ | otherwise -> consec y a+ Interval x y -> case b of + Single a+ | a < x -> consec a x+ | otherwise -> consec y a+ Interval x' y'+ | y < x' -> consec y x'+ | otherwise -> consec y' x+ where { consec a b = stepUp a == b || a == stepDown b }++-- | Performs a set union on two 'Interval's of elements to create a new enumInfSeg. If the elements of+-- the new enumInfSeg are not contiguous, each enumInfSeg is returned separately and unchanged. The first+-- item in the pair of items returned is 'Prelude.True' if any of the items were modified.+segmentUnion :: (Ord c, Enum c, InfBound c) => Interval c -> Interval c -> (Bool, [Interval c])+segmentUnion a b+ | areIntersecting a b = case a of+ Single _ -> case b of+ Single _ -> (True, [a])+ Interval _ _ -> (True, [b])+ Interval x y -> case b of+ Single _ -> (True, [a])+ Interval x' y' -> (True, [enumInfSeg (min x x') (max y y')])+ | areConsecutive a b = case a of+ Single aa -> case b of+ Single bb -> (True, [enumInfSeg aa bb ])+ Interval x y -> (True, [enumInfSeg (min aa x) (max aa y)])+ Interval x y -> case b of+ Single bb -> (True, [enumInfSeg (min bb x) (max bb y)])+ Interval x' y' -> (True, [enumInfSeg (min x x') (max y y')])+ | otherwise = (False, [a, b])++-- | Performs a set intersection on two 'Interval's of elements to create a new enumInfSeg. If the+-- elements of the new enumInfSeg are not contiguous, this function evaluates to an empty list.+segmentIntersect :: (Ord c, Enum c, InfBound c) => Interval c -> Interval c -> (Bool, [Interval c])+segmentIntersect a b = if areIntersecting a b then joined else (False, []) where+ joined = case a of+ Single aa -> case b of+ Single aa -> (True, [Single aa])+ Interval _ _ -> (True, [Single aa])+ Interval x y -> case b of+ Single aa -> (True, [Single aa])+ Interval x' y' -> (True, [enumInfSeg (max x x') (min y y')]) ++-- | Performs a set "delete" operation, deleteing any elements selected by the first enumInfSeg if+-- they are contained in the second enumInfSeg. This operation is not associative, i.e.+-- @'segmentDelete' a b /= 'segmentDelete' b a@.+segmentDelete :: (Ord c, Enum c, InfBound c) =>+ Interval c -> Interval c -> (Bool, [Interval c])+segmentDelete a b = if not (areIntersecting a b) then (False, [a]) else del where+ del = case a of+ Single _ -> case b of+ Single _ -> (True, [])+ Interval _ _ -> (True, [])+ Interval x y -> case b of+ Single x'+ | x==x' -> (True, [enumInfSeg (stepUp x) y ])+ | y==x' -> (True, [enumInfSeg x (stepDown y)])+ | otherwise -> (True, [enumInfSeg x (stepDown x'), enumInfSeg (stepUp x') y])+ Interval x' y'+ | x' > x && y' < y -> (True, [enumInfSeg x (stepDown x'), enumInfSeg (stepUp y') y])+ | x' <= x && y' >= y -> (True, [])+ | x' <= x && y' < y -> (True, [enumInfSeg (stepUp y') y])+ | x' > x && y' >= y -> (True, [enumInfSeg x (stepDown x')])+ | otherwise -> error "segmentDelete"++-- | Evaluates to the set of all elements not selected by the given 'Interval'.+segmentInvert :: (Ord c, Enum c, InfBound c) => Interval c -> [Interval c]+segmentInvert seg = canonicalSegment =<< case seg of+ Single x -> case x of+ NegInf -> [] -- [Single PosInf]+ PosInf -> [] -- [Single NegInf]+ Finite _ -> [mkSegment NegInf (stepDown x), mkSegment (stepUp x) PosInf]+ Interval x y -> case x of+ NegInf -> case y of+ NegInf -> [] -- [Single PosInf]+ PosInf -> [] -- []+ Finite _ -> [mkSegment (stepUp y) PosInf]+ PosInf -> case y of+ PosInf -> [] -- [Single NegInf]+ NegInf -> [] -- []+ Finite _ -> [mkSegment NegInf (stepDown y)]+ Finite _ -> case y of+ NegInf -> [mkSegment (stepUp x) PosInf ]+ PosInf -> [mkSegment NegInf (stepDown x)]+ Finite _ ->+ [ mkSegment NegInf (min (stepDown x) (stepDown y))+ , mkSegment (max (stepUp x) (stepUp y)) PosInf+ ]++-- | Eliminate overlapping and duplicate 'Interval's from a list of segments.+segmentNub :: (Ord c, Enum c, InfBound c) => [Interval c] -> [Interval c]+segmentNub = toList . fromList++----------------------------------------------------------------------------------------------------++-- | This function is used by 'associativeProduct' to generate the list of pairs on which to execute the+-- inner production function. It is a general function that may come in handy, but otherwise does+-- not specifically relate to 'SetM' or 'Interval' types.+--+-- What it does is, Given two lists of items, returns every possible unique combination of two+-- items. For example the pair (1,2) and (2,1) are considered to be the same combination, so only+-- (1,2) is selected. The selected items are returned as a list. The name of this function derives+-- from a similar matrix operation were all possible pairs are placed in a matrix and the+-- upper-triangluar elements are selected and returned. Pass 'Prelude.True' as the first parameter+-- to select items on the main diagonal of the matrix. Passing 'Prelude.False' is handy when you are+-- trying to evaluate a function on every possible 2-element combination of elements from a single+-- list, but don't need to evaluate each element with itself.+upperTriangular :: Bool -> [a] -> [b] -> [(a, b)]+upperTriangular mainDiag ax bx = do+ let iter bx = if Data.List.null bx then [] else bx : iter (tail bx)+ (a, bx) <- zip ax (if mainDiag then iter bx else if Data.List.null bx then [] else iter (tail bx))+ map (\b -> (a, b)) bx++-- Used by the various set operations, including 'unionWithM', 'intersectWithM', and 'deleteWithM', to+-- compute a new set from two parameter sets and a single operation on the compnent 'Interval's. What+-- it does is, given two lists of elements, the largest possible upper triangular matrix (using+-- 'upperTriangular') of all possible pairs of elements from a and b is formed, and on each pair a+-- given inner product function is executed. The first parameter, the product function, is intended+-- to be a function like 'segmentUnion', 'segmentIntersect', or 'segmentDelete'.+associativeProduct+ :: (Ord c, Enum c, InfBound c)+ => (Interval c -> Interval c -> (Bool, [Interval c]))+ -> [Interval c] -> [Interval c] -> [Interval c]+associativeProduct reduce a b =+ let f a b = upperTriangular True a b >>= snd . uncurry reduce+ in segmentNub (if length b > length a then f a b else f b a)++-- not for export+-- This equation assumes list arguments passed to it are already sorted list. This alrorithm works+-- in O(log (n^2)) time. Pass two functions, a function for combining intersecting items, and a+-- function for converting non-intersecting items in the list of @a@ to the list of @b@.+exclusiveProduct :: (a -> b -> (Bool, [a])) -> [a] -> [b] -> [a]+exclusiveProduct product ax bx = ax >>= loop False bx where+ loop hitOne bx a = case bx of+ [] -> if hitOne then [] else [a]+ b:bx ->+ let (hit, ax) = product a b+ in if hit+ then ax >>= loop False bx+ else if hitOne then [] else loop False bx a+ -- The logic is this: we are deleting or XOR-ing items bounded by segments in B from items+ -- bounded by segments in A. Both A and B are sorted. For every segment 'a' in A, the following+ -- evaluations take place: every element 'b' in B is checked against 'a' until we find a segment+ -- 'b[first]' that hits (intersects with) 'a'. The 'hitOne' boolean is set to True as soon as+ -- 'b[first]' is found. Now we continue with every 'b' segment after 'b[first]' until we find a+ -- segment 'b[missed]' that does not hit (intersect with) 'a'. Since 'b[missed]' does not+ -- intersect, every element 'b' above 'b[missed]' will also miss (not intersect with) 'a',+ -- assuming 'b' elements are sorted. Therefore, we can stop scanning for further elements in B,+ -- we know they will all miss (not intersect). If every element in B misses (does not intersect+ -- with) 'a', then the segment 'a' is returned unmodified (because of the definition of XOR).+ -- However if even one segment in B hit this 'a', the only the segments produced by+ -- 'segmentDelete' are returned.++-- | Unlike inner product, which works with associative operators, 'segmentExclusive'+-- works with non-associative operators, like 'segmentDelete' and 'segmentXOR'. Lists of elements+-- passed to this function are sorted. Lists that are already sorted can be multiplied in+-- in O(log (n*m)) time. The product function you pass will return @(Prelude.True, result)@ if the+-- two arguments passed to it "react" with each other, that is, if they can be multiplied to a+-- non-null or non-zero result. This function is used to implement set deletion.+nonAssociativeProduct :: Ord c => (c -> c -> (Bool, [c])) -> [c] -> [c] -> [c]+nonAssociativeProduct product ax bx = exclusiveProduct product (sort ax) (sort bx)++----------------------------------------------------------------------------------------------------++-- | A set-union of serveral 'Interval's, each segment being paired with a list of arbitrary value+-- @x@. It is like an extension of the list monad, except lists may be divided up and assigned to+-- ranges of integral values (or any type that instantiates 'InfBound').+data SetM c x+ = EmptySetM + | InfiniteM{ setValue :: [x] }+ | SetM { setSegmentsM :: [(Interval c, [x])], setValue :: [x] }+ deriving Eq+instance (Ord c, Enum c, InfBound c, Monoid x) =>+ Monoid (SetM c x) where+ mempty = EmptySetM + mappend a b = foldValueSetM mappend (unionM a b)+instance+ Functor (SetM c) where+ fmap f a = case a of+ EmptySetM -> EmptySetM+ InfiniteM x -> InfiniteM (fmap f x)+ SetM a x -> SetM (fmap (fmap (fmap f)) a) (fmap f x)+instance (Ord c, Enum c, InfBound c) =>+ Monad (SetM c) where+ return = InfiniteM . (:[])+ a >>= b = case a of+ EmptySetM -> EmptySetM+ InfiniteM x -> msum (map b x)+ SetM a x -> msum (map segs a ++ map deflts x) where+ segs (a, x) = msum (map (sieveM a . b) x)+ deflts x = case b x of+ EmptySetM -> EmptySetM+ InfiniteM x -> InfiniteM x+ SetM _ x -> InfiniteM x+instance (Ord c, Enum c, InfBound c) =>+ MonadPlus (SetM c) where { mzero = EmptySetM; mplus = unionM; }+instance (Ord c, Enum c, InfBound c) =>+ Applicative (SetM c) where { pure = return; (<*>) = ap; }+instance (Ord c, Enum c, InfBound c) =>+ Alternative (SetM c) where { empty = mzero; (<|>) = mplus; }++-- | Remove any component segment within the set that does not intersect with the given segment.+sieveM :: (Ord c, Enum c, InfBound c) => Interval c -> SetM c x -> SetM c x+sieveM b a = case a of+ EmptySetM -> EmptySetM+ InfiniteM x -> SetM [(b, x)] []+ SetM a x -> case filter (areIntersecting b . fst) a of+ [] -> EmptySetM+ [(a, x)] | a==wholeInterval -> InfiniteM x+ a -> SetM a x++-- | 'SetM' monads contain values accumulate into lists. This function will reduce these lists to a+-- single element using a folding function.+foldValueSetM :: (x -> x -> x) -> SetM c x -> SetM c x+foldValueSetM f s = case s of+ EmptySetM -> EmptySetM+ InfiniteM x -> InfiniteM (fol x)+ SetM s x ->+ SetM+ { setSegmentsM = map (\ (s, x) -> (s, fol x)) s+ , setValue = fol x+ }+ where+ fol x = case x of+ (a:b:x) -> [foldl f a (b:x)]+ [x] -> [x]+ [] -> []++-- not for export+-- Used to create a function useful to 'unionM' and 'intersectM' when those functions call+-- 'exclusiveProduct' to determine which component segments are overlaping, and what to do with 1.+-- segments on the low end do not overlap, 2. segments that overlap, and 3. segments on the high end+-- that do not overlap. The first parameter to this function is a function that takes three lists+-- for each of (1) (2) and (3), and returns a new list that combines the three. In the case of+-- 'intersectionM', (1) and (3) (the non-overlapping parts) are disgarded and only (2) (the+-- overlapping part) is returned. In the case of 'unionM', (1) (2) and (3) are simply concatenated.+joinSegments+ :: (Ord c, Enum c, InfBound c)+ => ([(Interval c, [x])] -> [(Interval c, [x])] -> [(Interval c, [x])] -> [(Interval c, [x])])+ -> (Interval c, [x]) -> (Interval c, [x])+ -> (Bool, [(Interval c, [x])])+joinSegments joiner (a, ax) (b, bx) =+ let (isecting, andAB) = segmentIntersect a b+ (_ , delAB) = segmentDelete a b+ (_ , delBA) = segmentDelete b a+ newA = map (\s -> (s, ax )) delAB+ newAB = map (\s -> (s, ax++bx)) andAB+ newB = map (\s -> (s, bx )) delBA+ in if not isecting+ then (False, [(a, ax), (b, bx)])+ else (,) True $ case (newA, newB) of+ ([], [lo, hi]) -> joiner [lo] newAB [hi]+ ([lo, hi], []) -> joiner [lo] newAB [hi]+ ([lo], [hi]) -> joiner [lo] newAB [hi]+ _ -> error "joinSegments"++unionM :: (Ord c, Enum c, InfBound c) => SetM c x -> SetM c x -> SetM c x+unionM a b = case a of+ EmptySetM -> b+ InfiniteM ax -> case b of+ EmptySetM -> InfiniteM ax+ InfiniteM bx -> InfiniteM (ax++bx)+ SetM b bx -> SetM b (ax++bx)+ SetM a ax -> case b of+ EmptySetM -> SetM a ax+ InfiniteM bx -> SetM a (ax++bx)+ SetM b bx ->+ SetM (exclusiveProduct (joinSegments (\lo mid hi -> lo++mid++hi)) a b) (ax++bx)++intersectM :: (Ord c, Enum c, InfBound c) => SetM c x -> SetM c x -> SetM c x+intersectM a b = case a of+ EmptySetM -> b+ InfiniteM ax -> case b of+ EmptySetM -> InfiniteM ax+ InfiniteM bx -> InfiniteM (ax++bx)+ SetM b bx -> SetM b (ax++bx)+ SetM a ax -> case b of+ EmptySetM -> SetM a ax+ InfiniteM bx -> SetM a (ax++bx)+ SetM b bx ->+ SetM (exclusiveProduct (joinSegments (\_ mid _ -> mid)) a b) (ax++bx)++deleteM :: (Ord c, Enum c, InfBound c) => SetM c x -> SetM c x -> SetM c x+deleteM a b = case a of+ EmptySetM -> EmptySetM+ InfiniteM ax -> case b of+ EmptySetM -> InfiniteM ax+ InfiniteM _ -> EmptySetM+ SetM b _ -> invertM (SetM b []) ax+ SetM a ax -> case b of+ EmptySetM -> SetM a ax+ InfiniteM _ -> EmptySetM+ SetM b _ -> flip SetM ax $ nubBy (\a b -> fst a == fst b) $+ exclusiveProduct (joinSegments (\lo _ hi -> lo++hi)) a b++setXUnionM :: (Ord c, Enum c, InfBound c) => SetM c x -> SetM c x -> SetM c x+setXUnionM a b = unionM (deleteM a b) (deleteM b a)++invertM :: (Ord c, Enum c, InfBound c) => SetM c x -> [y] -> SetM c y+invertM a y = case a of+ EmptySetM -> InfiniteM y+ InfiniteM _ -> EmptySetM+ SetM a _ ->+ SetM (map (flip (,) y) $ toList $ invert $ fromList $ map fst a) []++-- | Initialize a new intinite 'SetM', that is, the set that contains all possible elements.+infiniteM :: (Ord c, Enum c, InfBound c) => [x] -> SetM c x+infiniteM = InfiniteM++-- | Initialize a new 'SetM' object with a list of 'Interval's, which are 'segmentUnion'ed+-- together to create the set.+fromListM :: (Ord c, Enum c, InfBound c) => [Interval c] -> [x] -> SetM c x+fromListM a ax = case segmentNub a of+ [a] | a==wholeInterval -> InfiniteM ax+ [] -> EmptySetM+ a -> SetM (map (flip (,) ax) a) []++-- | Create a set with a single rangeM of elements, no gaps.+rangeM :: (Ord c, Enum c, InfBound c) => c -> c -> [x] -> SetM c x+rangeM a b x = SetM [(segment a b, x)] []++-- | Create a set with a single element.+pointM :: (Ord c, Enum c, InfBound c) => c -> [x] -> SetM c x+pointM c x = rangeM c c x++-- | Tests if an element is a memberM of the set.+memberM :: Ord c => SetM c x -> c -> Bool+memberM a c = case a of+ EmptySetM -> False+ InfiniteM _ -> True+ SetM a _ -> or (map (flip segmentMember c . fst) a)++-- | Test if a set encompases only one element, and if so, returns that one element.+isSingletonM :: (Ord c, Enum c, InfBound c) => SetM c x -> Maybe (c, [x])+isSingletonM a = case a of+ SetM a _ -> case a of+ [(Single (Finite a), x)] -> Just (a, x)+ _ -> Nothing+ _ -> Nothing++-- | Tests if a set is empty.+nullM :: SetM c x -> Bool+nullM a = case a of+ EmptySetM -> True+ SetM [] _ -> True+ _ -> False++lookupM :: (Ord c, Enum c, InfBound c) => SetM c x -> c -> [x]+lookupM a c = case a of+ EmptySetM -> []+ InfiniteM x -> x+ SetM a x -> case concatMap snd (filter (isWithin (Finite c) . fst) a) of { [] -> x; x -> x; }++toListM :: SetM c x -> [(Interval c, [x])]+toListM set = case set of+ EmptySetM -> []+ InfiniteM _ -> []+ SetM a _ -> a++defaultM :: SetM c x -> [x]+defaultM set = case set of+ EmptySetM -> []+ InfiniteM x -> x+ SetM _ x -> x++setToSetM :: (Ord c, Enum c, InfBound c) => Set c -> [x] -> SetM c x+setToSetM a x = case a of+ EmptySet -> EmptySetM+ InfiniteSet -> InfiniteM x+ InverseSet a -> setToSetM (forceInvert a) x+ Set a -> SetM (map (flip (,) x) a) []++setMtoSet :: SetM c x -> Set c+setMtoSet a = case a of+ EmptySetM -> EmptySet+ InfiniteM _ -> InfiniteSet+ SetM a _ -> Set (map fst a)++----------------------------------------------------------------------------------------------------++data Set c+ = EmptySet + | InfiniteSet+ | InverseSet (Set c)+ | Set [Interval c]+instance (Ord c, Enum c, InfBound c) => Eq (Set c) where+ a == b = case a of+ EmptySet -> case b of+ EmptySet -> True+ Set [] -> True+ _ -> False+ InfiniteSet -> case b of+ InfiniteSet -> True+ Set [s] | s==wholeInterval -> True+ _ -> False+ InverseSet a -> case b of+ InverseSet b -> a==b+ _ -> forceInvert a == b+ Set a -> case b of+ Set b -> a==b+ _ -> False++instance (Ord c, Enum c, InfBound c) => Monoid (Set c) where+ mempty = EmptySet+ mappend = Dao.Interval.union++instance Show c => Show (Set c) where+ show s = case s of+ EmptySet -> "enumSet()"+ InfiniteSet -> "enumSet(-Inf to +Inf)"+ InverseSet s -> "!enumSet("++show s++")"+ Set s -> "enumSet("++intercalate ", " (map show s)++")"++whole :: Set c+whole = InfiniteSet++-- not exported, creates a list from segments, but does not clean it with 'segmentNub'+fromListNoNub :: (Ord c, Enum c, InfBound c) => [Interval c] -> Set c+fromListNoNub a =+ if Data.List.null a+ then EmptySet + else if a==[wholeInterval] then InfiniteSet else Set a++fromList :: (Ord c, Enum c, InfBound c) => [Interval c] -> Set c+fromList a = if Data.List.null a then EmptySet else fromListNoNub a++fromPairs :: (Ord c, Enum c, InfBound c) => [(c, c)] -> Set c+fromPairs = fromList . map (uncurry segment)++range :: (Ord c, Enum c, InfBound c) => c -> c -> Set c+range a b = Set [segment a b]++point :: (Ord c, Enum c, InfBound c) => c -> Set c+point a = Set [single a]++toList :: (Ord c, Enum c, InfBound c) => Set c -> [Interval c]+toList s = case s of+ EmptySet -> []+ InfiniteSet -> [wholeInterval]+ InverseSet s -> toList (forceInvert s)+ Set s -> s++elems :: (Ord c, Enum c, Bounded c, InfBound c) => Set c -> [c]+elems = concatMap enumBoundedPair . toList++member :: (Ord c, InfBound c) => Set c -> c -> Bool+member s b = case s of+ EmptySet -> False+ InfiniteSet -> True+ InverseSet s -> not (member s b)+ Set [] -> False+ Set s -> or (map (flip segmentMember b) s)++null :: Set c -> Bool+null s = case s of+ EmptySet -> True+ InfiniteSet -> False+ InverseSet s -> not (Dao.Interval.null s)+ Set [] -> True+ Set _ -> False++isSingleton :: (Ord c, Enum c, InfBound c) => Set c -> Maybe c+isSingleton s = case s of+ InverseSet s -> isSingleton (forceInvert s)+ Set [Single c] -> toPoint c+ _ -> mzero++invert :: (Ord c, Enum c, InfBound c) => Set c -> Set c+invert s = case s of+ EmptySet -> InfiniteSet+ InfiniteSet -> EmptySet + InverseSet s -> s+ Set s -> InverseSet (Set s)++-- not for export+forceInvert :: (Ord c, Enum c, InfBound c) => Set c -> Set c+forceInvert s = case s of+ EmptySet -> InfiniteSet+ InfiniteSet -> EmptySet + InverseSet s -> s+ Set [] -> InfiniteSet+ Set [s] | s==wholeInterval -> EmptySet + Set s -> fromListNoNub (loop NegInf s >>= canonicalSegment) where+ loop mark s = case s of+ [] -> [mkSegment (stepUp mark) PosInf]+ [Interval a PosInf] -> [mkSegment (stepUp mark) (stepDown a)]+ Interval NegInf b : s -> loop b s+ Interval a b : s -> mkSegment (stepUp mark) (stepDown a) : loop b s+ Single a : s -> mkSegment (stepUp mark) (stepDown a) : loop a s++setXUnion :: (Ord c, Enum c, InfBound c) => Set c -> Set c -> Set c+setXUnion a b = Dao.Interval.delete (Dao.Interval.union a b) (Dao.Interval.intersect a b)++union :: (Ord c, Enum c, InfBound c) => Set c -> Set c -> Set c+union a b = case a of+ EmptySet -> b+ InfiniteSet -> InfiniteSet+ InverseSet a -> Dao.Interval.union (forceInvert a) b+ Set [] -> b+ Set a -> case b of+ EmptySet -> Set a+ InfiniteSet -> InfiniteSet+ InverseSet b -> Dao.Interval.union (Set a) (forceInvert b)+ Set [] -> Set a+ Set b -> fromListNoNub (associativeProduct segmentUnion a b)++intersect :: (Ord c, Enum c, InfBound c) => Set c -> Set c -> Set c+intersect a b = case a of+ EmptySet -> EmptySet + InfiniteSet -> b+ InverseSet a -> Dao.Interval.intersect (forceInvert a) b+ Set [] -> EmptySet + Set a -> case b of+ EmptySet -> EmptySet + InfiniteSet -> Set a+ InverseSet b -> Dao.Interval.intersect (Set a) (forceInvert b)+ Set [] -> EmptySet + Set b -> fromListNoNub (associativeProduct segmentIntersect a b)++delete :: (Ord c, Enum c, InfBound c) => Set c -> Set c -> Set c+delete a b = case b of+ EmptySet -> a+ InfiniteSet -> EmptySet + InverseSet b -> Dao.Interval.delete a (forceInvert b)+ Set [] -> a+ Set b -> case a of+ EmptySet -> EmptySet + InfiniteSet -> forceInvert (Set b)+ InverseSet a -> Dao.Interval.delete (forceInvert a) (Set b)+ Set [] -> EmptySet + Set a -> fromList (exclusiveProduct segmentDelete a b)+ -- Here we call 'fromList' instead of 'fromListNoNub' because an additional 'segmentNub'+ -- operation is required.++----------------------------------------------------------------------------------------------------++instance NFData a =>+ NFData (Inf a) where+ rnf NegInf = ()+ rnf PosInf = ()+ rnf (Finite c) = deepseq c ()++instance NFData a =>+ NFData (Interval a) where+ rnf (Single a ) = deepseq a ()+ rnf (Interval a b) = deepseq a $! deepseq b ()++instance (NFData a, NFData x) =>+ NFData (SetM a x) where+ rnf a = case a of+ EmptySetM -> ()+ InfiniteM ax -> deepseq ax ()+ SetM a ax -> deepseq a $! deepseq ax ()++instance NFData a => NFData (Set a) where+ rnf EmptySet = ()+ rnf InfiniteSet = ()+ rnf (Set a) = deepseq a ()+ rnf (InverseSet a) = deepseq a ()+
+ src/Dao/Lib/Array.hs view
@@ -0,0 +1,165 @@+-- "src/Dao/Lib/Array.hs" built-in array object+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++-- | This module provides the Dao programming language implementation of the 'Array' built-in data+-- type. 'Array's can be indexed from zero to (n-1) where n is the number of elements in the array.+-- Reads and updates are O(n). Resizing an array is not possible.+module Dao.Lib.Array where++import Dao.String+import Dao.Predicate+import Dao.Interpreter++import Control.Applicative+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.State++import Data.Array.IO+import Data.Monoid+import Data.Typeable++import qualified Data.IntMap as I++----------------------------------------------------------------------------------------------------++-- | The Dao programming language wrapper around Haskell's 'Data.Array.IO.IOArray' data type.+-- To inspect elements of the array outside of the IO monad, the only way for pure functions like+-- 'Dao.PPrint.PPrint' and 'Dao.Binary.Put' to work without being unsafe is to lazily evaluate these+-- functions after every update to the array. These functions are evaluated extra-lazily to ensure+-- that nothing actually happens until it becomes absolutely necessary to use them, if it ever is+-- necessary.+newtype Array = Array { toObjArray :: IOArray Int Object } deriving (Eq, Typeable)++arrayFromList :: Int -> [(Int, Object)] -> IO Array+arrayFromList len ox = do+ arr <- newArray (0, len) ONull+ forM_ ox $ \ (i, o) -> writeArray arr i o+ return $ Array{ toObjArray=arr }++arraySize :: Array -> IO Int+arraySize arr = uncurry subtract <$> getBounds (toObjArray arr)++arrayElems :: Array -> IO [Object]+arrayElems = getElems . toObjArray++arrayCheckIndex :: Array -> Int -> IO (Predicate ExecControl ())+arrayCheckIndex arr i = do+ bounds <- getBounds (toObjArray arr)+ return $+ if inRange bounds i+ then OK ()+ else PFail $ newError{ execErrorMessage=ustr "array index out of bounds" }++arrayLookup :: Array -> Int -> IO (Predicate ExecControl Object)+arrayLookup arr i = arrayCheckIndex arr i >>= \result -> case result of+ PFail err -> return (PFail err)+ Backtrack -> return Backtrack+ OK () -> OK <$> readArray (toObjArray arr) i++arrayUpdate :: Array -> Int -> Object -> IO (Predicate ExecControl ())+arrayUpdate arr i o = arrayCheckIndex arr i >>= \result -> case result of+ PFail err -> return $ PFail err+ Backtrack -> return Backtrack+ OK () -> OK <$> writeArray (toObjArray arr) i o++instance ReadIterable Array Object where+ readForLoop (Array arr) f = liftIO (getBounds arr) >>=+ flip execForM_ (liftIO . readArray arr >=> f) . range++instance UpdateIterable Array (Maybe Object) where+ updateForLoop a@(Array arr) f = do+ let err = "for loop iteration attempted to delete an item from an Array"+ liftIO (getBounds arr) >>=+ flip execForM_ (\i -> liftIO (readArray arr i) >>= f . Just >>=+ maybe (fail err) return >>= liftIO . writeArray arr i) . range+ return a++instance ObjectFunctor Array Int where+ objectFMap f = do+ arr <- get+ let a = toObjArray arr+ (bounds, elems) <- liftIO $ return (,) <*> getBounds a <*> getElems a+ forM_ (zip (range bounds) elems) $ \ (i, o) -> focalPathSuffix (Subscript [obj i] NullRef) $+ withInnerLens [] (f i o) >>= \ (_, (changed, ox)) ->+ when changed $ forM_ ox (liftIO . uncurry (writeArray $ toObjArray arr))++instance ObjectFunctor Array Object where { objectFMap f = objectFMap (\i -> f (obj i)) }+instance ObjectFunctor Array [Object] where { objectFMap f = objectFMap (\i -> f [obj i]) }++_objToInt :: [Object] -> Exec Int+_objToInt ix = case ix of+ [i] -> (derefObject i >>= xmaybe . fromObj) <|>+ throwBadTypeError "Array index value cannot be cast to integer" i []+ ix -> throwArityError "Array index is not one-dimensional" 1 ix []++arrayFromArgs :: [Object] -> IO Array+arrayFromArgs lists =+ forM lists+ (\o -> maybe (return (1, [o])) id $ msum $+ [ fromObj o >>= \ox -> Just $ return (length ox, ox)+ , fromObj o >>= \arr -> Just $ return (,) <*> arraySize arr <*> arrayElems arr+ ]+ ) >>= uncurry arrayFromList . (\ (ix, ox) -> (sum ix, zip [0..] $ concat ox)) . unzip++loadLibrary_Array :: DaoSetup+loadLibrary_Array = do+ daoClass (haskellType::Array)+ daoFunction "Array" $+ daoFunc+ { funcAutoDerefParams=True+ , daoForeignFunc = \ () -> fmap (flip (,) () . Just . obj) . liftIO . arrayFromArgs+ }++instance ObjectClass Array where { obj=new; fromObj=objFromHata; }++instance HataClass Array where+ haskellDataInterface = interface "Array" $ do+ autoDefEquality >> autoDefReadIterable >> autoDefUpdateIterable >> autoDefTraverse+ defSizer (fmap OInt . liftIO . arraySize)+ defIndexer $ \arr i -> _objToInt i >>= liftIO . arrayLookup arr >>= predicate+ defIndexUpdater $ \i f -> focusLiftExec (_objToInt i) >>= \i -> do+ arr <- get+ (_arr, (changed, o)) <- focusLiftExec (liftIO (arrayLookup arr i) >>= predicate) >>=+ flip withInnerLens f . Just+ case o of+ Nothing -> fail "cannot delete items from array"+ Just o -> when changed (liftIO $ writeArray (toObjArray arr) i o) >> return (Just o)+ let init ox = case ox of -- initializer list in round-brackets must be empty+ [] -> return (Array $ error "uninitialized Array") -- SUCCESS: return an uninitialized Array+ _ -> execThrow "cannot initialize array with parameters" ExecErrorUntyped [] -- FAIL+ let fromList m i maxbnd arr ox = case ox of+ [] -> liftIO $ arrayFromList maxbnd (I.assocs m)+ o:ox -> case o of+ InitAssign ref op o -> do+ i <- derefObject ref >>= _objToInt . return+ if i<0+ then execThrow "assigned to negative index value" op [(assertFailed, OInt i)]+ else do+ ref <- pure $ fromObj ref <|> Just (RefObject ref NullRef)+ o <- evalUpdateOp ref op o (I.lookup i m)+ case o of+ Just o -> do+ a <- fromList (I.insert i o m) (i+1) (max i maxbnd) arr ox+ return $ arr{ toObjArray=toObjArray a }+ Nothing -> execThrow "index to Array evaluated to void" op $+ maybe [] (\ref -> [(errOfReference, obj ref)]) ref+ InitSingle o -> fromList (I.insert i o m) (i+1) (max i maxbnd) arr ox+ defInitializer init (fromList mempty 0 0)+
+ src/Dao/Lib/File.hs view
@@ -0,0 +1,208 @@+-- "src/Dao/Lib/File.hs" built-in plain file object+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++module Dao.Lib.File where++import Dao.String+import Dao.Predicate+import Dao.PPrint+import qualified Dao.Binary as B+import Dao.Interpreter++import qualified Data.ByteString.Lazy as B+import qualified Data.Map.Lazy as M+import Data.Typeable++import Control.Applicative+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.State++import System.IO++data File = File { filePath :: UStr, fileHandle :: Maybe Handle } deriving Typeable++instance Eq File where { a==b = filePath a == filePath b }++instance Ord File where { compare a b = compare (filePath a) (filePath b) }++instance Show File where { show a = "File("++show (filePath a)++")" }++instance HasNullValue File where+ nullValue = File{ filePath=nil, fileHandle=Nothing }+ testNull (File{ filePath=name, fileHandle=Nothing }) = nil==name+ testNull (File{}) = False++instance PPrintable File where { pPrint = pShow }++instance B.Binary File MethodTable where+ get = return File <*> B.get <*> pure Nothing+ put f = B.put (filePath f)++errFilePath :: Name+errFilePath = ustr "filePath"++_openParamFail :: String -> [Object] -> Exec ig+_openParamFail func ox =+ throwArityError "expecting a file path as the only parameter" 1 ox [(errInFunc, obj (ustr func :: Name))]++_paramPath :: String -> [Object] -> Exec UStr+_paramPath func ox = case ox of+ [path] -> xmaybe (fromObj path <|> (filePath <$> fromObj path)) <|> _openParamFail func ox+ ox -> _openParamFail func ox++_catchIOException :: String -> File -> IO a -> Exec a+_catchIOException func file f = execCatchIO (liftIO f) $+ [ newExecIOHandler $ \e -> execThrow "" (ExecIOException e) $+ [(errInFunc, obj (ustr func :: Name)), (errFilePath, obj $ filePath file)]+ ]++_openFile :: String -> File -> IOMode -> Exec File+_openFile func file mode = _catchIOException func file $+ (\o -> file{ fileHandle=Just o}) <$> openFile (uchars $ filePath file) mode++_getHandle :: String -> File -> Exec Handle+_getHandle func file = case fileHandle file of+ Just h -> return h+ Nothing ->+ execThrow "function evaluated on a file handle which has not been opened" ExecErrorUntyped+ [(errInFunc, obj (ustr func :: Name)), (errFilePath, obj $ filePath file)]++_withClosedHandle :: String -> File -> Exec ()+_withClosedHandle func file = case fileHandle file of+ Nothing -> return ()+ Just _ -> execThrow "function cannot operate on open file handle" ExecErrorUntyped+ [(errInFunc, obj (ustr func :: Name)), (errFilePath, obj $ filePath file)]++_withContents :: (String -> IO Object) -> DaoFunc File+_withContents f =+ daoFunc+ { daoForeignFunc = \file ox -> case ox of+ [] -> do+ _withClosedHandle "read" file+ _catchIOException "read" file $ fmap (flip (,) file . Just) $ readFile (uchars $ filePath file) >>= f+ ox -> throwArityError "" 0 ox [(errInFunc, obj (ustr "read" :: Name))]+ }++gGetErrToExecError :: B.GGetErr -> ExecControl+gGetErrToExecError (B.GetErr{ B.gGetErrOffset=offset, B.gGetErrMsg=msg }) =+ newError+ { execErrorMessage = msg+ , execErrorInfo = M.fromList [(ustr "byteOffset", OLong (toInteger offset))]+ }++loadLibrary_File :: DaoSetup+loadLibrary_File = do+ let fileOpener func mode = daoFunction func $+ daoFunc+ { daoForeignFunc = \ () ->+ _paramPath func >=> fmap (flip (,) () . Just . obj) . flip (_openFile func . flip File Nothing) mode+ }+ fileOpener "readFile" ReadMode+ fileOpener "writeFile" WriteMode+ fileOpener "appendFile" AppendMode+ daoClass (haskellType::File)+ daoFunction "File" $+ daoFunc+ { daoForeignFunc = \ () -> fmap (flip (,) () . Just . obj . flip File Nothing) . _paramPath "File"+ }++instance ObjectClass File where { obj=new; fromObj=objFromHata; }++instance HataClass File where+ haskellDataInterface = interface "File" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest+ autoDefPPrinter >> autoDefBinaryFmt+ let fileOpener func mode = defMethod func $+ daoFunc+ { funcAutoDerefParams = False+ , daoForeignFunc = \file ox ->+ case ox of+ [] -> do+ _withClosedHandle func file+ f <- _openFile func file mode+ return (Just $ obj f, f)+ ox -> throwArityError "" 0 ox [(errInFunc, obj (ustr "File" :: Name))]+ }+ fileOpener "openRead" ReadMode+ fileOpener "openWrite" WriteMode+ fileOpener "openAppend" AppendMode+ defMethod "close" $+ daoFunc+ { funcAutoDerefParams = False+ , daoForeignFunc = \file ox -> fmap (flip (,) (file{ fileHandle=Nothing })) $ do+ case ox of+ [] -> _getHandle "close" file >>=+ _catchIOException "close" file . liftIO . hClose >> return Nothing+ ox -> throwArityError "" 0 ox [(errInFunc, obj (ustr "close" :: Name))]+ }+ defMethod "writeBinary" $+ daoFunc+ { daoForeignFunc = \file ox -> do+ mtab <- gets globalMethodTable+ handl <- _getHandle "writeBinary" file+ forM_ ox $ \o -> do+ bin <- execCatchIO (liftIO $ return (B.encode mtab o)) $+ [ newExecIOHandler $ \e -> execThrow "while encoding object" (ExecHaskellError e) $+ [ (errInFunc, obj (ustr "writeBinary" :: Name))+ , (errFilePath, obj $ filePath file)+ ]+ ]+ _catchIOException "writeBinary" file (B.hPutStr handl bin)+ return (Nothing, file)+ }+ defMethod "readBinary" $+ daoFunc+ { daoForeignFunc = \file ox -> case ox of+ [] -> do+ _withClosedHandle "readBinary" file+ mtab <- gets globalMethodTable+ bin <- _catchIOException "readBinary" file (liftIO $ B.readFile $ uchars $ filePath file)+ result <- execCatchIO (return $ fmapPFail gGetErrToExecError $ B.decode mtab bin) $+ [ newExecIOHandler $ \e -> execThrow "while decoding object" (ExecHaskellError e) $+ [(errInFunc, obj (ustr "readBinary" :: Name)), (errFilePath, obj $ filePath file)]+ ]+ predicate result+ ox -> throwArityError "" 0 ox [(errInFunc, obj (ustr "readBinary" :: Name))]+ }+ let writeFunc func putstr = defMethod func $+ daoFunc+ { daoForeignFunc = \file ox -> do+ ox <- requireAllStringArgs func ox+ handl <- _getHandle func file+ forM_ ox $ _catchIOException "write" file . putstr handl . uchars+ return (Nothing, file)+ }+ writeFunc "write" hPutStr+ defMethod "read" $ _withContents (return . obj)+ defMethod "readAllLines" $ _withContents (return . obj . fmap obj . lines)+ writeFunc "writeLine" hPutStrLn+ defMethod "readLine" $+ daoFunc+ { daoForeignFunc = \file ox -> case ox of+ [] -> do+ handl <- _getHandle "readLine" file+ _catchIOException "readLine" file (flip (,) file . Just . obj <$> hGetLine handl)+ ox -> throwArityError "" 0 ox [(errInFunc, obj (ustr "readLine" :: Name))]+ }+ let defPrinter func print = defMethod func $+ makePrintFunc $ \file str -> _getHandle func file >>= \h -> liftIO (print h str)+ defPrinter "print" hPutStr+ defPrinter "println" hPutStrLn+
+ src/Dao/Lib/ListEditor.hs view
@@ -0,0 +1,222 @@+-- "src/Dao/Lib/ListEditor.hs" built-in object for Dao programs that can+-- functions like a line editor, but for arbitrary types, not just strings.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++-- | This is a line-editor object, but it works with arbitrary lists of objects, but this will work+-- for editing arbitrary lists. You could use it to create an ordinary line editor by representing a+-- file as be a list of strings representing a file. each string could further be converted to a+-- StepList containing characters to edit the line. +module Dao.Lib.ListEditor where++import Dao.String+import Dao.StepList+import Dao.Predicate+import Dao.PPrint+import qualified Dao.Binary as B+import Dao.Interpreter+import Dao.Interpreter.AST++import Control.Applicative+import Control.Monad+import Control.Monad.State++import Data.Monoid+import qualified Data.IntMap as I+import Data.Typeable++----------------------------------------------------------------------------------------------------++newtype ListEditor = ListEditor { listEditor :: StepList Object } deriving (Eq, Ord, Show, Typeable)++loadLibrary_ListEditor :: DaoSetup+loadLibrary_ListEditor = do+ daoClass (haskellType::ListEditor)++instance Monoid ListEditor where+ mempty = ListEditor mempty+ mappend (ListEditor a) (ListEditor b) = ListEditor (a<>b)++instance HasNullValue ListEditor where+ nullValue = mempty+ testNull (ListEditor sl) = slNull sl++instance ToDaoStructClass ListEditor where+ toDaoStruct = renameConstructor "ListEditor" $ do+ "left" .=@ slLeftOfCursor . listEditor+ "right" .=@ slRightOfCursor . listEditor++instance FromDaoStructClass ListEditor where+ fromDaoStruct = do+ constructor "ListEditor"+ left <- optList "left"+ right <- optList "right"+ return $ ListEditor $ slFromLeftRight left right++instance PPrintable ListEditor where { pPrint = pPrintStructForm }++instance B.Binary ListEditor MethodTable where+ put (ListEditor sl) = B.put (slCursor sl) >> B.put (slLeftOfCursor sl ++ slRightOfCursor sl)+ get = B.get >>= \cur ->+ ListEditor . slCursorTo cur . uncurry slFromLeftRight . splitAt cur <$> B.get++instance ReadIterable ListEditor Object where+ readForLoop (ListEditor sl) = execForM_ (slToList sl)++instance UpdateIterable ListEditor (Maybe Object) where+ updateForLoop (ListEditor sl) = fmap (ListEditor . slFromList (slCursor sl) .+ concatMap (maybe [] return)) . execForM (fmap Just $ slToList sl)++instance ObjectClass ListEditor where { obj=new; fromObj=objFromHata; }++_getIndex :: [Object] -> Predicate err Int+_getIndex ix = case ix of+ [i] -> xmaybe (fromObj i)+ _ -> fail "must index ListEditor with a 1-dimentional integer value"++_withRange :: String -> (Int -> Int -> Exec a) -> [Object] -> Exec a+_withRange func f ox = do+ let err = throwArityError "expecting one or two integer parameters" 2 ox $+ [(errInFunc, obj (ustr func :: Name))]+ case ox of+ [] -> err+ [a] -> do+ a <- xmaybe (fromObj a) <|>+ throwBadTypeError "index parameter received is not an integer value" a+ [(errInFunc, obj (ustr func :: Name))]+ f (min 0 a) (max 0 a)+ [a, b] -> do+ let param n a = xmaybe (fromObj a) <|>+ (throwBadTypeError "in range to ListEditor" a $+ [(errInConstr, obj (ustr func :: Name)), (argNum, OInt n)]+ )+ param 1 a >>= \a -> param 2 b >>= \b -> f (min a b) (max a b)+ _ -> err++instance ObjectLens ListEditor Int where+ updateIndex i f = do+ sl <- slCursorTo i . listEditor <$> get+ (o, right) <- pure $ case slRightOfCursor sl of+ [] -> (Nothing, [])+ o:ox -> (Just o, ox)+ (result, (changed, o)) <- withInnerLens o f+ when changed $ put $ ListEditor $ case o of+ Nothing -> sl{slRightOfCursor=right, slLength=slLength sl - 1}+ Just o -> sl{slRightOfCursor=o:right }+ return result++instance ObjectFunctor ListEditor Int where+ objectFMap f = do+ (ListEditor sl) <- get+ o <- mapM (fmap snd . withInnerLens [] . uncurry f) $ concat $+ [ reverse $+ if slCursor sl > 0+ then zip (map negate [1..slCursor sl]) (slLeftOfCursor sl)+ else []+ , zip [0..] (slRightOfCursor sl)+ ]+ (changed, o) <- return $ unzip o+ when (or changed) $+ put $ ListEditor $ slCursorTo (slCursor sl) $ slFromIntMap $ I.fromList $ concat o++instance ObjectFunctor ListEditor [Object] where { objectFMap f = objectFMap (\i -> f [obj i]) }++instance Sizeable ListEditor where { getSizeOf = return . obj . slLength . listEditor }++instance HataClass ListEditor where+ haskellDataInterface = interface "ListEditor" $ do+ autoDefEquality >> autoDefOrdering >> autoDefNullTest+ autoDefPPrinter >> autoDefToStruct >> autoDefFromStruct+ autoDefSizeable >> return ()+ autoDefReadIterable >> autoDefUpdateIterable >> autoDefTraverse+ defIndexer $ \ (ListEditor sl) -> fmap (flip slIndex sl) . predicate . _getIndex >=> xmaybe+ defIndexUpdater (\ix f -> predicate (_getIndex ix) >>= flip updateIndex f)+ defInitializer+ (\ox -> + if null ox+ then return mempty+ else predicate $ (\i -> ListEditor $ mempty{ slCursor=i }) <$> _getIndex ox+ )+ (\ (ListEditor sl) ox -> do+ let loop im ox = case ox of+ [] -> return $ ListEditor $ slCursorTo (slCursor sl) $ slFromIntMap im+ (i, o):ox -> case o of+ InitSingle o -> loop (I.insert i o im) ox+ InitAssign ref op o -> do+ i <- (fromObj <$> derefObject ref >>= xmaybe) <|>+ (throwBadTypeError "ListEditor constructor assigns value to non-integer type" ref $+ [(errInFunc, obj (ustr "ListEditor" :: Name))]+ )+ o <- evalUpdateOp (Just $ RefObject ref NullRef) op o (I.lookup i im)+ loop (I.alter (const o) i im) ox+ loop mempty (zip [(slCursor sl)..] ox)+ )+ let deref sl = case slRightOfCursor sl of { [] -> Nothing; o:_ -> Just o; }+ defDeref (return . deref . listEditor)+ defInfixOp SHL $ \ _ (ListEditor sl) o -> case o of+ OList ox -> return $ obj $ ListEditor (ox<++sl)+ o -> return $ obj $ ListEditor (o <| sl)+ defInfixOp SHR $ \ _ (ListEditor sl) o -> case o of+ OList ox -> return $ obj $ ListEditor (ox++>sl)+ o -> return $ obj $ ListEditor (o |>sl)+ defInfixOp ADD $ \ _ (ListEditor a) o -> do+ (ListEditor b) <- xmaybe (fromObj o) <|> fail "added ListEditor object to non-ListEditor object"+ return $ obj $ ListEditor (a<>b)+ defMethod "insertLeft" $+ daoFunc+ { daoForeignFunc = \ (ListEditor sl) ox -> pure (snd (objConcat ox) <++ sl) >>= \sl -> return $+ (Just $ obj $ ListEditor sl, ListEditor sl)+ }+ defMethod "insertRight" $+ daoFunc+ { daoForeignFunc = \ (ListEditor sl) ox -> pure (snd (objConcat ox) ++> sl) >>= \sl -> return $+ (Just $ obj $ ListEditor sl, ListEditor sl)+ }+ defMethod "cursorTo" $+ daoFunc+ { daoForeignFunc = \ (ListEditor sl) ox -> predicate (_getIndex ox) >>= \i ->+ pure (slCursorTo i sl) >>= \sl -> return (deref sl, ListEditor sl)+ }+ defMethod "shift" $+ daoFunc+ { daoForeignFunc = \ (ListEditor sl) ox -> predicate (_getIndex ox) >>= \i ->+ pure (slCursorShift i sl) >>= \sl -> return (deref sl, ListEditor sl)+ }+ defMethod "copy" $+ daoFunc+ { daoForeignFunc = \ (ListEditor sl) -> _withRange "copy" $ \a b -> return $+ (Just $ obj $ ListEditor $ slCopyRelRange (a, b) sl, ListEditor sl)+ }+ defMethod "cut" $+ daoFunc+ { daoForeignFunc = \ (ListEditor sl) -> _withRange "cut" $ \a b -> return $+ (Just $ obj $ ListEditor $ slCopyRelRange (a, b) sl, ListEditor $ slDeleteRelRange (a, b) sl)+ }+ defMethod "copyRange" $+ daoFunc+ { daoForeignFunc = \ (ListEditor sl) -> _withRange "copyRange" $ \a b -> return $+ (Just $ obj $ ListEditor $ slCopyAbsRange (a, b) sl, ListEditor sl)+ }+ defMethod "cutRange" $+ daoFunc+ { daoForeignFunc = \ (ListEditor sl) -> _withRange "cut" $ \a b -> return $+ (Just $ obj $ ListEditor $ slCopyAbsRange (a, b) sl, ListEditor $ slDeleteAbsRange (a, b) sl)+ }+ defInfixOp ADD $ \ _ (ListEditor sl) ->+ xmaybe . fromObj >=> \ (ListEditor o) -> return (obj $ ListEditor $ sl <> o)+
+ src/Dao/PPrint.hs view
@@ -0,0 +1,358 @@+-- "src/Dao/PPrintM.hs" a pretty-printer designed especially for+-- printing Dao script code.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}++module Dao.PPrint where++import Dao.String+import qualified Dao.Tree as T++import qualified Data.Map as M++import Control.Monad.State++import Data.List+import Data.Char+import Data.Monoid++----------------------------------------------------------------------------------------------------++-- | This is the function you will probably care about most: take a value of any data type that+-- instantiates 'PPrintable', and a maximum text-wrapping width value, and a tab string, and will+-- convert that value to a 'Prelude.String'.+prettyPrint :: PPrintable a => Int -> String -> a -> String+prettyPrint maxWidth tab = showPPrint maxWidth tab . pPrint++-- | Calls 'prettyPrint' with the default values @80@ for the text-wrapping width, and a tab string+-- consisting of four single-space characters (four ASCII @0x20@ characters).+prettyShow :: PPrintable a => a -> String+prettyShow = prettyPrint 80 " "++----------------------------------------------------------------------------------------------------++-- | Remove trailing whitespace, I stole the idea from the Perl language.+chomp :: String -> String+chomp = foldl (\ out (spc, str) -> if null str then out else out++spc++str) "" . spcstr where+ spcstr cx = case cx of+ "" -> []+ cx -> (spc, str) : spcstr cx' where+ (spc, more) = span isSpace cx+ (str, cx' ) = break isSpace more++-- | like 'Prelude.map', but doesn't touch the last item in the list.+mapAlmost :: (a -> a) -> [a] -> [a]+mapAlmost fn ax = case ax of+ [] -> []+ [a] -> [a]+ a:ax -> fn a : mapAlmost fn ax++-- | like 'Prelude.map', but doesn't touch the first item in the list.+mapTail :: (a -> a) -> [a] -> [a]+mapTail fn ax = case ax of+ [] -> []+ a:ax -> a : map fn ax++----------------------------------------------------------------------------------------------------++class PPrintable a where { pPrint :: a -> PPrintM () }+type PPrint = PPrintM ()++-- | Put a new line regardless of whether or not we are aleady on a new line.+pNewLine :: PPrint+pNewLine = modify $ \st ->+ st{ printerCol = 0+ , printerBuf = ""+ , printerOut = printerOut st ++ [printerOutputTripple st]+ , lineCount = lineCount st + 1+ , printerTab = nextTab st+ }++-- | Like 'pNewLine' but also indicates that there *must* be a new line here (like after a comment)+-- to prevent lines from being joined.+pForceNewLine :: PPrint+pForceNewLine = modify (\st -> st{forcedNewLine=True})++-- | Place a new line unless we are already on a new line.+pEndLine :: PPrint+pEndLine = gets printerCol >>= \col ->+ if col==0 then modify (\st -> st{printerTab=nextTab st}) else pNewLine+ ++pIndent :: PPrint -> PPrint+pIndent indentedPrinter = do+ tab <- gets nextTab+ modify (\st -> st{nextTab=tab+1})+ indentedPrinter+ modify (\st -> st{nextTab=tab})++instance PPrintable () where { pPrint = return }+instance PPrintable UStr where { pPrint = pUStr }+instance PPrintable Name where { pPrint = pUStr . toUStr }+instance PPrintable t => PPrintable (T.Tree Name t) where+ pPrint t = case t of+ T.Void -> pString "tree"+ T.Leaf o -> leaf o+ T.Branch ox -> pList (pString "tree") "{ " ", " " }" (branch ox)+ T.LeafBranch o ox -> pList (leaf o) " { " ", " " }" (branch ox)+ where+ leaf o = pWrapIndent [pString "tree(", pPrint o, pString ")"]+ branch = map (\ (lbl, o) -> pMapAssoc (lbl, o)) . M.assocs++instance PPrintable Base16String where { pPrint = pShow }+instance PPrintable Base64String where { pPrint = pShow }++pMapAssoc :: (PPrintable a, PPrintable o) => (a, o) -> PPrint+pMapAssoc (a, o) = pWrapIndent [pPrint a, pString " = ", pPrint o]++-- not for export+appendString :: Int -> String -> PPrint+appendString len str = modify $ \st ->+ st{ printerCol = printerCol st + len+ , printerBuf = printerBuf st ++ str+ , charCount = charCount st + len+ }++-- | Print a 'Dao.String.UStr' as a single line.+pUStr :: UStr -> PPrint+pUStr u = if nil==u then return () else appendString (ulength u) (uchars u)++-- | Print a 'Prelude.String' as a single line.+pString :: String -> PPrint+pString s = if null s then return () else appendString (length s) s++-- | Print any value that instantiates 'Prelude.Show'.+pShow :: Show a => a -> PPrint+pShow = pString . show++-- | Shortcut for @('pPrint' . 'Data.List.concat')@+pConcat :: [String] -> PPrint+pConcat = pString . concat++-- | Just keep printing items along the line without wrapping until a 'pNewLine' or 'pEndLine'+-- occurs. Actually, this function simply a synonym for 'Control.Monad.sequence_'.+pNoWrap :: [PPrint] -> PPrint+pNoWrap = sequence_++-- | Try to print with the given function, but if the printed text runs past the 'maxWidth', or if+-- the printed text results in multiple lines of output, end the current line of text before+-- placing the text from the given function.+pWrap :: PPrint -> PPrint+pWrap fn = do+ st <- get+ let trySt = execState fn (subprint st)+ if printerCol st + charCount trySt > maxWidth st then pEndLine else return ()+ appendState trySt++-- | Evaluate the 'PPrintM' printer, and every line of output will be used as an item in a list and+-- printed across a line, wrapping on to the next line if the line goes past the width limit.+pInline :: [PPrint] -> PPrint+pInline = sequence_ . map pWrap++-- | Like 'pInline' but if the line wraps, every line after the first will be indented.+pWrapIndent :: [PPrint] -> PPrint+pWrapIndent px = do+ st <- get+ let trySt = execState (pInline px) (subprint st)+ case printerOut trySt of+ [] -> appendState trySt+ p:px ->+ let ind (tab, len, str) = (tab+1, len, str)+ in appendState (trySt{printerOut = p : map ind px, printerTab = printerTab trySt + 1})++-- | Will evaluate a 'PPrintM' function to create a block of text, and if the block of text can be+-- fit into a single line, it will be placed inline with the text precceding and succeeding it.+-- If it cannot be placed into a single line, it will be preceeded and succeeded by a 'pEndLine'.+-- Passing 'Prelude.False' as the first parameter means 'pEndLine' will not succeed the block of+-- text, which can come in handy (for example) when you need to follow an item with a closing+-- punctuation mark like a comma or semicolon, and you don't want that closing punctuation on the+-- next line.+pGroup :: Bool -> PPrint -> PPrint+pGroup after fn = do+ st <- get+ let trySt = execState (pEndLine >> fn) (subprint st)+ if charCount trySt > maxWidth st || forcedNewLine trySt+ then pEndLine >> appendState trySt >> (if after then pEndLine else return ())+ else appendState (stateJoinLines trySt)++pList :: PPrint -> String -> String -> String -> [PPrint] -> PPrint+pList header open separator close px = do+ let sep = ustr separator+ pGroup False $ do+ header >> pString open >> pEndLine+ pIndent $ pInline $ map (pGroup True) $ mapAlmost (>>(pUStr sep)) px+ pEndLine >> pString close++-- | Like 'pList' but there is no need to pass the first @'PPrintM' ()@ header parameter, this+-- parameter is set to @'Prelude.return' ()@.+pList_ :: String -> String -> String -> [PPrint] -> PPrint+pList_ = pList (return ())++pClosure :: PPrint -> String -> String -> [PPrint] -> PPrint+pClosure header open close px = do+ st <- get+ let content = do+ header >> pString open >> pEndLine+ pIndent (sequence_ $ mapAlmost (>>pEndLine) px)+ pEndLine >> pString close+ trySt = execState content (subprint st)+ if charCount trySt + printerCol st > maxWidth st then pEndLine else return ()+ appendState trySt++-- | A commonly used pattern, like 'pClosure' but the contents of it is always a list of items which+-- can be pretty-printed by the given @(o -> 'PPrintM' ())@ function.+pContainer :: String -> (o -> PPrint) -> [o] -> PPrint+pContainer label prin ox = pList (pString label) "{ " ", " " }" (map prin ox)++----------------------------------------------------------------------------------------------------++type PPrintM a = State Printer a+type POutput = (Int, Int, UStr)++-- not for export+data Printer+ = Printer+ { printerTab :: Int -- how many indentation marks should preceed this line+ , printerCol :: Int -- how many non-indentation characters are in the buffer+ , printerOut :: [POutput] -- all lines before the current line in the buffer+ , printerBuf :: String -- buffers the current line+ , nextTab :: Int+ , lineCount :: Int -- how many lines have been printed+ , charCount :: Int -- how many characters have been printed+ , maxWidth :: Int+ , forcedNewLine :: Bool+ }++initPrinter :: Int -> Printer+initPrinter width =+ Printer+ { printerTab = 0+ , printerCol = 0+ , printerOut = []+ , printerBuf = ""+ , maxWidth = width+ , lineCount = 0+ , charCount = 0+ , nextTab = 0+ , forcedNewLine = False+ }++printerOutputTripple :: Printer -> (Int, Int, UStr)+printerOutputTripple st = (printerTab st, printerCol st, ustr (printerBuf st))++instance Monoid Printer where+ mempty = initPrinter 80+ mappend origSt st = case printerOut st of+ [] ->+ (combine origSt st)+ { printerBuf = printerBuf origSt ++ printerBuf st+ , printerCol = printerCol origSt + printerCol st+ }+ (_, col, buf):out ->+ (combine origSt st)+ { printerOut = printerOut origSt +++ (printerTab origSt, printerCol origSt + col, ustr (printerBuf origSt ++ uchars buf)) : out+ , printerBuf = printerBuf st+ , printerCol = printerCol st+ }+ where+ combine origSt st = + origSt+ { charCount = charCount origSt + charCount st+ , lineCount = lineCount origSt + lineCount st+ , maxWidth = maxWidth origSt+ , printerTab = printerTab st+ , nextTab = nextTab st+ , forcedNewLine = forcedNewLine origSt || forcedNewLine st+ }++-- | Force a string into the 'printerBuf' buffer without modifying anything else. This should allow+-- you to put markers into the output without effecting any of the metrics used to control how the+-- output is indented or wrapped.+pDebug :: (Printer -> String) -> PPrint+pDebug fn = do+ st <- get+ let msg = "["++fn st++"]"+ put (st{printerBuf=printerBuf st ++ seq msg msg})++stateJoinLines :: Printer -> Printer+stateJoinLines st =+ st{printerBuf = str ++ printerBuf st, printerCol = len + printerCol st, printerOut=[]} where+ (len, str) = foldl joinln (0, "") (printerOut st)+ joinln (len0, str0) (_, len1, str1) = (len0+len1, str0 ++ uchars str1)++appendState :: Printer -> PPrint+appendState = modify . flip mappend++-- | A kind of pre-conversion, the 'PPrintState' is broken into a list of strings, each string+-- preceeded by it's indentation factor.+linesFromPPrintState :: Int -> PPrint -> [(Int, String)]+linesFromPPrintState maxWidth ps = end (execState ps (initPrinter maxWidth)) where+ end st = flip map (printerOut st ++ [printerOutputTripple st]) $ \ (a, _, b) ->+ (a, dropWhile isSpace (chomp (uchars b)))++printAcross :: [PPrint] -> PPrint+printAcross px = case px of+ [] -> return ()+ p:px -> do+ st <- get+ st <- return (st{printerBuf = printerBuf st})+ let trySt = execState p (subprint st)+ if withinMaxWidth st trySt+ then put (mappend st trySt)+ else pEndLine >> modify (\st -> mappend st trySt)+ printAcross px++withinMaxWidth :: Printer -> Printer -> Bool+withinMaxWidth st trySt = null (printerOut trySt) && printerCol st + printerCol trySt <= maxWidth st++subprint :: Printer -> Printer+subprint st = st{printerBuf="", printerCol=0, printerOut=[], charCount=0, lineCount=0}++tabAll :: Bool -> [POutput] -> [POutput]+tabAll alsoTabFinalLine ax = case ax of+ [] -> []+ [(tab, len, str)] -> if alsoTabFinalLine then [(tab+1, len, str)] else [(tab, len, str)]+ (tab, len, str):ax -> (tab+1, len, str) : tabAll alsoTabFinalLine ax++-- | Given a list of strings, each prefixed with an indentation count, and an indentation string,+-- concatenate all strings into a one big string, with each string being indented and on it's own+-- line.+linesToString :: String -> [(Int, String)] -> String+linesToString indentStr = intercalate "\n" .+ map (\ (indentCount, content) -> concat (replicate indentCount indentStr) ++ content)++-- Given an indentation string and a maximum width value, construct a string from the 'PPrintState'.+-- The maximum width value is used to call 'linesFromPPrintState', and the indentation string is+-- used to call 'linesToString'.+showPPrint :: Int -> String -> PPrint -> String+showPPrint maxWidth indentStr ps = linesToString indentStr (linesFromPPrintState maxWidth ps)++----------------------------------------------------------------------------------------------------++-- | Statements like "if" or "while" take a condition, and the Dao languages does not require these+-- conditions be enclosed in parenthases. The question is, should there be a space after the "if" or+-- "while" statement? This function resolves that question by checking if an object expression+-- already is enclosed in parentheses, and if so, does not put a space. Otherwise, a space will be+-- printed between the "if" tag or "while" tag and the condition.+class PrecedeWithSpace a where { precedeWithSpace :: a -> Bool }+instance PrecedeWithSpace Name where { precedeWithSpace _ = True }+
+ src/Dao/Parser.hs view
@@ -0,0 +1,1927 @@+-- "src/Dao/Parser.hs" a parser for defining general context-free+-- grammars that are parsed in two phases: the lexical and the+-- syntactic analysis phases.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++module Dao.Parser where++import Dao.String+import Dao.Token+import Dao.Predicate+import qualified Dao.Interval as Iv++import Control.Applicative+import Control.Monad+import Control.Monad.Cont+import Control.Monad.State+import Control.Monad.Error.Class++import Data.Data+import Data.Tuple+import Data.Monoid+import Data.Maybe+import Data.Word+import Data.Char hiding (Space)+import Data.List+import Data.Array.IArray+import qualified Data.Map as M++----------------------------------------------------------------------------------------------------+-- $Lexer_builder+-- When defining a computer language, one essential step will be to define your keywords and+-- operators, and define tokens for these keywords and operators.+--+-- However, it might be more convenient if there was a way to simply declare to your program "here+-- are my keywords, here are my operators, here is how you lex comments, here is how you lex white+-- spaces", stated simply using Haskell functions, and then let the token types be derived from+-- these declarations. The functions in this section intend to provide you with this ability.++-- | Here is class that allows you to create your own token type from a Haskell newtype, like so:+-- > newtype MyToken = MyToken{ unwrapMyToken :: TT }+-- > instance TokenType MyToken where+-- > 'wrapTT' = MyToken+-- > 'unwrapTT' = unwrapMyToken+-- > +-- > myTokenDB = 'makeTokenDB' $ do+-- > ....+class (Ix a, Show a) => TokenType a where { wrapTT :: TT -> a; unwrapTT :: a -> TT; }+instance TokenType TT where { wrapTT = id; unwrapTT = id; }++-- | An actual value used to symbolize a type of token is a 'TT'. For example, an integer token+-- might be assigned a value of @('TT' 0)@ a keyword might be @('TT' 1)@, an operator might be+-- @('TT' 2)@, and so on. You do not define the numbers representing these token types, these+-- numbers are defined automatically when you construct a 'LexBuilderM'.+--+-- A 'TT' value is just an integer wrapped in an opaque newtype and deriving 'Prelude.Eq',+-- 'Prelude.Ord', 'Prelude.Show', and 'Data.Ix.Ix'. The constructor for 'TT' is not exported, so you+-- can rest assured any 'TT' objects in your program can only be generated during construction of a+-- 'LexBuilderM'.+-- +-- It is also a good idea to wrap this 'TT' type in your own newtype and define your parser over+-- your newtype, which will prevent you from confusing the same 'TT' type in two different parsers.+-- For example:+-- > newtype MyToken { myTokenTT :: TT }+-- > myLexer :: 'Lexer' MyToken ()+-- > myLexer = ...+-- If you instantiate your newtype into the 'TokenType' class, you can also very easily instantiate+-- 'Prelude.Read' and 'Prelude.Show' for your tokens.+newtype TT = MkTT{ intTT :: Int } deriving (Eq, Ord, Ix, Typeable)+instance Show TT where { show (MkTT tt) = "TT"++show tt }++-- Not for export, wouldn't want people making arbitrary 'TT's now, would we?+enumTTFrom :: TT -> TT -> [TT]+enumTTFrom (MkTT a) (MkTT b) = map MkTT [a..b]++-- | The data type constructed from the 'LexBuilderM' monad, used to build a 'Lexer' for your+-- programming language, and also can be used to define the 'Prelude.Show' instance for your token+-- type using 'deriveShowFromTokenDB'.+data TokenDB tok =+ TokenDB+ { tableTTtoUStr :: Array TT UStr+ , tableUStrToTT :: M.Map UStr TT+ , tokenDBLexer :: Lexer tok ()+ }++-- | A state for the 'LexBuilderM' monad, used to declaring the regular expressions for a lexer. This+-- state is converted to a 'TokenDB' which is used by the parser to identify tokens.+-- > myTokens :: 'LexBuilderM'+-- > myTokens = do+-- > let keyword = 'stringTable' . 'Prelude.unwords'+-- > keyword "if then else case of let in where"+-- > key "() == /= -> \\ : :: ~ @"+-- > lexer "string.literal" 'lexStringLiteral'+-- > lexer "comment.endline" 'lexEndlineC_Comment'+-- > lexer "comment.inline" 'lexInlineC_Comment'+-- > -- (The dots in the token type name do not mean anything, it just looks nicer.)+data LexBuilderState+ = LexBuilderState+ { regexItemCounter :: TT+ , stringToIDTable :: M.Map UStr TT+ -- ^ contains all simple lexers which do not establish loops. This would be any lexer that+ -- takes a keyword or operator, or a lexer constructed from a 'Regex' (which might or might+ -- not loop) but produces only one kind of token.+ }++newtype LexBuilderM a = LexBuilderM{ runLexBuilder :: State LexBuilderState a }+type LexBuilder tok = LexBuilderM (Lexer tok ())++instance Monad LexBuilderM where+ return = LexBuilderM . return+ (LexBuilderM a) >>= b = LexBuilderM (a >>= runLexBuilder . b)+instance Functor LexBuilderM where { fmap f (LexBuilderM m) = LexBuilderM (fmap f m) }+instance Applicative LexBuilderM where { pure = return; (<*>) = ap; }+instance Monoid a =>+ Monoid (LexBuilderM a) where { mempty = return mempty; mappend = liftM2 mappend; }++-- | This class exists to make 'emptyToken', 'fullToken', and 'activate' functions polymorphic over+-- two different types: the 'RegexBaseType's and 'Regex' and @['Regex']@ types.+class RegexType rx where { toRegex :: rx -> Regex }+instance RegexType Char where { toRegex = rx }+instance RegexType String where { toRegex = rx }+instance RegexType UStr where { toRegex = rx }+instance RegexType (Iv.Set Char) where { toRegex = rx }+instance RegexType [Iv.Set Char] where { toRegex = rx }+instance RegexType Regex where { toRegex = id }+instance RegexType [Regex] where { toRegex = mconcat }+instance RegexType (Char, Char) where { toRegex = rx }+instance RegexType [(Char, Char)] where { toRegex = rx }++-- example :: Regex+-- example = regex 'a' "hello" ('0', '9')++-- not for export+initLexBuilder :: LexBuilderState+initLexBuilder = LexBuilderState (MkTT 1) mempty++-- not for export+newTokID :: UStr -> State LexBuilderState TT+newTokID u = do+ tok <- fmap (M.lookup u) (gets stringToIDTable)+ case tok of+ Nothing -> do+ tok <- gets regexItemCounter+ modify $ \st ->+ st{ regexItemCounter = MkTT (intTT tok + 1)+ , stringToIDTable = M.insert u tok (stringToIDTable st)+ }+ return tok+ Just tok -> return tok++-- not for export+makeRegex :: RegexType rx => Bool -> UStr -> rx -> LexBuilderM Regex+makeRegex keep u re = LexBuilderM $ newTokID u >>= \tok ->+ return (toRegex re . (if keep then rxToken else rxEmptyToken) tok)++-- | The 'tokenHold' function creates a token ID and associates it with a type that is used to+-- construct a 'Regex', and returns the 'Regex' to the 'LexBuilderM' monad. However this does not+-- actually 'activate' the 'Regex', it simply allows you to use the returned 'Regex' in more+-- complex expressions that might construct multiple tokens. You can 'tokenHold' expressions in any+-- order, ordering is not important.+fullToken :: (UStrType str, RegexType rx) => str -> rx -> LexBuilderM Regex+fullToken s = makeRegex True (toUStr s)++-- | 'token' has identical behavior as 'tokenHold', except the 'Regex' created will produce an empty+-- token, that is, a token that only indicates it's type and contains none of the string that+-- matched the regular expression. This can be done when it is clear (or you do not care) what+-- string was lexed when the token was created just by knowing the type of the token, for example+-- whitespace tokens, or keyword tokens which have the text used to create the token easily+-- retrievable by converting token to a string. Making use of 'token' can greatly improveme+-- performance as compared to using 'tokenHold' exclusively.+emptyToken :: (UStrType str, RegexType rx) => str -> rx -> LexBuilderM Regex+emptyToken s = makeRegex False (toUStr s)++-- | Activating a regular expression actually converts the regular expression to a 'Lexer'. The+-- /order of activation is important/, expressions that are defined first will have the first try at+-- lexing input strings, followed by every expression activated after it in order. It is also NOT+-- necessary to define an expression with 'regex' or 'regexHold' before activating it, however+-- expressions that have not been associated with a token type will simply consume input without+-- producing any tokens. This might be desireable for things like whitespace, but it is usually not+-- what you want to do.+activate :: (TokenType tok, RegexType rx) => rx -> LexBuilder tok+activate = return . regexToLexer . toRegex++-- | An array of tokenizers with every tokenizer indexed by the very first character they accept.+data LexTable lexFunc+ = LexNoTable{ lexFinal :: lexFunc }+ | LexTable{ lexTableArray :: Array Char (lexFunc), lexFinal :: lexFunc }++-- | Create a 'LexTable' array with the given list of 'Regex's, the indecies of the array created+-- are determined by the lowest and highest possible characters that of all of the 'Regex's you+-- provide. If your parser works with UTF characters beyond the ASCII range, the arrays can get+-- pretty big if you are not careful. Try to group parsers together that are near each other in the+-- UTF table, which involves making sure the 'Prelude.Int' value returned by 'Data.Char.ord' for the+-- characters that the tokenizer can match are all near each other on the number line. If your+-- parser only works on ASCII characters, you have nothing to worry about, your tokenizer array will+-- not be larger than 128 indecies.+makeRegexTable :: RegexType rx => rx -> LexTable Regex+makeRegexTable regex = case regexToLexerPairs (toRegex regex) of+ ([] , final) -> LexNoTable final+ (elems, final) -> LexTable (accumArray mappend mempty bounds elems) final where+ bounds = foldl (\ (lo, hi) c -> (min lo c, max hi c)) (initIdx, initIdx) (map fst elems)+ initIdx = fst (head elems)++lexTableRegexToLexer :: TokenType tok => LexTable Regex -> LexTable (Lexer tok ())+lexTableRegexToLexer table = case table of+ LexNoTable regex -> LexNoTable (regexToLexer regex)+ LexTable table regex -> LexTable (amap regexToLexer table) (regexToLexer regex)++makeLexTable :: (TokenType tok, RegexType rx) => rx -> LexTable (Lexer tok ())+makeLexTable = lexTableRegexToLexer . makeRegexTable++lexTableToLexer :: TokenType tok => LexTable (Lexer tok ()) -> Lexer tok ()+lexTableToLexer table = case table of+ LexNoTable final -> final+ LexTable table final -> do+ cx <- gets lexInput+ case cx of+ [] -> mzero+ c:_ -> if inRange (bounds table) c then table!c else final++-- | If you don't care about the 'LexTable' value but want to convert the 'Regex' to a 'Lexer' using a+-- 'LexTable', use this function. The array will be allocated in memory and used to perform lexing,+-- but your code will never see a reference to the 'LexTable' as it is passed directly to+-- 'lexTableToLexer'. This function is defined as:+-- > 'lexTableToLexer' . 'lexTableRegexToLexer' . 'makeRegexTable'+regexToTableLexer :: (RegexType rx, TokenType tok) => rx -> Lexer tok ()+regexToTableLexer = lexTableToLexer . lexTableRegexToLexer . makeRegexTable++-- | Once you have defined your 'LexBuilderM' function using monadic notation, convert the value of+-- this function to a 'TokenDB' value. The 'TokenDB' is mostly handy for retreiving the string+-- values associated with each token ID created by the 'newTokenType' function, for example when+-- using 'dervieShowFromTokenDB' which uses a 'TokenDB' to produce a function suitable for+-- instantiating your @'TokenType'@ into the 'Prelude.Show' class.+makeTokenDB :: TokenType tok => LexBuilderM (Lexer tok ()) -> TokenDB tok+makeTokenDB builder =+ TokenDB+ { tableTTtoUStr = array (MkTT 1, regexItemCounter st) $+ fmap swap $ fmap (fmap unwrapTT) $ M.assocs tabmap+ , tokenDBLexer = mplus (void (many lexer)) (return ())+ , tableUStrToTT = tabmap+ }+ where+ tabmap = stringToIDTable st+ (lexer, st) = runState (runLexBuilder builder) $+ LexBuilderState{regexItemCounter=MkTT 1, stringToIDTable=mempty}++-- | This function lets you easily "derive" the instance for 'Prelude.Show' for a given 'TokenType'+-- associated with a 'TokenDB'. It should be used like so:+-- > newtype MyToken = MyToken{ unwrapMyToken :: TT }+-- > instance 'TokenType' MyToken where{ wrapTT = MyToken; unwrapTT = unwrapMyToken; }+-- > instance 'Prelude.Show' MyToken where{ show = 'deriveShowFromTokenDB' myTokenDB }+-- > myTokenDB :: 'TokenDB' MyToken+-- > myTokenDB = 'makeTokenDB' $ ....+deriveShowFromTokenDB :: TokenType tok => TokenDB tok -> tok -> String+deriveShowFromTokenDB tokenDB tok =+ let str = uchars (tableTTtoUStr tokenDB ! unwrapTT tok)+ in if or (map (not . isAlphaNum) str) then show str else str++tokTypeToUStr :: (TokenType tok, HasTokenDB tok) => tok -> UStr+tokTypeToUStr tok =+ let arr = (tableTTtoUStr (tokenDBFromToken tok))+ tt = unwrapTT tok+ in if inRange (bounds arr) tt+ then arr!tt+ else error ("no registered identifier "++show tt++" in tokenDB")++tokTypeToString :: (TokenType tok, HasTokenDB tok) => tok -> String+tokTypeToString = uchars . tokTypeToUStr++tokenToUStr :: (TokenType tok, HasTokenDB tok) => TokenAt tok -> UStr+tokenToUStr tok = case asToken tok of+ EmptyToken t -> tokTypeToUStr t+ CharToken _ c -> ustr [c]+ Token _ u -> u++tokenToString :: (TokenType tok, HasTokenDB tok) => TokenAt tok -> String+tokenToString tok = case asToken tok of+ EmptyToken t -> uchars (tokTypeToUStr t)+ CharToken _ c -> [c]+ Token _ u -> uchars u++-- | Get token from a 'TokenDB' that was associated with the 'Dao.String.UStrType'.+maybeLookupToken :: (UStrType str, TokenType tok) => TokenDB tok -> str -> Maybe tok+maybeLookupToken tokenDB = fmap wrapTT . flip M.lookup (tableUStrToTT tokenDB) . toUStr++-- | Like 'maybeLookupToken' but evaluates to 'Prelude.error' if no such token was defined.+lookupToken :: (UStrType str, TokenType tok) => TokenDB tok -> str -> tok+lookupToken tokenDB str =+ fromMaybe (error $ "internal: token "++show (toUStr str)++" was never defined") $+ maybeLookupToken tokenDB str++mk_keyword :: UStrType str => Regex -> str -> LexBuilderM (UStr, TT)+mk_keyword regex key = do+ let ukey = toUStr key+ keyChars = uchars ukey+ if fst (runRegex regex keyChars :: (Bool, ([TokenAt TT], String)))+ then LexBuilderM (newTokID ukey) >>= return . (,) ukey+ else error ("keyword token "++show keyChars++"does not match it's own keyword Regex")++-- | Create a single keyword 'Regex'. 'Control.Monad.mapM'-ing over this function is not the same as+-- using 'keywordTable', 'keywordTable' creates an actual table when evalauting to a 'Lexer'. This+-- function creates no table, it will simply evaluate to a lexer that returns a token of the keyword+-- type if the keyword matches the input, or else it returns the default token type.+keyword :: (UStrType str, UStrType key) => str -> Regex -> key -> LexBuilderM Regex+keyword deflt regex key = do+ deflt <- LexBuilderM (newTokID (toUStr deflt))+ (ukey, tt) <- mk_keyword regex key+ return (regex . rxMakeToken (\str -> if ustr str==ukey then (False, tt) else (True, deflt)))++-- | To construct a keyword table, you must provide three parameters: the first two are a default+-- 'TokenType' and a 'Regex' used to split non-keyword words out of the character stream. The third+-- parameter is a list of keywords strings. Every keyword string will become it's own token type.+-- The resultant 'Regex' will, when evaluated as a 'Lexer', first try to split a non-keyword string+-- off of the stream. But if that string matches a keyword, a keyword token of it's own type is+-- emitted. Otherwise, it will emit a non-keyword token of the 'TokenType' given here. Keyword+-- tokens are empty, non-keyword contain the characters that matched the regex.+--+-- A common way to use this function is with the 'Data.List.words' function:+-- > 'keywordTable' MyVarNameType ('rx' ('from' @'@a@'@ 'to' @'@z@'@)) $+-- > 'Data.List.words' $ 'Data.List.unwords' $ +-- > [ "while for if then else goto return break continue"+-- > "class instance new delete super typeof" ]+keywordTable :: (UStrType str, UStrType key) => str -> Regex -> [key] -> LexBuilderM Regex+keywordTable deflt regex keys = do+ deflt <- LexBuilderM (newTokID (toUStr deflt))+ keyDict <- fmap M.fromList (forM keys (mk_keyword regex))+ return (regex . rxMakeToken (maybe (True, deflt) ((,) False) . flip M.lookup keyDict . ustr))++-- | Creates a token type with 'regex' where the text lexed from the input is identical to name of+-- the token. The difference between an operator and a keyword is that operators will be lexed+-- regardless of the characters following it in the stream, which means if you have an operator "op"+-- and the next characters in the stream are @"open()"@, then the lexical analysis will split this+-- into @["op", "en()"]@, the remainder @"en()"@ characters must be lexed by some other lexer,+-- and @"op"@ will be treated as a single operator token. Keywords do not work this way.+operator :: UStrType str => str -> LexBuilderM Regex+operator str = do+ let u = toUStr str+ case ulength u of+ 0 -> return id+ 1 -> makeRegex False u (head (uchars u))+ _ -> makeRegex False u u++-- | Creates a 'TokenTable' using a list of keywords or operators you provide to it. Use this+-- function to ensure operators do not interfear with each other. For example, if you have two+-- operators @"=="@ and @"="@, the lexer must try to split the @"=="@ operator from the stream+-- before it tries splitting @"="@. This function ensures that the order in which operators are+-- tried is the correct order.+-- +-- Every string provided becomes it's own token type. For example:+-- > myKeywords = 'tokenTable' $ 'Data.List.words' $ 'Data.List.unwords' $+-- > [ "** * / % + - & | ^",+-- > "= *= /= %= += -= &= |= ^=",+-- > "== != <= >= < >" ]+operatorTable :: UStrType str => [str] -> LexBuilderM Regex+operatorTable = fmap mconcat . mapM operator .+ sortBy (\a b -> compare (ulength b) (ulength a)) . map toUStr++-- | Retrieve a 'TokenType' from a 'UStrType' (or a subclass of 'UStrType' like 'MetaToken') value.+-- This is necesary for building tokenizing regular expressions that are more complicated than a+-- typeical keyword or operator. You must declare in the 'Regex' when to create a token from the+-- given token types returned to the 'LexBuilderM' monad by this function.+getTokID :: (UStrType tokID, TokenType tok) => tokID -> LexBuilderM tok+getTokID tokID = fmap wrapTT (LexBuilderM (newTokID (toUStr tokID)))++----------------------------------------------------------------------------------------------------+-- $Regular_expressions+-- Provides a very simple data type for constructing expressions that can match strings. This type+-- is used in a 'LexBuilderM' to and are associated with token types such than when the 'LexBuilderM'+-- is converted to a 'Lexer', a token of the associated type is generated when a regex matches+-- the beginning of the input string that is being lexically analyzed.++-- | Any function with a type @'RegexUnit' -> 'RegexUnit'@ or 'Regex' can be used in a+-- dot-operator-separated sequence of expressions. But the 'rx' function provided by this class+-- introduces a kind of constant expression that can be used in sequences of 'RegexUnit's. For+-- example, suppose you have defined a regular expression @digit@ of type+-- @'RegexUnit' -> 'RegexUnit'@ to match an arbitrarily long sequence of numeric characters, and a+-- regular expression @space@ of type 'Regex' to match an arbitrarily long sequence of whitespace+-- characters. You could then define a regular expression with a sequence like so:+-- > myRegex :: Regex+-- > myRegex = 'rx' "first" . space . digit . space . 'rx' "second" . space . digit+-- Here, we take advantage of the fact that 'Prelude.String' is instnatiated into the+-- 'RegexBaseType' class, because this allows us to write @'rx' "first"@ and @'rx' "second"@, which+-- will match the strings "first" and "second" if they occur in the input string to be matched. This+-- expression is equivalent to the POSIX regular expression+-- @first[[:space:]]+[[:digit:]]+[[:space:]]+second[[:space:]]+[[:digit:]]+@+-- which would match strings like the following:+-- > "first 1231 second 99"+-- > "first 0 second 1233491202"+-- +-- /To create a choice/ you can use 'Data.Monoid.mappend', 'Data.Monoid.mconcat', or the infix+-- operator equivalent of 'Data.Monoid.mappend', which is 'Data.Monoid.<>'. The reason is,+-- 'Data.Monoid.Monoid' instantiates functions of type @a -> b@, and this provides a default+-- instnatiation for functions of type 'Regex'. For example,+-- suppose you have two regular expressions, @regexA@ and @regexB@. If you want to construct a+-- 'Regex' that tries matching @regexA@, and if it fails, then tries @regexB@, you would write:+-- > matchAorB = regexA 'Data.Monoid.<>' regexB+-- You can use 'Data.Monoid.concat' to create larger choices:+-- > 'Data.Monoid.mconcat' ['rx' "tryThis", 'rx' "thenThis", regexA, regexB]+-- or equivalently:+-- > 'rx' 'Data.Monoid.<>' "tryThis" 'Data.Monoid.<>' 'rx' "thenThis" 'Data.Monoid.<>' regexA 'Data.Monoid.<>' regexB+-- +-- Another advantage of 'Regex' being a function of type @'RegexUnit' -> 'RegexUnit'@ is that any+-- function of the type @a -> a@ can easily be used with the 'Prelude.fix' function to create loops,+-- for example, to lex an arbitrarily long sequence of numbers separated by spacecs:+-- > do space <- 'newTokenType' "SPACE"+-- > number <- 'newTokenType' "NUMBER"+-- > 'regex' $ 'Prelude.fix' $ \loop ->+-- > ('rxRepeat1'('ch' ' ') . 'rxEmptyToken' space 'Data.Monoid.<>' 'rxRepeat1'('from' '0' 'to' '9') . 'rxToken' number) . loop+type Regex = RegexUnit -> RegexUnit++-- Not for export, this is a type used in the 'RxMakeToken' constructor, it is a basically a data+-- representing a function that can convert a string to a 'TT', but could be a constant function.+data MakeToken = MakeToken (String -> (Bool, TT)) | ConstToken Bool TT+instance Eq MakeToken where+ ConstToken kA ttA == ConstToken kB ttB = kA==kB && ttA==ttB+ _ == _ = False+instance Show MakeToken where+ show (ConstToken kA ttA) = (if kA then "full " else "empty ")++show ttA+ show _ = "MakeToken"++evalMakeToken :: TokenType tok => MakeToken -> String -> Lexer tok ()+evalMakeToken mkt withStr = (if keep then makeToken else makeEmptyToken) (wrapTT tt) where+ (keep, tt) = case mkt of+ ConstToken keep tt -> (keep, tt)+ MakeToken make -> make withStr++-- | This is an intermediate type for constructing 'Regex's. You will not be using it directly. You+-- will instead use any function that evaluates to a 'Regex', which is a function of this data type.+data RegexUnit+ = RxBacktrack+ | RxSuccess+ | RxChoice { getSubRegexes :: [RegexUnit] }+ | RxStep { rxStepUnit :: RxPrimitive , subRegex :: RegexUnit }+ | RxExpect { rxErrMsg :: UStr , subRegex :: RegexUnit }+ | RxDontMatch{ subRegex :: RegexUnit }+ | RxMakeToken{ rxMakeFunc :: MakeToken , subRegex :: RegexUnit }+ deriving Eq+instance Show RegexUnit where+ show rx = loop rx where+ loop rx = case rx of+ RxBacktrack -> "RxBacktrack"+ RxSuccess -> "RxSuccess"+ RxChoice c -> "RxChoice "++show c+ RxStep p _ -> "RxStep ("++showRegexPrim p++") ..."+ RxExpect e _ -> "RxExpect "++show e++" ..."+ RxDontMatch _ -> "RxDontMatch ..."+ RxMakeToken t _ -> "RxMakeToken ("++show t++") ..."+instance Show (RegexUnit -> RegexUnit) where { show rx = show (rx RxSuccess) }+instance Monoid RegexUnit where+ mempty = RxBacktrack+ mappend a b = case a of+ RxBacktrack -> b+ RxSuccess -> a+ RxExpect _ _ -> a+ RxStep a' ax -> case b of+ RxStep b' bx -> if a' == b' then RxStep a' (ax<>bx) else RxChoice [a,b]+ b -> RxChoice [a,b]+ RxDontMatch ax -> case b of+ RxDontMatch bx -> RxDontMatch (ax<>bx)+ b -> RxChoice [a,b]+ RxChoice ax -> case b of+ RxChoice bx -> reduce (loop ax bx)+ b -> reduce (loop ax [b])+ RxMakeToken ta ax -> case b of+ RxMakeToken tb bx | ta==tb -> RxMakeToken ta (ax<>bx)+ b -> RxChoice [a,b]+ where+ reduce list = case list of+ [] -> RxBacktrack+ [item] -> item+ list -> RxChoice list+ loop ax bx = case ax of+ [] -> bx+ [a] -> case bx of+ [] -> [a]+ b:bx -> case a<>b of+ RxChoice ax -> ax++bx+ b -> b : loop ax bx -- NEEDS TO BE TESTED, changed from (a:b:ax)+ a:ax -> a : loop ax bx++-- | Convert a 'Regex' function to a 'Lexer'. The resulting 'Lexer' will not call 'makeToken' or+-- 'makeEmptyToken', it will only match the beginning of the input string according to the 'Regex',+-- leaving the matched characters in the 'lexBuffer'.+-- +-- /BUG FIX:/ 'regexToLexer' should be distributive over 'Data.Monoid.mappend'ed 'Regex's, i.e the+-- statement:+-- > 'regexToLexer' (regex1 'Data.Monoid.<>' regex2)+-- should be identical to:+-- > 'regexToLexer' regex1 'Data.Monoid.<>' 'regexToLexer' regex2+-- however I have discovered that this is not the case for all possible 'Regex's. I need to write a+-- QuickCheck test to figure out why and correct this problem.+regexToLexer :: TokenType tok => Regex -> Lexer tok ()+regexToLexer re = loop (re RxSuccess) where+ loop re = case re of+ RxBacktrack -> lexBacktrack+ RxSuccess -> return ()+-- RxMakeToken tt re -> gets lexBuffer >>= evalMakeToken tt >> loop re+ RxMakeToken tt re -> case re of+ RxMakeToken tt re -> loop (RxMakeToken tt re)+ _ -> gets lexBuffer >>= evalMakeToken tt >> loop re+ -- if there are two 'RxMakeToken's in a row, use the later one. This makes for more+ -- intuitive regular expressions. -- TODO, maybe lets not do this.+ RxChoice re -> msum $ map loop re+ RxStep r re -> do+ keep <- gets lexBuffer+ clearBuffer+ mplus (regexPrimToLexer r >> modify (\st -> st{lexBuffer = keep ++ lexBuffer st}))+ (modify (\st -> st{lexBuffer=keep, lexInput = lexBuffer st ++ lexInput st}) >> mzero)+ loop re+ RxExpect err re -> mplus (loop re) (fail (uchars err))+ RxDontMatch re -> case re of+ RxDontMatch re -> loop re -- double negative means succeed on match+ re -> do+ keep <- gets lexBuffer+ matched <- clearBuffer >> mplus (loop re >> return True) (return False)+ if matched+ then do -- fail if matched+ modify $ \st ->+ st{ lexBuffer = ""+ , lexInput = keep ++ lexBuffer st ++ lexInput st+ }+ mzero+ else do -- success if not matched+ modify $ \st ->+ st{ lexBuffer = keep+ , lexInput = lexBuffer st ++ lexInput st+ }+ return ()++-- Evaluate a 'Regex' to a 'Lexer' using 'regexToLexer', but also create a list of pairs, every pair+-- containing a character in the set of characters that this 'Regex' accepts. So, for example, if+-- the 'Regex' passed to this function is created from an 'Dao.Interval.EnumSet' with characters from+-- @a@ to @z@, this function will evaluate to a list of 26 pairs with every character from @a@ to+-- @z@ as the first element and the 'Lexer' as the second element. Every pair has the exact same+-- lexer function. This function returns 'Data.Maybe.Nothing' if the 'Regex' given does not evaluate+-- to a 'RxChoice' or 'RxStep' regex.+-- +-- Evaluates to a pair, the first element being a list of paris mapping characters to lexers, the+-- second being the lexers which cannot be mapped to characters but should be tried if any of the+-- character lexers backtrack. Lexers that cannot be mapped to characters but occur in the middle of+-- the list of 'RegexUnit's list are bunched together and prepended to the lexers that can be mapped+-- to characters.+regexToLexerPairs :: Regex -> ([(Char, Regex)], Regex)+regexToLexerPairs regex = case regex RxSuccess of+ RxChoice [] -> (mzero, mempty)+ RxChoice [r] -> regexToLexerPairs (const r)+ RxChoice rx -> loop mempty [] (flatten rx)+ RxStep p r -> loop mempty [] [RxStep p r]+ _ -> (mzero, mempty)+ where+ flatten = concatMap $ \r -> case r of+ RxChoice rx -> flatten rx+ r -> [r]+ done pre stk = (stk, const pre)+ loop :: RegexUnit -> [(Char, Regex)] -> [RegexUnit] -> ([(Char, Regex)], Regex)+ loop pre stk rx = case rx of+ [] -> done pre stk+ r:rx -> case r of+ RxSuccess -> done (pre<>RxSuccess) stk+ RxBacktrack -> loop pre stk rx+ RxStep p _ -> loopStep r p where+ loopStep r p = case p of+ RxDelete -> loop (pre<>r) stk rx+ RxString u -> case uchars u of+ "" -> done (pre<>RxSuccess) stk+ c:_ -> loop mempty (create pre stk [c] r) rx+ RxCharSet cs -> case Iv.elems cs of+ [] -> loop pre stk rx+ cx -> loop mempty (create pre stk cx r) rx+ RxRepeat _ _ p -> loopStep r p+ r -> loop (pre<>r) stk rx+ create pre stk cx r = let fn = const (pre<>r) in stk ++ map (\c -> (c, fn)) cx++-- Not for export+data RxPrimitive+ = RxDelete+ | RxString { rxString :: UStr }+ | RxCharSet { rxCharSet :: Iv.Set Char }+ | RxRepeat { rxLowerLim :: Iv.Inf Int, rxUpperLim :: Iv.Inf Int, subRegexUnit :: RxPrimitive }+ deriving Eq++showRegexPrim :: RxPrimitive -> String+showRegexPrim re = case re of+ RxDelete -> "(delete)"+ RxString str -> show str+ RxCharSet ch -> show ch+ RxRepeat lo hi re -> "repeat("++show lo++".."++show hi++", "++showRegexPrim re++")"++regexPrimToLexer :: TokenType tok => RxPrimitive -> Lexer tok ()+regexPrimToLexer re = case re of+ RxDelete -> clearBuffer+ RxString str -> lexString (uchars str)+ RxCharSet set -> lexCharP (Iv.member set)+ RxRepeat lo hi re -> rept lo hi re+ where+ rept lo hi re = fromMaybe (seq (error "internal error") $! return ()) $ do+ getLo <- mplus (Iv.toPoint lo >>= return . lowerLim re) (return (return ()))+ getHi <- mplus (Iv.toPoint hi >>= return . upperLim re) (return (noLimit re))+ return (getLo >> mplus getHi (return ()))+ lowerLim re lo = case re of+ RxDelete -> clearBuffer+ RxString str -> lowerLimLex lo (lexString (uchars str))+ RxCharSet set -> do+ keep <- gets lexBuffer+ clearBuffer >> lexWhile (Iv.member set)+ got <- gets lexBuffer+ if length got < lo+ then do+ modify (\st -> st{lexInput = keep ++ got ++ lexInput st, lexBuffer = ""})+ mzero+ else modify (\st -> st{lexBuffer = keep ++ got})+ RxRepeat lo' hi' re -> lowerLimLex lo (rept lo' hi' re)+ lowerLimLex lo lex = replicateM_ lo lex+ upperLim re hi = case re of+ RxDelete -> clearBuffer+ RxString str -> replicateM_ hi (lexString (uchars str))+ RxCharSet set -> lexWhile (Iv.member set)+ RxRepeat lo' hi' re -> replicateM_ hi (rept lo' hi' re)+ noLimit re = case re of+ RxDelete -> clearBuffer+ RxString str -> forever (lexString (uchars str))+ RxCharSet set -> lexWhile (Iv.member set)+ RxRepeat lo hi re -> forever (rept lo hi re)++-- | Any type which instantiates the 'RegexBaseType' class can be used to with the 'rx' function to+-- construct a part of a 'Regex' which can be used in a sequence of 'Regex's.+--+-- Probably the most useful instance of this class apart from that of 'Prelude.String' is the+-- instance for the type @['Dao.Interval.Set' 'Data.Char.Char']@, which allows you to union ranges of+-- character sets like so:+-- > 'rx'['from' '0' 'to' '9', from @'A'@ 'to' @'Z'@, 'from' '@a@' 'to' '@z@', 'ch' '@_@']+-- which would be equivalent to the POSIX regular expression @[0-9A-Za-z_]@, a regular expression+-- that matches any single alphabetic, numeric, of underscore character.+class RegexBaseType t where+ rxPrim :: t -> RxPrimitive+ rx :: t -> Regex+ rx = RxStep . rxPrim+instance RegexBaseType Char where { rxPrim = RxCharSet . Iv.point }+instance RegexBaseType String where { rxPrim = RxString . ustr }+instance RegexBaseType UStr where { rxPrim = RxString }+instance RegexBaseType (Iv.Set Char) where { rxPrim = RxCharSet }+instance RegexBaseType [Iv.Set Char] where { rxPrim = RxCharSet . foldl Iv.union mempty }+instance RegexBaseType (Char, Char) where { rxPrim = RxCharSet . Iv.fromPairs . (:[]) }+instance RegexBaseType [(Char, Char)] where { rxPrim = RxCharSet . Iv.fromPairs }++-- | The type of the 'from' and 'to' functions are specially defined so that you can write ranges of+-- characters. For example, if you want to match upper-case characters, you would simply write:+-- > from 'A' to 'Z'+-- or equivalently:+-- > 'A' `to` 'Z'+-- but I prefer to use 'from' because the use of single quotes and back-quotes together in the same+-- expression is tedius. This would be equivalent to the POSIX regular expressions: @[A-Z]@+from :: Char -> (Char -> Char -> Iv.Set Char) -> Char -> Iv.Set Char+from a to b = to a b++-- | The type of the 'from' and 'to' functions are specially defined so that you can write ranges of+-- characters. For example, if you want to match upper-case characters, you would simply write:+-- > from 'A' to 'Z'+-- or equivalently:+-- > 'A' `to` 'Z'+-- but I prefer to use 'from' because the use of single quotes and back-quotes together in the same+-- expression is tedius. This would be equivalent to the POSIX regular expressions: @[A-Z]@+to :: Char -> Char -> Iv.Set Char+to toch frch = Iv.range frch toch++-- | Create a character set that matches only a single character. For example if you want to match+-- just a single lowercase letter-A character:+-- > ch 'a'+-- This would b equivalent to the POSIX regular expression @[a]@+-- Be careful not to confuse this function with the 'rx' function, which is instantiated to create+-- 'Regex' functions from 'Prelude.Char's. The expression @'rx' @'@@a@@'@ cannot be used in a+-- set of other @'Dao.Interval.Set' 'Prelude.Char'@ types to create a larger set:+-- > badRegex = 'repeat' ['from' '0' 'to' '9', 'rx' @'@.@'@] -- /COMPILE-TIME ERROR!!!/+-- >+-- > -- This matches strings ending in dots, like "98765.", "123.", and "0."+-- > -- but does not match "0.0" or ".123"+-- > numberEndingWithDot 'repeat' ['from' '0' 'to' '9'] . 'rx' '.' +-- >+-- > -- This matches strings like "0.0.0.0", "123.", ".99", "...", "0.0" and "1..20"+-- > dotsOrDigits = 'repeat' ['from' '0' 'to' '9', 'ch' '.']+-- This function is also useful with the 'Prelude.map' function to create a set of characters from a+-- 'Prelude.String':+-- > stringOfVowels = 'rxRepeat' ('Prelude.map' 'ch' "AEIOUaeiou")+ch :: Char -> Iv.Set Char+ch = Iv.point++-- | Produces a character set that matches any character, like the POSIX regular expression dot+-- (@.@). /NEVER use this in a 'rxRepeat' function/ unless you really want to dump the entire+-- remainder of the input string into the 'lexBuffer'.+anyChar :: Iv.Set Char+anyChar = Iv.whole++-- | Invert a character set: this 'Regex' will match any characters not in the union of the sets+-- provided.+invert :: [Iv.Set Char] -> Iv.Set Char+invert = Iv.invert . foldl Iv.union mempty++-- | An optional regex, tries to match, but succeeds regardless of whether or not the given+-- actually matches. In fact, this 'Regex' is exactly identical to the equation:+-- > \regex -> regex 'Data.Monoid.<>' 'Prelude.id'+opt :: Regex -> Regex+opt = (<>id)++-- | This 'Regex' matches nothing and succeeds, and deletes any 'Regex' appended to it with the dot+-- operator. Any 'Regex' occurring after a 'halt' will not be evaluated.+halt :: Regex+halt = const RxSuccess++-- | Marks a point in a 'Regex' sequence where the matching must not fail, and if it does fail, the+-- resulting 'Lexer' to which this 'Regex' evaluates will evaluate to 'Control.Monad.fail' with an+-- error message provided as a paramater to this function. For example:+-- > decimalPoint = digits . 'rx' '.' . 'cantFail' "must have digits after a decimal point" . digits+cantFail :: String -> Regex+cantFail msg = RxExpect (ustr msg)++-- | This is a look-ahead 'Regex' that matches if the 'Regex' parameter provided does not match. An+-- extremely inefficient function, you should avoid using it and consider re-designing your+-- 'LexBuilderM' if you rely on this function too often. This function is designed to occur only at+-- the end of your 'Regex', that is, every 'Regex' that occurs after 'rxDont' is part of the 'Regex'+-- to not match. For example:+-- > myRegex = 'rx' "else" . spaces . 'rxDont' . 'rx' "if" . spaces . rx "end"+-- will succeed only if the string else is not followed by a string @"if end"@. There is no way to+-- make the 'Regex' first check if the next string is not @"if"@ and if it is not then continue+-- matching with @spaces@ and @'rx' "end"@ after that.+dont :: Regex+dont = RxDontMatch++-- | Clear the 'lexBuffer' without creating a token, effectively deleting the characters from the+-- input stream, ignoring those characters.+rxClear :: Regex+rxClear = RxStep RxDelete++-- | Force an error to occur.+rxErr :: String -> Regex+rxErr msg = cantFail msg . mempty++-- | Repeat a regular 'RegexBaseType' regular expression a number of times, with the number of times+-- repeated being limited by an upper and lower bound. Fails to match if the minimum number of+-- occurences cannot be matched, otherwise continues to repeat as many times as possible (greedily)+-- but not exceeding the upper bound given.+rxLimitMinMax :: RegexBaseType rx => Int -> Int -> rx -> Regex+rxLimitMinMax lo hi = RxStep . RxRepeat (Iv.Finite lo) (Iv.Finite hi) . rxPrim++-- | Repeats greedily, matching the 'RegexBaseType' regular expression as many times as possible,+-- but backtracks if the regex cannot be matched a minimum of the given number of times.+rxLimitMin :: RegexBaseType rx => Int -> rx -> Regex+rxLimitMin lo = RxStep . RxRepeat (Iv.Finite lo) Iv.PosInf . rxPrim++-- | Match a 'RegexBaseType' regex as many times as possible (greedily) but never exceeding the+-- maximum number of times given. Must match at least one character, or else backtracks.+rxLimitMax :: RegexBaseType rx => Int -> rx -> Regex+rxLimitMax hi = RxStep . RxRepeat Iv.NegInf (Iv.Finite hi) . rxPrim++-- | Like 'rx' but repeats, but must match at least one character. It is similar to the @*@ operator+-- in POSIX regular expressions. /WARNING:/ do not create regex loops using only regexs of this+-- type, your regex will loop indefinitely.+rxRepeat :: RegexBaseType rx => rx -> Regex+rxRepeat = RxStep . RxRepeat Iv.NegInf Iv.PosInf . rxPrim++-- | Defined as @'rxLimitMin' 1@, matches a primitive 'RegexBaseType' one or more times, rather than+-- zero or more times. It is imilar to the @+@ operator in POSIX regular expressions.+rxRepeat1 :: RegexBaseType rx => rx -> Regex+rxRepeat1 = rxLimitMin 1++-- | Create a token, keep the portion of the string that has matched the regex up to this point, but+-- clear the match buffer once the token has been created.+rxToken :: TokenType tok => tok -> Regex+rxToken tok = RxMakeToken (ConstToken True $ unwrapTT tok)++-- | Create a token, disgarding the portion of the string that has matched the regex up to this point.+rxEmptyToken :: TokenType tok => tok -> Regex+rxEmptyToken tok = RxMakeToken (ConstToken False $ unwrapTT tok)++rxMakeToken :: TokenType tok => (String -> (Bool, tok)) -> Regex+rxMakeToken make = RxMakeToken (MakeToken (fmap (fmap unwrapTT) make))++----------------------------------------------------------------------------------------------------+-- $Lexical_Analysis+-- There is only one type used for lexical analysis: the 'Lexer'. This monad is used to analyze+-- text in a 'Prelude.String', and to emit 'Token's. Internally, the 'Token's+-- emitted are automatically stored with their line and column number information in a 'TokenAt'+-- object.+--+-- Although lexical analysis and syntactic analysis are both separate stages, keep in mind that+-- Haskell is a lazy language. So when each phase is composed into a single function, syntactic+-- analysis will occur as tokens become available as they are emitted the lexical analyzer. So what+-- tends to happen is that lexical and syntactic analysis will occur in parallel.+--+-- Although if your syntactic analysis does something like apply 'Data.List.reverse' to the entire+-- token stream and then begin parsing the 'Data.List.reverse'd stream, this will force the entire+-- lexical analysis phase to complete and store the entire token stream into memory before the+-- syntactic analyse can begin. Any parser that scans forward over tokens will consume a lot of+-- memory. But through use of 'Parser' it is difficult to make this mistake.++-- | This is the state used by every 'Lexer'. It keeps track of the line number and column+-- number, the current input string, and the list of emitted 'Token's.+data LexerState tok+ = LexerState+ { lexTabWidth :: TabWidth+ -- ^ When computing the column number of tokens, the number of spaces a @'\TAB'@ character+ -- counts for should be configured. The default set in 'newLexerState' is 4.+ , lexCurrentLine :: LineNum+ , lexCurrentColumn :: ColumnNum+ , lexTokenCounter :: Word+ -- ^ some algorithms would like to know if you lexed any tokens at all, and will fail if you+ -- did not. There needs to be some way of knowing how many tokens your 'Lexer' created.+ , tokenStream :: [TokenAt tok]+ , lexBuffer :: String+ -- ^ stores the characters consumed by 'Lexer's. This buffer is never cleared until+ -- 'makeToken' is evaluated. Retrieve this string using:+ -- > 'Control.Monad.State.gets' 'lexBuffer'+ , lexInput :: String+ -- ^ contains the remainder of the input string to be analyzed. Retrieve this string using:+ -- > 'Control.Monad.State.gets' 'lexInput'+ }+instance HasLineNumber (LexerState tok) where { lineNumber = lexCurrentLine }+instance HasColumnNumber (LexerState tok) where { columnNumber = lexCurrentColumn }++-- | Create a new lexer state using the given input 'Prelude.String'. This is only realy useful if+-- you must evaluate 'runLexerState'.+newLexerState :: String -> LexerState tok+newLexerState input =+ LexerState+ { lexTabWidth = 4+ , lexTokenCounter = 0+ , lexCurrentLine = 1+ , lexCurrentColumn = 1+ , tokenStream = []+ , lexBuffer = ""+ , lexInput = input+ }++-- | 'parse' will evaluate the 'Lexer' over the input string first. If the 'Lexer' fails, it+-- will evaluate to a 'Dao.Prelude.PFail' value containing a 'Error' value of type:+-- > 'Error' ('LexerState')+-- However the 'Parser's evaluate to 'Error's containing type:+-- > 'Error' ('TokStreamState' st)+-- This function provides an easy way to convert between the two 'Error' types, however since+-- the state value @st@ is polymorphic, you will need to insert your parser state into the error+-- value after evaluating this function. For example:+-- > case tokenizerResult of+-- > 'Dao.Predicate.PFail' lexErr -> 'Dao.Predicate.PFail' (('lexErrToParseErr' lexErr){'parseStateAtErr' = Nothing})+-- > ....+lexErrToParseErr :: TokenType tok => ParseError (LexerState tok) tok -> ParseError (TokStreamState st tok) tok+lexErrToParseErr (lexErr@ParseError{parseStateAtErr=st}) =+ lexErr+ { parseStateAtErr = mzero+ , parseErrLoc = maybe LocationUnknown (\st -> atPoint (lexCurrentLine st) (lexCurrentColumn st)) st+ , parseErrMsg = return (ustr "Lexical analysis failed on ") <> parseErrMsg lexErr <>+ (do st <- st+ return $ ustr $ concat $ concat $+ [ do guard (not (null (lexBuffer st)))+ ["\nBuffered token string: ", show (lexBuffer st)]+ , ["\nString failed on: ", show (take 20 (lexInput st))]+ ]+ )+ }++lexCurrentLocation :: LexerState tok -> Location+lexCurrentLocation st = atPoint (lineNumber st) (columnNumber st)++-- | The 'Lexer' is very similar in many ways to regular expressions, however 'Lexer's always+-- begin evaluating at the beginning of the input string. The lexical analysis phase of parsing+-- must generate 'Token's from the input string. 'Lexer's provide you the means to do with+-- primitive functions like 'lexString', 'lexChar', and 'lexUntil', and combinators like 'defaultTo'+-- and 'lexUntilTermChar'. These primitive functions collect characters into a buffer, and you can+-- then empty the buffer and use the buffered characters to create a 'Token' using the+-- 'makeToken' function.+-- +-- The 'Control.Monad.fail' function is overloaded such that it halts 'lexecialAnalysis' with a+-- useful error message about the location of the failure. 'Control.Monad.Error.throwError' can+-- also be used, and 'Control.Monad.Error.catchError' will catch errors thrown by+-- 'Control.Monad.Error.throwError' and 'Control.Monad.fail'. 'Control.Monad.mzero' causes+-- backtracking. Be careful when recovering from backtracking using 'Control.Monad.mplus' because+-- the 'lexBuffer' is not cleared. It is usually better to backtrack using 'lexBacktrack' (or don't+-- backtrack at all, because it is inefficient). However you don't need to worry too much; if a+-- 'Lexer' backtracks while being evaluated in lexical analysis the 'lexInput' will not be+-- affected at all and the 'lexBuffer' is ingored entirely.+newtype Lexer tok a = Lexer{+ lexerToPredicateT :: PredicateT (ParseError (LexerState tok) tok) (State (LexerState tok)) a+ }+ deriving (Functor, Applicative, Alternative, MonadPlus)++instance TokenType tok =>+ Monad (Lexer tok) where+ (Lexer fn) >>= mfn = Lexer (fn >>= lexerToPredicateT . mfn)+ return = Lexer . return+ fail msg = do+ st <- get+ throwError $+ (parserErr (lexCurrentLocation st)){parseErrMsg = Just (ustr msg), parseStateAtErr=Just st}++instance TokenType tok => MonadState (LexerState tok) (Lexer tok) where+ get = Lexer (lift get)+ put = Lexer . lift . put++instance TokenType tok => MonadError (ParseError (LexerState tok) tok) (Lexer tok) where+ throwError = Lexer . throwError+ catchError (Lexer try) catcher = Lexer (catchError try (lexerToPredicateT . catcher))++instance TokenType tok => MonadPlusError (ParseError (LexerState tok) tok) (Lexer tok) where+ catchPredicate (Lexer try) = Lexer (catchPredicate try)+ predicate = Lexer . predicate++instance (TokenType tok, Monoid a) => Monoid (Lexer tok a) where+ mempty = return mempty+ mappend a b = liftM2 mappend a b++-- | Append the first string parameter to the 'lexBuffer', and set the 'lexInput' to the value of+-- the second string parameter. Most lexers simply takes the input, breaks it, then places the two+-- halves back into the 'LexerState', which is what this function does. *Be careful* you don't pass+-- the wrong string as the second parameter. Or better yet, don't use this function.+lexSetState :: TokenType tok => String -> String -> Lexer tok ()+lexSetState got remainder = modify $ \st ->+ st{lexBuffer = lexBuffer st ++ got, lexInput = remainder}++-- | Unlike simply evaluating 'Control.Monad.mzero', 'lexBacktrack' will push the contents of the+-- 'lexBuffer' back onto the 'lexInput'. This is inefficient, so if you rely on this too often you+-- should probably re-think the design of your lexer.+lexBacktrack :: TokenType tok => Lexer tok ig+lexBacktrack = modify (\st -> st{lexBuffer = "", lexInput = lexBuffer st ++ lexInput st}) >> mzero++-- | Single character look-ahead, never consumes any tokens, never backtracks unless we are at the+-- end of input.+lexLook1 :: TokenType tok => Lexer tok Char+lexLook1 = gets lexInput >>= \input -> case input of { "" -> mzero ; c:_ -> return c }++-- | Arbitrary look-ahead, creates a and returns copy of the portion of the input string that+-- matches the predicate. This function never backtracks, and it might be quite inefficient because+-- it must force strict evaluation of all characters that match the predicate.+lexCopyWhile :: TokenType tok => (Char -> Bool) -> Lexer tok String+lexCopyWhile predicate = fmap (takeWhile predicate) (gets lexInput)++-- | A fundamental 'Lexer', uses 'Data.List.break' to break-off characters from the input string+-- until the given predicate evaluates to 'Prelude.True'. Backtracks if no characters are lexed.+lexWhile :: TokenType tok => (Char -> Bool) -> Lexer tok ()+lexWhile predicate = do+ (got, remainder) <- fmap (span predicate) (gets lexInput)+ if null got then mzero else lexSetState got remainder++-- | Like 'lexUnit' but inverts the predicate, lexing until the predicate does not match. This+-- function is defined as:+-- > \predicate -> 'lexUntil' ('Prelude.not' . predicate)+lexUntil :: TokenType tok => (Char -> Bool) -> Lexer tok ()+lexUntil predicate = lexWhile (not . predicate)++-- lexer: update line/column with string+lexUpdLineColWithStr :: TokenType tok => String -> Lexer tok ()+lexUpdLineColWithStr input = do+ st <- get+ let tablen = lexTabWidth st+ countNLs lns cols input = case break (=='\n') input of+ ("" , "" ) -> (lns, cols)+ (_ , '\n':after) -> countNLs (lns+1) 1 after+ (before, after ) -> (lns, cols + foldl (+) 0 (map charPrintWidth (before++after)))+ charPrintWidth c = case c of+ c | c=='\t' -> tablen+ c | isPrint c -> 1+ _ -> 0+ (newLine, newCol) = countNLs (lineNumber st) (columnNumber st) input+ put (st{lexCurrentLine=newLine, lexCurrentColumn=newCol})++-- | Create a 'Token' using the contents of the 'lexBuffer', then clear the 'lexBuffer'. This+-- function backtracks if the 'lexBuffer' is empty. If you pass "Prelude.False' as the first+-- parameter the tokens in the 'lexBuffer' are not stored with the token, the token will only+-- contain the type.+makeGetToken :: TokenType tok => Bool -> tok -> Lexer tok (Token tok)+makeGetToken storeChars typ = do+ st <- get+ let str = lexBuffer st+ token <- case str of+ [] -> mzero+ [c] | storeChars -> return $ CharToken{tokType=typ, tokChar=c}+ _ | storeChars -> return $ Token{tokType=typ, tokUStr=ustr str}+ _ -> return $ EmptyToken{tokType=typ}+ put $+ st{ lexBuffer = ""+ , tokenStream = tokenStream st +++ [ TokenAt+ { tokenAtLineNumber = lineNumber st+ , tokenAtColumnNumber = columnNumber st+ , getTokenValue = token+ } ]+ , lexTokenCounter = lexTokenCounter st + 1+ }+ lexUpdLineColWithStr str+ return token++-- | Create a token in the stream without returning it (you usually don't need the token anyway). If+-- you do need the token, use 'makeGetToken'.+makeToken :: TokenType tok => tok -> Lexer tok ()+makeToken = void . makeGetToken True++-- | Create a token in the stream without returning it (you usually don't need the token anyway). If+-- you do need the token, use 'makeGetToken'. The token created will not store any characters, only+-- the type of the token. This can save a lot of memory, but it requires you have very descriptive+-- token types.+makeEmptyToken :: TokenType tok => tok -> Lexer tok ()+makeEmptyToken = void . makeGetToken False++-- | Clear the 'lexBuffer' without creating a token.+clearBuffer :: TokenType tok => Lexer tok ()+clearBuffer = get >>= \st -> lexUpdLineColWithStr (lexBuffer st) >> put (st{lexBuffer=""})++-- | A fundamental lexer using 'Data.List.stripPrefix' to check whether the given string is at the+-- very beginning of the input.+lexString :: TokenType tok => String -> Lexer tok ()+lexString str = gets lexInput >>= maybe mzero return . stripPrefix str >>= lexSetState str++-- | A fundamental lexer succeeding if the next 'Prelude.Char' in the 'lexInput' matches the+-- given predicate. See also: 'charSet' and 'unionCharP'.+lexCharP :: TokenType tok => (Char -> Bool) -> Lexer tok ()+lexCharP predicate = gets lexInput >>= \input -> case input of+ c:input | predicate c -> lexSetState [c] input+ _ -> mzero++-- | Succeeds if the next 'Prelude.Char' on the 'lexInput' matches the given 'Prelude.Char'+lexChar :: TokenType tok => Char -> Lexer tok ()+lexChar c = lexCharP (==c)++-- | Backtracks if there are still characters in the input.+lexEOF :: TokenType tok => Lexer tok ()+lexEOF = fmap (=="") (gets lexInput) >>= guard++-- | Takes a 'tokenStream' resulting from the evaulation of lexical analysis and breaks it into+-- 'Line's. This makes things a bit more efficient because it is not necessary to store a line+-- number with every single token. It is necessary for initializing a 'Parser'.+tokenStreamToLines :: [TokenAt tok] -> [Line tok]+tokenStreamToLines toks = loop toks where+ makeLine num toks =+ Line+ { lineLineNumber = num+ , lineTokens = map (\t -> (tokenAtColumnNumber t, getToken t)) toks+ }+ loop toks = case toks of+ [] -> []+ t:toks ->+ let num = tokenAtLineNumber t+ (line, toks') = span ((==num) . tokenAtLineNumber) (t:toks)+ in makeLine num line : loop toks'++-- | The 'Lexer's analogue of 'Control.Monad.State.runState', runs the lexer using an existing+-- 'LexerState'.+lexicalAnalysis+ :: TokenType tok+ => Lexer tok a -> LexerState tok -> (Predicate (ParseError (LexerState tok) tok) a, LexerState tok)+lexicalAnalysis lexer st = runState (runPredicateT (lexerToPredicateT lexer)) st++-- | Perform a simple evaluation of a 'Lexer' against the beginning of a string, returning the+-- tokens and the remaining string. Evaluates to @('Prelude.True', (tokens, remainder))@ if the+-- lexer succeeded, evaluates to @('Prelude.False', (tokens, remainder))@ if the lexer failed or+-- backtracked, where @tokens@ are the tokens produced and @remainder@ is the portion of the string+-- that was not tokenized.+runLexer :: TokenType tok => Lexer tok a -> String -> (Bool, ([TokenAt tok], String))+runLexer lexer inputStr =+ let (lexResult, st) = lexicalAnalysis lexer (newLexerState inputStr)+ in (case lexResult of { OK _ -> True; _ -> False; }, (tokenStream st, lexInput st))++-- | Convert a 'Regex' to a 'Lexer' and match a string against it using 'runLexer', so it only+-- matches at the beginning of a string, not at any arbitrary point in the middle of the string.+runRegex :: TokenType tok => Regex -> String -> (Bool, ([TokenAt tok], String))+runRegex lexer inputStr =+ let (a, (b, c)) = runLexer (regexToLexer lexer) inputStr in (a, (fmap (fmap wrapTT) b, c))++testLexicalAnalysis_withFilePath+ :: (Show tok, TokenType tok)+ => Lexer tok () -> FilePath -> TabWidth -> String -> IO ()+testLexicalAnalysis_withFilePath tokenizer filepath tablen input = putStrLn report where+ (result, st) = lexicalAnalysis tokenizer ((newLexerState input){lexTabWidth=tablen})+ lines = tokenStreamToLines (tokenStream st)+ more = take 21 (lexInput st)+ remain = "\nremaining: "++(if length more > 20 then show (take 20 more)++"..." else show more)+ loc = show (lineNumber st) ++ ":" ++ show (lexCurrentColumn st)+ report = (++remain) $ intercalate "\n" (map showLine lines) ++ '\n' : case result of+ OK _ -> "No failures during lexical analysis."+ Backtrack -> reportFilepath ++ ": lexical analysis evalauted to \"Backtrack\""+ PFail err -> show (fmapParseErrorState (const ()) err)+ showLine line = unlines $ ["----------", show line]+ reportFilepath = (if null filepath then "" else filepath)++":"++loc++-- | Run the 'lexicalAnalysis' with the 'Lexer' on the given 'Prelude.String' and print out+-- every token created.+testLexicalAnalysis+ :: (Show tok, TokenType tok)+ => Lexer tok () -> TabWidth -> String -> IO ()+testLexicalAnalysis a b c = testLexicalAnalysis_withFilePath a "" b c++-- | Run the 'lexicalAnalysis' with the 'Lexer' on the contents of the file at the the given+-- 'System.IO.FilePath' 'Prelude.String' and print out every token created.+testLexicalAnalysisOnFile+ ::(Show tok, TokenType tok) + => Lexer tok () -> TabWidth -> FilePath -> IO ()+testLexicalAnalysisOnFile a b c = readFile c >>= testLexicalAnalysis_withFilePath a c b++----------------------------------------------------------------------------------------------------+-- $Fundamental_parser_data_types+-- A parser is defined as a stateful monad for analyzing a stream of tokens. A token stream is+-- represented by a list of 'Line' structures, and the parser monad's jobs is to look at the+-- current line, and extract the current token in the current line in the state, and use the tokens+-- to construct data. 'TokStream' is the fundamental parser, but it might be very tedious to use. It+-- is better to construct parsers using 'TokStream' which is a higher-level, easier to use data type+-- that is converted into the lower-level 'TokStream' type.++-- | The 'TokStreamState' contains a stream of all tokens created by the 'lexicalAnalysis' phase.+-- This is the state associated with a 'TokStream' in the instantiation of+-- 'Control.Monad.State.MonadState', so 'Control.Monad.State.get' returns a value of this data type.+data TokStreamState st tok+ = TokStreamState+ { userState :: st+ , getLines :: [Line tok]+ , tokenQueue :: [TokenAt tok]+ -- ^ single look-ahead is common, but the next token exists within the 'Prelude.snd' value+ -- within a pair within a list within the 'lineTokens' field of a 'Line' data structure.+ -- Rather than traverse that same path every time 'nextToken' or 'withToken' is called, the+ -- next token is cached here.+ , finalLocation :: Location -- ^ the line and column number marking the end of the file.+ }+instance Functor (TokStreamState st) where+ fmap f s =+ TokStreamState+ { userState = userState s+ , getLines = fmap (fmap f) (getLines s)+ , tokenQueue = fmap (fmap f) (tokenQueue s)+ , finalLocation = finalLocation s+ }++newTokStream :: TokenType tok => st -> [Line tok] -> TokStreamState st tok+newTokStream userState lines =+ TokStreamState+ { userState = userState+ , getLines = lines+ , tokenQueue = []+ , finalLocation = LocationUnknown+ }++newTokStreamFromLexer :: TokenType tok => st -> LexerState tok -> TokStreamState st tok+newTokStreamFromLexer userState lexerState =+ (newTokStream userState $ tokenStreamToLines $ tokenStream lexerState)+ { finalLocation =+ atPoint (lexCurrentColumn lexerState) (lexCurrentColumn lexerState)+ }++-- The 'TokStreamState' data structure has a field of a polymorphic type reserved for containing+-- arbitrary stateful information. 'TokStream' instantiates 'Control.Monad.State.Class.MonadState'+-- usch that 'Control.Monad.State.Class.MonadState.get' and+-- 'Control.Monad.State.Class.MonadState.put' return the 'TokStreamState' type, however if you wish+-- to modify the arbitrary state value using a function similar to how the+-- 'Control.Monad.State.Class.MonadState.modify' would do, you can use this function.+--modifyUserState :: TokenType tok => (st -> st) -> TokStream st tok ()+--modifyUserState fn = modify (\st -> st{userState = fn (userState st)})++-- | The task of the 'TokStream' monad is to look at every token in order and construct syntax trees+-- in the 'syntacticAnalysis' phase.+--+-- This function instantiates all the useful monad transformers, including 'Data.Functor.Functor',+-- 'Control.Monad.Monad', 'Control.MonadPlus', 'Control.Monad.State.MonadState',+-- 'Control.Monad.Error.MonadError' and 'Dao.Predicate.MonadPlusError'. Backtracking can be done+-- with 'Control.Monad.mzero' and "caught" with 'Control.Monad.mplus'. 'Control.Monad.fail' and+-- 'Control.Monad.Error.throwError' evaluate to a control value containing a 'Error' value+-- which can be caught by 'Control.Monad.Error.catchError', and which automatically contain+-- information about the location of the failure and the current token in the stream that caused the+-- failure.+newtype TokStream st tok a+ = TokStream{+ parserToPredicateT ::+ PredicateT (ParseError (TokStreamState st tok) tok) (State (TokStreamState st tok)) a+ }+instance Functor (TokStream st tok) where { fmap f (TokStream a) = TokStream (fmap f a) }+instance TokenType tok =>+ Monad (TokStream st tok) where+ (TokStream ma) >>= mfa = TokStream (ma >>= parserToPredicateT . mfa)+ return a = TokStream (return a)+ fail msg = do+ tok <- optional (nextToken False)+ st <- get+ throwError $+ ParseError+ { parseErrLoc = maybe LocationUnknown asLocation tok+ , parseErrMsg = Just (ustr msg)+ , parseErrTok = fmap getToken tok+ , parseStateAtErr = Just st+ }+instance TokenType tok =>+ MonadPlus (TokStream st tok) where+ mzero = TokStream mzero+ mplus (TokStream a) (TokStream b) = TokStream (mplus a b)+instance TokenType tok =>+ Applicative (TokStream st tok) where { pure = return ; (<*>) = ap; }+instance TokenType tok =>+ Alternative (TokStream st tok) where { empty = mzero; (<|>) = mplus; }+instance TokenType tok =>+ MonadState (TokStreamState st tok) (TokStream st tok) where+ get = TokStream (PredicateT (fmap OK get))+ put = TokStream . PredicateT . fmap OK . put+instance TokenType tok =>+ MonadError (ParseError (TokStreamState st tok) tok) (TokStream st tok) where+ throwError err = do+ st <- get+ predicate (PFail (err{parseStateAtErr=Just st}))+ catchError (TokStream ptrans) catcher = TokStream $ do+ pval <- catchPredicate ptrans+ case pval of+ OK a -> return a+ Backtrack -> mzero+ PFail err -> parserToPredicateT (catcher err)+instance TokenType tok =>+ MonadPlusError (ParseError (TokStreamState st tok) tok) (TokStream st tok) where+ catchPredicate (TokStream ptrans) = TokStream (catchPredicate ptrans)+ predicate = TokStream . predicate+instance (TokenType tok, Monoid a) =>+ Monoid (TokStream st tok a) where { mempty = return mempty; mappend a b = liftM2 mappend a b; }++-- Return the next token in the state along with it's line and column position. If the boolean+-- parameter is true, the current token will also be removed from the state.+nextToken :: TokenType tok => Bool -> TokStream st tok (TokenAt tok)+nextToken doRemove = do+ st <- get+ case tokenQueue st of+ [] -> case getLines st of+ [] -> mzero+ line:lines -> do+ modify (\st -> st{tokenQueue=lineToTokensAt line, getLines=lines})+ nextToken doRemove+ tok:tokx | doRemove -> put (st{tokenQueue=tokx}) >> return tok+ tok:_ -> return tok++----------------------------------------------------------------------------------------------------++-- | The public API for the 'TokStream' monad wraps 'TokStream' in a newtype called 'Parser.+data Parser st tok a+ = ParserNull+ | Parser{ parserToTokStream :: TokStream st tok a }+ -- 'Parser' has it's own 'ParserNull' constructor because of lookup tables. When a lookup table is+ -- evaluated, it selects the next parser from the table based on the current token in the stream.+ -- But before it evaluates the next parser, it must shift the current token from the stream. If+ -- the next parser is 'mzero', then the token will need to be unshifed immediately. To prevent+ -- this unnecessary shift-unshift, the looked-up parser is checked: if the next parser is+ -- 'ParserNull' (which could happen often because it is the default value used when constructing+ -- the table) then the current token is not shifted at all.+instance Show (Parser st tok a) where { show p = case p of { ParserNull -> "ParserNull"; Parser _ -> "Parser ..."; }}+instance TokenType tok => Functor (Parser st tok) where+ fmap f p = case p of { ParserNull -> ParserNull; Parser p -> Parser (fmap f p); }+instance TokenType tok =>+ Monad (Parser st tok) where+ return = Parser . return+ parser >>= bind = case parser of+ ParserNull -> ParserNull+ Parser p -> Parser $ p >>= \a -> case bind a of { ParserNull -> mzero; Parser p -> p; }+ --parserA >> parserB = case parserA of+ -- ParserNull -> ParserNull+ -- Parser parserA -> case parserB of+ -- ParserNull -> Parser (parserA >> mzero)+ -- Parser parserB -> Parser (parserA >> parserB)+ fail = Parser . fail+instance TokenType tok =>+ MonadPlus (Parser st tok) where+ mzero = ParserNull+ mplus a b = case a of+ ParserNull -> b+ Parser a -> Parser $ case b of+ ParserNull -> a+ Parser b -> mplus a b+instance TokenType tok => Applicative (Parser st tok) where { pure = return; (<*>) = ap; }+instance TokenType tok => Alternative (Parser st tok) where { empty = mzero; (<|>) = mplus; }+instance TokenType tok => MonadState st (Parser st tok) where+ get = Parser (gets userState)+ put ust = Parser (modify (\st -> st{userState=ust}))+instance TokenType tok =>+ MonadError (ParseError (TokStreamState st tok) tok) (Parser st tok) where+ throwError = Parser . throwError+ catchError trial catcher = Parser $+ catchError (parserToTokStream trial) (\err -> parserToTokStream (catcher err))+instance TokenType tok =>+ MonadPlusError (ParseError (TokStreamState st tok) tok) (Parser st tok) where+ catchPredicate ptrans = Parser (catchPredicate (parserToTokStream ptrans))+ predicate = Parser . predicate+instance TokenType tok => Monoid (Parser st tok a) where {mempty=mzero; mappend=mplus; }++----------------------------------------------------------------------------------------------------++-- | This class exists to provide the 'token'. Ordinarily you would want to use the 'tokenType'+-- function to construct a parser that succeeds when the 'TokenType' value you provide to it matches+-- the next 'Token' in the 'TokStream'. However you need to have this 'TokenType' value first to+-- construct the parser, which means retrieving it by name from the 'TokenDB'. But things are more+-- convenient if you have a 'MetaType' that you used to construct your 'Lexer' using the 'fullToken'+-- or 'emptyToken' functions, which is possible because these functions construct 'Lexer's from+-- 'UStrType's, and your 'MetaToken' type also instantiates 'UStrType'. If you have created a data+-- type to be used as a 'MetaType' and used it to construct tokens, the 'Dao.String.UStr' value of+-- the 'MetaToken' type is associated with the 'TT' value of your 'TokenType' in the 'TokenDB'. This+-- function conveniently sees your 'MetaToken' type, retrieves it from the 'TokenDB', and uses the+-- 'TT' value retrieved to construct a 'TokStream' using the 'tokenType' function.+-- > newtype MyToken = MyToken{ unwrapMyToken :: TT }+-- > instance 'TokenType' MyToken where { 'unwrapTT' = unwrapMyToken; 'wrapTT' = MyToken; }+-- > data MyMetaTT WHITESPACE | NUMBER | LABEL+-- > +-- > instance 'MetaToken' MyMetaTT MyToken where { 'tokenDBFromMetaValue' _ = getTokenDB }+-- > instance 'HasTokenDB' MyToken where+-- > getTokenDB = 'makeTokenDB' $ do+-- > mySpace <- emptyToken WHITESPACE $ 'rxRepeat1'('Prelude.map' 'ch' "\n\t ")+-- > let number = 'from' '0' 'to' '9'+-- > myNumber <- fullToken NUMBER $ 'rxRepeat1' number+-- > let alpha_ = [ch '_', 'from' '@A@' 'to' '@Z@', 'from' '@a@' 'to' '@z@']+-- > myLabel <- fullToken LABLE $ 'rxRepeat1' alpha_ . rxRepeat(number : alpha_)+-- > -- now the 'TT' values have been associated with the 'Dao.String.UStr' values+-- > -- of your 'MetaToken' type.+-- > 'activate' [mySpace, myNumber, myLabel]+-- > +-- > data MyAST = NullAST | Assign{ label :: 'Prelude.String', intValue :: 'Prelude.Int' }+-- > mkAssign :: String -> String -> MyAST+-- > mkAssign lbl val = Assign{ label = lbl, intValue = 'Prelude.read' val }+-- > +-- > -- make a 'TokStream' that parses a label, followed by a space, followed by a number+-- > myTokStream :: TokStream () MyToken MyAST+-- > myTokStream = 'Control.Applicative.pure' mkAssign 'Control.Applicative.<*>' ('token' LABEL) 'Control.Applicative.<*>' ('token' NUMBER)+class (Enum meta, UStrType meta) =>+ MetaToken meta tok | meta -> tok where { tokenDBFromMetaValue :: meta -> TokenDB tok }++-- | This class exists to to provide the 'tokenBy' function.+class TokenType tok => HasTokenDB tok where { tokenDB :: TokenDB tok }+getTokenDB :: HasTokenDB tok => Parser st tok (TokenDB tok)+getTokenDB = return tokenDB++tokenDBFromToken :: HasTokenDB tok => tok -> TokenDB tok+tokenDBFromToken _ = tokenDB++tokenDBFromParser :: HasTokenDB tok => Parser st tok a -> TokenDB tok+tokenDBFromParser _ = tokenDB++-- | If the given 'Parser' backtracks then evaluate to @return ()@, otherwise ignore the result of+-- the 'Parser' and evaluate to @return ()@.+ignore :: TokenType tok => Parser st tok ig -> Parser st tok ()+ignore parser = mplus (parser >> return ()) (return ()) ++-- | Return the default value provided in the case that the given 'Parser' fails, otherwise+-- return the value returned by the 'Parser'.+defaultTo :: TokenType tok => a -> Parser st tok a -> Parser st tok a+defaultTo defaultValue parser = mplus parser (return defaultValue)++-- | Given two parameters: 1. an error message and 2. a 'Parser', will succeed normally if+-- evaluating the given 'Parser' succeeds. But if the given 'Parser' backtracks, this this function+-- will evaluate to a 'Parser' failure with the given error message. If the given 'Parser' fails,+-- it's error message is used instead of the error message given to this function. The string+-- "expecting " is automatically prepended to the given error message so it is a good idea for your+-- error message to simple state what you were expecting, like "a string" or "an integer". I+-- typically write 'expect' statements like so:+-- > fracWithExp = do+-- > fractionalPart <- parseFractional+-- > 'tokenStrType' 'Alphabetic' (\tok -> tok=="E" || tok=="e")+-- > 'expect' "an integer expression after the 'e'" $ do+-- > exponentPart <- parseSignedInteger+-- > return (makeFracWithExp fractionalPart exponentPart :: 'Prelude.Double')+expect+ :: (TokenType tok, UStrType str, MonadError (ParseError (TokStreamState st tok) tok) (Parser st tok))+ => str -> Parser st tok a -> Parser st tok a+expect errMsg parser = do+ loc <- mplus (look1 asLocation) (Parser (gets finalLocation))+ let expectMsg = "expecting " ++ uchars errMsg+ mplus parser (throwError ((parserErr loc){parseErrMsg = Just (ustr expectMsg)}))++-- | Given a constructor that takes an arbitray value and a 'Dao.NewTokStream.Location' value, and a+-- 'Dao.NewTokStream.Parser' that evaluates to the same type of arbitrary value, this function+-- automatically notes the location of the current token, then evaluates the parser, then notes the+-- location of the next token to create a 'Dao.NewTokStream.Location' value and apply it to the+-- constructor.+withLoc :: TokenType tok => Parser st tok (Location -> a) -> Parser st tok a+withLoc parser = do+ before <- look1 asLocation+ cons <- parser+ after <- mplus (look1 asLocation) (Parser (gets finalLocation))+ return (cons (before<>after))++shift :: TokenType tok => (TokenAt tok -> a) -> Parser st tok a+shift as = Parser (fmap as (nextToken True))++look1 :: TokenType tok => (TokenAt tok -> a) -> Parser st tok a+look1 as = Parser (fmap as (nextToken False))++unshift :: TokenType tok => TokenAt tok -> Parser st tok ()+unshift tok = Parser $ modify (\st -> st{tokenQueue = tok : tokenQueue st})++metaTypeToTokenType :: (TokenType tok, MetaToken meta tok) => meta -> tok+metaTypeToTokenType meta =+ case M.lookup (toUStr meta) (tableUStrToTT (tokenDBFromMetaValue meta)) of+ Nothing -> error $ "internal: parser defined to use meta token "++show (toUStr meta)+++ " without having activated any tokenizer that constructs a token of that meta type"+ Just tt -> wrapTT tt++-- | This function takes two parameters, the first is a polymorphic function we can call+-- @getter@ that takes some of the contents of the current token in the stream. The first value+-- is a 'MetaToken' value we can call @meta@. This function will check the whether current token+-- in the stream has an identifier value that matches the given @meta@ value. If so, the current+-- token is shifted off of the stream and passed to the @getter@ function to extract the+-- necessary information from the token.+-- +-- Valid @getter@ functions are 'asTokType', 'asString', 'asUStr', 'as0', 'asToken',+-- 'asTokenAt', 'asTriple', 'asLineColumn', 'asLocation', or any composition of functions with+-- any of the above as the right-most function.+token :: (TokenType tok, MetaToken meta tok) => meta -> (TokenAt tok -> a) -> Parser st tok a+token meta as = look1 asTokType >>= \tok ->+ if tok == metaTypeToTokenType meta then shift as else mzero++-- | Useful for keywords or operators, this function is used to check if the next 'Token' value+-- in the 'Parser' is of a 'TokenType' labeled by the given constant string. This function+-- has similar behavior to @('tokenString' 'shift')@, /HOWEVER/ unlike 'tokenString', /this+-- function is much more efficient/ because the 'Token' identifier is looked up in the 'TokenDB'+-- only once and then used to add this parser to a parse table instead of merely comparing the+-- string value of the token.+-- +-- Valid @getter@ functions are 'asTokType', 'asString', 'asUStr', 'as0', 'asToken',+-- 'asTokenAt', 'asTriple', 'asLineColumn', 'asLocation', or any composition of functions with+-- any of the above as the right-most function.+tokenBy :: (UStrType name, HasTokenDB tok) => name -> (TokenAt tok -> a) -> Parser st tok a+tokenBy name as = do+ db <- getTokenDB+ let uname = toUStr name + tok <- look1 id+ case M.lookup uname (tableUStrToTT db) of+ Nothing -> if asUStr tok == uname then shift as else mzero+ Just tt -> if unwrapTT (asTokType tok) == tt then shift as else mzero++-- | A 'marker' immediately stores the cursor onto the runtime call stack. It then evaluates the+-- given 'Parser'. If the given 'Parser' fails, the position of the failure (stored in a+-- 'Dao.Token.Location') is updated such that the starting point of the failure points to the cursor+-- stored on the stack by this 'marker'. When used correctly, this function makes error reporting a+-- bit more helpful.+marker :: TokenType tok => Parser st tok a -> Parser st tok a+marker parser = do+ before <- mplus (look1 asLocation) (Parser (gets finalLocation))+ pval <- catchPredicate parser+ case pval of+ PFail err -> throwError $ err{parseErrLoc = before <> parseErrLoc err}+ pval -> predicate pval++-- | The 'Parser's analogue of 'Control.Monad.State.runState', runs the parser using an existing+-- 'TokStreamState'.+runParserState+ :: TokenType tok+ => Parser st tok a+ -> TokStreamState st tok+ -> (Predicate (ParseError (TokStreamState st tok) tok) a, TokStreamState st tok)+runParserState parser initTokSt = case parser of+ ParserNull -> (Backtrack, initTokSt)+ Parser (TokStream parser) -> runState (runPredicateT parser) initTokSt++-- | This is the second phase of parsing, takes a stream of tokens created by the 'lexicalAnalysis'+-- phase (the @['Line' tok]@ parameter) and constructs a syntax tree data structure using the+-- 'Parser' monad provided.+syntacticAnalysis+ :: TokenType tok+ => Parser st tok synTree+ -> TokStreamState st tok+ -> (Predicate (ParseError (TokStreamState st tok) tok) synTree, TokStreamState st tok)+syntacticAnalysis = runParserState++getNullToken :: TokenType tok => Parser st tok tok+getNullToken = return (wrapTT (MkTT 0))++isEOF :: TokenType tok => Parser st tok Bool+isEOF = Parser (get >>= \st -> return (null (getLines st) && null (tokenQueue st)))++----------------------------------------------------------------------------------------------------++-- | Parse table, used to create arrays of tokens that can be used to efficiently select the next+-- parse action to take based on the value of the current token in the token stream.+data PTable tok a+ = PTableNull+ | PTable { pTableArray :: Array TT (Maybe (TokenAt tok -> a)) }+instance TokenType tok => Functor (PTable tok) where+ fmap f ptab = case ptab of+ PTableNull -> PTableNull+ PTable arr -> PTable (fmap (fmap (fmap f)) arr)+instance (Monoid a, TokenType tok) => Monoid (PTable tok a) where+ mempty = PTableNull+ mappend a b = case a of+ PTableNull -> b+ PTable arr -> case b of+ PTableNull -> PTable arr+ PTable arr' -> PTable (mergeArrays mappend mempty arr arr' )++newtype TableItem tok a = TableItem{ tableItemToPair :: (tok, TokenAt tok -> a) }+instance TokenType tok => Functor (TableItem tok) where+ fmap f (TableItem ti) = TableItem (fmap (fmap f) ti)++tableItem :: (TokenType tok, MetaToken meta tok) =>+ meta -> (TokenAt tok -> a) -> TableItem tok a+tableItem meta parser = TableItem (metaTypeToTokenType meta, parser)++tableItemBy :: (TokenType tok, HasTokenDB tok, UStrType name) =>+ name -> (TokenAt tok -> a) -> TableItem tok a+tableItemBy name parser =+ case M.lookup (toUStr name) (tableUStrToTT (tokdb parser)) of+ Nothing -> error ("tableItemBy "++show (toUStr name)++" not activated in TokenDB")+ Just tok -> TableItem (wrapTT tok, parser)+ where+ tokdb :: HasTokenDB tok => (TokenAt tok -> a) -> TokenDB tok+ tokdb parser = tokenDBFromToken (tokType parser)+ tokType :: HasTokenDB tok => (TokenAt tok -> a) -> tok+ tokType _ = undefined -- don't want the value, just the type to be inferred++-- | Use this function if your 'TableItem' contains a parser which you also need to evalaute as a+-- stand-alon parser.+evalPTableItem :: TokenType tok => TableItem tok a -> Parser st tok a+evalPTableItem (TableItem (tokType, parser)) =+ look1 asTokType >>= \t -> if tokType==t then shift id >>= return . parser else mzero++-- | Usually, 'PTable's and 'TableItem's will contain monadic 'Parser' function elements, and evaluating a+-- 'PTable or 'TableItem' will result in that 'Parser' function element being returned to the monadic+-- function which evaluated it, in which case it must be 'Control.Monad.join'ed. This function+-- provides a handy shortcut to the expression @('Control.Monad.join' . 'evalPTableItem')@. See also+-- 'joinEvalPTable'.+joinEvalPTableItem :: TokenType tok => TableItem tok (Parser st tok a) -> Parser st tok a+joinEvalPTableItem = join . evalPTableItem++table :: Monoid a => TokenType tok => [TableItem tok a] -> PTable tok a+table tokParserAssocs = case asocs of+ [] -> PTableNull+ (tok1, _):_ -> PTable (accumArray mappend mempty bnds ttParserAssocs) where+ ttParserAssocs = fmap (\ (tok, parser) -> (unwrapTT tok, Just parser)) asocs+ tt = unwrapTT tok1+ bnds = foldl (\ (lo, hi) (tt, _) -> (min lo tt, max hi tt)) (tt, tt) ttParserAssocs+ where+ asocs = fmap tableItemToPair tokParserAssocs++-- | Like 'bindPTable' but operates on a single 'TableItem'.+bindPTableItem :: TokenType tok =>+ TableItem tok (Parser st tok a) -> (a -> Parser st tok b) -> TableItem tok (Parser st tok b)+bindPTableItem (TableItem item) bind = TableItem (fmap (fmap (>>=bind)) item)++-- | Like 'bindPTable' but operates on a list of 'TableItem's.+bindPTableItemList :: TokenType tok =>+ [TableItem tok (Parser st tok a)] -> (a -> Parser st tok b) -> [TableItem tok (Parser st tok b)]+bindPTableItemList list bind = fmap (flip bindPTableItem bind) list++-- | 'PTable's take functions from a 'TokenAt' type to an arbitrary value, but usually this value is+-- a monadic computation. This function takes a table containing monadic computations in the+-- 'Parser' monad and binds it to a monadic 'Parser' function you provide using the+-- 'Control.Monad.>>=' function, evaluating to a new 'PTable' containing the function you provided.+bindPTable :: TokenType tok =>+ PTable tok (Parser st tok a) -> (a -> Parser st tok b) -> PTable tok (Parser st tok b)+bindPTable table bind = case table of+ PTableNull -> PTableNull+ PTable arr -> PTable (fmap (fmap (fmap (>>=bind))) arr)++evalPTable :: TokenType tok => PTable tok a -> Parser st tok a+evalPTable ptable = case ptable of+ PTableNull -> mzero+ PTable arr -> do+ tok <- look1 id+ let tt = unwrapTT (asTokType tok)+ if inRange (bounds arr) tt+ then case arr!tt of+ Nothing -> mzero+ Just fn -> shift id >>= return . fn+ else mzero++-- | 'PTable's take functions from a 'TokenAt' type to an arbitrary value, but usually this value is+-- a monadic computation, therefore evaluating the table results in a computation that you must join+-- to the monad in which it was evaluated using 'Control.Monad.join'. This is so common it is+-- worthwhile to provide the 'joinEvalPTable' function as a handy shortcut for the expression:+-- > 'Control.Monad.join' ('evalPTable' myTable)+joinEvalPTable :: TokenType tok => PTable tok (Parser st tok a) -> Parser st tok a+joinEvalPTable = join . evalPTable++----------------------------------------------------------------------------------------------------+-- $Infix_operator_table+-- An infix operator table is an efficient method to parse equations expressed with infix operators.+-- These functions and data types allow you to define a grammar that can handle operator precedence+-- and right or left associativity (for example the rules for PEMDAS/BOMDAS).+--+-- The polymorphic types used throughout use @op@ as the operator token type, and @o@ as the+-- expression type. Your grammar must be some form of tree data structure similar to this example:+-- > data Equation = LEAF Int | BRANCH Equation Char Equation+-- You have at least two constructors, leaves store values and branches store operators and branch+-- to other trees. In the above example, the @Equation@ data type would be bound to the polymorphic+-- type @o@, and @Data.Char.Char@ would be bound to the polymorphic @op@ type.++---------+-- Personal note: I considered making the 'InfixConstr' and 'OpPrec' types of the form:+-- > type InfixConstr a op b = a -> op -> a -> b+-- > data OpPrec a op b = OpPrec Associativity [UStr] (InfixConster a op b)+-- But this required me to add another function to the 'OpTableParser' of the type @(a -> b)@ to+-- handle the case where the there was only a single value not followed by an operator. But I+-- figured this could be handled without complicating things: you can simply require the parsed type+-- to be @a@ and use 'fmap' to convert the result of the parse table to @b@ using @(a -> b)@.+-- +-- I also tried making InfixConstr like this:+-- > type InfixConstr a op b = a -> op -> b -> a+-- and also+-- > type InfixConstr a op b = a -> op -> b -> b+-- But doing this does not allow one to easily mix together right and left associative parsers into+-- a single table. Again, it is best to assume a consistent type for all tokens over which to fold,+-- and place the burden of matching inconsistent types on whoever is writting the infix parser.++-- | An infix constructor is a function of this form. It takes a 'Location' as it's final parameter,+-- which will denote the location of the @op@ token. The 'Location' can just be ignored if you want.+type InfixConstr st tok op o = o -> op -> o -> Parser st tok o++-- | Used to define right or left associativity for infix operators.+newtype Associativity = Associativity{ associatesLeft :: Bool } deriving (Eq, Ord)+instance Show Associativity where+ show (Associativity left) = if left then "leftAssoc" else "rightAssoc"++associatesRight :: Associativity -> Bool+associatesRight = not . associatesLeft++rightAssoc :: Associativity+rightAssoc = Associativity False++leftAssoc :: Associativity+leftAssoc = Associativity True++runAssociativity+ :: TokenType tok+ => Associativity+ -> InfixConstr st tok op o+ -> o+ -> [(o, op)]+ -> Parser st tok o+runAssociativity assoc constr o stack =+ if associatesLeft assoc+ then foldl (\ lhs (rhs, op) -> lhs >>= \lhs -> constr lhs op rhs) (return o) stack+ else (foldr (\ (rhs, op) next initObj -> next rhs >>= constr initObj op) return stack) o++-- | This data type is used to relate an 'Associativity' and an 'InfixConstr' to some operators,+-- the operators being given as strings.+data OpPrec st tok op o+ = OpPrec+ { opPrecAssoc :: Associativity+ , opPrecOps :: [UStr]+ , opPrecConstr :: InfixConstr st tok op o+ }++opLeft :: UStrType str => [str] -> InfixConstr st tok op o -> OpPrec st tok op o+opLeft = OpPrec leftAssoc . map toUStr++opRight :: UStrType str => [str] -> InfixConstr st tok op o -> OpPrec st tok op o+opRight = OpPrec rightAssoc . map toUStr++-- Stored in the 'OpTableParser', used to make choices on when to stack a token and when backtrack.+type OpTablePrec st tok op o = Maybe (Associativity, Int, InfixConstr st tok op o)++-- | Use this data type to setup an operator table parser which parses a sequence of @o@ type data+-- separated by @op@ type operator tokens, where the @op@ tokens have been assigned the properties+-- of fixity and right or left associativity.+data OpTableParser st tok op o+ = OpTableParser+ { opTableErrMsg :: UStr+ , opTableAutoShift :: Bool+ -- ^ instructs the 'evalOpTableParser' whether or not to automatically shift the operator+ -- token from the token stream. Usually you should set this tor 'Prelude.True'. However if+ -- your @op@ parser is more complicated than just converting a token, and actually needs to+ -- parse tokens in between terms, set this to 'Prelude.False' and make the 'opTableOpAs'+ -- function perform parsing and shifting of the operator. If you do set this to+ -- 'Prelude.False', make sure your 'opTableOpAs' function evaluates 'shift' at least once.+ -- The original reason for providing this option is to more easily build parsers that keep+ -- comments in the abstract syntax tree. Parsers that keep comments will usually parse all+ -- comments at the beginning of the file, then proceed with parsing, and every token parsed+ -- will have comments immediately after it parsed and paired with it. But it would be+ -- impossible to create such a parser without the ability to specify exactly when to shift the+ -- operator token and parse the comments.+ , opTableOpAs :: TokenAt tok -> Parser st tok op+ -- ^ 'TokenAt' values are automatically retrieved from the token stream by the+ -- 'evalOpTableParser' function, and this function is used to convert those 'TokenAt' values+ -- to values of data type @op@. During evaluation of 'evalOpTableParser' this function is+ -- evaluated before the operator token is shifted from the token stream.+ , opTableObjParser :: Parser st tok o+ -- ^ This function will parse the non-opreator values of the equation.+ , opTableConstr :: InfixConstr st tok op o+ -- ^ This function is used to construct an @o@ from a stack of @o@ and @op@ values, it is+ -- passed to 'runAssociativity'. In an arithmetic parser, for example, this function might be+ -- of the form:+ -- > constr :: Int -> String -> Int -> Int+ -- > constr a op b = if op=="+" then a+b else if op=="*" then a*b+ , opTableArray :: Maybe (Array TT (OpTablePrec st tok op o))+ }++-- | Evaluate an 'OpTableParser' to a 'Parser'.+evalOpTableParser :: (HasTokenDB tok, TokenType tok, Show o) => OpTableParser st tok op o -> Parser st tok o+evalOpTableParser optab = evalOpTableParserWithInit (opTableObjParser optab) optab++-- | Same as 'evalOpTableParser', but lets you provide a different parser for parsing the first+-- object term in the expression. This is useful if the function for parsing the initial term of the+-- expression is a prefix expression (but necessarily returns the same data type) of the function+-- that parses object terms in the expression. After the initial parser function is evaluated, the+-- 'opTableObjParser' function is used for every other term after it.+evalOpTableParserWithInit+ :: (HasTokenDB tok, TokenType tok)+ => Parser st tok o+ -> OpTableParser st tok op o+ -> Parser st tok o+evalOpTableParserWithInit initParser optab = do+ initObj <- initParser+ maybe (return initObj) (begin initObj) (opTableArray optab)+ where+ begin o table = mplus (lookGetPrec table >>= bindLeft table o) (return o)+ lookGetPrec table = do+ tok <- look1 id+ let tt = unwrapTT (asTokType tok)+ if inRange (bounds table) tt+ then case table!tt of+ Nothing -> mzero+ Just (a,b,c) -> return (tok, a, b, c)+ else mzero+ bindLeft table leftObj (tok, assoc, prec, constr) = flip mplus (return leftObj) $ do+ op <- opTableOpAs optab tok+ if opTableAutoShift optab then shift as0 else return ()+ (rightObj, opInfo) <- bindRight table tok assoc prec+ o <- constr leftObj op rightObj+ case opInfo of+ Nothing -> return o+ Just opInfo -> bindLeft table o opInfo+ bindRight table tok assoc prec = do+ expect (uchars (opTableErrMsg optab)++" after "++show tok++" token") $ do+ leftObj <- opTableObjParser optab+ flip mplus (return (leftObj, Nothing)) $ do+ opInfo@(nextTok, nextAssoc, nextPrec, constr) <- lookGetPrec table+ let mergeStack = return (leftObj, Just opInfo)+ let msg assoc = "associates to the "++(if associatesLeft assoc then "left" else "right")+ case () of+ () | assoc/=nextAssoc && prec==nextPrec -> fail $ unwords $+ [ "ambiguous experession, infix operators"+ , show tok, "and", show nextTok, "are of the same prescedence, but"+ , show tok, msg assoc, "whereas", show nextTok, msg nextAssoc+ ]+ () | associatesLeft assoc && prec>=nextPrec -> mergeStack+ () | associatesRight assoc && prec> nextPrec -> mergeStack+ () -> do+ op <- opTableOpAs optab nextTok+ if opTableAutoShift optab then shift as0 else return ()+ (rightObj, opInfo) <- bindRight table nextTok nextAssoc nextPrec+ constr leftObj op rightObj >>= \o -> return (o, opInfo)++-- | Sets up the 'OpTableParser' data structure.+--+-- The first parameter is a function used to parse the right and left hand sides of each infix+-- operator. This function may safely recurse to itself via the 'Parser' created by the evaluation+-- of 'evalOpTableParser' provided that there is at least one other parser that does not recurse+-- which is tried before it. For example:+-- > myOperatorTable :: OpPrecTable MyOperator MySymbol+-- > myOperatorFromToken :: 'TokenAt' MyTokType -> 'Parser' () MyTokType MyOperator+-- > nonRecursive :: 'Parser' () MyTokType MySymbol -- This parser never recurses to 'myExpression'.+-- >+-- > -- This parser does recurse to 'myExpression'+-- > recursive :: 'Parser' () MyTokType MySymbol+-- > recursive = 'evalOpTableParser' myExpression+-- >+-- > -- This expression parser is OK, it will not loop infinitely as long as nonRecursive takes at+-- > least one token from the token stream.+-- > myExpression :: 'OpTableParser' () MyTokType MyOperator MySymbol+-- > myExpression = 'newOpTableParser' (nonRecursive >> recursive) myOperatorFromToken myOperatorTable+-- >+-- > -- These expression parsers will loop infinitely doing nothing:+-- > badExpression1 = 'newOpTableParser' recursive myOperatorFromToken myOperatorTable+-- > badExpression2 = 'newOpTableParser' (nonRecursive<|>recursive) myOperatorFromToken myOperatorTable+-- > +-- +-- The second parameter is a function which produces an @op@ data type from a 'TokenAt' value.+-- Operators are taken from the token stream by the table evaluator, and this function will take the+-- 'TokenAt' value provided by the table evaluator and convert it to an operator data type. The @op@+-- typed data evaluated from this function will be used to construct the final @o@ value.+-- +-- The final parameters is an 'OpPrecTable' which you construct with the 'opPrecTable' or 'opTable'+-- functions, where you will assign prescedence and associativity properties to every operator+-- token.+newOpTableParser+ :: (HasTokenDB tok, TokenType tok, UStrType errMsg)+ => errMsg+ -> Bool+ -> (TokenAt tok -> Parser st tok op)+ -> Parser st tok o+ -> InfixConstr st tok op o+ -> [OpPrec st tok op o]+ -> OpTableParser st tok op o+newOpTableParser errMsg autoShift asOp objParser constr optab =+ OpTableParser+ { opTableErrMsg = toUStr errMsg+ , opTableAutoShift = autoShift+ , opTableObjParser = objParser+ , opTableOpAs = asOp+ , opTableConstr = constr+ , opTableArray = inferTypes objParser tokenDB optab+ }+ where+ ttBounds (tt, _) = foldl (\ (lo, hi) (tt, _) -> (min lo tt, max hi tt)) (tt, tt) . tail+ maxPrec = length optab+ precList+ :: (TokenType tok, HasTokenDB tok)+ => Parser st tok o+ -> TokenDB tok+ -> [OpPrec st tok op o]+ -> [(TT, OpTablePrec st tok op o)]+ precList _ db optab = do+ (n, p) <- zip [0..] optab+ op <- opPrecOps p+ case M.lookup op (tableUStrToTT db) of+ Nothing -> error ("internal, token "++show op++" has not been activated in the TokenDB")+ Just op -> return (op, Just (opPrecAssoc p, maxPrec-n, opPrecConstr p))+ inferTypes+ :: (TokenType tok, HasTokenDB tok)+ => Parser st tok o+ -> TokenDB tok+ -> [OpPrec st tok op o]+ -> Maybe (Array TT (OpTablePrec st tok op o))+ inferTypes parser db optab = case precList parser db optab of+ [] -> Nothing+ ax -> Just (accumArray (flip const) Nothing (ttBounds (head ax) ax) ax)++-- | Evaluate a parser with infix operators in between each term. Provide an initial parser to be+-- evaluated for the first term only, then the function for parsing terms of the expression, then+-- the infix operator parser. All infix operators will be of the same prescedence and the given+-- associativity. No tables are created by this function, so it is a good idea for your infix+-- operator parser and or the parser for evaluating terms to be functions of the 'table' function.+simpleInfixedWithInit+ :: (TokenType tok, UStrType errMsg)+ => errMsg+ -> Associativity+ -> InfixConstr st tok op o+ -> Parser st tok o+ -> Parser st tok o+ -> Parser st tok op+ -> Parser st tok o+simpleInfixedWithInit errMsg assoc constr initPar objPar opPar = initPar >>= loop [] where+ loop stack initObj = flip mplus (runAssociativity assoc constr initObj stack) $+ opPar >>= \op -> expect (toUStr errMsg) (objPar >>= \o -> loop (stack++[(o, op)]) initObj)++-- | Same as 'simpleInfixedWithInit' except the fourth parameter (the function for parsing the terms+-- of the expression) is used as both the initial term parser and the function for parsing terms of+-- the expression.+simpleInfixed+ :: (TokenType tok, UStrType errMsg)+ => errMsg+ -> Associativity+ -> InfixConstr st tok op o+ -> Parser st tok o+ -> Parser st tok op+ -> Parser st tok o+simpleInfixed errMsg assoc constr objPar opPar = objPar >>= loop [] where+ loop stack initObj = flip mplus (runAssociativity assoc constr initObj stack) $+ opPar >>= \op -> expect (toUStr errMsg) (objPar >>= \o -> loop (stack++[(o, op)]) initObj)++----------------------------------------------------------------------------------------------------+-- | A 'Language' is a data structure that allows you to easily define a+-- two-phase parser (a parser with a 'lexicalAnalysis' phase, and a 'syntacticAnalysis' phase). The+-- fields supplied to this data type define the grammar, and the 'parse' function can be used to+-- parse an input string using the context-free grammar defined in this data structure. *Note* that+-- the parser might have two phases, but because Haskell is a lazy language and 'parse' is a pure+-- function, both phases happen at the same time, so the resulting parser does not need to parse the+-- entire input in the first phase before beginning the second phase.+-- +-- This data type can be constructed from a 'Parser' in such a way that the resulting+-- 'Parser' is stored in this object permenantly. It might then be possible to reduce+-- initialization time by using an *INLINE* pragma, which will hopefully cause the compiler to+-- define as much of the 'Parser'@'@s sparse matrix as it possibly can at compile time. But this+-- is not a guarantee, of course, you never really know how much an optimization helps until you do+-- proper profiling.+data Language st tok synTree+ = Language+ { columnWidthOfTab :: TabWidth+ -- ^ specify how many columns a @'\t'@ character takes up. This number is important to get+ -- accurate line:column information in error messages.+ , mainLexer :: Lexer tok ()+ -- ^ *the order of these tokenizers is important,* these are the tokenizers passed to the+ -- 'lexicalAnalysis' phase to generate the stream of tokens for the 'syntacticAnalysis' phase.+ , mainParser :: Parser st tok synTree+ -- ^ this is the parser entry-point which is used to evaluate the 'syntacticAnalysis' phase.+ }++-- | Construct a 'Language' from a 'Parser'. This defines a complete parser that can be used+-- by the 'parse' function. In constructing this 'Language', the 'Parser' will be converted+-- to a 'Parser' which can be referenced directly from this object. This encourages the runtime+-- to cache the 'Parser' which can lead to better performance. Using an INLINE pragma on this+-- value could possibly improve performance even further.+newLanguage :: (HasTokenDB tok, TokenType tok) =>+ TabWidth -> Parser st tok synTree -> Language st tok synTree+newLanguage tabw parser =+ Language+ { columnWidthOfTab = tabw+ , mainLexer = tokenDBLexer tokenDB+ , mainParser = parser+ }++-- | This is /the function that parses/ an input string according to a given 'Language'.+parse :: TokenType tok =>+ Language st tok synTree -> st -> String -> Predicate (ParseError st tok) synTree+parse lang st input = case lexicalResult of+ OK () -> case parserResult of+ OK a -> OK a+ Backtrack -> Backtrack+ PFail err -> PFail $ err{parseStateAtErr = Just (userState parserState)}+ Backtrack -> Backtrack+ PFail err -> PFail $ (lexErrToParseErr err){parseStateAtErr = Nothing}+ where+ initState = (newLexerState input){lexTabWidth = columnWidthOfTab lang}+ (lexicalResult, lexicalState) = lexicalAnalysis (mainLexer lang) initState+ (parserResult , parserState ) =+ syntacticAnalysis (mainParser lang) (newTokStreamFromLexer st lexicalState)++----------------------------------------------------------------------------------------------------++mergeArrays :: Ix i => (e -> e -> e) -> e -> Array i e -> Array i e -> Array i e+mergeArrays plus zero a b =+ accumArray plus zero (boun (bounds a) (bounds b)) (assocs a ++ assocs b) where+ boun (loA, hiA) (loB, hiB) = (min loA loB, max hiA hiB)++----------------------------------------------------------------------------------------------------++class Parseable a where+ parser :: TokenType tok => Parser st tok a++class HasParseTable a where+ parseTable :: TokenType tok => PTable tok a+
+ src/Dao/Predicate.hs view
@@ -0,0 +1,253 @@+-- "src/Dao/Predicate.hs" provides 'PredicateT' which combines the+-- Maybe and Either types into a single monad.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.+++{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FunctionalDependencies #-}++-- | Provides a monad that essentially combines the monadic functionality of 'Prelude.Maybe' and+-- 'Prelude.Either' into a single monad 'Predicate. Both the 'Prelude.Maybe' and 'Prelude.Either'+-- data types are both monads but it is convenient to have a single data type combining the two.+-- 'PFail' is analogous to @'Prelude.Left'@, 'Backtrack' is analogous to+-- @'Prelude.Right' $ 'Prelude.Nothing'@, and 'OK' is analogous to+-- @'Prelude.Right' . 'Prelude.Just'@. All relevant monad transformers are instnatiated, including+-- 'Control.Applicative.Applicative', 'Control.Applicative.Alternative', 'Control.Monad.MonadPlus',+-- and 'Control.Monad.Error.MonadError'. +--+-- A new monad transformer 'PredicateT' is also introduced which lifts the 'Predicate' monad into+-- another monad and wraps it into the 'PredicateT' data type which instantiates the+-- 'Control.Monad.Trans.MonadTrans' class. Further, a new class 'MonadPlusError' is defined which+-- allows you to directly manipulate the 'Predicate' value of a 'PredicateT' transformer.+-- +-- Here is a simple example of how to use this module.+-- > newtype MyErr = MyErr String+-- > newtype MyIO a = WrapMyIO { unwrapMyIO :: 'PredicateT' MyErr IO a }+-- > deriving (Functor, Applicative, Alternative)+-- >+-- > instance 'Control.Monad.Monad' MyIO where -- this instance can also be derived+-- > 'Control.Monad.return' = WrapMyIO . 'Control.Monad.return'+-- > f 'Control.Monad.>>= bindTo = WrapMyIO $ unwrapMyIO f 'Control.Monad.>>=' unwrapMyIO . bindTo+-- > 'Control.Monad.fail' message = WrapMyIO $ 'PFail' (MyErr message)+-- > +-- > instance 'Control.Monad.MonadPlus' MyIO where -- this instance can also be derived+-- > 'Control.Monad.mzero' = WrapMyIO 'mzero'+-- > 'Control.Monad.mplus' (WrapMyIO try1) (WrapMyIO try2) = WrapMyIO ('mplus' try1 try2)+-- >+-- > instance 'Control.Monad.Error.Class.MonadError' MyErr MyIO where+-- > 'Control.Monad.Error.Class.MonadError.throwError' = WrapMyIO . 'Control.Monad.Error.Class.MonadError.throwError'+-- > 'Control.Monad.Error.Class.MonadError.catchError' (WrapMyIO try) catch = WrapMyIO ('catchError' try (unwrapMyIO . catch))+-- > +-- > instance 'Control.Monad.IO.Class.MonadIO' MyIO where+-- > 'Control.Monad.IO.Class.liftIO' = WrapMyIO . 'liftIO'+-- > +-- > doStep :: MyIO ()+-- > doStep = ...+-- > +-- > doJump :: MyIO ()+-- > doJump = ...+module Dao.Predicate where++import Control.Applicative+import Control.Monad+import Control.Monad.Error++import Data.Monoid++-- | 'Predicate' is a data type that combines 'Prelude.Maybe' and 'Prelude.Either' into a single+-- type. The 'Predicate' monad allows you to construct predicate functions that evaluate to 'OK'+-- (true) 'Backtrack' (false), and also provides a 'PFail' constructor for indicating a "does not+-- make sense" condition.+--+-- The truth condition 'OK' can 'Control.Monad.return' a value which makes it a 'Data.Functor', an+-- 'Control.Applicative.Applicative', and a 'Control.Monad.Monad'. The false condition 'Backtrack'+-- serves as the 'Control.Monad.mzero' for 'Control.Monad.MonadPlus' and 'Control.Applicative.empty'+-- value for 'Control.Applicative.Alternative'. The 'PFail' condition, like 'Prelude.Left', can be+-- used as an error condition in the 'Control.Monad.Error.ErrorClass' which can be caught by+-- 'Control.Monad.Error.catchError'.+--+-- This data type was originally intended for use in the Dao parser, but it is now used in several+-- contexts throughout the program.+data Predicate err ok+ = Backtrack -- ^ analogous to 'Prelude.Nothing'+ | PFail err -- ^ analogous to 'Prelude.Left'+ | OK ok -- ^ OK is "just right" i.e. it is analogous to 'Prelude.Just' and 'Prelude.Right'.+ deriving (Eq, Ord, Show)++instance Functor (Predicate err) where+ fmap fn (OK a) = OK (fn a)+ fmap _ (PFail u) = PFail u+ fmap _ Backtrack = Backtrack++instance Monad (Predicate err) where+ return = OK+ ma >>= mfn = case ma of+ OK ok -> mfn ok+ PFail err -> PFail err+ Backtrack -> Backtrack++instance MonadPlus (Predicate err) where+ mzero = Backtrack+ mplus Backtrack b = b+ mplus a _ = a++instance MonadError err (Predicate err) where+ throwError = PFail+ catchError try catch = case try of+ PFail err -> catch err+ try -> try++instance Applicative (Predicate err) where { pure = return; (<*>) = ap; }++instance Alternative (Predicate err) where { empty = mzero; (<|>) = mplus; }++instance Monoid ok => Monoid (Predicate err ok) where+ mempty = Backtrack+ mappend (OK a) (OK b) = OK(a<>b)+ mappend a _ = a++----------------------------------------------------------------------------------------------------++-- | A monad transformer lifting 'Predicate' into an outer monad. Use 'runPreicateT' to remove the+-- 'PredicateT' outer monad and retrieve the inner 'Predictate' value.+newtype PredicateT err m ok = PredicateT { runPredicateT :: m (Predicate err ok) }++instance Monad m => Monad (PredicateT err m) where+ return a = PredicateT (return (OK a))+ PredicateT ma >>= fma = PredicateT $ do+ a <- ma+ case a of+ Backtrack -> return Backtrack+ PFail u -> return (PFail u)+ OK o -> runPredicateT (fma o)+ PredicateT ma >> PredicateT mb = PredicateT $ do+ a <- ma+ case a of+ Backtrack -> return Backtrack+ PFail u -> return (PFail u)+ OK _ -> mb+ fail msg = PredicateT{ runPredicateT = return (PFail (error msg)) }++instance Functor m => Functor (PredicateT err m) where+ fmap f (PredicateT ma) = PredicateT (fmap (fmap f) ma)++instance Monad m => MonadPlus (PredicateT err m) where+ mzero = PredicateT (return Backtrack)+ mplus (PredicateT a) (PredicateT b) = PredicateT $ do+ result <- a+ case result of+ Backtrack -> b+ PFail u -> return (PFail u)+ OK o -> return (OK o)++instance Monad m => MonadError err (PredicateT err m) where+ throwError msg = PredicateT{ runPredicateT = return (PFail msg) }+ catchError ptrans catcher = PredicateT $ do+ value <- runPredicateT ptrans+ case value of+ Backtrack -> return Backtrack+ PFail u -> runPredicateT (catcher u)+ OK a -> return (OK a)++instance (Functor m, Monad m) => Applicative (PredicateT err m) where { pure = return; (<*>) = ap; }++instance (Functor m, Monad m) => Alternative (PredicateT err m) where { empty = mzero; (<|>) = mplus; }++instance MonadTrans (PredicateT err) where { lift m = PredicateT(m >>= return . OK) }++instance MonadIO m => MonadIO (PredicateT err m) where { liftIO = PredicateT . liftIO . fmap OK }++----------------------------------------------------------------------------------------------------++-- | Often it is necessary to evaluate a sub-predicate monad within the 'Predicate' or 'PredicateT'+-- monads within the current 'Predicate' monad. Simply evaluating the sub-predicate would cause the+-- current predicate monad to evaluates to 'PFail' or 'Backtrack' if the sub-predicate evaluates to+-- either of these values. But using 'catchPredicate', it is possible to safely evaluate the+-- sub-predicate and capture it's 'Predicate' result, where you can then make a decision on how to+-- behave.+-- > do p <- 'catchPredicate' couldFailOrBacktrack+-- > case p of+-- > 'OK' rval -> useReturnValue rval -- use the return value from couldFailOrBacktrack+-- > 'PFail' msg -> printMyError msg -- report the error from couldFailOrBacktrack+-- > 'Backtrack' -> return () -- ignore backtracking+-- The above procedure prints the error message created if the sub-predicate evaluated to 'PFail'.+-- If you would like to "re-throw" a 'Predicate' that you have received you can use the 'predicate'+-- function. For example, this line of code could be added to the above procedure:+-- > predicate p+-- and the function will evaluate to the same exact 'Predicate' value that @couldFailOrBacktrack@+-- had produced after the necessary response to the failure has been made, e.g. after the error+-- message has been printed.+class MonadPlusError err m | m -> err where+ -- | Unlifts the whole 'Predicate' value, unlike 'catchError' which only catches the value stored+ -- in a 'PFail' constructor.+ catchPredicate :: m a -> m (Predicate err a)+ -- | This will force the 'Predicate' value of the current computation. The following should+ -- generally be true for all instances of 'MonadPlusError'.+ -- > 'Control.Monad.return' = 'predicate' . 'OK'+ -- > 'Control.Monad.Error.State.throwError' = 'predicate' . 'PFail'+ -- > 'Control.Monad.mzero' = 'predicate' 'Backtrack'+ predicate :: Predicate err a -> m a++instance MonadPlusError err (Predicate err) where { catchPredicate = OK; predicate = id; }++instance Monad m => MonadPlusError err (PredicateT err m) where+ catchPredicate (PredicateT fn) = PredicateT{ runPredicateT = fn >>= \o -> return (OK o) }+ predicate pval = PredicateT (return pval)++-- | Evaluates to an empty list if the given 'Predicate' is 'Backtrack' or 'PFail', otherwise returns a+-- list containing the value in the 'OK' value.+okToList :: Predicate err o -> [o]+okToList pval = case pval of+ OK o -> [o]+ Backtrack -> []+ PFail _ -> []++-- | Like 'okToList', but evaluates to 'Data.Maybe.Nothing' if the given 'Predicate' is 'Backtrack' or+-- 'PFail', or 'Data.Maybe.Just' containing the value in the 'OK' value.+okToJust :: Predicate err o -> Maybe o+okToJust pval = case pval of+ OK o -> Just o+ Backtrack -> Nothing+ PFail _ -> Nothing++-- | If given 'Data.Maybe.Nothing', evaluates to 'PFail' with the given error information.+-- Otherwise, evaluates to 'OK'.+maybeToPFail :: err -> Maybe o -> Predicate err o+maybeToPFail err o = case o of+ Nothing -> PFail err+ Just ok -> OK ok++-- | Like 'Prelude.fmap' but operates on the error report data of the 'Predicate'.+fmapPFail :: (errA -> errB) -> Predicate errA o -> Predicate errB o+fmapPFail f pval = case pval of+ OK o -> OK o+ Backtrack -> Backtrack+ PFail err -> PFail (f err)++-- | Like 'Data.Either.partitionEithers', but operates on a list of 'Predicates'.+partitionPredicates :: [Predicate err o] -> ([err], [o])+partitionPredicates = loop [] [] where+ loop errs oks ox = case ox of+ [] -> (errs, oks)+ OK o : ox -> loop errs (oks++[o]) ox+ PFail err : ox -> loop (errs++[err]) oks ox+ Backtrack : ox -> loop errs oks ox+
+ src/Dao/Random.hs view
@@ -0,0 +1,483 @@+-- "src/Dao/Random.hs" generates objects from integers that can be used+-- to test the parsers/pretty-printer, and the binary encoder/decoder.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++-- | Hints for making good random object generators:+-- Avoid using scramble more than once per constructor. If you have a data type constructed with:+-- > return DataType <*> 'scrambO' <*> 'scrambO'+-- then just do this instead+-- > 'scramble' $ return DataType <*> 'randO' <*> 'randO'+-- +-- Do not bother preceding generators for newtype data types with 'recurse' or 'countNode', it isn't+-- really necessary.+-- +-- Recursive data types containing lists with 'randList' should probably generate small lists for+-- 'randO' instances, and generate larger lists for 'defaultO' instances.+-- +-- When instantiating 'randO', use only 'randO' to fill-in all the objects contained in the object.+-- When instantiating 'defaultO', use only 'defaultO' to fill-in all the objects contained in the+-- object.+--+-- When instantiating 'defaultO', it is OK to use 'defaultO' to fill-in other non-recursive data+-- types, but /BE VERY CAREFUL/ not to call 'defaultO' for another non-recursive type.+module Dao.Random where++import Dao.String+import qualified Dao.Tree as T++import Control.Exception+import Control.Applicative+import Control.Monad.Trans.Class+import Control.Monad.IO.Class+import Control.Monad.State++import Data.Monoid+import Data.Char+import Data.Word+import Data.Ratio+import Data.Time+import Data.Array.IArray+import qualified Data.ByteString.Char8 as B++import System.Random+import System.IO++----------------------------------------------------------------------------------------------------++-- | A simple, stateful monad for generating arbitrary data types based on pseudo-random numbers+-- without lifting the @IO@ or @ST@ monads, i.e. it can be evaluated in a pure way.+newtype RandT m a = RandT { runRandT :: StateT RandOState m a }+ deriving (Functor, Applicative, Monad, MonadPlus, Alternative)++instance MonadTrans RandT where { lift = RandT . lift }+instance MonadIO m => MonadIO (RandT m) where { liftIO = RandT . liftIO }++type RandO a = RandT IO a++data RandOState+ = RandOState+ { integerState :: Integer -- ^ the predictable random generator+ , stdGenState :: StdGen -- ^ the unpredictable random generator+ , nodeCounter :: Int -- ^ counts how many nodes have been created for this particular run+ , depthLimit :: Int -- ^ sets the limit of the recursion depth+ , currentDepth :: Int -- ^ counts the current recursion depth for this particular run+ , deepestSoFar :: Int -- ^ keeps track of how deep the deepest recursion has gone+ , traceLevel :: Int + -- ^ when performing a trace, keeps track of how many trace recursions there have been+ }++-- | Initializes the 'RandOState' with two integer values: a maximium recursion depth value (limits+-- the number of times you can recursively call 'limSubRandO') and the seed value passed to+-- 'System.Random.mkStdGen'.+initRandOState :: Int -> Int -> RandOState+initRandOState subdepthlim seed =+ RandOState+ { integerState = fromIntegral seed+ , stdGenState = mkStdGen seed+ , nodeCounter = 0 + , depthLimit = subdepthlim+ , currentDepth = 0+ , deepestSoFar = 0+ , traceLevel = 0+ }++-- | Increment the internal node counter of the random generator state. This is good for measuring+-- the "weight" of randomly generated objects. /NOTE:/ do not use this if you also start your+-- 'randO' instance with 'recurse', because 'recurse' also calls this function.+countNode_ :: RandO Int+countNode_ = do+ i' <- RandT $ gets nodeCounter+ let i = i' + 1+ RandT $ modify (\st -> st{nodeCounter=i})+ return i++-- | Algorithmically identical 'countNode_' but its function type is such that it can be written like so:+-- > 'countNode' $ do ...+-- whereas the 'countNode' function must be written like so:+-- > do { 'countNode_'; ... }+-- or+-- > countNode_ >> ...+-- /NOTE:/ do not use this if you also start your 'randO' instance with 'recurse', because 'recurse'+-- also calls this function.+countNode :: RandO a -> RandO a+countNode fn = countNode_ >> fn++newtype RandChoice o = RandChoice{ getChoiceArray :: Maybe (Array Int (RandO o)) }+instance Functor RandChoice where+ fmap f (RandChoice arr) = RandChoice $ fmap (fmap (fmap f)) arr+instance Monoid (RandChoice o) where+ mempty = RandChoice Nothing+ mappend (RandChoice a) (RandChoice b) = RandChoice $ msum+ [ do (loA, hiA) <- fmap bounds a+ (loB, hiB) <- fmap bounds b+ listA <- fmap elems a+ listB <- fmap elems b+ return $ listArray (min loA loB, max hiA hiB) (listA++listB)+ , a, b+ ]++-- | Similar to monadic bind, allows you to create a new 'RandChoice' by using the value produced by+-- another 'RandChoice'.+bindRandChoice :: RandChoice o -> (o -> RandChoice p) -> RandChoice p+bindRandChoice (RandChoice arr) f = RandChoice $ fmap (fmap (\o -> o >>= runRandChoiceOf . f)) arr++-- | Instantiate your data types into this class if you can generate arbitrary objects from random+-- numbers using the 'RandO' monad. Minimal complete definition is one of either 'randO' or+-- 'randChoice', and one of either 'defaultO' or 'defaultChoice'.+class HasRandGen o where+ -- | This is the function used to generate a random object of a data type that only has one+ -- constructor. You must define either this or 'randChoice' or both. The 'randChoice' will be+ -- defined as @('randChoiceList' ['randO'])@.+ randO :: RandO o+ randO = runRandChoice+ -- | This is the function used to generate a random object of a data type that has multiple+ -- constructors. Use 'randChoiceList' to build a list of 'RandO' data types, each item producing+ -- an object with a different constructor. You must define either this or 'randO' or both. The+ -- 'randO' will be defined using @('runChoiceList' 'randChoice')@ by default.+ randChoice :: RandChoice o+ randChoice = randChoiceList [randO]+ -- | The 'randO' and/or 'randChoice' functions can be defined without any restrictions, they can+ -- even be called recursively. But since we are working with randomness, recursion may produce+ -- arbitrarily large objects which may consume all memory and crash the program. The 'recurse'+ -- function can be used to count the depth of the recursive data type constructed and to limit the+ -- depth, and when the limit is reached a non-recursive default value should be constructed+ -- instead. This is the function that should produce the non-recursive data.+ --+ -- Keep in mind that it is impossible to enforce whether or not the data generated by any function+ -- will be recursive or not unless the data type itself is inherently not recursive. So it is the+ -- programmers responsiblity to understand how to use this function. This function must terminate,+ -- it is your responsibility to see that it does, it is your responsability to make sure you never+ -- call a recursive function within this function.+ --+ -- For data types that are inherenly not recursive, for example types instantiating+ -- 'Prelude.Enum', this function may safely be defined by calling 'randO'. For recursive data+ -- types, if the data type instantiates 'Data.Monoid.Monoid', consider returning+ -- 'Data.Monoid.mempty'.+ -- + -- Either this function or 'defaultChoice' must be defined.+ defaultO :: RandO o+ defaultO = runDefaultChoice+ -- | This function is to 'defaultO' what 'randChoice' is to 'randO': it lets you construct a+ -- random object from a list of choices, but like 'defaultO' every choice provided here must NOT+ -- be a recursive function.+ defaultChoice :: RandChoice o+ defaultChoice = randChoiceList [defaultO]++runRandChoiceOf :: RandChoice o -> RandO o+runRandChoiceOf (RandChoice{ getChoiceArray=arr }) = case arr of+ Nothing -> fail "null RandChoice"+ Just arr -> let (lo, hi) = bounds arr in join $ (arr!) . (lo+) <$> nextInt (hi-lo)++runRandChoice :: HasRandGen o => RandO o+runRandChoice = runRandChoiceOf randChoice++runDefaultChoice :: HasRandGen o => RandO o+runDefaultChoice = runRandChoiceOf defaultChoice++randChoiceList :: forall o . [RandO o] -> RandChoice o+randChoiceList items = RandChoice{ getChoiceArray = guard (not $ null items) >> (Just arr) } where+ len = length items+ arr :: Array Int (RandO o)+ arr = listArray (0, len-1) items++instance HasRandGen () where { randO = return (); defaultO = randO; }+instance HasRandGen Int where { randO = randInt; defaultO = randO; }+instance HasRandGen Integer where { randO = fmap fromIntegral randInt; defaultO = randO; }+instance HasRandGen Char where { randO = fmap chr randInt; defaultO = randO }+instance HasRandGen Word64 where { randO = fromIntegral <$> (randO::RandO Int); defaultO = randO; }+instance HasRandGen Rational where+ randO = return (%) <*> defaultO <*> ((+1) <$> defaultO)+ defaultO = randO+instance HasRandGen Double where { randO = fromRational <$> randO; defaultO = randO; }+instance HasRandGen UTCTime where+ randO = do+ day <- fmap (ModifiedJulianDay . unsign . flip mod 73000) randInt+ sec <- fmap (fromRational . toRational . flip mod 86400) randInt+ return (UTCTime{utctDay=day, utctDayTime=sec})+ defaultO = randO+instance HasRandGen NominalDiffTime where+ randO = randInteger (fromRational 0) $ \i -> do+ div <- randInt+ fmap (fromRational . (% fromIntegral div) . longFromInts) (replicateM (mod i 2 + 1) randInt)+ defaultO = randO+instance HasRandGen Name where { randO = fmap (fromUStr . randUStr) randInt; defaultO = randO; }+instance HasRandGen UStr where+ randO = fmap (ustr . unwords . fmap (uchars . toUStr)) (randList 0 9 :: RandO [Name])+ defaultO = randO+instance HasRandGen Bool where { randO = fmap (0/=) (nextInt 2); defaultO = randO; }+instance HasRandGen a => HasRandGen (Maybe a) where+ randO = randO >>= \n -> if n then return Nothing else fmap Just randO+ defaultO = return Nothing++instance (Ord p, HasRandGen p, HasRandGen o) => HasRandGen (T.Tree p o) where+ defaultO = return T.Void+ randO = recurse $ do+ branchCount <- nextInt 4+ cuts <- fmap (map (+1) . randToBase 6) randInt+ fmap (T.fromList . concat) $ replicateM (branchCount+1) $ do+ wx <- replicateM 6 randO+ forM cuts $ \cut -> do+ obj <- randO+ return (take cut wx, obj)++-- | Construct a value from an 'Prelude.Int'. Actually, you have a 50/50 chance of drawing a zero,+-- but this is because zeros are used often for you data type.+randInteger :: a -> (Int -> RandO a) -> RandO a+randInteger zero mkOther = do+ i <- randInt+ let (x, r) = divMod i 2+ if r==0 then return zero else mkOther x++-- | Generate a random object given a maximum recursion limit, a seed value, and a 'RandO' generator+-- function. The weight (meaning the number of calls to 'countNode', 'countNode_', or 'recurse') of+-- the generated item is also returned.+genRandWeightedWith :: RandO a -> Int -> Int -> IO (a, Int)+genRandWeightedWith (RandT gen) subdepthlim seed =+ fmap (fmap nodeCounter) $ runStateT gen (initRandOState subdepthlim seed)++-- | This function you probably will care most about. does the work of evaluating the+-- 'Control.Monad.State.evalState' function with a 'RandOState' defined by the same two parameters+-- you would pass to 'initRandOState'. In other words, arbitrary random values for any data type @a@+-- that instantates 'HasRandGen' can be generated using two integer values passed to this function.+genRandWeighted :: HasRandGen a => Int -> Int -> IO (a, Int)+genRandWeighted subdepthlim seed = genRandWeightedWith randO subdepthlim seed++-- | Like 'genRandWeightedWith' but the weight value is ignored, only being evaluated to the random+-- object,+genRandWith :: RandO a -> Int -> Int -> IO a+genRandWith gen subdepthlim seed = fmap fst $ genRandWeightedWith gen subdepthlim seed++-- | Like 'genRandWeightedWith' but the weight value is ignored, only being evaluated to the random+-- object.+genRand :: HasRandGen a => Int -> Int -> IO a+genRand subdepthlim seed = genRandWith randO subdepthlim seed++randTrace :: MonadIO m => String -> RandT m a -> RandT m a+randTrace msg rand = do+ trlev <- RandT $ gets traceLevel+ nc <- RandT $ gets nodeCounter+ sd <- RandT $ gets currentDepth+ let prin msg = liftIO $ do+ hPutStrLn stderr (replicate trlev ' ' ++ msg)+ hFlush stderr >>= evaluate+ () <- prin $ "begin "++msg++" c="++show nc++" d="++show sd+ RandT $ modify $ \st -> st{traceLevel=trlev+1}+ a <- rand >>= liftIO . evaluate+ RandT $ modify $ \st -> st{traceLevel=trlev}+ () <- prin $ "end "++msg+ return a++-- | Take another integer from the seed value. Provide a maximum value, the pseudo-random integer+-- returned will be the seed value modulo this maximum value (so passing 0 will result in a+-- divide-by-zero exception, passing 1 will always return 0). The seed value is then updated with+-- the result of this division. For example, if the seed value is 10023, and you pass 10 to this+-- function, the result returned will be 3, and the new seed value will be 1002.+-- Using numbers generated from this seed value is very useful for generating objects that are+-- somewhat predictable, but the contents of which are otherwise unpredictable. For example, if you+-- want to generate random functions but always with the names "a", "b", or "c", like so:+-- > a(...), b(...), c(...)+-- where the arguments to these functions can be arbitrary, then have your function generator+-- generate the names of these functions using 'nextInt' like so:+-- > 'Prelude.fmap' ('Prelude.flip' 'Prelude.lookup ('Prelude.zip' "abc" [0,1,2]))'nextInt' 3+-- then the arguments to these functions can be generated using 'randInt'. The names of the+-- functions will be predictable for your seed values: any seed value divisible by 3 will generate a+-- function named "a", but the arguments will be arbitrary because they were generated by 'randInt'.+nextInt :: Int -> RandO Int+nextInt maxval = if abs maxval==1 || maxval==0 then return 0 else do+ st <- RandT $ get+ let (i, rem) = divMod (integerState st) (fromIntegral (abs maxval))+ RandT $ put $ st{integerState=i}+ return (fromIntegral rem)++-- | Generate a random integer from the pseudo-random number generator.+randInt :: RandO Int+randInt = RandT $+ state (\st -> let (i, gen) = next (stdGenState st) in (i, st{stdGenState=gen}))++-- | Mark a recursion point, also increments the 'nodeCounter'. The recusion depth limit set when+-- evaluating a 'randO' computation will not be exceeded. When the number of 'recurse' functions+-- called without returning has reached this limit and this function is evaluated again, the given+-- 'RandO' generator will not be evaluated, the default value will be returned.+recurse :: HasRandGen a => RandO a -> RandO a+recurse fn = do+ st <- RandT get+ if currentDepth st > depthLimit st+ then defaultO+ else do+ countNode_+ i <- (+1) <$> (RandT $ gets currentDepth)+ RandT $ modify (\st -> st{currentDepth = i, deepestSoFar = max (deepestSoFar st) i})+ a <- fn+ RandT $ modify (\st -> st{currentDepth = currentDepth st - 1})+ return a++-- | The 'nextInt' function lets you derive objects from a non-random seed value internal to the+-- state of the 'RandT' monad. This is useful for random objects that have multiple constructors,+-- and you want to generate one of every constructor by simply initializing the random seed with+-- incrementing integers.+--+-- However every instantiation of 'randChoice' makes use of this seed value. Consequently if your+-- data type is composed entirely of objects which all instantiate 'randChoice', every node of the+-- object will be generated by a non-random number. In some cases this is desirable, it allows you+-- to generate every possible object with a large enough sequence of random numbers.+--+-- However when you wish to generate a very random, varied set of random objects, this+-- predictability is not desirable. To get around this, you can use the 'scramble' function. This+-- will use the current seed value to initialize a new child random generator with a child random+-- seed, and the provided 'RandT' function will be evaluated with in this child environment. After+-- evaluation is complete, the parent seed is restored. Since the child random seed is derived from the+-- parent seed, you are still guaranteed to always generate the same object from the same seed value,+-- but the child object generated will be much more varied and less predictable.+-- +-- So instead of generating a child node of your object with ordinary 'randO', use 'scrambO' (which+-- is equivalent to @('scramble' 'randO')@ and this will make your objects more varied, even for+-- predictable input.+scramble :: RandO a -> RandO a+scramble fn = do+ newGen <- randInt+ oldst <- RandT get+ RandT (put $ oldst{ stdGenState=mkStdGen newGen })+ let wrap x = toInteger (fromIntegral x :: Word)+ x <- wrap <$> randInt+ y <- wrap <$> randInt+ RandT (modify $ \st -> st{ integerState = x*(1 + (toInteger (maxBound::Word))) + y })+ a <- fn+ RandT (modify $ \st -> st{ integerState=integerState oldst, stdGenState=stdGenState oldst })+ return a++-- | This function is defined simply as @('scramble' 'randO')@, but I expect it to be used often+-- enough it warrants it's own function name.+scrambO :: HasRandGen a => RandO a+scrambO = scramble randO++-- | The number of unique values a 'Prelude.Int' can be, which is @('Prelude.maxBound'+1)*2@.+intBase :: Integer+intBase = (fromIntegral (maxBound::Int) + 1) * 2++-- | Take an ordinary 'Prelude.Int' and make it unsigned by checking if it is a negative value, and+-- if it is, returning the maximum unsigned value plus the negative value, otherwise returning the+-- positive value unchanged. For example, -1 will return @2*('Prelude.maxBound'+1)-1@ and @+1@ will+-- return @1@.+unsign :: Int -> Integer+unsign i = if i<0 then intBase + fromIntegral i else fromIntegral i++-- | Creates a string of digits from 0 to the given @base@ value by converting a random unsigned+-- integer to the list of digits that represents the random integer in that @base@. For example, if+-- you want a list of digits from 0 to 4 to be produced from a number 54, pass 4 as the base, then+-- the number 54. Each digit of the base-4 number representation of 54 will be returned as a+-- separate integer: @[2,1,3]@ (from lowest to highest place value, where 123 in base 10 would+-- return the list @[3,2,1]@).+randToBase :: Int -> Int -> [Int]+randToBase base i = loop (unsign i) where+ loop i = if i==0 then [] else let (i' , sym) = divMod i b in fromIntegral sym : loop i'+ b = fromIntegral base++-- | When generating 'Prelude.Integers' from 'Int's, treat a list of 'Int's as a list of symbols in+-- a base M number, where M is the @('Prelude.maxBound'::'Prelude.Int')@ multiplied by two to allow+-- for every negative number to also be considered a unique symbol.+longFromInts :: [Int] -> Integer+longFromInts = foldl (\a b -> a*intBase + unsign b) 0++randEnum :: (Bounded x, Enum x) => x -> x -> RandO x+randEnum lo hi = fmap toEnum (nextInt (abs (fromEnum lo - fromEnum hi)))++----------------------------------------------------------------------------------------------------++randUStr :: Int -> UStr+randUStr = ustr . B.unpack . getRandomWord++randMultiName :: RandO [UStr]+randMultiName = do+ i0 <- randInt+ let (i1, len) = divMod i0 4+ fmap ((randUStr i1 :) . map randUStr) (replicateM len randInt)++-- | When you want to use 'randList' or 'randListOf', you must provide a maximum bound for the+-- number of values generated for the list. Lets say you want a maximum bound of 20 items for your+-- data types. It sounds reasonable, but if your data type is recursive, and your recursion depth+-- limit 'depthLimit' is set to 4, your data type has a chance of creating 20^4 or 160000 nodes! You+-- may want to call 'randListOf' or 'randList' with a diminishing upper bound, a bound which gets+-- lower and lower as the recursion depth increases.+--+-- That is the purpose of this function. You provide an initial integer value (like 24) and this+-- value will be logarithmically scaled based on the 'currentDepth' value. The scaling equation is:+-- > \x -> 'Prelude.floor' (x / 2^'depthLimit')+-- So if you provide a value of 24 to this function, the value returned will be:+-- > 'Prelude.floor' (24 / 2^'depthLimit')+-- And if the 'depthLimit' is 4 then @'Prelude.floor' (2 / 2^4) == 'Prelude.floor' (2/16) == 1@. So+-- for passing a value of 24 means the maximum number of nodes will be @24*12*6*3*1 == 5184@ nodes,+-- which is large, but considerably smaller than 160000 nodes.+depthLimitedInt :: Int -> RandO Int+depthLimitedInt x = getCurrentDepth >>= \d -> return (div x (2^d))++getCurrentDepth :: Monad m => RandT m Int+getCurrentDepth = RandT $ gets currentDepth++randListOf :: Int -> Int -> RandO a -> RandO [a]+randListOf minlen maxlen rando = do+ -- half of all lists will be null, unless the 'minlen' parameter is greater than 0+ minlen <- return (min minlen maxlen)+ maxlen <- return (max minlen maxlen)+ empt <- if minlen==0 then nextInt 2 else return 0+ if empt==1+ then return []+ else do+ ln <- nextInt (maxlen-minlen)+ replicateM (minlen+ln) rando++randList :: HasRandGen a => Int -> Int -> RandO [a]+randList lo hi = randListOf lo hi randO++defaultList :: HasRandGen a => Int -> Int -> RandO [a]+defaultList lo hi = randListOf lo hi defaultO++randRational :: Int -> RandO Rational+randRational i0 = do+ let (i1, len1) = divMod i0 4+ (_ , len2) = divMod i1 4+ a <- fmap longFromInts (replicateM (len1+1) randInt)+ b <- fmap longFromInts (replicateM (len2+1) randInt)+ return (a%b)++getRandomWord :: Int -> B.ByteString+getRandomWord i = randomWords ! (mod i (rangeSize (bounds randomWords) - 1))++randomWords :: Array Int B.ByteString+randomWords = listArray (0, length list - 1) (map B.pack list) where+ list = words $ unwords $+ [ "a academia accomplished added also an analysis and application applications apply are arent slim"+ , "argument arguments as at avoids be because been behavior between book both by calculus plus were"+ , "calling can change changes code commercial computability computation computer concepts earth was"+ , "constructs contrast conversely declarative definition depending depends describing metal key fee"+ , "designed developed development difference different domains domain easier effects fire water add"+ , "elaborations elements eliminating emphasize entscheidungsproblem eschewing star best least being"+ , "especially evaluation example executing expression facilitate financial formal greatest open etc"+ , "functional has have hope how however imperative industrial input investigate is home close where"+ , "it key lack lambda language languages largely like make many math mathematical may from flow she"+ , "motivations much mutable notion numeric of on one ones only organizations output paradigm pit he"+ , "specific pioneering practice predict produce program programming prominent purely rather trust I"+ , "recursion referential result roots same science side so software some specifically state move me"+ , "statistics style subject such supported symbolic system than that the they child this super mesh"+ , "transparency treats twice understand use used value values variety viewed which wide will bill X"+ , "dates times database structured listing setting dictionary returning throwing catching law factor"+ , "option procedure alpha beta electron proton neutron shift hard soft bean beam fix drug undo minus"+ , "field magic latice jump assemble area volume interesting slice sector region cylinder sphere plan"+ , "inside without trying patterned rules"+ ]+
+ src/Dao/RefTable.hs view
@@ -0,0 +1,75 @@+-- "src/Dao/RefTable.hs" a data type which maps opaque reference+-- handles to 'Dynamic' objects.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++-- | This module is used to implement a kind of un-typed global state for the Dao runtime which can+-- store object of arbitrary type in a place that can be accessed anywhere. It is used for data+-- types like file handles which have their own unique identifiers and you don't need Dao to assign+-- it a new unique ID to it them for you, you can just index it by a hash of the the unique ID.+-- +-- Every value stored in a table gets it's own MVar, so updating the value is thread safe, but it is+-- a bit memory intensive. So a 'RefTable' should only be used for things that really need to be+-- well protected, like UNIX file descriptors or sockets, ordinary file handles, credentials,+-- handles to access databases -- anything that needs to be allocated and released in order for it+-- to be used during runtime.+module Dao.RefTable+ ( RefMonad,+ RefNode, refNodeDestructor, refNodeValue, refTableNode,+ RefTable, newRefTable, initializeWithKey, destroyWithKey+ )+ where++import qualified Dao.HashMap as H++import Control.Applicative+import Control.Concurrent.MVar+import Control.Monad.Reader++----------------------------------------------------------------------------------------------------++type RefMonad key val a = ReaderT (RefTable key val) IO a++data RefNode a = RefNode{ refNodeDestructor :: IO (), refNodeValue :: MVar a }++refTableNode :: (a -> IO ()) -> a -> IO (RefNode a)+refTableNode destructor a = newMVar a >>= \mvar ->+ return $ RefNode{ refNodeDestructor=destructor a, refNodeValue=mvar }++newtype RefTable key a = RefTable { refTableMVar :: MVar (H.HashMap key (RefNode a)) }++newRefTable :: IO (RefTable key a)+newRefTable = RefTable <$> newMVar H.empty++initializeWithKey :: Ord key => a -> IO () -> H.Index key -> RefMonad key a (MVar a)+initializeWithKey o destructor key = do+ mvar <- H.hashLookup key <$> (asks refTableMVar >>= liftIO . readMVar)+ case mvar of+ Just node -> return (refNodeValue node)+ Nothing -> do+ mvar <- liftIO $ newMVar o+ asks refTableMVar >>=+ liftIO . flip modifyMVar (\hmap -> return (H.hashInsert key (RefNode destructor mvar) hmap, mvar))++destroyWithKey :: Ord key => H.Index key -> RefMonad key a ()+destroyWithKey key = do+ mvar <- H.hashLookup key <$> (asks refTableMVar >>= liftIO . readMVar)+ case mvar of+ Nothing -> return ()+ Just _ -> asks refTableMVar >>= liftIO . flip modifyMVar_ (return . H.hashDelete key)+
+ src/Dao/Stack.hs view
@@ -0,0 +1,88 @@+-- "src/Dao/Stack.hs" provides the stack mechanism used for storing+-- local variables during evaluation.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++module Dao.Stack where++import qualified Data.Map as M++import Control.Monad+import Control.Monad.Identity++newtype Stack key val = Stack { mapList :: [M.Map key val] } deriving Show++emptyStack :: Stack key val+emptyStack = Stack []++stackLookup :: Ord key => key -> Stack key val -> Maybe val+stackLookup key stack = foldl (\f -> mplus f . (M.lookup key)) Nothing (mapList stack)++-- | Always update in the top of the stack, regardless of whether the key being updated has been+-- defined at some lower level in the stack. If the stack is empty, the update function is evaluated+-- with 'Prelude.Nothing' and the result @a@ is returned, but the updated @val@ is disgarded.+stackUpdateTopM+ :: (Monad m, Ord key)+ => (Maybe val -> m (a, Maybe val)) -> key -> Stack key val -> m (a, Stack key val)+stackUpdateTopM updVal key (Stack stack) = case stack of+ [] -> updVal Nothing >>= \ (a, _) -> return (a, Stack [])+ s:stack -> updVal (M.lookup key s) >>= \ (a, o) -> return (a, Stack $ M.alter (const o) key s : stack)++-- | If the key does not exist, the update will occur in the top level of the stack. If the key does+-- exist, regardless of whether the key exists in the top or in some lower level, the value at that+-- key will be updated in the level in which it is defined. If the stack is empty, the update+-- function is evaluated with 'Prelude.Nothing' and the result @a@ is returned, but the updated+-- @val@ is disgarded.+stackUpdateM+ :: (Monad m, Ord key)+ => (Maybe val -> m (a, Maybe val)) -> key -> Stack key val -> m (a, Stack key val)+stackUpdateM updVal key (Stack stack) = loop [] stack where+ loop rx stack = case stack of+ [] -> atTop (reverse rx)+ s:stack -> case M.lookup key s of+ Just o -> updVal (Just o) >>= \ (a, o) ->+ return (a, Stack $ reverse rx ++ M.alter (const o) key s : stack)+ Nothing -> loop (s:rx) stack+ atTop stack = case stack of+ [] -> updVal Nothing >>= \ (a, _) -> return (a, Stack [])+ s:stack -> updVal Nothing >>= \ (a, o) -> return (a, Stack $ M.alter (const o) key s : stack)++stackUpdate+ :: Ord key+ => (Maybe val -> (a, Maybe val))+ -> key -> Stack key val -> (a, Stack key val)+stackUpdate upd key = runIdentity . stackUpdateM (return . upd) key++stackUpdateTop+ :: Ord key+ => (Maybe val -> (a, Maybe val))+ -> key -> Stack key val -> (a, Stack key val)+stackUpdateTop upd key = runIdentity . stackUpdateTopM (return . upd) key++-- | Define or undefine a value at an address on the top tree in the stack.+stackDefine :: Ord key => key -> Maybe val -> Stack key val -> Stack key val+stackDefine key val = snd . stackUpdate (const ((), val)) key++stackPush :: Ord key => M.Map key val -> Stack key val -> Stack key val+stackPush init stack = stack{ mapList = init : mapList stack }++stackPop :: Ord key => Stack key val -> (Stack key val, M.Map key val)+stackPop stack =+ let mx = mapList stack+ in if null mx then (stack, M.empty) else (stack{mapList=tail mx}, head mx)+
+ src/Dao/StepList.hs view
@@ -0,0 +1,414 @@+-- "src/Dao/StepList.hs" provides a fundamental data type used in the+-- Dao System, the "StepList", which is a cursor that can step forward+-- or backward through a list.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.+++{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- | This is a line-editor object, but it works with arbitrary lists of objects, but this will work+-- for editing arbitrary lists. You could use it to create an ordinary line editor by representing a+-- file as be a list of strings representing a file. each string could further be converted to a+-- StepList containing characters to edit the line. +--+-- This module provides a basic list cursor interface, that is, a list that you can step through+-- forward or backward, item by item. This is useful for building line editors. This module export+-- operators, so it is best not to import this module qualified. Therefore functions similar to+-- 'Data.Set.empty' or 'Data.Set.singleton' are named 'slEmpty' and 'slSingleton' to prevent name+-- conflicts without using qualified importing.+--+module Dao.StepList where++import Control.Applicative+import Control.Monad++import Data.Array.IArray+import qualified Data.IntMap as I+import Data.List+import Data.Monoid+import Data.Typeable++----------------------------------------------------------------------------------------------------++data StepList a+ = StepList+ { slCursor :: Int+ , slLength :: Int+ , slLeftOfCursor :: [a]+ -- ^ items to the left of the cursor are stored in reverse order. If your 'StepList' is:+ -- > list_A = [0, 1, 2, 3, 4] <> [5, 6, 7, 8]+ -- evaluating @'leftOfCursor' list_A@ produces:+ -- > [4, 3, 2, 1, 0]+ , slRightOfCursor :: [a]+ -- ^ items to the left of the cursor are stored in forward order. If your 'StepList' is:+ -- > list_A = [0, 1, 2, 3, 4] <> [5, 6, 7, 8]+ -- evaluating @'leftOfCursor' list_A@ produces:+ -- > [5, 6, 7, 8]+ }+ deriving (Eq, Ord, Show, Read, Typeable)++instance Monoid (StepList a) where { mempty = slEmpty; mappend = (+:+); }+instance Functor StepList where { fmap = slMap }+instance MonadPlus StepList where { mzero = mempty; mplus = (+:+); }+instance Applicative StepList where { pure = return; (<*>) = ap; }+instance Alternative StepList where { empty = mempty; (<|>) = (+:+); }+instance Monad StepList where+ -- | Return is defined by 'slSingletonL'. This is because when lists are used as a monad, the+ -- 'Control.Monad.return' operations that occur earlier in the computation place items in the list+ -- earlier than 'Control.Monad.return' operations that occur later in the computation. Therefore a+ -- monadic computation like @a >> b@ will have @a@ placed to the left of @b@.+ return = slSingletonL+ -- | Just like how the '(Prelude.>>=)' operator instantiated for Haskell lists is the+ -- 'Prelude.concatMap' function, the '(Prelude.>>=)' operator for 'StepList's is the 'slConcatMap'+ -- function.+ (>>=) = flip slConcatMap++-- | Test if the 'StepList' contains no elements.+slNull :: StepList a -> Bool+slNull sl = null (slLeftOfCursor sl) && null (slRightOfCursor sl)++-- | Create an empty 'StepList'.+slEmpty :: StepList a+slEmpty = StepList 0 0 [] []++-- | Create a 'StepList' with a single item to the right of the cursor.+slSingletonR :: a -> StepList a+slSingletonR a = StepList 1 1 [a] []++-- | Create a 'StepList' with a single item to the left of the cursor.+slSingletonL :: a -> StepList a+slSingletonL a = StepList 1 1 [a] []++-- | Create a 'StepList' from a list of elements and an initial cursor position. The cursor position+-- can be out of range, it will be placed at the end by default.+slFromList :: Int -> [a] -> StepList a+slFromList i lst =+ let len = length lst+ cur = max 0 i+ (left, right) = splitAt cur lst+ in StepList+ { slCursor = min cur len+ , slLength = len+ , slLeftOfCursor = left+ , slRightOfCursor = right+ }++-- | Create a 'StepList' with two lists, the items to the left of the cursor, and the items to the+-- right of the cursor. The elements of the list to the left of the cursor are not reversed when+-- constructing the 'StepList' with this constructor. When the cursor is moved back to the+-- beginning of the list, the items will be reversed, i.e.+-- > slToList (slFromList [0,1,2,3] [4,5,6,7])+-- will evaluate to the list:+-- > [3,2,1,0,4,5,6,7]+slFromLeftRight :: [a] -> [a] -> StepList a+slFromLeftRight left right =+ StepList{ slCursor=cur, slLength=cur+rightlen, slLeftOfCursor=left, slRightOfCursor=right } where+ cur = length left+ rightlen = length right++-- | Assign values to integer indicies in the 'Data.IntMap.IntMap' data type, and this function will+-- convert the 'Data.IntMap.IntMap' to a 'StepList'. Negative indicies will be placed to the left of+-- the cursor, with the lowest value being furthest left, positive indicies will be placed to the+-- right of the cursor with the highest value being furthest right.+slFromIntMap :: I.IntMap a -> StepList a+slFromIntMap im = slFromLeftRight (fmap snd $ sortfst left) (fmap snd $ sortfst right) where+ sortfst = sortBy (\a b -> compare (fst a) (fst b))+ (left, right) = partition ((<0) . fst) $ I.assocs im++slToArray :: StepList a -> Maybe (Array Int a)+slToArray (StepList cur len left right) =+ if len==0+ then Nothing+ else Just $ array (negate cur, len-cur-1)+ (zip (iterate (subtract 1) (negate 1)) left ++ zip (iterate (+1) 0) right)++-- | Creates an array storing the list where the indicies are 'Data.Array.IArray.bounds' such that+-- value at index zero is the value returned by 'slHeadR', items before the cursor have negative+-- indicies, and items after the cursor are zero or positive. If the cursor is at the right-most+-- position of the list, the resulting array, every item is assigned to a negative index.+slFromArray :: Maybe (Array Int a) -> StepList a+slFromArray arr = case arr of+ Nothing -> mempty+ Just arr ->+ let bnds = bounds arr+ (lo, hi) = maybe bnds id $ do+ (lo, hi) <- return bnds+ (lo, hi) <- return (min lo hi, max lo hi)+ return $ if lo>0 then (0, hi-lo) else (lo, hi)+ len = hi - lo + 1+ cur = negate lo+ left = map (arr!) (takeWhile (>=lo) $ iterate (subtract 1) (negate 1))+ right = map (arr!) (takeWhile (<=hi) $ iterate (+1) 0)+ in StepList cur len left right++-- | Convert the 'StepList' to an ordinary Haskell list.+slToList :: StepList a -> [a]+slToList (StepList _ _ left right) = reverse left ++ right++-- | When two 'StepList's are concatenated with @a +:+ b@ think of @b@ being inserted into the cursor+-- position at @a@, with all the items to the left of the cursor in @b@ being placed to the right of+-- the cursor of @a@ and all the items to the right of the cursor of @b@ being placed to the left+-- the cursor of @a@. Here is an ASCII illustration:+-- > list_A = [a0, a1, a2, a3, a4] <> [a5, a6, a7, a8]+-- > list_B = [b0, b1] <> [b2, b3, b4, b5, b6, b7, b8]+-- > ----- concatenate -----+-- > list_A<>list_B = [a0, a1, a2, a3, a4, b0, b1] <> [b2, b3, b4, b5, b6, b7, b8, a5, a6, a7, a8]+(+:+) :: StepList a -> StepList a -> StepList a+(StepList c1 n1 b1 a1) +:+ (StepList c2 n2 b2 a2) = StepList (c1+c2) (n1+n2) (b1++b2) (a2++a1)+infixr 5 +:+++slMap :: (a -> b) -> StepList a -> StepList b+slMap f (StepList cur len left right) = StepList cur len (map f left) (map f right)++slConcat :: [StepList a] -> StepList a+slConcat = foldl (+:+) slEmpty++-- | The @(Control.Monad.>>=)@ operator applied to lists is the same as the 'Data.List.concatMap'+-- function. In the case of 'StepList', the @(Control.Monad.>>=)@ operator is defined with+-- 'Data.Monoid.mconcat' and 'Prelude.map'.+slConcatMap :: (a -> StepList b) -> StepList a -> StepList b+slConcatMap f = slConcat . map f . slToList++-- | Place an item to the left of the cursor. This operator binds to the RIGHT with a precedence of+-- 5, it does not bind left. The reason is that this operator is more similar to the Haskell list+-- operator @(:)@.+-- > x <: ([0, 1, 2] <> [3, 4])+-- > ([0, 1, 2, x] <> [3, 4])+(<|) :: a -> (StepList a -> StepList a)+(<|) a (StepList cur len left right) = StepList (cur+1) (len+1) (a:left) right+infixr 5 <|++-- | Place an item to the right of the cursor. This operator binds to the right with a precedence of+-- 5, just like the Haskell list operator @(:)@.+-- > x :> ([0, 1, 2] <> [3, 4])+-- > ([0, 1, 2] <> [x, 3, 4])+(|>) :: a -> StepList a -> StepList a+(|>) a (StepList cur len left right) = StepList cur (len+1) left (a:right)+infixr 5 |>++-- | Place an item to the left of the cursor. This operator binds to the RIGHT with a precedence of+-- 5, it does not bind left. The reason is that this operator is more similar to the Haskell list+-- operator @(++)@.+-- > [a0, a1, a2] <++ ([b0, b1, b2] <> [b3, b4])+-- > ([b0, ab, b2, a0, a1, a2] <> [b3, b4])+(<++) :: [a] -> StepList a -> StepList a+(<++) ox sl = let len = length ox in+ sl{ slLeftOfCursor = slLeftOfCursor sl ++ reverse ox+ , slCursor = slCursor sl + len+ , slLength = slLength sl + len+ }+infixr 5 <++++-- | Place an item to the right of the cursor. This operator binds to the right with a precedence of+-- 5, just like the Haskell list operator @(++)@.+-- > [a0, a1, a2] ++> ([b0, b1, b2] <> [b3, b4])+-- > ([b0, b1, b2] <> [a0, a1, a2, b3, b4])+(++>) :: [a] -> StepList a -> StepList a+(++>) ox sl = let len = length ox in+ sl{ slRightOfCursor = ox ++ slRightOfCursor sl+ , slLength = slLength sl + len+ }+infixr 5 ++>++-- | Returns 'Prelude.True' if it is possible to move the cursor left or right by @n@ steps.+slShiftCheck :: Int -> StepList a -> Bool+slShiftCheck delta (StepList cur len _ _) = inRange (0, len) (cur+delta)++-- | Returns 'Prelude.True' if it the index is within the bounds of the list.+slIndexCheck :: Int -> StepList a -> Bool+slIndexCheck i (StepList _ len _ _) = inRange (0, len) i++-- | Shift the cursor @delta@ elements to the left if @delta@ is negative, or @delta@ elements to+-- the right if @delta@ is positive.+slCursorShift :: Int -> StepList a -> StepList a+slCursorShift delta0 a@(StepList cur len left right)+ | delta0==0 = a+ | delta0< 0 =+ let delta = max delta0 (negate cur)+ (middle, left') = splitAt (abs delta) left+ in StepList (cur+delta) len left' (reverse middle ++ right)+ | delta0> 0 =+ let delta = min delta0 (len-cur)+ (middle, right') = splitAt delta right+ in StepList (cur+delta) len (reverse middle ++ left) right'+ | otherwise = error "case statement of Dao.StepList.slCursorShift"++-- | Place the cursor at an index position.+slCursorTo :: Int -> StepList a -> StepList a+slCursorTo i a@(StepList cur _ _ _) = slCursorShift (i-cur) a++-- | Lookup a value at an absolute index (not relative to the cursor).+slIndex :: Int -> StepList a -> Maybe a+slIndex i sl =+ let (StepList _ _ _ right) = slCursorShift i sl+ in case right of { [] -> Nothing; o:_ -> return o; }++-- | A bounds value expressed as a pair of indicies relative to the current cursor position can be+-- converted to an bounds value expressed as a pair of indicies relative to the 0th element in the+-- 'StepList'. This is the inverse operation of 'slAbsToRel'.+slRelToAbs :: StepList a -> (Int, Int) -> (Int, Int)+slRelToAbs (StepList cur _ _ _) (lo, hi) = (cur+lo, cur+hi)++-- | A bounds value expressed as a pair of indicies relative to the 0th position in the 'StepList'+-- can be converted to a bounds value expressed as a pair of indicies relative to the current+-- cursor position. This is the inverse operation of 'slRelToAbs'.+slAbsToRel :: StepList a -> (Int, Int) -> (Int, Int)+slAbsToRel (StepList cur _ _ _) (lo, hi) = (lo-cur, hi-cur)++-- | Selects items around the cursor, with the option of deleting the items selected from the+-- 'StepList'. The first boolean parameter indicates whether or not the list should be altered by+-- deleting the items that were selected. Items to the left of the cursor are selected if the+-- indicies are negative and items to the right of the cursor are selected if the indicies are+-- positive. Specify the range to select by passing a lower and upper bound relative to the cursor.+-- For example, to select a range of ten items, five before the cursor and five after the cursor,+-- use @slGetRelRange False (negate 5) 5@. To delete that same range from the 'StepList' and also+-- return it use @slGetRelRange True (negate 5) 5@.+--+-- This function evaluates to a pair of 'StepList's. The 'Prelude.fst' is the selected items, the+-- 'Prelude.snd' is the modified 'StepList' which may be identical to the parameter 'StepList' if+-- the 'doDelete::Bool' parameter is 'Prelude.False'.+slCutRelRange :: Bool -> (Int, Int) -> StepList a -> (StepList a, StepList a)+slCutRelRange doDelete (lo, hi) o@(StepList cur len left right) = maybe (mempty, o) id $ do+ let cutBiased minLen list = do+ let lim = Just . min minLen . abs+ lo <- lim $ min lo hi+ hi <- lim $ max lo hi+ let cutLen = hi-lo+ guard (cutLen/=0)+ (keep, list) <- Just $ splitAt lo list+ (cut , list) <- Just $ splitAt cutLen list+ return (cutLen, keep, cut, list)+ case lo of+ lo|lo<0 -> case hi of+ hi|hi<=0 -> do+ (cutLen, keep, cut, left) <- cutBiased cur left+ cut <- Just $ StepList cutLen cutLen cut []+ Just $+ if doDelete+ then (cut, StepList (max 0 (cur-cutLen)) (len-cutLen) (left++keep) right)+ else (cut, o)+ hi|hi>0 -> do+ lo <- Just $ min cur (abs lo)+ hi <- Just $ min (len-cur) hi+ let cutLen = hi+lo+ (midLeft, left ) <- Just $ splitAt lo left+ (midRight, right) <- Just $ splitAt hi right+ middle <- Just $ StepList lo cutLen midLeft midRight+ Just $+ if doDelete+ then (middle, StepList (max 0 (cur-cutLen)) (len-cutLen) left right)+ else (middle, o)+ _ -> undefined+ lo|lo>=0 -> case hi of+ hi|hi<=0 -> if lo==hi then Nothing else Just $ slCutRelRange doDelete (hi, lo) o+ hi|hi>0 -> do+ (cutLen, keep, cut, right) <- cutBiased (len-cur) right+ cut <- Just $ StepList 0 cutLen [] cut+ Just $+ if doDelete+ then (cut, StepList cur (max cur (len-cutLen)) left (keep++right))+ else (cut, o)+ _ -> undefined+ _ -> undefined++-- | Like 'slCutRelRange' but operates on an upper and lower bound indicated by absolute indicies,+-- rather than indicies relative to the cursor.+slCutAbsRange :: Bool -> (Int, Int) -> StepList a -> (StepList a, StepList a)+slCutAbsRange doDelete bnds list = slCutRelRange doDelete (slAbsToRel list bnds) list++slDeleteRelRange :: (Int, Int) -> StepList a -> StepList a+slDeleteRelRange bnds = snd . slCutAbsRange True bnds++slDeleteAbsRange :: (Int, Int) -> StepList a -> StepList a+slDeleteAbsRange bnds list = slDeleteRelRange (slAbsToRel list bnds) list++slCopyRelRange :: (Int, Int) -> StepList a -> StepList a+slCopyRelRange bnds = fst . slCutRelRange False bnds++slCopyAbsRange :: (Int, Int) -> StepList a -> StepList a+slCopyAbsRange bnds list = slCopyRelRange (slAbsToRel list bnds) list++-- | Many functions may need to modify a 'StepList' but only on the elements to the left or right of+-- the cursor. These functions take a boolean type called 'Bias'+data Bias = ToLeft | ToRight deriving (Eq, Ord, Bounded, Enum, Ix, Show, Read)++-- | Modify the items to the left or right of the cursor. The function will return a polymorphic+-- value paired with an updated list of items to the left of the cursor.+biasedApply :: Bias -> ([elems] -> (result, [elems])) -> StepList elems -> (result, StepList elems)+biasedApply bias f (StepList cur len left right) = case bias of+ ToRight -> let (result, right') = f right in (result, StepList cur (length right') left right')+ ToLeft -> + let (result, left') = f left+ newlen = length left'+ in (result, StepList newlen (len-cur+newlen) left' right)++-- | Evaluates to true if the cursor is beyond the left or right-most position in the list.+slAtEnd :: Bias -> StepList a -> Bool+slAtEnd bias (StepList cur len _ _) = case bias of+ ToRight -> cur==len+ ToLeft -> cur==0++-- | Move the cursor to the right-most or left-most end of the 'StepList', depending on the 'Bias'+-- value given. The word Return has nothing to do with monad, we use this term because this function+-- is similar to the /carriage return/ of a typewriter.+slReturn :: Bias -> StepList a -> StepList a+slReturn bias (StepList cur len left right) = case bias of+ ToLeft -> StepList 0 (cur+len) [] (reverse left ++ right)+ ToRight -> StepList len len (left ++ reverse right) []++slHeadR :: StepList a -> a+slHeadR (StepList _ _ _ a) = head a++slHeadL :: StepList a -> a+slHeadL (StepList _ _ a _) = head a++slTail :: Bias -> StepList elems -> StepList elems+slTail bias = snd . biasedApply bias ((,) () . tail)++slTake :: Bias -> Int -> StepList a -> StepList a+slTake bias n = snd . biasedApply bias ((,) () . take n)++slTakeWhile :: Bias -> (a -> Bool) -> StepList a -> StepList a+slTakeWhile bias p = snd . biasedApply bias ((,) () . takeWhile p)++slDrop :: Bias -> Int -> StepList a -> StepList a+slDrop bias n = snd . biasedApply bias ((,) () . drop n)++slDropWhile :: Bias -> (a -> Bool) -> StepList a -> StepList a+slDropWhile bias p = snd . biasedApply bias ((,) () . dropWhile p)++slSplitAt :: Bias -> Int -> StepList a -> ([a], StepList a)+slSplitAt bias n = biasedApply bias (splitAt n)++slSpan :: Bias -> (a -> Bool) -> StepList a -> ([a], StepList a)+slSpan bias p = biasedApply bias (span p)++slBreak :: Bias -> (a -> Bool) -> StepList a -> ([a], StepList a)+slBreak bias p = biasedApply bias (break p)++-- | Apply a modifier to the whole list, try to keep the cursor in the same place. If the 'StepList'+-- shrinks below where the cursor was, the cursor is placed at the end of the 'StepList'.+slMapAll :: ([a] -> [b]) -> StepList a -> StepList b+slMapAll fn (StepList cur _ left right) =+ let (left', right') = splitAt cur (fn $ reverse left ++ right)+ newlen = length left' + length right'+ in StepList (min cur newlen) newlen (reverse left') right'+
+ src/Dao/String.hs view
@@ -0,0 +1,521 @@+-- "src/Dao/String.hs" provides the fundamental string data type+-- called "UStr" which is used throughout the Dao System.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.+++{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}++-- | This module has two purposes. Firstly, this module depends on no other module in the Dao+-- program, so it may be imported by any other module, and as such it provides the classes and+-- functions that must be available to every module. Most of these essential functions are related+-- to strings, which is why this module is named so.+--+-- Therefore secondly, this module provides the /universal string/ data type 'UStr', and a type+-- class 'UStrType' that allows you to declare arbitrary data types to be convertible to and from+-- universal strings. Universal strings are built upon the "Data.ByteString.Lazy.UTF8" module in+-- the @utf8-string@ package of the Haskell platform. All strings used in the Dao runtime are stored+-- as this data type.+--+-- /NOTE:/ though this module is absolutely essential to every other module in the Dao system, not+-- all data structures should need to instantiate 'UStrType'. By contrst, the+-- 'Dao.Interpreter.Structured' (not defined in this module) should be instantiated by nearly all+-- data structures, especially if it is necessary to manipulate these structures within the Dao+-- programming language.+--+-- Dao is a high-level language, like a macro language or a scripting language. One thing+-- scripting/meta languages all have in common is the use of strings as a way to store and transmit+-- data in a human-readable but structured format; strings are often the universal intermediate code+-- of the runtime environment. Data structures can be converted to a string, stored in memory,+-- transmitted over a socket or pipe, saved to disk. Data from the disk, the socket, or in memory+-- can be parsed to reconstruct the data structures.+--+-- However it is my opinion that use of strings as intermediate data structures is very poor design+-- in any programming language; it is an anti-pattern. I believe the universal data type should the+-- tree rather than the string. Therefore I have provided the "Dao.Tree" and "Dao.Struct" modules,+-- and the 'Dao.Interpreter.Structured' type class which expand on the ideas of 'Prelude.Show' and+-- 'Prelude.Read' by using a 'Dao.Tree.Tree' as the intermediate data structure, rather than a+-- 'Dao.String.UStr'.+module Dao.String where++import Control.Monad+import Control.Monad.State+import Control.DeepSeq+import Control.Exception (assert)++import Data.String+import Data.Monoid+import Data.Typeable+import qualified Data.Binary as B+import Data.Bits+import Data.Char+import Data.List (partition, stripPrefix)+import Data.Word+import Data.Array.Unboxed+import qualified Data.ByteString.Lazy.UTF8 as U+import qualified Data.ByteString.Lazy as B+import qualified Codec.Binary.UTF8.String as UTF8++import Numeric++-- Necessary for the HasNullValue instances+import Data.Int+import Data.Ratio+import Data.Complex+import Data.Time.Clock+import qualified Data.IntMap as IM+import qualified Data.Map as M+import qualified Data.Set as S++-- | Objects which can be used as a predicate testing whether or not the object is null, or of a+-- default value, should instantiate this class.+class HasNullValue a where { nullValue :: a; testNull :: a -> Bool; }+instance HasNullValue () where { nullValue = (); testNull () = True; }+instance HasNullValue UStr where { nullValue = mempty; testNull = (==mempty); }+instance HasNullValue [a] where { nullValue = []; testNull = null; }+instance HasNullValue Char where { nullValue = '\0'; testNull = (==nullValue); }+instance HasNullValue Int where { nullValue = 0; testNull = (==nullValue); }+instance HasNullValue Int64 where { nullValue = 0; testNull = (==nullValue); }+instance HasNullValue Word where { nullValue = 0; testNull = (==nullValue); }+instance HasNullValue Word64 where { nullValue = 0; testNull = (==nullValue); }+instance HasNullValue Double where { nullValue = 0; testNull = (==nullValue); }+instance HasNullValue Integer where { nullValue = 0; testNull = (==nullValue); }+instance HasNullValue (Ratio Integer) where { nullValue = 0%1; testNull = (==nullValue); }+instance HasNullValue (Complex Double) where { nullValue = 0:+0; testNull = (==nullValue); }+instance HasNullValue NominalDiffTime where { nullValue = fromRational 0; testNull = (==nullValue); }+instance HasNullValue (IM.IntMap a) where { nullValue = IM.empty; testNull = IM.null }+instance HasNullValue (M.Map k a) where { nullValue = M.empty; testNull = M.null }+instance HasNullValue (S.Set a) where { nullValue = S.empty; testNull = S.null }+instance HasNullValue U.ByteString where { nullValue = mempty; testNull = (==mempty); }++-- | This is the /universal string/ type. It is a @newtype@ wrapper around+-- 'Data.ByteString.Lazy.UTF8.ByteString', but has an API that is used throughout the Dao system.+-- There is serious consideration to replace this module with "Data.Text", but even if that happens,+-- this module will be kept to provide a stable API to the string package upon which it is built.+newtype UStr = UStr { toUTF8ByteString :: U.ByteString } deriving (Eq, Ord, Typeable)+instance Monoid UStr where { mempty = toUStr ""; mappend a b = toUStr (uchars a ++ uchars b); }++-- | To provide intermediate string representations of data structures is one of the purposes of+-- 'Prelude.Show' and 'Prelude.Read' in the Haskell language. In Haskell, 'Prelude.Read' and+-- 'Prelude.Show' must, by convention, output a string that can be converted back to an exactly+-- equivalent data structure to the structure that produced the output when the output string is+-- parsed by 'Prelude.Read'. In other words @read (show a) == a && show (read a) == a@ should+-- evaluate to 'Prelude.True' for any type of @a@.+-- +-- This is not merely a convention for the 'UStrType', it is a requirement. The minimal complete+-- definition is 'toUStr' and one or both of 'fromUStr' and 'maybeFromUStr'. The 'nil' function is+-- part of the minimal complete definition, except when your data type is also an instance of+-- 'Data.Monoid.Monoid'. If your data type is also a 'Data.Monoid.Monoid', then the default instance+-- of 'nil' is 'Data.monoid.mempty'.+-- +-- Another big difference between 'UStrType' and 'Prelude.Show'/'Prelude.Read' is that 'UStrType' is+-- not intented to be used to construct parsers, it is used as an abstract interface to a parser.+-- 'Prelude.Read' provides 'Prelude.lex' for taking a lexeme from the head of the input,+-- 'Prelude.readParen' for parsing items from within parentheses, and 'readsPrec' which+-- parameterizes the current precedence value and allows you to backtrack if a lexeme has a lower+-- prescedence. All of this functionality (and more) is provided in the "Dao.Parser"+-- module, it is not provided here in the 'UStrType'.+-- +-- When instantiating this class, you will may find the 'uchars' and 'ustr' to be useful if parsing+-- strings is necessary. If you want to use 'Prelude.Show' to instantiate 'toUStr', you can simply+-- use 'derive_toUStr' and 'derive_fromUStr'. The 'uchars' function is used to convert any+-- 'UStrType' to a 'Prelude.String' by first converting the 'UStrType' to a 'UStr', and 'ustr' is+-- does the inverse, however 'UStr' also instantiates 'UStrType' /so the way to convert a 'UStr' to+-- a 'Prelude.String' is to use 'uchars', the way to convert a 'Prelude.String' to a 'UStr' is to+-- use 'ustr'/. +class UStrType a where+ -- | Like 'Prelude.Show.show', converts your data type to a universal string.+ toUStr :: a -> UStr+ -- | Like 'Prelude.read', constructs your data type from a universal string.+ fromUStr :: UStr -> a+ fromUStr str = maybe (error ("cannot construct data from UStr "++show str)) id (maybeFromUStr str)+ -- | Like 'Prelude.reads' except the entire string must be consumed, and the return type is a+ -- 'Prelude.Maybe' instead of a list. The return type here is not similar to+ -- 'Prelude.ReadS' which is a synonym for @'Prelude.String' -> [(a, 'Prelude.String')]@ a pair+ -- containing the read object and the remainder.+ maybeFromUStr :: UStr -> Maybe a+ maybeFromUStr = Just . fromUStr+ nil :: a+ nil = fromUStr mempty+instance UStrType UStr where { toUStr = id; fromUStr = id; }+instance UStrType String where+ toUStr = UStr . U.fromString+ fromUStr = U.toString . toUTF8ByteString+instance UStrType U.ByteString where+ toUStr = UStr+ fromUStr = toUTF8ByteString+ maybeFromUStr = Just . fromUStr++-- | This function lets you use the instantiation of 'Prelude.Show' to instantiate 'toUStr',+-- typically used when your data type uses Haskell's @deriving@ keyword to derive 'Prelude.Show'.+-- Note that this function also requires you to instantiate 'Prelude.Read' (also, perhaps, by the+-- @deriving@ keyword), because although this function does not use any of the 'Prelude.Read'+-- functions, this requirement emphasizes the importance of 'UStr' being a data structure that is+-- used to store an intermediate representation of structured data.+derive_ustr :: (Enum a, Read a, Show a) => a -> UStr+derive_ustr = toUStr . show++-- | This function lets you use the instantiation of 'Prelude.Read' to instantiate 'toUStr',+-- typically used when your data type uses Haskell's @deriving@ keyword to derive 'Prelude.Read'.+-- Note that this function also requires you to instantiate 'Prelude.Show' (also, perhaps, by the+-- @deriving@ keyword), because although this function does not use any of the 'Prelude.Show'+-- functions, this requirement emphasizes the importance of 'UStr' being a data structure that is+-- used to store an intermediate representation of structured data.+derive_fromUStr :: (Enum a, Read a, Show a) => UStr -> a+derive_fromUStr = read . uchars++-- | This function lets you use the instantiation of 'Prelude.Read' to instantiate 'toUStr',+-- typically used when your data type uses Haskell's @deriving@ keyword to derive 'Prelude.Read'.+-- Note that this function also requires you to instantiate 'Prelude.Show' (also, perhaps, by the+-- @deriving@ keyword), because although this function does not use any of the 'Prelude.Show'+-- functions, this requirement emphasizes the importance of 'UStr' being a data structure that is+-- used to store an intermediate representation of structured data.+derive_maybeFromUStr :: (Enum a, Read a, Show a) => UStr -> Maybe a+derive_maybeFromUStr u = case reads (uchars u) of+ [(a, "")] -> Just a+ _ -> Nothing++-- | Convert a 'Prelude.String' to an object classed as a 'UStrType' by first converting it to a+-- 'UStr' using 'toUStr'. /NOTE:/ this is the function you use to convert a 'Prelude.String' to a+-- 'UStr', and for the 'UStr' type, this function never fails (never evaluates to the "bottom"+-- value).+ustr :: UStrType str => String -> str+ustr = fromUStr . toUStr++-- | Convert a 'Prelude.String' to an object classed as a 'UStrType' by first converting it to a+-- 'UStr' using 'toUStr', but uses 'maybeFromUStr' to convert from the 'UStrType' object. /NOTE:/+-- this is the function you use to convert a 'Prelude.String' to a 'UStr' (this is possible because+-- 'UStr' instantiates 'UStrType'), and for the 'UStr' type, this function never evaluates to+-- 'Prelude.Nothing'.+maybeUStr :: UStrType str => String -> Maybe str+maybeUStr = maybeFromUStr . toUStr++-- | Convert an object classed as a 'UStrType' to a 'Prelude.String'. /NOTE:/ this is the function+-- you should use to convert a 'UStr' to a 'Prelude.String' (this is possible because 'UStr'+-- instantiates 'UStrType').+uchars :: UStrType str => str -> String+uchars = U.toString . toUTF8ByteString . toUStr++-- | Convert an object classed as a 'UStrType' to a @['Data.Word.Word8']@ list. Since 'UStr's store+-- data as UTF-8 encoded strings, this function simply returns the UTF-8 formatted octet stream from+-- that the 'Data.ByteString.Lazy.UTF8.ByteString' data structure. Of course, unless your 'UStrType'+-- is simply a @newtype@ of 'UStr' a conversion to a 'UStr' is done behind the scenes, which will+-- transparently encode a UTF8 string.+utf8bytes :: UStrType str => str -> [Word8]+utf8bytes = UTF8.encode . uchars . toUStr++-- | The inverse of 'utf8bytes', tries to decode a stream of octets into a properly formatted UTF-8+-- 'Data.ByteString.Lazy.UTF8.ByteString'. If encoding fails, this function evaluates to+-- 'Prelude.error' (evaluates to the "bottom" value).+upack :: [Word8] -> UStr+upack ax = toUStr (UTF8.decode ax)++-- | Modify a 'UStr' with the APIs provided in the "Data.ByteString.UTF8.Lazy" module.+fmapUTF8String :: (U.ByteString -> U.ByteString) -> UStr -> UStr+fmapUTF8String f = UStr . f . toUTF8ByteString++-- | Split a longer string up by the shorter string, for example:+-- > splitString (ustr "--") (one-one -- two-two -----> three-three -- four-four")+-- will be split to+-- > ["one-one ", " two-two ", "", "-> three-three ", " four-four"]+splitString :: UStr -> UStr -> [UStr]+splitString a b = case compare la lb of+ EQ -> return $ if a==b then nil else a+ LT -> delstr a b+ GT -> delstr b a+ where+ la = ulength a+ lb = ulength b+ len = min la lb+ loop ox str i ax bx =+ if i>0+ then case stripPrefix ax bx of+ Nothing -> loop ox (head bx : str) (i-1) ax (tail bx)+ Just bx -> loop (ustr (reverse str) : ox) "" (i-len) ax bx+ else reverse $ ustr (reverse str ++ bx) : ox+ delstr a b = loop [] "" (abs $ lb-la) (uchars a) (uchars b)++----------------------------------------------------------------------------------------------------++-- | A Variable-Length Integer (VLI) encoder. The bits of a variable-length integer will have a+-- format like so:+-- > bit column number: 7 6543210+-- > ---------+-- > 1st highest order byte: 1 XXXXXXX+-- > 2nd highest order byte: 1 XXXXXXX+-- > 3rd highest order byte: 1 XXXXXXX+-- > ...+-- > lowest order byte : 0 XXXXXXX+-- If the highest-order bit is a one, it indicates there are more bytes to follow. If the highest+-- order bit is 0, then there are no more bytes. The 7 lower-order bits will be concatenated in+-- /big-endian order/ to form the length value for the string. By this method, most all strings+-- will have a length prefix of only one or two bytes.+vlIntegralToWord8s :: (Integral a, Bits a) => a -> [Word8]+vlIntegralToWord8s = reverse . (\ (a:ax) -> (a .&. 0x7F) : ax) .+ fix (\loop w -> let v = 0x80 .|. fromIntegral (w .&. 0x7F)+ in case shiftR w 7 of{ 0 -> [v]; w -> v : loop w; })++-- | Inverse operation of 'bitsToVLI'+vlWord8sToIntegral :: (Integral a, Bits a) => [Word8] -> (a, [Word8])+vlWord8sToIntegral = loop 0 where+ fn a w = shiftL a 7 .|. fromIntegral (w .&. 0x7F) + loop a wx = case wx of+ [] -> (a, [])+ w:wx -> if w .&. 0x80 == 0 then (fn a w, wx) else loop (fn a w) wx++-- | Since a negative number expressed in a 'Prelude.Integer' type translates to an infinite+-- sequence of 0xFF bytes when converting it to a VLI, it needs to be encoded specially with a+-- negation bit in the very first position.+vlIntegerToWord8s :: Integer -> [Word8]+vlIntegerToWord8s w = reverse $ (\ (b:bx) -> (if w<0 then b .|. 0x40 else b):bx) $ loop (abs w) where+ loop w = fromInteger (w .&. 0x3F) :+ fix (\loop w -> case w of+ 0 -> []+ w -> (0x80 .|. fromInteger (w .&. 0x7F)) : loop (shiftR w 7)+ ) (shiftR w 6)++vlWord8sToInteger :: [Word8] -> (Integer, [Word8])+vlWord8sToInteger = loop 0 where+ fn s m a w = shiftL a s .|. fromIntegral (w .&. m)+ loop a wx = case wx of+ [] -> (a, [])+ w:wx ->+ if w .&. 0x80 == 0+ then ((if w .&. 0x40 == 0 then id else negate) $ fn 6 0x3F a w, wx)+ else loop (fn 7 0x7F a w) wx++-- | When reading from a binary file, gather the bits of a Variable-Length Integer.+vlGatherWord8s :: B.Get [Word8]+vlGatherWord8s = loop [] where+ loop wx = B.getWord8 >>= \w -> if w .&. 0x80 == 0 then return (wx++[w]) else loop (wx++[w])++-- | Encode only positive 'Prelude.Integer's. This differs from 'vlPutInteger' in that the sign of+-- the integer is not stored in the byte stream, saving a single bit of space. This can actually+-- simplify some equations that expect an VLInteger to be encoded as a multiple-of-7 length string+-- of bits as you don't need to make additional rules for the final byte which would only have+-- 6-bits if the sign is stored with it.+vlPutPosInteger :: Integer -> B.Put+vlPutPosInteger i = assert (i>=0) $ mapM_ B.putWord8 $ vlIntegralToWord8s $ i++-- | Decode only positive 'Prelude.Integer's. This differs from 'vlPutInteger' in that the sign of+-- the integer is not stored in the byte stream, saving a single bit of space. This can actually+-- simplify some equations that expect an VLInteger to be encoded as a multiple-of-7 length string+-- of bits as you don't need to make additional rules for the final byte which only have 6-bits if+-- the sign is stored with it.+vlGetPosInteger :: B.Get Integer+vlGetPosInteger = fmap (fst . vlWord8sToIntegral) vlGatherWord8s++-- | Encode a positive or negative 'Prelude.Integer' using 'vlWord8sToInteger'. The sign of the integer+-- is stored in the final byte in the list of encoded bytes, so the final encoded byte only has 6+-- bits of information, rather than 7 in the case of positive integers.+vlPutInteger :: Integer -> B.Put+vlPutInteger = mapM_ B.putWord8 . vlIntegerToWord8s++-- | Decode a positive or negative 'Prelude.Integer' using 'vlWord8sToInteger'. The sign of the integer+-- is stored in the final byte in the list of encoded bytes, so the final encoded byte only has 6+-- bits of information, rather than 7 in the case of positive integers.+vlGetInteger :: B.Get Integer+vlGetInteger = fmap (fst . vlWord8sToInteger) vlGatherWord8s++-- | Return the length of the 'UStr'.+ulength :: UStr -> Int+ulength = U.length . toUTF8ByteString++-- | Length of a list, but unlike 'Data.List.length', allows a polymorphic length type.+iLength :: Num len => [a] -> len+iLength = foldl (+) 0 . map (const 1)++-- | Used to encode a 'UStr' data type without any prefix at all. The instantiation of 'UStr' into+-- the 'Data.Binary.Binary' class places a prefix before every 'UStr' as it is serialized, allowing+-- it to be used more safely in more complex data types.+encodeUStr :: UStr -> B.Put+encodeUStr u = mapM_ B.putWord8 $+ vlIntegralToWord8s (U.length (toUTF8ByteString u)) ++ (UTF8.encode (uchars u))++-- | Used to decode a 'UStr' data type without any prefix. The instantiation of 'UStr' into the+-- 'Data.Binary.Binary' class places a prefix before every 'UStr' as it is serialized, allowing it+-- to be used more safely in more complex data types.+decodeUStr :: B.Get UStr+decodeUStr = do+ (strlen, undecoded) <- fmap vlWord8sToIntegral vlGatherWord8s+ if null undecoded+ then fmap (toUStr . (UTF8.decode)) (replicateM strlen B.getWord8)+ else fail "binary data decoder failed on UStr"++----------------------------------------------------------------------------------------------------++bytesBitArith :: (Word8 -> Word8 -> Word8) -> B.ByteString -> B.ByteString -> B.ByteString+bytesBitArith f a b = B.pack $ map (uncurry f) $ B.zip a b++bytesShift :: B.ByteString -> Int64 -> B.ByteString+bytesShift str i = let (len, r) = fmap fromIntegral (divMod (abs i) 8) in case compare i 0 of+ EQ -> str -- identity+ LT -> mappend (B.replicate len 0) $ if r==0 then str else -- shift right+ snd $ B.mapAccumL (\prev b -> (b, shiftR b r .|. rotateR ((2^r-1) .&. prev) r)) 0 str+ GT -> -- shift left+ (if r==0 then id else snd . B.mapAccumR (\prev b -> (b, shiftL b r .|. ((2^r-1) .&. prev))) 0+ ) (B.drop len str)++bytesRotate :: B.ByteString -> Int64 -> B.ByteString+bytesRotate str i =+ let { len = B.length str; (cur8, r) = fmap fromIntegral (divMod i (len*8)); cur = div cur8 8; }+ in if r==0 then str else let { (a, b) = B.splitAt cur str; str = b<>a; } in+ snd $ B.mapAccumL (\prev b -> (b, shiftL b r .|. ((2^r-1) .&. prev))) (B.last str) str++bytesBit :: Int64 -> B.ByteString+bytesBit i = let (len, r) = fmap fromIntegral (divMod i 8) in+ if r==0 then B.snoc (B.replicate len 0) 1 else B.snoc (B.replicate (len-1) 0) (bit r)++bytesTestBit :: B.ByteString -> Int64 -> Bool+bytesTestBit str i = let (len, r) = fmap fromIntegral (divMod i 8) in testBit (B.index str len) r++bytesBitSize :: B.ByteString -> Int64+bytesBitSize = (8*) . B.length++bytesPopCount :: B.ByteString -> Int64+bytesPopCount = B.foldl (\count b -> count + fromIntegral (popCount b)) 0++----------------------------------------------------------------------------------------------------++instance IsString UStr where { fromString = ustr }+instance Read UStr where { readsPrec n str = map (\ (s, rem) -> (toUStr (s::String), rem)) $ readsPrec n str }+instance Show UStr where { show u = show (uchars u) }+instance B.Binary UStr where+ put u = encodeUStr u+ get = decodeUStr+instance NFData UStr where { rnf (UStr a) = deepseq a () }++-- | A type synonym for 'UStr' used where a string is used as some kind of identifier.+newtype Name = Name { nameToUStr :: UStr } deriving (Eq, Ord, Typeable)+instance Monoid Name where { mempty = nil; mappend (Name a) (Name b) = Name (a<>b); }+instance Show Name where { show = show . nameToUStr }+instance UStrType Name where+ toUStr = nameToUStr+ maybeFromUStr nm = + let str = uchars nm+ ck f c = c=='_' || f c+ in case str of + c:cx | ck isAlpha c || and (fmap (ck isAlphaNum) cx) -> Just (Name nm)+ _ -> Nothing+ fromUStr str = maybe (error msg) id $ maybeFromUStr str where+ msg = "'Dao.String.Name' object must be constructed from alpha-numeric and underscore characters only:\n"+ ++ take 256 (uchars str)+instance IsString Name where { fromString = ustr }+instance B.Binary Name where+ put (Name u) = encodeUStr u+ get = decodeUStr >>= \u -> case maybeFromUStr u of+ Just nm -> return nm+ Nothing -> fail "binary data contains invalid 'Dao.String.Name' object"+instance NFData Name where { rnf (Name a) = deepseq a () }++-- | A type synonym for 'UStr' used where a string is storing a file path or URL.+type UPath = UStr++----------------------------------------------------------------------------------------------------++-- | Breaks a long list into a list of lists no longer than the specified length.+breakInto :: Int -> [a] -> [[a]]+breakInto i bx = if null bx then [] else let (grp, bx') = splitAt i bx in grp : breakInto i bx'++-- | An array mapping 6-bit values to base-64 character symbols+base64Symbols :: UArray Word Char+base64Symbols = listArray (0,63) (['A'..'Z']++['a'..'z']++['0'..'9']++"+/")++-- | Encoding arbitrary bytes in a 'Data.ByteString.Lazy.ByteString' to base-64 character symbols+-- according to RFC 3548.+b64Encode :: B.ByteString -> [[Char]]+b64Encode = breakInto 76 . concatMap enc . breakInto 3 . B.unpack where+ windows = [(0xFC0000, 18), (0x03F000, 12), (0x000FC0, 6), (0x00003F, 0)]+ enc [] = []+ enc bx =+ let len = length bx+ buf = foldl (\buf b -> shiftL buf 8 .|. fromIntegral b) 0 (take 3 (bx++replicate (3-len) 0))+ in take 4 $ (++"==") $ take (len+1) $ flip map windows $ \ (mask, shft) ->+ base64Symbols ! shiftR (mask.&.buf) shft++-- | An array mapping base-64 character symbols to their 6-bit values.+base64Values :: UArray Char Int+base64Values = array ('+', 'z') $ concat $+ [ zip ['+', 'z'] (repeat 0xAAAAAAA) -- 0xAAAAAAA is the undefined value+ , zip ['A'..'Z'] [0..25]+ , zip ['a'..'z'] [26..51]+ , zip ['0'..'9'] [52..61]+ , [('+', 62), ('/', 63), ('=', 0xFFFFFFF)] -- 0xFFFFFFF is the end-of-input value+ ]++-- | Decoding base-64 character symbols according to RFC 3548 into a string of bytes stored in a+-- 'Data.ByteString.Lazy.ByteString'. If decoding fails, the invalid character and it's position in+-- the input string are returned as a pair in a 'Data.Either.Left' value, otherwise the+-- 'Data.ByteString.Lazy.ByteString' is returned as the 'Data.Either.Right' value.+b64Decode :: [Char] -> Either (Char, Word64) B.ByteString+b64Decode = loop 0 [] . breakInto 4 . filter (flip notElem " \t\r\n\v\f\0") where+ loop i bx cxx = case cxx of+ [] -> Right (B.pack bx)+ cx:cxx -> case sum 0 0 i cx of+ Left (c, i) -> Left (c, i)+ Right (i, bx') -> loop i (bx++bx') cxx+ sum tk b i cx = case cx of+ [] -> Right (i, take (3-tk) (splitup b))+ c:cx -> if inRange (bounds base64Values) c+ then case base64Values!c of+ 0xAAAAAAA -> Left (c, i)+ 0xFFFFFFF -> sum (tk+1) (shiftL b 6) (i+1) cx+ c -> sum tk (shiftL b 6 .|. c) (i+1) cx+ else Left (c, i)+ splitup b = map fromIntegral [shiftR (b.&.0xFF0000) 16, shiftR (b.&.0xFF00) 8, b.&.0xFF]++newtype Base64String = Base64String B.ByteString deriving Typeable+instance Show Base64String where { show (Base64String s) = unlines (b64Encode s) }+instance Read Base64String where+ readsPrec _ str =+ case partition (\c -> isSpace c || (inRange (bounds base64Values) c && base64Values!c /= 0xAAAAAAA)) str of+ ("" , _ ) -> []+ (str, rem) -> case b64Decode $ filter (not . isSpace) str of+ Left (ch, pos) -> error ("invalid charcter "++show ch++" at index "++show pos++" in base64-encoded string")+ Right u -> [(Base64String u, rem)]+newtype Base16String = Base16String B.ByteString deriving Typeable+instance Show Base16String where+ show (Base16String u) = unlines $ fmap (unwords . fmap hex) $ breakInto 32 (B.unpack u) where+ hex b = [arr ! (shiftR (b.&.0xF0) 4), arr ! (b.&.0x0F)]+ arr :: Array Word8 Char+ arr = array (0,15) (zip [0..15] "0123456789ABCDEF")++showHex :: (Show i, Integral i) => i -> String+showHex = ("0x"++) . map toUpper . flip Numeric.showHex ""++showOrdinal :: (Show n, Integral n) => n -> String+showOrdinal n = show n ++ case mod n 100 of+ n | 11<=n||n<=13 -> "th"+ n -> case mod n 10 of { 1 -> "st"; 2 -> "nd"; 3 -> "rd"; _ -> "th"; }++----------------------------------------------------------------------------------------------------++-- | This is a simlpe string tokenizer for breaking up strings into tokens that can be easily used+-- in rules in Doa scripts.+simpleTokenizer :: String -> [String]+simpleTokenizer = fix $ \loop cx -> case cx of+ "" -> []+ c:cx -> maybe ([c] : loop cx) (\ (cx, rem) -> cx : loop rem) $+ foldl (\f split -> mplus f (split (c:cx))) Nothing $ concat $+ [ fmap (\predicate (c:cx) -> guard (predicate c) >> Just (span predicate (c:cx))) $+ [isSpace, isNumber, isAlpha]+ , [\ (c:cx) -> guard (elem c "([{<>}])") >> Just ([c], cx)]+ , [\ (c:cx) -> Just $ span (==c) (c:cx)]+ ]+
+ src/Dao/Token.hs view
@@ -0,0 +1,400 @@+-- "src/Dao/Token.hs" Defines the 'Token' and 'Location' types+-- used by "src/Dao/Interpreter.hs" and "src/Dao/Parser.hs".+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++{-# LANGUAGE DeriveDataTypeable #-}++module Dao.Token where++import Dao.String+import Dao.PPrint++import Data.Monoid+import Data.Word+import Data.List (intercalate)+import Data.Typeable+import Data.Data++import Control.Monad+import Control.DeepSeq++----------------------------------------------------------------------------------------------------++type LineNum = Word+type ColumnNum = Word+type TabWidth = Word++-- | If an object contains a location, it can instantiate this class to allow locations to be+-- updated or deleted (deleted by converting it to 'LocationUnknown'. Only three types in this+-- module instantiate this class, but any data type that makes up an Abstract Syntax Tree, for+-- example 'Dao.Interpreter.ObjectExpr' or 'Dao.Interpreter.AST.ObjectExrpr' also instantiate this class.+class HasLocation a where+ getLocation :: a -> Location+ setLocation :: a -> Location -> a+ delLocation :: a -> a++instance HasLocation () where+ { getLocation _ = LocationUnknown; setLocation a _ = a; delLocation a = a; }++instance HasLocation UStr where+ { getLocation _ = LocationUnknown; setLocation a _ = a; delLocation a = a; }++instance HasLocation Name where+ { getLocation _ = LocationUnknown; setLocation a _ = a; delLocation a = a; }++instance HasLocation a => HasLocation (Maybe a) where+ getLocation = maybe LocationUnknown getLocation+ setLocation o loc = fmap (flip setLocation loc) o+ delLocation o = fmap delLocation o++-- | Contains two points, a starting and and ending point, where each point consists of a row (line+-- number) and column (character count from the beginning of a line) for locating entities in a+-- parsable text. This type does not contain information regarding the source of the text, or+-- whether or not the input text is a file or stream.+data Location+ = LocationUnknown+ | Location+ { startingLine :: LineNum+ -- ^ the 'Location' but without the starting/ending character count+ , startingColumn :: ColumnNum+ , endingLine :: LineNum+ , endingColumn :: ColumnNum+ }+ deriving (Eq, Typeable, Data)++instance HasLocation Location where+ getLocation = id+ setLocation = flip const+ delLocation = const LocationUnknown++instance Show Location where+ show t = case t of+ LocationUnknown -> ""+ Location a b _ _ -> show a ++ ':' : show b++instance Monoid Location where+ mempty = LocationUnknown+-- Location+-- { startingLine = 0+-- , startingColumn = 0+-- , endingLine = 0+-- , endingColumn = 0+-- }+ mappend loc a = case loc of+ LocationUnknown -> a+ _ -> case a of+ LocationUnknown -> loc+ _ ->+ loc+ { startingLine = min (startingLine loc) (startingLine a)+ , startingColumn = min (startingColumn loc) (startingColumn a)+ , endingLine = max (endingLine loc) (endingLine a)+ , endingColumn = max (endingColumn loc) (endingColumn a)+ }+instance Ord Location where+ compare a b = case (a,b) of+ (LocationUnknown, LocationUnknown) -> EQ+ (_ , LocationUnknown) -> LT+ (LocationUnknown, _ ) -> GT+ _ -> compare (abs(ela-sla), abs(eca-sca), sla, sca) (abs(elb-slb), abs(ecb-scb), slb, scb)+ where+ sla = startingLine a+ ela = endingLine a+ slb = startingLine b+ elb = endingLine b+ sca = startingColumn a+ eca = endingColumn a+ scb = startingColumn b+ ecb = endingColumn b+ -- ^ Greater-than is determined by a heuristic value of how large and uncertain the position of+ -- the error is. If the exact location is known, it has the lowest uncertainty and is therefore+ -- less than a location that might occur across two lines. The 'LocationUnknown' value is the most+ -- uncertain and is greater than everything except itself. Using this comparison function, you can+ -- sort lists of locations from least to greatest and hopefully get the most helpful, most+ -- specific location at the top of the list.++instance HasNullValue Location where+ nullValue = LocationUnknown+ testNull LocationUnknown = True+ testNull _ = False++instance PPrintable Location where+ pPrint o = case o of+ LocationUnknown -> pString "srcLoc()"+ Location a b c d -> pList (pString "srcLoc") "(" ", " ")" [pShow a, pShow b, pShow c, pShow d]++instance NFData Location where+ rnf LocationUnknown = ()+ rnf (Location a b c d) = deepseq a $! deepseq b $! deepseq c $! deepseq d ()++-- | Create a location where the starting and ending point is the same row and column.+atPoint :: LineNum -> ColumnNum -> Location+atPoint a b =+ Location+ { startingLine = a+ , endingLine = a+ , startingColumn = b+ , endingColumn = b+ }++-- | The the coordinates from a 'Location':+-- @(('startingLine', 'startingColumn'), ('endingLine', 'endingColumn'))@+locationCoords :: Location -> Maybe ((LineNum, ColumnNum), (LineNum, ColumnNum))+locationCoords loc = case loc of+ LocationUnknown -> Nothing+ _ -> Just ((startingLine loc, startingColumn loc), (endingLine loc, endingColumn loc))++----------------------------------------------------------------------------------------------------+-- $All_about_tokens+-- This module was designed to create parsers which operate in two phases: a lexical analysis phase+-- (see 'lexicalAnalysis') where input text is split up into tokens, and a syntactic analysis phase+-- where a stream of tokens is converted into data. 'Token' is the data type that makes this+-- possible.++class HasLineNumber a where { lineNumber :: a -> LineNum }+class HasColumnNumber a where { columnNumber :: a -> ColumnNum }+class HasToken a where { getToken :: a tok -> Token tok }++-- | Every token emitted by a lexical analyzer must have at least a type. 'Token' is polymorphic+-- over the type of token.+data Token tok+ = EmptyToken { tokType :: tok }+ -- ^ Often times, tokens may not need to contain any text. This is often true of opreator+ -- symbols and keywords. This constructor constructs a token with just a type and no text. The+ -- more descriptive your token types are, the less you need you will have for storing the text+ -- along with the token type, and the more memory you will save.+ | CharToken { tokType :: tok, tokChar :: !Char }+ -- ^ Constructs tokens along with the text. If the text is only a single character, this+ -- constructor is used, which can save a little memory as compared to storing a+ -- 'Dao.String.UStr'.+ | Token { tokType :: tok, tokUStr :: UStr }+ -- ^ Constructs tokens that contain a copy of the text extracted by the lexical analyzer to+ -- create the token.+ deriving (Eq, Typeable)++instance Show tok => Show (Token tok) where+ show tok =+ let cont = tokToStr tok+ in show (tokType tok) ++ (if null cont then "" else ' ':show cont)++instance Functor Token where+ fmap f t = case t of+ EmptyToken t -> EmptyToken (f t)+ CharToken t c -> CharToken (f t) c+ Token t u -> Token (f t) u++-- | If the lexical analyzer emitted a token with a copy of the text used to create it, this+-- function can retrieve that text. Returns 'Dao.String.nil' if there is no text.+tokToUStr :: Token tok -> UStr+tokToUStr tok = case tok of+ EmptyToken _ -> nil+ CharToken _ c -> ustr [c]+ Token _ u -> u++-- | Like 'tokToUStr' but returns a 'Prelude.String' or @""@ instead.+tokToStr :: Token tok -> String+tokToStr tok = case tok of+ EmptyToken _ -> ""+ CharToken _ c -> [c]+ Token _ u -> uchars u++-- | This data type stores the starting point (the line number and column number) in the+-- source file of where the token was emitted along with the 'Token' itself.+data TokenAt tok+ = TokenAt+ { tokenAtLineNumber :: LineNum+ , tokenAtColumnNumber :: ColumnNum+ , getTokenValue :: Token tok+ }++instance Show tok =>+ Show (TokenAt tok) where+ show tok = let (a,b,c) = asTriple tok in show a++':':show b++' ':show c++instance HasLineNumber (TokenAt tok) where { lineNumber = tokenAtLineNumber }++instance HasColumnNumber (TokenAt tok) where { columnNumber = tokenAtColumnNumber }++instance HasToken TokenAt where { getToken = getTokenValue }++instance Functor TokenAt where+ fmap f t =+ TokenAt+ { tokenAtLineNumber = tokenAtLineNumber t+ , tokenAtColumnNumber = tokenAtColumnNumber t+ , getTokenValue = fmap f (getToken t)+ }++-- | Extract the type of the token.+asTokType :: TokenAt tok -> tok+asTokType = tokType . getToken++-- | Extract the string value stored in this token. /WARNING:/ keyword and operator tokens contain+-- no strings to save memory, so evaluating this function on any token type defind by+-- 'Dao.Parser.operator', 'Dao.Parser.operatorTable', or 'Dao.Parser.keyword' will result in a null+-- sring.+asString :: TokenAt tok -> String+asString = tokToStr . getToken++-- | Extract the string value stored in this token. /WARNING:/ keyword and operator tokens contain+-- no strings to save memory, so evaluating this function on any token type defind by+-- 'Dao.Parser.operator', 'Dao.Parser.operatorTable', or 'Dao.Parser.keyword' will result in a null+-- sring.+asUStr :: TokenAt tok -> UStr+asUStr = tokToUStr . getToken++-- | Extract the string value stored in this token. /WARNING:/ keyword and operator tokens contain+-- no strings to save memory, so evaluating this function on any token type defind by+-- 'Dao.Parser.operator', 'Dao.Parser.operatorTable', or 'Dao.Parser.keyword' will result in a null+-- sring.+asName :: TokenAt tok -> Name+asName = fromUStr . asUStr++-- | That is as-zero, because "0" looks kind of like "()".+-- This function is useful when it is necessary to pass a function argument to functions like+-- 'Dao.Parser.token' and 'Dao.Parser.tokenBy' but you want to ignore the token returned.+as0 :: TokenAt tok -> ()+as0 = const ()++-- | Retrieve the token part of a 'TokenAt' object. +asToken :: TokenAt tok -> Token tok+asToken = getToken++-- | Synonym for 'Prelude.id', used when it is necessary to pass a function argument to functions like+-- 'Dao.Parser.token' and 'Dao.Parser.tokenBy' but you just want the whole 'TokenAt' object.+asTokenAt :: TokenAt tok -> TokenAt tok+asTokenAt = id++-- | Convert the contents of a 'TokenAt' object to a tripple containg it's component parts.+asTriple :: TokenAt tok -> (LineNum, ColumnNum, Token tok)+asTriple tok = (lineNumber tok, columnNumber tok, getToken tok)++-- | Convert the contents of a 'TokenAt' object to a pair containg it's component parts, but not the+-- 'Token' itself.+asLineColumn :: TokenAt tok -> (LineNum, ColumnNum)+asLineColumn tok = (lineNumber tok, columnNumber tok)++-- | Convert the contents of the 'TokenAt' object's 'lineNumber' and 'columnNumber' to a 'Location'+-- object.+asLocation :: TokenAt tok -> Location+asLocation = uncurry atPoint . asLineColumn++-- | The lexical analysis phase emits a stream of 'TokenAt' objects, but it is not memory+-- efficient to store the line and column number with every single token. To save space, the token+-- stream is "compressed" into 'Lines', where 'TokenAt' that has the same 'lineNumber' is+-- placed into the same 'Line' object. The 'Line' stores the 'lineNumber', and the+-- 'lineNumber's are deleted from every 'TokenAt', leaving only the 'columnNumber' and 'Token'+-- in each line.+data Line tok+ = Line+ { lineLineNumber :: LineNum+ , lineTokens :: [(ColumnNum, Token tok)]+ -- ^ a list of tokens, each with an associated column number.+ }++instance HasLineNumber (Line tok) where { lineNumber = lineLineNumber }++instance Show tok => Show (Line tok) where+ show line = concat $+ [ "Line ", show (lineLineNumber line), ":\n"+ , intercalate ", " $+ map (\ (col, tok) -> show col++" "++show tok) (lineTokens line)+ ]++instance Functor Line where+ fmap f t =+ Line+ { lineLineNumber = lineLineNumber t+ , lineTokens = fmap (fmap (fmap f)) (lineTokens t)+ }++lineToTokensAt :: Line tok -> [TokenAt tok]+lineToTokensAt line = map f (lineTokens line) where+ lineNum = lineNumber line+ f (colNum, tok) =+ TokenAt+ { tokenAtLineNumber = lineNum+ , tokenAtColumnNumber = colNum+ , getTokenValue = tok+ }++----------------------------------------------------------------------------------------------------+-- $Error_handling+-- The lexical analyzer and syntactic analysis monads all instantiate+-- 'Control.Monad.ParseError.Class.MonadError' in the Monad Transformer Library ("mtl" package). This is+-- the type used for 'Control.Monad.ParseError.Class.throwError' and+-- 'Control.Monad.ParseError.Class.catchError'.++-- | This data structure is used by both the lexical analysis and the syntactic analysis phase.+data ParseError st tok+ = ParseError+ { parseErrLoc :: Location+ , parseErrMsg :: Maybe UStr+ , parseErrTok :: Maybe (Token tok)+ , parseStateAtErr :: Maybe st+ }+ deriving (Eq, Typeable)++instance (PPrintable st, Show tok) => Show (ParseError st tok) where { show err = prettyShow err }++instance Functor (ParseError st) where+ fmap f e =+ ParseError+ { parseErrLoc = parseErrLoc e+ , parseErrMsg = parseErrMsg e+ , parseErrTok = fmap (fmap f) (parseErrTok e)+ , parseStateAtErr = parseStateAtErr e+ }++instance (PPrintable st, Show tok) => PPrintable (ParseError st tok) where+ pPrint err = do+ pString (show (parseErrLoc err)++": ")+ sp <-+ maybe+ (return False)+ (\tok -> pString ("(on token: "++show tok++")") >> return True)+ (parseErrTok err)+ maybe+ (return False)+ (\msg -> when sp (pString " ") >> pUStr msg >> return True)+ (parseErrMsg err)+ maybe (return ()) (\st -> pEndLine >> pIndent (pPrint st)) (parseStateAtErr err)++fmapParseErrorState :: (stA -> stB) -> ParseError stA tok -> ParseError stB tok+fmapParseErrorState f err@(ParseError{ parseStateAtErr=st }) = err{parseStateAtErr=fmap f st}++-- | An initial blank parser error you can use to construct more detailed error messages.+parserErr :: Location -> ParseError st tok+parserErr loc =+ ParseError+ { parseErrLoc = loc+ , parseErrMsg = Nothing+ , parseErrTok = Nothing+ , parseStateAtErr = Nothing+ }++newParseError :: ParseError st tok+newParseError =+ ParseError+ { parseErrLoc = LocationUnknown+ , parseErrMsg = Nothing+ , parseErrTok = Nothing+ , parseStateAtErr = Nothing+ }+
+ src/Dao/Tree.hs view
@@ -0,0 +1,437 @@+-- "src/Dao/Tree.hs" provides a fundamental data type used in the Dao+-- System, the "Tree", which is similar to the "Data.Map" data type.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.+++{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Dao.Tree where++import Dao.String (HasNullValue, nullValue, testNull) -- TODO: move the HasNullValue class to it's own module.++import Control.Applicative+import Control.DeepSeq+import Control.Monad+import Control.Monad.Identity+import Control.Monad.Trans+import Control.Monad.State++import Data.Typeable+import Data.Monoid+import Data.List (intercalate)+-- import Data.Binary+import qualified Data.Map as M+import Data.Word++----------------------------------------------------------------------------------------------------++data Tree p n+ = Void+ | Leaf { branchData :: n }+ | Branch { branchMap :: M.Map p (Tree p n) }+ | LeafBranch + { branchData :: n+ , branchMap :: M.Map p (Tree p n)+ }+ deriving Typeable+instance (Show p, Show n) => Show (Tree p n) where+ show o = case o of+ Void -> "voidTree"+ Leaf o -> "(leaf "++show o++")"+ Branch t -> "(branch ["++branch t++"])"+ LeafBranch o t -> "(leaf "++show o++" branch ["++branch t++"])"+ where { branch t = intercalate ", " (M.assocs t >>= \ (nm, o) -> [show nm++"="++show o]) }+instance (Eq p, Eq n) => Eq (Tree p n) where+ (==) a b = case (a, b) of+ (Void , Void ) -> True+ (Leaf a , Leaf b ) -> a == b+ (Branch a , Branch b ) -> a == b+ (LeafBranch a aa, LeafBranch b bb) -> a == b && aa == bb+ _ -> False+instance (Ord p, Ord n) => Ord (Tree p n) where+ compare a b = case (a, b) of+ (Void , Void ) -> EQ+ (Leaf a , Leaf b ) -> compare a b+ (Branch aa, Branch bb) -> compare aa bb+ (LeafBranch a aa, LeafBranch b bb) -> case compare a b of+ EQ -> compare aa bb+ e -> e+ (Void , _ ) -> LT+ (_ , Void ) -> GT+ (Leaf _ , _ ) -> LT+ (_ , Leaf _ ) -> GT+ (Branch _ , _ ) -> LT+ (_ , Branch _ ) -> GT+instance Ord p => Functor (Tree p) where+ fmap f tree = case tree of+ Void -> Void+ Leaf a -> Leaf (f a)+ Branch m -> Branch (fmap (fmap f) m)+ LeafBranch a m -> LeafBranch (f a) (fmap (fmap f) m)+instance (Ord p, Monoid n) => Monoid (Tree p n) where+ mempty = Void+ mappend = unionWith mappend+instance (NFData a, NFData b) => NFData (Tree a b) where+ rnf Void = ()+ rnf (Leaf a ) = deepseq a ()+ rnf (Branch b) = deepseq b ()+ rnf (LeafBranch a b) = deepseq a $! deepseq b ()+instance HasNullValue (Tree a b) where { nullValue = Void; testNull = Dao.Tree.null; }++-- | A combinator to modify the data in the 'Leaf' and 'LeafBranch' nodes of a tree when passed to+-- one of the functions below.+type ModLeaf n = Maybe n -> Maybe n++-- | A combinator to modify the data in the 'Branch' and 'LeafBranch' nodes of a tree when passed to+-- one of the functions below.+type ModBranch p n = Maybe (M.Map p (Tree p n)) -> Maybe (M.Map p (Tree p n))++-- | If a 'Tree' is 'Void' or a contains a branch that is equivalent to 'Data.Map.empty',+-- 'Data.Maybe.Nothing' is returned.+notVoid :: Tree p n -> Maybe (Tree p n)+notVoid t = case t of+ Void -> Nothing+ Branch b | M.null b -> Nothing+ LeafBranch a b | M.null b -> Just (Leaf a)+ _ -> Just t++-- | If the given node is a 'Leaf' or 'LeafBranch', returns the Leaf portion of the node.+getLeaf :: Tree p n -> Maybe n+getLeaf t = case t of { Leaf n -> Just n; LeafBranch n _ -> Just n; _ -> Nothing }++-- | If the given node is a 'Branch' or 'LeafBranch', returns the branch portion of the node.+getBranch :: Tree p a -> Maybe (M.Map p (Tree p a))+getBranch t = case t of { Branch b -> Just b; LeafBranch _ b -> Just b; _ -> Nothing }++-- | Use a 'ModLeaf' function to insert, update, or remove 'Leaf' and 'LeafBranch' nodes.+alterLeaf :: ModLeaf n -> Tree p n -> Tree p n+alterLeaf alt t = maybe Void id $ case t of+ Void -> alt Nothing >>= \o -> Just (Leaf o)+ Leaf o -> alt (Just o) >>= \o -> Just (Leaf o)+ Branch b -> mplus (alt Nothing >>= \o -> Just (LeafBranch o b)) (Just (Branch b))+ LeafBranch o b -> mplus (alt (Just o) >>= \o -> Just (LeafBranch o b)) (Just (Branch b))++alterBranch :: (Eq p, Ord p) => ModBranch p n -> Tree p n -> Tree p n+alterBranch alt t = maybe Void id $ case t of+ Void -> alt Nothing >>= \b -> Just (Branch b)+ Leaf o -> mplus (alt Nothing >>= \b -> Just (LeafBranch o b)) (Just (Leaf o))+ Branch b -> alt (Just b) >>= \b -> Just (Branch b)+ LeafBranch o b -> mplus (alt (Just b) >>= \b -> Just (LeafBranch o b)) (Just (Leaf o))++----------------------------------------------------------------------------------------------------++data ZipTree p n = ZipTree{ focus :: Tree p n, history :: [(p, Tree p n)] }++newtype UpdateTreeT p n m a = UpdateTreeT{ getUpdateTreeStateT :: StateT (ZipTree p n) m a }+type UpdateTree p n a = UpdateTreeT p n Identity a++instance Monad m =>+ Monad (UpdateTreeT p n m) where+ return = UpdateTreeT . return+ (UpdateTreeT a) >>= b = UpdateTreeT (a >>= getUpdateTreeStateT . b)+instance Functor m =>+ Functor (UpdateTreeT p n m) where { fmap f (UpdateTreeT m) = UpdateTreeT (fmap f m); }+instance (Monad m, Functor m) =>+ Applicative (UpdateTreeT p n m) where { pure = return; (<*>) = ap; }+instance Monad m =>+ MonadState (ZipTree p n) (UpdateTreeT p n m) where { state = UpdateTreeT . state; }+instance MonadTrans (UpdateTreeT p n) where { lift m = UpdateTreeT (lift m); }++-- | Like 'Control.Monad.State.runState', evaluates an 'UpdateTree' monad transformer lifting the+-- 'Control.Monad.Identity.Identity' monad, removing the identity monad after evaluation to give you+-- a pure function.+runUpdateTree :: Ord p => UpdateTree p n a -> Tree p n -> (a, Tree p n)+runUpdateTree updfn = runIdentity . runUpdateTreeT updfn++-- | Like 'Control.Monad.State.execState', disgards the value returned to the 'UpdateTree' monad and+-- only returns the 'Tree'.+execUpdateTree :: Ord p => UpdateTree p n a -> Tree p n -> Tree p n+execUpdateTree updfn = snd . runUpdateTree updfn++-- | Update a 'Tree' using an 'UpdateTreeT' monad, much like how 'Control.Monad.State.runStateT'+-- works. Evaluates to a monadic computation of the lifted type @m@ that 'Control.Monad.return's a+-- pair containing the value last 'Control.Monad.return'ed to the lifted monad and the updated+-- 'Tree'.+runUpdateTreeT :: (Ord p, Functor m, Monad m) => UpdateTreeT p n m a -> Tree p n -> m (a, Tree p n)+runUpdateTreeT updfn tree = fmap (fmap focus) $+ runStateT (getUpdateTreeStateT (updfn >>= \a -> home >> return a)) (ZipTree{focus=tree, history=[]})++-- | Go to the node with the given path. If the path does not exist, it is created.+goto :: (Ord p, Monad m) => [p] -> UpdateTreeT p n m (Tree p n)+goto path = case path of+ [] -> gets focus+ (p:path) -> do+ st <- get+ let step tree = put $ st{focus=tree, history=(p, focus st):history st}+ case getBranch (focus st) >>= M.lookup p of+ Nothing -> step Void+ Just tree -> step tree+ goto path++-- | Go up one level in the tree, storing the current sub-tree into the upper tree, unless the+-- current tree is 'Void', in which case it is deleted from the upper tree.+back :: (Ord p, Monad m) => UpdateTreeT p n m ()+back = modify $ \st -> case history st of+ [] -> st+ (p, tree):hist ->+ st{ history = hist+ , focus = flip alterBranch tree $ \branch -> flip mplus (fmap (M.delete p) branch) $ do+ subTree <- notVoid (focus st)+ fmap (M.insert p subTree) (mplus branch (return mempty))+ }++-- | Returns 'Prelude.True' if we are at the top level of the tree.+atTop :: (Functor m, Monad m) => UpdateTreeT p n m Bool+atTop = fmap Prelude.null (gets history)++-- | Go back to the top level of the tree.+home :: (Ord p, Functor m, Monad m) => UpdateTreeT p n m ()+home = atTop >>= flip unless (back >> home)++-- | Return the current path.+getPath :: (Ord p, Functor m, Monad m) => UpdateTreeT p n m [p]+getPath = fmap (reverse . fmap fst) (gets history)++-- | Modify the tree node at the current 'focus'. After the update, if there is a leaf attached at+-- the focus, the value of the leaf is returned.+modifyNode :: (Ord p, Functor m, Monad m) => (Tree p n -> Tree p n) -> UpdateTreeT p n m (Maybe n)+modifyNode mod = modify (\st -> st{focus=mod(focus st)}) >> fmap getLeaf (gets focus)++-- | Modify the tree node using a 'ModBranch' function which allows you to alter the 'Data.Map.Map'+-- object containing the branches of the current node.+modifyBranch :: (Ord p, Functor m, Monad m) => ModBranch p n -> UpdateTreeT p n m ()+modifyBranch mod = modifyNode (alterBranch mod) >> return ()++-- | Modify the tree node using a 'ModLeaf' function which allows you to alter the 'Data.Map.Map'+-- object containing the current of the current node.+modifyLeaf :: (Ord p, Functor m, Monad m) => ModLeaf n -> UpdateTreeT p n m (Maybe n)+modifyLeaf mod = modifyNode (alterLeaf mod)++----------------------------------------------------------------------------------------------------+-- $MapLikeFunctions+-- In this section I have made my best effor to create API functions as similar as possible to that+-- of the "Data.Map" module.+----------------------------------------------------------------------------------------------------++alter :: Ord p => (Tree p a -> Tree p a) -> [p] -> Tree p a -> Tree p a+alter mod path = execUpdateTree (goto path >> modifyNode mod)+--alterNode alt px t = runIdentity $ alterNodeM (return . alt) px t++-- | Insert a 'Leaf' at a given address.+insert :: Ord p => [p] -> n -> Tree p n -> Tree p n+insert path n = execUpdateTree (goto path >> modifyLeaf (const (Just n)))+--insert px a = alter (const (Just a)) (flip mplus (Just Void)) px++-- | Update a 'Leaf' at a given address.+update :: Ord p => [p] -> ModLeaf a -> Tree p a -> Tree p a+update path mod = execUpdateTree (goto path >> modifyLeaf mod)+--update path mod = alter mod (flip mplus (Just Void)) path++-- | Delete a 'Leaf' or 'Branch' at a given address.+delete :: Ord p => [p] -> Tree p a -> Tree p a+delete path = execUpdateTree (goto path >> modifyLeaf (const Nothing))+--delete px = alter (const Nothing) id px++-- | Create a 'Tree' from a list of associationes, the 'Prelude.fst' element containing the branch,+-- the 'Prelude.snd' element containing the leaf value. This is the inverse operation of 'assocs'.+fromList :: Ord p => [([p], a)] -> Tree p a+fromList = foldl (\ tree (px, a) -> insert px a tree) Void++-- | Lookup a 'Tree' value (the whole node, not just the data stored in the node) at given address.+-- NOTE: this may not be what you want. If you want return the data that is stored in a 'Leaf' or+-- 'LeafBranch', use 'lookup', or just do @'lookup' atBranch inTree >>= 'getLeaf'@.+lookupNode :: Ord p => [p] -> Tree p a -> Maybe (Tree p a)+lookupNode px t = case px of+ [] -> Just t+ p:px -> case t of+ Branch t -> next p t+ LeafBranch _ t -> next p t+ _ -> Nothing+ where { next p t = M.lookup p t >>= Dao.Tree.lookupNode px }++-- | This function analogous to the 'Data.Map.lookup' function, which returns a value stored in a+-- leaf, or nothing if there is no leaf at the given path.+lookup :: Ord p => [p] -> Tree p a -> Maybe a+lookup px t = lookupNode px t >>= getLeaf++-- | Using @[p]@ as a path, traverse the path through the given 'Tree' as far as possible, return+-- the last node that could be reached along with the remainder of the path that was not traversed.+-- This is used to lookup whether or not a leaf has been stored into the tree at the given path, or+-- at some sub-path of the given path.+partialLookup :: Ord p => [p] -> Tree p a -> Maybe ([p], Tree p a)+partialLookup px t = case px of+ [] -> Just ([], t)+ p:px -> mplus (getBranch t >>= M.lookup p >>= partialLookup px) $+ (getLeaf t >>= \ _ -> return (p:px, t))++-- | Using @[p]@ as a path, traverse a tree and retrieve every leaf found along the path until+-- traversal cannot continue. Evaluates to the list of leaves retrieved, the portion of the path+-- that could not be traversed, and the node at which traversal stopped.+leavesAlongPath :: Ord p => [p] -> Tree p a -> ([a], ([p], Tree p a))+leavesAlongPath px t = maybe ([], (px, t)) id $ loop [] px t where+ loop ax px t = return (ax ++ maybe [] (:[]) (getLeaf t)) >>= \ax -> case px of+ [] -> return (ax, ([], t))+ p:px -> mplus (getBranch t >>= M.lookup p >>= loop ax px) (return (ax, (p:px, t)))++-- | There are only two kinds values defined as a 'MergeType': 'union' and 'intersection.+type MergeType p a+ = (Tree p a -> Tree p a -> Tree p a)+ -> M.Map p (Tree p a)+ -> M.Map p (Tree p a)+ -> M.Map p (Tree p a)++-- | Merge two trees together.+mergeWithKey :: Ord p+ => ([p] -> Maybe a -> Maybe b -> Maybe c)+ -> (Tree p a -> Tree p c)+ -> (Tree p b -> Tree p c)+ -> Tree p a -> Tree p b -> Tree p c+mergeWithKey overlap leftOnly rightOnly left right = loop [] left right where+ -- loop :: Ord p => [p] -> Tree p a -> Tree p b -> Tree p c+ loop px left right = case left of+ Void -> case right of+ Void -> Void+ Leaf y -> rightOnly (Leaf y )+ Branch b -> rightOnly (Branch b)+ LeafBranch y b -> rightOnly (LeafBranch y b)+ Leaf x -> case right of+ Void -> leftOnly (Leaf x )+ Leaf y -> maybe Void id (fmap Leaf (overlap px (Just x) (Just y)))+ Branch b -> leafbranch (Just x) Nothing M.empty b+ LeafBranch y b -> leafbranch (Just x) (Just y) M.empty b+ Branch a -> case right of+ Void -> leftOnly (Branch a)+ Leaf y -> leafbranch Nothing (Just y) a M.empty+ Branch b -> leafbranch Nothing Nothing a b + LeafBranch y b -> leafbranch Nothing (Just y) a b + LeafBranch x a -> case right of+ Void -> leftOnly (LeafBranch x a)+ Leaf y -> leafbranch (Just x) (Just y) a M.empty+ Branch b -> leafbranch (Just x) Nothing a b + LeafBranch y b -> leafbranch (Just x) (Just y) a b + where+ -- leafbranch :: Ord p => M.Map p (Tree p a) -> M.Map p (Tree p b) -> Maybe a -> Maybe b -> Tree p c+ leafbranch x y left right = + let c = M.mergeWithKey both (bias leftOnly) (bias rightOnly) left right -- :: M.Map p (Tree p c)+ in case overlap px x y of+ Nothing -> notEmpty Branch c+ Just z -> notEmpty (LeafBranch z) c+ -- notEmpty :: Ord p => (M.Map p (Tree p a) -> Tree p a) -> M.Map p (Tree p a) -> Tree p a+ notEmpty cons c = if M.null c then Void else cons c+ -- both :: Ord p => p -> Tree p a -> Tree p b -> Maybe (Tree p c)+ both p left right = notVoid (loop (px++[p]) left right)+ -- bias :: Ord p => (Tree p a -> Tree p b) -> M.Map p (Tree p a) -> M.Map p (Tree p b)+ bias fn = M.mapMaybe (notVoid . fn)++mergeWith :: Ord p => (Maybe a -> Maybe b -> Maybe c) -> (Tree p a -> Tree p c) -> (Tree p b -> Tree p c) -> Tree p a -> Tree p b -> Tree p c+mergeWith overlap = mergeWithKey (\ _ -> overlap)++unionWithKey :: Ord p => ([p] -> a -> a -> a) -> Tree p a -> Tree p a -> Tree p a+unionWithKey overlap = mergeWithKey (\k a b -> msum [liftM2 (overlap k) a b, a, b]) id id++unionWith :: Ord p => (a -> a -> a) -> Tree p a -> Tree p a -> Tree p a+unionWith overlap = unionWithKey (\ _ -> overlap)++union :: Ord p => Tree p a -> Tree p a -> Tree p a+union = unionWith const++unionsWith :: Ord p => (a -> a -> a) -> [Tree p a] -> Tree p a+unionsWith overlap = foldl (unionWith overlap) Void++unions :: Ord p => [Tree p a] -> Tree p a+unions = unionsWith (flip const)++intersectionWithKey :: Ord p => ([p] -> a -> a -> a) -> Tree p a -> Tree p a -> Tree p a+intersectionWithKey overlap = mergeWithKey (\k -> liftM2 (overlap k)) (const Void) (const Void)++intersectionWith :: Ord p => (a -> a -> a) -> Tree p a -> Tree p a -> Tree p a+intersectionWith overlap = intersectionWithKey (\ _ -> overlap)++intersection :: Ord p => Tree p a -> Tree p a -> Tree p a+intersection = intersectionWith const++intersectionsWith :: Ord p => (a -> a -> a) -> [Tree p a] -> Tree p a+intersectionsWith overlap = foldl (intersectionWith overlap) Void++intersections :: Ord p => [Tree p a] -> Tree p a+intersections = intersectionsWith (flip const)++differenceWithKey :: Ord p => ([p] -> a -> b -> Maybe a) -> Tree p a -> Tree p b -> Tree p a+differenceWithKey overlap = mergeWithKey (\k a b -> mplus (b >>= \b -> a >>= \a -> overlap k a b) a) id (const Void)++differenceWith :: Ord p => (a -> b -> Maybe a) -> Tree p a -> Tree p b -> Tree p a+differenceWith overlap = differenceWithKey (\ _ -> overlap)++difference :: Ord p => Tree p a -> Tree p b -> Tree p a+difference = differenceWith (\ _ _ -> Nothing)++-- | Get all items and their associated path.+assocs :: Tree p a -> [([p], a)]+assocs t = loop [] t where+ recurs px b = M.assocs b >>= \ (p, t) -> loop (px++[p]) t+ loop px t = case t of+ Void -> []+ Leaf a -> [(px, a)]+ Branch b -> recurs px b+ LeafBranch a b -> (px, a) : recurs px b++-- | Apply @'Prelude.map' 'Prelude.snd'@ to the result of 'assocs', behaves just like how+-- 'Data.Map.elems' or 'Data.Array.IArray.elems' works.+elems :: Tree p a -> [a]+elems t = fmap snd (assocs t)++-- | Counts the number of *nodes*, which includes the number of 'Branch'es and 'Leaf's.+size :: Tree p a -> Word64+size t = case t of+ Void -> 0+ Leaf _ -> 1+ Branch m -> 0 + f m+ LeafBranch _ m -> 1 + f m+ where { f m = foldl (\sz tre -> sz + size tre) (fromIntegral (M.size m)) (M.elems m) }++branchCount :: Tree p a -> Int+branchCount = maybe 0 M.size . getBranch++null :: Tree p a -> Bool+null Void = True+null _ = False++----------------------------------------------------------------------------------------------------++data TreeDiff a b+ = LeftOnly a -- something exists in the "left" branch but not in the "right" branch.+ | RightOnly b -- something exists in the "right" branch but not in the "left" branch.+ | TreeDiff a b -- something exists in the "left" and "right" branches but they are not equal+ deriving (Eq, Typeable)++-- | Produce a difference report of two trees with the given comparison predicate. If the predicate+-- returns 'Prelude.True', the node is ignored, otherwise the differences is reported.+treeDiffWith :: Ord p => (a -> b -> Bool) -> Tree p a -> Tree p b -> Tree p (TreeDiff a b)+treeDiffWith compare = mergeWithKey leaf (fmap LeftOnly) (fmap RightOnly) where+ leaf _ a b = msum $+ [ a >>= \a -> b >>= \b -> if compare a b then Nothing else Just (TreeDiff a b)+ , fmap LeftOnly a, fmap RightOnly b+ ]++-- | Call 'treeDiffWith' using 'Prelude.(==)' as the comparison predicate.+treeDiff :: (Eq a, Ord p) => Tree p a -> Tree p a -> Tree p (TreeDiff a a)+treeDiff = treeDiffWith (==)+
+ src/dao-main.hs view
@@ -0,0 +1,122 @@+-- "src/dao-main.hs" the Main module for the "dao" executable program.+-- Provides an interactive command line interface to the Dao System.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not,+-- please see <http://www.gnu.org/licenses/agpl.html>.+++-- {-# LANGUAGE TemplateHaskell #-}++module Main where++import Dao+import Dao.Predicate+import Dao.PPrint++import Data.Char++import Control.Monad+import Control.Monad.IO.Class++import System.Environment+import System.IO+--import System.Console.Readline++----------------------------------------------------------------------------------------------------++version :: String+version = "0.0 (experimental)"++disclaimer :: String+disclaimer = unlines $+ [ "\"Dao\" version "++version+ , "Copyright (C) 2008-2014 Ramin Honary."+ , "This program comes with ABSOLUTELY NO WARRANTY."+ , "This is free software, and you are welcome to redistribute it under"+ , "the terms and conditions of the GNU Affero General Public License."+ , "Enter the command \":license\" for details."+ , "-----------------------------------------------"+ ]++inputLoop :: Exec (Maybe UStr)+inputLoop = do+ liftIO $ putStr "dao> " >> hFlush stdout+ closed <- liftIO $ hIsClosed stdin+ eof <- liftIO isEOF+ if closed || eof+ then return Nothing+ else do+ -- hSetEcho stdin True+ str <- liftIO getLine+ ---------------------------------------------------+ --readline "dao> " >>= \str -> case str of+ -- Nothing -> return Nothing+ -- Just str -> addHistory str >> return (Just str)+ case words (uchars str) of+ o | o==[":quit" ] || o==[":", "quit" ] -> return Nothing+ o | o==[":license"] || o==[":", "license"] -> liftIO (putStrLn license_text) >> inputLoop+ ((':':_) : _) -> do+ evalScriptString (dropWhile (\c -> isSpace c || c==':') str)+ inputLoop+ (a:cmds) | head a==':' -> do+ liftIO $ hPutStr stderr $ unwords $+ ["Error: unknown meta-command"] ++ if null cmds then [] else [show $ head cmds]+ inputLoop+ _ -> return $ Just $ toUStr str++main :: IO ()+main = do+ hSetBuffering stderr LineBuffering+ hSetBuffering stdout LineBuffering+ argv <- getArgs+ when (elem "--version" argv) (putStr disclaimer)+ argv <- return $ fmap ustr $ filter (/="--version") argv+ --initialize -- initialize the ReadLine library+ result <- setupDao $ do+ loadDaoStandardLibrary+ daoInitialize $ do+ loadEveryModule argv+ daoInputLoop inputLoop+ daoShutdown+ case result of+ OK () -> return ()+ PFail (ExecReturn o) -> maybe (return ()) (putStrLn . prettyShow) o+ PFail (err@ExecError{}) -> hPutStrLn stderr (prettyShow err)+ Backtrack -> hPutStrLn stderr "(does not compute)"+ --restorePrompt -- shut-down the ReadLine library+ hPutStrLn stderr "Dao has exited."++license_text :: String+license_text = unlines $+ [ "Dao version: "++version+ , "Copyright (C) 2008-2014 Ramin Honary"+ , ""+ , "This program is free software: you can redistribute it and/or modify"+ , "it under the terms and conditions 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"+ , "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 (see the file called \"LICENSE\"). If not,"+ , "please see <http://www.gnu.org/licenses/agpl.html>."+ ]+
+ tests/main.hs view
@@ -0,0 +1,26 @@+-- "tests/main.hs" calls the main function of the Dao.Test module.+-- +-- Copyright (C) 2008-2014 Ramin Honary.+-- This file is part of the Dao System.+--+-- The Dao System 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.+-- +-- The Dao System 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 (see the file called "LICENSE"). If not, see+-- <http://www.gnu.org/licenses/agpl.html>.++module Main where+import qualified Dao.Test+import qualified Dao.CoreTests++main :: IO ()+main = Dao.Test.main Dao.CoreTests.unitTester+