diff --git a/Cargo.lock b/Cargo.lock index 1db0b96..897b2f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1061,6 +1061,7 @@ dependencies = [ "pastey", "some_executor", "substack", + "task-local", "tokio", "tokio-util", "trait-set", @@ -1110,7 +1111,9 @@ dependencies = [ "orchid-base", "orchid-extension", "ordered-float", + "pastey", "rust_decimal", + "subslice-offset", "substack", "test_executors", "tokio", @@ -1632,6 +1635,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subslice-offset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c883fb2521558a8be70f0f1922babf736f9f72dfbe6ae4f397de3aefb74627ec" + [[package]] name = "substack" version = "1.1.1" @@ -1677,6 +1686,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "task-local" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2c821daee0efdf6414970c8185a1c22e259a7ed87b2fd9f7d3c5f5503fd2863" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "test_executors" version = "0.3.5" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - 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. - - - Copyright (C) - - 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 . - -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: - - Copyright (C) - 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 -. - - 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 -. diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index 0a62eed..ab5d27f 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,3 +1,2 @@ -import macros::common::(+ *) - -let main = 1 + 2 +let my_tuple = option::some t[1, 2] +let main = tuple::get (option::expect my_tuple "tuple is none") 1 diff --git a/orchid-api-traits/src/relations.rs b/orchid-api-traits/src/relations.rs index 41afb70..3d473d8 100644 --- a/orchid-api-traits/src/relations.rs +++ b/orchid-api-traits/src/relations.rs @@ -4,7 +4,7 @@ use std::future::Future; use super::coding::Coding; use crate::helpers::enc_vec; -pub trait Request: fmt::Debug + Coding + Sized + 'static { +pub trait Request: fmt::Debug + Sized + 'static { type Response: fmt::Debug + Coding + 'static; } diff --git a/orchid-api/src/atom.rs b/orchid-api/src/atom.rs index f437d4d..78d6bd3 100644 --- a/orchid-api/src/atom.rs +++ b/orchid-api/src/atom.rs @@ -145,16 +145,6 @@ impl Request for ExtAtomPrint { type Response = FormattingUnit; } -/// Can specify the recipient of an atom as well. The main use case for this is -/// to be able to return an atom to other extensions, so it can be combined with -/// a [crate::Move]. -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(ExtHostReq)] -pub struct CreateAtom(pub Atom, pub SysId); -impl Request for CreateAtom { - type Response = ExprTicket; -} - /// Requests that apply to an existing atom instance #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(HostExtReq)] diff --git a/orchid-api/src/expr.rs b/orchid-api/src/expr.rs index 113306c..e61d430 100644 --- a/orchid-api/src/expr.rs +++ b/orchid-api/src/expr.rs @@ -43,17 +43,6 @@ pub struct Acquire(pub SysId, pub ExprTicket); #[extends(ExprNotif, ExtHostNotif)] pub struct Release(pub SysId, pub ExprTicket); -/// Decrement the reference count for one system and increment it for another, -/// to indicate passing an owned reference. Equivalent to [Acquire] followed by -/// [Release]. -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] -#[extends(ExprNotif, ExtHostNotif)] -pub struct Move { - pub dec: SysId, - pub inc: SysId, - pub expr: ExprTicket, -} - /// A description of a new expression. It is used as the return value of /// [crate::atom::Call] or [crate::atom::CallRef], or a constant in the /// [crate::tree::Tree]. @@ -67,8 +56,9 @@ pub enum ExpressionKind { /// template Arg(u64), /// Insert the specified host-expression in the template here. When the clause - /// is used in the const tree, this variant is forbidden. - Slot { tk: ExprTicket, by_value: bool }, + /// is used in the const tree, this variant is forbidden. The ticket held + /// within is always owning. To avoid a leak, it must be deserialized. + Slot(ExprTicket), /// The lhs must be fully processed before the rhs can be processed. /// Equivalent to Haskell's function of the same name Seq(Box, Box), @@ -115,11 +105,12 @@ impl Request for Inspect { type Response = Inspected; } -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] +#[derive(Clone, Debug, Coding, Hierarchy)] #[extends(ExtHostReq)] #[extendable] pub enum ExprReq { Inspect(Inspect), + Create(Create), } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] @@ -128,5 +119,11 @@ pub enum ExprReq { pub enum ExprNotif { Acquire(Acquire), Release(Release), - Move(Move), +} + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(ExprReq, ExtHostReq)] +pub struct Create(pub Expression); +impl Request for Create { + type Response = ExprTicket; } diff --git a/orchid-api/src/location.rs b/orchid-api/src/location.rs index 361368b..ed4340f 100644 --- a/orchid-api/src/location.rs +++ b/orchid-api/src/location.rs @@ -17,6 +17,8 @@ pub enum Location { Gen(CodeGenInfo), /// Range and file SourceRange(SourceRange), + /// Multiple locations + Multi(Vec), } #[derive(Clone, Debug, Coding)] diff --git a/orchid-api/src/proto.rs b/orchid-api/src/proto.rs index 11576e3..8df1d73 100644 --- a/orchid-api/src/proto.rs +++ b/orchid-api/src/proto.rs @@ -85,7 +85,6 @@ pub enum ExtHostReq { IntReq(interner::IntReq), Fwd(atom::Fwd), ExtAtomPrint(atom::ExtAtomPrint), - CreateAtom(atom::CreateAtom), SysFwd(system::SysFwd), ExprReq(expr::ExprReq), SubLex(lexer::SubLex), diff --git a/orchid-api/src/tree.rs b/orchid-api/src/tree.rs index ad53241..eec6d7f 100644 --- a/orchid-api/src/tree.rs +++ b/orchid-api/src/tree.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::fmt; use std::num::NonZeroU64; use std::ops::Range; use std::rc::Rc; @@ -56,6 +57,15 @@ pub enum Paren { Square, Curly, } +impl fmt::Display for Paren { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", match self { + Self::Round => "()", + Self::Curly => "{}", + Self::Square => "[]", + }) + } +} #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)] pub struct TreeId(pub NonZeroU64); diff --git a/orchid-base/src/format.rs b/orchid-base/src/format.rs index 74b3f94..bb42821 100644 --- a/orchid-base/src/format.rs +++ b/orchid-base/src/format.rs @@ -7,7 +7,7 @@ use std::rc::Rc; use std::str::FromStr; use futures::future::join_all; -use itertools::Itertools; +use itertools::{Itertools, chain}; use never::Never; use regex::Regex; @@ -47,12 +47,14 @@ impl FmtUnit { } } pub fn sequence( + head: &str, delim: &str, + tail: &str, seq_bnd: Option, seq: impl IntoIterator, ) -> Self { let items = seq.into_iter().collect_vec(); - FmtUnit::new(Variants::sequence(items.len(), delim, seq_bnd), items) + Variants::default().sequence(items.len(), head, delim, tail, seq_bnd).units_own(items) } } impl From for FmtUnit @@ -110,8 +112,29 @@ pub struct Variant { #[test] fn variants_parse_test() { - let vars = Variants::default().bounded("({0})"); - println!("final: {vars:?}") + let vars = Rc::new(Variants::default().bounded("({{{0}}})")); + let expected_vars = Rc::new(Variants(vec![Variant { + bounded: true, + elements: vec![ + FmtElement::String(Rc::new("({".to_string())), + FmtElement::Sub { bounded: Some(false), slot: 0 }, + FmtElement::String(Rc::new("})".to_string())), + ], + }])); + assert_eq!(vars.as_ref(), expected_vars.as_ref()); + let unit = vars.units(["1".into()]); + assert_eq!(unit, FmtUnit { + subs: vec![FmtUnit { + subs: vec![], + variants: Rc::new(Variants(vec![Variant { + bounded: true, + elements: vec![FmtElement::String(Rc::new("1".to_string()))] + }])) + }], + variants: expected_vars + }); + let str = take_first(&unit, true); + assert_eq!(str, "({1})"); } /// Represents a collection of formatting strings for the same set of parameters @@ -208,12 +231,27 @@ impl Variants { self.add(false, s); self } - pub fn sequence(len: usize, delim: &str, seq_bnd: Option) -> Rc { - let seq = Itertools::intersperse( - FmtElement::sequence(len, seq_bnd).into_iter(), - FmtElement::str(delim), + pub fn sequence( + mut self, + len: usize, + head: &str, + delim: &str, + tail: &str, + seq_bnd: Option, + ) -> Self { + let seq = chain!( + [FmtElement::str(head)], + Itertools::intersperse( + FmtElement::sequence(len, seq_bnd).into_iter(), + FmtElement::str(delim), + ), + [FmtElement::str(tail)], ); - Rc::new(Variants(vec![Variant { bounded: true, elements: seq.collect_vec() }])) + self.0.push(Variant { bounded: true, elements: seq.collect_vec() }); + self + } + pub fn units_own(self, subs: impl IntoIterator) -> FmtUnit { + FmtUnit::new(Rc::new(self), subs) } pub fn units(self: &Rc, subs: impl IntoIterator) -> FmtUnit { FmtUnit::new(self.clone(), subs) diff --git a/orchid-base/src/location.rs b/orchid-base/src/location.rs index 6a6d145..27fa11f 100644 --- a/orchid-base/src/location.rs +++ b/orchid-base/src/location.rs @@ -2,8 +2,9 @@ use std::fmt; use std::hash::Hash; -use std::ops::Range; +use std::ops::{Add, AddAssign, Range}; +use futures::future::join_all; use trait_set::trait_set; use crate::error::ErrPos; @@ -25,6 +26,7 @@ pub enum Pos { Gen(CodeGenInfo), /// Range and file SrcRange(SrcRange), + Multi(Vec), } impl Pos { pub fn pretty_print(&self, get_src: &mut impl GetSrc) -> String { @@ -39,6 +41,7 @@ impl Pos { match_mapping!(api, api::Location => Pos { None, Inherit, SlotTarget, Gen(cgi => CodeGenInfo::from_api(cgi, i).await), + Multi(v => join_all(v.iter().map(|l| Pos::from_api(l, i))).await) } { api::Location::SourceRange(sr) => Self::SrcRange(SrcRange::from_api(sr, i).await) }) @@ -47,6 +50,7 @@ impl Pos { match_mapping!(self, Pos => api::Location { None, Inherit, SlotTarget, Gen(cgi.to_api()), + Multi(v => v.iter().map(|pos| pos.to_api()).collect()), } { Self::SrcRange(sr) => api::Location::SourceRange(sr.to_api()), }) @@ -60,9 +64,36 @@ impl fmt::Display for Pos { Pos::None => f.write_str("N/A"), Pos::Gen(g) => write!(f, "{g}"), Pos::SrcRange(sr) => write!(f, "{sr}"), + Pos::Multi(posv) => { + write!(f, "{}", posv[0])?; + for pos in posv { + write!(f, "+{}", pos)?; + } + Ok(()) + }, } } } +impl Add for Pos { + type Output = Pos; + fn add(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Pos::Multi(l), Pos::Multi(r)) => Pos::Multi(l.into_iter().chain(r).collect()), + (Pos::None, any) => any, + (any, Pos::None) => any, + (Pos::Multi(v), single) => Pos::Multi(v.into_iter().chain([single]).collect()), + (single, Pos::Multi(v)) => Pos::Multi([single].into_iter().chain(v).collect()), + (l, r) => Pos::Multi(vec![l, r]), + } + } +} +impl AddAssign for Pos { + fn add_assign(&mut self, rhs: Self) { + let mut tmp = Pos::None; + std::mem::swap(&mut tmp, self); + *self = tmp + rhs; + } +} /// Exact source code location. Includes where the code was loaded from, what /// the original source code was, and a byte range. @@ -77,7 +108,7 @@ impl SrcRange { } /// Create a dud [SourceRange] for testing. Its value is unspecified and /// volatile. - pub async fn mock(i: &Interner) -> Self { Self { range: 0..1, path: sym!(test; i).await } } + pub async fn mock(i: &Interner) -> Self { Self { range: 0..1, path: sym!(test; i) } } /// Path the source text was loaded from pub fn path(&self) -> Sym { self.path.clone() } /// Byte range diff --git a/orchid-base/src/name.rs b/orchid-base/src/name.rs index 2d835df..4951ba6 100644 --- a/orchid-base/src/name.rs +++ b/orchid-base/src/name.rs @@ -311,7 +311,7 @@ impl NameLike for VName {} /// cloning the token. #[macro_export] macro_rules! sym { - ($seg1:tt $( :: $seg:tt)* ; $i:expr) => { async { + ($seg1:tt $( :: $seg:tt)* ; $i:expr) => { $crate::name::Sym::from_tok( $i.i(&[ $i.i(stringify!($seg1)).await @@ -319,9 +319,7 @@ macro_rules! sym { ]) .await ).unwrap() - } }; - (@NAME $seg:tt) => {} } /// Create a [VName] literal. @@ -329,12 +327,12 @@ macro_rules! sym { /// The components are interned much like in [sym]. #[macro_export] macro_rules! vname { - ($seg1:tt $( :: $seg:tt)* ; $i:expr) => { async { + ($seg1:tt $( :: $seg:tt)* ; $i:expr) => { $crate::name::VName::new([ $i.i(stringify!($seg1)).await $( , $i.i(stringify!($seg)).await )* ]).unwrap() - } }; + }; } /// Create a [VPath] literal. @@ -342,12 +340,12 @@ macro_rules! vname { /// The components are interned much like in [sym]. #[macro_export] macro_rules! vpath { - ($seg1:tt $( :: $seg:tt)+ ; $i:expr) => { async { + ($seg1:tt $( :: $seg:tt)+ ; $i:expr) => { $crate::name::VPath(vec![ $i.i(stringify!($seg1)).await $( , $i.i(stringify!($seg)).await )+ ]) - } }; + }; () => { $crate::name::VPath(vec![]) } @@ -367,7 +365,7 @@ mod test { fn recur() { spin_on(async { let i = Interner::new_master(); - let myname = vname!(foo::bar; i).await; + let myname = vname!(foo::bar; i); let _borrowed_slice: &[Tok] = myname.borrow(); let _deref_pathslice: &[Tok] = &myname; let _as_slice_out: &[Tok] = myname.as_slice(); @@ -379,15 +377,15 @@ mod test { spin_on(async { let i = Interner::new_master(); assert_eq!( - sym!(foo::bar::baz; i).await, + sym!(foo::bar::baz; i), Sym::new([i.i("foo").await, i.i("bar").await, i.i("baz").await], &i).await.unwrap() ); assert_eq!( - vname!(foo::bar::baz; i).await, + vname!(foo::bar::baz; i), VName::new([i.i("foo").await, i.i("bar").await, i.i("baz").await]).unwrap() ); assert_eq!( - vpath!(foo::bar::baz; i).await, + vpath!(foo::bar::baz; i), VPath::new([i.i("foo").await, i.i("bar").await, i.i("baz").await]) ); }) diff --git a/orchid-base/src/tree.rs b/orchid-base/src/tree.rs index 0a62571..32adc07 100644 --- a/orchid-base/src/tree.rs +++ b/orchid-base/src/tree.rs @@ -307,7 +307,7 @@ pub async fn ttv_fmt<'a: 'b, 'b>( ttv: impl IntoIterator>, c: &(impl FmtCtx + ?Sized), ) -> FmtUnit { - FmtUnit::sequence(" ", None, join_all(ttv.into_iter().map(|t| t.print(c))).await) + FmtUnit::sequence("", " ", "", None, join_all(ttv.into_iter().map(|t| t.print(c))).await) } pub fn indent(s: &str) -> String { s.replace("\n", "\n ") } diff --git a/orchid-extension/Cargo.toml b/orchid-extension/Cargo.toml index e58929b..ad6c3e6 100644 --- a/orchid-extension/Cargo.toml +++ b/orchid-extension/Cargo.toml @@ -31,6 +31,7 @@ ordered-float = "5.0.0" pastey = "0.1.1" some_executor = "0.6.1" substack = "1.1.1" +task-local = "0.1.0" tokio = { version = "1.47.1", optional = true, features = [] } tokio-util = { version = "0.7.16", optional = true, features = ["compat"] } diff --git a/orchid-extension/src/atom.rs b/orchid-extension/src/atom.rs index 92a17d6..d987e95 100644 --- a/orchid-extension/src/atom.rs +++ b/orchid-extension/src/atom.rs @@ -12,21 +12,20 @@ use futures::future::LocalBoxFuture; use futures::{AsyncRead, AsyncWrite, FutureExt, StreamExt, stream}; use orchid_api_derive::Coding; use orchid_api_traits::{Coding, Decode, Encode, Request, enc_vec}; -use orchid_base::clone; use orchid_base::error::{OrcErrv, OrcRes, mk_errv, mk_errv_floating}; -use orchid_base::format::{FmtCtx, FmtUnit, Format}; -use orchid_base::interner::Interner; +use orchid_base::format::{FmtCtx, FmtUnit, Format, fmt}; use orchid_base::location::Pos; use orchid_base::name::Sym; use orchid_base::reqnot::Requester; use trait_set::trait_set; use crate::api; +use crate::context::{ctx, i}; use crate::conv::ToExpr; // use crate::error::{ProjectError, ProjectResult}; use crate::expr::{Expr, ExprData, ExprHandle, ExprKind}; use crate::gen_expr::GExpr; -use crate::system::{DynSystemCard, SysCtx, atom_info_for, downcast_atom}; +use crate::system::{DynSystemCard, atom_info_for, downcast_atom}; #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)] pub struct AtomTypeId(pub NonZeroU32); @@ -91,19 +90,18 @@ pub struct ForeignAtom { } impl ForeignAtom { pub fn pos(&self) -> Pos { self.pos.clone() } - pub fn ctx(&self) -> &SysCtx { &self.expr.ctx } pub fn ex(self) -> Expr { let (handle, pos) = (self.expr.clone(), self.pos.clone()); let data = ExprData { pos, kind: ExprKind::Atom(ForeignAtom { ..self }) }; - Expr::new(handle, data) + Expr::from_data(handle, data) } pub(crate) fn new(handle: Rc, atom: api::Atom, pos: Pos) -> Self { ForeignAtom { atom, expr: handle, pos } } pub async fn request(&self, m: M) -> Option { - let rep = (self.ctx().reqnot().request(api::Fwd( + let rep = (ctx().reqnot().request(api::Fwd( self.atom.clone(), - Sym::parse(M::NAME, self.ctx().i()).await.unwrap().tok().to_api(), + Sym::parse(M::NAME, &i()).await.unwrap().tok().to_api(), enc_vec(&m).await, ))) .await?; @@ -121,40 +119,38 @@ impl fmt::Debug for ForeignAtom { } impl Format for ForeignAtom { async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - FmtUnit::from_api(&self.ctx().reqnot().request(api::ExtAtomPrint(self.atom.clone())).await) + FmtUnit::from_api(&ctx().reqnot().request(api::ExtAtomPrint(self.atom.clone())).await) } } impl ToExpr for ForeignAtom { - async fn to_expr(self) -> GExpr { self.ex().to_expr().await } + async fn to_gen(self) -> GExpr { self.ex().to_gen().await } } pub struct NotTypAtom { pub pos: Pos, pub expr: Expr, pub typ: Box, - pub ctx: SysCtx, } impl NotTypAtom { pub async fn mk_err(&self) -> OrcErrv { mk_errv( - self.ctx.i().i("Not the expected type").await, - format!("This expression is not a {}", self.typ.name()), + i().i("Not the expected type").await, + format!("The expression {} is not a {}", fmt(&self.expr, &i()).await, self.typ.name()), [self.pos.clone()], ) } } -pub trait AtomMethod: Request { +pub trait AtomMethod: Request + Coding { const NAME: &str; } pub trait Supports: AtomCard { - fn handle(&self, ctx: SysCtx, req: M) -> impl Future::Response>; + fn handle(&self, req: M) -> impl Future::Response>; } trait_set! { trait AtomReqCb = for<'a> Fn( &'a A, - SysCtx, Pin<&'a mut dyn AsyncRead>, Pin<&'a mut dyn AsyncWrite>, ) -> LocalBoxFuture<'a, ()> @@ -171,24 +167,18 @@ impl MethodSetBuilder { assert!(!M::NAME.is_empty(), "AtomMethod::NAME cannoot be empty"); self.handlers.push(( M::NAME, - Rc::new( - move |a: &A, ctx: SysCtx, req: Pin<&mut dyn AsyncRead>, rep: Pin<&mut dyn AsyncWrite>| { - async { Supports::::handle(a, ctx, M::decode(req).await).await.encode(rep).await } - .boxed_local() - }, - ), + Rc::new(move |a: &A, req: Pin<&mut dyn AsyncRead>, rep: Pin<&mut dyn AsyncWrite>| { + async { Supports::::handle(a, M::decode(req).await).await.encode(rep).await } + .boxed_local() + }), )); self } - pub async fn pack(&self, ctx: SysCtx) -> MethodSet { + pub async fn pack(&self) -> MethodSet { MethodSet { handlers: stream::iter(self.handlers.iter()) - .then(|(k, v)| { - clone!(ctx; async move { - (Sym::parse(k, ctx.i()).await.unwrap(), v.clone()) - }) - }) + .then(async |(k, v)| (Sym::parse(k, &i()).await.unwrap(), v.clone())) .collect() .await, } @@ -202,7 +192,6 @@ impl MethodSet { pub(crate) async fn dispatch<'a>( &'a self, atom: &'a A, - ctx: SysCtx, key: Sym, req: Pin<&'a mut dyn AsyncRead>, rep: Pin<&'a mut dyn AsyncWrite>, @@ -210,7 +199,7 @@ impl MethodSet { match self.handlers.get(&key) { None => false, Some(handler) => { - handler(atom, ctx, req, rep).await; + handler(atom, req, rep).await; true }, } @@ -228,33 +217,23 @@ pub struct TAtom { } impl TAtom { pub fn ex(&self) -> Expr { self.untyped.clone().ex() } - pub fn ctx(&self) -> &SysCtx { self.untyped.ctx() } - pub fn i(&self) -> &Interner { self.ctx().i() } + pub fn pos(&self) -> Pos { self.untyped.pos() } pub async fn downcast(expr: Rc) -> Result { match Expr::from_handle(expr).atom().await { - Err(expr) => Err(NotTypAtom { - ctx: expr.handle().get_ctx(), - pos: expr.data().await.pos.clone(), - expr, - typ: Box::new(A::info()), - }), + Err(expr) => + Err(NotTypAtom { pos: expr.data().await.pos.clone(), expr, typ: Box::new(A::info()) }), Ok(atm) => match downcast_atom::(atm).await { Ok(tatom) => Ok(tatom), - Err(fa) => Err(NotTypAtom { - pos: fa.pos.clone(), - ctx: fa.ctx().clone(), - expr: fa.ex(), - typ: Box::new(A::info()), - }), + Err(fa) => Err(NotTypAtom { pos: fa.pos.clone(), expr: fa.ex(), typ: Box::new(A::info()) }), }, } } pub async fn request(&self, req: M) -> M::Response where A: Supports { M::Response::decode(Pin::new( - &mut &(self.untyped.ctx().reqnot().request(api::Fwd( + &mut &(ctx().reqnot().request(api::Fwd( self.untyped.atom.clone(), - Sym::parse(M::NAME, self.untyped.ctx().i()).await.unwrap().tok().to_api(), + Sym::parse(M::NAME, &i()).await.unwrap().tok().to_api(), enc_vec(&req).await, ))) .await @@ -268,13 +247,15 @@ impl Deref for TAtom { fn deref(&self) -> &Self::Target { &self.value } } impl ToExpr for TAtom { - async fn to_expr(self) -> GExpr { self.untyped.to_expr().await } + async fn to_gen(self) -> GExpr { self.untyped.to_gen().await } +} +impl Format for TAtom { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + self.untyped.print(c).await + } } -pub struct AtomCtx<'a>(pub &'a [u8], pub Option, pub SysCtx); -impl FmtCtx for AtomCtx<'_> { - fn i(&self) -> &Interner { self.2.i() } -} +pub struct AtomCtx<'a>(pub &'a [u8], pub Option); pub trait AtomDynfo: 'static { fn tid(&self) -> TypeId; @@ -296,24 +277,19 @@ pub trait AtomDynfo: 'static { ctx: AtomCtx<'a>, write: Pin<&'b mut dyn AsyncWrite>, ) -> LocalBoxFuture<'a, Option>>; - fn deserialize<'a>( - &'a self, - ctx: SysCtx, - data: &'a [u8], - refs: &'a [Expr], - ) -> LocalBoxFuture<'a, api::Atom>; + fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom>; fn drop<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, ()>; } trait_set! { - pub trait AtomFactoryFn = FnOnce(SysCtx) -> LocalBoxFuture<'static, api::Atom> + DynClone; + pub trait AtomFactoryFn = FnOnce() -> LocalBoxFuture<'static, api::Atom> + DynClone; } pub struct AtomFactory(Box); impl AtomFactory { - pub fn new(f: impl AsyncFnOnce(SysCtx) -> api::Atom + Clone + 'static) -> Self { - Self(Box::new(|ctx| f(ctx).boxed_local())) + pub fn new(f: impl AsyncFnOnce() -> api::Atom + Clone + 'static) -> Self { + Self(Box::new(|| f().boxed_local())) } - pub async fn build(self, ctx: SysCtx) -> api::Atom { (self.0)(ctx).await } + pub async fn build(self) -> api::Atom { (self.0)().await } } impl Clone for AtomFactory { fn clone(&self) -> Self { AtomFactory(clone_box(&*self.0)) } @@ -330,10 +306,10 @@ impl Format for AtomFactory { } } -pub async fn err_not_callable(i: &Interner) -> OrcErrv { - mk_errv_floating(i.i("This atom is not callable").await, "Attempted to apply value as function") +pub async fn err_not_callable() -> OrcErrv { + mk_errv_floating(i().i("This atom is not callable").await, "Attempted to apply value as function") } -pub async fn err_not_command(i: &Interner) -> OrcErrv { - mk_errv_floating(i.i("This atom is not a command").await, "Settled on an inactionable value") +pub async fn err_not_command() -> OrcErrv { + mk_errv_floating(i().i("This atom is not a command").await, "Settled on an inactionable value") } diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index 488b3f0..b0c02dc 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -25,26 +25,26 @@ use crate::atom::{ AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet, MethodSetBuilder, TAtom, err_not_callable, err_not_command, get_info, }; +use crate::context::{SysCtxEntry, ctx, i}; use crate::expr::Expr; use crate::gen_expr::{GExpr, bot}; -use crate::system::{SysCtx, SysCtxEntry}; use crate::system_ctor::CtedObj; pub struct OwnedVariant; impl AtomicVariant for OwnedVariant {} impl> AtomicFeaturesImpl for A { fn _factory(self) -> AtomFactory { - AtomFactory::new(async move |ctx| { - let serial = - ctx.get_or_default::().next_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + AtomFactory::new(async move || { + let serial = ctx() + .get_or_default::() + .next_id + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); let atom_id = api::AtomId(NonZero::new(serial + 1).unwrap()); - let (typ_id, _) = get_info::(ctx.get::().inst().card()); + let (typ_id, _) = get_info::(ctx().get::().inst().card()); let mut data = enc_vec(&typ_id).await; self.encode(Pin::<&mut Vec>::new(&mut data)).await; - let g = ctx.get_or_default::().objects.read().await; - g.insert(atom_id, Box::new(self)); - std::mem::drop(g); - api::Atom { drop: Some(atom_id), data: api::AtomData(data), owner: ctx.sys_id() } + ctx().get_or_default::().objects.read().await.insert(atom_id, Box::new(self)); + api::Atom { drop: Some(atom_id), data: api::AtomData(data), owner: ctx().sys_id() } }) } fn _info() -> Self::_Info { OwnedAtomDynfo { msbuild: A::reg_reqs(), ms: OnceCell::new() } } @@ -58,8 +58,8 @@ pub(crate) struct AtomReadGuard<'a> { guard: RwLockReadGuard>>, } impl<'a> AtomReadGuard<'a> { - async fn new(id: api::AtomId, ctx: &'a SysCtx) -> Self { - let guard = ctx.get_or_default::().objects.read().await; + async fn new(id: api::AtomId) -> Self { + let guard = ctx().get_or_default::().objects.read().await; if guard.get(&id).is_none() { panic!("Received invalid atom ID: {id:?}"); } @@ -72,8 +72,8 @@ impl Deref for AtomReadGuard<'_> { } /// Remove an atom from the store -pub(crate) async fn take_atom(id: api::AtomId, ctx: &SysCtx) -> Box { - let mut g = ctx.get_or_default::().objects.write().await; +pub(crate) async fn take_atom(id: api::AtomId) -> Box { + let mut g = ctx().get_or_default::().objects.write().await; g.remove(&id).unwrap_or_else(|| panic!("Received invalid atom ID: {}", id.0)) } @@ -89,64 +89,53 @@ impl AtomDynfo for OwnedAtomDynfo { Box::new(::Data::decode(Pin::new(&mut &data[..])).await) as Box }) } - fn call(&self, AtomCtx(_, id, ctx): AtomCtx, arg: Expr) -> LocalBoxFuture<'_, GExpr> { - Box::pin(async move { take_atom(id.unwrap(), &ctx).await.dyn_call(arg).await }) + fn call(&self, AtomCtx(_, id): AtomCtx, arg: Expr) -> LocalBoxFuture<'_, GExpr> { + Box::pin(async move { take_atom(id.unwrap()).await.dyn_call(arg).await }) } - fn call_ref<'a>( - &'a self, - AtomCtx(_, id, ctx): AtomCtx<'a>, - arg: Expr, - ) -> LocalBoxFuture<'a, GExpr> { - Box::pin(async move { AtomReadGuard::new(id.unwrap(), &ctx).await.dyn_call_ref(arg).await }) + fn call_ref<'a>(&'a self, AtomCtx(_, id): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> { + Box::pin(async move { AtomReadGuard::new(id.unwrap()).await.dyn_call_ref(arg).await }) } - fn print(&self, AtomCtx(_, id, ctx): AtomCtx<'_>) -> LocalBoxFuture<'_, FmtUnit> { - Box::pin( - async move { AtomReadGuard::new(id.unwrap(), &ctx).await.dyn_print(ctx.clone()).await }, - ) + fn print(&self, AtomCtx(_, id): AtomCtx<'_>) -> LocalBoxFuture<'_, FmtUnit> { + Box::pin(async move { AtomReadGuard::new(id.unwrap()).await.dyn_print().await }) } fn handle_req<'a, 'b: 'a, 'c: 'a>( &'a self, - AtomCtx(_, id, ctx): AtomCtx, + AtomCtx(_, id): AtomCtx, key: Sym, req: Pin<&'b mut dyn AsyncRead>, rep: Pin<&'c mut dyn AsyncWrite>, ) -> LocalBoxFuture<'a, bool> { Box::pin(async move { - let a = AtomReadGuard::new(id.unwrap(), &ctx).await; - let ms = self.ms.get_or_init(self.msbuild.pack(ctx.clone())).await; - ms.dispatch(a.as_any_ref().downcast_ref().unwrap(), ctx.clone(), key, req, rep).await + let a = AtomReadGuard::new(id.unwrap()).await; + let ms = self.ms.get_or_init(self.msbuild.pack()).await; + ms.dispatch(a.as_any_ref().downcast_ref().unwrap(), key, req, rep).await }) } fn command<'a>( &'a self, - AtomCtx(_, id, ctx): AtomCtx<'a>, + AtomCtx(_, id): AtomCtx<'a>, ) -> LocalBoxFuture<'a, OrcRes>> { - Box::pin(async move { take_atom(id.unwrap(), &ctx).await.dyn_command(ctx.clone()).await }) + Box::pin(async move { take_atom(id.unwrap()).await.dyn_command().await }) } - fn drop(&self, AtomCtx(_, id, ctx): AtomCtx) -> LocalBoxFuture<'_, ()> { - Box::pin(async move { take_atom(id.unwrap(), &ctx).await.dyn_free(ctx.clone()).await }) + fn drop(&self, AtomCtx(_, id): AtomCtx) -> LocalBoxFuture<'_, ()> { + Box::pin(async move { take_atom(id.unwrap()).await.dyn_free().await }) } fn serialize<'a, 'b: 'a>( &'a self, - AtomCtx(_, id, ctx): AtomCtx<'a>, + AtomCtx(_, id): AtomCtx<'a>, mut write: Pin<&'b mut dyn AsyncWrite>, ) -> LocalBoxFuture<'a, Option>> { Box::pin(async move { let id = id.unwrap(); id.encode(write.as_mut()).await; - AtomReadGuard::new(id, &ctx).await.dyn_serialize(ctx.clone(), write).await + AtomReadGuard::new(id).await.dyn_serialize(write).await }) } - fn deserialize<'a>( - &'a self, - ctx: SysCtx, - data: &'a [u8], - refs: &'a [Expr], - ) -> LocalBoxFuture<'a, api::Atom> { + fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom> { Box::pin(async move { let refs = T::Refs::from_iter(refs.iter().cloned()); - let obj = T::deserialize(DeserCtxImpl(data, &ctx), refs).await; - obj._factory().build(ctx).await + let obj = T::deserialize(DeserCtxImpl(data), refs).await; + obj._factory().build().await }) } } @@ -162,14 +151,12 @@ pub trait DeserializeCtx: Sized { t } } - fn sys(&self) -> SysCtx; } -struct DeserCtxImpl<'a>(&'a [u8], &'a SysCtx); +struct DeserCtxImpl<'a>(&'a [u8]); impl DeserializeCtx for DeserCtxImpl<'_> { async fn read(&mut self) -> T { T::decode(Pin::new(&mut self.0)).await } fn is_empty(&self) -> bool { self.0.is_empty() } - fn sys(&self) -> SysCtx { self.1.clone() } } pub trait RefSet { @@ -220,22 +207,21 @@ pub trait OwnedAtom: Atomic + Any + Clone + 'static { fn val(&self) -> impl Future>; #[allow(unused_variables)] fn call_ref(&self, arg: Expr) -> impl Future { - async move { bot(err_not_callable(arg.ctx().i()).await) } + async move { bot(err_not_callable().await) } } fn call(self, arg: Expr) -> impl Future { async { - let ctx = arg.ctx(); let gcl = self.call_ref(arg).await; - self.free(ctx).await; + self.free().await; gcl } } #[allow(unused_variables)] - fn command(self, ctx: SysCtx) -> impl Future>> { - async move { Err(err_not_command(ctx.i()).await) } + fn command(self) -> impl Future>> { + async move { Err(err_not_command().await) } } #[allow(unused_variables)] - fn free(self, ctx: SysCtx) -> impl Future { async {} } + fn free(self) -> impl Future { async {} } #[allow(unused_variables)] fn print_atom<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future { async { format!("OwnedAtom({})", type_name::()).into() } @@ -243,14 +229,13 @@ pub trait OwnedAtom: Atomic + Any + Clone + 'static { #[allow(unused_variables)] fn serialize( &self, - ctx: SysCtx, write: Pin<&mut (impl AsyncWrite + ?Sized)>, ) -> impl Future { assert_serializable::(); async { panic!("Either implement serialize or set Refs to Never for {}", type_name::()) } } #[allow(unused_variables)] - fn deserialize(ctx: impl DeserializeCtx, refs: Self::Refs) -> impl Future { + fn deserialize(dctx: impl DeserializeCtx, refs: Self::Refs) -> impl Future { assert_serializable::(); async { panic!("Either implement deserialize or set Refs to Never for {}", type_name::()) @@ -269,12 +254,11 @@ pub trait DynOwnedAtom: DynClone + 'static { fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()>; fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr>; fn dyn_call(self: Box, arg: Expr) -> LocalBoxFuture<'static, GExpr>; - fn dyn_command(self: Box, ctx: SysCtx) -> LocalBoxFuture<'static, OrcRes>>; - fn dyn_free(self: Box, ctx: SysCtx) -> LocalBoxFuture<'static, ()>; - fn dyn_print(&self, ctx: SysCtx) -> LocalBoxFuture<'_, FmtUnit>; + fn dyn_command(self: Box) -> LocalBoxFuture<'static, OrcRes>>; + fn dyn_free(self: Box) -> LocalBoxFuture<'static, ()>; + fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit>; fn dyn_serialize<'a>( &'a self, - ctx: SysCtx, sink: Pin<&'a mut dyn AsyncWrite>, ) -> LocalBoxFuture<'a, Option>>; } @@ -290,23 +274,20 @@ impl DynOwnedAtom for T { fn dyn_call(self: Box, arg: Expr) -> LocalBoxFuture<'static, GExpr> { self.call(arg).boxed_local() } - fn dyn_command(self: Box, ctx: SysCtx) -> LocalBoxFuture<'static, OrcRes>> { - self.command(ctx).boxed_local() + fn dyn_command(self: Box) -> LocalBoxFuture<'static, OrcRes>> { + self.command().boxed_local() } - fn dyn_free(self: Box, ctx: SysCtx) -> LocalBoxFuture<'static, ()> { - self.free(ctx).boxed_local() - } - fn dyn_print(&self, ctx: SysCtx) -> LocalBoxFuture<'_, FmtUnit> { - async move { self.print_atom(&FmtCtxImpl { i: ctx.i() }).await }.boxed_local() + fn dyn_free(self: Box) -> LocalBoxFuture<'static, ()> { self.free().boxed_local() } + fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit> { + async move { self.print_atom(&FmtCtxImpl { i: &i() }).await }.boxed_local() } fn dyn_serialize<'a>( &'a self, - ctx: SysCtx, sink: Pin<&'a mut dyn AsyncWrite>, ) -> LocalBoxFuture<'a, Option>> { match TypeId::of::() == TypeId::of::<::Refs>() { true => ready(None).boxed_local(), - false => async { Some(self.serialize(ctx, sink).await.to_vec()) }.boxed_local(), + false => async { Some(self.serialize(sink).await.to_vec()) }.boxed_local(), } } } @@ -318,16 +299,16 @@ pub(crate) struct ObjStore { } impl SysCtxEntry for ObjStore {} -pub async fn own(typ: TAtom) -> A { - let ctx = typ.untyped.ctx(); - let g = ctx.get_or_default::().objects.read().await; +pub async fn own(typ: &TAtom) -> A { + let g = ctx().get_or_default::().objects.read().await; let atom_id = typ.untyped.atom.drop.expect("Owned atoms always have a drop ID"); let dyn_atom = g.get(&atom_id).expect("Atom ID invalid; atom type probably not owned by this crate"); dyn_atom.as_any_ref().downcast_ref().cloned().expect("The ID should imply a type as well") } -pub async fn debug_print_obj_store(ctx: &SysCtx, show_atoms: bool) { +pub async fn debug_print_obj_store(show_atoms: bool) { + let ctx = ctx(); let store = ctx.get_or_default::(); let keys = store.objects.read().await.keys().cloned().collect_vec(); let mut message = "Atoms in store:".to_string(); @@ -342,7 +323,7 @@ pub async fn debug_print_obj_store(ctx: &SysCtx, show_atoms: bool) { }; let atom = clone_box(&**atom); std::mem::drop(g); - message += &format!("\n{k:?} -> {}", take_first(&atom.dyn_print(ctx.clone()).await, true)); + message += &format!("\n{k:?} -> {}", take_first(&atom.dyn_print().await, true)); } } eprintln!("{message}") diff --git a/orchid-extension/src/atom_thin.rs b/orchid-extension/src/atom_thin.rs index 2d737d1..e710169 100644 --- a/orchid-extension/src/atom_thin.rs +++ b/orchid-extension/src/atom_thin.rs @@ -15,20 +15,20 @@ use crate::atom::{ AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet, MethodSetBuilder, err_not_callable, err_not_command, get_info, }; +use crate::context::ctx; use crate::expr::Expr; use crate::gen_expr::{GExpr, bot}; -use crate::system::SysCtx; use crate::system_ctor::CtedObj; pub struct ThinVariant; impl AtomicVariant for ThinVariant {} impl> AtomicFeaturesImpl for A { fn _factory(self) -> AtomFactory { - AtomFactory::new(async move |ctx| { - let (id, _) = get_info::(ctx.get::().inst().card()); + AtomFactory::new(async move || { + let (id, _) = get_info::(ctx().get::().inst().card()); let mut buf = enc_vec(&id).await; self.encode(Pin::new(&mut buf)).await; - api::Atom { drop: None, data: api::AtomData(buf), owner: ctx.sys_id() } + api::Atom { drop: None, data: api::AtomData(buf), owner: ctx().sys_id() } }) } fn _info() -> Self::_Info { ThinAtomDynfo { msbuild: Self::reg_reqs(), ms: OnceCell::new() } } @@ -40,8 +40,8 @@ pub struct ThinAtomDynfo { ms: OnceCell>, } impl AtomDynfo for ThinAtomDynfo { - fn print<'a>(&self, AtomCtx(buf, _, ctx): AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit> { - Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.print(ctx).await }) + fn print<'a>(&self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit> { + Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.print().await }) } fn tid(&self) -> TypeId { TypeId::of::() } fn name(&self) -> &'static str { type_name::() } @@ -56,21 +56,21 @@ impl AtomDynfo for ThinAtomDynfo { } fn handle_req<'a, 'm1: 'a, 'm2: 'a>( &'a self, - AtomCtx(buf, _, sys): AtomCtx<'a>, + AtomCtx(buf, _): AtomCtx<'a>, key: Sym, req: Pin<&'m1 mut dyn AsyncRead>, rep: Pin<&'m2 mut dyn AsyncWrite>, ) -> LocalBoxFuture<'a, bool> { Box::pin(async move { - let ms = self.ms.get_or_init(self.msbuild.pack(sys.clone())).await; - ms.dispatch(&T::decode(Pin::new(&mut &buf[..])).await, sys, key, req, rep).await + let ms = self.ms.get_or_init(self.msbuild.pack()).await; + ms.dispatch(&T::decode(Pin::new(&mut &buf[..])).await, key, req, rep).await }) } fn command<'a>( &'a self, - AtomCtx(buf, _, ctx): AtomCtx<'a>, + AtomCtx(buf, _): AtomCtx<'a>, ) -> LocalBoxFuture<'a, OrcRes>> { - async move { T::decode(Pin::new(&mut &buf[..])).await.command(ctx).await }.boxed_local() + async move { T::decode(Pin::new(&mut &buf[..])).await.command().await }.boxed_local() } fn serialize<'a, 'b: 'a>( &'a self, @@ -82,19 +82,14 @@ impl AtomDynfo for ThinAtomDynfo { Some(Vec::new()) }) } - fn deserialize<'a>( - &'a self, - ctx: SysCtx, - data: &'a [u8], - refs: &'a [Expr], - ) -> LocalBoxFuture<'a, api::Atom> { + fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom> { assert!(refs.is_empty(), "Refs found when deserializing thin atom"); - Box::pin(async { T::decode(Pin::new(&mut &data[..])).await._factory().build(ctx).await }) + Box::pin(async { T::decode(Pin::new(&mut &data[..])).await._factory().build().await }) } - fn drop<'a>(&'a self, AtomCtx(buf, _, ctx): AtomCtx<'a>) -> LocalBoxFuture<'a, ()> { + fn drop<'a>(&'a self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, ()> { Box::pin(async move { - let string_self = T::decode(Pin::new(&mut &buf[..])).await.print(ctx.clone()).await; - writeln!(ctx.logger(), "Received drop signal for non-drop atom {string_self:?}"); + let string_self = T::decode(Pin::new(&mut &buf[..])).await.print().await; + writeln!(ctx().logger(), "Received drop signal for non-drop atom {string_self:?}"); }) } } @@ -104,14 +99,14 @@ pub trait ThinAtom: { #[allow(unused_variables)] fn call(&self, arg: Expr) -> impl Future { - async move { bot(err_not_callable(arg.ctx().i()).await) } + async move { bot(err_not_callable().await) } } #[allow(unused_variables)] - fn command(&self, ctx: SysCtx) -> impl Future>> { - async move { Err(err_not_command(ctx.i()).await) } + fn command(&self) -> impl Future>> { + async move { Err(err_not_command().await) } } #[allow(unused_variables)] - fn print(&self, ctx: SysCtx) -> impl Future { + fn print(&self) -> impl Future { async { format!("ThinAtom({})", type_name::()).into() } } } diff --git a/orchid-extension/src/context.rs b/orchid-extension/src/context.rs new file mode 100644 index 0000000..3d3f557 --- /dev/null +++ b/orchid-extension/src/context.rs @@ -0,0 +1,90 @@ +use std::any::{Any, TypeId, type_name}; +use std::fmt; +use std::num::NonZero; +use std::rc::Rc; + +use memo_map::MemoMap; +use orchid_base::builtin::Spawner; +use orchid_base::interner::Interner; +use orchid_base::logging::Logger; +use orchid_base::reqnot::ReqNot; +use task_local::task_local; + +use crate::api; +use crate::system_ctor::CtedObj; + +#[derive(Clone)] +pub struct SysCtx(Rc>>); +impl SysCtx { + pub fn new( + id: api::SysId, + i: Interner, + reqnot: ReqNot, + spawner: Spawner, + logger: Logger, + cted: CtedObj, + ) -> Self { + let this = Self(Rc::new(MemoMap::new())); + this.add(id).add(i).add(reqnot).add(spawner).add(logger).add(cted); + this + } + pub fn add(&self, t: T) -> &Self { + assert!(self.0.insert(TypeId::of::(), Box::new(t)), "Key already exists"); + self + } + pub fn get_or_insert(&self, f: impl FnOnce() -> T) -> &T { + (self.0.get_or_insert_owned(TypeId::of::(), || Box::new(f())).downcast_ref()) + .expect("Keyed by TypeId") + } + pub fn get_or_default(&self) -> &T { self.get_or_insert(T::default) } + pub fn try_get(&self) -> Option<&T> { + Some(self.0.get(&TypeId::of::())?.downcast_ref().expect("Keyed by TypeId")) + } + pub fn get(&self) -> &T { + self.try_get().unwrap_or_else(|| panic!("Context {} missing", type_name::())) + } + /// Shorthand to get the messaging link + pub fn reqnot(&self) -> &ReqNot { self.get::>() } + /// Shorthand to get the system ID + pub fn sys_id(&self) -> api::SysId { *self.get::() } + /// Spawn a task that will eventually be executed asynchronously + pub fn spawn(&self, f: impl Future + 'static) { + (self.get::())(Box::pin(CTX.scope(self.clone(), f))) + } + /// Shorthand to get the logger + pub fn logger(&self) -> &Logger { self.get::() } + /// Shorthand to get the constructed system object + pub fn cted(&self) -> &CtedObj { self.get::() } +} +impl fmt::Debug for SysCtx { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "SysCtx({:?})", self.sys_id()) + } +} +pub trait SysCtxEntry: 'static + Sized {} +impl SysCtxEntry for api::SysId {} +impl SysCtxEntry for ReqNot {} +impl SysCtxEntry for Spawner {} +impl SysCtxEntry for CtedObj {} +impl SysCtxEntry for Logger {} +impl SysCtxEntry for Interner {} + +task_local! { + static CTX: SysCtx; +} + +pub async fn with_ctx(ctx: SysCtx, f: F) -> F::Output { CTX.scope(ctx, f).await } +pub fn ctx() -> SysCtx { CTX.get() } + +/// Shorthand to get the [Interner] instance +pub fn i() -> Interner { ctx().get::().clone() } + +pub fn mock_ctx() -> SysCtx { + let ctx = SysCtx(Rc::default()); + ctx + .add(Logger::new(api::LogStrategy::StdErr)) + .add(Interner::new_master()) + .add::(Rc::new(|_| panic!("Cannot fork in test environment"))) + .add(api::SysId(NonZero::::MIN)); + ctx +} diff --git a/orchid-extension/src/conv.rs b/orchid-extension/src/conv.rs index 5a0d05e..d9e1c98 100644 --- a/orchid-extension/src/conv.rs +++ b/orchid-extension/src/conv.rs @@ -4,14 +4,13 @@ use std::pin::Pin; use dyn_clone::DynClone; use never::Never; use orchid_base::error::{OrcErrv, OrcRes, mk_errv}; -use orchid_base::interner::Interner; use orchid_base::location::Pos; use trait_set::trait_set; use crate::atom::{AtomicFeatures, ForeignAtom, TAtom, ToAtom}; +use crate::context::i; use crate::expr::Expr; use crate::gen_expr::{GExpr, atom, bot}; -use crate::system::{SysCtx, downcast_atom}; pub trait TryFromExpr: Sized { fn try_from_expr(expr: Expr) -> impl Future>; @@ -27,18 +26,14 @@ impl TryFromExpr for (T, U) { } } -async fn err_not_atom(pos: Pos, i: &Interner) -> OrcErrv { - mk_errv(i.i("Expected an atom").await, "This expression is not an atom", [pos]) -} - -async fn err_type(pos: Pos, i: &Interner) -> OrcErrv { - mk_errv(i.i("Type error").await, "The atom is a different type than expected", [pos]) +async fn err_not_atom(pos: Pos) -> OrcErrv { + mk_errv(i().i("Expected an atom").await, "This expression is not an atom", [pos]) } impl TryFromExpr for ForeignAtom { async fn try_from_expr(expr: Expr) -> OrcRes { match expr.atom().await { - Err(ex) => Err(err_not_atom(ex.data().await.pos.clone(), ex.ctx().i()).await), + Err(ex) => Err(err_not_atom(ex.data().await.pos.clone()).await), Ok(f) => Ok(f), } } @@ -47,27 +42,34 @@ impl TryFromExpr for ForeignAtom { impl TryFromExpr for TAtom { async fn try_from_expr(expr: Expr) -> OrcRes { let f = ForeignAtom::try_from_expr(expr).await?; - match downcast_atom::(f).await { + match f.clone().downcast::().await { Ok(a) => Ok(a), - Err(f) => Err(err_type(f.pos(), f.ctx().i()).await), + Err(e) => Err(e.mk_err().await), } } } -impl TryFromExpr for SysCtx { - async fn try_from_expr(expr: Expr) -> OrcRes { Ok(expr.ctx()) } -} - pub trait ToExpr { - fn to_expr(self) -> impl Future; + fn to_gen(self) -> impl Future; + fn to_expr(self) -> impl Future + where Self: Sized { + async { self.to_gen().await.create().await } + } } pub trait ToExprDyn { - fn to_expr_dyn<'a>(self: Box) -> Pin + 'a>> + fn to_gen_dyn<'a>(self: Box) -> Pin + 'a>> + where Self: 'a; + + fn to_expr_dyn<'a>(self: Box) -> Pin + 'a>> where Self: 'a; } impl ToExprDyn for T { - fn to_expr_dyn<'a>(self: Box) -> Pin + 'a>> + fn to_gen_dyn<'a>(self: Box) -> Pin + 'a>> + where Self: 'a { + Box::pin(self.to_gen()) + } + fn to_expr_dyn<'a>(self: Box) -> Pin + 'a>> where Self: 'a { Box::pin(self.to_expr()) } @@ -76,35 +78,39 @@ trait_set! { pub trait ClonableToExprDyn = ToExprDyn + DynClone; } impl ToExpr for Box { - async fn to_expr(self) -> GExpr { self.to_expr_dyn().await } + async fn to_gen(self) -> GExpr { self.to_gen_dyn().await } + async fn to_expr(self) -> Expr { self.to_expr_dyn().await } } impl ToExpr for Box { - async fn to_expr(self) -> GExpr { self.to_expr_dyn().await } + async fn to_gen(self) -> GExpr { self.to_gen_dyn().await } + async fn to_expr(self) -> Expr { self.to_expr_dyn().await } } impl Clone for Box { fn clone(&self) -> Self { dyn_clone::clone_box(&**self) } } impl ToExpr for GExpr { - async fn to_expr(self) -> GExpr { self } + async fn to_gen(self) -> GExpr { self } + async fn to_expr(self) -> Expr { self.create().await } } impl ToExpr for Expr { - async fn to_expr(self) -> GExpr { self.slot() } + async fn to_gen(self) -> GExpr { self.slot() } + async fn to_expr(self) -> Expr { self } } impl ToExpr for OrcRes { - async fn to_expr(self) -> GExpr { + async fn to_gen(self) -> GExpr { match self { Err(e) => bot(e), - Ok(t) => t.to_expr().await, + Ok(t) => t.to_gen().await, } } } impl ToExpr for A { - async fn to_expr(self) -> GExpr { atom(self) } + async fn to_gen(self) -> GExpr { atom(self) } } impl ToExpr for Never { - async fn to_expr(self) -> GExpr { match self {} } + async fn to_gen(self) -> GExpr { match self {} } } diff --git a/orchid-extension/src/coroutine_exec.rs b/orchid-extension/src/coroutine_exec.rs index 812b48d..6b5a76c 100644 --- a/orchid-extension/src/coroutine_exec.rs +++ b/orchid-extension/src/coroutine_exec.rs @@ -8,7 +8,6 @@ use futures::stream::{self, LocalBoxStream}; use futures::{FutureExt, SinkExt, StreamExt}; use never::Never; use orchid_base::error::OrcRes; -use orchid_base::format::{FmtCtx, FmtUnit}; use crate::atom::Atomic; use crate::atom_owned::{OwnedAtom, OwnedVariant}; @@ -23,7 +22,6 @@ enum Command { } struct BuilderCoroutineData { - name: Option, receiver: Mutex>, } @@ -36,11 +34,14 @@ impl BuilderCoroutine { None => panic!("Before the stream ends, we should have gotten a Halt"), Some(Command::Halt(expr)) => expr, Some(Command::Execute(expr, reply)) => call( - lambda(0, seq([arg(0)], call(Replier { reply, builder: self }.to_expr().await, [arg(0)]))), + lambda(0, [seq( + [arg(0)], + call(Replier { reply, builder: self }.to_gen().await, [arg(0)]), + )]), [expr], ), Some(Command::Register(expr, reply)) => - call(Replier { reply, builder: self }.to_expr().await, [expr]), + call(Replier { reply, builder: self }.to_gen().await, [expr]), } } } @@ -62,23 +63,13 @@ impl OwnedAtom for Replier { std::mem::drop(self.reply); self.builder.run().await } - async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - match &self.builder.0.name { - None => "BuilderCoroutine".into(), - Some(name) => format!("BuilderCoroutine({name})").into(), - } - } } -pub async fn exec( - debug: impl AsRef, - f: impl for<'a> AsyncFnOnce(ExecHandle<'a>) -> R + 'static, -) -> GExpr { +pub async fn exec(f: impl for<'a> AsyncFnOnce(ExecHandle<'a>) -> R + 'static) -> GExpr { let (cmd_snd, cmd_recv) = channel(0); - let halt = async { Command::Halt(f(ExecHandle(cmd_snd, PhantomData)).await.to_expr().await) } + let halt = async { Command::Halt(f(ExecHandle(cmd_snd, PhantomData)).await.to_gen().await) } .into_stream(); let coro = BuilderCoroutine(Rc::new(BuilderCoroutineData { - name: Some(debug.as_ref().to_string()), receiver: Mutex::new(stream::select(halt, cmd_recv).boxed_local()), })); coro.run().await @@ -90,12 +81,12 @@ pub struct ExecHandle<'a>(Sender, PhantomData<&'a ()>); impl ExecHandle<'_> { pub async fn exec(&mut self, val: impl ToExpr) -> OrcRes { let (reply_snd, mut reply_recv) = channel(1); - self.0.send(Command::Execute(val.to_expr().await, reply_snd)).await.expect(WEIRD_DROP_ERR); + self.0.send(Command::Execute(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR); T::try_from_expr(reply_recv.next().await.expect(WEIRD_DROP_ERR)).await } pub async fn register(&mut self, val: impl ToExpr) -> Expr { let (reply_snd, mut reply_recv) = channel(1); - self.0.send(Command::Register(val.to_expr().await, reply_snd)).await.expect(WEIRD_DROP_ERR); + self.0.send(Command::Register(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR); reply_recv.next().await.expect(WEIRD_DROP_ERR) } } diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index e3ea16b..00cc407 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -29,10 +29,11 @@ use trait_set::trait_set; use crate::api; use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId}; use crate::atom_owned::take_atom; +use crate::context::{SysCtx, ctx, i, with_ctx}; use crate::expr::{BorrowedExprStore, Expr, ExprHandle}; use crate::lexer::{LexContext, ekey_cascade, ekey_not_applicable}; use crate::parser::{PTokTree, ParsCtx, get_const, linev_into_api}; -use crate::system::{SysCtx, atom_by_idx}; +use crate::system::atom_by_idx; use crate::system_ctor::{CtedObj, DynSystemCtor}; use crate::tree::{LazyMemberFactory, TreeIntoApiCtxImpl}; @@ -62,7 +63,6 @@ pub struct SystemRecord { trait_set! { pub trait WithAtomRecordCallback<'a, T> = AsyncFnOnce( Box, - SysCtx, AtomTypeId, &'a [u8] ) -> T @@ -78,7 +78,7 @@ pub async fn with_atom_record<'a, F: Future, T>( let inst = ctx.get::().inst(); let id = AtomTypeId::decode(Pin::new(&mut data)).await; let atom_record = atom_by_idx(inst.card(), id.clone()).expect("Atom ID reserved"); - cb(atom_record, ctx, id, data).await + with_ctx(ctx, async move { cb(atom_record, id, data).await }).await } pub struct ExtensionOwner { @@ -157,7 +157,8 @@ pub fn extension_init( clone!(logger, get_ctx, init_ctx, systems_weak, interner_weak, decls, msg_logger); async move { let interner_cell = interner_weak.upgrade().expect("Interner dropped before request"); - let i = interner_cell.borrow().clone().expect("Request arrived before interner set"); + let interner = + interner_cell.borrow().clone().expect("Request arrived before interner set"); if !matches!(req, api::HostExtReq::AtomReq(api::AtomReq::AtomPrint(_))) { writeln!(msg_logger, "{} extension received request {req:?}", data.name); } @@ -169,153 +170,156 @@ pub fn extension_init( } hand.handle(&sys_drop, &()).await }, - api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) => { - let ctx = get_ctx(sys_id).await; - take_atom(atom, &ctx).await.dyn_free(ctx.clone()).await; - hand.handle(&atom_drop, &()).await - }, + api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) => + with_ctx(get_ctx(sys_id).await, async move { + take_atom(atom).await.dyn_free().await; + hand.handle(&atom_drop, &()).await + }) + .await, api::HostExtReq::Ping(ping @ api::Ping) => hand.handle(&ping, &()).await, api::HostExtReq::Sweep(sweep @ api::Sweep) => - hand.handle(&sweep, &i.sweep_replica().await).await, + hand.handle(&sweep, &interner.sweep_replica().await).await, api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => { let (sys_id, _) = (decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system)) .expect("NewSystem call received for invalid system"); let cted = data.systems[sys_id].new_system(&new_sys); - let lex_filter = - cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| { - char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned())) - }); - let lazy_members = Mutex::new(HashMap::new()); - let ctx = init_ctx(new_sys.id, cted.clone(), hand.reqnot()).await; - let const_root = stream::iter(cted.inst().dyn_env()) - .then(|mem| { - let lazy_mems = &lazy_members; - clone!(i, ctx; async move { - let name = i.i(&mem.name).await; - let mut tia_ctx = TreeIntoApiCtxImpl { - lazy_members: &mut *lazy_mems.lock().await, - sys: ctx, - basepath: &[], - path: Substack::Bottom.push(name.clone()), - }; - (name.to_api(), mem.kind.into_api(&mut tia_ctx).await) + with_ctx(init_ctx(new_sys.id, cted.clone(), hand.reqnot()).await, async move { + let lex_filter = + cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| { + char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned())) + }); + let lazy_members = Mutex::new(HashMap::new()); + let const_root = stream::iter(cted.inst().dyn_env().await) + .then(|mem| { + let lazy_mems = &lazy_members; + async move { + let name = i().i(&mem.name).await; + let mut tia_ctx = TreeIntoApiCtxImpl { + lazy_members: &mut *lazy_mems.lock().await, + basepath: &[], + path: Substack::Bottom.push(name.clone()), + }; + (name.to_api(), mem.kind.into_api(&mut tia_ctx).await) + } }) - }) - .collect() + .collect() + .await; + let prelude = + cted.inst().dyn_prelude().await.iter().map(|sym| sym.to_api()).collect(); + let record = SystemRecord { ctx: ctx(), lazy_members }; + let systems = systems_weak.upgrade().expect("System constructed during shutdown"); + systems.write().await.insert(new_sys.id, record); + let line_types = join_all( + (cted.inst().dyn_parsers().iter()) + .map(|p| async { interner.i(p.line_head()).await.to_api() }), + ) .await; - let prelude = - cted.inst().dyn_prelude(&i).await.iter().map(|sym| sym.to_api()).collect(); - let record = SystemRecord { ctx, lazy_members }; - let systems = systems_weak.upgrade().expect("System constructed during shutdown"); - systems.write().await.insert(new_sys.id, record); - let line_types = join_all( - (cted.inst().dyn_parsers().iter()) - .map(|p| async { i.i(p.line_head()).await.to_api() }), - ) - .await; - let response = api::NewSystemResponse { lex_filter, const_root, line_types, prelude }; - hand.handle(&new_sys, &response).await - }, - api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) => { - let sys_ctx = get_ctx(sys_id).await; - let systems = systems_weak.upgrade().expect("Member queried during shutdown"); - let systems_g = systems.read().await; - let mut lazy_members = - systems_g.get(&sys_id).expect("System not found").lazy_members.lock().await; - let (path, cb) = match lazy_members.insert(tree_id, MemberRecord::Res) { - None => panic!("Tree for ID not found"), - Some(MemberRecord::Res) => panic!("This tree has already been transmitted"), - Some(MemberRecord::Gen(path, cb)) => (path, cb), - }; - let tree = cb.build(Sym::new(path.clone(), &i).await.unwrap(), sys_ctx.clone()).await; - let mut tia_ctx = TreeIntoApiCtxImpl { - sys: sys_ctx, - path: Substack::Bottom, - basepath: &path, - lazy_members: &mut lazy_members, - }; - hand.handle(&get_tree, &tree.into_api(&mut tia_ctx).await).await + let response = + api::NewSystemResponse { lex_filter, const_root, line_types, prelude }; + hand.handle(&new_sys, &response).await + }) + .await }, + api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) => + with_ctx(get_ctx(sys_id).await, async move { + let systems = systems_weak.upgrade().expect("Member queried during shutdown"); + let systems_g = systems.read().await; + let mut lazy_members = + systems_g.get(&sys_id).expect("System not found").lazy_members.lock().await; + let (path, cb) = match lazy_members.insert(tree_id, MemberRecord::Res) { + None => panic!("Tree for ID not found"), + Some(MemberRecord::Res) => panic!("This tree has already been transmitted"), + Some(MemberRecord::Gen(path, cb)) => (path, cb), + }; + let tree = cb.build(Sym::new(path.clone(), &interner).await.unwrap()).await; + let mut tia_ctx = TreeIntoApiCtxImpl { + path: Substack::Bottom, + basepath: &path, + lazy_members: &mut lazy_members, + }; + hand.handle(&get_tree, &tree.into_api(&mut tia_ctx).await).await + }) + .await, api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => { let api::SysFwded(sys_id, payload) = fwd; let ctx = get_ctx(sys_id).await; - let sys = ctx.cted().inst(); - sys.dyn_request(hand, payload).await + with_ctx(ctx.clone(), async move { + let sys = ctx.cted().inst(); + sys.dyn_request(hand, payload).await + }) + .await }, - api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) => { - let mut sys_ctx = get_ctx(sys).await; - let text = Tok::from_api(text, &i).await; - let src = Sym::from_api(src, sys_ctx.i()).await; - let rep = Reporter::new(); - let expr_store = BorrowedExprStore::new(); - let trigger_char = text.chars().nth(pos as usize).unwrap(); - let ekey_na = ekey_not_applicable(&i).await; - let ekey_cascade = ekey_cascade(&i).await; - let lexers = sys_ctx.cted().inst().dyn_lexers(); - for lx in lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char)) { - let ctx = LexContext { - id, - pos, - text: &text, - src: src.clone(), - ctx: sys_ctx.clone(), - rep: &rep, - exprs: &expr_store, - }; - match lx.lex(&text[pos as usize..], &ctx).await { - Err(e) if e.any(|e| *e == ekey_na) => continue, - Err(e) => { - let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api())); - expr_store.dispose().await; - return hand.handle(&lex, &eopt).await; - }, - Ok((s, expr)) => { - let expr = expr.into_api(&mut (), &mut sys_ctx).await; - let pos = (text.len() - s.len()) as u32; - expr_store.dispose().await; - return hand.handle(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await; - }, + api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) => + with_ctx(get_ctx(sys).await, async move { + let text = Tok::from_api(text, &i()).await; + let src = Sym::from_api(src, &i()).await; + let rep = Reporter::new(); + let expr_store = BorrowedExprStore::new(); + let trigger_char = text.chars().nth(pos as usize).unwrap(); + let ekey_na = ekey_not_applicable().await; + let ekey_cascade = ekey_cascade().await; + let lexers = ctx().cted().inst().dyn_lexers(); + for lx in lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char)) + { + let ctx = LexContext::new(&expr_store, &text, id, pos, src.clone(), &rep); + match lx.lex(&text[pos as usize..], &ctx).await { + Err(e) if e.any(|e| *e == ekey_na) => continue, + Err(e) => { + let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api())); + expr_store.dispose().await; + return hand.handle(&lex, &eopt).await; + }, + Ok((s, expr)) => { + let expr = expr.into_api(&mut (), &mut ()).await; + let pos = (text.len() - s.len()) as u32; + expr_store.dispose().await; + return hand.handle(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await; + }, + } } - } - writeln!(logger, "Got notified about n/a character '{trigger_char}'"); - expr_store.dispose().await; - hand.handle(&lex, &None).await - }, + writeln!(logger, "Got notified about n/a character '{trigger_char}'"); + expr_store.dispose().await; + hand.handle(&lex, &None).await + }) + .await, api::HostExtReq::ParseLine(pline) => { let api::ParseLine { module, src, exported, comments, sys, line, idx } = &pline; - let ctx = get_ctx(*sys).await; - let parsers = ctx.cted().inst().dyn_parsers(); - let src = Sym::from_api(*src, ctx.i()).await; - let comments = - join_all(comments.iter().map(|c| Comment::from_api(c, src.clone(), &i))).await; - let expr_store = BorrowedExprStore::new(); - let mut from_api_ctx = (ctx.clone(), &expr_store); - let line: Vec = - ttv_from_api(line, &mut from_api_ctx, &mut (), &src, &i).await; - let snip = Snippet::new(line.first().expect("Empty line"), &line); - let parser = parsers[*idx as usize]; - let module = Sym::from_api(*module, ctx.i()).await; - let reporter = Reporter::new(); - let pctx = ParsCtx::new(ctx.clone(), module, &reporter); - let parse_res = parser.parse(pctx, *exported, comments, snip).await; - let o_line = match reporter.merge(parse_res) { - Err(e) => Err(e.to_api()), - Ok(t) => Ok(linev_into_api(t, ctx.clone()).await), - }; - mem::drop(line); - expr_store.dispose().await; - hand.handle(&pline, &o_line).await - }, - api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) => { - let ctx = get_ctx(sys).await; - let cnst = get_const(id, ctx.clone()).await; - hand.handle(fpc, &cnst.api_return(ctx).await).await + with_ctx(get_ctx(*sys).await, async { + let parsers = ctx().cted().inst().dyn_parsers(); + let src = Sym::from_api(*src, &i()).await; + let comments = + join_all(comments.iter().map(|c| Comment::from_api(c, src.clone(), &interner))) + .await; + let expr_store = BorrowedExprStore::new(); + let line: Vec = + ttv_from_api(line, &mut &expr_store, &mut (), &src, &i()).await; + let snip = Snippet::new(line.first().expect("Empty line"), &line); + let parser = parsers[*idx as usize]; + let module = Sym::from_api(*module, &i()).await; + let reporter = Reporter::new(); + let pctx = ParsCtx::new(module, &reporter); + let parse_res = parser.parse(pctx, *exported, comments, snip).await; + let o_line = match reporter.merge(parse_res) { + Err(e) => Err(e.to_api()), + Ok(t) => Ok(linev_into_api(t).await), + }; + mem::drop(line); + expr_store.dispose().await; + hand.handle(&pline, &o_line).await + }) + .await }, + api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) => + with_ctx(get_ctx(sys).await, async move { + let cnst = get_const(id).await; + hand.handle(fpc, &cnst.serialize().await).await + }) + .await, api::HostExtReq::AtomReq(atom_req) => { let atom = atom_req.get_atom(); let atom_req = atom_req.clone(); - with_atom_record(&get_ctx, atom, async move |nfo, ctx, id, buf| { - let actx = AtomCtx(buf, atom.drop, ctx.clone()); + with_atom_record(&get_ctx, atom, async move |nfo, id, buf| { + let actx = AtomCtx(buf, atom.drop); match &atom_req { api::AtomReq::SerializeAtom(ser) => { let mut buf = enc_vec(&id).await; @@ -334,7 +338,7 @@ pub fn extension_init( api::AtomReq::Fwded(fwded) => { let api::Fwded(_, key, payload) = &fwded; let mut reply = Vec::new(); - let key = Sym::from_api(*key, &i).await; + let key = Sym::from_api(*key, &interner).await; let some = nfo .handle_req( actx, @@ -347,18 +351,18 @@ pub fn extension_init( }, api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => { let expr_store = BorrowedExprStore::new(); - let expr_handle = ExprHandle::borrowed(ctx.clone(), *arg, &expr_store); + let expr_handle = ExprHandle::borrowed(*arg, &expr_store); let ret = nfo.call_ref(actx, Expr::from_handle(expr_handle.clone())).await; - let api_expr = ret.api_return(ctx.clone()).await; + let api_expr = ret.serialize().await; mem::drop(expr_handle); expr_store.dispose().await; hand.handle(call, &api_expr).await }, api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => { let expr_store = BorrowedExprStore::new(); - let expr_handle = ExprHandle::borrowed(ctx.clone(), *arg, &expr_store); + let expr_handle = ExprHandle::borrowed(*arg, &expr_store); let ret = nfo.call(actx, Expr::from_handle(expr_handle.clone())).await; - let api_expr = ret.api_return(ctx.clone()).await; + let api_expr = ret.serialize().await; mem::drop(expr_handle); expr_store.dispose().await; hand.handle(call, &api_expr).await @@ -368,7 +372,7 @@ pub fn extension_init( Ok(opt) => match opt { None => hand.handle(cmd, &Ok(api::NextStep::Halt)).await, Some(cont) => { - let cont = cont.api_return(ctx.clone()).await; + let cont = cont.serialize().await; hand.handle(cmd, &Ok(api::NextStep::Continue(cont))).await }, }, @@ -383,12 +387,12 @@ pub fn extension_init( let ctx = get_ctx(*sys).await; // SAFETY: deserialization implicitly grants ownership to previously owned exprs let refs = (refs.iter()) - .map(|tk| Expr::from_handle(ExprHandle::deserialize(ctx.clone(), *tk))) + .map(|tk| Expr::from_handle(ExprHandle::deserialize(*tk))) .collect_vec(); let id = AtomTypeId::decode(Pin::new(&mut read)).await; let inst = ctx.cted().inst(); let nfo = atom_by_idx(inst.card(), id).expect("Deserializing atom with invalid ID"); - hand.handle(&deser, &nfo.deserialize(ctx.clone(), read, &refs).await).await + hand.handle(&deser, &nfo.deserialize(read, &refs).await).await }, } } diff --git a/orchid-extension/src/expr.rs b/orchid-extension/src/expr.rs index 17ea4e1..8297046 100644 --- a/orchid-extension/src/expr.rs +++ b/orchid-extension/src/expr.rs @@ -13,8 +13,8 @@ use orchid_base::reqnot::Requester; use crate::api; use crate::atom::ForeignAtom; +use crate::context::{ctx, i}; use crate::gen_expr::{GExpr, GExprKind}; -use crate::system::SysCtx; pub struct BorrowedExprStore(RefCell>>>); impl BorrowedExprStore { @@ -22,7 +22,7 @@ impl BorrowedExprStore { pub async fn dispose(self) { let elements = self.0.borrow_mut().take().unwrap(); for handle in elements { - handle.drop_one().await + handle.on_borrow_expire().await } } } @@ -34,58 +34,67 @@ impl Drop for BorrowedExprStore { } } -#[derive(destructure)] -pub struct ExprHandle { - pub tk: api::ExprTicket, - pub ctx: SysCtx, -} +#[derive(destructure, PartialEq, Eq, Hash)] +pub struct ExprHandle(api::ExprTicket); impl ExprHandle { - /// This function does not signal to take ownership of the expr. - pub fn borrowed(ctx: SysCtx, tk: api::ExprTicket, store: &BorrowedExprStore) -> Rc { - let this = Rc::new(Self { ctx, tk }); + /// Do not signal to take ownership of the expr. Instead, the + /// [BorrowedExprStore] signifies the lifetime of the borrow, and when it is + /// freed, it signals to take ownership of any exprs that ended up outliving + /// it. It is used to receive exprs sent via [ExprHandle::ticket] as an + /// optimization over [ExprHandle::from_ticket] + pub fn borrowed(tk: api::ExprTicket, store: &BorrowedExprStore) -> Rc { + let this = Rc::new(Self(tk)); store.0.borrow_mut().as_mut().unwrap().insert(this.clone()); this } - pub fn deserialize(ctx: SysCtx, tk: api::ExprTicket) -> Rc { Rc::new(Self { ctx, tk }) } - pub fn get_ctx(&self) -> SysCtx { self.ctx.clone() } - /// Drop one instance of the handle silently; if it's the last one, do - /// nothing, otherwise send an Acquire - pub async fn drop_one(self: Rc) { - match Rc::try_unwrap(self) { - Err(rc) => rc.ctx.reqnot().notify(api::Acquire(rc.ctx.sys_id(), rc.tk)).await, - Ok(hand) => { - // avoid calling destructor - hand.destructure(); - }, - } + /// This function takes over the loose reference pre-created via + /// [ExprHandle::serialize] in the sender. It must therefore pair up with a + /// corresponding call to that function. + pub fn deserialize(tk: api::ExprTicket) -> Rc { Rc::new(Self(tk)) } + /// This function takes ownership of a borrowed expr sent via + /// [ExprHandle::ticket] and signals immediately to record that ownership. It + /// is used in place of [ExprHandle::borrowed] when it's impractical to + /// determine how long the borrow will live. + /// + /// # Safety + /// + /// You need to ensure that the [api::Acquire] sent by this function arrives + /// before the borrow expires, so you still need a borrow delimited by some + /// message you will send in the future. + pub async fn from_ticket(tk: api::ExprTicket) -> Rc { + let store = BorrowedExprStore::new(); + let expr = Self::borrowed(tk, &store); + store.dispose().await; + expr } + /// The raw ticket used in messages. If you want to transfer ownership via the + /// ticket, you should use [ExprHandle::serialize]. Only send this if you want + /// to lend the expr, and you expect the receiver to use + /// [ExprHandle::borrowed] or [ExprHandle::from_ticket] + pub fn ticket(&self) -> api::ExprTicket { self.0 } + async fn send_acq(&self) { ctx().reqnot().notify(api::Acquire(ctx().sys_id(), self.0)).await } + /// If this is the last one reference, do nothing, otherwise send an Acquire + pub async fn on_borrow_expire(self: Rc) { self.serialize().await; } /// Drop the handle and get the ticket without a release notification. /// Use this with messages that imply ownership transfer. This function is /// safe because abusing it is a memory leak. - pub fn serialize(self) -> api::ExprTicket { self.destructure().0 } -} -impl Eq for ExprHandle {} -impl PartialEq for ExprHandle { - fn eq(&self, other: &Self) -> bool { - self.ctx.sys_id() == other.ctx.sys_id() && self.tk == other.tk - } -} -impl Hash for ExprHandle { - fn hash(&self, state: &mut H) { - self.ctx.sys_id().hash(state); - self.tk.hash(state); + pub async fn serialize(self: Rc) -> api::ExprTicket { + match Rc::try_unwrap(self) { + Err(rc) => { + rc.send_acq().await; + rc.0 + }, + Ok(hand) => hand.destructure().0, + } } } impl fmt::Debug for ExprHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ExprHandle({})", self.tk.0) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ExprHandle({})", self.0.0) } } impl Drop for ExprHandle { fn drop(&mut self) { - let notif = api::Release(self.ctx.sys_id(), self.tk); - let reqnot = self.ctx.reqnot().clone(); - self.ctx.spawner()(Box::pin(async move { reqnot.notify(notif).await })) + let notif = api::Release(ctx().sys_id(), self.0); + ctx().spawn(async move { ctx().reqnot().clone().notify(notif).await }) } } @@ -96,19 +105,23 @@ pub struct Expr { } impl Expr { pub fn from_handle(handle: Rc) -> Self { Self { handle, data: Rc::default() } } - pub fn new(handle: Rc, d: ExprData) -> Self { + pub fn from_data(handle: Rc, d: ExprData) -> Self { Self { handle, data: Rc::new(OnceCell::from(d)) } } - + /// Creates an instance without incrementing the reference count. This is + /// only safe to be called on a reference created with an [Expr::serialize] + /// call which created the loose reference it can take ownership of. + pub async fn deserialize(tk: api::ExprTicket) -> Self { + Self::from_handle(ExprHandle::deserialize(tk)) + } pub async fn data(&self) -> &ExprData { (self.data.get_or_init(async { - let details = self.handle.ctx.reqnot().request(api::Inspect { target: self.handle.tk }).await; - let pos = Pos::from_api(&details.location, self.handle.ctx.i()).await; + let details = ctx().reqnot().request(api::Inspect { target: self.handle.ticket() }).await; + let pos = Pos::from_api(&details.location, &i()).await; let kind = match details.kind { api::InspectedKind::Atom(a) => ExprKind::Atom(ForeignAtom::new(self.handle.clone(), a, pos.clone())), - api::InspectedKind::Bottom(b) => - ExprKind::Bottom(OrcErrv::from_api(&b, self.handle.ctx.i()).await), + api::InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b, &i()).await), api::InspectedKind::Opaque => ExprKind::Opaque, }; ExprData { pos, kind } @@ -122,20 +135,22 @@ impl Expr { } } pub fn handle(&self) -> Rc { self.handle.clone() } - pub fn ctx(&self) -> SysCtx { self.handle.ctx.clone() } pub fn slot(&self) -> GExpr { GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(self.clone()) } } + /// Increments the refcount to ensure that the ticket remains valid even if + /// the handle is freed. To avoid a leak, [Expr::deserialize] must eventually + /// be called. + pub async fn serialize(self) -> api::ExprTicket { self.handle.serialize().await } } impl Format for Expr { async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { match &self.data().await.kind { ExprKind::Opaque => "OPAQUE".to_string().into(), ExprKind::Bottom(b) => format!("Bottom({b})").into(), - ExprKind::Atom(a) => FmtUnit::from_api( - &self.handle.ctx.reqnot().request(api::ExtAtomPrint(a.atom.clone())).await, - ), + ExprKind::Atom(a) => + FmtUnit::from_api(&ctx().reqnot().request(api::ExtAtomPrint(a.atom.clone())).await), } } } diff --git a/orchid-extension/src/func_atom.rs b/orchid-extension/src/func_atom.rs index 907ccb6..632233a 100644 --- a/orchid-extension/src/func_atom.rs +++ b/orchid-extension/src/func_atom.rs @@ -19,11 +19,11 @@ use trait_set::trait_set; use crate::atom::Atomic; use crate::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; +use crate::context::{SysCtxEntry, ctx, i}; use crate::conv::ToExpr; use crate::coroutine_exec::{ExecHandle, exec}; use crate::expr::Expr; use crate::gen_expr::GExpr; -use crate::system::{SysCtx, SysCtxEntry}; trait_set! { trait FunCB = Fn(Vec) -> LocalBoxFuture<'static, OrcRes> + 'static; @@ -43,14 +43,11 @@ struct FunRecord { fun: Rc, } -fn process_args>( - debug: impl AsRef + Clone + 'static, - f: F, -) -> FunRecord { +fn process_args>(f: F) -> FunRecord { let argtyps = F::argtyps(); let fun = Rc::new(move |v: Vec| { clone!(f, v mut); - exec(debug.clone(), async move |mut hand| { + exec(async move |mut hand| { let mut norm_args = Vec::with_capacity(v.len()); for (expr, typ) in v.into_iter().zip(argtyps) { if *typ != TypeId::of::() { @@ -77,13 +74,14 @@ pub(crate) struct Fun { record: FunRecord, } impl Fun { - pub async fn new>(path: Sym, ctx: SysCtx, f: F) -> Self { + pub async fn new>(path: Sym, f: F) -> Self { + let ctx = ctx(); let funs: &FunsCtx = ctx.get_or_default(); let mut fung = funs.0.lock().await; let record = if let Some(record) = fung.get(&path) { record.clone() } else { - let record = process_args(path.to_string(), f); + let record = process_args(f); fung.insert(path.clone(), record.clone()); record }; @@ -101,20 +99,19 @@ impl OwnedAtom for Fun { async fn call_ref(&self, arg: Expr) -> GExpr { let new_args = self.args.iter().cloned().chain([arg]).collect_vec(); if new_args.len() == self.record.argtyps.len() { - (self.record.fun)(new_args).await.to_expr().await + (self.record.fun)(new_args).await.to_gen().await } else { - Self { args: new_args, record: self.record.clone(), path: self.path.clone() }.to_expr().await + Self { args: new_args, record: self.record.clone(), path: self.path.clone() }.to_gen().await } } async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await } - async fn serialize(&self, _: SysCtx, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs { + async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs { self.path.to_api().encode(write).await; self.args.clone() } - async fn deserialize(mut ctx: impl DeserializeCtx, args: Self::Refs) -> Self { - let sys = ctx.sys(); - let path = Sym::from_api(ctx.decode().await, sys.i()).await; - let record = (sys.get::().0.lock().await.get(&path)) + async fn deserialize(mut ds_cx: impl DeserializeCtx, args: Self::Refs) -> Self { + let path = Sym::from_api(ds_cx.decode().await, &i()).await; + let record = (ctx().get::().0.lock().await.get(&path)) .expect("Function missing during deserialization") .clone(); Self { args, path, record } @@ -134,8 +131,8 @@ pub struct Lambda { record: FunRecord, } impl Lambda { - pub fn new>(debug: impl AsRef + Clone + 'static, f: F) -> Self { - Self { args: vec![], record: process_args(debug, f) } + pub fn new>(f: F) -> Self { + Self { args: vec![], record: process_args(f) } } } impl Atomic for Lambda { @@ -148,9 +145,9 @@ impl OwnedAtom for Lambda { async fn call_ref(&self, arg: Expr) -> GExpr { let new_args = self.args.iter().cloned().chain([arg]).collect_vec(); if new_args.len() == self.record.argtyps.len() { - (self.record.fun)(new_args).await.to_expr().await + (self.record.fun)(new_args).await.to_gen().await } else { - Self { args: new_args, record: self.record.clone() }.to_expr().await + Self { args: new_args, record: self.record.clone() }.to_gen().await } } async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await } @@ -182,7 +179,7 @@ mod expr_func_derives { async fn apply<'a>(&self, _: ExecHandle<'a>, v: Vec) -> OrcRes { assert_eq!(v.len(), Self::argtyps().len(), "Arity mismatch"); let [$([< $t:lower >],)*] = v.try_into().unwrap_or_else(|_| panic!("Checked above")); - Ok(self($($t::try_from_expr([< $t:lower >]).await?,)*).await.to_expr().await) + Ok(self($($t::try_from_expr([< $t:lower >]).await?,)*).await.to_gen().await) } } } diff --git a/orchid-extension/src/gen_expr.rs b/orchid-extension/src/gen_expr.rs index b78a591..a04e57a 100644 --- a/orchid-extension/src/gen_expr.rs +++ b/orchid-extension/src/gen_expr.rs @@ -6,12 +6,13 @@ use orchid_base::error::{OrcErr, OrcErrv}; use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; use orchid_base::location::Pos; use orchid_base::name::Sym; +use orchid_base::reqnot::Requester; use orchid_base::{match_mapping, tl_cache}; use crate::api; use crate::atom::{AtomFactory, ToAtom}; +use crate::context::ctx; use crate::expr::Expr; -use crate::system::SysCtx; #[derive(Clone, Debug)] pub struct GExpr { @@ -19,29 +20,32 @@ pub struct GExpr { pub pos: Pos, } impl GExpr { - pub async fn api_return(self, ctx: SysCtx) -> api::Expression { + /// Release notifications will not be sent for the slots. Use this with + /// messages that imply ownership transfer + pub async fn serialize(self) -> api::Expression { if let GExprKind::Slot(ex) = self.kind { let hand = ex.handle(); mem::drop(ex); api::Expression { location: api::Location::SlotTarget, - kind: match Rc::try_unwrap(hand) { - Ok(h) => api::ExpressionKind::Slot { tk: h.serialize(), by_value: true }, - Err(rc) => api::ExpressionKind::Slot { tk: rc.tk, by_value: false }, - }, + // an instance is leaked here, we must take ownership of it when we receive this + kind: api::ExpressionKind::Slot(hand.serialize().await), } } else { api::Expression { location: api::Location::Inherit, - kind: self.kind.api_return(ctx).boxed_local().await, + kind: self.kind.serialize().boxed_local().await, } } } pub fn at(self, pos: Pos) -> Self { GExpr { pos, kind: self.kind } } + pub async fn create(self) -> Expr { + Expr::deserialize(ctx().reqnot().request(api::Create(self.serialize().await)).await).await + } } impl Format for GExpr { async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - self.kind.print(c).await + self.kind.print(c).boxed_local().await } } @@ -57,21 +61,21 @@ pub enum GExprKind { Bottom(OrcErrv), } impl GExprKind { - pub async fn api_return(self, ctx: SysCtx) -> api::ExpressionKind { + pub async fn serialize(self) -> api::ExpressionKind { match_mapping!(self, Self => api::ExpressionKind { Call( - f => Box::new(f.api_return(ctx.clone()).await), - x => Box::new(x.api_return(ctx).await) + f => Box::new(f.serialize().await), + x => Box::new(x.serialize().await) ), Seq( - a => Box::new(a.api_return(ctx.clone()).await), - b => Box::new(b.api_return(ctx).await) + a => Box::new(a.serialize().await), + b => Box::new(b.serialize().await) ), - Lambda(arg, body => Box::new(body.api_return(ctx).await)), + Lambda(arg, body => Box::new(body.serialize().await)), Arg(arg), Const(name.to_api()), Bottom(err.to_api()), - NewAtom(fac.clone().build(ctx).await), + NewAtom(fac.clone().build().await), } { Self::Slot(_) => panic!("processed elsewhere") }) @@ -118,7 +122,7 @@ pub fn seq(deps: impl IntoIterator, val: GExpr) -> GExpr { pub fn arg(n: u64) -> GExpr { inherit(GExprKind::Arg(n)) } -pub fn lambda(n: u64, b: GExpr) -> GExpr { inherit(GExprKind::Lambda(n, Box::new(b))) } +pub fn lambda(n: u64, [b]: [GExpr; 1]) -> GExpr { inherit(GExprKind::Lambda(n, Box::new(b))) } pub fn call(f: GExpr, argv: impl IntoIterator) -> GExpr { (argv.into_iter()).fold(f, |f, x| inherit(GExprKind::Call(Box::new(f), Box::new(x)))) diff --git a/orchid-extension/src/lexer.rs b/orchid-extension/src/lexer.rs index b41c1b2..e62af31 100644 --- a/orchid-extension/src/lexer.rs +++ b/orchid-extension/src/lexer.rs @@ -12,38 +12,48 @@ use orchid_base::parse::ParseCtx; use orchid_base::reqnot::Requester; use crate::api; +use crate::context::{ctx, i}; use crate::expr::BorrowedExprStore; use crate::parser::PTokTree; -use crate::system::SysCtx; use crate::tree::GenTokTree; -pub async fn ekey_cascade(i: &Interner) -> Tok { - i.i("An error cascading from a recursive call").await +pub async fn ekey_cascade() -> Tok { + i().i("An error cascading from a recursive call").await } -pub async fn ekey_not_applicable(i: &Interner) -> Tok { - i.i("Pseudo-error to communicate that the current branch in a dispatch doesn't apply").await +pub async fn ekey_not_applicable() -> Tok { + i().i("Pseudo-error to communicate that the current branch in a dispatch doesn't apply").await } const MSG_INTERNAL_ERROR: &str = "This error is a sentinel for the extension library.\ it should not be emitted by the extension."; -pub async fn err_cascade(i: &Interner) -> OrcErrv { - mk_errv(ekey_cascade(i).await, MSG_INTERNAL_ERROR, [Pos::None]) +pub async fn err_cascade() -> OrcErrv { + mk_errv(ekey_cascade().await, MSG_INTERNAL_ERROR, [Pos::None]) } -pub async fn err_not_applicable(i: &Interner) -> OrcErrv { - mk_errv(ekey_not_applicable(i).await, MSG_INTERNAL_ERROR, [Pos::None]) +pub async fn err_not_applicable() -> OrcErrv { + mk_errv(ekey_not_applicable().await, MSG_INTERNAL_ERROR, [Pos::None]) } pub struct LexContext<'a> { pub(crate) exprs: &'a BorrowedExprStore, - pub ctx: SysCtx, pub text: &'a Tok, pub id: api::ParsId, pub pos: u32, + i: Interner, pub(crate) src: Sym, pub(crate) rep: &'a Reporter, } impl<'a> LexContext<'a> { + pub fn new( + exprs: &'a BorrowedExprStore, + text: &'a Tok, + id: api::ParsId, + pos: u32, + src: Sym, + rep: &'a Reporter, + ) -> Self { + Self { exprs, i: i(), id, pos, rep, src, text } + } pub fn src(&self) -> &Sym { &self.src } /// This function returns [PTokTree] because it can never return /// [orchid_base::tree::Token::NewExpr]. You can use @@ -51,17 +61,10 @@ impl<'a> LexContext<'a> { /// for embedding in the return value. pub async fn recurse(&self, tail: &'a str) -> OrcRes<(&'a str, PTokTree)> { let start = self.pos(tail); - let Some(lx) = self.ctx.reqnot().request(api::SubLex { pos: start, id: self.id }).await else { - return Err(err_cascade(self.ctx.i()).await); + let Some(lx) = ctx().reqnot().request(api::SubLex { pos: start, id: self.id }).await else { + return Err(err_cascade().await); }; - let tree = PTokTree::from_api( - &lx.tree, - &mut (self.ctx.clone(), self.exprs), - &mut (), - &self.src, - self.ctx.i(), - ) - .await; + let tree = PTokTree::from_api(&lx.tree, &mut { self.exprs }, &mut (), &self.src, &i()).await; Ok((&self.text[lx.pos as usize..], tree)) } @@ -75,7 +78,7 @@ impl<'a> LexContext<'a> { } } impl ParseCtx for LexContext<'_> { - fn i(&self) -> &Interner { self.ctx.i() } + fn i(&self) -> &Interner { &self.i } fn rep(&self) -> &Reporter { self.rep } } @@ -83,7 +86,7 @@ pub trait Lexer: Send + Sync + Sized + Default + 'static { const CHAR_FILTER: &'static [RangeInclusive]; fn lex<'a>( tail: &'a str, - ctx: &'a LexContext<'a>, + lctx: &'a LexContext<'a>, ) -> impl Future>; } diff --git a/orchid-extension/src/lib.rs b/orchid-extension/src/lib.rs index 8e16689..41db629 100644 --- a/orchid-extension/src/lib.rs +++ b/orchid-extension/src/lib.rs @@ -11,6 +11,7 @@ pub mod func_atom; pub mod gen_expr; pub mod lexer; // pub mod msg; +pub mod context; pub mod other_system; pub mod parser; pub mod reflection; diff --git a/orchid-extension/src/other_system.rs b/orchid-extension/src/other_system.rs index e6d4b62..c7043df 100644 --- a/orchid-extension/src/other_system.rs +++ b/orchid-extension/src/other_system.rs @@ -1,15 +1,12 @@ -use std::marker::PhantomData; -use std::mem::size_of; - use crate::api; use crate::system::{DynSystemCard, SystemCard}; pub struct SystemHandle { - pub(crate) _card: PhantomData, + pub(crate) card: C, pub(crate) id: api::SysId, } impl SystemHandle { - pub(crate) fn new(id: api::SysId) -> Self { Self { _card: PhantomData, id } } + pub(crate) fn new(id: api::SysId) -> Self { Self { card: C::default(), id } } pub fn id(&self) -> api::SysId { self.id } } impl Clone for SystemHandle { @@ -21,16 +18,7 @@ pub trait DynSystemHandle { fn get_card(&self) -> &dyn DynSystemCard; } -pub fn leak_card() -> &'static T { - const { - if 0 != size_of::() { - panic!("Attempted to leak positively sized Card. Card types must always be zero-sized"); - } - } - Box::leak(Box::default()) -} - impl DynSystemHandle for SystemHandle { fn id(&self) -> api::SysId { self.id } - fn get_card(&self) -> &'static dyn DynSystemCard { leak_card::() } + fn get_card(&self) -> &dyn DynSystemCard { &self.card } } diff --git a/orchid-extension/src/parser.rs b/orchid-extension/src/parser.rs index 5a66f1e..a1ad42c 100644 --- a/orchid-extension/src/parser.rs +++ b/orchid-extension/src/parser.rs @@ -16,10 +16,10 @@ use orchid_base::reqnot::Requester; use orchid_base::tree::{TokTree, Token, ttv_into_api}; use crate::api; +use crate::context::{SysCtxEntry, ctx, i}; use crate::conv::ToExpr; use crate::expr::Expr; use crate::gen_expr::GExpr; -use crate::system::{SysCtx, SysCtxEntry}; use crate::tree::{GenTok, GenTokTree}; pub type PTok = Token; @@ -81,19 +81,18 @@ pub type ParserObj = &'static dyn DynParser; pub struct ParsCtx<'a> { _parse: PhantomData<&'a mut ()>, - ctx: SysCtx, module: Sym, reporter: &'a Reporter, + i: Interner, } impl<'a> ParsCtx<'a> { - pub(crate) fn new(ctx: SysCtx, module: Sym, reporter: &'a Reporter) -> Self { - Self { _parse: PhantomData, ctx, module, reporter } + pub(crate) fn new(module: Sym, reporter: &'a Reporter) -> Self { + Self { _parse: PhantomData, module, reporter, i: i() } } - pub fn ctx(&self) -> &SysCtx { &self.ctx } pub fn module(&self) -> Sym { self.module.clone() } } impl ParseCtx for ParsCtx<'_> { - fn i(&self) -> &Interner { self.ctx.i() } + fn i(&self) -> &Interner { &self.i } fn rep(&self) -> &Reporter { self.reporter } } @@ -118,7 +117,7 @@ impl ParsedLine { name: Tok, f: F, ) -> Self { - let cb = Box::new(|ctx| async move { f(ctx).await.to_expr().await }.boxed_local()); + let cb = Box::new(|ctx| async move { f(ctx).await.to_gen().await }.boxed_local()); let kind = ParsedLineKind::Mem(ParsedMem { name, exported, kind: ParsedMemKind::Const(cb) }); let comments = comments.into_iter().cloned().collect(); ParsedLine { comments, sr: sr.clone(), kind } @@ -136,7 +135,7 @@ impl ParsedLine { let comments = comments.into_iter().cloned().collect(); ParsedLine { comments, sr: sr.clone(), kind: line_kind } } - pub async fn into_api(self, mut ctx: SysCtx) -> api::ParsedLine { + pub async fn into_api(self) -> api::ParsedLine { api::ParsedLine { comments: self.comments.into_iter().map(|c| c.to_api()).collect(), source_range: self.sr.to_api(), @@ -146,23 +145,23 @@ impl ParsedLine { exported: mem.exported, kind: match mem.kind { ParsedMemKind::Const(cb) => api::ParsedMemberKind::Constant(api::ParsedConstId( - ctx.get_or_default::().consts.add(cb).id(), + ctx().get_or_default::().consts.add(cb).id(), )), ParsedMemKind::Mod { lines, use_prelude } => api::ParsedMemberKind::Module { - lines: linev_into_api(lines, ctx).boxed_local().await, + lines: linev_into_api(lines).boxed_local().await, use_prelude, }, }, }), ParsedLineKind::Rec(tv) => - api::ParsedLineKind::Recursive(ttv_into_api(tv, &mut (), &mut ctx).await), + api::ParsedLineKind::Recursive(ttv_into_api(tv, &mut (), &mut ()).await), }, } } } -pub(crate) async fn linev_into_api(v: Vec, ctx: SysCtx) -> Vec { - join_all(v.into_iter().map(|l| l.into_api(ctx.clone()))).await +pub(crate) async fn linev_into_api(v: Vec) -> Vec { + join_all(v.into_iter().map(|l| l.into_api())).await } pub enum ParsedLineKind { @@ -183,26 +182,23 @@ pub enum ParsedMemKind { #[derive(Clone)] pub struct ConstCtx { - ctx: SysCtx, constid: api::ParsedConstId, } impl ConstCtx { - pub fn ctx(&self) -> &SysCtx { &self.ctx } - pub fn i(&self) -> &Interner { self.ctx.i() } pub fn names<'b>( &'b self, names: impl IntoIterator + 'b, ) -> impl Stream> + 'b { let resolve_names = api::ResolveNames { constid: self.constid, - sys: self.ctx.sys_id(), + sys: ctx().sys_id(), names: names.into_iter().map(|n| n.to_api()).collect_vec(), }; stream(async |mut cx| { - for name_opt in self.ctx.reqnot().request(resolve_names).await { + for name_opt in ctx().reqnot().request(resolve_names).await { cx.emit(match name_opt { - Err(e) => Err(OrcErrv::from_api(&e, self.ctx.i()).await), - Ok(name) => Ok(Sym::from_api(name, self.ctx.i()).await), + Err(e) => Err(OrcErrv::from_api(&e, &i()).await), + Ok(name) => Ok(Sym::from_api(name, &i()).await), }) .await } @@ -213,9 +209,9 @@ impl ConstCtx { } } -pub(crate) async fn get_const(id: api::ParsedConstId, ctx: SysCtx) -> GExpr { - let ent = ctx.get_or_default::(); - let rec = ent.consts.get(id.0).expect("Bad ID or double read of parsed const"); - let ctx = ConstCtx { constid: id, ctx: ctx.clone() }; - rec.remove()(ctx).await +pub(crate) async fn get_const(id: api::ParsedConstId) -> GExpr { + let cb = (ctx().get_or_default::().consts.get(id.0)) + .expect("Bad ID or double read of parsed const") + .remove(); + cb(ConstCtx { constid: id }).await } diff --git a/orchid-extension/src/reflection.rs b/orchid-extension/src/reflection.rs index dc6d7ea..c623b10 100644 --- a/orchid-extension/src/reflection.rs +++ b/orchid-extension/src/reflection.rs @@ -9,7 +9,7 @@ use orchid_base::name::{NameLike, VPath}; use orchid_base::reqnot::Requester; use crate::api; -use crate::system::{SysCtx, SysCtxEntry, WeakSysCtx}; +use crate::context::{SysCtxEntry, ctx, i}; #[derive(Debug)] pub struct ReflMemData { @@ -33,37 +33,33 @@ pub enum ReflMemKind { pub struct ReflModData { inferred: Mutex, path: VPath, - ctx: WeakSysCtx, members: MemoMap, ReflMem>, } #[derive(Clone, Debug)] pub struct ReflMod(Rc); impl ReflMod { - fn ctx(&self) -> SysCtx { - self.0.ctx.upgrade().expect("ReflectedModule accessed after context drop") - } pub fn path(&self) -> &[Tok] { &self.0.path[..] } pub fn is_root(&self) -> bool { self.0.path.is_empty() } async fn try_populate(&self) -> Result<(), api::LsModuleError> { - let ctx = self.ctx(); - let path_tok = ctx.i().i(&self.0.path[..]).await; - let reply = match ctx.reqnot().request(api::LsModule(ctx.sys_id(), path_tok.to_api())).await { + let path_tok = i().i(&self.0.path[..]).await; + let reply = match ctx().reqnot().request(api::LsModule(ctx().sys_id(), path_tok.to_api())).await + { Err(api::LsModuleError::TreeUnavailable) => panic!("Reflected tree accessed outside an interpreter call. This extension is faulty."), Err(err) => return Err(err), Ok(details) => details, }; for (k, v) in reply.members { - let k = ctx.i().ex(k).await; + let k = i().ex(k).await; let mem = match self.0.members.get(&k) { Some(mem) => mem, None => { - let path = self.0.path.clone().name_with_suffix(k.clone()).to_sym(ctx.i()).await; + let path = self.0.path.clone().name_with_suffix(k.clone()).to_sym(&i()).await; let kind = match v.kind { api::MemberInfoKind::Constant => ReflMemKind::Const, api::MemberInfoKind::Module => - ReflMemKind::Mod(default_module(&ctx, VPath::new(path.segs()))), + ReflMemKind::Mod(default_module(VPath::new(path.segs()))), }; self.0.members.get_or_insert(&k, || default_member(self.is_root(), kind)) }, @@ -91,7 +87,6 @@ impl ReflMod { self.0.members.get(key).cloned() } pub async fn get_by_path(&self, path: &[Tok]) -> Result { - let ctx = self.ctx(); let (next, tail) = path.split_first().expect("Attempted to walk by empty path"); let inferred_g = self.0.inferred.lock().await; if let Some(next) = self.0.members.get(next) { @@ -107,7 +102,7 @@ impl ReflMod { if !*inferred_g { return Err(InvalidPathError { keep_ancestry: true }); } - let candidate = default_module(&ctx, self.0.path.clone().suffix([next.clone()])); + let candidate = default_module(self.0.path.clone().suffix([next.clone()])); if tail.is_empty() { return match candidate.try_populate().await { Ok(()) => { @@ -135,6 +130,7 @@ impl ReflMod { } } +#[derive(Clone)] struct ReflRoot(ReflMod); impl SysCtxEntry for ReflRoot {} @@ -143,13 +139,8 @@ pub struct InvalidPathError { keep_ancestry: bool, } -fn default_module(ctx: &SysCtx, path: VPath) -> ReflMod { - ReflMod(Rc::new(ReflModData { - ctx: ctx.downgrade(), - inferred: Mutex::new(true), - path, - members: MemoMap::new(), - })) +fn default_module(path: VPath) -> ReflMod { + ReflMod(Rc::new(ReflModData { inferred: Mutex::new(true), path, members: MemoMap::new() })) } fn default_member(is_root: bool, kind: ReflMemKind) -> ReflMem { @@ -159,8 +150,8 @@ fn default_member(is_root: bool, kind: ReflMemKind) -> ReflMem { })) } -fn get_root(ctx: &SysCtx) -> &ReflRoot { - ctx.get_or_insert(|| ReflRoot(default_module(ctx, VPath::new([])))) +fn get_root() -> ReflRoot { + ctx().get_or_insert(|| ReflRoot(default_module(VPath::new([])))).clone() } -pub fn refl(ctx: &SysCtx) -> ReflMod { get_root(ctx).0.clone() } +pub fn refl() -> ReflMod { get_root().0.clone() } diff --git a/orchid-extension/src/system.rs b/orchid-extension/src/system.rs index 1442160..9a6fd6d 100644 --- a/orchid-extension/src/system.rs +++ b/orchid-extension/src/system.rs @@ -1,22 +1,18 @@ -use std::any::{Any, TypeId, type_name}; -use std::fmt; +use std::any::{Any, TypeId}; use std::future::Future; use std::num::NonZero; use std::pin::Pin; -use std::rc::{Rc, Weak}; +use futures::FutureExt; use futures::future::LocalBoxFuture; -use memo_map::MemoMap; -use orchid_api_traits::{Coding, Decode}; +use orchid_api_traits::{Coding, Decode, Encode, Request}; use orchid_base::boxed_iter::BoxedIter; -use orchid_base::builtin::Spawner; -use orchid_base::interner::Interner; -use orchid_base::logging::Logger; use orchid_base::name::Sym; -use orchid_base::reqnot::{Receipt, ReqNot}; +use orchid_base::reqnot::{Receipt, Requester}; use crate::api; use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId, AtomicFeatures, ForeignAtom, TAtom, get_info}; +use crate::context::ctx; use crate::coroutine_exec::Replier; use crate::entrypoint::ExtReq; use crate::func_atom::{Fun, Lambda}; @@ -32,7 +28,7 @@ pub trait SystemCard: Default + Send + Sync + 'static { fn atoms() -> impl IntoIterator>>; } -pub trait DynSystemCard: Send + Sync + 'static { +pub trait DynSystemCard: Send + Sync + Any + 'static { fn name(&self) -> &'static str; /// Atoms explicitly defined by the system card. Do not rely on this for /// querying atoms as it doesn't include the general atom types @@ -84,16 +80,16 @@ impl DynSystemCard for T { /// System as defined by author pub trait System: Send + Sync + SystemCard + 'static { - fn prelude(i: &Interner) -> impl Future>; - fn env() -> Vec; + fn prelude() -> impl Future>; + fn env() -> impl Future>; fn lexers() -> Vec; fn parsers() -> Vec; fn request(hand: ExtReq<'_>, req: Self::Req) -> impl Future>; } pub trait DynSystem: Send + Sync + DynSystemCard + 'static { - fn dyn_prelude<'a>(&'a self, i: &'a Interner) -> LocalBoxFuture<'a, Vec>; - fn dyn_env(&'_ self) -> Vec; + fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec>; + fn dyn_env(&self) -> LocalBoxFuture<'_, Vec>; fn dyn_lexers(&self) -> Vec; fn dyn_parsers(&self) -> Vec; fn dyn_request<'a>(&self, hand: ExtReq<'a>, req: Vec) -> LocalBoxFuture<'a, Receipt<'a>>; @@ -101,10 +97,8 @@ pub trait DynSystem: Send + Sync + DynSystemCard + 'static { } impl DynSystem for T { - fn dyn_prelude<'a>(&'a self, i: &'a Interner) -> LocalBoxFuture<'a, Vec> { - Box::pin(Self::prelude(i)) - } - fn dyn_env(&'_ self) -> Vec { Self::env() } + fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec> { Box::pin(Self::prelude()) } + fn dyn_env(&self) -> LocalBoxFuture<'_, Vec> { Self::env().boxed_local() } fn dyn_lexers(&self) -> Vec { Self::lexers() } fn dyn_parsers(&self) -> Vec { Self::parsers() } fn dyn_request<'a>(&self, hand: ExtReq<'a>, req: Vec) -> LocalBoxFuture<'a, Receipt<'a>> { @@ -118,7 +112,7 @@ impl DynSystem for T { pub async fn downcast_atom(foreign: ForeignAtom) -> Result, ForeignAtom> where A: AtomicFeatures { let mut data = &foreign.atom.data.0[..]; - let ctx = foreign.ctx().clone(); + let ctx = ctx(); let value = AtomTypeId::decode(Pin::new(&mut data)).await; let own_inst = ctx.get::().inst(); let owner = if *ctx.get::() == foreign.atom.owner { @@ -135,73 +129,23 @@ where A: AtomicFeatures { if value != typ_id { return Err(foreign); } - let val = dynfo.decode(AtomCtx(data, foreign.atom.drop, ctx)).await; + let val = dynfo.decode(AtomCtx(data, foreign.atom.drop)).await; let value = *val.downcast::().expect("atom decode returned wrong type"); Ok(TAtom { value, untyped: foreign }) } -#[derive(Clone)] -pub struct WeakSysCtx(Weak>>); -impl WeakSysCtx { - pub fn upgrade(&self) -> Option { Some(SysCtx(self.0.upgrade()?)) } +pub async fn dep_req>(req: Req) -> Req::Response { + let ctx = ctx(); + let mut msg = Vec::new(); + req.into().encode(std::pin::pin!(&mut msg)).await; + let own_inst = ctx.get::().inst(); + let owner = if own_inst.card().type_id() == TypeId::of::() { + ctx.sys_id() + } else { + (ctx.get::().deps().find(|s| s.get_card().type_id() == TypeId::of::())) + .expect("System not in dependency array") + .id() + }; + let reply = ctx.reqnot().request(api::SysFwd(owner, msg)).await; + Req::Response::decode(std::pin::pin!(&reply[..])).await } -impl fmt::Debug for WeakSysCtx { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "WeakSysCtx") } -} - -#[derive(Clone)] -pub struct SysCtx(Rc>>); -impl SysCtx { - pub fn new( - id: api::SysId, - i: Interner, - reqnot: ReqNot, - spawner: Spawner, - logger: Logger, - cted: CtedObj, - ) -> Self { - let this = Self(Rc::new(MemoMap::new())); - this.add(id).add(i).add(reqnot).add(spawner).add(logger).add(cted); - this - } - pub fn downgrade(&self) -> WeakSysCtx { WeakSysCtx(Rc::downgrade(&self.0)) } - pub fn add(&self, t: T) -> &Self { - assert!(self.0.insert(TypeId::of::(), Box::new(t)), "Key already exists"); - self - } - pub fn get_or_insert(&self, f: impl FnOnce() -> T) -> &T { - (self.0.get_or_insert_owned(TypeId::of::(), || Box::new(f())).downcast_ref()) - .expect("Keyed by TypeId") - } - pub fn get_or_default(&self) -> &T { self.get_or_insert(T::default) } - pub fn try_get(&self) -> Option<&T> { - Some(self.0.get(&TypeId::of::())?.downcast_ref().expect("Keyed by TypeId")) - } - pub fn get(&self) -> &T { - self.try_get().unwrap_or_else(|| panic!("Context {} missing", type_name::())) - } - /// Shorthand to get the [Interner] instance - pub fn i(&self) -> &Interner { self.get::() } - /// Shorthand to get the messaging link - pub fn reqnot(&self) -> &ReqNot { self.get::>() } - /// Shorthand to get the system ID - pub fn sys_id(&self) -> api::SysId { *self.get::() } - /// Shorthand to get the task spawner callback - pub fn spawner(&self) -> &Spawner { self.get::() } - /// Shorthand to get the logger - pub fn logger(&self) -> &Logger { self.get::() } - /// Shorthand to get the constructed system object - pub fn cted(&self) -> &CtedObj { self.get::() } -} -impl fmt::Debug for SysCtx { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "SysCtx({:?})", self.sys_id()) - } -} -pub trait SysCtxEntry: 'static + Sized {} -impl SysCtxEntry for api::SysId {} -impl SysCtxEntry for ReqNot {} -impl SysCtxEntry for Spawner {} -impl SysCtxEntry for CtedObj {} -impl SysCtxEntry for Logger {} -impl SysCtxEntry for Interner {} diff --git a/orchid-extension/src/system_ctor.rs b/orchid-extension/src/system_ctor.rs index 3d95fc8..933db61 100644 --- a/orchid-extension/src/system_ctor.rs +++ b/orchid-extension/src/system_ctor.rs @@ -62,6 +62,8 @@ pub trait SystemCtor: Send + Sync + 'static { type Instance: System; const NAME: &'static str; const VERSION: f64; + /// Create a system instance. When this function is called, a context object + /// isn't yet available fn inst(deps: ::Sat) -> Self::Instance; } diff --git a/orchid-extension/src/tree.rs b/orchid-extension/src/tree.rs index 7afc75c..fcbab87 100644 --- a/orchid-extension/src/tree.rs +++ b/orchid-extension/src/tree.rs @@ -14,19 +14,19 @@ use substack::Substack; use trait_set::trait_set; use crate::api; +use crate::context::i; use crate::conv::ToExpr; use crate::entrypoint::MemberRecord; use crate::expr::{BorrowedExprStore, Expr, ExprHandle}; use crate::func_atom::{ExprFunc, Fun}; use crate::gen_expr::{GExpr, sym_ref}; -use crate::system::SysCtx; pub type GenTokTree = TokTree; pub type GenTok = Token; impl TokenVariant for GExpr { type FromApiCtx<'a> = (); - type ToApiCtx<'a> = SysCtx; + type ToApiCtx<'a> = (); async fn from_api( _: &api::Expression, _: &mut Self::FromApiCtx<'_>, @@ -35,33 +35,31 @@ impl TokenVariant for GExpr { ) -> Self { panic!("Received new expression from host") } - async fn into_api(self, ctx: &mut Self::ToApiCtx<'_>) -> api::Expression { - self.api_return(ctx.clone()).await - } + async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> api::Expression { self.serialize().await } } impl TokenVariant for Expr { - type FromApiCtx<'a> = (SysCtx, &'a BorrowedExprStore); + type FromApiCtx<'a> = &'a BorrowedExprStore; async fn from_api( api: &api::ExprTicket, - (ctx, exprs): &mut Self::FromApiCtx<'_>, + exprs: &mut Self::FromApiCtx<'_>, _: SrcRange, _: &Interner, ) -> Self { // SAFETY: receiving trees from sublexers implies borrowing - Expr::from_handle(ExprHandle::borrowed(ctx.clone(), *api, exprs)) + Expr::from_handle(ExprHandle::borrowed(*api, exprs)) } type ToApiCtx<'a> = (); - async fn into_api(self, (): &mut Self::ToApiCtx<'_>) -> api::ExprTicket { self.handle().tk } + async fn into_api(self, (): &mut Self::ToApiCtx<'_>) -> api::ExprTicket { self.handle().ticket() } } -pub async fn x_tok(x: impl ToExpr) -> GenTok { GenTok::NewExpr(x.to_expr().await) } +pub async fn x_tok(x: impl ToExpr) -> GenTok { GenTok::NewExpr(x.to_gen().await) } pub async fn ref_tok(path: Sym) -> GenTok { GenTok::NewExpr(sym_ref(path)) } pub fn lazy( public: bool, name: &str, - cb: impl AsyncFnOnce(Sym, SysCtx) -> MemKind + Clone + 'static, + cb: impl AsyncFnOnce(Sym) -> MemKind + Clone + 'static, ) -> Vec { vec![GenMember { name: name.to_string(), @@ -71,7 +69,7 @@ pub fn lazy( }] } pub fn cnst(public: bool, name: &str, value: impl ToExpr + Clone + 'static) -> Vec { - lazy(public, name, async |_, _| MemKind::Const(value.to_expr().await)) + lazy(public, name, async |_| MemKind::Const(value.to_gen().await)) } pub fn module( public: bool, @@ -86,8 +84,8 @@ pub fn root_mod(name: &str, mems: impl IntoIterator>) -> ( (name.to_string(), kind) } pub fn fun(public: bool, name: &str, xf: impl ExprFunc) -> Vec { - let fac = LazyMemberFactory::new(async move |sym, ctx| { - MemKind::Const(Fun::new(sym, ctx, xf).await.to_expr().await) + let fac = LazyMemberFactory::new(async move |sym| { + MemKind::Const(Fun::new(sym, xf).await.to_gen().await) }); vec![GenMember { name: name.to_string(), kind: MemKind::Lazy(fac), public, comments: vec![] }] } @@ -149,14 +147,14 @@ pub fn merge_trivial(trees: impl IntoIterator>) -> Vec LocalBoxFuture<'static, MemKind> + DynClone + FnOnce(Sym) -> LocalBoxFuture<'static, MemKind> + DynClone } pub struct LazyMemberFactory(Box); impl LazyMemberFactory { - pub fn new(cb: impl AsyncFnOnce(Sym, SysCtx) -> MemKind + Clone + 'static) -> Self { - Self(Box::new(|s, ctx| cb(s, ctx).boxed_local())) + pub fn new(cb: impl AsyncFnOnce(Sym) -> MemKind + Clone + 'static) -> Self { + Self(Box::new(|s| cb(s).boxed_local())) } - pub async fn build(self, path: Sym, ctx: SysCtx) -> MemKind { (self.0)(path, ctx).await } + pub async fn build(self, path: Sym) -> MemKind { (self.0)(path).await } } impl Clone for LazyMemberFactory { fn clone(&self) -> Self { Self(clone_box(&*self.0)) } @@ -169,11 +167,10 @@ pub struct GenMember { pub comments: Vec, } impl GenMember { - pub async fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::Member { - let name = ctx.sys().i().i::(&self.name).await; - let kind = self.kind.into_api(&mut ctx.push_path(name.clone())).await; - let comments = - join_all(self.comments.iter().map(async |cmt| ctx.sys().i().i(cmt).await.to_api())).await; + pub async fn into_api(self, tia_cx: &mut impl TreeIntoApiCtx) -> api::Member { + let name = i().i::(&self.name).await; + let kind = self.kind.into_api(&mut tia_cx.push_path(name.clone())).await; + let comments = join_all(self.comments.iter().map(async |cmt| i().i(cmt).await.to_api())).await; api::Member { kind, name: name.to_api(), comments, exported: self.public } } } @@ -187,7 +184,7 @@ impl MemKind { pub async fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::MemberKind { match self { Self::Lazy(lazy) => api::MemberKind::Lazy(ctx.with_lazy(lazy)), - Self::Const(c) => api::MemberKind::Const(c.api_return(ctx.sys()).await), + Self::Const(c) => api::MemberKind::Const(c.serialize().await), Self::Mod { members } => api::MemberKind::Module(api::Module { members: stream(async |mut cx| { for m in members { @@ -203,24 +200,20 @@ impl MemKind { } pub trait TreeIntoApiCtx { - fn sys(&self) -> SysCtx; fn with_lazy(&mut self, fac: LazyMemberFactory) -> api::TreeId; fn push_path(&mut self, seg: Tok) -> impl TreeIntoApiCtx; } pub struct TreeIntoApiCtxImpl<'a, 'b> { - pub sys: SysCtx, pub basepath: &'a [Tok], pub path: Substack<'a, Tok>, pub lazy_members: &'b mut HashMap, } impl TreeIntoApiCtx for TreeIntoApiCtxImpl<'_, '_> { - fn sys(&self) -> SysCtx { self.sys.clone() } fn push_path(&mut self, seg: Tok) -> impl TreeIntoApiCtx { TreeIntoApiCtxImpl { lazy_members: self.lazy_members, - sys: self.sys.clone(), basepath: self.basepath, path: self.path.push(seg), } diff --git a/orchid-host/src/atom.rs b/orchid-host/src/atom.rs index 50a83d1..d6bf439 100644 --- a/orchid-host/src/atom.rs +++ b/orchid-host/src/atom.rs @@ -10,7 +10,7 @@ use orchid_base::tree::AtomRepr; use crate::api; use crate::ctx::Ctx; -use crate::expr::{Expr, ExprParseCtx, PathSetBuilder}; +use crate::expr::{Expr, PathSetBuilder}; use crate::extension::Extension; use crate::system::System; @@ -58,15 +58,15 @@ impl AtomHand { #[must_use] pub async fn call(self, arg: Expr) -> Expr { let owner_sys = self.0.owner.clone(); + let ctx = owner_sys.ctx(); let reqnot = owner_sys.reqnot(); - owner_sys.ext().exprs().give_expr(arg.clone()); + ctx.exprs.give_expr(arg.clone()); let ret = match Rc::try_unwrap(self.0) { Ok(data) => reqnot.request(api::FinalCall(data.api(), arg.id())).await, Err(hand) => reqnot.request(api::CallRef(hand.api_ref(), arg.id())).await, }; - let mut parse_ctx = ExprParseCtx { ctx: owner_sys.ctx(), exprs: owner_sys.ext().exprs() }; - let val = Expr::from_api(&ret, PathSetBuilder::new(), &mut parse_ctx).await; - owner_sys.ext().exprs().take_expr(arg.id()); + let val = Expr::from_api(&ret, PathSetBuilder::new(), ctx.clone()).await; + ctx.exprs.take_expr(arg.id()); val } #[must_use] diff --git a/orchid-host/src/ctx.rs b/orchid-host/src/ctx.rs index 268d31a..50a13f4 100644 --- a/orchid-host/src/ctx.rs +++ b/orchid-host/src/ctx.rs @@ -18,7 +18,7 @@ pub struct CtxData { pub spawn: Spawner, pub systems: RwLock>, pub system_id: RefCell, - pub common_exprs: ExprStore, + pub exprs: ExprStore, pub root: RwLock, } #[derive(Clone)] @@ -43,7 +43,7 @@ impl Ctx { i: Interner::default(), systems: RwLock::default(), system_id: RefCell::new(NonZero::new(1).unwrap()), - common_exprs: ExprStore::default(), + exprs: ExprStore::default(), root: RwLock::default(), })) } diff --git a/orchid-host/src/expr.rs b/orchid-host/src/expr.rs index de4fb88..cfb1544 100644 --- a/orchid-host/src/expr.rs +++ b/orchid-host/src/expr.rs @@ -21,12 +21,6 @@ use crate::atom::AtomHand; use crate::ctx::Ctx; use crate::expr_store::ExprStore; -#[derive(Clone)] -pub struct ExprParseCtx<'a> { - pub ctx: &'a Ctx, - pub exprs: &'a ExprStore, -} - #[derive(Debug)] pub struct ExprData { pos: Pos, @@ -61,42 +55,34 @@ impl Expr { ) } #[must_use] - pub async fn from_api( - api: &api::Expression, - psb: PathSetBuilder<'_, u64>, - ctx: &mut ExprParseCtx<'_>, - ) -> Self { - let pos = Pos::from_api(&api.location, &ctx.ctx.i).await; + pub async fn from_api(api: &api::Expression, psb: PathSetBuilder<'_, u64>, ctx: Ctx) -> Self { + let pos = Pos::from_api(&api.location, &ctx.i).await; let kind = match &api.kind { api::ExpressionKind::Arg(n) => { assert!(psb.register_arg(n), "Arguments must be enclosed in a matching lambda"); ExprKind::Arg }, - api::ExpressionKind::Bottom(bot) => - ExprKind::Bottom(OrcErrv::from_api(bot, &ctx.ctx.i).await), + api::ExpressionKind::Bottom(bot) => ExprKind::Bottom(OrcErrv::from_api(bot, &ctx.i).await), api::ExpressionKind::Call(f, x) => { let (lpsb, rpsb) = psb.split(); ExprKind::Call( - Expr::from_api(f, lpsb, ctx).boxed_local().await, + Expr::from_api(f, lpsb, ctx.clone()).boxed_local().await, Expr::from_api(x, rpsb, ctx).boxed_local().await, ) }, - api::ExpressionKind::Const(name) => ExprKind::Const(Sym::from_api(*name, &ctx.ctx.i).await), + api::ExpressionKind::Const(name) => ExprKind::Const(Sym::from_api(*name, &ctx.i).await), api::ExpressionKind::Lambda(x, body) => { let lbuilder = psb.lambda(x); let body = Expr::from_api(body, lbuilder.stack(), ctx).boxed_local().await; ExprKind::Lambda(lbuilder.collect(), body) }, api::ExpressionKind::NewAtom(a) => - ExprKind::Atom(AtomHand::from_api(a, pos.clone(), &mut ctx.ctx.clone()).await), - api::ExpressionKind::Slot { tk, by_value: false } => - return ctx.exprs.get_expr(*tk).expect("Invalid slot"), - api::ExpressionKind::Slot { tk, by_value: true } => - return ctx.exprs.take_expr(*tk).expect("Invalid slot"), + ExprKind::Atom(AtomHand::from_api(a, pos.clone(), &mut ctx.clone()).await), + api::ExpressionKind::Slot(tk) => return ctx.exprs.take_expr(*tk).expect("Invalid slot"), api::ExpressionKind::Seq(a, b) => { let (apsb, bpsb) = psb.split(); ExprKind::Seq( - Expr::from_api(a, apsb, ctx).boxed_local().await, + Expr::from_api(a, apsb, ctx.clone()).boxed_local().await, Expr::from_api(b, bpsb, ctx).boxed_local().await, ) }, @@ -169,8 +155,8 @@ async fn print_exprkind<'a>( ExprKind::Bottom(e) if e.len() == 1 => format!("Bottom({e})").into(), ExprKind::Bottom(e) => format!("Bottom(\n\t{}\n)", indent(&e.to_string())).into(), ExprKind::Call(f, x) => tl_cache!(Rc: Rc::new(Variants::default() - .unbounded("{0} {1l}") - .bounded("({0} {1b})"))) + .unbounded("{0b} {1l}") + .bounded("({0b} {1})"))) .units([print_expr(f, c, visited).await, print_expr(x, c, visited).await]), ExprKind::Identity(id) => tl_cache!(Rc: Rc::new(Variants::default().bounded("{{{0}}}"))).units([print_expr( @@ -180,11 +166,11 @@ async fn print_exprkind<'a>( .await]), ExprKind::Const(c) => format!("{c}").into(), ExprKind::Lambda(None, body) => tl_cache!(Rc: Rc::new(Variants::default() - .unbounded("\\.{0l}") + // .unbounded("\\.{0l}") .bounded("(\\.{0b})"))) .units([print_expr(body, c, visited).await]), ExprKind::Lambda(Some(path), body) => tl_cache!(Rc: Rc::new(Variants::default() - .unbounded("\\{0b}. {1l}") + // .unbounded("\\{0b}. {1l}") .bounded("(\\{0b}. {1b})"))) .units([format!("{path}").into(), print_expr(body, c, visited).await]), ExprKind::Seq(l, r) => @@ -361,14 +347,14 @@ impl TokenVariant for Expr { pub struct ExprWillPanic; impl TokenVariant for Expr { - type FromApiCtx<'a> = ExprParseCtx<'a>; + type FromApiCtx<'a> = Ctx; async fn from_api( api: &api::Expression, ctx: &mut Self::FromApiCtx<'_>, _: SrcRange, _: &Interner, ) -> Self { - Self::from_api(api, PathSetBuilder::new(), ctx).await + Self::from_api(api, PathSetBuilder::new(), ctx.clone()).await } type ToApiCtx<'a> = ExprWillPanic; async fn into_api(self, ExprWillPanic: &mut Self::ToApiCtx<'_>) -> api::Expression { diff --git a/orchid-host/src/expr_store.rs b/orchid-host/src/expr_store.rs index e50515a..1fa7a99 100644 --- a/orchid-host/src/expr_store.rs +++ b/orchid-host/src/expr_store.rs @@ -13,7 +13,6 @@ use crate::expr::Expr; pub struct ExprStoreData { exprs: RefCell>, parent: Option, - tracking_parent: bool, } #[derive(Clone, Default)] pub struct ExprStore(Rc); @@ -25,16 +24,12 @@ impl ExprStore { /// but operations on the parent can access the child exprs too until this /// store is dropped. #[must_use] - pub fn derive(&self, tracking_parent: bool) -> Self { - Self(Rc::new(ExprStoreData { - exprs: RefCell::default(), - parent: Some(self.clone()), - tracking_parent, - })) + pub fn derive(&self) -> Self { + Self(Rc::new(ExprStoreData { exprs: RefCell::default(), parent: Some(self.clone()) })) } pub fn give_expr(&self, expr: Expr) { - if self.0.tracking_parent { - self.0.parent.as_ref().unwrap().give_expr(expr.clone()); + if let Some(parent) = self.0.parent.as_ref() { + parent.give_expr(expr.clone()) } match self.0.exprs.borrow_mut().entry(expr.id()) { Entry::Occupied(mut oe) => oe.get_mut().0 += 1, @@ -44,8 +39,8 @@ impl ExprStore { } } pub fn take_expr(&self, ticket: api::ExprTicket) -> Option { - if self.0.tracking_parent { - self.0.parent.as_ref().unwrap().take_expr(ticket); + if let Some(parent) = self.0.parent.as_ref() { + parent.take_expr(ticket); } match self.0.exprs.borrow_mut().entry(ticket) { Entry::Vacant(_) => panic!("Attempted to double-take expression"), @@ -79,13 +74,11 @@ impl Drop for ExprStore { if 1 < Rc::strong_count(&self.0) { return; } - if !self.0.tracking_parent { - return; - } - let parent = self.0.parent.as_ref().unwrap(); - for (id, (count, _)) in self.0.exprs.borrow().iter() { - for _ in 0..*count { - parent.take_expr(*id); + if let Some(parent) = self.0.parent.as_ref() { + for (id, (count, _)) in self.0.exprs.borrow().iter() { + for _ in 0..*count { + parent.take_expr(*id); + } } } } diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index bc2969a..24f9efa 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -28,8 +28,7 @@ use crate::api; use crate::atom::AtomHand; use crate::ctx::Ctx; use crate::dealias::{ChildError, ChildErrorKind, walk}; -use crate::expr::ExprKind; -use crate::expr_store::ExprStore; +use crate::expr::{Expr, PathSetBuilder}; use crate::system::SystemCtor; use crate::tree::MemberKind; @@ -47,7 +46,6 @@ pub struct ExtensionData { systems: Vec, logger: Logger, next_pars: RefCell, - exprs: ExprStore, exiting_snd: Sender<()>, lex_recur: Mutex>>>, } @@ -92,7 +90,6 @@ impl Extension { ExtensionData { name: init.name.clone(), exiting_snd, - exprs: ctx.common_exprs.derive(false), ctx: ctx.clone(), systems: (init.systems.iter().cloned()) .map(|decl| SystemCtor { decl, ext: WeakExtension(weak.clone()) }) @@ -111,25 +108,15 @@ impl Extension { } match notif { api::ExtHostNotif::ExprNotif(api::ExprNotif::Acquire(acq)) => { - let target = this.0.exprs.get_expr(acq.1).expect("Invalid ticket"); - this.0.exprs.give_expr(target) + let target = this.0.ctx.exprs.get_expr(acq.1).expect("Invalid ticket"); + this.0.ctx.exprs.give_expr(target) } api::ExtHostNotif::ExprNotif(api::ExprNotif::Release(rel)) => { if this.is_own_sys(rel.0).await { - this.0.exprs.take_expr(rel.1); + this.0.ctx.exprs.take_expr(rel.1); } else { writeln!(this.reqnot().logger(), "Not our system {:?}", rel.0) } - } - api::ExtHostNotif::ExprNotif(api::ExprNotif::Move(mov)) => { - if !this.is_own_sys(mov.dec).await { - writeln!(this.reqnot().logger(), "Not our system {:?}", mov.dec); - return; - } - let recp = this.ctx().system_inst(mov.inc).await.expect("invallid recipient sys id"); - let expr = this.0.exprs.get_expr(mov.expr).expect("invalid ticket"); - recp.ext().0.exprs.give_expr(expr); - this.0.exprs.take_expr(mov.expr); }, api::ExtHostNotif::Log(api::Log(str)) => this.logger().log(str), } @@ -180,17 +167,23 @@ impl Extension { } hand.handle(&sl, &rep_out.next().await.unwrap()).await }, - api::ExtHostReq::ExprReq(api::ExprReq::Inspect( - ins @ api::Inspect { target }, - )) => { - let expr = this.exprs().get_expr(target).expect("Invalid ticket"); - hand - .handle(&ins, &api::Inspected { - refcount: expr.strong_count() as u32, - location: expr.pos().to_api(), - kind: expr.to_api().await, - }) - .await + api::ExtHostReq::ExprReq(expr_req) => match expr_req { + api::ExprReq::Inspect(ins @ api::Inspect { target }) => { + let expr = ctx.exprs.get_expr(target).expect("Invalid ticket"); + hand + .handle(&ins, &api::Inspected { + refcount: expr.strong_count() as u32, + location: expr.pos().to_api(), + kind: expr.to_api().await, + }) + .await + }, + api::ExprReq::Create(ref cre @ api::Create(ref expr)) => { + let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await; + let expr_id = expr.id(); + ctx.exprs.give_expr(expr); + hand.handle(cre, &expr_id).await + }, }, api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => { let reply: ::Response = 'reply: { @@ -249,13 +242,6 @@ impl Extension { let unit = atom.print(&FmtCtxImpl { i: &this.ctx().i }).await; hand.handle(eap, &unit.to_api()).await }, - api::ExtHostReq::CreateAtom(ref create @ api::CreateAtom(ref atom, target)) => { - let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await; - let target = ctx.system_inst(target).await.expect("Invalid recipient for atom"); - let expr = ExprKind::Atom(atom).at(Pos::None); - target.ext().exprs().give_expr(expr.clone()); - hand.handle(create, &expr.id()).await - }, } }) } @@ -273,8 +259,6 @@ impl Extension { pub fn logger(&self) -> &Logger { &self.0.logger } pub fn system_ctors(&self) -> impl Iterator { self.0.systems.iter() } #[must_use] - pub fn exprs(&self) -> &ExprStore { &self.0.exprs } - #[must_use] pub async fn is_own_sys(&self, id: api::SysId) -> bool { let Some(sys) = self.ctx().system_inst(id).await else { writeln!(self.logger(), "Invalid system ID {id:?}"); diff --git a/orchid-host/src/lex.rs b/orchid-host/src/lex.rs index c52d8fc..27b741e 100644 --- a/orchid-host/src/lex.rs +++ b/orchid-host/src/lex.rs @@ -13,7 +13,7 @@ use orchid_base::tree::recur; use crate::api; use crate::ctx::Ctx; -use crate::expr::{Expr, ExprParseCtx}; +use crate::expr::Expr; use crate::expr_store::ExprStore; use crate::parsed::{ParsTok, ParsTokTree, tt_to_api}; use crate::system::System; @@ -60,14 +60,7 @@ impl<'a> LexCtx<'a> { } #[must_use] pub async fn des_subtree(&mut self, tree: &api::TokenTree, exprs: ExprStore) -> ParsTokTree { - ParsTokTree::from_api( - tree, - &mut { exprs }, - &mut ExprParseCtx { ctx: self.ctx, exprs: &self.ctx.common_exprs }, - self.path, - &self.ctx.i, - ) - .await + ParsTokTree::from_api(tree, &mut { exprs }, &mut self.ctx.clone(), self.path, &self.ctx.i).await } #[must_use] pub fn strip_char(&mut self, tgt: char) -> bool { @@ -146,9 +139,9 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes { let mut errors = Vec::new(); if ctx.tail.starts_with(|c| sys.can_lex(c)) { let (source, pos, path) = (ctx.source.clone(), ctx.get_pos(), ctx.path.clone()); + let temp_store = ctx.ctx.exprs.derive(); let ctx_lck = &Mutex::new(&mut *ctx); let errors_lck = &Mutex::new(&mut errors); - let temp_store = sys.ext().exprs().derive(true); let temp_store_cb = temp_store.clone(); let lx = sys .lex(source, path, pos, |pos| { diff --git a/orchid-host/src/parsed.rs b/orchid-host/src/parsed.rs index 43033a8..62bc9c4 100644 --- a/orchid-host/src/parsed.rs +++ b/orchid-host/src/parsed.rs @@ -185,7 +185,7 @@ impl Tree for ParsedModule { impl Format for ParsedModule { async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { let head_str = format!("export ::({})\n", self.exports.iter().join(", ")); - Variants::sequence(self.items.len() + 1, "\n", None).units( + Variants::default().sequence(self.items.len() + 1, "", "\n", "", None).units_own( [head_str.into()].into_iter().chain(join_all(self.items.iter().map(|i| i.print(c))).await), ) } diff --git a/orchid-host/src/sys_parser.rs b/orchid-host/src/sys_parser.rs index f4953be..804fc29 100644 --- a/orchid-host/src/sys_parser.rs +++ b/orchid-host/src/sys_parser.rs @@ -11,7 +11,6 @@ use orchid_base::tree::ttv_from_api; use substack::Substack; use crate::api; -use crate::expr::ExprParseCtx; use crate::expr_store::ExprStore; use crate::parse::HostParseCtx; use crate::parsed::{ @@ -35,7 +34,7 @@ impl Parser { comments: Vec, callback: &mut impl AsyncFnMut(ModPath<'_>, Vec) -> OrcRes>, ) -> OrcRes> { - let mut temp_store = self.system.ext().exprs().derive(true); + let mut temp_store = self.system.ctx().exprs.derive(); let src_path = line.first().expect("cannot be empty").sr.path(); let line = join_all((line.into_iter()).map(|t| async { tt_to_api(&mut temp_store.clone(), t).await })) @@ -57,7 +56,6 @@ impl Parser { i: self.system.i(), mod_path: &mod_path, ext_exprs: &mut temp_store, - pctx: &mut ExprParseCtx { ctx: self.system.ctx(), exprs: self.system.ext().exprs() }, src_path: &src_path, sys: &self.system, }) @@ -73,7 +71,6 @@ struct ConvCtx<'a> { src_path: &'a Sym, i: &'a Interner, ext_exprs: &'a mut ExprStore, - pctx: &'a mut ExprParseCtx<'a>, } async fn conv( parsed_v: Vec, @@ -87,7 +84,8 @@ async fn conv( api::ParsedLineKind::Member(api::ParsedMember { name, exported, kind }) => (name, exported, kind), api::ParsedLineKind::Recursive(rec) => { - let tokens = ttv_from_api(rec, ctx.ext_exprs, ctx.pctx, ctx.src_path, ctx.i).await; + let tokens = + ttv_from_api(rec, ctx.ext_exprs, &mut ctx.sys.ctx().clone(), ctx.src_path, ctx.i).await; items.extend(callback(module.clone(), tokens).await?); continue; }, diff --git a/orchid-host/src/tree.rs b/orchid-host/src/tree.rs index a496bcb..4f098cc 100644 --- a/orchid-host/src/tree.rs +++ b/orchid-host/src/tree.rs @@ -22,7 +22,7 @@ use orchid_base::reqnot::Requester; use crate::api; use crate::ctx::Ctx; use crate::dealias::{ChildErrorKind, Tree, absolute_path, resolv_glob, walk}; -use crate::expr::{Expr, ExprParseCtx, PathSetBuilder}; +use crate::expr::{Expr, PathSetBuilder}; use crate::parsed::{ItemKind, ParsedMemberKind, ParsedModule}; use crate::system::System; @@ -90,8 +90,7 @@ impl Root { for (path, (sys_id, pc_id)) in deferred_consts { let sys = this.ctx.system_inst(sys_id).await.expect("System dropped since parsing"); let api_expr = sys.reqnot().request(api::FetchParsedConst(sys.id(), pc_id)).await; - let mut xp_ctx = ExprParseCtx { ctx: &this.ctx, exprs: sys.ext().exprs() }; - let expr = Expr::from_api(&api_expr, PathSetBuilder::new(), &mut xp_ctx).await; + let expr = Expr::from_api(&api_expr, PathSetBuilder::new(), this.ctx.clone()).await; new.0.write().await.consts.insert(path, expr); } new @@ -178,8 +177,7 @@ impl Module { api::MemberKind::Lazy(id) => (Some(LazyMemberHandle { id, sys: ctx.sys.id(), path: name.clone() }), None), api::MemberKind::Const(val) => { - let mut expr_ctx = ExprParseCtx { ctx: ctx.sys.ctx(), exprs: ctx.sys.ext().exprs() }; - let expr = Expr::from_api(&val, PathSetBuilder::new(), &mut expr_ctx).await; + let expr = Expr::from_api(&val, PathSetBuilder::new(), ctx.sys.ctx().clone()).await; ctx.consts.insert(name.clone(), expr); (None, Some(MemberKind::Const)) }, @@ -463,8 +461,7 @@ impl LazyMemberHandle { let sys = ctx.system_inst(self.sys).await.expect("Missing system for lazy member"); match sys.get_tree(self.id).await { api::MemberKind::Const(c) => { - let mut pctx = ExprParseCtx { ctx: &ctx, exprs: sys.ext().exprs() }; - let expr = Expr::from_api(&c, PathSetBuilder::new(), &mut pctx).await; + let expr = Expr::from_api(&c, PathSetBuilder::new(), ctx.clone()).await; let (.., path) = self.destructure(); consts.insert(path, expr); MemberKind::Const diff --git a/orchid-std/Cargo.toml b/orchid-std/Cargo.toml index f56e74f..dec2e23 100644 --- a/orchid-std/Cargo.toml +++ b/orchid-std/Cargo.toml @@ -19,7 +19,9 @@ orchid-extension = { version = "0.1.0", path = "../orchid-extension", features = "tokio", ] } ordered-float = "5.0.0" +pastey = "0.1.1" rust_decimal = "1.38.0" +subslice-offset = "0.1.1" substack = "1.1.1" tokio = { version = "1.47.1", features = ["full"] } diff --git a/orchid-std/src/lib.rs b/orchid-std/src/lib.rs index 495ee80..d1c6fd3 100644 --- a/orchid-std/src/lib.rs +++ b/orchid-std/src/lib.rs @@ -2,8 +2,11 @@ mod macros; mod std; pub use std::number::num_atom::{Float, HomoArray, Int, Num}; +pub use std::option::OrcOpt; +pub use std::reflection::sym_atom::{SymAtom, sym_expr}; pub use std::std_system::StdSystem; pub use std::string::str_atom::OrcString; +pub use std::tuple::{HomoTpl, Tpl, Tuple, UntypedTuple}; pub use macros::macro_system::MacroSystem; pub use macros::mactree::{MacTok, MacTree}; diff --git a/orchid-std/src/macros/instantiate_tpl.rs b/orchid-std/src/macros/instantiate_tpl.rs index 5693ece..ae45981 100644 --- a/orchid-std/src/macros/instantiate_tpl.rs +++ b/orchid-std/src/macros/instantiate_tpl.rs @@ -4,12 +4,13 @@ use never::Never; use orchid_base::format::fmt; use orchid_extension::atom::{Atomic, TAtom}; use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; +use orchid_extension::context::i; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::exec; use orchid_extension::expr::Expr; use orchid_extension::gen_expr::GExpr; -use crate::macros::mactree::{MacTok, MacTree, map_mactree}; +use crate::macros::mactree::{MacTok, MacTree}; #[derive(Clone)] pub struct InstantiateTplCall { @@ -35,24 +36,24 @@ impl OwnedAtom for InstantiateTplCall { self.clone().call(arg).await } async fn call(mut self, arg: Expr) -> GExpr { - exec("macros::instantiate_tpl", async move |mut h| { + exec(async move |mut h| { match h.exec::>(arg.clone()).await { - Err(_) => panic!("Expected a macro param, found {}", fmt(&arg, arg.ctx().i()).await), - Ok(t) => self.argv.push(own(t).await), + Err(_) => panic!("Expected a macro param, found {}", fmt(&arg, &i()).await), + Ok(t) => self.argv.push(own(&t).await), }; if self.argv.len() < self.argc { - return self.to_expr().await; + return self.to_gen().await; } let mut args = self.argv.into_iter(); - let ret = map_mactree(&self.tpl, &mut false, &mut |mt| match mt.tok() { + let ret = self.tpl.map(&mut false, &mut |mt| match mt.tok() { MacTok::Slot => Some(args.next().expect("Not enough arguments to fill all slots")), _ => None, }); assert!(args.next().is_none(), "Too many arguments for all slots"); - ret.to_expr().await + ret.to_gen().await }) .await - .to_expr() + .to_gen() .await } } diff --git a/orchid-std/src/macros/let_line.rs b/orchid-std/src/macros/let_line.rs index d99bf99..ce323c6 100644 --- a/orchid-std/src/macros/let_line.rs +++ b/orchid-std/src/macros/let_line.rs @@ -11,11 +11,12 @@ use orchid_base::parse::{ use orchid_base::sym; use orchid_base::tree::Paren; use orchid_extension::atom::TAtom; +use orchid_extension::context::i; use orchid_extension::conv::TryFromExpr; use orchid_extension::gen_expr::{atom, call, sym_ref}; use orchid_extension::parser::{ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser}; -use crate::macros::mactree::{MacTok, MacTree, glossary_v, map_mactree_v}; +use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq}; use crate::macros::ph_lexer::PhAtom; #[derive(Default)] @@ -40,21 +41,18 @@ impl Parser for LetLine { let aliased = parse_tokv(tail, &ctx).await; Ok(vec![ParsedLine::cnst(&line.sr(), &comments, exported, name, async move |ctx| { let rep = Reporter::new(); - let dealiased = dealias_mac_v(aliased, &ctx, &rep).await; - let macro_input = MacTok::S(Paren::Round, dealiased).at(sr.pos()); + let macro_input = + MacTok::S(Paren::Round, dealias_mac_v(&aliased, &ctx, &rep).await).at(sr.pos()); if let Some(e) = rep.errv() { return Err(e); } - Ok(call(sym_ref(sym!(macros::lower; ctx.i()).await), [call( - sym_ref(sym!(macros::resolve; ctx.i()).await), - [atom(macro_input)], - )])) + Ok(call(sym_ref(sym!(macros::resolve; i())), [atom(macro_input)])) })]) } } -pub async fn dealias_mac_v(aliased: Vec, ctx: &ConstCtx, rep: &Reporter) -> Vec { - let keys = glossary_v(&aliased).collect_vec(); +pub async fn dealias_mac_v(aliased: &MacTreeSeq, ctx: &ConstCtx, rep: &Reporter) -> MacTreeSeq { + let keys = aliased.glossary().iter().cloned().collect_vec(); let mut names: HashMap<_, _> = HashMap::new(); let mut stream = pin!(ctx.names(&keys).zip(stream::iter(&keys))); while let Some((canonical, local)) = stream.next().await { @@ -65,13 +63,13 @@ pub async fn dealias_mac_v(aliased: Vec, ctx: &ConstCtx, rep: &Reporter }, } } - map_mactree_v(&aliased, &mut false, &mut |tree| match &*tree.tok { + aliased.map(&mut false, &mut |tree| match &*tree.tok { MacTok::Name(n) => names.get(n).map(|new_n| MacTok::Name(new_n.clone()).at(tree.pos())), _ => None, }) } -pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> Vec { +pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> MacTreeSeq { if let Some((idx, arg)) = line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?))) { let (head, lambda) = line.split_at(idx as u32); let (_, body) = lambda.pop_front().unwrap(); @@ -86,14 +84,14 @@ pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> Vec .await, ), }; - all + MacTreeSeq::new(all) } else { - parse_tokv_no_lambdas(&line, ctx).await + MacTreeSeq::new(parse_tokv_no_lambdas(&line, ctx).await) } } async fn parse_tokv_no_lambdas(line: &[PTokTree], ctx: &impl ParseCtx) -> Vec { - stream::iter(line).filter_map(|tt| parse_tok(tt, ctx)).collect().await + stream::iter(line).filter_map(|tt| parse_tok(tt, ctx)).collect::>().await } pub async fn parse_tok(tree: &PTokTree, ctx: &impl ParseCtx) -> Option { @@ -115,7 +113,7 @@ pub async fn parse_tok(tree: &PTokTree, ctx: &impl ParseCtx) -> Option }, PTok::Handle(expr) => match TAtom::::try_from_expr(expr.clone()).await { Err(_) => MacTok::Value(expr.clone()), - Ok(ta) => MacTok::Ph(ta.value.to_full(ta.ctx()).await), + Ok(ta) => MacTok::Ph(ta.value.to_full().await), }, PTok::NewExpr(never) => match *never {}, PTok::LambdaHead(_) => panic!("Lambda-head handled in the sequence parser"), diff --git a/orchid-std/src/macros/macro_lib.rs b/orchid-std/src/macros/macro_lib.rs index 865576e..a6d8378 100644 --- a/orchid-std/src/macros/macro_lib.rs +++ b/orchid-std/src/macros/macro_lib.rs @@ -1,97 +1,65 @@ -use hashbrown::HashMap; -use itertools::{Itertools, chain}; -use orchid_base::error::Reporter; -use orchid_base::{clone, sym}; +use orchid_base::sym; use orchid_extension::atom::TAtom; use orchid_extension::atom_owned::own; +use orchid_extension::context::i; use orchid_extension::conv::ToExpr; -use orchid_extension::coroutine_exec::exec; -use orchid_extension::func_atom::Lambda; use orchid_extension::gen_expr::{call, sym_ref}; -use orchid_extension::reflection::{ReflMemKind, refl}; -use orchid_extension::tree::{GenMember, MemKind, fun, lazy, prefix}; -use substack::Substack; +use orchid_extension::tree::{GenMember, fun, prefix}; -use crate::MacTok; -use crate::macros::macro_value::{Macro, Matcher}; -use crate::macros::mactree::{LowerCtx, MacTree, Ph}; -use crate::macros::resolve::{ResolveCtx, resolve}; -use crate::macros::utils::{mactree, mactreev, mk_macro}; +use crate::macros::mactree::MacTree; +use crate::macros::resolve::resolve; +use crate::macros::utils::{build_macro, mactree, mactreev}; -pub fn gen_macro_lib() -> Vec { +pub async fn gen_macro_lib() -> Vec { prefix("macros", [ - fun(true, "lower", |tpl: TAtom| async move { - let ctx = LowerCtx { sys: tpl.untyped.ctx().clone(), rep: &Reporter::new() }; - let res = own(tpl).await.lower(ctx, Substack::Bottom).await; - if let Some(e) = Reporter::new().errv() { Err(e) } else { Ok(res) } - }), - fun(true, "recur", async |tpl: TAtom| { - call(sym_ref(sym!(macros::lower; tpl.i()).await), [call( - sym_ref(sym!(macros::resolve; tpl.i()).await), - [tpl.to_expr().await], - )]) - }), - fun(true, "resolve", |tpl: TAtom| async move { - exec("macros::resolve", async move |mut h| { - let ctx = tpl.ctx().clone(); - let root = refl(&ctx); - let tpl = own(tpl.clone()).await; - let mut macros = HashMap::new(); - for n in tpl.glossary() { - if let Ok(ReflMemKind::Const) = root.get_by_path(n).await.map(|m| m.kind()) { - let Ok(mac) = h.exec::>(sym_ref(n.clone())).await else { continue }; - let mac = own(mac).await; - macros.entry(mac.canonical_name(&ctx).await).or_insert(mac); - } - } - let mut named = HashMap::new(); - let mut priod = Vec::new(); - for (_, mac) in macros.iter() { - for rule in mac.0.rules.iter() { - if rule.glossary.is_subset(tpl.glossary()) { - match &rule.pattern { - Matcher::Named(m) => - named.entry(m.head()).or_insert(Vec::new()).push((m, mac, rule)), - Matcher::Priod(p) => priod.push((mac.0.prio, (p, mac, rule))), - } - } - } - } - let priod = priod.into_iter().sorted_unstable_by_key(|(p, _)| *p).map(|(_, r)| r).collect(); - let mut rctx = ResolveCtx { h, ctx: ctx.clone(), named, priod }; - let resolve_res = resolve(&mut rctx, &tpl).await; - std::mem::drop(rctx); - match resolve_res { - Some(out_tree) => out_tree.to_expr().await, - None => tpl.to_expr().await, - } - }) - .await - }), + fun(true, "resolve", async |tpl: TAtom| resolve(own(&tpl).await).await), // TODO test whether any of this worked - lazy(true, "common", async |_, ctx| { - let add_macro = { - clone!(ctx); - mk_macro(Some(1), ["+"], [( - mactreev!(ctx.i(); "...$" lhs 0 macros::common::+ "...$" rhs 1), - Lambda::new("std::number::add", async move |lhs: TAtom, rhs: TAtom| { - mactree!(ctx.i(); std::number::add - (macros::recur "'" lhs.ex();) - (macros::recur "'" rhs.ex();) - ) - }), - )]) - }; - let mul_macro = mk_macro(Some(2), ["*"], [( - mactreev!(ctx.i(); "...$" lhs 0 macros::common::* "...$" rhs 1), - Lambda::new("std::number::mul", async |lhs: TAtom, rhs: TAtom| { - mactree!(lhs.ctx().i(); std::number::mul - (macros::recur "'" lhs.ex();) - (macros::recur "'" rhs.ex();) - ) - }), - )]); - MemKind::Mod { members: chain!(add_macro, mul_macro).collect_vec() } - }), + prefix("common", [ + build_macro(None, ["..", "_"]).finish(), + build_macro(Some(1), ["+"]) + .rule(mactreev!("...$" lhs 0 macros::common::+ "...$" rhs 1), [async |[lhs, rhs]| { + call(sym_ref(sym!(std::number::add; i())), [resolve(lhs).await, resolve(rhs).await]) + }]) + .finish(), + build_macro(Some(2), ["*"]) + .rule(mactreev!("...$" lhs 0 macros::common::* "...$" rhs 1), [async |[lhs, rhs]| { + call(sym_ref(sym!(std::number::mul; i())), [resolve(lhs).await, resolve(rhs).await]) + }]) + .finish(), + build_macro(None, ["comma_list", ","]) + .rule( + mactreev!(macros::common::comma_list ( "...$" head 0 macros::common::, "...$" tail 1)), + [async |[head, tail]| { + call(sym_ref(sym!(std::tuple::cat; i())), [ + call(sym_ref(sym!(std::tuple::one; i())), [head.to_gen().await]), + resolve(mactree!(macros::common::comma_list "push" tail ;)).await, + ]) + }], + ) + .rule(mactreev!(macros::common::comma_list ( "...$" final_tail 0 )), [async |[tail]| { + call(sym_ref(sym!(std::tuple::one; i())), [tail.to_gen().await]) + }]) + .rule(mactreev!(macros::common::comma_list()), [async |[]| { + sym_ref(sym!(std::tuple::empty; i())) + }]) + .finish(), + build_macro(None, ["semi_list", ";"]) + .rule( + mactreev!(macros::common::semi_list ( "...$" head 0 macros::common::; "...$" tail 1)), + [async |[head, tail]| { + call(sym_ref(sym!(std::tuple::cat; i())), [ + call(sym_ref(sym!(std::tuple::one; i())), [resolve(head).await]), + resolve(mactree!(macros::common::semi_list "push" tail ;)).await, + ]) + }], + ) + .rule(mactreev!(macros::common::semi_list ( "...$" final_tail 0 )), [async |[tail]| { + call(sym_ref(sym!(std::tuple::one; i())), [resolve(tail).await]) + }]) + .rule(mactreev!(macros::common::semi_list()), [async |[]| { + sym_ref(sym!(std::tuple::empty; i())) + }]) + .finish(), + ]), ]) } diff --git a/orchid-std/src/macros/macro_line.rs b/orchid-std/src/macros/macro_line.rs index 0895b8d..7820af9 100644 --- a/orchid-std/src/macros/macro_line.rs +++ b/orchid-std/src/macros/macro_line.rs @@ -12,14 +12,15 @@ use orchid_base::parse::{ use orchid_base::tree::{Paren, Token}; use orchid_base::{clone, sym}; use orchid_extension::atom::TAtom; +use orchid_extension::context::i; use orchid_extension::conv::{ToExpr, TryFromExpr}; use orchid_extension::gen_expr::{atom, call, sym_ref}; use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; use crate::macros::let_line::{dealias_mac_v, parse_tokv}; -use crate::macros::macro_value::{Macro, MacroData, Matcher, Rule}; -use crate::macros::mactree::{glossary_v, map_mactree_v}; -use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher}; +use crate::macros::macro_value::{Macro, MacroData, Rule}; +use crate::macros::mactree::MacTreeSeq; +use crate::macros::rule::matcher::Matcher; use crate::{Int, MacTok}; #[derive(Default)] @@ -114,7 +115,7 @@ impl Parser for MacroLine { }; let pattern = parse_tokv(pattern, &ctx).await; let mut placeholders = Vec::new(); - map_mactree_v(&pattern, &mut false, &mut |tok| { + pattern.map(&mut false, &mut |tok| { if let MacTok::Ph(ph) = tok.tok() { placeholders.push((ph.clone(), tok.pos())) } @@ -123,23 +124,21 @@ impl Parser for MacroLine { let mut body_mactree = parse_tokv(body, &ctx).await; for (ph, ph_pos) in placeholders.iter().rev() { let name = ctx.module().suffix([ph.name.clone()], ctx.i()).await; - body_mactree = vec![ - MacTok::Lambda(MacTok::Name(name).at(ph_pos.clone()), body_mactree).at(ph_pos.clone()), - ] + body_mactree = + MacTreeSeq::new([ + MacTok::Lambda(MacTok::Name(name).at(ph_pos.clone()), body_mactree).at(ph_pos.clone()) + ]) } let body_sr = body.sr(); rules.push((name.clone(), placeholders, pattern)); lines.push(ParsedLine::cnst(&sr, &line.output, true, name, async move |ctx| { let rep = Reporter::new(); - let body = dealias_mac_v(body_mactree, &ctx, &rep).await; + let body = dealias_mac_v(&body_mactree, &ctx, &rep).await; let macro_input = MacTok::S(Paren::Round, body).at(body_sr.pos()); if let Some(e) = rep.errv() { return Err(e); } - Ok(call(sym_ref(sym!(macros::lower; ctx.i()).await), [call( - sym_ref(sym!(macros::resolve; ctx.i()).await), - [macro_input.to_expr().await], - )])) + Ok(call(sym_ref(sym!(macros::resolve; i())), [macro_input.to_gen().await])) })) } let mac_cell = Rc::new(OnceCell::new()); @@ -152,20 +151,15 @@ impl Parser for MacroLine { let rep = Reporter::new(); let rules = rules.borrow_mut().take().expect("once cell initializer runs"); let rules = stream::iter(rules) - .then(|(body_name, placeholders, pattern_macv)| { + .then(|(body_name, placeholders, pattern_rel)| { let cctx = &cctx; let rep = &rep; - let prio = &prio; async move { - let pattern_abs = dealias_mac_v(pattern_macv, cctx, rep).await; - let glossary = glossary_v(&pattern_abs).collect(); - let pattern_res = match prio { - None => NamedMatcher::new(&pattern_abs, cctx.i()).await.map(Matcher::Named), - Some(_) => PriodMatcher::new(&pattern_abs, cctx.i()).await.map(Matcher::Priod), - }; + let pattern = dealias_mac_v(&pattern_rel, cctx, rep).await; + let pattern_res = Matcher::new(pattern.clone()).await; let placeholders = placeholders.into_iter().map(|(ph, _)| ph.name).collect_vec(); match pattern_res { - Ok(pattern) => Some(Rule { body_name, pattern, glossary, placeholders }), + Ok(matcher) => Some(Rule { body_name, matcher, pattern, placeholders }), Err(e) => { rep.report(e); None diff --git a/orchid-std/src/macros/macro_system.rs b/orchid-std/src/macros/macro_system.rs index e070ef1..73fe613 100644 --- a/orchid-std/src/macros/macro_system.rs +++ b/orchid-std/src/macros/macro_system.rs @@ -1,15 +1,16 @@ -use orchid_base::interner::Interner; +use never::Never; use orchid_base::name::Sym; use orchid_base::reqnot::Receipt; use orchid_base::sym; use orchid_extension::atom::{AtomDynfo, AtomicFeatures}; +use orchid_extension::context::i; use orchid_extension::entrypoint::ExtReq; use orchid_extension::lexer::LexerObj; use orchid_extension::other_system::SystemHandle; use orchid_extension::parser::ParserObj; use orchid_extension::system::{System, SystemCard}; use orchid_extension::system_ctor::SystemCtor; -use orchid_extension::tree::GenMember; +use orchid_extension::tree::{GenMember, merge_trivial}; use crate::macros::instantiate_tpl::InstantiateTplCall; use crate::macros::let_line::LetLine; @@ -17,8 +18,10 @@ use crate::macros::macro_lib::gen_macro_lib; use crate::macros::macro_line::MacroLine; use crate::macros::macro_value::Macro; use crate::macros::mactree_lexer::MacTreeLexer; +use crate::macros::match_macros::gen_match_macro_lib; use crate::macros::ph_lexer::{PhAtom, PhLexer}; -use crate::macros::requests::MacroReq; +use crate::macros::std_macros::gen_std_macro_lib; +use crate::macros::utils::MacroBodyArgCollector; use crate::{MacTree, StdSystem}; #[derive(Default)] @@ -32,26 +35,30 @@ impl SystemCtor for MacroSystem { } impl SystemCard for MacroSystem { type Ctor = Self; - type Req = MacroReq; + type Req = Never; fn atoms() -> impl IntoIterator>> { [ Some(InstantiateTplCall::dynfo()), Some(MacTree::dynfo()), Some(Macro::dynfo()), Some(PhAtom::dynfo()), + Some(MacroBodyArgCollector::dynfo()), ] } } impl System for MacroSystem { - async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { todo!("Handle {req:?}") } - async fn prelude(i: &Interner) -> Vec { + async fn request(_: ExtReq<'_>, req: Never) -> Receipt<'_> { match req {} } + async fn prelude() -> Vec { vec![ - sym!(macros::resolve; i).await, - sym!(macros::common::+; i).await, - sym!(macros::common::*; i).await, + sym!(macros::common::+; i()), + sym!(macros::common::*; i()), + sym!(macros::common::,; i()), + sym!(std::tuple::t; i()), ] } fn lexers() -> Vec { vec![&MacTreeLexer, &PhLexer] } fn parsers() -> Vec { vec![&LetLine, &MacroLine] } - fn env() -> Vec { gen_macro_lib() } + async fn env() -> Vec { + merge_trivial([gen_macro_lib().await, gen_std_macro_lib().await, gen_match_macro_lib().await]) + } } diff --git a/orchid-std/src/macros/macro_value.rs b/orchid-std/src/macros/macro_value.rs index 014efce..fd6e6a4 100644 --- a/orchid-std/src/macros/macro_value.rs +++ b/orchid-std/src/macros/macro_value.rs @@ -1,15 +1,15 @@ use std::borrow::Cow; use std::rc::Rc; -use hashbrown::HashSet; use never::Never; use orchid_base::interner::Tok; use orchid_base::name::Sym; use orchid_extension::atom::Atomic; use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant}; -use orchid_extension::system::SysCtx; +use orchid_extension::context::i; -use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher}; +use crate::macros::mactree::MacTreeSeq; +use crate::macros::rule::matcher::Matcher; #[derive(Debug)] pub struct MacroData { @@ -21,23 +21,18 @@ pub struct MacroData { #[derive(Clone, Debug)] pub struct Macro(pub Rc); impl Macro { - pub async fn canonical_name(&self, ctx: &SysCtx) -> Sym { - self.0.module.suffix([self.0.rules[0].body_name.clone()], ctx.i()).await + pub async fn canonical_name(&self) -> Sym { + self.0.module.suffix([self.0.rules[0].body_name.clone()], &i()).await } } #[derive(Debug)] pub struct Rule { - pub pattern: Matcher, - pub glossary: HashSet, + pub pattern: MacTreeSeq, + pub matcher: Matcher, pub placeholders: Vec>, pub body_name: Tok, } -#[derive(Debug)] -pub enum Matcher { - Named(NamedMatcher), - Priod(PriodMatcher), -} impl Atomic for Macro { type Data = (); type Variant = OwnedVariant; diff --git a/orchid-std/src/macros/mactree.rs b/orchid-std/src/macros/mactree.rs index ce90f06..ad2b63f 100644 --- a/orchid-std/src/macros/mactree.rs +++ b/orchid-std/src/macros/mactree.rs @@ -5,10 +5,9 @@ use std::rc::Rc; use futures::FutureExt; use futures::future::join_all; use hashbrown::HashSet; -use itertools::Itertools; use orchid_api_derive::Coding; -use orchid_base::error::{OrcErrv, Reporter, mk_errv}; -use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants, fmt}; +use orchid_base::error::OrcErrv; +use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; use orchid_base::interner::Tok; use orchid_base::location::Pos; use orchid_base::name::Sym; @@ -16,16 +15,89 @@ use orchid_base::tl_cache; use orchid_base::tree::{Paren, indent}; use orchid_extension::atom::Atomic; use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant}; -use orchid_extension::conv::ToExpr; use orchid_extension::expr::Expr; -use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref}; -use orchid_extension::system::SysCtx; -use substack::Substack; -#[derive(Clone)] -pub struct LowerCtx<'a> { - pub sys: SysCtx, - pub rep: &'a Reporter, +fn union_rc_sets(seq: impl IntoIterator>>) -> Rc> { + let mut acc = Rc::>::default(); + for right in seq { + if acc.is_empty() { + acc = right; + continue; + } + if right.is_empty() { + continue; + } + acc = match (Rc::try_unwrap(acc), Rc::try_unwrap(right)) { + (Ok(mut left), Ok(right)) => { + left.extend(right); + Rc::new(left) + }, + (Ok(mut owned), Err(borrowed)) | (Err(borrowed), Ok(mut owned)) => { + owned.extend(borrowed.iter().cloned()); + Rc::new(owned) + }, + (Err(left), Err(right)) => Rc::new(left.union(&right).cloned().collect()), + } + } + acc +} + +#[derive(Debug, Clone)] +pub struct MacTreeSeq { + pub items: Rc>, + pub top_glossary: Rc>, + pub glossary: Rc>, +} +impl MacTreeSeq { + pub fn new(i: impl IntoIterator) -> Self { + let mut items = Vec::new(); + let mut top_glossary = HashSet::new(); + let mut glossary = HashSet::new(); + for item in i { + glossary.extend(item.glossary().iter().cloned()); + if let MacTok::Name(n) = item.tok() { + top_glossary.insert(n.clone()); + } + items.push(item); + } + Self { items: Rc::new(items), top_glossary: Rc::new(top_glossary), glossary: Rc::new(glossary) } + } + pub fn map Option>(&self, changed: &mut bool, map: &mut F) -> Self { + Self::new(self.items.iter().map(|tree| ro(changed, |changed| tree.map(changed, map)))) + } + pub fn glossary(&self) -> &HashSet { &self.glossary } + pub fn concat(self, other: Self) -> Self { + if self.items.is_empty() { + return other; + } else if other.items.is_empty() { + return self; + } + let items = match (Rc::try_unwrap(self.items), Rc::try_unwrap(other.items)) { + (Ok(mut left), Ok(mut right)) => { + left.append(&mut right); + left + }, + (Ok(mut left), Err(right)) => { + left.extend_from_slice(&right[..]); + left + }, + (Err(left), Ok(mut right)) => { + right.splice(0..0, left.iter().cloned()); + right + }, + (Err(left), Err(right)) => left.iter().chain(&right[..]).cloned().collect(), + }; + Self { + items: Rc::new(items), + top_glossary: union_rc_sets([self.top_glossary, other.top_glossary]), + glossary: union_rc_sets([self.glossary, other.glossary]), + } + } +} +impl Format for MacTreeSeq { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + mtreev_fmt(&self.items[..], c).await + } } #[derive(Debug, Clone)] @@ -38,66 +110,21 @@ impl MacTree { pub fn tok(&self) -> &MacTok { &self.tok } pub fn pos(&self) -> Pos { self.pos.clone() } pub fn glossary(&self) -> &HashSet { &self.glossary } - pub async fn lower(&self, ctx: LowerCtx<'_>, args: Substack<'_, Sym>) -> GExpr { - let expr = match self.tok() { - MacTok::Bottom(e) => bot(e.clone()), - MacTok::Lambda(arg, body) => { - let MacTok::Name(name) = &*arg.tok else { - return bot(mk_errv( - ctx.sys.i().i("Syntax error after macros").await, - "This token ends up as a binding, consider replacing it with a name", - [arg.pos()], - )); - }; - let arg_pos = args.len() as u64; - let args = args.push(name.clone()); - let body = match &body[..] { - [] => bot(mk_errv( - ctx.sys.i().i("Empty lambda body").await, - "Lambdas must evaluate to an expression", - [self.pos()], - )), - [f, argv @ ..] => call( - f.lower(ctx.clone(), args.clone()).boxed_local().await, - lower_v(argv, ctx, args).await, - ), - }; - lambda(arg_pos, body) + pub fn map Option>(&self, changed: &mut bool, map: &mut F) -> Self { + let tok = match map(self.clone()) { + Some(new_tok) => { + *changed = true; + return new_tok; }, - MacTok::Name(name) => match args.iter().enumerate().find(|(_, n)| *n == name) { - None => sym_ref(name.clone()), - Some((i, _)) => arg((args.len() - i - 1) as u64), + None => match &*self.tok { + MacTok::Lambda(arg, body) => + MacTok::Lambda(ro(changed, |changed| arg.map(changed, map)), body.map(changed, map)), + MacTok::Name(_) | MacTok::Value(_) => return self.clone(), + MacTok::Slot | MacTok::Ph(_) | MacTok::Bottom(_) => return self.clone(), + MacTok::S(p, body) => MacTok::S(*p, body.map(changed, map)), }, - MacTok::Ph(ph) => { - return bot(mk_errv( - ctx.sys.i().i("Placeholder in value").await, - format!("Placeholder {ph} is only supported in macro patterns"), - [self.pos()], - )); - }, - MacTok::S(Paren::Round, body) => match &body[..] { - [fun, argv @ ..] => call( - fun.lower(ctx.clone(), args.clone()).boxed_local().await, - lower_v(argv, ctx, args).await, - ), - [] => - return bot(mk_errv( - ctx.sys.i().i("Empty ()").await, - "Empty () is not a meaningful expression", - [self.pos()], - )), - }, - MacTok::S(..) => { - return bot(mk_errv( - ctx.sys.i().i("[] or {} after macros").await, - format!("{} didn't match any macro", fmt(self, ctx.sys.i()).await), - [self.pos()], - )); - }, - MacTok::Slot => panic!("Uninstantiated template should never be exposed"), - MacTok::Value(v) => v.clone().to_expr().await, }; - expr.at(self.pos()) + if *changed { tok.at(self.pos()) } else { self.clone() } } } impl Atomic for MacTree { @@ -119,35 +146,31 @@ impl Format for MacTree { } } -pub async fn lower_v(v: &[MacTree], ctx: LowerCtx<'_>, args: Substack<'_, Sym>) -> Vec { - join_all(v.iter().map(|t| t.lower(ctx.clone(), args.clone())).collect::>()).await -} - #[derive(Debug, Clone)] pub enum MacTok { - S(Paren, Vec), + S(Paren, MacTreeSeq), Name(Sym), /// Only permitted in arguments to `instantiate_tpl` Slot, Value(Expr), - Lambda(MacTree, Vec), + Lambda(MacTree, MacTreeSeq), /// Only permitted in "pattern" values produced by macro blocks, which are /// never accessed as variables by usercode Ph(Ph), Bottom(OrcErrv), } impl MacTok { - pub fn build_glossary(&self) -> HashSet { + pub fn build_glossary(&self) -> Rc> { match self { - MacTok::Bottom(_) | MacTok::Ph(_) | MacTok::Slot | MacTok::Value(_) => HashSet::new(), - MacTok::Name(sym) => HashSet::from([sym.clone()]), - MacTok::S(_, body) => body.iter().flat_map(|mt| &*mt.glossary).cloned().collect(), + MacTok::Bottom(_) | MacTok::Ph(_) | MacTok::Slot | MacTok::Value(_) => Rc::default(), + MacTok::Name(sym) => Rc::new(HashSet::from([sym.clone()])), + MacTok::S(_, body) => union_rc_sets(body.items.iter().map(|mt| mt.glossary.clone())), MacTok::Lambda(arg, body) => - body.iter().chain([arg]).flat_map(|mt| &*mt.glossary).cloned().collect(), + union_rc_sets(body.items.iter().chain([arg]).map(|mt| mt.glossary.clone())), } } pub fn at(self, pos: impl Into) -> MacTree { - MacTree { pos: pos.into(), glossary: Rc::new(self.build_glossary()), tok: Rc::new(self) } + MacTree { pos: pos.into(), glossary: self.build_glossary(), tok: Rc::new(self) } } } impl Format for MacTok { @@ -157,7 +180,7 @@ impl Format for MacTok { Self::Lambda(arg, b) => tl_cache!(Rc: Rc::new(Variants::default() .unbounded("\\{0} {1l}") .bounded("(\\{0} {1b})"))) - .units([arg.print(c).boxed_local().await, mtreev_fmt(b, c).await]), + .units([arg.print(c).boxed_local().await, b.print(c).await]), Self::Name(n) => format!("{n}").into(), Self::Ph(ph) => format!("{ph}").into(), Self::S(p, body) => match *p { @@ -165,7 +188,7 @@ impl Format for MacTok { Paren::Curly => tl_cache!(Rc: Rc::new(Variants::default().bounded("{{0b}}"))), Paren::Square => tl_cache!(Rc: Rc::new(Variants::default().bounded("[{0b}]"))), } - .units([mtreev_fmt(body, c).await]), + .units([body.print(c).await]), Self::Slot => "$SLOT".into(), Self::Bottom(err) if err.len() == 1 => format!("Bottom({}) ", err.one().unwrap()).into(), Self::Bottom(err) => format!("Botttom(\n{}) ", indent(&err.to_string())).into(), @@ -177,7 +200,7 @@ pub async fn mtreev_fmt<'b>( v: impl IntoIterator, c: &(impl FmtCtx + ?Sized), ) -> FmtUnit { - FmtUnit::sequence(" ", None, join_all(v.into_iter().map(|t| t.print(c))).await) + FmtUnit::sequence("", " ", "", None, join_all(v.into_iter().map(|t| t.print(c))).await) } #[derive(Clone, Debug, Hash, PartialEq, Eq)] @@ -203,36 +226,6 @@ pub enum PhKind { Vector { at_least_one: bool, priority: u8 }, } -pub fn map_mactree Option>( - src: &MacTree, - changed: &mut bool, - map: &mut F, -) -> MacTree { - let tok = match map(src.clone()) { - Some(new_tok) => { - *changed = true; - return new_tok; - }, - None => match &*src.tok { - MacTok::Lambda(arg, body) => MacTok::Lambda( - ro(changed, |changed| map_mactree(arg, changed, map)), - map_mactree_v(body, changed, map), - ), - MacTok::Name(_) | MacTok::Value(_) => return src.clone(), - MacTok::Slot | MacTok::Ph(_) | MacTok::Bottom(_) => return src.clone(), - MacTok::S(p, body) => MacTok::S(*p, map_mactree_v(body, changed, map)), - }, - }; - if *changed { tok.at(src.pos()) } else { src.clone() } -} -pub fn map_mactree_v Option>( - src: &[MacTree], - changed: &mut bool, - map: &mut F, -) -> Vec { - src.iter().map(|tree| ro(changed, |changed| map_mactree(tree, changed, map))).collect_vec() -} - /// reverse "or". Inside, the flag is always false, but raising it will raise /// the outside flag too. fn ro(flag: &mut bool, cb: impl FnOnce(&mut bool) -> T) -> T { @@ -241,7 +234,3 @@ fn ro(flag: &mut bool, cb: impl FnOnce(&mut bool) -> T) -> T { *flag |= new_flag; val } - -pub fn glossary_v(src: &[MacTree]) -> impl Iterator { - src.iter().flat_map(|mt| mt.glossary()).cloned() -} diff --git a/orchid-std/src/macros/mactree_lexer.rs b/orchid-std/src/macros/mactree_lexer.rs index 11da296..4c252fe 100644 --- a/orchid-std/src/macros/mactree_lexer.rs +++ b/orchid-std/src/macros/mactree_lexer.rs @@ -12,21 +12,21 @@ use orchid_extension::tree::{GenTok, GenTokTree, x_tok}; use crate::macros::instantiate_tpl::InstantiateTplCall; use crate::macros::let_line::parse_tok; -use crate::macros::mactree::{MacTok, MacTree}; +use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq}; #[derive(Default)] pub struct MacTreeLexer; impl Lexer for MacTreeLexer { const CHAR_FILTER: &'static [RangeInclusive] = &['\''..='\'']; - async fn lex<'a>(tail: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { + async fn lex<'a>(tail: &'a str, lctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { let Some(tail2) = tail.strip_prefix('\'') else { - return Err(err_not_applicable(ctx.i()).await); + return Err(err_not_applicable().await); }; let tail3 = tail2.trim_start(); let mut args = Vec::new(); - return match mac_tree(tail3, &mut args, ctx).await { + return match mac_tree(tail3, &mut args, lctx).await { Ok((tail4, mactree)) => { - let range = ctx.pos_tt(tail, tail4); + let range = lctx.pos_tt(tail, tail4); let tok = match &args[..] { [] => x_tok(mactree).await, _ => { @@ -38,7 +38,7 @@ impl Lexer for MacTreeLexer { }; Ok((tail4, tok.at(range))) }, - Err(e) => Ok((tail2, GenTok::Bottom(e).at(ctx.pos_lt(1, tail2)))), + Err(e) => Ok((tail2, GenTok::Bottom(e).at(lctx.pos_lt(1, tail2)))), }; async fn mac_tree<'a>( tail: &'a str, @@ -51,7 +51,8 @@ impl Lexer for MacTreeLexer { return loop { let tail2 = body_tail.trim_start(); if let Some(tail3) = tail2.strip_prefix(*rp) { - break Ok((tail3, MacTok::S(*paren, items).at(ctx.pos_tt(tail, tail3).pos()))); + let tok = MacTok::S(*paren, MacTreeSeq::new(items)); + break Ok((tail3, tok.at(ctx.pos_tt(tail, tail3).pos()))); } else if tail2.is_empty() { return Err(mk_errv( ctx.i().i("Unclosed block").await, @@ -83,7 +84,7 @@ impl Lexer for MacTreeLexer { body.push(body_tok); tail3 = tail5; } - Ok((tail3, MacTok::Lambda(param, body).at(ctx.pos_tt(tail, tail3).pos()))) + Ok((tail3, MacTok::Lambda(param, MacTreeSeq::new(body)).at(ctx.pos_tt(tail, tail3).pos()))) } else { let (tail2, sub) = ctx.recurse(tail).await?; let parsed = parse_tok(&sub, ctx).await.expect("Unexpected invalid token"); diff --git a/orchid-std/src/macros/match_macros.rs b/orchid-std/src/macros/match_macros.rs new file mode 100644 index 0000000..00308a5 --- /dev/null +++ b/orchid-std/src/macros/match_macros.rs @@ -0,0 +1,178 @@ +use std::borrow::Cow; + +use async_fn_stream::stream; +use futures::future::join_all; +use futures::{Stream, StreamExt, stream}; +use never::Never; +use orchid_api::ExprTicket; +use orchid_api_derive::Coding; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::format::fmt; +use orchid_base::name::Sym; +use orchid_base::sym; +use orchid_extension::atom::{Atomic, TAtom}; +use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; +use orchid_extension::context::i; +use orchid_extension::conv::ToExpr; +use orchid_extension::coroutine_exec::{ExecHandle, exec}; +use orchid_extension::expr::{Expr, ExprHandle}; +use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref}; +use orchid_extension::tree::{GenMember, fun, prefix}; + +use crate::macros::resolve::resolve; +use crate::macros::utils::{build_macro, mactree, mactreev}; +use crate::std::reflection::sym_atom::SymAtom; +use crate::std::tuple::Tuple; +use crate::{HomoTpl, MacTok, MacTree, OrcOpt, Tpl, UntypedTuple, api}; + +#[derive(Clone, Coding)] +pub struct MatcherData { + keys: Vec, + matcher: ExprTicket, +} +impl MatcherData { + async fn matcher(&self) -> Expr { Expr::from_handle(ExprHandle::from_ticket(self.matcher).await) } + pub async fn run_matcher( + &self, + h: &mut ExecHandle<'_>, + val: impl ToExpr, + ) -> OrcRes>> { + h.exec::>>(call(self.matcher().await.to_gen().await, [val.to_gen().await])) + .await + } + pub fn keys(&self) -> impl Stream { + stream(async |mut h| { + for tk in &self.keys { + h.emit(Sym::from_api(*tk, &i()).await).await + } + }) + } +} +#[derive(Clone)] +pub struct MatcherAtom { + /// The names that subresults may be bound to + pub(super) keys: Vec, + /// Takes the value-to-be-matched, returns an `option (tuple T1..TN)` of the + /// subresults to be bound to the names returned by [Self::keys] + pub(super) matcher: Expr, +} +impl Atomic for MatcherAtom { + type Data = MatcherData; + type Variant = OwnedVariant; +} +impl OwnedAtom for MatcherAtom { + type Refs = Never; + async fn val(&self) -> std::borrow::Cow<'_, Self::Data> { + Cow::Owned(MatcherData { + keys: self.keys.iter().map(|t| t.to_api()).collect(), + matcher: self.matcher.handle().ticket(), + }) + } +} + +pub async fn gen_match_macro_lib() -> Vec { + prefix("pattern", [ + fun( + true, + "match_one", + async |mat: TAtom, value: Expr, then: Expr, default: Expr| { + exec(async move |mut h| match mat.run_matcher(&mut h, value).await? { + OrcOpt(Some(values)) => + Ok(call(then.to_gen().await, join_all(values.0.into_iter().map(|x| x.to_gen())).await)), + OrcOpt(None) => Ok(default.to_gen().await), + }) + .await + }, + ), + fun(true, "matcher", async |names: HomoTpl>, matcher: Expr| MatcherAtom { + keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0, &i()).await)).await, + matcher, + }), + build_macro(None, ["match", "match_rule", "_row", "=>"]) + .rule(mactreev!("pattern::match" { "..$" rules 0 }), [async |[rules]| { + exec(async move |mut h| { + let rule_lines = h + .exec::>(call(sym_ref(sym!(macros::resolve; i())), [ + mactree!(macros::common::semi_list "push" rules.clone();).to_gen().await, + ])) + .await?; + let mut rule_atoms = Vec::<(TAtom, Expr)>::new(); + for line_exprh in rule_lines.iter() { + let line_mac = h + .exec::>(Expr::from_handle(ExprHandle::from_ticket(*line_exprh).await)) + .await?; + let Tpl((matcher, body)) = h + .exec(call(sym_ref(sym!(macros::resolve; i())), [ + mactree!(pattern::_row "push" own(&line_mac).await ;).to_gen().await, + ])) + .await?; + rule_atoms.push((matcher, body)); + } + let base_case = lambda(0, [bot(mk_errv( + i().i("No branches match").await, + "None of the pattern provided matches this value", + [rules.pos()], + ))]); + let match_expr = stream::iter(rule_atoms.into_iter().rev()) + .fold(base_case, async |tail, (mat, body)| { + lambda(0, [call(sym_ref(sym!(pattern::match_one; i())), [ + mat.to_gen().await, + arg(0), + body.to_gen().await, + call(tail, [arg(0)]), + ])]) + }) + .await; + Ok(match_expr) + }) + .await + }]) + .rule(mactreev!(pattern::match_rule (( "...$" pattern 0 ))), [async |[pattern]| { + resolve(mactree!(pattern::match_rule "push" pattern; )).await + }]) + .rule(mactreev!(pattern::_row ( "...$" pattern 0 pattern::=> "...$" value 1 )), [ + async |[pattern, mut value]| { + exec(async move |mut h| -> OrcRes, GExpr)>> { + let Ok(pat) = h + .exec::>(call(sym_ref(sym!(macros::resolve; i())), [ + mactree!(pattern::match_rule "push" pattern.clone();).to_gen().await, + ])) + .await + else { + return Err(mk_errv( + i().i("Invalid pattern").await, + format!("Could not parse {} as a match pattern", fmt(&pattern, &i()).await), + [pattern.pos()], + )); + }; + value = (pat.keys()) + .fold(value, async |value, name| mactree!("l_" name; ( "push" value ; ))) + .await; + Ok(Tpl((pat, resolve(value).await))) + }) + .await + }, + ]) + .finish(), + fun(true, "ref_body", async |val| OrcOpt(Some(UntypedTuple(vec![val])))), + build_macro(None, ["ref"]) + .rule(mactreev!(pattern::match_rule(pattern::ref "$" name)), [async |[name]| { + let MacTok::Name(name) = name.tok() else { + return Err(mk_errv( + i().i("pattern 'ref' requires a name to bind to").await, + format!( + "'ref' was interpreted as a binding matcher, \ + but it was followed by {} instead of a name", + fmt(&name, &i()).await + ), + [name.pos()], + )); + }; + Ok(MatcherAtom { + keys: vec![name.clone()], + matcher: sym_ref(sym!(pattern::ref_body; i())).to_expr().await, + }) + }]) + .finish(), + ]) +} diff --git a/orchid-std/src/macros/mod.rs b/orchid-std/src/macros/mod.rs index e94927a..774b75c 100644 --- a/orchid-std/src/macros/mod.rs +++ b/orchid-std/src/macros/mod.rs @@ -6,10 +6,11 @@ pub mod macro_system; mod macro_value; pub mod mactree; mod mactree_lexer; +pub mod match_macros; mod ph_lexer; -mod requests; mod resolve; mod rule; +pub mod std_macros; mod utils; use mactree::{MacTok, MacTree}; diff --git a/orchid-std/src/macros/ph_lexer.rs b/orchid-std/src/macros/ph_lexer.rs index 41167c6..cd6e019 100644 --- a/orchid-std/src/macros/ph_lexer.rs +++ b/orchid-std/src/macros/ph_lexer.rs @@ -4,8 +4,8 @@ use orchid_base::format::FmtUnit; use orchid_base::parse::{name_char, name_start}; use orchid_extension::atom::Atomic; use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; +use orchid_extension::context::i; use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; -use orchid_extension::system::SysCtx; use orchid_extension::tree::{GenTokTree, x_tok}; use crate::macros::mactree::{Ph, PhKind}; @@ -13,17 +13,15 @@ use crate::macros::mactree::{Ph, PhKind}; #[derive(Clone, Coding)] pub struct PhAtom(orchid_api::TStr, PhKind); impl PhAtom { - pub async fn to_full(&self, ctx: &SysCtx) -> Ph { - Ph { kind: self.1, name: ctx.i().ex(self.0).await } - } + pub async fn to_full(&self) -> Ph { Ph { kind: self.1, name: i().ex(self.0).await } } } impl Atomic for PhAtom { type Data = Self; type Variant = ThinVariant; } impl ThinAtom for PhAtom { - async fn print(&self, ctx: SysCtx) -> FmtUnit { - Ph { name: ctx.i().ex(self.0).await, kind: self.1 }.to_string().into() + async fn print(&self) -> FmtUnit { + Ph { name: i().ex(self.0).await, kind: self.1 }.to_string().into() } } @@ -54,7 +52,7 @@ impl Lexer for PhLexer { (prio_num, tail) } else { return Err(mk_errv( - ctx.ctx.i().i("Invalid priority, must be 0-255").await, + i().i("Invalid priority, must be 0-255").await, format!("{prio} is not a valid placeholder priority"), [ctx.pos_lt(prio.len(), tail)], )); @@ -70,10 +68,10 @@ impl Lexer for PhLexer { let (name, priority, tail) = name_and_prio(tail, ctx).await?; (tail, name, PhKind::Vector { at_least_one: true, priority }) } else { - return Err(err_not_applicable(ctx.ctx.i()).await); + return Err(err_not_applicable().await); } }; - let ph_atom = PhAtom(ctx.ctx.i().i::(name).await.to_api(), phkind); + let ph_atom = PhAtom(i().i::(name).await.to_api(), phkind); Ok((tail, x_tok(ph_atom).await.at(ctx.pos_tt(line, tail)))) } } diff --git a/orchid-std/src/macros/requests.rs b/orchid-std/src/macros/requests.rs deleted file mode 100644 index 2b7e935..0000000 --- a/orchid-std/src/macros/requests.rs +++ /dev/null @@ -1,67 +0,0 @@ -use orchid_api_derive::{Coding, Hierarchy}; -use orchid_api_traits::Request; - -use crate::api; -use crate::macros::mactree::PhKind; - -/* TODO: -Create handlers and wrappers for these, probably expose MacTree to other crates. -Define new extension binary to test the request functionality. -*/ - -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extendable] -pub enum MacroReq { - CreateMacro(CreateMacro), - CreateQuote(CreateQuote), -} - -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(MacroReq)] -pub struct CreateMacro { - pub module: api::TStrv, - pub prio: Option, - pub rules: Vec, -} -impl Request for CreateMacro { - type Response = api::ExprTicket; -} - -#[derive(Clone, Debug, Coding)] -pub struct CreateRule { - pub pattern: Vec, - pub body_name: api::TStr, -} - -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(MacroReq)] -pub struct CreateQuote(MsgMacTree); -impl Request for CreateQuote { - type Response = api::ExprTicket; -} - -#[derive(Clone, Debug, Coding)] -pub struct MsgMacTree { - pub tok: MsgMacTok, - pub location: api::Location, -} - -#[derive(Clone, Debug, Coding)] -pub enum MsgMacTok { - S(api::Paren, Vec), - Name(api::TStrv), - /// Only permitted in arguments to `instantiate_tpl` - Slot, - Value(api::ExprTicket), - Lambda(Box, Vec), - /// Only permitted in "pattern" values produced by macro blocks, which are - /// never accessed as variables by usercode - Ph(MsgPh), - Bottom(Vec), -} - -#[derive(Clone, Debug, Coding)] -pub struct MsgPh { - kind: PhKind, - name: api::TStr, -} diff --git a/orchid-std/src/macros/resolve.rs b/orchid-std/src/macros/resolve.rs index 7885c03..f804d78 100644 --- a/orchid-std/src/macros/resolve.rs +++ b/orchid-std/src/macros/resolve.rs @@ -1,91 +1,275 @@ -use futures::FutureExt; -use hashbrown::HashMap; +use std::ops::{Add, Range}; + +use async_fn_stream::stream; +use futures::{FutureExt, StreamExt}; +use hashbrown::{HashMap, HashSet}; use itertools::Itertools; +use orchid_base::error::mk_errv; +use orchid_base::format::fmt; use orchid_base::location::Pos; use orchid_base::name::Sym; -use orchid_base::sym; use orchid_base::tree::Paren; +use orchid_extension::atom::TAtom; +use orchid_extension::atom_owned::own; +use orchid_extension::context::i; use orchid_extension::conv::ToExpr; -use orchid_extension::coroutine_exec::ExecHandle; -use orchid_extension::gen_expr::{GExpr, call, sym_ref}; -use orchid_extension::system::SysCtx; +use orchid_extension::coroutine_exec::{ExecHandle, exec}; +use orchid_extension::gen_expr::{GExpr, bot, call, lambda, sym_ref}; +use orchid_extension::reflection::{ReflMemKind, refl}; +use subslice_offset::SubsliceOffset; +use substack::Substack; use crate::macros::macro_value::{Macro, Rule}; -use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher}; +use crate::macros::mactree::MacTreeSeq; use crate::macros::rule::state::{MatchState, StateEntry}; use crate::{MacTok, MacTree}; -pub struct ResolveCtx<'a> { - pub ctx: SysCtx, - pub h: ExecHandle<'a>, - pub named: HashMap>, - pub priod: Vec<(&'a PriodMatcher, &'a Macro, &'a Rule)>, +pub async fn resolve(tpl: MacTree) -> GExpr { + exec(async move |mut h| { + let root = refl(); + let mut macros = HashMap::new(); + for n in tpl.glossary() { + if let Ok(ReflMemKind::Const) = root.get_by_path(n).await.map(|m| m.kind()) { + let Ok(mac) = h.exec::>(sym_ref(n.clone())).await else { continue }; + let mac = own(&mac).await; + macros.entry(mac.canonical_name().await).or_insert(mac); + } + } + let mut exclusive = Vec::new(); + let mut prios = Vec::::new(); + let mut priod = Vec::::new(); + for (_, mac) in macros.iter() { + let mut record = FilteredMacroRecord { mac, rules: Vec::new() }; + for (rule_i, rule) in mac.0.rules.iter().enumerate() { + if rule.pattern.glossary.is_subset(tpl.glossary()) { + record.rules.push(rule_i); + } + } + if !record.rules.is_empty() { + match mac.0.prio { + None => exclusive.push(record), + Some(prio) => { + let i = prios.partition_point(|p| *p > prio); + prios.insert(i, prio); + priod.insert(i, record); + }, + } + } + } + let mut rctx = ResolveCtx { h, exclusive, priod }; + resolve_one(&mut rctx, Substack::Bottom, &tpl).await + }) + .await } -pub async fn resolve(ctx: &mut ResolveCtx<'_>, value: &MacTree) -> Option { +/// Rules belonging to one macro that passed a particular filter +pub struct FilteredMacroRecord<'a> { + mac: &'a Macro, + /// The rules in increasing order of index + rules: Vec, +} + +struct ResolveCtx<'a> { + pub h: ExecHandle<'a>, + /// If these overlap, that's a compile-time error + pub exclusive: Vec>, + /// If these overlap, the priorities decide the order. In case of a tie, the + /// order is unspecified + pub priod: Vec>, +} + +async fn resolve_one( + ctx: &mut ResolveCtx<'_>, + arg_stk: Substack<'_, Sym>, + value: &MacTree, +) -> GExpr { match value.tok() { MacTok::Ph(_) | MacTok::Slot => panic!("Forbidden element in value mactree"), - MacTok::Bottom(_) | MacTok::Value(_) | MacTok::Name(_) => None, - MacTok::Lambda(arg, body) => - Some(MacTok::Lambda(arg.clone(), resolve_seq(ctx, body).await?).at(value.pos())), - MacTok::S(ptyp, body) => Some(MacTok::S(*ptyp, resolve_seq(ctx, body).await?).at(value.pos())), + MacTok::Bottom(err) => bot(err.clone()), + MacTok::Value(v) => v.clone().to_gen().await, + MacTok::Name(n) => sym_ref(n.clone()), + MacTok::Lambda(arg, body) => { + let MacTok::Name(name) = &*arg.tok else { + return bot(mk_errv( + i().i("Syntax error after macros").await, + "This token ends up as a binding, consider replacing it with a name", + [arg.pos()], + )); + }; + let arg_pos = arg_stk.len() as u64; + let arg_stk = arg_stk.push(name.clone()); + lambda(arg_pos, [resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await]) + }, + MacTok::S(Paren::Round, body) => resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await, + MacTok::S(..) => bot(mk_errv( + i().i("Leftover [] or {} not matched by macro").await, + format!("{} was not matched by any macro", fmt(value, &i()).await), + [value.pos()], + )), } } -pub async fn resolve_seq(ctx: &mut ResolveCtx<'_>, val: &[MacTree]) -> Option> { - let mut any_changed = false; - let mut i = 0; - let mut val = val.to_vec(); - 'all_named: while i < val.len() { - 'one_named: { - let MacTok::Name(key) = val[i].tok() else { break 'one_named }; - let Some(options) = ctx.named.get(key) else { break 'one_named }; - let matches = (options.iter()) - .filter_map(|r| Some((r.1, r.2, r.0.apply(&val[i..], |_| false)?))) - .collect_vec(); - match matches.len() { - 0 => break 'one_named, - 1 => { - any_changed = true; - let (mac, rule, (state, tail)) = matches.into_iter().exactly_one().unwrap(); - let end = val.len() - tail.len(); - let body_call = mk_body_call(mac, rule, &state, &ctx.ctx).await; - std::mem::drop(state); - val.splice(i..end, [MacTok::Value(ctx.h.register(body_call).await).at(Pos::None)]); - i = end; - }, - 2.. => todo!("Named macros conflict!"), +type XMatches<'a> = Vec<(Range, &'a Macro, &'a Rule, MatchState<'a>)>; + +/// find the subsection of the slice that satisfies both the lower and upper +/// limit. +fn subsection( + slice: &[T], + lower_limit: impl FnMut(&T) -> bool, + mut upper_limit: impl FnMut(&T) -> bool, +) -> Range { + let start = slice.partition_point(lower_limit); + let len = slice[start..].partition_point(|t| !upper_limit(t)); + start..start + len +} + +async fn resolve_seq( + ctx: &mut ResolveCtx<'_>, + arg_stk: Substack<'_, Sym>, + val: MacTreeSeq, + fallback_pos: Pos, +) -> GExpr { + if val.items.is_empty() { + return bot(mk_errv( + i().i("Empty sequence").await, + "() or (\\arg ) left after macro execution. \ + This is usually caused by an incomplete call to a macro with bad error detection", + [fallback_pos], + )); + } + // A sorted collection of overlapping but non-nested matches to exclusive + // macros + let mut x_matches: XMatches = Vec::new(); + let top_glossary = val.top_glossary.clone(); + let mut new_val = val.items.to_vec(); + 'x_macros: for x in &ctx.exclusive { + let mut rules_iter = x.rules.iter(); + let ((before, state, after), rule) = 'rules: loop { + let Some(ridx) = rules_iter.next() else { continue 'x_macros }; + let rule = &x.mac.0.rules[*ridx]; + if rule.pattern.top_glossary.is_subset(&top_glossary) + && let Some(record) = rule.matcher.apply(&val.items[..], &|_| true).await + { + break 'rules (record, rule); + }; + }; + let new_r = (before.len()..new_val.len() - after.len(), x.mac, rule, state); + // elements that overlap with us + let overlap = + subsection(&x_matches[..], |r| new_r.0.start < r.0.end, |r| r.0.start < new_r.0.end); + let overlapping = &x_matches[overlap.clone()]; + // elements that fully contain us + let geq_range = + subsection(overlapping, |r| r.0.start <= new_r.0.start, |r| new_r.0.end <= r.0.end); + let geq = &overlapping[geq_range.clone()]; + // if any of these is equal to us, all of them must be, otherwise the larger + // ranges would have overridden the smaller ones + if let Some(example) = geq.first() { + // if they are equal to us, record the conflict. + if example.0 == new_r.0 { + let idx = (x_matches.subslice_offset(geq)) + .expect("this slice is statically derived from x_matches"); + x_matches.insert(idx, new_r); } - continue 'all_named; + // either way, we matched so no further rules can run. + continue 'x_macros; } - i += 1; - } - for (matcher, mac, rule) in &ctx.priod { - let Some(state) = matcher.apply(&val, |_| false) else { continue }; - return Some(vec![ - MacTok::Value(ctx.h.register(mk_body_call(mac, rule, &state, &ctx.ctx).await).await) - .at(Pos::None), - ]); - } - for expr in val.iter_mut() { - if let Some(new) = resolve(ctx, expr).boxed_local().await { - *expr = new; - any_changed = true; + // elements we fully contain. Equal ranges have been handled above + let lt_range = + subsection(overlapping, |r| new_r.0.start <= r.0.start, |r| r.0.end <= new_r.0.end); + let lt = &overlapping[lt_range.clone()]; + if lt.is_empty() { + // an empty range + let i = x_matches.partition_point(|r| r.0.start < new_r.0.start); + x_matches.insert(i, new_r); + } else { + let lt_start = + x_matches.subslice_offset(overlapping).expect("Slice statically derived from x_matches"); + x_matches.splice(lt_start..lt_start + lt_range.len(), [new_r]); } } - if any_changed { Some(val) } else { None } + // apply exclusive matches + if !x_matches.is_empty() { + // ranges of indices into x_matches which setwise conflict with each other. + // Pairwise conflict reporting is excess noise, but a single conflict error + // doesn't reveal where within the parenthesized block to look, so it's easiest + // to group them setwise even if these sets may associate macros which don't + // directly conflict. + let conflict_sets = (0..x_matches.len()).map(|x| x..x + 1).coalesce(|lran, rran| { + // each index was mapped to a range that contains only itself. Now we check if + // the last match in the first range overlaps the first match in the second + // range, and combine them if this is the case. + if x_matches[rran.start].0.start < x_matches[lran.end].0.end { + Ok(lran.start..rran.end) + } else { + Err((lran, rran)) + } + }); + let mac_conflict_tk = i().i("Macro conflict").await; + let error = conflict_sets + .filter(|r| 1 < r.len()) + .map(|set| { + mk_errv( + mac_conflict_tk.clone(), + "Multiple partially overlapping syntax elements detected. \n\ + Try parenthesizing whichever side is supposed to be the subexpression.", + x_matches[set].iter().flat_map(|rec| rec.3.names()).flat_map(|name| name.1).cloned(), + ) + }) + .reduce(|l, r| l + r); + if let Some(error) = error { + return bot(error); + } + // no conflicts, apply all exclusive matches + for (range, mac, rule, state) in x_matches.into_iter().rev() { + // backwards so that the non-overlapping ranges remain valid + let pos = (state.names().flat_map(|r| r.1).cloned().reduce(Pos::add)) + .expect("All macro rules must contain at least one locally defined name"); + let subex = ctx.h.register(mk_body_call(mac, rule, &state, pos.clone()).await).await; + new_val.splice(range, [MacTok::Value(subex).at(pos)]); + } + }; + // Does this glossary refresh actually pay off? + let top_glossary = (new_val.iter()) + .flat_map(|t| if let MacTok::Name(t) = t.tok() { Some(t.clone()) } else { None }) + .collect::>(); + for FilteredMacroRecord { mac, rules } in &ctx.priod { + for ridx in rules { + let rule = &mac.0.rules[*ridx]; + if !rule.pattern.top_glossary.is_subset(&top_glossary) { + continue; + } + let Some((pre, state, suf)) = rule.matcher.apply(&new_val, &|_| true).await else { continue }; + let range = pre.len()..new_val.len() - suf.len(); + let pos = (state.names().flat_map(|pair| pair.1).cloned().reduce(Pos::add)) + .expect("All macro rules must contain at least one locally defined name"); + let subex = ctx.h.register(mk_body_call(mac, rule, &state, pos.clone()).await).await; + std::mem::drop(state); + new_val.splice(range, [MacTok::Value(subex).at(pos)]); + } + } + let exprs = stream(async |mut h| { + for mt in new_val { + h.emit(resolve_one(ctx, arg_stk.clone(), &mt).await).await + } + }) + .collect::>() + .boxed_local() + .await; + exprs.into_iter().reduce(|f, x| call(f, [x])).expect( + "We checked first that it isn't empty, and named macros get replaced with their results", + ) } -async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, ctx: &SysCtx) -> GExpr { +async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, pos: Pos) -> GExpr { let mut call_args = vec![]; for name in rule.placeholders.iter() { call_args.push(match state.get(name).expect("Missing state entry for placeholder") { - StateEntry::Scalar(scal) => (**scal).clone().to_expr().await, - StateEntry::Vec(vec) => MacTok::S(Paren::Round, vec.to_vec()).at(Pos::None).to_expr().await, + StateEntry::Scalar(scal) => (**scal).clone().to_gen().await, + StateEntry::Vec(vec) => + MacTok::S(Paren::Round, MacTreeSeq::new(vec.iter().cloned())).at(Pos::None).to_gen().await, }); } - call(sym_ref(sym!(macros::lower; ctx.i()).await), [call( - sym_ref(mac.0.module.suffix([rule.body_name.clone()], ctx.i()).await), - call_args, - )]) + call(sym_ref(mac.0.module.suffix([rule.body_name.clone()], &i()).await), call_args) + .at(pos.clone()) } diff --git a/orchid-std/src/macros/rule/build.rs b/orchid-std/src/macros/rule/build.rs index fc434e8..7f9ddd1 100644 --- a/orchid-std/src/macros/rule/build.rs +++ b/orchid-std/src/macros/rule/build.rs @@ -2,9 +2,10 @@ use futures::FutureExt; use futures::future::join_all; use itertools::Itertools; use orchid_base::error::{OrcRes, mk_errv}; -use orchid_base::interner::{Interner, Tok}; +use orchid_base::interner::Tok; use orchid_base::join_ok; use orchid_base::side::Side; +use orchid_extension::context::i; use super::shared::{AnyMatcher, ScalMatcher, VecMatcher}; use super::vec_attrs::vec_attrs; @@ -31,29 +32,29 @@ fn scal_cnt<'a>(iter: impl Iterator) -> usize { iter.take_while(|expr| vec_attrs(expr).is_none()).count() } -pub async fn mk_any(pattern: &[MacTree], i: &Interner) -> OrcRes { +pub async fn mk_any(pattern: &[MacTree]) -> OrcRes { let left_split = scal_cnt(pattern.iter()); if pattern.len() <= left_split { - return Ok(AnyMatcher::Scalar(mk_scalv(pattern, i).await?)); + return Ok(AnyMatcher::Scalar(mk_scalv(pattern).await?)); } let (left, not_left) = pattern.split_at(left_split); let right_split = not_left.len() - scal_cnt(pattern.iter().rev()); let (mid, right) = not_left.split_at(right_split); join_ok! { - left = mk_scalv(left, i).await; - mid = mk_vec(mid, i).await; - right = mk_scalv(right, i).await; + left = mk_scalv(left).await; + mid = mk_vec(mid).await; + right = mk_scalv(right).await; } Ok(AnyMatcher::Vec { left, mid, right }) } /// Pattern MUST NOT contain vectorial placeholders -async fn mk_scalv(pattern: &[MacTree], i: &Interner) -> OrcRes> { - join_all(pattern.iter().map(|pat| mk_scalar(pat, i))).await.into_iter().collect() +async fn mk_scalv(pattern: &[MacTree]) -> OrcRes> { + join_all(pattern.iter().map(mk_scalar)).await.into_iter().collect() } /// Pattern MUST start and end with a vectorial placeholder -pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes { +pub async fn mk_vec(pattern: &[MacTree]) -> OrcRes { debug_assert!(!pattern.is_empty(), "pattern cannot be empty"); debug_assert!(pattern.first().map(vec_attrs).is_some(), "pattern must start with a vectorial"); debug_assert!(pattern.last().map(vec_attrs).is_some(), "pattern must end with a vectorial"); @@ -68,8 +69,8 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes { (&[], &[]) => Ok(VecMatcher::Placeh { key, nonzero }), (&[], _) => { join_ok! { - sep = mk_scalv(r_sep, i).await; - right = mk_vec(r_side, i).boxed_local().await; + sep = mk_scalv(r_sep).await; + right = mk_vec(r_side).boxed_local().await; } Ok(VecMatcher::Scan { direction: Side::Left, @@ -80,8 +81,8 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes { }, (_, &[]) => { join_ok! { - left = mk_vec(l_side, i).boxed_local().await; - sep = mk_scalv(l_sep, i).await; + left = mk_vec(l_side).boxed_local().await; + sep = mk_scalv(l_sep).await; } Ok(VecMatcher::Scan { direction: Side::Right, @@ -95,10 +96,10 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes { l_side.iter().chain(r_side.iter()).filter_map(vec_attrs).collect::>(); key_order.sort_by_key(|(_, prio, _)| -(*prio as i64)); join_ok! { - left = mk_vec(l_side, i).boxed_local().await; - left_sep = mk_scalv(l_sep, i).await; - right_sep = mk_scalv(r_sep, i).await; - right = mk_vec(r_side, i).boxed_local().await; + left = mk_vec(l_side).boxed_local().await; + left_sep = mk_scalv(l_sep).await; + right_sep = mk_scalv(r_sep).await; + right = mk_vec(r_side).boxed_local().await; } Ok(VecMatcher::Middle { left: Box::new(left), @@ -113,7 +114,7 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes { } /// Pattern MUST NOT be a vectorial placeholder -async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes { +async fn mk_scalar(pattern: &MacTree) -> OrcRes { Ok(match &*pattern.tok { MacTok::Name(n) => ScalMatcher::Name(n.clone()), MacTok::Ph(Ph { name, kind }) => match kind { @@ -122,10 +123,10 @@ async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes { }, PhKind::Scalar => ScalMatcher::Placeh { key: name.clone() }, }, - MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body, i).boxed_local().await?)), + MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(&body.items).boxed_local().await?)), MacTok::Lambda(..) => return Err(mk_errv( - i.i("Lambda in matcher").await, + i().i("Lambda in matcher").await, "Lambdas can't be matched for, only generated in templates", [pattern.pos()], )), @@ -136,50 +137,52 @@ async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes { #[cfg(test)] mod test { - use orchid_base::interner::Interner; use orchid_base::location::SrcRange; use orchid_base::sym; use orchid_base::tokens::Paren; + use orchid_extension::context::{i, mock_ctx, with_ctx}; use test_executors::spin_on; use super::mk_any; use crate::macros::MacTok; - use crate::macros::mactree::{Ph, PhKind}; + use crate::macros::mactree::{MacTreeSeq, Ph, PhKind}; #[test] fn test_scan() { - spin_on(async { - let i = Interner::new_master(); - let ex = |tok: MacTok| async { tok.at(SrcRange::mock(&i).await.pos()) }; + spin_on(with_ctx(mock_ctx(), async { + let ex = |tok: MacTok| async { tok.at(SrcRange::mock(&i()).await.pos()) }; let pattern = vec![ ex(MacTok::Ph(Ph { kind: PhKind::Vector { priority: 0, at_least_one: false }, - name: i.i("::prefix").await, + name: i().i("::prefix").await, })) .await, - ex(MacTok::Name(sym!(prelude::do; i).await)).await, - ex(MacTok::S(Paren::Round, vec![ - ex(MacTok::Ph(Ph { - kind: PhKind::Vector { priority: 0, at_least_one: false }, - name: i.i("expr").await, - })) - .await, - ex(MacTok::Name(sym!(prelude::; ; i).await)).await, - ex(MacTok::Ph(Ph { - kind: PhKind::Vector { priority: 1, at_least_one: false }, - name: i.i("rest").await, - })) - .await, - ])) + ex(MacTok::Name(sym!(prelude::do; i()))).await, + ex(MacTok::S( + Paren::Round, + MacTreeSeq::new([ + ex(MacTok::Ph(Ph { + kind: PhKind::Vector { priority: 0, at_least_one: false }, + name: i().i("expr").await, + })) + .await, + ex(MacTok::Name(sym!(prelude::; ; i()))).await, + ex(MacTok::Ph(Ph { + kind: PhKind::Vector { priority: 1, at_least_one: false }, + name: i().i("rest").await, + })) + .await, + ]), + )) .await, ex(MacTok::Ph(Ph { kind: PhKind::Vector { priority: 0, at_least_one: false }, - name: i.i("::suffix").await, + name: i().i("::suffix").await, })) .await, ]; - let matcher = mk_any(&pattern, &i).await.expect("This matcher isn't broken"); + let matcher = mk_any(&pattern).await.expect("This matcher isn't broken"); println!("{matcher}"); - }) + })) } } diff --git a/orchid-std/src/macros/rule/matcher.rs b/orchid-std/src/macros/rule/matcher.rs index 27b8f8e..c72b7f7 100644 --- a/orchid-std/src/macros/rule/matcher.rs +++ b/orchid-std/src/macros/rule/matcher.rs @@ -1,87 +1,61 @@ use std::fmt; +use std::rc::Rc; -use itertools::Itertools; use orchid_base::error::OrcRes; -use orchid_base::interner::{Interner, Tok}; -use orchid_base::location::Pos; use orchid_base::name::Sym; +use orchid_extension::context::i; use super::any_match::any_match; -use super::build::{mk_any, mk_vec}; -use super::shared::{AnyMatcher, VecMatcher}; +use super::build::mk_any; +use super::shared::AnyMatcher; use super::state::{MatchState, StateEntry}; use super::vec_attrs::vec_attrs; -use super::vec_match::vec_match; -use crate::macros::mactree::{Ph, PhKind}; +use crate::macros::mactree::{MacTreeSeq, Ph, PhKind}; use crate::macros::{MacTok, MacTree}; -pub struct NamedMatcher { +pub struct Matcher { inner: AnyMatcher, - head: Sym, - after_tok: Tok, -} -impl NamedMatcher { - pub async fn new(pattern: &[MacTree], i: &Interner) -> OrcRes { - let head = match pattern.first().map(|tree| tree.tok()) { - Some(MacTok::Name(name)) => name.clone(), - _ => panic!("Named matchers must begin with a name"), - }; - let after_tok = i.i("::after").await; - let inner = match pattern.last().and_then(vec_attrs).is_some() { - true => mk_any(pattern, i).await?, - false => { - let kind = PhKind::Vector { priority: 0, at_least_one: false }; - let suffix = [MacTok::Ph(Ph { name: after_tok.clone(), kind }).at(Pos::None)]; - mk_any(&pattern.iter().cloned().chain(suffix).collect_vec(), i).await? - }, - }; - Ok(Self { after_tok, inner, head }) - } - pub fn head(&self) -> Sym { self.head.clone() } - /// Also returns the tail, if any, which should be matched further - /// Note that due to how priod works below, the main usable information from - /// the tail is its length - pub fn apply<'a>( - &self, - seq: &'a [MacTree], - save_loc: impl Fn(Sym) -> bool, - ) -> Option<(MatchState<'a>, &'a [MacTree])> { - let mut state = any_match(&self.inner, seq, &save_loc)?; - match state.remove(self.after_tok.clone()) { - Some(StateEntry::Scalar(_)) => panic!("{} can never be a scalar entry!", self.after_tok), - Some(StateEntry::Vec(v)) => Some((state, v)), - None => Some((state, &[][..])), - } - } -} -impl fmt::Display for NamedMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) } -} -impl fmt::Debug for NamedMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "NamedMatcher({self})") } } -pub struct PriodMatcher(VecMatcher); -impl PriodMatcher { - pub async fn new(pattern: &[MacTree], i: &Interner) -> OrcRes { - assert!( - pattern.first().and_then(vec_attrs).is_some() && pattern.last().and_then(vec_attrs).is_some(), - "Prioritized matchers must start and end with a vectorial", - ); - Ok(Self(mk_vec(pattern, i).await?)) +impl Matcher { + pub async fn new(pattern: MacTreeSeq) -> OrcRes { + let mut pattern = Rc::unwrap_or_clone(pattern.items); + let kind = PhKind::Vector { at_least_one: false, priority: 0 }; + let first = pattern.first().expect("Empty pattern is not allowed"); + if vec_attrs(first).is_none() { + let pos = first.pos(); + pattern.insert(0, MacTok::Ph(Ph { name: i().i("::before").await, kind }).at(pos)); + } + let last = pattern.last().expect("first returned Some above"); + if vec_attrs(last).is_none() { + let pos = last.pos(); + pattern.insert(0, MacTok::Ph(Ph { name: i().i("::after").await, kind }).at(pos)); + } + Ok(Matcher { inner: mk_any(&pattern).await? }) } - /// tokens before the offset always match the prefix - pub fn apply<'a>( + /// Also returns the head and tail, which should be matched by overarching + /// matchers attempted later. + pub async fn apply<'a>( &self, seq: &'a [MacTree], - save_loc: impl Fn(Sym) -> bool, - ) -> Option> { - vec_match(&self.0, seq, &save_loc) + save_loc: &dyn Fn(Sym) -> bool, + ) -> Option<(&'a [MacTree], MatchState<'a>, &'a [MacTree])> { + let mut result = any_match(&self.inner, seq, &save_loc)?; + async fn remove_frame<'a>(result: &mut MatchState<'a>, key: &str) -> &'a [MacTree] { + match result.remove(i().i(key).await) { + Some(StateEntry::Scalar(_)) => panic!("{key} is defined in the constructor as a Vec"), + Some(StateEntry::Vec(v)) => v, + None => &[], + } + } + let before = remove_frame(&mut result, "::before").await; + let after = remove_frame(&mut result, "::after").await; + Some((before, result, after)) } } -impl fmt::Display for PriodMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } +impl fmt::Display for Matcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) } } -impl fmt::Debug for PriodMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PriodMatcher({self})") } +impl fmt::Debug for Matcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "NamedMatcher({self})") } } diff --git a/orchid-std/src/macros/rule/scal_match.rs b/orchid-std/src/macros/rule/scal_match.rs index dad5a6e..ceb095b 100644 --- a/orchid-std/src/macros/rule/scal_match.rs +++ b/orchid-std/src/macros/rule/scal_match.rs @@ -19,7 +19,7 @@ pub fn scal_match<'a>( (ScalMatcher::Placeh { key }, _) => Some(MatchState::from_ph(key.clone(), StateEntry::Scalar(expr))), (ScalMatcher::S(c1, b_mat), MacTok::S(c2, body)) if c1 == c2 => - any_match(b_mat, &body[..], save_loc), + any_match(b_mat, &body.items, save_loc), _ => None, } } diff --git a/orchid-std/src/macros/rule/state.rs b/orchid-std/src/macros/rule/state.rs index a850268..5ef4c07 100644 --- a/orchid-std/src/macros/rule/state.rs +++ b/orchid-std/src/macros/rule/state.rs @@ -54,6 +54,9 @@ impl<'a> MatchState<'a> { pub fn from_name(name: Sym, location: Pos) -> Self { Self { name_posv: HashMap::from([(name, vec![location])]), placeholders: HashMap::new() } } + pub fn names(&self) -> impl Iterator { + self.name_posv.iter().map(|(sym, vec)| (sym.clone(), &vec[..])) + } pub fn get(&self, key: &Tok) -> Option<&StateEntry<'a>> { self.placeholders.get(key) } pub fn remove(&mut self, name: Tok) -> Option> { self.placeholders.remove(&name) diff --git a/orchid-std/src/macros/std_macros.rs b/orchid-std/src/macros/std_macros.rs new file mode 100644 index 0000000..5911cfb --- /dev/null +++ b/orchid-std/src/macros/std_macros.rs @@ -0,0 +1,177 @@ +use futures::{StreamExt, stream}; +use orchid_base::error::OrcRes; +use orchid_base::sym; +use orchid_extension::atom::TAtom; +use orchid_extension::atom_owned::own; +use orchid_extension::context::i; +use orchid_extension::conv::ToExpr; +use orchid_extension::coroutine_exec::exec; +use orchid_extension::expr::Expr; +use orchid_extension::gen_expr::{GExpr, call, sym_ref}; +use orchid_extension::tree::{GenMember, fun, prefix}; + +use crate::macros::match_macros::MatcherAtom; +use crate::macros::resolve::resolve; +use crate::macros::utils::{build_macro, mactree, mactreev}; +use crate::{HomoTpl, MacTree, OrcOpt, Tpl}; + +pub async fn gen_std_macro_lib() -> Vec { + prefix("std", [ + prefix("option", [ + fun(false, "is_some_body", |sub: TAtom, val: OrcOpt| { + exec(async move |mut h| { + let Some(sub_val) = val.0 else { return Ok(OrcOpt(None)) }; + h.exec::>(call(sub.to_gen().await, [sub_val.to_gen().await])).await + }) + }), + fun(false, "is_none_body", async |val: OrcOpt| { + if val.0.is_none() { OrcOpt(Some(Tpl(()))) } else { OrcOpt(None) } + }), + build_macro(None, ["of", "empty"]) + .rule(mactreev!(pattern::match_rule ( std::option::of "...$" sub_pattern 0)), [ + |[sub]: [_; _]| { + exec(async move |mut h| { + let sub = h + .exec::>( + resolve(mactree!(pattern::match_rule "push" sub;)).await, + ) + .await?; + Ok(MatcherAtom { + keys: sub.keys().collect().await, + matcher: h + .register(call(sym_ref(sym!(std::option::is_some_body; i())), [sub + .to_gen() + .await])) + .await, + }) + }) + }, + ]) + .rule(mactreev!(pattern::match_rule(std::option::empty)), [|[]: [_; _]| { + exec(async |mut h| { + Ok(MatcherAtom { + keys: vec![], + matcher: h.register(sym_ref(sym!(std::option::is_none_body; i()))).await, + }) + }) + }]) + .finish(), + ]), + prefix("tuple", [ + build_macro(None, ["t"]) + .rule(mactreev!(std::tuple::t [ "...$" elements 0 ]), [|[elements]: [_; _]| { + exec(async move |mut h| { + let tup = h + .exec::>>(call(sym_ref(sym!(macros::resolve; i())), [ + mactree!((macros::common::comma_list "push" elements ;)).to_gen().await, + ])) + .await?; + let val = stream::iter(&tup.0[..]) + .fold(sym_ref(sym!(std::tuple::empty; i())), async |head, new| { + call(sym_ref(sym!(std::tuple::cat; i())), [ + head, + call(sym_ref(sym!(std::tuple::one; i())), [call( + sym_ref(sym!(macros::resolve; i())), + [new.clone().to_gen().await], + )]), + ]) + }) + .await; + Ok(val) + }) + }]) + .rule( + mactreev!(pattern::match_rule(std::tuple::t[ "...$" elements 0 macros::common::..])), + [async |[elements]: [_; _]| parse_tpl(elements, Some(mactree!(macros::common::_))).await], + ) + .rule( + mactreev!(pattern::match_rule( + std::tuple::t[ "...$" elements 1 macros::common::.. "...$" tail 0] + )), + [async |[elements, tail]: [_; _]| parse_tpl(elements, Some(tail)).await], + ) + .rule(mactreev!(pattern::match_rule(std::tuple::t[ "...$" elements 0])), [ + |[elements]: [_; _]| parse_tpl(elements, None), + ]) + .finish(), + fun(false, "matcher_body", tuple_matcher_body), + ]), + ]) +} + +fn parse_tpl(elements: MacTree, tail_matcher: Option) -> impl Future { + exec(async move |mut h| -> OrcRes { + let tup = h + .exec::>>(call(sym_ref(sym!(macros::resolve; i())), [ + mactree!((macros::common::comma_list "push" elements ;)).to_gen().await, + ])) + .await?; + let mut subs = Vec::with_capacity(tup.0.len()); + for mac_a in &tup.0[..] { + let mac = own(mac_a).await; + let sub = h + .exec::>(call(sym_ref(sym!(macros::resolve; i())), [ + mactree!(pattern::match_rule "push" mac ;).to_gen().await, + ])) + .await?; + subs.push(sub); + } + let tail_matcher = match tail_matcher { + Some(mac) => Some( + h.exec::>(call(sym_ref(sym!(macros::resolve; i())), [ + mactree!(pattern::match_rule "push" mac ;).to_gen().await, + ])) + .await?, + ), + None => None, + }; + Ok(MatcherAtom { + keys: stream::iter(&subs[..]) + .flat_map(|t| t.keys()) + .chain(stream::iter(&tail_matcher).flat_map(|mat| mat.keys())) + .collect() + .await, + matcher: call(sym_ref(sym!(std::tuple::matcher_body; i())), [ + HomoTpl(subs).to_gen().await, + OrcOpt(tail_matcher).to_gen().await, + ]) + .to_expr() + .await, + }) + }) +} + +fn tuple_matcher_body( + children: HomoTpl>, + tail: OrcOpt>, + value: HomoTpl, +) -> impl Future { + exec(async move |mut h| -> OrcRes> { + if value.0.len() < children.0.len() { + return Ok(OrcOpt(None)); + } + let mut binds = Vec::new(); + for (sub_mat, sub_val) in children.0.iter().zip(&value.0) { + match sub_mat.run_matcher(&mut h, sub_val.clone()).await? { + OrcOpt(None) => return Ok(OrcOpt(None)), + OrcOpt(Some(subres)) => binds.extend(subres.0), + } + } + match tail.0 { + None if children.0.len() < value.0.len() => return Ok(OrcOpt(None)), + None => (), + Some(tail_mat) => { + let tail_tpl = stream::iter(&value.0[children.0.len()..]) + .fold(sym_ref(sym!(std::tuple::empty; i())), async |prefix, new| { + call(sym_ref(sym!(std::tuple::cat; i())), [prefix, new.clone().to_gen().await]) + }) + .await; + match tail_mat.run_matcher(&mut h, tail_tpl).await? { + OrcOpt(Some(tail_binds)) => binds.extend(tail_binds.0), + OrcOpt(None) => return Ok(OrcOpt(None)), + } + }, + }; + todo!() + }) +} diff --git a/orchid-std/src/macros/utils.rs b/orchid-std/src/macros/utils.rs index 3372ee3..f0dfd0c 100644 --- a/orchid-std/src/macros/utils.rs +++ b/orchid-std/src/macros/utils.rs @@ -1,166 +1,275 @@ +use std::borrow::Cow; use std::rc::Rc; use async_fn_stream::stream; use futures::StreamExt; +use futures::future::LocalBoxFuture; use itertools::{Itertools, chain}; +use never::Never; use orchid_base::name::{NameLike, Sym, VPath}; +use orchid_extension::atom::{Atomic, TAtom}; +use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; +use orchid_extension::context::i; use orchid_extension::conv::ToExpr; -use orchid_extension::gen_expr::sym_ref; +use orchid_extension::gen_expr::{GExpr, sym_ref}; use orchid_extension::tree::{GenMember, MemKind, cnst, lazy}; -use crate::macros::macro_value::{Macro, MacroData, Matcher, Rule}; -use crate::macros::mactree::map_mactree_v; -use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher}; +use crate::macros::macro_value::{Macro, MacroData, Rule}; +use crate::macros::mactree::MacTreeSeq; +use crate::macros::rule::matcher::Matcher; use crate::{MacTok, MacTree}; -pub(crate) fn mk_macro( +pub type Args = Vec; + +#[derive(Clone)] +pub struct MacroBodyArgCollector { + argc: usize, + args: Args, + cb: Rc LocalBoxFuture<'static, GExpr>>, +} +impl Atomic for MacroBodyArgCollector { + type Data = (); + type Variant = OwnedVariant; +} +impl OwnedAtom for MacroBodyArgCollector { + type Refs = Never; + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } + async fn call_ref(&self, arg: orchid_extension::expr::Expr) -> GExpr { + eprintln!("This is an intermediary value. It should never be copied"); + self.clone().call(arg).await + } + async fn call(mut self, arg: orchid_extension::expr::Expr) -> GExpr { + let atom = (TAtom::downcast(arg.handle()).await).unwrap_or_else(|_| { + panic!("This is an intermediary value, the argument types are known in advance") + }); + self.args.push(own(&atom).await); + if self.argc == self.args.len() { + (self.cb)(self.args).await.to_gen().await + } else { + self.to_gen().await + } + } +} + +fn body_name(name: &str, counter: usize) -> String { format!("({name})::{counter}") } + +pub(crate) fn build_macro( prio: Option, own_kws: impl IntoIterator, - rules: impl IntoIterator, B)>, -) -> Vec { - let own_kws = own_kws.into_iter().collect_vec(); - let name = own_kws[0]; - let (patterns, bodies) = rules.into_iter().unzip::<_, _, Vec>, Vec>(); - let main_const = lazy(true, name, async move |path, ctx| { - let module = (Sym::new(path.split_last_seg().1.iter().cloned(), ctx.i()).await) - .expect("Default macro in global root"); - MemKind::Const( - Macro(Rc::new(MacroData { - module, - prio, - rules: stream(async |mut h| { - for (counter, pat) in patterns.into_iter().enumerate() { - let mut placeholders = Vec::new(); - map_mactree_v(&pat, &mut false, &mut |tt| { - if let MacTok::Ph(ph) = &*tt.tok { - placeholders.push(ph.name.clone()) - } - None - }); - let pattern = match prio { - Some(_) => Matcher::Priod(PriodMatcher::new(&pat, ctx.i()).await.unwrap()), - None => Matcher::Named(NamedMatcher::new(&pat, ctx.i()).await.unwrap()), - }; - h.emit(Rule { - glossary: pat.iter().flat_map(|t| t.glossary()).cloned().collect(), - pattern, - placeholders, - body_name: ctx.i().i(&format!("({name})::{counter}")).await, - }) - .await; - } - }) - .collect() +) -> MacroBuilder { + MacroBuilder { + prio, + own_kws: own_kws.into_iter().collect(), + patterns: Vec::new(), + body_consts: Vec::new(), + } +} +pub(crate) struct MacroBuilder { + prio: Option, + own_kws: Vec<&'static str>, + patterns: Vec, + body_consts: Vec, +} +impl MacroBuilder { + pub(crate) fn rule( + mut self, + pat: MacTreeSeq, + body: [impl AsyncFn([MacTree; N]) -> R + 'static; 1], + ) -> Self { + let [body] = body; + let body = Rc::new(body); + let name = &body_name(self.own_kws[0], self.body_consts.len()); + self.body_consts.extend(match N { + 0 => lazy(true, name, async move |_| { + let argv = [].into_iter().collect_array().expect("N is 0"); + MemKind::Const(body(argv).await.to_gen().await) + }), + 1.. => cnst(true, name, MacroBodyArgCollector { + argc: N, + args: Vec::new(), + cb: Rc::new(move |argv| { + let arr = argv.into_iter().collect_array::().expect("argc should enforce the length"); + let body = body.clone(); + Box::pin(async move { body(arr).await.to_gen().await }) + }), + }), + }); + self.patterns.push(pat); + self + } + pub(crate) fn finish(self) -> Vec { + let Self { own_kws, prio, patterns, body_consts } = self; + let name = own_kws[0]; + let main_const = lazy(true, name, async move |path| { + let module = (Sym::new(path.split_last_seg().1.iter().cloned(), &i()).await) + .expect("Default macro in global root"); + MemKind::Const( + Macro(Rc::new(MacroData { + module, + prio, + rules: stream(async |mut h| { + for (counter, pattern) in patterns.into_iter().enumerate() { + let mut placeholders = Vec::new(); + pattern.map(&mut false, &mut |tt| { + if let MacTok::Ph(ph) = &*tt.tok { + placeholders.push(ph.name.clone()) + } + None + }); + h.emit(Rule { + matcher: Matcher::new(pattern.clone()).await.unwrap(), + pattern, + placeholders, + body_name: i().i(&format!("({name})::{counter}")).await, + }) + .await; + } + }) + .collect() + .await, + })) + .to_gen() .await, - })) - .to_expr() - .await, - ) - }); - let kw_consts = own_kws[1..].iter().flat_map(|kw| { - lazy(true, kw, async |path, ctx| { - let name = VPath::new(path.split_last_seg().1.iter().cloned()) - .name_with_suffix(ctx.i().i(*kw).await) - .to_sym(ctx.i()) - .await; - MemKind::Const(sym_ref(name)) - }) - }); - let body_consts = (bodies.into_iter().enumerate()) - .flat_map(|(counter, body)| cnst(false, &format!("({name})::{counter}"), body)); - chain!(main_const, kw_consts, body_consts).collect() + ) + }); + let kw_consts = own_kws[1..].iter().flat_map(|kw| { + lazy(true, kw, async |path| { + let main_const_name = VPath::new(path.split_last_seg().1.iter().cloned()) + .name_with_suffix(i().i(name).await) + .to_sym(&i()) + .await; + MemKind::Const(sym_ref(main_const_name)) + }) + }); + chain!(main_const, kw_consts, body_consts).collect() + } } macro_rules! mactree { - ($i:expr; $($body:tt)*) => { - $crate::macros::utils::mactreev!($i; ($($body)*)).remove(0) + ($($body:tt)*) => { + $crate::macros::utils::mactreev!(($($body)*)).items[0].clone() }; } -macro_rules! mactreev { - (@RECUR $i:expr; $ret:ident) => {}; - (@RECUR $i:expr; $ret:ident "..$" $name:ident $prio:literal $($tail:tt)*) => { - ret.push(MacTok::Ph(Ph{ - name: i.i(stringify!($name)).await, - kind: PhKind::Vector{ at_least_one: false, priority: $prio } - }).at(Pos::Inherit)); - mactreev!(@RECUR $i; $ret $($tail)*); +macro_rules! mactreev_impl { + (@RECUR $ret:ident) => {}; + (@RECUR $ret:ident "..$" $name:ident $prio:literal $($tail:tt)*) => { + $ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{ + name: orchid_extension::context::i().i(stringify!($name)).await, + kind: $crate::macros::mactree::PhKind::Vector{ at_least_one: false, priority: $prio } + }).at(orchid_base::location::Pos::Inherit)); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); }; - (@RECUR $i:expr; $ret:ident "...$" $name:ident $prio:literal $($tail:tt)*) => { - $ret.push(MacTok::Ph(Ph{ - name: $i.i(stringify!($name)).await, + (@RECUR $ret:ident "...$" $name:ident $prio:literal $($tail:tt)*) => { + $ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{ + name: orchid_extension::context::i().i(stringify!($name)).await, kind: $crate::macros::mactree::PhKind::Vector{ at_least_one: true, priority: $prio } }).at(orchid_base::location::Pos::Inherit)); - mactreev!(@RECUR $i; $ret $($tail)*); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); }; - (@RECUR $i:expr; $ret:ident "$" $name:ident $($tail:tt)*) => { - $ret.push(MacTok::Ph(Ph{ - name: $i.i(stringify!(name)).await, + (@RECUR $ret:ident "$" $name:ident $($tail:tt)*) => { + $ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{ + name: orchid_extension::context::i().i(stringify!(name)).await, kind: $crate::macros::mactree::PhKind::Scalar }).at(orchid_base::location::Pos::Inherit)); - mactreev!(@RECUR $i; $ret $($tail)*); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); }; - (@RECUR $i:expr; $ret:ident "'" $arg:expr ; $($tail:tt)*) => { - $ret.push(MacTok::Value($arg).at(orchid_base::location::Pos::Inherit)); - mactreev!(@RECUR $i; $ret $($tail)*); + (@RECUR $ret:ident "Val" $arg:expr ; $($tail:tt)*) => { + $ret.push( + $crate::macros::mactree::MacTok::Value($arg) + .at(orchid_base::location::Pos::Inherit) + ); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); }; - (@RECUR $i:expr; $ret:ident "" $arg:expr ; $($tail:tt)*) => { + (@RECUR $ret:ident "push" $arg:expr ; $($tail:tt)*) => { $ret.push($arg); - mactreev!(@RECUR $i; $ret $($tail)*); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); }; - (@RECUR $i:expr; $ret:ident "l_" $arg:expr ; ($($body:tt)*) $($tail:tt)*) => { + (@RECUR $ret:ident "l_" $arg:expr ; ($($body:tt)*) $($tail:tt)*) => { $ret.push(MacTok::Lambda( - MacTok::Value($arg).at(orchid_base::location::Pos::Inherit), - mactreev!(i; $($body)*) - ).at(Pos::Inherit)); - mactreev!(@RECUR $i; $ret $($tail)*); - }; - (@RECUR $i:expr; $ret:ident "l" $argh:tt $(:: $arg:tt)* ($($body:tt)*) $($tail:tt)*) => { - $ret.push(MacTok::Lambda( - MacTok::Name(sym!($argh $(:: $arg)*; $i).await).at(orchid_base::location::Pos::Inherit), - mactreev!(i; $($body)*) + MacTok::Name($arg).at(orchid_base::location::Pos::Inherit), + $crate::macros::utils::mactreev!($($body)*) ).at(orchid_base::location::Pos::Inherit)); - mactreev!(@RECUR $i; $ret $($tail)*); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); }; - (@RECUR $i:expr; $ret:ident ( $($body:tt)* ) $($tail:tt)*) => { + (@RECUR $ret:ident "l" $argh:tt $(:: $arg:tt)+ ($($body:tt)*) $($tail:tt)*) => { + $ret.push(MacTok::Lambda( + MacTok::Name(sym!($argh $(:: $arg)+; orchid_extension::context::i()).await).at(orchid_base::location::Pos::Inherit), + $crate::macros::utils::mactreev!($($body)*) + ).at(orchid_base::location::Pos::Inherit)); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); + }; + (@RECUR $ret:ident $name:literal $($tail:tt)*) => { + assert!( + $name.contains("::"), + "{} was treated as a name, but it doesn't have a namespace prefix", + $name + ); + let sym = orchid_base::name::Sym::parse( + $name, + &orchid_extension::context::i() + ).await.expect("Empty string in sym literal in Rust"); $ret.push( - MacTok::S(orchid_base::tree::Paren::Round, mactreev!($i; $($body)*)) + $crate::macros::mactree::MacTok::Name(sym) .at(orchid_base::location::Pos::Inherit) ); - mactreev!(@RECUR $i; $ret $($tail)*); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); }; - (@RECUR $i:expr; $ret:ident [ $($body:tt)* ] $($tail:tt)*) => { + (@RECUR $ret:ident ( $($body:tt)* ) $($tail:tt)*) => { $ret.push( - MacTok::S(orchid_base::tree::Paren::Square, mactreev!($i; $($body)*)) + $crate::macros::mactree::MacTok::S( + orchid_base::tree::Paren::Round, + $crate::macros::utils::mactreev!($($body)*) + ) + .at(orchid_base::location::Pos::Inherit) + ); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); + }; + (@RECUR $ret:ident [ $($body:tt)* ] $($tail:tt)*) => { + $ret.push( + $crate::macros::mactree::MacTok::S( + orchid_base::tree::Paren::Square, + $crate::macros::utils::mactreev!($($body)*) + ) + .at(orchid_base::location::Pos::Inherit) + ); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); + }; + (@RECUR $ret:ident { $($body:tt)* } $($tail:tt)*) => { + $ret.push( + $crate::macros::mactree::MacTok::S( + orchid_base::tree::Paren::Curly, + $crate::macros::utils::mactreev!($($body)*) + ) + .at(orchid_base::location::Pos::Inherit) + ); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); + }; + (@RECUR $ret:ident $ns:ident :: $nhead:tt $($tail:tt)*) => { + $crate::macros::utils::mactreev_impl!(@NAME_MUNCHER $ret ($ns :: $nhead) $($tail)*) + }; + (@NAME_MUNCHER $ret:ident ($($munched:tt)*) :: $name:tt $($tail:tt)*) => { + $crate::macros::utils::mactreev_impl!(@NAME_MUNCHER $ret ($($munched)* :: $name) $($tail)*) + }; + (@NAME_MUNCHER $ret:ident ($($munched:tt)*) $($tail:tt)*) => { + let sym = orchid_base::sym!($($munched)* ; orchid_extension::context::i()); + $ret.push( + $crate::macros::mactree::MacTok::Name(sym) .at(orchid_base::location::Pos::Inherit) ); - mactreev!(@RECUR $i; $ret $($tail)*); + $crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*); }; - (@RECUR $i:expr; $ret:ident { $($body:tt)* } $($tail:tt)*) => { - $ret.push( - MacTok::S(orchid_base::tree::Paren::Curly, mactreev!($i; $($body)*)) - .at(orchid_base::location::Pos::Inherit) - ); - mactreev!(@RECUR $i; $ret $($tail)*); - }; - (@RECUR $i:expr; $ret:ident $nhead:tt $($tail:tt)*) => { - mactreev!(@NAME_MUNCHER $i; $ret ($nhead) $($tail)*) - }; - (@NAME_MUNCHER $i:expr; $ret:ident ($($munched:tt)*) :: $name:tt $($tail:tt)*) => { - mactreev!(@NAME_MUNCHER $i; $ret ($($munched)* :: $name) $($tail)*) - }; - (@NAME_MUNCHER $i:expr; $ret:ident ($($munched:tt)*) $($tail:tt)*) => { - let sym = orchid_base::sym!($($munched)* ; $i).await; - $ret.push(MacTok::Name(sym).at(orchid_base::location::Pos::Inherit)); - mactreev!(@RECUR $i; $ret $($tail)*); - }; - ($i:expr; ) => { Vec::new() }; - ($i:expr; $($tail:tt)*) => { + () => { Vec::new() }; +} +macro_rules! mactreev { + ($($tail:tt)*) => { { - let mut ret = Vec::::new(); - mactreev!(@RECUR $i; ret $($tail)*); - ret + let mut ret = Vec::<$crate::macros::mactree::MacTree>::new(); + ret.extend([]); // silence unneeded mut warning + $crate::macros::utils::mactreev_impl!(@RECUR ret $($tail)*); + $crate::macros::mactree::MacTreeSeq::new(ret) } }; } -pub(crate) use {mactree, mactreev}; + +pub(crate) use {mactree, mactreev, mactreev_impl}; diff --git a/orchid-std/src/std/mod.rs b/orchid-std/src/std/mod.rs index 17b8ee9..7087cc2 100644 --- a/orchid-std/src/std/mod.rs +++ b/orchid-std/src/std/mod.rs @@ -1,4 +1,8 @@ pub mod number; -pub mod string; - +pub mod option; +pub mod protocol; +pub mod record; +pub mod reflection; pub mod std_system; +pub mod string; +pub mod tuple; diff --git a/orchid-std/src/std/number/num_atom.rs b/orchid-std/src/std/number/num_atom.rs index 919b885..674a027 100644 --- a/orchid-std/src/std/number/num_atom.rs +++ b/orchid-std/src/std/number/num_atom.rs @@ -1,15 +1,20 @@ use orchid_api_derive::Coding; +use orchid_api_traits::Request; use orchid_base::error::OrcRes; use orchid_base::format::FmtUnit; +use orchid_base::name::Sym; use orchid_base::number::Numeric; -use orchid_extension::atom::{AtomFactory, Atomic, AtomicFeatures, ToAtom, TAtom}; +use orchid_extension::atom::{AtomFactory, Atomic, AtomicFeatures, Supports, TAtom, ToAtom}; use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; +use orchid_extension::context::i; use orchid_extension::conv::TryFromExpr; use orchid_extension::expr::Expr; -use orchid_extension::system::SysCtx; use ordered_float::NotNan; use rust_decimal::prelude::Zero; +use crate::std::protocol::types::GetTagIdMethod; +use crate::std::string::to_string::ToStringMethod; + #[derive(Clone, Debug, Coding)] pub struct Int(pub i64); impl Atomic for Int { @@ -17,13 +22,23 @@ impl Atomic for Int { type Data = Self; } impl ThinAtom for Int { - async fn print(&self, _: SysCtx) -> FmtUnit { self.0.to_string().into() } + async fn print(&self) -> FmtUnit { self.0.to_string().into() } } impl TryFromExpr for Int { async fn try_from_expr(expr: Expr) -> OrcRes { TAtom::::try_from_expr(expr).await.map(|t| t.value) } } +impl Supports for Int { + async fn handle(&self, _: GetTagIdMethod) -> ::Response { + Sym::parse("std::number::Int", &i()).await.unwrap().to_api() + } +} +impl Supports for Int { + async fn handle(&self, _: ToStringMethod) -> ::Response { + self.0.to_string() + } +} #[derive(Clone, Debug, Coding)] pub struct Float(pub NotNan); @@ -32,13 +47,18 @@ impl Atomic for Float { type Data = Self; } impl ThinAtom for Float { - async fn print(&self, _: SysCtx) -> FmtUnit { self.0.to_string().into() } + async fn print(&self) -> FmtUnit { self.0.to_string().into() } } impl TryFromExpr for Float { async fn try_from_expr(expr: Expr) -> OrcRes { Ok(Self(Num::try_from_expr(expr).await?.0.to_f64())) } } +impl Supports for Float { + async fn handle(&self, _: ToStringMethod) -> ::Response { + self.0.to_string() + } +} pub struct Num(pub Numeric); impl TryFromExpr for Num { diff --git a/orchid-std/src/std/number/num_lexer.rs b/orchid-std/src/std/number/num_lexer.rs index d4e6495..a2a9240 100644 --- a/orchid-std/src/std/number/num_lexer.rs +++ b/orchid-std/src/std/number/num_lexer.rs @@ -3,6 +3,7 @@ use std::ops::RangeInclusive; use orchid_base::error::OrcRes; use orchid_base::number::{num_to_errv, parse_num}; use orchid_extension::atom::ToAtom; +use orchid_extension::context::i; use orchid_extension::lexer::{LexContext, Lexer}; use orchid_extension::tree::{GenTokTree, x_tok}; @@ -12,13 +13,13 @@ use super::num_atom::Num; pub struct NumLexer; impl Lexer for NumLexer { const CHAR_FILTER: &'static [RangeInclusive] = &['0'..='9']; - async fn lex<'a>(all: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { + async fn lex<'a>(all: &'a str, lxcx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { let ends_at = all.find(|c: char| !c.is_ascii_hexdigit() && !"xX._pP".contains(c)); let (chars, tail) = all.split_at(ends_at.unwrap_or(all.len())); let fac = match parse_num(chars) { Ok(numeric) => Num(numeric).to_atom_factory(), - Err(e) => return Err(num_to_errv(e, ctx.pos(all), ctx.src(), ctx.ctx.i()).await), + Err(e) => return Err(num_to_errv(e, lxcx.pos(all), lxcx.src(), &i()).await), }; - Ok((tail, x_tok(fac).await.at(ctx.pos_lt(chars.len(), tail)))) + Ok((tail, x_tok(fac).await.at(lxcx.pos_lt(chars.len(), tail)))) } } diff --git a/orchid-std/src/std/number/num_lib.rs b/orchid-std/src/std/number/num_lib.rs index 9d71446..5df711e 100644 --- a/orchid-std/src/std/number/num_lib.rs +++ b/orchid-std/src/std/number/num_lib.rs @@ -6,28 +6,28 @@ use super::num_atom::{Float, HomoArray, Int, Num}; pub fn gen_num_lib() -> Vec { prefix("std::number", [ - fun(true, "add", |a: Num, b: Num| async move { + fun(true, "add", async |a: Num, b: Num| { Num(match HomoArray::new([a.0, b.0]) { HomoArray::Int([a, b]) => Numeric::Int(a + b), HomoArray::Float([a, b]) => Numeric::Float(a + b), }) }), - fun(true, "neg", |a: Num| async move { + fun(true, "neg", async |a: Num| { Num(match a.0 { Numeric::Int(i) => Numeric::Int(-i), Numeric::Float(f) => Numeric::Float(-f), }) }), - fun(true, "mul", |a: Num, b: Num| async move { + fun(true, "mul", async |a: Num, b: Num| { Num(match HomoArray::new([a.0, b.0]) { HomoArray::Int([a, b]) => Numeric::Int(a * b), HomoArray::Float([a, b]) => Numeric::Float(a * b), }) }), - fun(true, "idiv", |a: Int, b: Int| async move { Int(a.0 / b.0) }), - fun(true, "imod", |a: Int, b: Int| async move { Int(a.0 % b.0) }), - fun(true, "fdiv", |a: Float, b: Float| async move { Float(a.0 / b.0) }), - fun(true, "fmod", |a: Float, b: Float| async move { + fun(true, "idiv", async |a: Int, b: Int| Int(a.0 / b.0)), + fun(true, "imod", async |a: Int, b: Int| Int(a.0 % b.0)), + fun(true, "fdiv", async |a: Float, b: Float| Float(a.0 / b.0)), + fun(true, "fmod", async |a: Float, b: Float| { Float(a.0 - NotNan::new((a.0 / b.0).trunc()).unwrap() * b.0) }), ]) diff --git a/orchid-std/src/std/option.rs b/orchid-std/src/std/option.rs new file mode 100644 index 0000000..8443cb6 --- /dev/null +++ b/orchid-std/src/std/option.rs @@ -0,0 +1,75 @@ +use std::borrow::Cow; +use std::pin::Pin; + +use futures::AsyncWrite; +use orchid_api_traits::Encode; +use orchid_base::error::mk_errv; +use orchid_base::sym; +use orchid_extension::atom::{Atomic, ForeignAtom, TAtom}; +use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; +use orchid_extension::context::i; +use orchid_extension::conv::{ToExpr, TryFromExpr}; +use orchid_extension::expr::{Expr, ExprHandle}; +use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::tree::{GenMember, cnst, fun, prefix}; + +use crate::{OrcString, api}; + +#[derive(Clone)] +pub struct OptAtom(Option); +impl Atomic for OptAtom { + type Data = Option; + type Variant = OwnedVariant; +} +impl OwnedAtom for OptAtom { + type Refs = Vec; + async fn val(&self) -> Cow<'_, Self::Data> { + Cow::Owned(self.0.as_ref().map(|ex| ex.handle().ticket())) + } + async fn deserialize(mut ctx: impl DeserializeCtx, refs: Self::Refs) -> Self { + Self(ctx.read::().await.then(|| refs.into_iter().next().unwrap())) + } + async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs { + self.0.is_some().encode(write).await; + self.0.iter().cloned().collect() + } +} + +pub struct OrcOpt(pub Option); +impl TryFromExpr for OrcOpt { + async fn try_from_expr(expr: Expr) -> orchid_base::error::OrcRes { + let atom = TAtom::::try_from_expr(expr).await?; + match atom.value { + None => Ok(OrcOpt(None)), + Some(tk) => Ok(OrcOpt(Some( + T::try_from_expr(Expr::from_handle(ExprHandle::from_ticket(tk).await)).await?, + ))), + } + } +} +impl ToExpr for OrcOpt { + async fn to_gen(self) -> orchid_extension::gen_expr::GExpr { + if let Some(val) = self.0 { + call(sym_ref(sym!(std::option::some; i())), [val.to_gen().await]) + } else { + sym_ref(sym!(std::option::none; i())) + } + } +} + +pub fn gen_option_lib() -> Vec { + prefix("std::option", [ + cnst(true, "none", OptAtom(None)), + fun(true, "some", async |ex: Expr| OptAtom(Some(ex))), + fun(true, "expect", async |opt: ForeignAtom, msg: OrcString| { + match OrcOpt::try_from_expr(opt.clone().ex()).await? { + OrcOpt(Some(ex)) => Ok::(ex), + OrcOpt(None) => Err(mk_errv( + i().i("Unwrapped std::option::none").await, + msg.get_string().await.as_str(), + [opt.pos()], + )), + } + }), + ]) +} diff --git a/orchid-std/src/std/protocol/mod.rs b/orchid-std/src/std/protocol/mod.rs new file mode 100644 index 0000000..6ccc49f --- /dev/null +++ b/orchid-std/src/std/protocol/mod.rs @@ -0,0 +1,4 @@ +pub mod parse_impls; +pub mod proto_parser; +pub mod type_parser; +pub mod types; diff --git a/orchid-std/src/std/protocol/parse_impls.rs b/orchid-std/src/std/protocol/parse_impls.rs new file mode 100644 index 0000000..4557ab2 --- /dev/null +++ b/orchid-std/src/std/protocol/parse_impls.rs @@ -0,0 +1,78 @@ +use itertools::{Itertools, chain}; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::interner::Tok; +use orchid_base::name::Sym; +use orchid_base::parse::{ + Import, ParseCtx, Parsed, Snippet, expect_tok, line_items, parse_multiname, token_errv, +}; +use orchid_base::tree::{Paren, Token}; +use orchid_extension::parser::{ + PTokTree, ParsCtx, ParsedLine, ParsedLineKind, p_tree2gen, p_v2gen, +}; + +pub async fn parse_impls( + ctx: &ParsCtx<'_>, + lines: &mut Vec, + impls: &mut Vec<(Sym, Tok)>, + body_tt: &PTokTree, +) -> OrcRes<()> { + let i = ctx.i().clone(); + let body = match &body_tt.tok { + Token::S(Paren::Round, body) => line_items(ctx, Snippet::new(body_tt, body)).await, + Token::S(ptyp, _) => + return Err(mk_errv( + i.i("Incorrect paren type").await, + format!("Expected () block, found {ptyp}"), + [body_tt.sr().pos()], + )), + _ => + return Err( + token_errv(ctx, body_tt, "Expected body", |s| { + format!("Expected (impl ...) block, found {s}") + }) + .await, + ), + }; + for Parsed { tail: line, output: comments } in body { + if let Ok(Parsed { tail, .. }) = expect_tok(ctx, line, i.i("impl").await).await { + let Parsed { tail, output: name_tt } = parse_multiname(ctx, tail).await?; + let (name, name_sr) = match name_tt.into_iter().at_most_one() { + Ok(None) => panic!("multiname is always at least one name"), + Ok(Some(ref n @ Import { name: Some(_), ref sr, .. })) => + (n.clone().mspath().to_sym(&i).await, sr.clone()), + Ok(Some(Import { name: None, sr, .. })) => + return Err(mk_errv( + i.i("impl line with globstar").await, + "::* is not permitted in a protocol impl", + [sr.pos()], + )), + Err(e) => + return Err(mk_errv( + i.i("Impl line with multiple protocol names").await, + "::() is not permitted in a protocol impl", + e.map(|i| i.sr.pos()), + )), + }; + let Parsed { tail, .. } = expect_tok(ctx, tail, i.i("as").await).await?; + let cnst_name = i.i(&format!("{}{}", lines.len(), name.iter().join("__"))).await; + lines.push(ParsedLine { + comments, + sr: line.sr(), + kind: ParsedLineKind::Rec(Vec::from_iter(chain![ + [Token::Name(i.i("let").await).at(line.sr())], + [Token::Name(cnst_name.clone()).at(name_sr)], + [Token::Name(i.i("=").await).at(line.sr())], + tail.iter().cloned().map(p_tree2gen), + ])), + }); + impls.push((name, cnst_name)); + } else { + lines.push(ParsedLine { + sr: line.sr(), + comments, + kind: ParsedLineKind::Rec(p_v2gen(line.to_vec())), + }); + } + } + Ok(()) +} diff --git a/orchid-std/src/std/protocol/proto_parser.rs b/orchid-std/src/std/protocol/proto_parser.rs new file mode 100644 index 0000000..e350ef4 --- /dev/null +++ b/orchid-std/src/std/protocol/proto_parser.rs @@ -0,0 +1,77 @@ +use std::rc::Rc; + +use hashbrown::HashMap; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::parse::{Comment, Parsed, expect_end, try_pop_no_fluff}; +use orchid_base::sym; +use orchid_base::tree::Token; +use orchid_extension::context::i; +use orchid_extension::coroutine_exec::exec; +use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; + +use crate::std::protocol::parse_impls::parse_impls; +use crate::std::protocol::types::Tag; + +#[derive(Default)] +pub struct AsProtoParser; +impl Parser for AsProtoParser { + const LINE_HEAD: &'static str = "as_proto"; + async fn parse<'a>( + pcx: ParsCtx<'a>, + exported: bool, + cmts: Vec, + line: PSnippet<'a>, + ) -> OrcRes> { + let Parsed { output: body_tt, tail } = try_pop_no_fluff(&pcx, line).await?; + expect_end(&pcx, tail).await?; + if exported { + return Err(mk_errv( + i().i("Exported internal line").await, + "as_proto cannot be exported, the type shares the enclosing module's visibility", + [line.sr().pos()], + )); + } + let mut lines = Vec::new(); + let mut impls = Vec::new(); + parse_impls(&pcx, &mut lines, &mut impls, body_tt).await?; + let id = pcx.module(); + let proto_tag_name = i().i("__protocol_tag__").await; + let proto_tag_path = id.suffix([proto_tag_name.clone()], &i()).await; + lines.push(ParsedLine::cnst(&line.sr(), &cmts, true, proto_tag_name, async |_ccx| { + exec(async move |mut h| { + let mut new_impls = HashMap::new(); + for (k, v) in impls { + new_impls.insert(k.clone(), h.register(sym_ref(id.suffix([v], &i()).await)).await); + } + Tag { id, impls: Rc::new(new_impls) } + }) + .await + })); + lines.push(ParsedLine::cnst(&line.sr(), [], false, i().i("resolve").await, async move |_| { + call(sym_ref(sym!(std::protocol::resolve; i())), [sym_ref(proto_tag_path)]) + })); + Ok(lines) + } +} + +#[derive(Default)] +pub struct ProtoParser; +impl Parser for ProtoParser { + const LINE_HEAD: &'static str = "proto"; + async fn parse<'a>( + ctx: ParsCtx<'a>, + exported: bool, + cmts: Vec, + line: PSnippet<'a>, + ) -> OrcRes> { + let Parsed { output: name_tt, tail } = try_pop_no_fluff(&ctx, line).await?; + let Token::Name(name) = &name_tt.tok else { + return Err(mk_errv(i().i("missing name for type").await, "A type needs a name", [name_tt + .sr() + .pos()])); + }; + let lines = AsProtoParser::parse(ctx, false, cmts.clone(), tail).await?; + Ok(vec![ParsedLine::module(&line.sr(), &cmts, exported, name, true, lines)]) + } +} diff --git a/orchid-std/src/std/protocol/type_parser.rs b/orchid-std/src/std/protocol/type_parser.rs new file mode 100644 index 0000000..ce10ba3 --- /dev/null +++ b/orchid-std/src/std/protocol/type_parser.rs @@ -0,0 +1,82 @@ +use std::rc::Rc; + +use hashbrown::HashMap; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::parse::{Comment, Parsed, expect_end, try_pop_no_fluff}; +use orchid_base::sym; +use orchid_base::tree::Token; +use orchid_extension::context::i; +use orchid_extension::coroutine_exec::exec; +use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; + +use crate::std::protocol::parse_impls::parse_impls; +use crate::std::protocol::types::Tag; + +#[derive(Default)] +pub struct AsTypeParser; +impl Parser for AsTypeParser { + const LINE_HEAD: &'static str = "as_type"; + async fn parse<'a>( + ctx: ParsCtx<'a>, + exported: bool, + cmts: Vec, + line: PSnippet<'a>, + ) -> OrcRes> { + let Parsed { output: body_tt, tail } = try_pop_no_fluff(&ctx, line).await?; + expect_end(&ctx, tail).await?; + if exported { + return Err(mk_errv( + i().i("Exported internal line").await, + "as_type cannot be exported, the type shares the enclosing module's visibility", + [line.sr().pos()], + )); + } + let mut lines = Vec::new(); + let mut impls = Vec::new(); + parse_impls(&ctx, &mut lines, &mut impls, body_tt).await?; + let id = ctx.module(); + let type_tag_name = i().i("__type_tag__").await; + let type_tag_path = id.suffix([type_tag_name.clone()], &i()).await; + lines.push(ParsedLine::cnst(&line.sr(), &cmts, true, type_tag_name, async |_ccx| { + exec(async move |mut h| { + let mut new_impls = HashMap::new(); + for (k, v) in impls { + new_impls.insert(k.clone(), h.register(sym_ref(id.suffix([v], &i()).await)).await); + } + Tag { id, impls: Rc::new(new_impls) } + }) + .await + })); + let type_tag_path_1 = type_tag_path.clone(); + lines.push(ParsedLine::cnst(&line.sr(), [], false, i().i("wrap").await, async move |_ccx| { + call(sym_ref(sym!(std::protocol::wrap; i())), [sym_ref(type_tag_path_1)]) + })); + let type_tag_path_1 = type_tag_path.clone(); + lines.push(ParsedLine::cnst(&line.sr(), [], false, i().i("unwrap").await, async move |_ccx| { + call(sym_ref(sym!(std::protocol::unwrap; i())), [sym_ref(type_tag_path_1)]) + })); + Ok(lines) + } +} + +#[derive(Default)] +pub struct TypeParser; +impl Parser for TypeParser { + const LINE_HEAD: &'static str = "type"; + async fn parse<'a>( + ctx: ParsCtx<'a>, + exported: bool, + cmts: Vec, + line: PSnippet<'a>, + ) -> OrcRes> { + let Parsed { output: name_tt, tail } = try_pop_no_fluff(&ctx, line).await?; + let Token::Name(name) = &name_tt.tok else { + return Err(mk_errv(i().i("missing name for type").await, "A type needs a name", [name_tt + .sr() + .pos()])); + }; + let lines = AsTypeParser::parse(ctx, false, cmts.clone(), tail).await?; + Ok(vec![ParsedLine::module(&line.sr(), &cmts, exported, name, true, lines)]) + } +} diff --git a/orchid-std/src/std/protocol/types.rs b/orchid-std/src/std/protocol/types.rs new file mode 100644 index 0000000..bf20858 --- /dev/null +++ b/orchid-std/src/std/protocol/types.rs @@ -0,0 +1,141 @@ +use std::borrow::Cow; +use std::rc::Rc; + +use hashbrown::HashMap; +use never::Never; +use orchid_api_derive::Coding; +use orchid_api_traits::Request; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::format::fmt; +use orchid_base::name::Sym; +use orchid_extension::atom::{AtomMethod, Atomic, ForeignAtom, MethodSetBuilder, Supports, TAtom}; +use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; +use orchid_extension::context::i; +use orchid_extension::conv::ToExpr; +use orchid_extension::expr::Expr; +use orchid_extension::gen_expr::call; +use orchid_extension::tree::{GenMember, fun, prefix}; + +use crate::api; + +#[derive(Clone, Debug)] +pub struct Tag { + pub id: Sym, + pub impls: Rc>, +} +impl Atomic for Tag { + type Data = api::TStrv; + type Variant = OwnedVariant; + fn reg_reqs() -> MethodSetBuilder { MethodSetBuilder::new().handle::() } +} +impl OwnedAtom for Tag { + type Refs = Never; + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.id.to_api()) } +} +impl Supports for Tag { + async fn handle(&self, req: GetImplMethod) -> ::Response { + self.impls.get(&Sym::from_api(req.0, &i()).await).map(|expr| expr.handle().ticket()) + } +} +#[derive(Clone, Debug, Coding)] +pub struct GetImplMethod(pub api::TStrv); +impl Request for GetImplMethod { + type Response = Option; +} +impl AtomMethod for GetImplMethod { + const NAME: &str = "std::protocol::get_impl"; +} +#[derive(Clone, Debug, Coding)] +pub struct GetTagIdMethod; +impl Request for GetTagIdMethod { + type Response = api::TStrv; +} +impl AtomMethod for GetTagIdMethod { + const NAME: &str = "std::protocol::get_tag_id"; +} + +#[derive(Clone, Debug)] +pub struct Tagged { + pub tag: Tag, + pub value: Expr, +} +impl Atomic for Tagged { + type Data = api::TStrv; + type Variant = OwnedVariant; +} +impl OwnedAtom for Tagged { + type Refs = Never; + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.tag.id.to_api()) } +} +impl Supports for Tagged { + async fn handle(&self, req: GetImplMethod) -> ::Response { + self.tag.handle(req).await + } +} + +pub async fn get_impl(receiver: ForeignAtom, proto: ForeignAtom) -> OrcRes { + let Some(proto_id) = proto.request(GetTagIdMethod).await else { + return Err(mk_errv(i().i("Not a protocol").await, "Protocol does not have a tag ID", [ + proto.pos() + ])); + }; + let Some(impl_val_opt) = receiver.request(GetImplMethod(proto_id)).await else { + return Err(mk_errv( + i().i("Receiver not tagged").await, + "The receiver does not have a type tag", + [receiver.pos()], + )); + }; + if let Some(impl_val) = impl_val_opt { + return Ok(Expr::deserialize(impl_val).await); + } + let Some(type_id) = receiver.request(GetTagIdMethod).await else { + return Err(mk_errv( + i().i("Incorrect protocols implementation in extension").await, + "Atom provides an impl table but no tag ID", + [receiver.pos()], + )); + }; + let Some(impl_val_opt) = proto.request(GetImplMethod(type_id)).await else { + return Err(mk_errv( + i().i("Incorrect protocols implementation in extension").await, + "Proto table atom provides a tag ID but no impl table", + [receiver.pos()], + )); + }; + if let Some(impl_val) = impl_val_opt { + return Ok(Expr::deserialize(impl_val).await); + } + return Err(mk_errv( + i().i("Implementation not found").await, + "This protocol is not implemented for this receiver", + [receiver.pos(), proto.pos()], + )); +} + +pub fn gen_protocol_lib() -> Vec { + prefix("std::protocol", [ + fun(false, "resolve", async |tag: ForeignAtom, value: ForeignAtom| { + Ok(call(get_impl(value.clone(), tag).await?.to_gen().await, [value.to_gen().await])) + }), + fun(false, "wrap", async |tag: TAtom, value: Expr| Tagged { tag: own(&tag).await, value }), + fun(false, "unwrap", async |tag: TAtom, value: TAtom| { + let own_tag = own(&tag).await; + let own_val = own(&value).await; + if own_val.tag.id == own_tag.id { + Ok(own_val.value.to_gen().await) + } else { + Err(mk_errv( + i().i("Type mismatch").await, + format!( + "{} has type {}, expected {}", + fmt(&value, &i()).await, + own_val.tag.id, + own_tag.id + ), + [value.pos()], + )) + } + }), + ]) +} diff --git a/orchid-std/src/std/record/mod.rs b/orchid-std/src/std/record/mod.rs new file mode 100644 index 0000000..7678c76 --- /dev/null +++ b/orchid-std/src/std/record/mod.rs @@ -0,0 +1,2 @@ +pub mod record_atom; +pub mod record_lib; diff --git a/orchid-std/src/std/record/record_atom.rs b/orchid-std/src/std/record/record_atom.rs new file mode 100644 index 0000000..db09645 --- /dev/null +++ b/orchid-std/src/std/record/record_atom.rs @@ -0,0 +1,39 @@ +use std::borrow::Cow; +use std::pin::Pin; +use std::rc::Rc; + +use futures::AsyncWrite; +use futures::future::join_all; +use hashbrown::HashMap; +use orchid_api_traits::Encode; +use orchid_base::interner::Tok; +use orchid_extension::atom::Atomic; +use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; +use orchid_extension::context::i; +use orchid_extension::expr::Expr; + +use crate::api; + +#[derive(Clone)] +pub struct Record(pub Rc, Expr>>); +impl Atomic for Record { + type Data = (); + type Variant = OwnedVariant; +} +impl OwnedAtom for Record { + type Refs = Vec; + async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs { + let (keys, values) = + self.0.iter().map(|(k, v)| (k.to_api(), v.clone())).unzip::<_, _, Vec<_>, Vec<_>>(); + keys.encode(write).await; + values + } + async fn deserialize(mut dctx: impl DeserializeCtx, refs: Self::Refs) -> Self { + let keys = + join_all(dctx.decode::>().await.iter().map(|t| async { i().ex(*t).await })) + .await; + Record(Rc::new(keys.into_iter().zip(refs).collect())) + } + + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } +} diff --git a/orchid-std/src/std/record/record_lib.rs b/orchid-std/src/std/record/record_lib.rs new file mode 100644 index 0000000..5e7b128 --- /dev/null +++ b/orchid-std/src/std/record/record_lib.rs @@ -0,0 +1,30 @@ +use std::rc::Rc; + +use hashbrown::HashMap; +use orchid_extension::atom::TAtom; +use orchid_extension::atom_owned::own; +use orchid_extension::expr::Expr; +use orchid_extension::tree::{GenMember, cnst, fun, prefix}; + +use crate::std::option::OrcOpt; +use crate::std::record::record_atom::Record; +use crate::std::string::str_atom::IntStrAtom; + +pub fn gen_record_lib() -> Vec { + prefix("std::record", [ + cnst(true, "empty", Record(Rc::new(HashMap::new()))), + fun(true, "set", async |map: TAtom, key: IntStrAtom, val: Expr| { + let mut map = own(&map).await.0.as_ref().clone(); + map.insert(key.0.clone(), val); + Record(Rc::new(map)) + }), + fun(true, "get", async |map: TAtom, key: IntStrAtom| { + OrcOpt(own(&map).await.0.get(&key.0).cloned()) + }), + fun(true, "delete", async |map: TAtom, key: IntStrAtom| { + let mut map = own(&map).await.0.as_ref().clone(); + map.remove(&key.0); + Record(Rc::new(map)) + }), + ]) +} diff --git a/orchid-std/src/std/reflection/mod.rs b/orchid-std/src/std/reflection/mod.rs new file mode 100644 index 0000000..5c5976c --- /dev/null +++ b/orchid-std/src/std/reflection/mod.rs @@ -0,0 +1 @@ +pub mod sym_atom; diff --git a/orchid-std/src/std/reflection/sym_atom.rs b/orchid-std/src/std/reflection/sym_atom.rs new file mode 100644 index 0000000..ebc78f0 --- /dev/null +++ b/orchid-std/src/std/reflection/sym_atom.rs @@ -0,0 +1,67 @@ +use std::borrow::Cow; + +use orchid_api::TStrv; +use orchid_api_derive::{Coding, Hierarchy}; +use orchid_api_traits::Request; +use orchid_base::error::mk_errv; +use orchid_base::name::{NameLike, Sym}; +use orchid_extension::atom::{Atomic, Supports, TAtom}; +use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; +use orchid_extension::context::i; +use orchid_extension::expr::{Expr, ExprHandle}; +use orchid_extension::system::dep_req; +use orchid_extension::tree::{GenMember, fun, prefix}; + +use crate::std::std_system::StdReq; +use crate::std::string::str_atom::IntStrAtom; +use crate::std::string::to_string::ToStringMethod; +use crate::{HomoTpl, StdSystem, api}; + +#[derive(Clone, Coding)] +pub struct SymAtomData(pub api::TStrv); +#[derive(Clone)] +pub struct SymAtom(pub(crate) Sym); +impl Atomic for SymAtom { + type Data = SymAtomData; + type Variant = OwnedVariant; +} +impl OwnedAtom for SymAtom { + type Refs = (); + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(SymAtomData(self.0.tok().to_api())) } +} +impl Supports for SymAtom { + async fn handle(&self, _: ToStringMethod) -> ::Response { + self.0.to_string() + } +} + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(StdReq)] +pub struct CreateSymAtom(pub TStrv); +impl Request for CreateSymAtom { + type Response = api::ExprTicket; +} + +pub async fn sym_expr(sym: Sym) -> Expr { + Expr::from_handle(ExprHandle::deserialize( + dep_req::(CreateSymAtom(sym.to_api())).await, + )) +} + +pub async fn gen_sym_lib() -> Vec { + prefix("std::refl::sym", [ + fun(true, "from_str", async move |str: TAtom| { + match Sym::parse(&i().ex(*str).await, &i()).await { + Ok(sym) => Ok(SymAtom(sym)), + Err(_) => Err(mk_errv( + i().i("Cannot parse sym from empty string").await, + "Empty string passed to std::refl::sym::from_str", + [str.pos()], + )), + } + }), + fun(true, "to_tpl", async move |sym: TAtom| { + HomoTpl(own(&sym).await.0.segs().map(IntStrAtom).collect()) + }), + ]) +} diff --git a/orchid-std/src/std/std_system.rs b/orchid-std/src/std/std_system.rs index 580db38..6d692cf 100644 --- a/orchid-std/src/std/std_system.rs +++ b/orchid-std/src/std/std_system.rs @@ -1,10 +1,15 @@ -use never::Never; -use orchid_base::interner::Interner; +use std::rc::Rc; + +use futures::future::join_all; +use orchid_api_derive::{Coding, Hierarchy}; use orchid_base::name::Sym; use orchid_base::reqnot::Receipt; use orchid_base::sym; use orchid_extension::atom::{AtomDynfo, AtomicFeatures}; +use orchid_extension::context::i; +use orchid_extension::conv::ToExpr; use orchid_extension::entrypoint::ExtReq; +use orchid_extension::expr::Expr; use orchid_extension::lexer::LexerObj; use orchid_extension::parser::ParserObj; use orchid_extension::system::{System, SystemCard}; @@ -15,9 +20,25 @@ use super::number::num_lib::gen_num_lib; use super::string::str_atom::{IntStrAtom, StrAtom}; use super::string::str_lib::gen_str_lib; use crate::std::number::num_lexer::NumLexer; +use crate::std::option::{OptAtom, gen_option_lib}; +use crate::std::protocol::proto_parser::{AsProtoParser, ProtoParser}; +use crate::std::protocol::type_parser::{AsTypeParser, TypeParser}; +use crate::std::protocol::types::{Tag, Tagged, gen_protocol_lib}; +use crate::std::record::record_atom::Record; +use crate::std::record::record_lib::gen_record_lib; +use crate::std::reflection::sym_atom::{CreateSymAtom, SymAtom, gen_sym_lib}; use crate::std::string::str_lexer::StringLexer; +use crate::std::string::to_string::AsStrTag; +use crate::std::tuple::{CreateTuple, Tuple, TupleBuilder, gen_tuple_lib}; use crate::{Float, Int}; +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extendable] +pub enum StdReq { + CreateTuple(CreateTuple), + CreateSymAtom(CreateSymAtom), +} + #[derive(Default)] pub struct StdSystem; impl SystemCtor for StdSystem { @@ -29,15 +50,51 @@ impl SystemCtor for StdSystem { } impl SystemCard for StdSystem { type Ctor = Self; - type Req = Never; + type Req = StdReq; fn atoms() -> impl IntoIterator>> { - [Some(Int::dynfo()), Some(Float::dynfo()), Some(StrAtom::dynfo()), Some(IntStrAtom::dynfo())] + [ + Some(Int::dynfo()), + Some(Float::dynfo()), + Some(StrAtom::dynfo()), + Some(IntStrAtom::dynfo()), + Some(OptAtom::dynfo()), + Some(Record::dynfo()), + Some(Tuple::dynfo()), + Some(TupleBuilder::dynfo()), + Some(Tag::dynfo()), + Some(Tagged::dynfo()), + Some(AsStrTag::dynfo()), + ] } } impl System for StdSystem { - async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { match req {} } + async fn request(xreq: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { + match req { + StdReq::CreateTuple(ref req @ CreateTuple(ref items)) => { + let tpl = Tuple(Rc::new(join_all(items.iter().copied().map(Expr::deserialize)).await)); + let tk = tpl.to_expr().await.serialize().await; + xreq.handle(req, &tk).await + }, + StdReq::CreateSymAtom(ref req @ CreateSymAtom(sym_tok)) => { + let sym_atom = SymAtom(Sym::from_api(sym_tok, &i()).await); + xreq.handle(req, &sym_atom.to_expr().await.serialize().await).await + }, + } + } fn lexers() -> Vec { vec![&StringLexer, &NumLexer] } - fn parsers() -> Vec { vec![] } - fn env() -> Vec { merge_trivial([gen_num_lib(), gen_str_lib()]) } - async fn prelude(i: &Interner) -> Vec { vec![sym!(std; i).await] } + fn parsers() -> Vec { vec![&AsTypeParser, &TypeParser, &AsProtoParser, &ProtoParser] } + async fn env() -> Vec { + merge_trivial([ + gen_num_lib(), + gen_str_lib(), + gen_option_lib(), + gen_record_lib(), + gen_tuple_lib(), + gen_protocol_lib(), + gen_sym_lib().await, + ]) + } + async fn prelude() -> Vec { + vec![sym!(std; i()), sym!(std::tuple; i()), sym!(std::option; i())] + } } diff --git a/orchid-std/src/std/string/mod.rs b/orchid-std/src/std/string/mod.rs index 47ae88e..bc00ae0 100644 --- a/orchid-std/src/std/string/mod.rs +++ b/orchid-std/src/std/string/mod.rs @@ -1,3 +1,4 @@ pub mod str_atom; pub mod str_lexer; pub mod str_lib; +pub mod to_string; diff --git a/orchid-std/src/std/string/str_atom.rs b/orchid-std/src/std/string/str_atom.rs index b621173..c74c85c 100644 --- a/orchid-std/src/std/string/str_atom.rs +++ b/orchid-std/src/std/string/str_atom.rs @@ -11,9 +11,11 @@ use orchid_base::format::{FmtCtx, FmtUnit}; use orchid_base::interner::Tok; use orchid_extension::atom::{AtomMethod, Atomic, MethodSetBuilder, Supports, TAtom}; use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; +use orchid_extension::context::i; use orchid_extension::conv::TryFromExpr; use orchid_extension::expr::Expr; -use orchid_extension::system::SysCtx; + +use crate::std::string::to_string::ToStringMethod; #[derive(Copy, Clone, Debug, Coding)] pub struct StringGetVal; @@ -24,8 +26,11 @@ impl AtomMethod for StringGetVal { const NAME: &str = "std::string_get_val"; } impl Supports for StrAtom { - async fn handle(&self, _: SysCtx, _: StringGetVal) -> ::Response { - self.0.clone() + async fn handle(&self, _: StringGetVal) -> ::Response { self.0.clone() } +} +impl Supports for StrAtom { + async fn handle(&self, _: ToStringMethod) -> ::Response { + self.0.as_str().to_string() } } @@ -46,7 +51,7 @@ impl Deref for StrAtom { impl OwnedAtom for StrAtom { type Refs = (); async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn serialize(&self, _: SysCtx, sink: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs { + async fn serialize(&self, sink: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs { self.deref().encode(sink).await } async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { @@ -58,7 +63,7 @@ impl OwnedAtom for StrAtom { } #[derive(Debug, Clone)] -pub struct IntStrAtom(Tok); +pub struct IntStrAtom(pub(crate) Tok); impl Atomic for IntStrAtom { type Variant = OwnedVariant; type Data = orchid_api::TStr; @@ -72,19 +77,28 @@ impl OwnedAtom for IntStrAtom { async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { format!("{:?}i", *self.0).into() } - async fn serialize(&self, _: SysCtx, write: Pin<&mut (impl AsyncWrite + ?Sized)>) { + async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) { self.0.encode(write).await } - async fn deserialize(mut ctx: impl DeserializeCtx, _: ()) -> Self { - let s = ctx.decode::().await; - Self(ctx.sys().i().i(&s).await) + async fn deserialize(mut dctx: impl DeserializeCtx, _: ()) -> Self { + let s = dctx.decode::().await; + Self(i().i(&s).await) + } +} +impl TryFromExpr for IntStrAtom { + async fn try_from_expr(expr: Expr) -> OrcRes { + Ok(IntStrAtom(i().ex(TAtom::::try_from_expr(expr).await?.value).await)) + } +} +impl Supports for IntStrAtom { + async fn handle(&self, _: ToStringMethod) -> ::Response { + self.0.as_str().to_string() } } #[derive(Clone)] pub struct OrcString { kind: OrcStringKind, - ctx: SysCtx, } #[derive(Clone)] @@ -95,7 +109,7 @@ pub enum OrcStringKind { impl OrcString { pub async fn get_string(&self) -> Rc { match &self.kind { - OrcStringKind::Int(tok) => self.ctx.i().ex(**tok).await.rc(), + OrcStringKind::Int(tok) => i().ex(**tok).await.rc(), OrcStringKind::Val(atom) => atom.request(StringGetVal).await, } } @@ -104,12 +118,11 @@ impl OrcString { impl TryFromExpr for OrcString { async fn try_from_expr(expr: Expr) -> OrcRes { if let Ok(v) = TAtom::::try_from_expr(expr.clone()).await { - return Ok(OrcString { ctx: expr.ctx(), kind: OrcStringKind::Val(v) }); + return Ok(OrcString { kind: OrcStringKind::Val(v) }); } - let ctx = expr.ctx(); match TAtom::::try_from_expr(expr).await { - Ok(t) => Ok(OrcString { ctx: t.untyped.ctx().clone(), kind: OrcStringKind::Int(t) }), - Err(e) => Err(mk_errv(ctx.i().i("A string was expected").await, "", e.pos_iter())), + Ok(t) => Ok(OrcString { kind: OrcStringKind::Int(t) }), + Err(e) => Err(mk_errv(i().i("A string was expected").await, "", e.pos_iter())), } } } diff --git a/orchid-std/src/std/string/str_lexer.rs b/orchid-std/src/std/string/str_lexer.rs index 5acc40f..e96ad87 100644 --- a/orchid-std/src/std/string/str_lexer.rs +++ b/orchid-std/src/std/string/str_lexer.rs @@ -5,10 +5,12 @@ use orchid_base::location::SrcRange; use orchid_base::name::Sym; use orchid_base::parse::ParseCtx; use orchid_base::sym; -use orchid_base::tree::wrap_tokv; +use orchid_base::tree::{Paren, wrap_tokv}; +use orchid_extension::context::i; +use orchid_extension::gen_expr::sym_ref; use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; use orchid_extension::parser::p_tree2gen; -use orchid_extension::tree::{GenTokTree, ref_tok, x_tok}; +use orchid_extension::tree::{GenTok, GenTokTree, ref_tok, x_tok}; use super::str_atom::IntStrAtom; @@ -97,9 +99,9 @@ fn parse_string(str: &str) -> Result { pub struct StringLexer; impl Lexer for StringLexer { const CHAR_FILTER: &'static [std::ops::RangeInclusive] = &['"'..='"', '`'..='`']; - async fn lex<'a>(all: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { + async fn lex<'a>(all: &'a str, lctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { let Some(mut tail) = all.strip_prefix('"') else { - return Err(err_not_applicable(ctx.ctx.i()).await); + return Err(err_not_applicable().await); }; let mut ret = None; let mut cur = String::new(); @@ -121,19 +123,27 @@ impl Lexer for StringLexer { } let add_frag = |prev: Option, new: GenTokTree| async { let Some(prev) = prev else { return new }; - let concat_fn = ref_tok(sym!(std::string::concat; ctx.i()).await) + let concat_fn = ref_tok(sym!(std::string::concat; lctx.i())) .await .at(SrcRange::zw(prev.sr.path(), prev.sr.start())); wrap_tokv([concat_fn, prev, new]) }; loop { if let Some(rest) = tail.strip_prefix('"') { - return Ok((rest, add_frag(ret, str_to_gen(&mut cur, tail, &mut errors, ctx).await).await)); + return Ok(( + rest, + add_frag(ret, str_to_gen(&mut cur, tail, &mut errors, lctx).await).await, + )); } else if let Some(rest) = tail.strip_prefix('$') { - ret = Some(add_frag(ret, str_to_gen(&mut cur, tail, &mut errors, ctx).await).await); - let (new_tail, tree) = ctx.recurse(rest).await?; + ret = Some(add_frag(ret, str_to_gen(&mut cur, tail, &mut errors, lctx).await).await); + let (new_tail, tree) = lctx.recurse(rest).await?; tail = new_tail; - ret = Some(add_frag(ret, p_tree2gen(tree)).await); + // wrap the received token in a call to to_str + let to_str = sym_ref(sym!(std::string::to_str; i())); + let sr = tree.sr(); + let inj_to_str_tok = GenTok::NewExpr(to_str).at(sr.map_range(|_| sr.start()..sr.start())); + let to_str_call = GenTok::S(Paren::Round, vec![inj_to_str_tok, p_tree2gen(tree)]).at(sr); + ret = Some(add_frag(ret, to_str_call).await); } else if tail.starts_with('\\') { // parse_string will deal with it, we just have to skip the next char tail = &tail[2..]; @@ -143,11 +153,11 @@ impl Lexer for StringLexer { cur.push(c); tail = ch.as_str(); } else { - let range = ctx.pos(all)..ctx.pos(""); + let range = lctx.pos(all)..lctx.pos(""); return Err(mk_errv( - ctx.i().i("No string end").await, + lctx.i().i("No string end").await, "String never terminated with \"", - [SrcRange::new(range.clone(), ctx.src())], + [SrcRange::new(range.clone(), lctx.src())], )); } } diff --git a/orchid-std/src/std/string/str_lib.rs b/orchid-std/src/std/string/str_lib.rs index b2b90ec..26f21a2 100644 --- a/orchid-std/src/std/string/str_lib.rs +++ b/orchid-std/src/std/string/str_lib.rs @@ -1,15 +1,60 @@ use std::rc::Rc; -use orchid_extension::tree::{GenMember, comments, fun, prefix}; +use orchid_base::format::fmt; +use orchid_base::sym; +use orchid_extension::atom::ForeignAtom; +use orchid_extension::context::i; +use orchid_extension::conv::ToExpr; +use orchid_extension::coroutine_exec::exec; +use orchid_extension::expr::Expr; +use orchid_extension::gen_expr::{call, sym_ref}; +use orchid_extension::tree::{GenMember, cnst, comments, fun, prefix}; use super::str_atom::StrAtom; use crate::OrcString; +use crate::std::protocol::types::get_impl; +use crate::std::string::to_string::{AsStrTag, ToStringMethod}; pub fn gen_str_lib() -> Vec { - prefix("std::string", [comments( - ["Concatenate two strings"], - fun(true, "concat", |left: OrcString, right: OrcString| async move { - StrAtom::new(Rc::new(left.get_string().await.to_string() + &right.get_string().await)) - }), - )]) + prefix("std::string", [ + comments( + ["Concatenate two strings"], + fun(true, "concat", async |left: OrcString, right: OrcString| { + StrAtom::new(Rc::new(left.get_string().await.to_string() + &right.get_string().await)) + }), + ), + comments( + ["Converts a value to string. This function is used in interpolation. \ + It supports the std::string::to_string protocol in Orchid, \ + the std::string::to_string request in Rust, \ + and expression debug printing as a fallback (print_atom for Atomic implementors in Rust).\n\n\ + This function is infallible."], + fun(true, "to_str", async |input: Expr| { + exec(async move |mut h| { + if let Ok(atom) = h.exec::(input.clone()).await { + if let Some(str) = atom.request(ToStringMethod).await { + return StrAtom::new(Rc::new(str)).to_gen().await; + } + let proto_ref = sym_ref(sym!(std::string::to_string::__protocol_tag__; i())); + let proto = h.exec(proto_ref).await.expect("This protocol is defined in this system"); + if let Ok(cb) = get_impl(atom.clone(), proto).await { + return call(cb.to_gen().await, [atom.to_gen().await]).to_gen().await; + } + } + return StrAtom::new(Rc::new(fmt(&input, &i()).await)).to_gen().await; + }) + .await + }), + ), + prefix("to_string", [ + cnst(true, "__type_tag__", AsStrTag), + fun(true, "resolve", async |atom: ForeignAtom| { + exec(async |mut h| { + let proto = h.exec(sym_ref(sym!(std::string::to_string; i()))).await?; + Ok(call(get_impl(atom.clone(), proto).await?.to_gen().await, [atom.to_gen().await])) + }) + .await + }), + ]), + ]) } diff --git a/orchid-std/src/std/string/to_string.rs b/orchid-std/src/std/string/to_string.rs new file mode 100644 index 0000000..1ea8fff --- /dev/null +++ b/orchid-std/src/std/string/to_string.rs @@ -0,0 +1,36 @@ +use orchid_api_derive::Coding; +use orchid_api_traits::Request; +use orchid_base::name::Sym; +use orchid_extension::atom::{AtomMethod, Atomic, MethodSetBuilder, Supports}; +use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; +use orchid_extension::context::i; + +use crate::std::protocol::types::{GetImplMethod, GetTagIdMethod}; + +#[derive(Coding, Clone, Debug)] +pub struct AsStrTag; +impl Atomic for AsStrTag { + type Data = AsStrTag; + type Variant = ThinVariant; + fn reg_reqs() -> MethodSetBuilder { + MethodSetBuilder::new().handle::().handle::() + } +} +impl ThinAtom for AsStrTag {} +impl Supports for AsStrTag { + async fn handle(&self, _: GetTagIdMethod) -> ::Response { + Sym::parse("std::string::to_string", &i()).await.unwrap().to_api() + } +} +impl Supports for AsStrTag { + async fn handle(&self, _: GetImplMethod) -> ::Response { None } +} + +#[derive(Coding, Clone, Debug)] +pub struct ToStringMethod; +impl Request for ToStringMethod { + type Response = String; +} +impl AtomMethod for ToStringMethod { + const NAME: &str = "std::string::to_string"; +} diff --git a/orchid-std/src/std/tuple.rs b/orchid-std/src/std/tuple.rs new file mode 100644 index 0000000..5490b9c --- /dev/null +++ b/orchid-std/src/std/tuple.rs @@ -0,0 +1,211 @@ +use std::borrow::Cow; +use std::num::NonZero; +use std::pin::Pin; +use std::rc::Rc; + +use futures::AsyncWrite; +use futures::future::join_all; +use never::Never; +use orchid_api::ExprTicket; +use orchid_api_derive::{Coding, Hierarchy}; +use orchid_api_traits::Request; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; +use orchid_extension::atom::{Atomic, TAtom}; +use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant, own}; +use orchid_extension::context::i; +use orchid_extension::conv::{ToExpr, TryFromExpr}; +use orchid_extension::expr::{Expr, ExprHandle}; +use orchid_extension::gen_expr::GExpr; +use orchid_extension::system::dep_req; +use orchid_extension::tree::{GenMember, cnst, fun, prefix}; + +use crate::std::std_system::StdReq; +use crate::{Int, StdSystem, api}; + +#[derive(Clone)] +pub struct Tuple(pub(super) Rc>); + +impl Atomic for Tuple { + type Data = Vec; + type Variant = OwnedVariant; +} + +impl OwnedAtom for Tuple { + type Refs = Vec; + async fn val(&self) -> Cow<'_, Self::Data> { + Cow::Owned(self.0.iter().map(|x| x.handle().ticket()).collect()) + } + async fn serialize(&self, _: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs { + self.0.as_ref().clone() + } + async fn deserialize(_: impl DeserializeCtx, refs: Self::Refs) -> Self { Self(Rc::new(refs)) } + async fn print_atom<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + Variants::default() + .sequence(self.0.len(), "t[", ", ", "]", Some(true)) + .sequence(self.0.len(), "t[\n", ",\n", "\n]", Some(true)) + .units_own(join_all(self.0.iter().map(|x| x.print(c))).await) + } +} + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(StdReq)] +pub struct CreateTuple(pub Vec); +impl Request for CreateTuple { + type Response = api::ExprTicket; +} + +#[derive(Clone)] +pub struct TupleBuilder { + arity: NonZero, + items: Vec, +} +impl Atomic for TupleBuilder { + type Data = (); + type Variant = OwnedVariant; +} +impl OwnedAtom for TupleBuilder { + type Refs = Never; + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } + async fn call(mut self, arg: Expr) -> GExpr { + self.items.push(arg); + if self.arity.get() == self.items.len().try_into().expect("counting up from 0") { + Tuple(Rc::new(self.items)).to_gen().await + } else { + self.to_gen().await + } + } +} + +pub fn gen_tuple_lib() -> Vec { + prefix("std::tuple", [ + cnst(true, "empty", Tuple(Rc::new(Vec::new()))), + fun(true, "one", async |item: Expr| Tuple(Rc::new(vec![item]))), + fun(true, "new", async |arity: TAtom| { + if let Ok(arity) = u32::try_from(arity.value.0).and_then(|v| v.try_into()) { + TupleBuilder { arity, items: Vec::new() }.to_gen().await + } else { + Tuple(Rc::new(Vec::new())).to_gen().await + } + }), + fun(true, "get", async |tup: TAtom, idx: TAtom| { + if let Ok(idx) = usize::try_from(idx.0) + && let Some(val) = own(&tup).await.0.get(idx) + { + return Ok(val.clone()); + } + return Err(mk_errv( + i().i("Tuple index out of bounds").await, + format!("{} is out of bounds for Tuple{}", idx.0, tup.len()), + [idx.pos()], + )); + }), + fun(true, "set", async |tup: TAtom, idx: TAtom, val: Expr| { + if let Ok(idx) = usize::try_from(idx.0) { + let mut new_vec = own(&tup).await.0.to_vec(); + if let Some(slot) = new_vec.get_mut(idx) { + *slot = val; + return Ok(Tuple(Rc::new(new_vec))); + } + } + return Err(mk_errv( + i().i("Tuple index out of bounds").await, + format!("{} is out of bounds for Tuple{}", idx.0, tup.len()), + [idx.pos()], + )); + }), + fun(true, "len", async |tup: TAtom| { + Int(tup.len().try_into().expect("Tuple was created with an Int length")) + }), + fun(true, "cat", async |left: TAtom, right: TAtom| { + Tuple(Rc::new(own(&left).await.0.iter().chain(own(&right).await.0.iter()).cloned().collect())) + }), + ]) +} + +pub struct UntypedTuple(pub Vec); +impl TryFromExpr for UntypedTuple { + async fn try_from_expr(expr: Expr) -> OrcRes { + let tpl = TAtom::::try_from_expr(expr.clone()).await?; + let exprs = + join_all(tpl.iter().map(async |t| Expr::from_handle(ExprHandle::from_ticket(*t).await))) + .await; + Ok(UntypedTuple(exprs)) + } +} +impl ToExpr for UntypedTuple { + async fn to_gen(self) -> GExpr { + let exprs = join_all(self.0.into_iter().map(async |expr| expr.serialize().await)).await; + Expr::deserialize(dep_req::(CreateTuple(exprs)).await).await.to_gen().await + } +} + +pub struct Tpl(pub T); + +mod tpl_impls { + use itertools::Itertools; + use orchid_base::error::{OrcRes, mk_errv}; + use orchid_extension::context::i; + use orchid_extension::conv::{ToExpr, TryFromExpr}; + use orchid_extension::expr::Expr; + use orchid_extension::gen_expr::GExpr; + + use super::{Tpl, UntypedTuple}; + + macro_rules! tpl_derives { + ($len:literal $($t:ident)*) => { + pastey::paste! { + impl<$( $t: TryFromExpr, )*> TryFromExpr for Tpl<($( $t, )*)> { + async fn try_from_expr(expr: Expr) -> OrcRes { + let tpl = UntypedTuple::try_from_expr(expr.clone()).await?; + let Some([$( [< $t:lower >], )*]) = tpl.0.iter().cloned().collect_array() else { + return Err(mk_errv( + i().i("Tuple arity mismatch").await, + format!("Expected a {}-ary tuple, found {}-ary", $len, tpl.0.len()), + [expr.data().await.pos.clone()] + )); + }; + Ok(Tpl(( $( $t::try_from_expr([< $t:lower >]).await?, )* ))) + } + } + impl<$( $t: ToExpr, )*> ToExpr for Tpl<($( $t, )*)> { + async fn to_gen(self) -> GExpr { + let Self(($( [< $t:lower >], )*)) = self; + UntypedTuple(vec![ + $( [< $t:lower >].to_expr().await, )* + ]).to_gen().await + } + } + } + }; + } + tpl_derives!(0); + tpl_derives!(1 A); + tpl_derives!(2 A B); + tpl_derives!(3 A B C); + tpl_derives!(4 A B C D); + tpl_derives!(5 A B C D E); + tpl_derives!(6 A B C D E F); + tpl_derives!(7 A B C D E F G); + tpl_derives!(8 A B C D E F G H); + tpl_derives!(9 A B C D E F G H I); + tpl_derives!(10 A B C D E F G H I J); +} + +pub struct HomoTpl(pub Vec); + +impl TryFromExpr for HomoTpl { + async fn try_from_expr(expr: Expr) -> OrcRes { + let tpl = TAtom::::try_from_expr(expr.clone()).await?; + let mut res = Vec::new(); + for item in tpl.iter() { + res.push(T::try_from_expr(Expr::from_handle(ExprHandle::from_ticket(*item).await)).await?); + } + Ok(HomoTpl(res)) + } +} +impl ToExpr for HomoTpl { + async fn to_gen(self) -> GExpr { + UntypedTuple(join_all(self.0.into_iter().map(async |t| t.to_expr().await)).await).to_gen().await + } +} diff --git a/orchidlang/.gitignore b/orchidlang/.gitignore deleted file mode 100644 index 1de5659..0000000 --- a/orchidlang/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/orchidlang/Cargo.lock b/orchidlang/Cargo.lock deleted file mode 100644 index 6efbf84..0000000 --- a/orchidlang/Cargo.lock +++ /dev/null @@ -1,828 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "anstream" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" - -[[package]] -name = "anstyle-parse" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" -dependencies = [ - "anstyle", - "windows-sys", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bound" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6021ae095f16f54aaae093f4c723700430e71eab731d3b0a07fc8fe258fd5110" - -[[package]] -name = "bstr" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "4.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "clap_lex" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" - -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - -[[package]] -name = "const_format" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dyn-clone" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" - -[[package]] -name = "either" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "globset" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "intern-all" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c9bf7d7b0572f7b4398fddc93ac1a200a92d1ba319a27dac04649b2223c0f6" -dependencies = [ - "hashbrown", - "lazy_static", - "trait-set", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "memchr" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" - -[[package]] -name = "never" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" - -[[package]] -name = "num-traits" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "numtoa" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "orchidlang" -version = "0.3.0" -dependencies = [ - "bound", - "clap", - "const_format", - "dyn-clone", - "hashbrown", - "intern-all", - "itertools", - "never", - "once_cell", - "ordered-float", - "paste", - "rayon", - "rust-embed", - "substack", - "take_mut", - "termsize", - "trait-set", - "unicode-segmentation", -] - -[[package]] -name = "ordered-float" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" -dependencies = [ - "num-traits", -] - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "proc-macro2" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rayon" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_termios" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" - -[[package]] -name = "regex-automata" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "rust-embed" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.50", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" -dependencies = [ - "globset", - "sha2", - "walkdir", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "serde" -version = "1.0.197" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.197" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "strsim" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" - -[[package]] -name = "substack" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffccc3d80f0a489de67aa74ff31ab852abb973e1c6dacf3704889e00ca544e7f" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - -[[package]] -name = "termion" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" -dependencies = [ - "libc", - "numtoa", - "redox_syscall", - "redox_termios", -] - -[[package]] -name = "termsize" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e86d824a8e90f342ad3ef4bd51ef7119a9b681b0cc9f8ee7b2852f02ccd2517" -dependencies = [ - "atty", - "kernel32-sys", - "libc", - "termion", - "winapi 0.2.8", -] - -[[package]] -name = "trait-set" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "walkdir" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] diff --git a/orchidlang/Cargo.toml b/orchidlang/Cargo.toml deleted file mode 100644 index 03c0523..0000000 --- a/orchidlang/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "orchidlang" -version = "0.3.0" -edition = "2024" -license = "GPL-3.0" -repository = "https://github.com/lbfalvy/orchid" -description = """ -An embeddable pure functional scripting language -""" -authors = ["Lawrence Bethlenfalvy "] - -[lib] -path = "src/lib.rs" - -[[bin]] -name = "orcx" -path = "src/bin/orcx.rs" -doc = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -hashbrown = "0.14" -ordered-float = "4.2" -itertools = "0.12" -dyn-clone = "1.0" -trait-set = "0.3" -paste = "1.0" -rust-embed = { version = "8.2", features = ["include-exclude"] } -take_mut = "0.2" -unicode-segmentation = "1.11" -never = "0.1" -substack = "1.1" -intern-all = "0.4.1" -once_cell = "1.19" -const_format = "0.2" -bound = "0.5" -# Dependencies of orcx -clap = { version = "4.5", features = ["derive"] } -rayon = "1.8" -termsize = "0.1" diff --git a/orchidlang/src/bin/cli/mod.rs b/orchidlang/src/bin/cli/mod.rs deleted file mode 100644 index 4453d13..0000000 --- a/orchidlang/src/bin/cli/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod prompt; -pub use prompt::cmd_prompt; diff --git a/orchidlang/src/bin/cli/prompt.rs b/orchidlang/src/bin/cli/prompt.rs deleted file mode 100644 index 66837bc..0000000 --- a/orchidlang/src/bin/cli/prompt.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::io::{self, Error, Write}; - -pub fn cmd_prompt(prompt: &str) -> Result<(String, Vec), Error> { - print!("{}", prompt); - io::stdout().flush()?; - let mut cmdln = String::new(); - io::stdin().read_line(&mut cmdln)?; - let mut segments = cmdln.split(' '); - let cmd = if let Some(cmd) = segments.next() { cmd } else { "" }; - Ok((cmd.to_string(), segments.map(str::to_string).collect())) -} diff --git a/orchidlang/src/bin/features/macro_debug.rs b/orchidlang/src/bin/features/macro_debug.rs deleted file mode 100644 index 545d28f..0000000 --- a/orchidlang/src/bin/features/macro_debug.rs +++ /dev/null @@ -1,74 +0,0 @@ -use itertools::Itertools; -use orchidlang::error::Reporter; -use orchidlang::facade::macro_runner::MacroRunner; -use orchidlang::libs::std::exit_status::OrcExitStatus; -use orchidlang::location::{CodeGenInfo, CodeLocation}; -use orchidlang::name::Sym; -use orchidlang::pipeline::project::{ItemKind, ProjItem, ProjectTree}; -use orchidlang::sym; - -use crate::cli::cmd_prompt; - -/// A little utility to step through the reproject of a macro set -pub fn main(tree: ProjectTree, symbol: Sym) -> OrcExitStatus { - print!("Macro debugger starting on {symbol}"); - let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orcx::macro_runner))); - let expr_ent = match tree.0.walk1_ref(&[], &symbol[..], |_| true) { - Ok((e, _)) => e.clone(), - Err(e) => { - eprintln!("{}", e.at(&location.origin())); - return OrcExitStatus::Failure; - }, - }; - let mut expr = match expr_ent.item() { - Some(ProjItem { kind: ItemKind::Const(c) }) => c.clone(), - _ => { - eprintln!("macro-debug argument must be a constant"); - return OrcExitStatus::Failure; - }, - }; - let reporter = Reporter::new(); - let macro_runner = MacroRunner::new(&tree, None, &reporter); - reporter.assert_exit(); - println!("\nInitial state: {expr}"); - // print_for_debug(&code); - let mut steps = macro_runner.step(expr.clone()).enumerate(); - loop { - let (cmd, _) = cmd_prompt("\ncmd> ").unwrap(); - match cmd.trim() { - "" | "n" | "next" => match steps.next() { - None => print!("Halted"), - Some((idx, c)) => { - expr = c; - print!("Step {idx}: {expr}"); - }, - }, - "p" | "print" => { - let glossary = expr.value.collect_names(); - let gl_str = glossary.iter().join(", "); - print!("code: {expr}\nglossary: {gl_str}") - }, - "d" | "dump" => print!("Rules: {}", macro_runner.repo), - "q" | "quit" => return OrcExitStatus::Success, - "complete" => { - match steps.last() { - Some((idx, c)) => print!("Step {idx}: {c}"), - None => print!("Already halted"), - } - return OrcExitStatus::Success; - }, - "h" | "help" => print!( - "Available commands: - \t, n, next\t\ttake a step - \tp, print\t\tprint the current state - \td, dump\t\tprint the rule table - \tq, quit\t\texit - \th, help\t\tprint this text" - ), - _ => { - print!("unrecognized command \"{}\", try \"help\"", cmd); - continue; - }, - } - } -} diff --git a/orchidlang/src/bin/features/mod.rs b/orchidlang/src/bin/features/mod.rs deleted file mode 100644 index 166b976..0000000 --- a/orchidlang/src/bin/features/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod macro_debug; -pub mod print_project; -pub mod shared; -pub mod tests; diff --git a/orchidlang/src/bin/features/print_project.rs b/orchidlang/src/bin/features/print_project.rs deleted file mode 100644 index f554df1..0000000 --- a/orchidlang/src/bin/features/print_project.rs +++ /dev/null @@ -1,55 +0,0 @@ -use itertools::Itertools; -use orchidlang::pipeline::project::{ItemKind, ProjItem, ProjectMod}; -use orchidlang::tree::{ModEntry, ModMember}; - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct ProjPrintOpts { - pub width: u16, - pub hide_locations: bool, -} - -fn indent(amount: u16) -> String { " ".repeat(amount.into()) } - -pub fn print_proj_mod(module: &ProjectMod, lvl: u16, opts: ProjPrintOpts) -> String { - let mut acc = String::new(); - let tab = indent(lvl); - for (key, ModEntry { member, x }) in &module.entries { - let mut line_acc = String::new(); - for c in &x.comments { - line_acc += &format!("{tab}, --[|{}|]--\n", c); - } - if x.exported { - line_acc += &format!("{tab}export "); - } else { - line_acc += &tab - } - match member { - ModMember::Sub(module) => { - line_acc += &format!("module {key} {{\n"); - line_acc += &print_proj_mod(module, lvl + 1, opts); - line_acc += &format!("{tab}}}"); - }, - ModMember::Item(ProjItem { kind: ItemKind::None }) => { - line_acc += &format!("keyword {key}"); - }, - ModMember::Item(ProjItem { kind: ItemKind::Alias(tgt) }) => { - line_acc += &format!("alias {key} => {tgt}"); - }, - ModMember::Item(ProjItem { kind: ItemKind::Const(val) }) => { - line_acc += &format!("const {key} := {val}"); - }, - } - if !x.locations.is_empty() && !opts.hide_locations { - let locs = x.locations.iter().map(|l| l.to_string()).join(", "); - let line_len = line_acc.split('\n').last().unwrap().len(); - match usize::from(opts.width).checked_sub(locs.len() + line_len + 4) { - Some(padding) => line_acc += &" ".repeat(padding), - None => line_acc += &format!("\n{tab} @ "), - } - line_acc += &locs; - } - line_acc += "\n"; - acc += &line_acc - } - acc -} diff --git a/orchidlang/src/bin/features/shared.rs b/orchidlang/src/bin/features/shared.rs deleted file mode 100644 index 82f505e..0000000 --- a/orchidlang/src/bin/features/shared.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::io::BufReader; -use std::thread; - -use orchidlang::facade::loader::Loader; -use orchidlang::libs::asynch::system::AsynchSystem; -use orchidlang::libs::directfs::DirectFS; -use orchidlang::libs::io::{IOService, Sink, Source, Stream}; -use orchidlang::libs::scheduler::system::SeqScheduler; -use orchidlang::libs::std::std_system::StdConfig; - -pub fn stdin_source() -> Source { BufReader::new(Box::new(std::io::stdin())) } -pub fn stdout_sink() -> Sink { Box::new(std::io::stdout()) } -pub fn stderr_sink() -> Sink { Box::new(std::io::stderr()) } - -pub fn with_std_env(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T { - with_env(stdin_source(), stdout_sink(), stderr_sink(), cb) -} - -pub fn with_env( - stdin: Source, - stdout: Sink, - stderr: Sink, - cb: impl for<'a> FnOnce(Loader<'a>) -> T, -) -> T { - let mut asynch = AsynchSystem::new(); - let scheduler = SeqScheduler::new(&mut asynch); - let std_streams = [ - ("stdin", Stream::Source(stdin)), - ("stdout", Stream::Sink(stdout)), - ("stderr", Stream::Sink(stderr)), - ]; - let env = Loader::new() - .add_system(StdConfig { impure: true }) - .add_system(asynch) - .add_system(scheduler.clone()) - .add_system(IOService::new(scheduler.clone(), std_streams)) - .add_system(DirectFS::new(scheduler)); - cb(env) -} - -pub fn worker_cnt() -> usize { thread::available_parallelism().map(usize::from).unwrap_or(1) } - -macro_rules! unwrap_exit { - ($param:expr) => { - match $param { - Ok(v) => v, - Err(e) => { - eprintln!("{e}"); - return ExitCode::FAILURE; - }, - } - }; - ($param:expr; $error:expr) => { - match $param { - Ok(v) => v, - Err(e) => { - eprintln!("{e}"); - return $error; - }, - } - }; -} - -pub(crate) use unwrap_exit; diff --git a/orchidlang/src/bin/features/tests.rs b/orchidlang/src/bin/features/tests.rs deleted file mode 100644 index e25c7aa..0000000 --- a/orchidlang/src/bin/features/tests.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::fmt; -use std::io::BufReader; -use std::path::Path; - -use hashbrown::HashMap; -use itertools::Itertools; -use orchidlang::error::{ProjectError, ProjectResult, Reporter}; -use orchidlang::facade::loader::Loader; -use orchidlang::facade::macro_runner::MacroRunner; -use orchidlang::facade::merge_trees::NortConst; -use orchidlang::facade::process::Process; -use orchidlang::foreign::error::{RTError, RTErrorObj, RTResult}; -use orchidlang::foreign::inert::Inert; -use orchidlang::interpreter::error::RunError; -use orchidlang::interpreter::nort; -use orchidlang::libs::io::{Sink, Source}; -use orchidlang::libs::std::exit_status::OrcExitStatus; -use orchidlang::name::Sym; -use rayon::iter::ParallelIterator; -use rayon::slice::ParallelSlice; - -use super::shared::{with_env, worker_cnt}; - -pub fn mock_source() -> Source { BufReader::new(Box::new(&[][..])) } -pub fn mock_sink() -> Sink { Box::>::default() } -pub fn with_mock_env(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T { - with_env(mock_source(), mock_sink(), mock_sink(), cb) -} - -#[derive(Clone)] -pub struct TestDidNotHalt(Sym); -impl RTError for TestDidNotHalt {} -impl fmt::Display for TestDidNotHalt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Test {} did not halt", self.0) - } -} - -#[derive(Clone)] -pub struct TestDidNotSucceed(Sym, nort::Expr); -impl RTError for TestDidNotSucceed {} -impl fmt::Display for TestDidNotSucceed { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Test {} settled on {}", self.0, self.1) - } -} - -pub fn run_test(proc: &mut Process, name: Sym, data: NortConst) -> RTResult<()> { - let res = proc.run(data.value, Some(10_000)).map_err(|e| match e { - RunError::Extern(e) => e, - RunError::Interrupted(_) => TestDidNotHalt(name.clone()).pack(), - })?; - match res.clone().downcast()? { - Inert(OrcExitStatus::Success) => Ok(()), - _ => Err(TestDidNotSucceed(name, res).pack()), - } -} -pub fn run_tests( - dir: &Path, - macro_limit: usize, - threads: Option, - tests: &[(Sym, NortConst)], -) -> ProjectResult<()> { - with_mock_env(|env| { - let reporter = Reporter::new(); - env.proc_dir(dir.to_owned(), true, Some(macro_limit), &reporter); - reporter.bind() - })?; - let threads = threads.unwrap_or_else(worker_cnt); - rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap(); - let batch_size = tests.len().div_ceil(threads); - let errors = (tests.par_chunks(batch_size)) - .map(|tests| { - with_mock_env(|env| { - let reporter = Reporter::new(); - let mut proc = env.proc_dir(dir.to_owned(), true, Some(macro_limit), &reporter); - reporter.assert(); // checked above - (tests.iter()) - .filter_map(|(test, constant)| { - Some((test.clone(), run_test(&mut proc, test.clone(), constant.clone()).err()?)) - }) - .collect_vec() - }) - }) - .collect::>() - .into_iter() - .flatten() - .collect::>(); - if errors.is_empty() { Ok(()) } else { Err(TestsFailed(errors).pack()) } -} - -pub struct TestsFailed(HashMap); -impl ProjectError for TestsFailed { - const DESCRIPTION: &'static str = "Various tests failed"; - fn message(&self) -> String { - ([format!("{} tests failed. Errors:", self.0.len())].into_iter()) - .chain(self.0.iter().map(|(k, e)| format!("In {k}, {e}"))) - .join("\n") - } -} - -pub fn get_tree_tests(dir: &Path, reporter: &Reporter) -> ProjectResult> { - with_mock_env(|env| { - let tree = env.load_dir(dir.to_owned(), reporter); - let tree = MacroRunner::new(&tree, Some(10_000), reporter).run_macros(tree, reporter); - (tree.all_consts().into_iter()) - .filter(|(_, rep)| rep.comments.iter().any(|s| s.trim() == "test")) - .map(|(k, v)| Ok((k.clone(), NortConst::convert_from(v, reporter)))) - .collect::>>() - }) -} diff --git a/orchidlang/src/bin/orcx.rs b/orchidlang/src/bin/orcx.rs deleted file mode 100644 index fabda21..0000000 --- a/orchidlang/src/bin/orcx.rs +++ /dev/null @@ -1,283 +0,0 @@ -mod cli; -mod features; - -use std::fs::File; -use std::io::{stdin, stdout, Write}; -use std::path::PathBuf; -use std::process::ExitCode; - -use clap::{Parser, Subcommand}; -use hashbrown::HashSet; -use itertools::Itertools; -use never::Never; -use orchidlang::error::Reporter; -use orchidlang::facade::macro_runner::MacroRunner; -use orchidlang::facade::merge_trees::{merge_trees, NortConst}; -use orchidlang::facade::process::Process; -use orchidlang::foreign::inert::Inert; -use orchidlang::gen::tpl; -use orchidlang::gen::traits::Gen; -use orchidlang::interpreter::gen_nort::nort_gen; -use orchidlang::interpreter::nort::{self}; -use orchidlang::libs::std::exit_status::OrcExitStatus; -use orchidlang::libs::std::string::OrcString; -use orchidlang::location::{CodeGenInfo, CodeLocation, SourceRange}; -use orchidlang::name::Sym; -use orchidlang::parse::context::FlatLocContext; -use orchidlang::parse::lexer::{lex, Lexeme}; -use orchidlang::sym; -use orchidlang::tree::{ModMemberRef, TreeTransforms}; -use orchidlang::virt_fs::{decl_file, DeclTree}; - -use crate::features::macro_debug; -use crate::features::print_project::{print_proj_mod, ProjPrintOpts}; -use crate::features::shared::{stderr_sink, stdout_sink, unwrap_exit, with_env, with_std_env}; -use crate::features::tests::{get_tree_tests, mock_source, run_test, run_tests, with_mock_env}; - -#[derive(Subcommand, Debug)] -enum Command { - /// Run unit tests, any constant annotated --[[ test ]]-- - Test { - /// Specify an exact test to run - #[arg(long)] - only: Option, - #[arg(long, short)] - threads: Option, - #[arg(long)] - system: Option, - }, - #[command(arg_required_else_help = true)] - MacroDebug { - #[arg(long, short)] - symbol: String, - }, - ListMacros, - ProjectTree { - #[arg(long, default_value_t = false)] - hide_locations: bool, - #[arg(long)] - width: Option, - }, - Repl, -} -/// Orchid interpreter -#[derive(Parser, Debug)] -#[command(name = "Orchid Executor")] -#[command(author = "Lawrence Bethlenfalvy ")] -#[command(long_about = Some("Execute Orchid projects from the file system"))] -struct Args { - /// Folder containing main.orc or the manually specified entry module - #[arg(short, long, default_value = ".")] - pub dir: String, - /// Alternative entrypoint for the interpreter - #[arg(short, long)] - pub main: Option, - /// Maximum number of steps taken by the macro executor - #[arg(long, default_value_t = 10_000)] - pub macro_limit: usize, - - #[command(subcommand)] - pub command: Option, -} -impl Args { - /// Validate the project directory and the - pub fn chk_dir_main(&self) -> Result<(), String> { - let dir_path = PathBuf::from(&self.dir); - if !dir_path.is_dir() { - return Err(format!("{} is not a directory", dir_path.display())); - } - let segs = match &self.main { - Some(s) => s.split("::").collect::>(), - None => match File::open("./main.orc") { - Ok(_) => return Ok(()), - Err(e) => return Err(format!("Cannot open './main.orc'\n{e}")), - }, - }; - if segs.len() < 2 { - return Err("Entry point too short".to_string()); - }; - let (_, pathsegs) = segs.split_last().unwrap(); - let mut possible_files = pathsegs.iter().scan(dir_path, |path, seg| { - path.push(seg); - Some(path.with_extension("orc")) - }); - if possible_files.all(|p| File::open(p).is_err()) { - let out_path = pathsegs.join("::"); - let pbuf = PathBuf::from(&self.dir); - return Err(format!("{out_path} not found in {}", pbuf.display())); - } - Ok(()) - } - - pub fn chk_proj(&self) -> Result<(), String> { self.chk_dir_main() } -} - -pub fn main() -> ExitCode { - let args = Args::parse(); - unwrap_exit!(args.chk_proj()); - let dir = PathBuf::from(args.dir); - let main_s = args.main.as_ref().map_or("tree::main::main", |s| s); - let main = Sym::parse(main_s).expect("--main cannot be empty"); - let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orcx::entrypoint))); - let reporter = Reporter::new(); - - // subcommands - #[allow(clippy::blocks_in_conditions)] - match args.command { - Some(Command::ListMacros) => with_mock_env(|env| { - let tree = env.load_main(dir, [main], &reporter); - let mr = MacroRunner::new(&tree, None, &reporter); - println!("Parsed rules: {}", mr.repo); - ExitCode::SUCCESS - }), - Some(Command::ProjectTree { hide_locations, width }) => { - let tree = with_mock_env(|env| env.load_main(dir, [main], &reporter)); - let w = width.or_else(|| termsize::get().map(|s| s.cols)).unwrap_or(74); - let print_opts = ProjPrintOpts { width: w, hide_locations }; - println!("Project tree: {}", print_proj_mod(&tree.0, 0, print_opts)); - ExitCode::SUCCESS - }, - Some(Command::MacroDebug { symbol }) => with_mock_env(|env| { - let tree = env.load_main(dir, [main], &reporter); - let symbol = Sym::parse(&symbol).expect("macro-debug needs an argument"); - macro_debug::main(tree, symbol).code() - }), - Some(Command::Test { only: Some(_), threads: Some(_), .. }) => { - eprintln!( - "Each test case runs in a single thread. - --only and --threads cannot both be specified" - ); - ExitCode::FAILURE - }, - Some(Command::Test { only: Some(_), system: Some(_), .. }) => { - eprintln!( - "Conflicting test filters applied. --only runs a single test by - symbol name, while --system runs all tests in a system" - ); - ExitCode::FAILURE - }, - Some(Command::Test { only: None, threads, system: None }) => { - let tree_tests = reporter.unwrap_exit(get_tree_tests(&dir, &reporter)); - unwrap_exit!(run_tests(&dir, args.macro_limit, threads, &tree_tests)); - ExitCode::SUCCESS - }, - Some(Command::Test { only: Some(symbol), threads: None, system: None }) => { - let symbol = Sym::parse(&symbol).expect("Test needs an argument"); - with_env(mock_source(), stdout_sink(), stderr_sink(), |env| { - // iife in lieu of try blocks - let tree = env.load_main(dir.clone(), [symbol.clone()], &reporter); - let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter); - let consts = mr.run_macros(tree, &reporter).all_consts(); - let test = consts.get(&symbol).expect("Test not found"); - let nc = NortConst::convert_from(test.clone(), &reporter); - let mut proc = Process::new(merge_trees(consts, env.systems(), &reporter), env.handlers()); - unwrap_exit!(run_test(&mut proc, symbol.clone(), nc.clone())); - ExitCode::SUCCESS - }) - }, - Some(Command::Test { only: None, threads, system: Some(system) }) => { - let subtrees = unwrap_exit!(with_mock_env(|env| { - match env.systems().find(|s| s.name == system) { - None => Err(format!("System {system} not found")), - Some(sys) => { - let mut paths = HashSet::new(); - sys.code.search_all((), |path, node, ()| { - if matches!(node, ModMemberRef::Item(_)) { - let name = Sym::new(path.unreverse()).expect("Empty path means global file"); - paths.insert(name); - } - }); - Ok(paths) - }, - } - })); - let in_subtrees = |sym: Sym| subtrees.iter().any(|sub| sym[..].starts_with(&sub[..])); - let tests = with_mock_env(|env| { - let tree = env.load_main(dir.clone(), [main.clone()], &reporter); - let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter); - let src_consts = mr.run_macros(tree, &reporter).all_consts(); - let consts = merge_trees(src_consts, env.systems(), &reporter); - (consts.into_iter()) - .filter(|(k, v)| in_subtrees(k.clone()) && v.comments.iter().any(|c| c.trim() == "test")) - .collect_vec() - }); - eprintln!("Running {} tests", tests.len()); - unwrap_exit!(run_tests(&dir, args.macro_limit, threads, &tests)); - eprintln!("All tests pass"); - ExitCode::SUCCESS - }, - None => with_std_env(|env| { - let proc = env.proc_main(dir, [main.clone()], true, Some(args.macro_limit), &reporter); - reporter.assert_exit(); - let ret = unwrap_exit!(proc.run(nort::Clause::Constant(main).into_expr(location), None)); - drop(proc); - match ret.clone().downcast() { - Ok(Inert(OrcExitStatus::Success)) => ExitCode::SUCCESS, - Ok(Inert(OrcExitStatus::Failure)) => ExitCode::FAILURE, - Err(_) => { - println!("{}", ret.clause); - ExitCode::SUCCESS - }, - } - }), - Some(Command::Repl) => with_std_env(|env| { - let sctx = env.project_ctx(&reporter); - loop { - let reporter = Reporter::new(); - print!("orc"); - let mut src = String::new(); - let mut paren_tally = 0; - loop { - print!("> "); - stdout().flush().unwrap(); - let mut buf = String::new(); - stdin().read_line(&mut buf).unwrap(); - src += &buf; - let range = SourceRange::mock(); - let spctx = sctx.parsing(range.code()); - let pctx = FlatLocContext::new(&spctx, &range); - let res = - lex(Vec::new(), &buf, &pctx, |_| Ok::<_, Never>(false)).unwrap_or_else(|e| match e {}); - res.tokens.iter().for_each(|e| match &e.lexeme { - Lexeme::LP(_) => paren_tally += 1, - Lexeme::RP(_) => paren_tally -= 1, - _ => (), - }); - if 0 == paren_tally { - break; - } - } - let tree = env.load_project_main( - [sym!(tree::main::__repl_input__)], - DeclTree::ns("tree::main", [decl_file(&format!("const __repl_input__ := {src}"))]), - &reporter, - ); - let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter); - let proj_consts = mr.run_macros(tree, &reporter).all_consts(); - let consts = merge_trees(proj_consts, env.systems(), &reporter); - let ctx = nort_gen(location.clone()); - let to_string_tpl = tpl::A(tpl::C("std::string::convert"), tpl::Slot); - if let Err(err) = reporter.bind() { - eprintln!("{err}"); - continue; - } - let proc = Process::new(consts, env.handlers()); - let prompt = tpl::C("tree::main::__repl_input__").template(ctx.clone(), []); - let out = match proc.run(prompt, Some(1000)) { - Ok(out) => out, - Err(e) => { - eprintln!("{e}"); - continue; - }, - }; - if let Ok(out) = proc.run(to_string_tpl.template(ctx, [out.clone()]), Some(1000)) { - if let Ok(s) = out.clone().downcast::>() { - println!("{}", s.0.as_str()); - continue; - } - } - println!("{out}") - } - }), - } -} diff --git a/orchidlang/src/facade/loader.rs b/orchidlang/src/facade/loader.rs deleted file mode 100644 index ba9aa45..0000000 --- a/orchidlang/src/facade/loader.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! The main structure of the façade, collects systems and exposes various -//! operations over the whole set. - -use std::borrow::Borrow; -use std::path::PathBuf; - -use intern_all::i; - -use super::macro_runner::MacroRunner; -use super::merge_trees::merge_trees; -use super::process::Process; -use super::system::{IntoSystem, System}; -use super::unbound_ref::validate_refs; -use crate::error::Reporter; -use crate::gen::tree::ConstTree; -use crate::interpreter::context::RunEnv; -use crate::interpreter::handler::HandlerTable; -use crate::location::{CodeGenInfo, CodeOrigin}; -use crate::name::{PathSlice, Sym, VPath}; -use crate::pipeline::load_project::{load_project, ProjectContext}; -use crate::pipeline::project::ProjectTree; -use crate::sym; -use crate::utils::combine::Combine; -use crate::utils::sequence::Sequence; -use crate::virt_fs::{DeclTree, DirNode, Loaded, VirtFS}; - -/// A compiled environment ready to load user code. It stores the list of -/// systems and combines with usercode to produce a [Process] -pub struct Loader<'a> { - systems: Vec>, -} -impl<'a> Loader<'a> { - /// Initialize a new environment - #[must_use] - pub fn new() -> Self { Self { systems: Vec::new() } } - - /// Retrieve the list of systems - pub fn systems(&self) -> impl Iterator> { self.systems.iter() } - - /// Register a new system in the environment - #[must_use] - pub fn add_system<'b: 'a>(mut self, is: impl IntoSystem<'b> + 'b) -> Self { - self.systems.push(Box::new(is).into_system()); - self - } - - /// Extract the systems from the environment - pub fn into_systems(self) -> Vec> { self.systems } - - /// Initialize an environment with a prepared list of systems - pub fn from_systems(sys: impl IntoIterator>) -> Self { - Self { systems: sys.into_iter().collect() } - } - - /// Combine the `constants` fields of all systems - pub fn constants(&self) -> ConstTree { - (self.systems()) - .try_fold(ConstTree::tree::<&str>([]), |acc, sys| acc.combine(sys.constants.clone())) - .expect("Conflicting const trees") - } - - /// Extract the command handlers from the systems, consuming the loader in the - /// process. This has to consume the systems because handler tables aren't - /// Copy. It also establishes the practice that environments live on the - /// stack. - pub fn handlers(&self) -> HandlerTable<'_> { - (self.systems.iter()).fold(HandlerTable::new(), |t, sys| t.link(&sys.handlers)) - } - - /// Compile the environment from the set of systems and return it directly. - /// See [#load_dir] - pub fn project_ctx<'b>(&self, reporter: &'b Reporter) -> ProjectContext<'_, 'b> { - ProjectContext { - lexer_plugins: Sequence::new(|| { - self.systems().flat_map(|sys| &sys.lexer_plugins).map(|b| &**b) - }), - line_parsers: Sequence::new(|| { - self.systems().flat_map(|sys| &sys.line_parsers).map(|b| &**b) - }), - preludes: Sequence::new(|| self.systems().flat_map(|sys| &sys.prelude)), - reporter, - } - } - - /// Combine source code from all systems with the specified directory into a - /// common [VirtFS] - pub fn make_dir_fs(&self, dir: PathBuf) -> DeclTree { - let dir_node = DirNode::new(dir, ".orc").rc(); - DeclTree::tree([("tree", DeclTree::leaf(dir_node))]) - } - - /// All system trees merged into one - pub fn system_fs(&self) -> DeclTree { - (self.systems().try_fold(DeclTree::empty(), |acc, sub| acc.combine(sub.code.clone()))) - .expect("Conflicting system trees") - } - - /// A wrapper around [load_project] that only takes the arguments that aren't - /// fully specified by systems - pub fn load_project_main( - &self, - entrypoints: impl IntoIterator, - root: DeclTree, - reporter: &Reporter, - ) -> ProjectTree { - let tgt_loc = CodeOrigin::Gen(CodeGenInfo::no_details(sym!(facade::entrypoint))); - let constants = self.constants().unwrap_mod(); - let targets = entrypoints.into_iter().map(|s| (s, tgt_loc.clone())); - let root = self.system_fs().combine(root).expect("System trees conflict with root"); - load_project(&self.project_ctx(reporter), targets, &constants, &root) - } - - /// A wrapper around [load_project] that only takes the arguments that aren't - /// fully specified by systems - pub fn load_project(&self, root: DeclTree, reporter: &Reporter) -> ProjectTree { - let mut orc_files: Vec = Vec::new(); - find_all_orc_files([].borrow(), &mut orc_files, &root); - let entrypoints = (orc_files.into_iter()).map(|p| p.name_with_suffix(i!(str: "tree")).to_sym()); - let tgt_loc = CodeOrigin::Gen(CodeGenInfo::no_details(sym!(facade::entrypoint))); - let constants = self.constants().unwrap_mod(); - let targets = entrypoints.into_iter().map(|s| (s, tgt_loc.clone())); - let root = self.system_fs().combine(root).expect("System trees conflict with root"); - load_project(&self.project_ctx(reporter), targets, &constants, &root) - } - - /// Load a directory from the local file system as an Orchid project. - /// File loading proceeds along import statements and ignores all files - /// not reachable from the specified file. - pub fn load_main( - &self, - dir: PathBuf, - targets: impl IntoIterator, - reporter: &Reporter, - ) -> ProjectTree { - self.load_project_main(targets, self.make_dir_fs(dir), reporter) - } - - /// Load every orchid file in a directory - pub fn load_dir(&self, dir: PathBuf, reporter: &Reporter) -> ProjectTree { - self.load_project(self.make_dir_fs(dir), reporter) - } - - /// Build a process by calling other utilities in [crate::facade]. A sort of - /// facade over the facade. If you need a custom file system, consider - /// combining this with [Loader::load_project]. For usage with - /// [Loader::load_main] and [Loader::load_dir] we offer the shorthands - /// [Loader::proc_main] and [Loader::proc_dir]. - pub fn proc( - &'a self, - tree: ProjectTree, - check_refs: bool, - macro_limit: Option, - reporter: &Reporter, - ) -> Process<'a> { - let mr = MacroRunner::new(&tree, macro_limit, reporter); - let pm_tree = mr.run_macros(tree, reporter); - let consts = merge_trees(pm_tree.all_consts(), self.systems(), reporter); - if check_refs { - validate_refs(consts.keys().cloned().collect(), reporter, &mut |sym, location| { - (consts.get(&sym).map(|nc| nc.value.clone())) - .ok_or_else(|| RunEnv::sym_not_found(sym, location)) - }); - } - Process::new(consts, self.handlers()) - } - - /// Load a project and process everything - pub fn proc_dir( - &'a self, - dir: PathBuf, - check_refs: bool, - macro_limit: Option, - reporter: &Reporter, - ) -> Process<'a> { - self.proc(self.load_dir(dir.to_owned(), reporter), check_refs, macro_limit, reporter) - } - - /// Load a project and process everything to load specific symbols - pub fn proc_main( - &'a self, - dir: PathBuf, - targets: impl IntoIterator, - check_refs: bool, - macro_limit: Option, - reporter: &Reporter, - ) -> Process<'a> { - self.proc(self.load_main(dir.to_owned(), targets, reporter), check_refs, macro_limit, reporter) - } -} - -impl<'a> Default for Loader<'a> { - fn default() -> Self { Self::new() } -} - -fn find_all_orc_files(path: &PathSlice, paths: &mut Vec, vfs: &impl VirtFS) { - match vfs.read(path) { - Err(_) => (), - Ok(Loaded::Code(_)) => paths.push(path.to_vpath()), - Ok(Loaded::Collection(items)) => items - .iter() - .for_each(|suffix| find_all_orc_files(&path.to_vpath().suffix([suffix.clone()]), paths, vfs)), - } -} diff --git a/orchidlang/src/facade/macro_runner.rs b/orchidlang/src/facade/macro_runner.rs deleted file mode 100644 index cdf18b6..0000000 --- a/orchidlang/src/facade/macro_runner.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Encapsulates the macro runner's scaffolding. Relies on a [ProjectTree] -//! loaded by the [super::loader::Loader] - -use std::iter; - -use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj, ProjectResult, Reporter}; -use crate::location::CodeOrigin; -use crate::parse::parsed; -use crate::pipeline::project::{ItemKind, ProjItem, ProjectTree}; -use crate::rule::repository::Repo; -use crate::tree::TreeTransforms; - -/// Encapsulates the macro repository and the constant list, and allows querying -/// for macro execution results -pub struct MacroRunner { - /// Optimized catalog of substitution rules - pub repo: Repo, - /// Runtime code containing macro invocations - pub timeout: Option, -} -impl MacroRunner { - /// Initialize a macro runner - pub fn new(tree: &ProjectTree, timeout: Option, reporter: &Reporter) -> Self { - let rules = tree.all_rules(); - let repo = Repo::new(rules, reporter); - Self { repo, timeout } - } - - /// Process the macros in an expression. - pub fn process_expr(&self, expr: parsed::Expr) -> ProjectResult { - match self.timeout { - None => Ok((self.repo.pass(&expr)).unwrap_or_else(|| expr.clone())), - Some(limit) => { - let (o, leftover_gas) = self.repo.long_step(&expr, limit + 1); - if 0 < leftover_gas { - return Ok(o); - } - Err(MacroTimeout { location: expr.range.origin(), limit }.pack()) - }, - } - } - - /// Run all macros in the project. - pub fn run_macros(&self, tree: ProjectTree, reporter: &Reporter) -> ProjectTree { - ProjectTree(tree.0.map_data( - |_, item| match &item.kind { - ItemKind::Const(c) => match self.process_expr(c.clone()) { - Ok(expr) => ProjItem { kind: ItemKind::Const(expr) }, - Err(e) => { - reporter.report(e); - item - }, - }, - _ => item, - }, - |_, x| x, - |_, x| x, - )) - } - - /// Obtain an iterator that steps through the preprocessing of a constant - /// for debugging macros - pub fn step(&self, mut expr: parsed::Expr) -> impl Iterator + '_ { - iter::from_fn(move || { - expr = self.repo.step(&expr)?; - Some(expr.clone()) - }) - } -} - -/// Error raised when a macro runs too long -#[derive(Debug)] -pub struct MacroTimeout { - location: CodeOrigin, - limit: usize, -} -impl ProjectError for MacroTimeout { - const DESCRIPTION: &'static str = "Macro execution has not halted"; - - fn message(&self) -> String { - let Self { limit, .. } = self; - format!("Macro processing took more than {limit} steps") - } - - fn one_position(&self) -> CodeOrigin { self.location.clone() } -} - -struct MacroErrors(Vec); -impl ProjectError for MacroErrors { - const DESCRIPTION: &'static str = "Errors occurred during macro execution"; - fn positions(&self) -> impl IntoIterator + '_ { - self.0.iter().enumerate().flat_map(|(i, e)| { - e.positions().map(move |ep| ErrorPosition { - origin: ep.origin, - message: Some(match ep.message { - Some(msg) => format!("Error #{}: {}; {msg}", i + 1, e.message()), - None => format!("Error #{}: {}", i + 1, e.message()), - }), - }) - }) - } -} diff --git a/orchidlang/src/facade/merge_trees.rs b/orchidlang/src/facade/merge_trees.rs deleted file mode 100644 index e51cc6e..0000000 --- a/orchidlang/src/facade/merge_trees.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! Combine constants from [super::macro_runner::MacroRunner::run_macros] with -//! systems from [super::loader::Loader::systems] - -use std::sync::Arc; - -use hashbrown::HashMap; - -use super::system::System; -use crate::error::Reporter; -use crate::foreign::inert::Inert; -use crate::foreign::to_clause::ToClause; -use crate::intermediate::ast_to_ir::ast_to_ir; -use crate::intermediate::ir_to_nort::ir_to_nort; -use crate::interpreter::nort; -use crate::location::{CodeGenInfo, CodeLocation}; -use crate::name::{NameLike, Sym}; -use crate::pipeline::project::ConstReport; -use crate::sym; -use crate::tree::{ModMemberRef, TreeTransforms}; -use crate::utils::unwrap_or::unwrap_or; - -/// Equivalent of [crate::pipeline::project::ConstReport] for the interpreter's -/// representation, [crate::interpreter::nort]. -#[derive(Clone)] -pub struct NortConst { - /// Comments associated with the constant which may affect its interpretation - pub comments: Vec>, - /// Location of the definition, if known - pub location: CodeLocation, - /// Value assigned to the constant - pub value: nort::Expr, -} -impl NortConst { - /// Convert into NORT constant from AST constant - pub fn convert_from(value: ConstReport, reporter: &Reporter) -> NortConst { - let module = Sym::new(value.name.split_last().1[..].iter()) - .expect("Constant names from source are at least 2 long"); - let location = CodeLocation::new_src(value.range.clone(), value.name); - let nort = match ast_to_ir(value.value, value.range, module.clone()) { - Ok(ir) => ir_to_nort(&ir), - Err(e) => { - reporter.report(e); - Inert(0).to_expr(location.clone()) - }, - }; - Self { value: nort, location, comments: value.comments } - } -} - -/// Combine a list of symbols loaded from source and the constant trees from -/// each system. -pub fn merge_trees<'a: 'b, 'b>( - source: impl IntoIterator, - systems: impl IntoIterator> + 'b, - reporter: &Reporter, -) -> HashMap { - let mut out = HashMap::new(); - for (name, rep) in source.into_iter() { - out.insert(name.clone(), NortConst::convert_from(rep, reporter)); - } - for system in systems { - let const_module = system.constants.unwrap_mod_ref(); - const_module.search_all((), |stack, node, ()| { - let c = unwrap_or!(node => ModMemberRef::Item; return); - let location = CodeLocation::new_gen(CodeGenInfo::details( - sym!(facade::merge_tree), - format!("system.name={}", system.name), - )); - let value = c.clone().gen_nort(stack.clone(), location.clone()); - let crep = NortConst { value, comments: vec![], location }; - out.insert(Sym::new(stack.unreverse()).expect("root item is forbidden"), crep); - }); - } - out -} diff --git a/orchidlang/src/facade/mod.rs b/orchidlang/src/facade/mod.rs deleted file mode 100644 index 01dfe96..0000000 --- a/orchidlang/src/facade/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! A simplified set of commands each grouping a large subset of the operations -//! exposed by Orchid to make writing embeddings faster in the typical case. - -pub mod loader; -pub mod macro_runner; -pub mod merge_trees; -pub mod process; -pub mod system; -pub mod unbound_ref; diff --git a/orchidlang/src/facade/process.rs b/orchidlang/src/facade/process.rs deleted file mode 100644 index f8049d2..0000000 --- a/orchidlang/src/facade/process.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Run Orchid commands in the context of the loaded environment. Either -//! returned by [super::loader::Loader::proc], or constructed manually from the -//! return value of [super::merge_trees::merge_trees] and -//! [super::loader::Loader::handlers]. - -use hashbrown::HashMap; - -use super::merge_trees::NortConst; -use crate::interpreter::context::{Halt, RunEnv, RunParams}; -use crate::interpreter::error::RunError; -use crate::interpreter::handler::HandlerTable; -use crate::interpreter::nort::Expr; -use crate::interpreter::run::run; -use crate::name::Sym; - -/// This struct ties the state of systems to loaded code, and allows to call -/// Orchid-defined functions -pub struct Process<'a>(RunEnv<'a>); -impl<'a> Process<'a> { - /// Build a process from the return value of [crate::facade::merge_trees] and - pub fn new( - consts: impl IntoIterator, - handlers: HandlerTable<'a>, - ) -> Self { - let symbols: HashMap<_, _> = consts.into_iter().map(|(k, v)| (k, v.value)).collect(); - Self(RunEnv::new(handlers, move |sym, location| { - symbols.get(&sym).cloned().ok_or_else(|| RunEnv::sym_not_found(sym, location)) - })) - } - - /// Execute the given command in this process. If gas is specified, at most as - /// many steps will be executed and then the partial result returned. - /// - /// This is useful to catch infinite loops or ensure that a tenant program - /// yields - pub fn run(&self, prompt: Expr, gas: Option) -> Result> { - run(prompt, &self.0, &mut RunParams { stack: 1000, gas }) - } -} diff --git a/orchidlang/src/facade/unbound_ref.rs b/orchidlang/src/facade/unbound_ref.rs deleted file mode 100644 index 761d992..0000000 --- a/orchidlang/src/facade/unbound_ref.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Referencing a constant that doesn't exist is a runtime error in Orchid, even -//! though this common error condition is usually caused by faulty macro -//! execution. This module constains functions to detect and raise these errors -//! eagerly. - -use std::fmt; - -use hashbrown::HashSet; -use trait_set::trait_set; - -use crate::error::{ProjectError, Reporter}; -use crate::interpreter::nort::{Clause, Expr}; -use crate::location::{CodeGenInfo, CodeLocation}; -use crate::name::Sym; -use crate::sym; - -/// Start with a symbol -pub fn unbound_refs_sym( - symbol: Sym, - location: CodeLocation, - visited: &mut HashSet, - load: &mut impl FnMut(Sym, CodeLocation) -> Result, - reporter: &Reporter, -) { - if visited.insert(symbol.clone()) { - match load(symbol.clone(), location.clone()) { - Err(error) => reporter.report(MissingSymbol { symbol, location, error }.pack()), - Ok(expr) => unbound_refs_expr(expr, visited, load, reporter), - } - } -} - -/// Find all unbound constant names in a snippet. This is mostly useful to -/// detect macro errors. -pub fn unbound_refs_expr( - expr: Expr, - visited: &mut HashSet, - load: &mut impl FnMut(Sym, CodeLocation) -> Result, - reporter: &Reporter, -) { - expr.search_all(&mut |s: &Expr| { - if let Clause::Constant(symbol) = &*s.cls_mut() { - unbound_refs_sym(symbol.clone(), s.location(), visited, load, reporter) - } - None::<()> - }); -} - -/// Assert that the code contains no invalid references that reference missing -/// symbols. [Clause::Constant]s can be created procedurally, so this isn't a -/// total guarantee, more of a convenience. -pub fn validate_refs( - all_syms: HashSet, - reporter: &Reporter, - load: &mut impl FnMut(Sym, CodeLocation) -> Result, -) -> HashSet { - let mut visited = HashSet::new(); - for sym in all_syms { - let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orchidlang::validate_refs))); - unbound_refs_sym(sym, location, &mut visited, load, reporter); - } - visited -} - -trait_set! { - /// Any error the reference walker can package into a [MissingSymbol] - pub trait SubError = fmt::Display + Clone + Send + Sync + 'static; -} - -/// Information about a reproject failure -#[derive(Clone)] -pub struct MissingSymbol { - /// The error returned by the loader function. This is usually a ismple "not - /// found", but with unusual setups it might provide some useful info. - pub error: E, - /// Location of the first reference to the missing symbol. - pub location: CodeLocation, - /// The symbol in question - pub symbol: Sym, -} -impl ProjectError for MissingSymbol { - const DESCRIPTION: &'static str = "A name not referring to a known symbol was found in the source after \ - macro execution. This can either mean that a symbol name was mistyped, or \ - that macro execution didn't correctly halt."; - fn message(&self) -> String { format!("{}: {}", self.symbol, self.error) } - fn one_position(&self) -> crate::location::CodeOrigin { self.location.origin() } -} - -// struct MissingSymbols { -// errors: Vec, -// } -// impl ProjectError for MissingSymbols { -// fn positions(&self) -> impl IntoIterator { -// self.errors.iter().cloned() } } diff --git a/orchidlang/src/foreign/cps_box.rs b/orchidlang/src/foreign/cps_box.rs deleted file mode 100644 index 039910f..0000000 --- a/orchidlang/src/foreign/cps_box.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Automated wrappers to make working with CPS commands easier. - -use std::fmt; - -use trait_set::trait_set; - -use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData}; -use super::error::{RTError, RTResult}; -use crate::interpreter::nort::{Clause, Expr}; -use crate::location::CodeLocation; -use crate::utils::ddispatch::{Request, Responder}; -use crate::utils::pure_seq::pushed_ref; - -trait_set! { - /// A "well behaved" type that can be used as payload in a CPS box - pub trait CPSPayload = Clone + fmt::Debug + Send + 'static; - /// A function to handle a CPS box with a specific payload - pub trait CPSHandler = FnMut(&T, &Expr) -> RTResult; -} - -/// An Orchid Atom value encapsulating a payload and continuation points -#[derive(Debug, Clone)] -pub struct CPSBox { - /// Number of arguments not provided yet - pub argc: usize, - /// Details about the command - pub payload: T, - /// Possible continuations, in the order they were provided - pub continuations: Vec, -} -impl CPSBox { - /// Create a new command prepared to receive exacly N continuations - #[must_use] - pub fn new(argc: usize, payload: T) -> Self { - debug_assert!(argc > 0, "Null-ary CPS functions are invalid"); - Self { argc, continuations: Vec::new(), payload } - } - /// Unpack the wrapped command and the continuation - #[must_use] - pub fn unpack1(&self) -> (&T, Expr) { - match &self.continuations[..] { - [cont] => (&self.payload, cont.clone()), - _ => panic!("size mismatch"), - } - } - /// Unpack the wrapped command and 2 continuations (usually an async and a - /// sync) - #[must_use] - pub fn unpack2(&self) -> (&T, Expr, Expr) { - match &self.continuations[..] { - [c1, c2] => (&self.payload, c1.clone(), c2.clone()), - _ => panic!("size mismatch"), - } - } - /// Unpack the wrapped command and 3 continuations (usually an async success, - /// an async fail and a sync) - #[must_use] - pub fn unpack3(&self) -> (&T, Expr, Expr, Expr) { - match &self.continuations[..] { - [c1, c2, c3] => (&self.payload, c1.clone(), c2.clone(), c3.clone()), - _ => panic!("size mismatch"), - } - } - - fn assert_applicable(&self, err_loc: &CodeLocation) -> RTResult<()> { - match self.argc { - 0 => Err(NotAFunction(self.clone().atom_expr(err_loc.clone())).pack()), - _ => Ok(()), - } - } -} -impl Responder for CPSBox { - fn respond(&self, _request: Request) {} -} -impl Atomic for CPSBox { - fn as_any(self: Box) -> Box { self } - fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn type_name(&self) -> &'static str { std::any::type_name::() } - fn parser_eq(&self, _: &dyn Atomic) -> bool { false } - fn redirect(&mut self) -> Option<&mut Expr> { None } - fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } - fn apply(mut self: Box, call: CallData) -> RTResult { - self.assert_applicable(&call.location)?; - self.argc -= 1; - self.continuations.push(call.arg); - Ok(self.atom_cls()) - } - fn apply_mut(&mut self, call: CallData) -> RTResult { - self.assert_applicable(&call.location)?; - let new = Self { - argc: self.argc - 1, - continuations: pushed_ref(&self.continuations, call.arg), - payload: self.payload.clone(), - }; - Ok(new.atom_cls()) - } -} diff --git a/orchidlang/src/foreign/fn_bridge.rs b/orchidlang/src/foreign/fn_bridge.rs deleted file mode 100644 index e3e5e22..0000000 --- a/orchidlang/src/foreign/fn_bridge.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! Insert Rust functions in Orchid code almost seamlessly - -use std::any::{Any, TypeId}; -use std::fmt; -use std::marker::PhantomData; - -use intern_all::{i, Tok}; - -use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData}; -use super::error::RTResult; -use super::to_clause::ToClause; -use super::try_from_expr::TryFromExpr; -use crate::interpreter::nort::{Clause, Expr}; -use crate::utils::ddispatch::Responder; - -/// Return a unary lambda wrapped in this struct to take an additional argument -/// in a function passed to Orchid through [super::fn_bridge::xfn]. -/// -/// Container for a unary [FnOnce] that uniquely states the argument and return -/// type. Rust functions are never overloaded, but inexplicably the [Fn] traits -/// take the argument tuple as a generic parameter which means that it cannot -/// be a unique dispatch target. -/// -/// If the function takes an instance of [Thunk], it will contain the expression -/// the function was applied to without any specific normalization. If it takes -/// any other type, the argument will be fully normalized and cast using the -/// type's [TryFromExpr] impl. -pub struct Param { - data: F, - name: Tok, - _t: PhantomData, - _u: PhantomData, -} -unsafe impl Send for Param {} -impl Param { - /// Wrap a new function in a parametric struct - pub fn new(name: Tok, f: F) -> Self - where F: FnOnce(T) -> U { - Self { name, data: f, _t: PhantomData, _u: PhantomData } - } - /// Take out the function - pub fn get(self) -> F { self.data } -} -impl Clone for Param { - fn clone(&self) -> Self { - Self { name: self.name.clone(), data: self.data.clone(), _t: PhantomData, _u: PhantomData } - } -} -impl fmt::Display for Param { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.name) } -} -impl fmt::Debug for Param { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Param").field(&*self.name).finish() - } -} - -/// A marker struct that gets assigned an expression without normalizing it. -/// This behaviour cannot be replicated in usercode, it's implemented with an -/// explicit runtime [TypeId] check invoked by [Param]. -#[derive(Debug, Clone)] -pub struct Thunk(pub Expr); -impl TryFromExpr for Thunk { - fn from_expr(expr: Expr) -> RTResult { Ok(Thunk(expr)) } -} - -struct FnMiddleStage { - arg: Expr, - f: Param, -} - -impl Clone for FnMiddleStage { - fn clone(&self) -> Self { Self { arg: self.arg.clone(), f: self.f.clone() } } -} -impl fmt::Debug for FnMiddleStage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "FnMiddleStage({} {})", self.f, self.arg) - } -} -impl Responder for FnMiddleStage {} -impl< - T: 'static + TryFromExpr, - U: 'static + ToClause, - F: 'static + Clone + FnOnce(T) -> U + Any + Send, -> Atomic for FnMiddleStage -{ - fn as_any(self: Box) -> Box { self } - fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn type_name(&self) -> &'static str { std::any::type_name::() } - fn redirect(&mut self) -> Option<&mut Expr> { - // this should be ctfe'd - (TypeId::of::() != TypeId::of::()).then_some(&mut self.arg) - } - fn run(self: Box, r: RunData) -> AtomicResult { - let Self { arg, f: Param { data: f, .. } } = *self; - Ok(AtomicReturn::Change(0, f(arg.downcast()?).to_clause(r.location))) - } - fn apply_mut(&mut self, _: CallData) -> RTResult { panic!("Atom should have decayed") } -} - -impl Responder for Param {} - -impl< - T: 'static + TryFromExpr + Clone, - U: 'static + ToClause, - F: 'static + Clone + Send + FnOnce(T) -> U, -> Atomic for Param -{ - fn as_any(self: Box) -> Box { self } - fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn type_name(&self) -> &'static str { std::any::type_name::() } - fn redirect(&mut self) -> Option<&mut Expr> { None } - fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } - fn apply_mut(&mut self, call: CallData) -> RTResult { - Ok(FnMiddleStage { arg: call.arg, f: self.clone() }.atom_cls()) - } - fn apply(self: Box, call: CallData) -> RTResult { - Ok(FnMiddleStage { arg: call.arg, f: *self }.atom_cls()) - } -} - -/// Convert a Rust function to Orchid. If you can, register your Rust functions -/// statically with functions in [crate::gen::tree]. -pub fn xfn( - name: &str, - x: impl Xfn, -) -> impl Atomic + Clone { - x.to_atomic(i(name)) -} - -/// Trait for functions that can be directly passed to Orchid. Constraints in a -/// nutshell: -/// -/// - the function must live as long as ['static] -/// - All arguments must implement [TryFromExpr] -/// - all but the last argument must implement [Clone] and [Send] -/// - the return type must implement [ToClause] -/// -/// Take [Thunk] to consume the argument as-is, without normalization. -pub trait Xfn: Clone + Send + 'static { - /// Convert Rust type to Orchid function, given a name for logging - fn to_atomic(self, name: Tok) -> impl Atomic + Clone; -} - -/// Conversion functions from [Fn] traits into [Atomic]. Since Rust's type -/// system allows overloaded [Fn] implementations, we must specify the arity and -/// argument types for this process. Arities are only defined up to 9, but the -/// function can always return another call to `xfn_`N`ary` to consume more -/// arguments. -pub mod xfn_impls { - use intern_all::{i, Tok}; - - use super::super::atom::Atomic; - use super::super::try_from_expr::TryFromExpr; - #[allow(unused)] // for doc - use super::Thunk; - use super::{Param, ToClause, Xfn}; - - macro_rules! xfn_variant { - ( - $number:expr, - ($($t:ident)*) - ($($alt:expr)*) - ) => { - paste::paste!{ - impl< - $( $t : TryFromExpr + Clone + Send + 'static, )* - TLast: TryFromExpr + Clone + 'static, - TReturn: ToClause + Send + 'static, - TFunction: FnOnce( $( $t , )* TLast ) - -> TReturn + Clone + Send + 'static - > Xfn<$number, ($($t,)* TLast,), TReturn> for TFunction { - fn to_atomic(self, name: Tok) -> impl Atomic + Clone { - #[allow(unused_variables)] - let argc = 0; - let stage_n = name.clone(); - xfn_variant!(@BODY_LOOP self name stage_n argc - ( $( ( $t [< $t:lower >] ) )* ) - ( $( [< $t:lower >] )* ) - ) - } - } - } - }; - (@BODY_LOOP $function:ident $name:ident $stage_n:ident $argc:ident ( - ( $Next:ident $next:ident ) - $( ( $T:ident $t:ident ) )* - ) $full:tt) => {{ - Param::new($stage_n, move |$next : $Next| { - let $argc = $argc + 1; - let $stage_n = i(&format!("{}/{}", $name, $argc)); - xfn_variant!(@BODY_LOOP $function $name $stage_n $argc ( $( ( $T $t ) )* ) $full) - }) - }}; - (@BODY_LOOP $function:ident $name:ident $stage_n:ident $argc:ident ( - - ) ( $( $t:ident )* )) => {{ - Param::new($stage_n, |last: TLast| $function ( $( $t , )* last )) - }}; - } - - xfn_variant!(1, () (2 3 4 5 6 7 8 9 10 11 12 13 14 15 16)); - xfn_variant!(2, (A) (1 3 4 5 6 7 8 9 10 11 12 13 14 15 16)); - xfn_variant!(3, (A B) (1 2 4 5 6 7 8 9 10 11 12 13 14 15 16)); - xfn_variant!(4, (A B C) (1 2 3 5 6 7 8 9 10 11 12 13 14 15 16)); - xfn_variant!(5, (A B C D) (1 2 3 4 6 7 8 9 10 11 12 13 14 15 16)); - xfn_variant!(6, (A B C D E) (1 2 3 4 5 7 8 9 10 11 12 13 14 15 16)); - xfn_variant!(7, (A B C D E F) (1 2 3 4 5 6 8 9 10 11 12 13 14 15 16)); - xfn_variant!(8, (A B C D E F G) (1 2 3 4 5 6 7 9 10 11 12 13 14 15 16)); - xfn_variant!(9, (A B C D E F G H) (1 2 3 4 5 6 7 8 10 11 12 13 14 15 16)); - // at higher arities rust-analyzer fails to load the project -} diff --git a/orchidlang/src/foreign/inert.rs b/orchidlang/src/foreign/inert.rs deleted file mode 100644 index 9eecf49..0000000 --- a/orchidlang/src/foreign/inert.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! An [Atomic] that wraps inert data. - -use std::any::Any; -use std::fmt; -use std::ops::{Deref, DerefMut}; - -use ordered_float::NotNan; - -use super::atom::{Atom, Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData}; -use super::error::{RTError, RTResult}; -use super::try_from_expr::TryFromExpr; -use crate::foreign::error::AssertionError; -use crate::interpreter::nort::{Clause, Expr}; -use crate::libs::std::number::Numeric; -use crate::libs::std::string::OrcString; -use crate::utils::ddispatch::{Request, Responder}; - -/// A proxy trait that implements [Atomic] for blobs of data in Rust code that -/// cannot be processed and always report inert. Since these are expected to be -/// parameters of Rust functions it also automatically implements [TryFromExpr] -/// so that `Inert` arguments can be parsed directly. -pub trait InertPayload: fmt::Debug + Clone + Send + 'static { - /// Typename to be shown in the error when a conversion from [Expr] fails - /// - /// This will default to `type_name::()` when it becomes stable - const TYPE_STR: &'static str; - /// Proxies to [Responder] so that you don't have to implmeent it manually if - /// you need it, but behaves exactly as the default implementation. - #[allow(unused_mut, unused_variables)] // definition should show likely usage - fn respond(&self, mut request: Request) {} - /// Equality comparison used by the pattern matcher. Since the pattern matcher - /// only works with parsed code, you only need to implement this if your type - /// is directly parseable. - /// - /// If your type implements [PartialEq], this can simply be implemented as - /// ```ignore - /// fn strict_eq(&self, other: &Self) -> bool { self == other } - /// ``` - #[allow(unused_variables)] - fn strict_eq(&self, other: &Self) -> bool { false } -} - -/// An atom that stores a value and rejects all interpreter interactions. It is -/// used to reference foreign data in Orchid. -#[derive(Debug, Clone)] -pub struct Inert(pub T); -impl Inert { - /// Wrap the argument in a type-erased [Atom] for embedding in Orchid - /// structures. - pub fn atom(t: T) -> Atom { Atom::new(Inert(t)) } -} - -impl Deref for Inert { - type Target = T; - fn deref(&self) -> &Self::Target { &self.0 } -} - -impl DerefMut for Inert { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } -} - -impl Responder for Inert { - fn respond(&self, mut request: Request) { - if request.can_serve::() { request.serve(self.0.clone()) } else { self.0.respond(request) } - } -} -impl Atomic for Inert { - fn as_any(self: Box) -> Box { self } - fn as_any_ref(&self) -> &dyn Any { self } - fn type_name(&self) -> &'static str { std::any::type_name::() } - - fn redirect(&mut self) -> Option<&mut Expr> { None } - fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } - fn apply_mut(&mut self, call: CallData) -> RTResult { - Err(NotAFunction(self.clone().atom_expr(call.location)).pack()) - } - fn parser_eq(&self, other: &dyn Atomic) -> bool { - other.as_any_ref().downcast_ref::().map_or(false, |other| self.0.strict_eq(&other.0)) - } -} - -impl TryFromExpr for Inert { - fn from_expr(expr: Expr) -> RTResult { - let Expr { clause, location } = expr; - match clause.try_unwrap() { - Ok(Clause::Atom(at)) => at - .try_downcast::() - .map_err(|a| AssertionError::ext(location, T::TYPE_STR, format!("{a:?}"))), - Err(inst) => match &*inst.cls_mut() { - Clause::Atom(at) => at - .downcast_ref::() - .cloned() - .ok_or_else(|| AssertionError::ext(location, T::TYPE_STR, format!("{inst}"))), - cls => AssertionError::fail(location, "atom", format!("{cls}")), - }, - Ok(cls) => AssertionError::fail(location, "atom", format!("{cls}")), - } - } -} - -impl fmt::Display for Inert { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } -} - -impl InertPayload for bool { - const TYPE_STR: &'static str = "bool"; - fn strict_eq(&self, other: &Self) -> bool { self == other } - fn respond(&self, mut request: Request) { - request.serve_with(|| OrcString::from(self.to_string())) - } -} - -impl InertPayload for usize { - const TYPE_STR: &'static str = "usize"; - fn strict_eq(&self, other: &Self) -> bool { self == other } - fn respond(&self, mut request: Request) { - request.serve(Numeric::Uint(*self)); - request.serve_with(|| OrcString::from(self.to_string())) - } -} - -impl InertPayload for NotNan { - const TYPE_STR: &'static str = "NotNan"; - fn strict_eq(&self, other: &Self) -> bool { self == other } - fn respond(&self, mut request: Request) { - request.serve(Numeric::Float(*self)); - request.serve_with(|| OrcString::from(self.to_string())) - } -} diff --git a/orchidlang/src/foreign/mod.rs b/orchidlang/src/foreign/mod.rs deleted file mode 100644 index 4b91d14..0000000 --- a/orchidlang/src/foreign/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Interaction with foreign code -//! -//! Structures and traits used in the exposure of external functions and values -//! to Orchid code -pub mod atom; -pub mod cps_box; -pub mod error; -pub mod fn_bridge; -pub mod inert; -pub mod process; -pub mod to_clause; -pub mod try_from_expr; diff --git a/orchidlang/src/foreign/process.rs b/orchidlang/src/foreign/process.rs deleted file mode 100644 index f59e5a9..0000000 --- a/orchidlang/src/foreign/process.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! An [Atomic] implementor that runs a callback and turns into the return -//! value. Useful to generate expressions that depend on values in the -//! interpreter's context, and to defer the generation of expensive -//! subexpressions -use std::fmt; - -use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData}; -use super::error::RTResult; -use super::to_clause::ToClause; -use crate::interpreter::nort::{Clause, Expr}; -use crate::utils::ddispatch::Responder; - -/// An atom that immediately decays to the result of the function when -/// normalized. Can be used to build infinite recursive datastructures from -/// Rust. -#[derive(Clone)] -pub struct Unstable(F); -impl R + Send + 'static, R: ToClause> Unstable { - /// Wrap a function in an Unstable - pub const fn new(f: F) -> Self { Self(f) } -} -impl Responder for Unstable {} -impl fmt::Debug for Unstable { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Unstable").finish_non_exhaustive() - } -} -impl R + Send + 'static, R: ToClause> Atomic for Unstable { - fn as_any(self: Box) -> Box { self } - fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn type_name(&self) -> &'static str { std::any::type_name::() } - - fn apply_mut(&mut self, _: CallData) -> RTResult { panic!("This atom decays instantly") } - fn run(self: Box, run: RunData) -> AtomicResult { - let loc = run.location.clone(); - let clause = self.0(run).to_clause(loc); - Ok(AtomicReturn::Change(0, clause)) - } - fn redirect(&mut self) -> Option<&mut Expr> { None } -} diff --git a/orchidlang/src/foreign/to_clause.rs b/orchidlang/src/foreign/to_clause.rs deleted file mode 100644 index 169eb5e..0000000 --- a/orchidlang/src/foreign/to_clause.rs +++ /dev/null @@ -1,202 +0,0 @@ -//! Conversions from Rust values to Orchid expressions. Many APIs and -//! [super::fn_bridge] in particular use this to automatically convert values on -//! the boundary. The opposite conversion is [super::try_from_expr::TryFromExpr] - -use super::atom::{Atomic, RunData}; -use super::process::Unstable; -use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort::{Clause, ClauseInst, Expr}; -use crate::location::CodeLocation; -use crate::utils::clonable_iter::Clonable; - -/// A trait for things that are infallibly convertible to [ClauseInst]. These -/// types can be returned by callbacks passed to [super::fn_bridge::xfn]. -pub trait ToClause: Sized { - /// Convert this value to a [Clause]. If your value can only be directly - /// converted to a [ClauseInst], you can call `ClauseInst::to_clause` to - /// unwrap it if possible or fall back to [Clause::Identity]. - fn to_clause(self, location: CodeLocation) -> Clause; - - /// Convert the type to a [Clause]. - fn to_clsi(self, location: CodeLocation) -> ClauseInst { - ClauseInst::new(self.to_clause(location)) - } - - /// Convert to an expression via [ToClause]. - fn to_expr(self, location: CodeLocation) -> Expr { - Expr { clause: self.to_clsi(location.clone()), location } - } -} - -impl ToClause for T { - fn to_clause(self, _: CodeLocation) -> Clause { self.atom_cls() } -} -impl ToClause for Clause { - fn to_clause(self, _: CodeLocation) -> Clause { self } -} -impl ToClause for ClauseInst { - fn to_clause(self, _: CodeLocation) -> Clause { self.into_cls() } - fn to_clsi(self, _: CodeLocation) -> ClauseInst { self } -} -impl ToClause for Expr { - fn to_clause(self, location: CodeLocation) -> Clause { self.clause.to_clause(location) } - fn to_clsi(self, _: CodeLocation) -> ClauseInst { self.clause } - fn to_expr(self, _: CodeLocation) -> Expr { self } -} - -struct ListGen(Clonable) -where - I: Iterator + Send, - I::Item: ToClause + Send; -impl Clone for ListGen -where - I: Iterator + Send, - I::Item: ToClause + Send, -{ - fn clone(&self) -> Self { Self(self.0.clone()) } -} -impl ToClause for ListGen -where - I: Iterator + Send + 'static, - I::Item: ToClause + Clone + Send, -{ - fn to_clause(mut self, location: CodeLocation) -> Clause { - let ctx = nort_gen(location.clone()); - match self.0.next() { - None => tpl::C("std::list::end").template(ctx, []), - Some(val) => { - let atom = Unstable::new(|run| self.to_clause(run.location)); - tpl::a2(tpl::C("std::list::cons"), tpl::Slot, tpl::V(atom)) - .template(ctx, [val.to_clause(location)]) - }, - } - } -} - -/// Convert an iterator into a lazy-evaluated Orchid list. -pub fn list(items: I) -> impl ToClause -where - I: IntoIterator + Clone + Send + Sync + 'static, - I::IntoIter: Send, - I::Item: ToClause + Clone + Send, -{ - Unstable::new(move |RunData { location, .. }| { - ListGen(Clonable::new(items.clone().into_iter().map(move |t| t.to_clsi(location.clone())))) - }) -} - -mod implementations { - use std::any::Any; - use std::fmt; - use std::sync::Arc; - - use super::{list, ToClause}; - use crate::foreign::atom::{Atom, Atomic, AtomicResult, CallData, RunData}; - use crate::foreign::error::{AssertionError, RTErrorObj, RTResult}; - use crate::foreign::inert::Inert; - use crate::foreign::try_from_expr::TryFromExpr; - use crate::gen::tpl; - use crate::gen::traits::Gen; - use crate::interpreter::gen_nort::nort_gen; - use crate::interpreter::nort::{Clause, Expr}; - use crate::libs::std::tuple::Tuple; - use crate::location::CodeLocation; - use crate::utils::ddispatch::Responder; - - impl ToClause for Option { - fn to_clause(self, location: CodeLocation) -> Clause { - let ctx = nort_gen(location.clone()); - match self { - None => tpl::C("std::option::none").template(ctx, []), - Some(t) => - tpl::A(tpl::C("std::option::some"), tpl::Slot).template(ctx, [t.to_clause(location)]), - } - } - } - - impl ToClause for Result { - fn to_clause(self, location: CodeLocation) -> Clause { - let ctx = nort_gen(location.clone()); - match self { - Ok(t) => - tpl::A(tpl::C("std::result::ok"), tpl::Slot).template(ctx, [t.to_clause(location)]), - Err(e) => - tpl::A(tpl::C("std::result::err"), tpl::Slot).template(ctx, [e.to_clause(location)]), - } - } - } - - struct PendingError(RTErrorObj); - impl Responder for PendingError {} - impl fmt::Debug for PendingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "PendingError({})", self.0) - } - } - impl Atomic for PendingError { - fn as_any(self: Box) -> Box { self } - fn as_any_ref(&self) -> &dyn Any { self } - fn type_name(&self) -> &'static str { std::any::type_name::() } - - fn redirect(&mut self) -> Option<&mut Expr> { None } - fn run(self: Box, _: RunData) -> AtomicResult { Err(self.0) } - fn apply_mut(&mut self, _: CallData) -> RTResult { - panic!("This atom decays instantly") - } - } - - impl ToClause for RTResult { - fn to_clause(self, location: CodeLocation) -> Clause { - match self { - Err(e) => PendingError(e).atom_cls(), - Ok(t) => t.to_clause(location), - } - } - } - - impl ToClause for Vec { - fn to_clause(self, location: CodeLocation) -> Clause { list(self).to_clause(location) } - } - - impl ToClause for Atom { - fn to_clause(self, _: CodeLocation) -> Clause { Clause::Atom(self) } - } - - macro_rules! gen_tuple_impl { - ( ($($T:ident)*) ($($t:ident)*)) => { - impl<$($T: ToClause),*> ToClause for ($($T,)*) { - fn to_clause(self, location: CodeLocation) -> Clause { - let ($($t,)*) = self; - Inert(Tuple(Arc::new(vec![ - $($t.to_expr(location.clone()),)* - ]))).atom_cls() - } - } - - impl<$($T: TryFromExpr),*> TryFromExpr for ($($T,)*) { - fn from_expr(ex: Expr) -> RTResult { - let Inert(Tuple(slice)) = ex.clone().downcast()?; - match &slice[..] { - [$($t),*] => Ok(($($t.clone().downcast()?,)*)), - _ => AssertionError::fail(ex.location(), "Tuple length mismatch", format!("{ex}")) - } - } - } - }; - } - - gen_tuple_impl!((A)(a)); - gen_tuple_impl!((A B) (a b)); - gen_tuple_impl!((A B C) (a b c)); - gen_tuple_impl!((A B C D) (a b c d)); - gen_tuple_impl!((A B C D E) (a b c d e)); - gen_tuple_impl!((A B C D E F) (a b c d e f)); - gen_tuple_impl!((A B C D E F G) (a b c d e f g)); - gen_tuple_impl!((A B C D E F G H) (a b c d e f g h)); - gen_tuple_impl!((A B C D E F G H I) (a b c d e f g h i)); - gen_tuple_impl!((A B C D E F G H I J) (a b c d e f g h i j)); - gen_tuple_impl!((A B C D E F G H I J K) (a b c d e f g h i j k)); - gen_tuple_impl!((A B C D E F G H I J K L) (a b c d e f g h i j k l)); -} diff --git a/orchidlang/src/intermediate/ast_to_ir.rs b/orchidlang/src/intermediate/ast_to_ir.rs deleted file mode 100644 index 2de1bda..0000000 --- a/orchidlang/src/intermediate/ast_to_ir.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Convert the preprocessed AST into IR - -use std::collections::VecDeque; -use std::rc::Rc; - -use substack::Substack; - -use super::ir; -use crate::error::{ProjectError, ProjectResult}; -use crate::location::{CodeOrigin, SourceRange}; -use crate::name::Sym; -use crate::parse::parsed; -use crate::utils::unwrap_or::unwrap_or; - -trait IRErrorKind: Clone + Send + Sync + 'static { - const DESCR: &'static str; -} - -#[derive(Clone)] -struct IRError { - at: SourceRange, - sym: SourceRange, - _kind: T, -} -impl IRError { - fn new(at: SourceRange, sym: SourceRange, _kind: T) -> Self { Self { at, sym, _kind } } -} -impl ProjectError for IRError { - const DESCRIPTION: &'static str = T::DESCR; - fn message(&self) -> String { format!("In {}, {}", self.sym, T::DESCR) } - fn one_position(&self) -> CodeOrigin { CodeOrigin::Source(self.at.clone()) } -} - -#[derive(Clone)] -struct EmptyS; -impl IRErrorKind for EmptyS { - const DESCR: &'static str = "`()` as a clause is meaningless in lambda calculus"; -} - -#[derive(Clone)] -struct BadGroup; -impl IRErrorKind for BadGroup { - const DESCR: &'static str = "Only `(...)` may be used after macros. \ - `[...]` and `{...}` left in the code are signs of incomplete macro execution"; -} - -#[derive(Clone)] -struct InvalidArg; -impl IRErrorKind for InvalidArg { - const DESCR: &'static str = "Argument names can only be Name nodes"; -} - -#[derive(Clone)] -struct PhLeak; -impl IRErrorKind for PhLeak { - const DESCR: &'static str = "Placeholders shouldn't even appear \ - in the code during macro execution, this is likely a compiler bug"; -} - -/// Try to convert an expression from AST format to typed lambda -pub fn ast_to_ir(expr: parsed::Expr, symbol: SourceRange, module: Sym) -> ProjectResult { - expr_rec(expr, Context::new(symbol, module)) -} - -#[derive(Clone)] -struct Context<'a> { - names: Substack<'a, Sym>, - range: SourceRange, - module: Sym, -} - -impl<'a> Context<'a> { - #[must_use] - fn w_name<'b>(&'b self, name: Sym) -> Context<'b> - where 'a: 'b { - Context { names: self.names.push(name), range: self.range.clone(), module: self.module.clone() } - } -} -impl Context<'static> { - #[must_use] - fn new(symbol: SourceRange, module: Sym) -> Self { - Self { names: Substack::Bottom, range: symbol, module } - } -} - -/// Process an expression sequence -fn exprv_rec( - mut v: VecDeque, - ctx: Context<'_>, - location: SourceRange, -) -> ProjectResult { - let last = unwrap_or! {v.pop_back(); { - return Err(IRError::new(location, ctx.range, EmptyS).pack()); - }}; - let v_end = match v.back() { - None => return expr_rec(last, ctx), - Some(penultimate) => penultimate.range.end(), - }; - let f = exprv_rec(v, ctx.clone(), location.map_range(|r| r.start..v_end))?; - let x = expr_rec(last, ctx.clone())?; - let value = ir::Clause::Apply(Rc::new(f), Rc::new(x)); - Ok(ir::Expr::new(value, location, ctx.module)) -} - -/// Process an expression -fn expr_rec(parsed::Expr { value, range }: parsed::Expr, ctx: Context) -> ProjectResult { - match value { - parsed::Clause::S(parsed::PType::Par, body) => { - return exprv_rec(body.to_vec().into(), ctx, range); - }, - parsed::Clause::S(..) => return Err(IRError::new(range, ctx.range, BadGroup).pack()), - _ => (), - } - let value = match value { - parsed::Clause::Atom(a) => ir::Clause::Atom(a.clone()), - parsed::Clause::Lambda(arg, b) => { - let name = match &arg[..] { - [parsed::Expr { value: parsed::Clause::Name(name), .. }] => name, - [parsed::Expr { value: parsed::Clause::Placeh { .. }, .. }] => - return Err(IRError::new(range.clone(), ctx.range, PhLeak).pack()), - _ => return Err(IRError::new(range.clone(), ctx.range, InvalidArg).pack()), - }; - let body_ctx = ctx.w_name(name.clone()); - let body = exprv_rec(b.to_vec().into(), body_ctx, range.clone())?; - ir::Clause::Lambda(Rc::new(body)) - }, - parsed::Clause::Name(name) => { - let lvl_opt = (ctx.names.iter()).enumerate().find(|(_, n)| **n == name).map(|(lvl, _)| lvl); - match lvl_opt { - Some(lvl) => ir::Clause::LambdaArg(lvl), - None => ir::Clause::Constant(name.clone()), - } - }, - parsed::Clause::S(parsed::PType::Par, entries) => - exprv_rec(entries.to_vec().into(), ctx.clone(), range.clone())?.value, - parsed::Clause::S(..) => return Err(IRError::new(range, ctx.range, BadGroup).pack()), - parsed::Clause::Placeh { .. } => return Err(IRError::new(range, ctx.range, PhLeak).pack()), - }; - Ok(ir::Expr::new(value, range, ctx.module)) -} diff --git a/orchidlang/src/intermediate/ir.rs b/orchidlang/src/intermediate/ir.rs deleted file mode 100644 index b9cbab0..0000000 --- a/orchidlang/src/intermediate/ir.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! IR is an abstract representation of Orchid expressions that's impractical -//! for all purposes except converting to and from other representations. Future -//! innovations in the processing and execution of code will likely operate on -//! this representation. - -use std::fmt; -use std::rc::Rc; - -use crate::foreign::atom::AtomGenerator; -use crate::location::{CodeLocation, SourceRange}; -use crate::name::Sym; -use crate::utils::string_from_charset::string_from_charset; - -/// Indicates whether either side needs to be wrapped. Syntax whose end is -/// ambiguous on that side must use parentheses, or forward the flag -#[derive(PartialEq, Eq, Clone, Copy)] -struct Wrap(bool, bool); - -/// Code element with associated metadata -#[derive(Clone)] -pub struct Expr { - /// Code element - pub value: Clause, - /// Location metadata - pub location: CodeLocation, -} - -impl Expr { - /// Create an IR expression - pub fn new(value: Clause, location: SourceRange, module: Sym) -> Self { - Self { value, location: CodeLocation::new_src(location, module) } - } - - fn deep_fmt(&self, f: &mut fmt::Formatter<'_>, depth: usize, tr: Wrap) -> fmt::Result { - let Expr { value, .. } = self; - value.deep_fmt(f, depth, tr)?; - Ok(()) - } -} - -impl fmt::Debug for Expr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.deep_fmt(f, 0, Wrap(false, false)) - } -} - -/// Semantic code element -#[derive(Clone)] -pub enum Clause { - /// Function call expression - Apply(Rc, Rc), - /// Function expression - Lambda(Rc), - /// Reference to an external constant - Constant(Sym), - /// Reference to a function argument - LambdaArg(usize), - /// An opaque non-callable value, eg. a file handle - Atom(AtomGenerator), -} - -const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz"; - -fn parametric_fmt( - f: &mut fmt::Formatter<'_>, - depth: usize, - prefix: &str, - body: &Expr, - wrap_right: bool, -) -> fmt::Result { - if wrap_right { - write!(f, "(")?; - } - f.write_str(prefix)?; - f.write_str(&string_from_charset(depth as u64, ARGNAME_CHARSET))?; - f.write_str(".")?; - body.deep_fmt(f, depth + 1, Wrap(false, false))?; - if wrap_right { - write!(f, ")")?; - } - Ok(()) -} - -impl Clause { - fn deep_fmt(&self, f: &mut fmt::Formatter<'_>, depth: usize, Wrap(wl, wr): Wrap) -> fmt::Result { - match self { - Self::Atom(a) => write!(f, "{a:?}"), - Self::Lambda(body) => parametric_fmt(f, depth, "\\", body, wr), - Self::LambdaArg(skip) => { - let lambda_depth = (depth - skip - 1).try_into().unwrap(); - f.write_str(&string_from_charset(lambda_depth, ARGNAME_CHARSET)) - }, - Self::Apply(func, x) => { - if wl { - write!(f, "(")?; - } - func.deep_fmt(f, depth, Wrap(false, true))?; - write!(f, " ")?; - x.deep_fmt(f, depth, Wrap(true, wr && !wl))?; - if wl { - write!(f, ")")?; - } - Ok(()) - }, - Self::Constant(token) => write!(f, "{token}"), - } - } -} - -impl fmt::Debug for Clause { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.deep_fmt(f, 0, Wrap(false, false)) - } -} diff --git a/orchidlang/src/intermediate/ir_to_nort.rs b/orchidlang/src/intermediate/ir_to_nort.rs deleted file mode 100644 index f02f99e..0000000 --- a/orchidlang/src/intermediate/ir_to_nort.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Convert IR to the interpreter's NORT representation - -use super::ir; -use crate::interpreter::nort; -use crate::interpreter::nort_builder::NortBuilder; - -fn expr(expr: &ir::Expr, ctx: NortBuilder<(), usize>) -> nort::Expr { - clause(&expr.value, ctx).into_expr(expr.location.clone()) -} - -fn clause(cls: &ir::Clause, ctx: NortBuilder<(), usize>) -> nort::Clause { - match cls { - ir::Clause::Constant(name) => nort::Clause::Constant(name.clone()), - ir::Clause::Atom(a) => nort::Clause::Atom(a.run()), - ir::Clause::LambdaArg(n) => { - ctx.arg_logic(n); - nort::Clause::LambdaArg - }, - ir::Clause::Apply(f, x) => ctx.apply_logic(|c| expr(f, c), |c| expr(x, c)), - ir::Clause::Lambda(body) => ctx.lambda_logic(&(), |c| expr(body, c)), - } -} - -/// Convert an expression. -pub fn ir_to_nort(expr: &ir::Expr) -> nort::Expr { - let c = NortBuilder::new(&|count| { - let mut count: usize = *count; - Box::new(move |()| match count { - 0 => true, - _ => { - count -= 1; - false - }, - }) - }); - nort::ClauseInst::new(clause(&expr.value, c)).into_expr(expr.location.clone()) -} diff --git a/orchidlang/src/intermediate/mod.rs b/orchidlang/src/intermediate/mod.rs deleted file mode 100644 index fc03ef2..0000000 --- a/orchidlang/src/intermediate/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Intermediate representation. Currently just an indirection between -//! [super::parse::parsed] and [super::interpreter::nort], in the future -//! hopefully a common point for alternate encodings, optimizations and targets. - -pub mod ast_to_ir; -pub mod ir; -pub mod ir_to_nort; diff --git a/orchidlang/src/interpreter/apply.rs b/orchidlang/src/interpreter/apply.rs deleted file mode 100644 index 3acde03..0000000 --- a/orchidlang/src/interpreter/apply.rs +++ /dev/null @@ -1,109 +0,0 @@ -use never::Never; - -use super::context::{RunEnv, RunParams}; -use super::nort::{Clause, ClauseInst, Expr}; -use super::path_set::{PathSet, Step}; -use crate::foreign::atom::CallData; -use crate::foreign::error::RTResult; - -/// Process the clause at the end of the provided path. Note that paths always -/// point to at least one target. Note also that this is not cached as a -/// normalization step in the intermediate expressions. -fn map_at( - mut path: impl Iterator, - source: &Clause, - mapper: &mut impl FnMut(&Clause) -> Result, -) -> Result { - // Pass through some unambiguous wrapper clauses - match source { - Clause::Identity(alt) => return map_at(path, &alt.cls_mut(), mapper), - Clause::Lambda { args, body: Expr { location: b_loc, clause } } => - return Ok(Clause::Lambda { - args: args.clone(), - body: Expr { - clause: map_at(path, &clause.cls_mut(), mapper)?.into_inst(), - location: b_loc.clone(), - }, - }), - _ => (), - } - Ok(match (source, path.next()) { - (Clause::Lambda { .. } | Clause::Identity(_), _) => unreachable!("Handled above"), - // If the path ends and this isn't a lambda, process it - (val, None) => mapper(val)?, - // If it's an Apply, execute the next step in the path - (Clause::Apply { f, x }, Some(head)) => { - let proc = |x: &Expr| Ok(map_at(path, &x.cls_mut(), mapper)?.into_expr(x.location())); - match head { - None => Clause::Apply { f: proc(f)?, x: x.clone() }, - Some(n) => { - let i = x.len() - n - 1; - let mut argv = x.clone(); - argv[i] = proc(&x[i])?; - Clause::Apply { f: f.clone(), x: argv } - }, - } - }, - (_, Some(_)) => panic!("Path leads into node that isn't Apply or Lambda"), - }) -} - -/// Replace the [Clause::LambdaArg] placeholders at the ends of the [PathSet] -/// with the value in the body. Note that a path may point to multiple -/// placeholders. -#[must_use] -pub fn substitute( - paths: &PathSet, - value: ClauseInst, - body: &Clause, - on_sub: &mut impl FnMut(), -) -> Clause { - let PathSet { steps, next } = paths; - map_at(steps.iter().cloned(), body, &mut |chkpt| -> Result { - match (chkpt, next) { - (Clause::Lambda { .. } | Clause::Identity(_), _) => { - unreachable!("Handled by map_at") - }, - (Clause::Apply { f, x }, Some(conts)) => { - let mut argv = x.clone(); - let f = match conts.get(&None) { - None => f.clone(), - Some(sp) => substitute(sp, value.clone(), &f.cls_mut(), on_sub).into_expr(f.location()), - }; - for (i, old) in argv.iter_mut().rev().enumerate() { - if let Some(sp) = conts.get(&Some(i)) { - let tmp = substitute(sp, value.clone(), &old.cls_mut(), on_sub); - *old = tmp.into_expr(old.location()); - } - } - Ok(Clause::Apply { f, x: argv }) - }, - (Clause::LambdaArg, None) => { - on_sub(); - Ok(Clause::Identity(value.clone())) - }, - (_, None) => panic!("Argument path must point to LambdaArg"), - (_, Some(_)) => panic!("Argument path can only fork at Apply"), - } - }) - .unwrap_or_else(|e| match e {}) -} - -pub(super) fn apply_as_atom( - f: Expr, - arg: Expr, - env: &RunEnv, - params: &mut RunParams, -) -> RTResult { - let call = CallData { location: f.location(), arg, env, params }; - match f.clause.try_unwrap() { - Ok(clause) => match clause { - Clause::Atom(atom) => Ok(atom.apply(call)?), - _ => panic!("Not an atom"), - }, - Err(clsi) => match &mut *clsi.cls_mut() { - Clause::Atom(atom) => Ok(atom.apply_mut(call)?), - _ => panic!("Not an atom"), - }, - } -} diff --git a/orchidlang/src/interpreter/error.rs b/orchidlang/src/interpreter/error.rs deleted file mode 100644 index 90be6b0..0000000 --- a/orchidlang/src/interpreter/error.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Error produced by the interpreter. - -use std::fmt; - -use super::run::State; -use crate::foreign::error::{RTError, RTErrorObj}; - -/// Error produced by the interpreter. This could be because the code is faulty, -/// but equally because gas was being counted and it ran out. -#[derive(Debug)] -pub enum RunError<'a> { - /// A Rust function encountered an error - Extern(RTErrorObj), - /// Ran out of gas - Interrupted(State<'a>), -} - -impl<'a, T: RTError + 'static> From for RunError<'a> { - fn from(value: T) -> Self { Self::Extern(value.pack()) } -} - -impl<'a> From for RunError<'a> { - fn from(value: RTErrorObj) -> Self { Self::Extern(value) } -} - -impl<'a> fmt::Display for RunError<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Interrupted(i) => write!(f, "Ran out of gas:\n{i}"), - Self::Extern(e) => write!(f, "Program fault: {e}"), - } - } -} diff --git a/orchidlang/src/interpreter/gen_nort.rs b/orchidlang/src/interpreter/gen_nort.rs deleted file mode 100644 index 7326d58..0000000 --- a/orchidlang/src/interpreter/gen_nort.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Implementations of [Generable] for [super::nort] - -use intern_all::i; - -use super::nort_builder::NortBuilder; -use crate::foreign::atom::Atom; -use crate::foreign::to_clause::ToClause; -use crate::gen::traits::Generable; -use crate::interpreter::nort::{Clause, ClauseInst, Expr}; -use crate::location::CodeLocation; -use crate::name::Sym; - -/// Context data for instantiating templated expressions as [super::nort]. -/// Instances of this type are created via [nort_gen] -pub type NortGenCtx<'a> = (CodeLocation, NortBuilder<'a, str, str>); - -/// Create [NortGenCtx] instances to generate interpreted expressions -pub fn nort_gen<'a>(location: CodeLocation) -> NortGenCtx<'a> { - (location, NortBuilder::new(&|l| Box::new(move |r| l == r))) -} - -impl Generable for Expr { - type Ctx<'a> = NortGenCtx<'a>; - fn apply( - ctx: Self::Ctx<'_>, - f_cb: impl FnOnce(Self::Ctx<'_>) -> Self, - x_cb: impl FnOnce(Self::Ctx<'_>) -> Self, - ) -> Self { - (ctx.1.apply_logic(|c| f_cb((ctx.0.clone(), c)), |c| x_cb((ctx.0.clone(), c)))) - .into_expr(ctx.0.clone()) - } - fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { - Clause::arg(ctx.clone(), name).into_expr(ctx.0.clone()) - } - fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self { - Clause::atom(ctx.clone(), a).into_expr(ctx.0.clone()) - } - fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator) -> Self { - Clause::constant(ctx.clone(), name).into_expr(ctx.0.clone()) - } - fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self { - (ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)))).into_expr(ctx.0.clone()) - } -} - -impl Generable for ClauseInst { - type Ctx<'a> = NortGenCtx<'a>; - fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { Clause::arg(ctx, name).into_inst() } - fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self { Clause::atom(ctx, a).into_inst() } - fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator) -> Self { - Clause::constant(ctx, name).into_inst() - } - fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self { - (ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)).into_expr(ctx.0.clone()))) - .to_clsi(ctx.0.clone()) - } - fn apply( - ctx: Self::Ctx<'_>, - f: impl FnOnce(Self::Ctx<'_>) -> Self, - x: impl FnOnce(Self::Ctx<'_>) -> Self, - ) -> Self { - (ctx.1.apply_logic( - |c| f((ctx.0.clone(), c)).into_expr(ctx.0.clone()), - |c| x((ctx.0.clone(), c)).into_expr(ctx.0.clone()), - )) - .to_clsi(ctx.0.clone()) - } -} - -impl Generable for Clause { - type Ctx<'a> = NortGenCtx<'a>; - fn atom(_: Self::Ctx<'_>, a: Atom) -> Self { Clause::Atom(a) } - fn constant<'a>(_: Self::Ctx<'_>, name: impl IntoIterator) -> Self { - let sym = Sym::new(name.into_iter().map(i)).expect("Empty constant"); - Clause::Constant(sym) - } - fn apply( - ctx: Self::Ctx<'_>, - f: impl FnOnce(Self::Ctx<'_>) -> Self, - x: impl FnOnce(Self::Ctx<'_>) -> Self, - ) -> Self { - ctx.1.apply_logic( - |c| f((ctx.0.clone(), c)).into_expr(ctx.0.clone()), - |c| x((ctx.0.clone(), c)).into_expr(ctx.0.clone()), - ) - } - fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { - ctx.1.arg_logic(name); - Clause::LambdaArg - } - fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self { - ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)).into_expr(ctx.0.clone())) - } -} diff --git a/orchidlang/src/interpreter/mod.rs b/orchidlang/src/interpreter/mod.rs deleted file mode 100644 index 6993cf8..0000000 --- a/orchidlang/src/interpreter/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! functions to execute Orchid code -mod apply; -pub mod context; -pub mod error; -pub mod gen_nort; -pub mod handler; -pub mod nort; -pub mod nort_builder; -pub(crate) mod path_set; -pub mod run; diff --git a/orchidlang/src/interpreter/nort_builder.rs b/orchidlang/src/interpreter/nort_builder.rs deleted file mode 100644 index 04427e6..0000000 --- a/orchidlang/src/interpreter/nort_builder.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Helper for generating the interpreter's internal representation - -use std::cell::RefCell; -use std::mem; - -use substack::Substack; - -use super::nort::{AsDerefMut, Clause, Expr}; -use super::path_set::PathSet; -use crate::utils::pure_seq::pushed; - -enum IntGenData<'a, T: ?Sized> { - Lambda(&'a T, &'a RefCell>), - /// Counts left steps within a chain of [Clause::Apply] for collapsing. - Apply(&'a RefCell), - /// Replaces [IntGenData::Apply] when stepping left into non-apply to record - /// a [None] [super::path_set::Step]. - AppF, - /// Replaces [IntGenData::Apply] when stepping right to freeze the value. - AppArg(usize), -} - -impl<'a, T: ?Sized> Copy for IntGenData<'a, T> {} -impl<'a, T: ?Sized> Clone for IntGenData<'a, T> { - fn clone(&self) -> Self { *self } -} - -struct ArgCollector(RefCell>); -impl ArgCollector { - pub fn new() -> Self { Self(RefCell::new(None)) } - pub fn into_path(self) -> Option { self.0.into_inner() } -} - -/// Strategy used to find the lambda corresponding to a given argument in the -/// stack. The function is called on the data associated with the argument, then -/// the callback it returns is called on every lambda ancestor's associated -/// data from closest to outermost ancestor. The first lambda where this -/// callback returns true is considered to own the argument. -pub type LambdaPicker<'a, T, U> = &'a dyn for<'b> Fn(&'b U) -> Box bool + 'b>; - -/// Bundle of information passed down through recursive fnuctions to instantiate -/// runtime [Expr], [super::nort::ClauseInst] or [Clause]. -/// -/// The context used by [crate::gen::traits::Gen] to convert templates is which -/// includes this type is constructed with [super::gen_nort::nort_gen]. -pub struct NortBuilder<'a, T: ?Sized, U: ?Sized> { - stack: Substack<'a, IntGenData<'a, T>>, - lambda_picker: LambdaPicker<'a, T, U>, -} -impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> { - /// Create a new recursive [super::nort] builder from a location that will be - pub fn new(lambda_picker: LambdaPicker<'a, T, U>) -> Self { - Self { stack: Substack::Bottom, lambda_picker } - } - /// [Substack::pop] and clone the location - fn pop<'b>(&'b self, count: usize) -> NortBuilder<'b, T, U> - where 'a: 'b { - let mut new = *self; - new.stack = *self.stack.pop(count); - new - } - /// [Substack::push] and clone the location - fn push<'b>(&'b self, data: IntGenData<'a, T>) -> NortBuilder<'b, T, U> - where 'a: 'b { - let mut new = *self; - new.stack = self.stack.push(data); - new - } - fn non_app_step(self, f: impl FnOnce(NortBuilder) -> V) -> V { - if let Some(IntGenData::Apply(_)) = self.stack.value() { - f(self.pop(1).push(IntGenData::AppF)) - } else { - f(self) - } - } - - /// Climb back through the stack and find a lambda associated with this - /// argument, then record the path taken from the lambda to this argument in - /// the lambda's mutable cell. - pub fn arg_logic(self, name: &'a U) { - let mut lambda_chk = (self.lambda_picker)(name); - self.non_app_step(|ctx| { - let res = ctx.stack.iter().try_fold(vec![], |path, item| match item { - IntGenData::Apply(_) => panic!("This is removed after handling"), - IntGenData::Lambda(n, rc) => match lambda_chk(n) { - false => Ok(path), - true => Err((path, *rc)), - }, - IntGenData::AppArg(n) => Ok(pushed(path, Some(*n))), - IntGenData::AppF => Ok(pushed(path, None)), - }); - let (mut path, slot) = res.expect_err("Argument not wrapped in matching lambda"); - path.reverse(); - match &mut *slot.borrow_mut() { - slot @ None => *slot = Some(PathSet::end(path)), - Some(slot) => take_mut::take(slot, |p| p.overlay(PathSet::end(path))), - } - }) - } - - /// Push a stackframe corresponding to a lambda expression, build the body, - /// then record the path set collected by [NortBuilder::arg_logic] calls - /// within the body. - pub fn lambda_logic(self, name: &T, body: impl FnOnce(NortBuilder) -> Expr) -> Clause { - let coll = ArgCollector::new(); - let frame = IntGenData::Lambda(name, &coll.0); - let body = self.non_app_step(|ctx| body(ctx.push(frame))); - let args = coll.into_path(); - Clause::Lambda { args, body } - } - - /// Logic for collapsing Apply clauses. Different steps of the logic - /// communicate via mutable variables on the stack - pub fn apply_logic( - self, - f: impl FnOnce(NortBuilder) -> Expr, - x: impl FnOnce(NortBuilder) -> Expr, - ) -> Clause { - let mut fun: Expr; - let arg: Expr; - if let Some(IntGenData::Apply(rc)) = self.stack.value() { - // argument side commits backidx - arg = x(self.pop(1).push(IntGenData::AppArg(*rc.borrow()))); - // function side increments backidx - *rc.borrow_mut() += 1; - fun = f(self); - } else { - // function side starts from backidx 1 - fun = f(self.push(IntGenData::Apply(&RefCell::new(1)))); - // argument side commits 0 - arg = x(self.push(IntGenData::AppArg(0))); - }; - let mut cls_lk = fun.as_deref_mut(); - if let Clause::Apply { x, f: _ } = &mut *cls_lk { - x.push_back(arg); - mem::drop(cls_lk); - fun.clause.into_cls() - } else { - mem::drop(cls_lk); - Clause::Apply { f: fun, x: [arg].into() } - } - } -} - -impl<'a, T: ?Sized, U: ?Sized> Copy for NortBuilder<'a, T, U> {} -impl<'a, T: ?Sized, U: ?Sized> Clone for NortBuilder<'a, T, U> { - fn clone(&self) -> Self { *self } -} diff --git a/orchidlang/src/interpreter/path_set.rs b/orchidlang/src/interpreter/path_set.rs deleted file mode 100644 index 43c1afd..0000000 --- a/orchidlang/src/interpreter/path_set.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::collections::VecDeque; -use std::fmt; - -use hashbrown::HashMap; -use itertools::Itertools; - -use crate::utils::join::join_maps; - -/// A step into a [super::nort::Clause::Apply]. If [None], it steps to the -/// function. If [Some(n)], it steps to the `n`th _last_ argument. -pub type Step = Option; -fn print_step(step: Step) -> String { - if let Some(n) = step { format!("{n}") } else { "f".to_string() } -} - -/// A branching path selecting some placeholders (but at least one) in a Lambda -/// expression -#[derive(Clone)] -pub struct PathSet { - /// The single steps through [super::nort::Clause::Apply] - pub steps: VecDeque, - /// if Some, it splits at a [super::nort::Clause::Apply]. If None, it ends in - /// a [super::nort::Clause::LambdaArg] - pub next: Option>, -} - -impl PathSet { - /// Create a path set for more than one target - pub fn branch( - steps: impl IntoIterator, - conts: impl IntoIterator, - ) -> Self { - let conts = conts.into_iter().collect::>(); - assert!(1 < conts.len(), "Branching pathsets need multiple continuations"); - Self { steps: steps.into_iter().collect(), next: Some(conts) } - } - - /// Create a path set for one target - pub fn end(steps: impl IntoIterator) -> Self { - Self { steps: steps.into_iter().collect(), next: None } - } - - /// Create a path set that points to a slot that is a direct - /// child of the given lambda with no applications. In essence, this means - /// that this argument will be picked as the value of the expression after an - /// arbitrary amount of subsequent discarded parameters. - pub fn pick() -> Self { Self { steps: VecDeque::new(), next: None } } - - /// Merge two paths into one path that points to all targets of both. Only - /// works if both paths select leaf nodes of the same partial tree. - /// - /// # Panics - /// - /// if either path selects a node the other path dissects - pub fn overlay(self, other: Self) -> Self { - let (mut short, mut long) = match self.steps.len() < other.steps.len() { - true => (self, other), - false => (other, self), - }; - let short_len = short.steps.len(); - let long_len = long.steps.len(); - let match_len = (short.steps.iter()).zip(long.steps.iter()).take_while(|(a, b)| a == b).count(); - // fact: match_len <= short_len <= long_len - if short_len == match_len && match_len == long_len { - // implies match_len == short_len == long_len - match (short.next, long.next) { - (None, None) => Self::end(short.steps.iter().cloned()), - (Some(_), None) | (None, Some(_)) => { - panic!("One of these paths is faulty") - }, - (Some(s), Some(l)) => - Self::branch(short.steps.iter().cloned(), join_maps(s, l, |_, l, r| l.overlay(r))), - } - } else if short_len == match_len { - // implies match_len == short_len < long_len - // long.steps[0..match_len] is in steps - // long.steps[match_len] becomes the choice of branch below - // long.steps[match_len + 1..] is in tail - let mut conts = short.next.expect("One path ends inside the other"); - let tail_steps = long.steps.split_off(match_len + 1); - let tail = match long.next { - Some(n) => Self::branch(tail_steps, n), - None => Self::end(tail_steps), - }; - let branch = long.steps[match_len]; - let prev_c = conts.remove(&branch); - let new_c = if let Some(x) = prev_c { x.overlay(tail) } else { tail }; - conts.insert(branch, new_c); - Self::branch(short.steps, conts) - } else { - // implies match_len < short_len <= long_len - // steps[0..match_len] is in shared - // steps[match_len] become the branches below - // steps[match_len + 1..] is in new_long and new_short - let new_short_steps = short.steps.split_off(match_len + 1); - let short_last = short.steps.pop_back().expect("split at n + 1"); - let new_short = Self { next: short.next.clone(), steps: new_short_steps }; - let new_long_steps = long.steps.split_off(match_len + 1); - let new_long = Self { next: long.next.clone(), steps: new_long_steps }; - Self::branch(short.steps, [(short_last, new_short), (long.steps[match_len], new_long)]) - } - } - - /// Prepend a step to a path. If it had previously started at a node that is - /// at the specified step within an Apply clause, it now starts at the Apply. - /// - /// This is only valid if the new Apply is **separate** from the previous - /// root. - pub fn prepend(&mut self, step: Step) { self.steps.push_front(step); } -} - -impl fmt::Display for PathSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let step_s = self.steps.iter().copied().map(print_step).join(">"); - match &self.next { - Some(conts) => { - let opts = (conts.iter()) - .sorted_unstable_by_key(|(k, _)| k.map_or(0, |n| n + 1)) - .map(|(h, t)| format!("{}>{t}", print_step(*h))) - .join("|"); - if !step_s.is_empty() { - write!(f, "{step_s}>")?; - } - write!(f, "({opts})") - }, - None => write!(f, "{step_s}"), - } - } -} - -impl fmt::Debug for PathSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PathSet({self})") } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_combine() { - let ps1 = PathSet { next: None, steps: VecDeque::from([Some(2), None]) }; - let ps2 = PathSet { next: None, steps: VecDeque::from([Some(3), Some(1)]) }; - let sum = ps1.clone().overlay(ps2.clone()); - assert_eq!(format!("{sum}"), "(2>f|3>1)"); - } - - fn extend_scaffold() -> PathSet { - PathSet::branch([None, Some(1), None], [ - (None, PathSet::end([None, Some(1)])), - (Some(1), PathSet::end([None, Some(2)])), - ]) - } - - #[test] - fn test_extend_noclone() { - let mut ps = extend_scaffold(); - ps.prepend(Some(0)); - assert_eq!(format!("{ps}"), "0>f>1>f>(f>f>1|1>f>2)"); - } -} diff --git a/orchidlang/src/interpreter/run.rs b/orchidlang/src/interpreter/run.rs deleted file mode 100644 index 0966514..0000000 --- a/orchidlang/src/interpreter/run.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! Executes Orchid code - -use std::ops::{Deref, DerefMut}; -use std::sync::{MutexGuard, TryLockError}; -use std::{fmt, mem}; - -use bound::Bound; -use itertools::Itertools; - -use super::context::{Halt, RunEnv, RunParams}; -use super::error::RunError; -use super::nort::{Clause, Expr}; -use crate::foreign::atom::{AtomicReturn, RunData}; -use crate::foreign::error::{RTError, RTErrorObj}; -use crate::interpreter::apply::{apply_as_atom, substitute}; -use crate::location::CodeLocation; -use crate::utils::take_with_output::take_with_output; - -#[derive(Debug)] -struct Stackframe { - expr: Expr, - cls: Bound, Expr>, -} -impl Stackframe { - pub fn new(expr: Expr) -> Option { - match Bound::try_new(expr.clone(), |e| e.clause.0.try_lock()) { - Ok(cls) => Some(Stackframe { cls, expr }), - Err(bound_e) if matches!(bound_e.wrapped(), TryLockError::WouldBlock) => None, - Err(bound_e) => panic!("{:?}", bound_e.wrapped()), - } - } - pub fn wait_new(expr: Expr) -> Self { - Self { cls: Bound::new(expr.clone(), |e| e.clause.0.lock().unwrap()), expr } - } - pub fn record_cycle(&mut self) -> RTErrorObj { - let err = CyclicalExpression(self.expr.clone()).pack(); - *self.cls = Clause::Bottom(err.clone()); - err - } -} -impl Deref for Stackframe { - type Target = Clause; - fn deref(&self) -> &Self::Target { &self.cls } -} -impl DerefMut for Stackframe { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cls } -} -impl fmt::Display for Stackframe { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}\n at {}", *self.cls, self.expr.location) - } -} - -/// Interpreter state when processing was interrupted -pub struct State<'a> { - stack: Vec, - popped: Option, - env: &'a RunEnv<'a>, -} -impl<'a> State<'a> { - /// Create a new trivial state with a specified stack size and a single - /// element on the stack - fn new(base: Expr, env: &'a RunEnv<'a>) -> Self { - let stack = vec![Stackframe::new(base).expect("Initial state should not be locked")]; - State { stack, popped: None, env } - } - - /// Try to push an expression on the stack, raise appropriate errors if the - /// expression is already on the stack (and thus references itself), or if the - /// stack now exceeds the pre-defined height - fn push_expr(&'_ mut self, expr: Expr, params: &RunParams) -> Result<(), RunError<'a>> { - let sf = match Stackframe::new(expr.clone()) { - Some(sf) => sf, - None => match self.stack.iter_mut().rev().find(|sf| sf.expr.clause.is_same(&expr.clause)) { - None => Stackframe::wait_new(expr), - Some(sf) => return Err(RunError::Extern(sf.record_cycle())), - }, - }; - self.stack.push(sf); - if params.stack < self.stack.len() { - let so = StackOverflow(self.stack.iter().map(|sf| sf.expr.clone()).collect()); - return Err(RunError::Extern(so.pack())); - } - Ok(()) - } - - /// Process this state until it either completes, runs out of gas as specified - /// in the context, or produces an error. - pub fn run(mut self, params: &mut RunParams) -> Result> { - loop { - if params.no_gas() { - return Err(RunError::Interrupted(self)); - } - params.use_gas(1); - let top = self.stack.last_mut().expect("Stack never empty"); - let location = top.expr.location(); - let op = take_with_output(&mut *top.cls, |c| { - match step(c, self.popped, location, self.env, params) { - Err(e) => (Clause::Bottom(e.clone()), Err(RunError::Extern(e))), - Ok((cls, cmd)) => (cls, Ok(cmd)), - } - })?; - self.popped = None; - match op { - StackOp::Nop => continue, - StackOp::Push(ex) => self.push_expr(ex, params)?, - StackOp::Swap(ex) => { - self.stack.pop().expect("Stack never empty"); - self.push_expr(ex, params)? - }, - StackOp::Pop => { - let ret = self.stack.pop().expect("last_mut called above"); - if self.stack.is_empty() { - if let Some(alt) = self.env.dispatch(&ret.cls, ret.expr.location()) { - self.push_expr(alt, params)?; - params.use_gas(1); - continue; - } - return Ok(ret.expr); - } else { - self.popped = Some(ret.expr); - } - }, - } - } - } -} -impl<'a> fmt::Display for State<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.stack.iter().rev().join("\n")) - } -} -impl<'a> fmt::Debug for State<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "State({self})") } -} - -/// Process an expression with specific resource limits -pub fn run<'a>( - base: Expr, - env: &'a RunEnv<'a>, - params: &mut RunParams, -) -> Result> { - State::new(base, env).run(params) -} - -enum StackOp { - Pop, - Nop, - Swap(Expr), - Push(Expr), -} - -fn step( - top: Clause, - popped: Option, - location: CodeLocation, - env: &RunEnv, - params: &mut RunParams, -) -> Result<(Clause, StackOp), RTErrorObj> { - match top { - Clause::Bottom(err) => Err(err), - Clause::LambdaArg => Ok((Clause::Bottom(UnboundArg(location).pack()), StackOp::Nop)), - l @ Clause::Lambda { .. } => Ok((l, StackOp::Pop)), - Clause::Identity(other) => - Ok((Clause::Identity(other.clone()), StackOp::Swap(other.into_expr(location)))), - Clause::Constant(name) => { - let expr = env.load(name, location)?; - Ok((Clause::Identity(expr.clsi()), StackOp::Swap(expr.clone()))) - }, - Clause::Atom(mut at) => { - if let Some(delegate) = at.0.redirect() { - match popped { - Some(popped) => *delegate = popped, - None => { - let tmp = delegate.clone(); - return Ok((Clause::Atom(at), StackOp::Push(tmp))); - }, - } - } - match at.run(RunData { params, env, location })? { - AtomicReturn::Inert(at) => Ok((Clause::Atom(at), StackOp::Pop)), - AtomicReturn::Change(gas, c) => { - params.use_gas(gas); - Ok((c, StackOp::Nop)) - }, - } - }, - Clause::Apply { mut f, mut x } => { - if x.is_empty() { - return Ok((Clause::Identity(f.clsi()), StackOp::Swap(f))); - } - f = match popped { - None => return Ok((Clause::Apply { f: f.clone(), x }, StackOp::Push(f))), - Some(ex) => ex, - }; - let val = x.pop_front().expect("Empty args handled above"); - let f_mut = f.clause.cls_mut(); - let mut cls = match &*f_mut { - Clause::Lambda { args, body } => match args { - None => Clause::Identity(body.clsi()), - Some(args) => substitute(args, val.clsi(), &body.cls_mut(), &mut || params.use_gas(1)), - }, - Clause::Atom(_) => { - mem::drop(f_mut); - apply_as_atom(f, val, env, params)? - }, - c => unreachable!("Run should never settle on {c}"), - }; - if !x.is_empty() { - cls = Clause::Apply { f: cls.into_expr(location), x }; - } - Ok((cls, StackOp::Nop)) - }, - } -} - -#[derive(Clone)] -pub(crate) struct StackOverflow(Vec); -impl RTError for StackOverflow {} -impl fmt::Display for StackOverflow { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Stack depth exceeded {}:", self.0.len() - 1)?; // 1 for failed call, 1 for current - for item in self.0.iter().rev() { - match Stackframe::new(item.clone()) { - Some(sf) => writeln!(f, "{sf}")?, - None => writeln!(f, "Locked frame at {}", item.location)?, - } - } - Ok(()) - } -} - -#[derive(Clone)] -pub(crate) struct UnboundArg(CodeLocation); -impl RTError for UnboundArg {} -impl fmt::Display for UnboundArg { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Unbound argument found at {}. This is likely a codegen error", self.0) - } -} - -#[derive(Clone)] -pub(crate) struct CyclicalExpression(Expr); -impl RTError for CyclicalExpression {} -impl fmt::Display for CyclicalExpression { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "The expression {} contains itself", self.0) - } -} diff --git a/orchidlang/src/lib.rs b/orchidlang/src/lib.rs deleted file mode 100644 index 3037d07..0000000 --- a/orchidlang/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![warn(missing_docs)] -#![warn(unit_bindings)] -#![warn(clippy::unnecessary_wraps)] -#![doc(html_logo_url = "https://raw.githubusercontent.com/lbfalvy/orchid/master/icon.svg")] -#![doc(html_favicon_url = "https://raw.githubusercontent.com/lbfalvy/orchid/master/icon.svg")] -//! Orchid is a lazy, pure scripting language to be embedded in Rust -//! applications. Check out the repo for examples and other links. -pub mod error; -pub mod facade; -pub mod foreign; -pub mod gen; -pub mod intermediate; -pub mod interpreter; -pub mod libs; -pub mod location; -pub mod name; -pub mod parse; -pub mod pipeline; -pub mod rule; -pub mod tree; -pub mod utils; -pub mod virt_fs; diff --git a/orchidlang/src/libs/asynch/async.orc b/orchidlang/src/libs/asynch/async.orc deleted file mode 100644 index 4479c22..0000000 --- a/orchidlang/src/libs/asynch/async.orc +++ /dev/null @@ -1,7 +0,0 @@ -import std::panic - -export const block_on := \action. \cont. ( - action cont - (\e.panic "unwrapped asynch call") - \c.yield -) diff --git a/orchidlang/src/libs/asynch/delete_cell.rs b/orchidlang/src/libs/asynch/delete_cell.rs deleted file mode 100644 index 8c3effe..0000000 --- a/orchidlang/src/libs/asynch/delete_cell.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::sync::{Arc, Mutex}; - -pub struct DeleteCell(pub Arc>>); -impl DeleteCell { - pub fn new(t: T) -> Self { Self(Arc::new(Mutex::new(Some(t)))) } - pub fn take(&self) -> Option { self.0.lock().unwrap().take() } -} -impl DeleteCell { - pub fn clone_out(&self) -> Option { self.0.lock().unwrap().clone() } -} -impl Clone for DeleteCell { - fn clone(&self) -> Self { Self(self.0.clone()) } -} diff --git a/orchidlang/src/libs/asynch/mod.rs b/orchidlang/src/libs/asynch/mod.rs deleted file mode 100644 index 3c3e5ab..0000000 --- a/orchidlang/src/libs/asynch/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! An event queue other systems can use to trigger events on the main -//! interpreter thread. These events are handled when the Orchid code returns -//! `system::async::yield`, and may cause additional Orchid code to be executed -//! beyond being general Rust functions. -//! It also exposes timers. - -mod delete_cell; -pub mod poller; -pub mod system; diff --git a/orchidlang/src/libs/asynch/poller.rs b/orchidlang/src/libs/asynch/poller.rs deleted file mode 100644 index c9eb7c2..0000000 --- a/orchidlang/src/libs/asynch/poller.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! Abstract implementation of the poller - -use std::collections::BinaryHeap; -use std::mem; -use std::sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender}; -use std::thread::sleep; -use std::time::{Duration, Instant}; - -use super::delete_cell::DeleteCell; - -enum TimerKind { - Once(DeleteCell), - Recurring { period: Duration, data_cell: DeleteCell }, -} -impl Clone for TimerKind { - fn clone(&self) -> Self { - match self { - Self::Once(c) => Self::Once(c.clone()), - Self::Recurring { period, data_cell: data } => - Self::Recurring { period: *period, data_cell: data.clone() }, - } - } -} - -/// Indicates a bit of code which is to be executed at a -/// specific point in time -/// -/// In order to work with Rust's builtin [BinaryHeap] which is a max heap, the -/// [Ord] implemenetation of this struct is reversed; it can be intuitively -/// thought of as ordering by urgency. -struct Timer { - expires: Instant, - kind: TimerKind, -} -impl Clone for Timer { - fn clone(&self) -> Self { Self { expires: self.expires, kind: self.kind.clone() } } -} -impl Eq for Timer {} -impl PartialEq for Timer { - fn eq(&self, other: &Self) -> bool { self.expires.eq(&other.expires) } -} -impl PartialOrd for Timer { - fn partial_cmp(&self, other: &Self) -> Option { Some(other.cmp(self)) } -} -impl Ord for Timer { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { other.expires.cmp(&self.expires) } -} - -/// Representation of a scheduled timer -#[derive(Clone)] -pub struct TimerHandle(DeleteCell); -impl TimerHandle { - /// Cancel the timer - pub fn cancel(self) { mem::drop(self.0.take()) } -} - -/// The abstract event poller implementation used by the standard asynch -/// subsystem. -pub struct Poller { - timers: BinaryHeap>, - receiver: Receiver, -} - -impl Poller { - /// Create an event poller and a [Sender] that can produce events on it. - pub fn new() -> (Sender, Self) { - let (sender, receiver) = channel(); - let this = Self { receiver, timers: BinaryHeap::new() }; - (sender, this) - } - - /// Set a single-fire timer - pub fn set_timeout(&mut self, duration: Duration, data: TOnce) -> TimerHandle { - let data_cell = DeleteCell::new(data); - self - .timers - .push(Timer { kind: TimerKind::Once(data_cell.clone()), expires: Instant::now() + duration }); - TimerHandle(data_cell) - } - - /// Set a recurring timer - pub fn set_interval(&mut self, period: Duration, data: TRec) -> TimerHandle { - let data_cell = DeleteCell::new(data); - self.timers.push(Timer { - expires: Instant::now() + period, - kind: TimerKind::Recurring { period, data_cell: data_cell.clone() }, - }); - TimerHandle(data_cell) - } - - /// Process a timer popped from the timers heap of this event loop. - /// This function returns [None] if the timer had been cancelled. **This - /// behaviour is different from [EventLoop::run] which is returns None if - /// the event loop is empty, even though the types are compatible.** - fn process_next_timer( - &mut self, - Timer { expires, kind }: Timer, - ) -> Option> { - Some(match kind { - TimerKind::Once(data) => PollEvent::Once(data.take()?), - TimerKind::Recurring { period, data_cell } => { - let data = data_cell.clone_out()?; - self.timers.push(Timer { - expires: expires + period, - kind: TimerKind::Recurring { period, data_cell }, - }); - PollEvent::Recurring(data) - }, - }) - } - - /// Block until a message is received or the first timer expires - pub fn run(&mut self) -> Option> { - loop { - if let Some(expires) = self.timers.peek().map(|t| t.expires) { - return match self.receiver.recv_timeout(expires - Instant::now()) { - Ok(t) => Some(PollEvent::Event(t)), - Err(e) => { - if e == RecvTimeoutError::Disconnected { - // The receiver is now inert, but the timer must finish - sleep(expires - Instant::now()); - } - // pop and process the timer we've been waiting on - let timer = self.timers.pop().expect("checked before wait"); - let result = self.process_next_timer(timer); - // if the timer had been cancelled, repeat - if result.is_none() { - continue; - } - result - }, - }; - } else { - return match self.receiver.recv() { - Ok(t) => Some(PollEvent::Event(t)), - Err(RecvError) => None, - }; - } - } - } -} - -/// Events produced by [Poller]. -pub enum PollEvent { - /// An event was sent to the [Sender] associated with the [Poller]. - Event(TEv), - /// A single-fire timer expired - Once(TOnce), - /// A recurring event fired - Recurring(TRec), -} diff --git a/orchidlang/src/libs/asynch/system.rs b/orchidlang/src/libs/asynch/system.rs deleted file mode 100644 index eec26b3..0000000 --- a/orchidlang/src/libs/asynch/system.rs +++ /dev/null @@ -1,210 +0,0 @@ -//! Object to pass to [crate::facade::loader::Loader::add_system] to enable the -//! I/O subsystem. Also many other systems depend on it, these take a mut ref to -//! register themselves. - -use std::any::{type_name, Any, TypeId}; -use std::cell::RefCell; -use std::collections::VecDeque; -use std::fmt; -use std::rc::Rc; -use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -use hashbrown::HashMap; -use ordered_float::NotNan; -use rust_embed::RustEmbed; - -use super::poller::{PollEvent, Poller, TimerHandle}; -use crate::facade::system::{IntoSystem, System}; -use crate::foreign::atom::Atomic; -use crate::foreign::cps_box::CPSBox; -use crate::foreign::error::RTError; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::handler::HandlerTable; -use crate::interpreter::nort::Expr; -use crate::libs::std::number::Numeric; -use crate::location::{CodeGenInfo, CodeLocation}; -use crate::sym; -use crate::utils::unwrap_or::unwrap_or; -use crate::virt_fs::{DeclTree, EmbeddedFS, PrefixFS, VirtFS}; - -#[derive(Debug, Clone)] -struct Timer { - recurring: bool, - delay: NotNan, -} - -fn set_timer(rec: Inert, delay: Numeric) -> CPSBox { - CPSBox::new(2, Timer { recurring: rec.0, delay: delay.as_float() }) -} - -#[derive(Clone)] -struct CancelTimer(Arc>); -impl CancelTimer { - pub fn new(canceller: TimerHandle) -> Self { - Self(Arc::new(Mutex::new(move || canceller.clone().cancel()))) - } - pub fn cancel(&self) { self.0.lock().unwrap()() } -} -impl fmt::Debug for CancelTimer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("CancelTimer").finish_non_exhaustive() - } -} - -#[derive(Clone, Debug)] -struct Yield; -impl InertPayload for Yield { - const TYPE_STR: &'static str = "asynch::yield"; -} - -/// Error indicating a yield command when all event producers and timers had -/// exited -#[derive(Clone)] -pub struct InfiniteBlock; -impl RTError for InfiniteBlock {} -impl fmt::Display for InfiniteBlock { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - static MSG: &str = "User code yielded, but there are no timers or event \ - producers to wake it up in the future"; - write!(f, "{}", MSG) - } -} - -/// A thread-safe handle that can be used to send events of any type -#[derive(Clone)] -pub struct MessagePort(Sender>); -impl MessagePort { - /// Send an event. Any type is accepted, handlers are dispatched by type ID - pub fn send(&mut self, message: T) { let _ = self.0.send(Box::new(message)); } -} - -fn gen() -> CodeGenInfo { CodeGenInfo::no_details(sym!(asynch)) } - -#[derive(RustEmbed)] -#[folder = "src/libs/asynch"] -#[include = "*.orc"] -struct AsynchEmbed; - -fn code() -> DeclTree { - DeclTree::ns("system::async", [DeclTree::leaf( - PrefixFS::new(EmbeddedFS::new::(".orc", gen()), "", "async").rc(), - )]) -} - -type AnyHandler<'a> = Box) -> Vec + 'a>; - -/// Datastructures the asynch system will eventually be constructed from. -pub struct AsynchSystem<'a> { - poller: Poller, Expr, Expr>, - sender: Sender>, - handlers: HashMap>, -} - -impl<'a> AsynchSystem<'a> { - /// Create a new async event loop that allows registering handlers and taking - /// references to the port before it's converted into a [System] - #[must_use] - pub fn new() -> Self { - let (sender, poller) = Poller::new(); - Self { poller, sender, handlers: HashMap::new() } - } - - /// Register a callback to be called on the owning thread when an object of - /// the given type is found on the queue. Each type should signify a single - /// command so each type should have exactly one handler. - /// - /// # Panics - /// - /// if the given type is already handled. - pub fn register(&mut self, mut f: impl FnMut(Box) -> Vec + 'a) { - let cb = move |a: Box| f(a.downcast().expect("keyed by TypeId")); - let prev = self.handlers.insert(TypeId::of::(), Box::new(cb)); - assert!(prev.is_none(), "Duplicate handlers for async event {}", type_name::()) - } - - /// Obtain a message port for sending messages to the main thread. If an - /// object is passed to the MessagePort that does not have a handler, the - /// main thread panics. - #[must_use] - pub fn get_port(&self) -> MessagePort { MessagePort(self.sender.clone()) } -} - -impl<'a> Default for AsynchSystem<'a> { - fn default() -> Self { Self::new() } -} - -impl<'a> IntoSystem<'a> for AsynchSystem<'a> { - fn into_system(self) -> System<'a> { - let Self { mut handlers, poller, .. } = self; - let mut handler_table = HandlerTable::new(); - let polly = Rc::new(RefCell::new(poller)); - handler_table.register({ - let polly = polly.clone(); - move |t: &CPSBox| { - let mut polly = polly.borrow_mut(); - let (Timer { delay, recurring }, action, cont) = t.unpack2(); - let duration = Duration::from_secs_f64(**delay); - let cancel_timer = match *recurring { - true => CancelTimer::new(polly.set_interval(duration, action)), - false => CancelTimer::new(polly.set_timeout(duration, action)), - }; - let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel_timer))); - tpl.template(nort_gen(cont.location()), [cont]) - } - }); - handler_table.register(move |t: &CPSBox| { - let (command, cont) = t.unpack1(); - command.cancel(); - cont - }); - handler_table.register({ - let polly = polly.clone(); - let mut microtasks = VecDeque::new(); - move |_: &Inert| { - if let Some(expr) = microtasks.pop_front() { - return Ok(expr); - } - let mut polly = polly.borrow_mut(); - loop { - let next = unwrap_or!(polly.run(); - return Err(InfiniteBlock.pack()) - ); - match next { - PollEvent::Once(expr) => return Ok(expr), - PollEvent::Recurring(expr) => return Ok(expr), - PollEvent::Event(ev) => { - let handler = (handlers.get_mut(&ev.as_ref().type_id())) - .unwrap_or_else(|| panic!("Unhandled messgae type: {:?}", (*ev).type_id())); - let events = handler(ev); - // we got new microtasks - if !events.is_empty() { - microtasks = VecDeque::from(events); - // trampoline - let loc = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(system::asynch))); - return Ok(Inert(Yield).atom_expr(loc)); - } - }, - } - } - } - }); - System { - name: "system::asynch", - lexer_plugins: vec![], - line_parsers: vec![], - constants: ConstTree::ns("system::async", [ConstTree::tree([ - xfn_ent("set_timer", [set_timer]), - atom_ent("yield", [Inert(Yield)]), - ])]), - code: code(), - prelude: Vec::new(), - handlers: handler_table, - } - } -} diff --git a/orchidlang/src/libs/directfs/commands.rs b/orchidlang/src/libs/directfs/commands.rs deleted file mode 100644 index 2865f0b..0000000 --- a/orchidlang/src/libs/directfs/commands.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::ffi::OsString; -use std::fs::File; -use std::path::{Path, PathBuf}; - -use super::osstring::os_string_lib; -use crate::facade::system::{IntoSystem, System}; -use crate::foreign::atom::Atomic; -use crate::foreign::cps_box::CPSBox; -use crate::foreign::error::RTResult; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::foreign::process::Unstable; -use crate::foreign::to_clause::ToClause; -use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::handler::HandlerTable; -use crate::interpreter::nort::{Clause, Expr}; -use crate::libs::io::instances::io_error_handler; -use crate::libs::io::{Sink, Source}; -use crate::libs::scheduler::system::{SeqScheduler, SharedHandle}; -use crate::libs::std::runtime_error::RuntimeError; -use crate::utils::combine::Combine; -use crate::virt_fs::DeclTree; - -#[derive(Debug, Clone)] -struct ReadFileCmd(OsString); -impl InertPayload for ReadFileCmd { - const TYPE_STR: &'static str = "readfile command"; -} - -#[derive(Debug, Clone)] -struct ReadDirCmd(OsString); -impl InertPayload for ReadDirCmd { - const TYPE_STR: &'static str = "readdir command"; -} - -#[derive(Debug, Clone)] -struct WriteFile { - name: OsString, - append: bool, -} -impl InertPayload for WriteFile { - const TYPE_STR: &'static str = "writefile command"; -} - -#[must_use] -fn read_file(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { - let (ReadFileCmd(name), succ, fail, cont) = cmd.unpack3(); - let name = name.clone(); - let cancel = sched.run_orphan( - move |_| File::open(name), - |file, _| match file { - Err(e) => vec![io_error_handler(e, fail)], - Ok(f) => { - let source_handle = SharedHandle::wrap(Source::new(Box::new(f))); - let tpl = tpl::A(tpl::Slot, tpl::V(Inert(source_handle))); - vec![tpl.template(nort_gen(succ.location()), [succ])] - }, - }, - ); - let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))); - tpl.template(nort_gen(cont.location()), [cont]) -} - -#[must_use] -fn read_dir(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { - let (ReadDirCmd(name), succ, fail, cont) = cmd.unpack3(); - let name = name.clone(); - let cancel = sched.run_orphan( - move |_| { - Path::new(&name) - .read_dir()? - .map(|r| r.and_then(|e| Ok((e.file_name(), e.file_type()?.is_dir())))) - .collect() - }, - |items: std::io::Result>, _| match items { - Err(e) => vec![io_error_handler(e, fail)], - Ok(os_namev) => { - let converted = (os_namev.into_iter()) - .map(|(n, d)| { - Ok((Inert(n).atom_expr(succ.location()), Inert(d).atom_expr(succ.location()))) - }) - .collect::, Clause>>(); - match converted { - Err(e) => { - let e = e.into_expr(fail.location()); - let tpl = tpl::A(tpl::Slot, tpl::Slot); - vec![tpl.template(nort_gen(fail.location()), [fail, e])] - }, - Ok(names) => { - let names = names.to_expr(succ.location()); - let tpl = tpl::A(tpl::Slot, tpl::Slot); - vec![tpl.template(nort_gen(succ.location()), [succ, names])] - }, - } - }, - }, - ); - let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))); - tpl.template(nort_gen(cont.location()), [cont]) -} - -#[must_use] -fn write_file(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { - let (cmd, succ, fail, cont) = cmd.unpack3(); - let cmd = cmd.clone(); - let cancel = sched.run_orphan( - move |_| File::options().write(true).append(cmd.append).open(&cmd.name), - |file, _| match file { - Err(e) => vec![io_error_handler(e, fail)], - Ok(f) => { - let sink_handle = SharedHandle::wrap(Box::new(f) as Sink); - let tpl = tpl::A(tpl::Slot, tpl::V(Inert(sink_handle))); - vec![tpl.template(nort_gen(succ.location()), [succ])] - }, - }, - ); - let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))); - tpl.template(nort_gen(cont.location()), [cont]) -} - -fn open_file_read_cmd(name: OsString) -> CPSBox { CPSBox::new(3, ReadFileCmd(name)) } - -fn read_dir_cmd(name: OsString) -> CPSBox { CPSBox::new(3, ReadDirCmd(name)) } - -fn open_file_write_cmd(name: OsString) -> CPSBox { - CPSBox::new(3, WriteFile { name, append: false }) -} - -fn open_file_append_cmd(name: OsString) -> CPSBox { - CPSBox::new(3, WriteFile { name, append: true }) -} - -fn join_paths(root: OsString, sub: OsString) -> OsString { - let mut path = PathBuf::from(root); - path.push(sub); - path.into_os_string() -} - -fn pop_path(path: Inert) -> Option<(Inert, Inert)> { - let mut path = PathBuf::from(path.0); - let sub = path.file_name()?.to_owned(); - assert!(path.pop(), "file_name above returned Some"); - Some((Inert(path.into_os_string()), Inert(sub))) -} - -/// A rudimentary system to read and write files. -#[derive(Clone)] -pub struct DirectFS { - scheduler: SeqScheduler, -} -impl DirectFS { - /// Create a new instance of the system. - pub fn new(scheduler: SeqScheduler) -> Self { Self { scheduler } } -} - -impl IntoSystem<'static> for DirectFS { - fn into_system(self) -> System<'static> { - let mut handlers = HandlerTable::new(); - let sched = self.scheduler.clone(); - handlers.register(move |cmd| read_file(&sched, cmd)); - let sched = self.scheduler.clone(); - handlers.register(move |cmd| read_dir(&sched, cmd)); - let sched = self.scheduler; - handlers.register(move |cmd| write_file(&sched, cmd)); - System { - name: "system::directfs", - code: DeclTree::empty(), - prelude: Vec::new(), - lexer_plugins: vec![], - line_parsers: vec![], - constants: ConstTree::ns("system::fs", [ConstTree::tree([ - xfn_ent("read_file", [open_file_read_cmd]), - xfn_ent("read_dir", [read_dir_cmd]), - xfn_ent("write_file", [open_file_write_cmd]), - xfn_ent("append_file", [open_file_append_cmd]), - xfn_ent("join_paths", [join_paths]), - xfn_ent("pop_path", [pop_path]), - atom_ent("cwd", [Unstable::new(|_| -> RTResult<_> { - let path = - std::env::current_dir().map_err(|e| RuntimeError::ext(e.to_string(), "reading CWD"))?; - Ok(Inert(path.into_os_string())) - })]), - ])]) - .combine(os_string_lib()) - .expect("os_string library and directfs conflict"), - handlers, - } - } -} diff --git a/orchidlang/src/libs/directfs/mod.rs b/orchidlang/src/libs/directfs/mod.rs deleted file mode 100644 index fd81be4..0000000 --- a/orchidlang/src/libs/directfs/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! A rudimentary system exposing methods for Orchid to interact with the file -//! system. All paths are strings. -//! -//! The system depends on [crate::libs::scheduler] for scheduling blocking I/O -//! on a separate thread. -mod commands; -mod osstring; - -pub use commands::DirectFS; diff --git a/orchidlang/src/libs/directfs/osstring.rs b/orchidlang/src/libs/directfs/osstring.rs deleted file mode 100644 index 8df9034..0000000 --- a/orchidlang/src/libs/directfs/osstring.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::ffi::OsString; - -use crate::foreign::atom::Atomic; -use crate::foreign::error::RTResult; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::foreign::to_clause::ToClause; -use crate::foreign::try_from_expr::TryFromExpr; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::nort::{Clause, Expr}; -use crate::libs::std::string::OrcString; -use crate::location::CodeLocation; - -impl InertPayload for OsString { - const TYPE_STR: &'static str = "OsString"; - fn respond(&self, mut request: crate::utils::ddispatch::Request) { - request.serve_with(|| OrcString::from(self.to_string_lossy().to_string())) - } -} -impl TryFromExpr for OsString { - fn from_expr(exi: Expr) -> RTResult { Ok(Inert::from_expr(exi)?.0) } -} -impl ToClause for OsString { - fn to_clause(self, _: CodeLocation) -> Clause { Inert(self).atom_cls() } -} - -pub fn os_to_string(os: Inert) -> Result, Inert> { - os.0.into_string().map(|s| Inert(s.into())).map_err(Inert) -} - -pub fn string_to_os(str: Inert) -> Inert { Inert(str.0.get_string().into()) } - -pub fn os_print(os: Inert) -> Inert { - Inert(os.0.to_string_lossy().to_string().into()) -} - -pub fn os_string_lib() -> ConstTree { - ConstTree::ns("system::fs", [ConstTree::tree([ - xfn_ent("os_to_string", [os_to_string]), - xfn_ent("string_to_os", [string_to_os]), - xfn_ent("os_print", [os_print]), - ])]) -} diff --git a/orchidlang/src/libs/io/bindings.rs b/orchidlang/src/libs/io/bindings.rs deleted file mode 100644 index f7124d3..0000000 --- a/orchidlang/src/libs/io/bindings.rs +++ /dev/null @@ -1,71 +0,0 @@ -use super::flow::IOCmdHandlePack; -use super::instances::{BRead, ReadCmd, SRead, WriteCmd}; -use super::service::{Sink, Source}; -use crate::foreign::cps_box::CPSBox; -use crate::foreign::error::RTResult; -use crate::foreign::inert::Inert; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::libs::scheduler::system::SharedHandle; -use crate::libs::std::binary::Binary; -use crate::libs::std::runtime_error::RuntimeError; -use crate::libs::std::string::OrcString; -use crate::utils::combine::Combine; - -pub type WriteHandle = Inert>; -pub type ReadHandle = Inert>; - -type ReadCmdPack = CPSBox>; -type WriteCmdPack = CPSBox>; - -pub fn read_string(Inert(handle): ReadHandle) -> ReadCmdPack { - let cmd = ReadCmd::RStr(SRead::All); - CPSBox::new(3, IOCmdHandlePack { handle, cmd }) -} -pub fn read_line(Inert(handle): ReadHandle) -> ReadCmdPack { - let cmd = ReadCmd::RStr(SRead::Line); - CPSBox::new(3, IOCmdHandlePack { handle, cmd }) -} -pub fn read_bin(Inert(handle): ReadHandle) -> ReadCmdPack { - let cmd = ReadCmd::RBytes(BRead::All); - CPSBox::new(3, IOCmdHandlePack { handle, cmd }) -} -pub fn read_bytes(Inert(handle): ReadHandle, n: Inert) -> ReadCmdPack { - let cmd = ReadCmd::RBytes(BRead::N(n.0)); - CPSBox::new(3, IOCmdHandlePack { cmd, handle }) -} -pub fn read_until( - Inert(handle): ReadHandle, - Inert(pattern): Inert, -) -> RTResult { - let pattern = pattern.try_into().map_err(|_| { - let msg = format!("{pattern} doesn't fit into a byte"); - RuntimeError::ext(msg, "converting number to byte") - })?; - let cmd = ReadCmd::RBytes(BRead::Until(pattern)); - Ok(CPSBox::new(3, IOCmdHandlePack { handle, cmd })) -} -pub fn write_str(Inert(handle): WriteHandle, string: Inert) -> WriteCmdPack { - let cmd = WriteCmd::WStr(string.0.get_string()); - CPSBox::new(3, IOCmdHandlePack { handle, cmd }) -} -pub fn write_bin(Inert(handle): WriteHandle, bytes: Inert) -> WriteCmdPack { - CPSBox::new(3, IOCmdHandlePack { handle, cmd: WriteCmd::WBytes(bytes.0) }) -} -pub fn flush(Inert(handle): WriteHandle) -> WriteCmdPack { - CPSBox::new(3, IOCmdHandlePack { handle, cmd: WriteCmd::Flush }) -} - -pub fn io_bindings<'a>(std_streams: impl IntoIterator) -> ConstTree { - ConstTree::ns("system::io", [ConstTree::tree([ - xfn_ent("read_string", [read_string]), - xfn_ent("read_line", [read_line]), - xfn_ent("read_bin", [read_bin]), - xfn_ent("read_n_bytes", [read_bytes]), - xfn_ent("read_until", [read_until]), - xfn_ent("write_str", [write_str]), - xfn_ent("write_bin", [write_bin]), - xfn_ent("flush", [flush]), - ]) - .combine(ConstTree::tree(std_streams)) - .expect("std_stream name clashing with io functions")]) -} diff --git a/orchidlang/src/libs/io/flow.rs b/orchidlang/src/libs/io/flow.rs deleted file mode 100644 index a8c14ca..0000000 --- a/orchidlang/src/libs/io/flow.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::fmt; - -use crate::foreign::error::RTError; -use crate::libs::scheduler::cancel_flag::CancelFlag; - -pub trait IOHandler { - type Product; - - fn handle(self, result: T) -> Self::Product; - fn early_cancel(self) -> Self::Product; -} - -pub trait IOResult: Send { - type Handler; - type HandlerProduct; - - fn handle(self, handler: Self::Handler) -> Self::HandlerProduct; -} - -pub trait IOCmd: Send { - type Stream: Send; - type Result: Send; - type Handle; - - fn execute(self, stream: &mut Self::Stream, cancel: CancelFlag) -> Self::Result; -} - -#[derive(Debug, Clone)] -pub struct IOCmdHandlePack { - pub cmd: Cmd, - pub handle: Cmd::Handle, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct NoActiveStream(usize); -impl RTError for NoActiveStream {} -impl fmt::Display for NoActiveStream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "The stream {} had already been closed", self.0) - } -} diff --git a/orchidlang/src/libs/io/instances.rs b/orchidlang/src/libs/io/instances.rs deleted file mode 100644 index d883969..0000000 --- a/orchidlang/src/libs/io/instances.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::io::{self, BufRead, Read, Write}; -use std::sync::Arc; - -use super::flow::IOCmd; -use super::service::{Sink, Source}; -use crate::foreign::inert::Inert; -use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort::Expr; -use crate::libs::scheduler::cancel_flag::CancelFlag; -use crate::libs::scheduler::system::SharedHandle; -use crate::libs::std::binary::Binary; -use crate::libs::std::string::OrcString; -use crate::location::{CodeGenInfo, CodeLocation}; -use crate::sym; - -/// String reading command -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(super) enum SRead { - All, - Line, -} - -/// Binary reading command -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(super) enum BRead { - All, - N(usize), - Until(u8), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(super) enum ReadCmd { - RBytes(BRead), - RStr(SRead), -} - -impl IOCmd for ReadCmd { - type Stream = Source; - type Result = ReadResult; - type Handle = SharedHandle; - - // This is a buggy rule, check manually - #[allow(clippy::read_zero_byte_vec)] - fn execute(self, stream: &mut Self::Stream, _cancel: CancelFlag) -> Self::Result { - match self { - Self::RBytes(bread) => { - let mut buf = Vec::new(); - let result = match &bread { - BRead::All => stream.read_to_end(&mut buf).map(|_| ()), - BRead::Until(b) => stream.read_until(*b, &mut buf).map(|_| ()), - BRead::N(n) => { - buf.resize(*n, 0); - stream.read_exact(&mut buf) - }, - }; - ReadResult::RBin(bread, result.map(|_| buf)) - }, - Self::RStr(sread) => { - let mut buf = String::new(); - let sresult = match &sread { - SRead::All => stream.read_to_string(&mut buf).map(|_| ()), - SRead::Line => stream.read_line(&mut buf).map(|_| { - if buf.ends_with('\n') { - buf.pop(); - } - }), - }; - ReadResult::RStr(sread, sresult.map(|()| buf)) - }, - } - } -} - -/// Reading command (string or binary) -pub(super) enum ReadResult { - RStr(SRead, io::Result), - RBin(BRead, io::Result>), -} -impl ReadResult { - pub fn dispatch(self, succ: Expr, fail: Expr) -> Vec { - vec![match self { - ReadResult::RBin(_, Err(e)) | ReadResult::RStr(_, Err(e)) => io_error_handler(e, fail), - ReadResult::RBin(_, Ok(bytes)) => tpl::A(tpl::Slot, tpl::V(Inert(Binary(Arc::new(bytes))))) - .template(nort_gen(succ.location()), [succ]), - ReadResult::RStr(_, Ok(text)) => tpl::A(tpl::Slot, tpl::V(Inert(OrcString::from(text)))) - .template(nort_gen(succ.location()), [succ]), - }] - } -} - -/// Function to convert [io::Error] to Orchid data -pub(crate) fn io_error_handler(_e: io::Error, handler: Expr) -> Expr { - let ctx = nort_gen(CodeLocation::new_gen(CodeGenInfo::no_details(sym!(system::io::io_error)))); - tpl::A(tpl::Slot, tpl::V(Inert(0usize))).template(ctx, [handler]) -} - -/// Writing command (string or binary) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(super) enum WriteCmd { - WBytes(Binary), - WStr(String), - Flush, -} - -impl IOCmd for WriteCmd { - type Stream = Sink; - type Handle = SharedHandle; - type Result = WriteResult; - - fn execute(self, stream: &mut Self::Stream, _cancel: CancelFlag) -> Self::Result { - let result = match &self { - Self::Flush => stream.flush(), - Self::WStr(str) => write!(stream, "{}", str).map(|_| ()), - Self::WBytes(bytes) => stream.write_all(bytes.0.as_ref()).map(|_| ()), - }; - WriteResult { result, cmd: self } - } -} - -pub(super) struct WriteResult { - #[allow(unused)] - pub cmd: WriteCmd, - pub result: io::Result<()>, -} -impl WriteResult { - pub fn dispatch(self, succ: Expr, fail: Expr) -> Vec { - vec![self.result.map_or_else(|e| io_error_handler(e, fail), |()| succ)] - } -} diff --git a/orchidlang/src/libs/io/io.orc b/orchidlang/src/libs/io/io.orc deleted file mode 100644 index da38a31..0000000 --- a/orchidlang/src/libs/io/io.orc +++ /dev/null @@ -1,35 +0,0 @@ -import std::panic -import system::io -import system::async::yield - -export const print := \text. \ok. ( - io::write_str io::stdout text - (io::flush io::stdout - ok - (\e. panic "println threw on flush") - \_. yield - ) - (\e. panic "print threw on write") - \_. yield -) - -export const println := \line. \ok. ( - print (line ++ "\n") ok -) - -export const readln := \ok. ( - io::read_line io::stdin - ok - (\e. panic "readln threw") - \_. yield -) - -export const prompt := \line. \ok. ( - print line (readln ok) -) - -export module prelude ( - import super::* - - export ::(print, println, readln, prompt) -) diff --git a/orchidlang/src/libs/io/mod.rs b/orchidlang/src/libs/io/mod.rs deleted file mode 100644 index 4210705..0000000 --- a/orchidlang/src/libs/io/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! System that allows Orchid to interact with trait objects of Rust's `Writer` -//! and with `BufReader`s of `Reader` trait objects. -//! -//! You can pass standard streams during initialization, the stllib expects -//! `stdin`, `stdout` and `stderr`. This system depends on -//! [crate::libs::scheduler] to run blocking I/O operations off-thread, which in -//! turn depends on [crate::libs::asynch] to process results on the main thread, -//! and [crate::libs::std] for `std::panic`. -//! -//! ``` -//! use std::io::BufReader; -//! -//! use orchidlang::facade::loader::Loader; -//! use orchidlang::libs::asynch::system::AsynchSystem; -//! use orchidlang::libs::io::{IOService, Stream}; -//! use orchidlang::libs::scheduler::system::SeqScheduler; -//! use orchidlang::libs::std::std_system::StdConfig; -//! -//! let mut asynch = AsynchSystem::new(); -//! let scheduler = SeqScheduler::new(&mut asynch); -//! let std_streams = [ -//! ("stdin", Stream::Source(BufReader::new(Box::new(std::io::stdin())))), -//! ("stdout", Stream::Sink(Box::new(std::io::stdout()))), -//! ("stderr", Stream::Sink(Box::new(std::io::stderr()))), -//! ]; -//! let env = Loader::new() -//! .add_system(StdConfig { impure: false }) -//! .add_system(asynch) -//! .add_system(scheduler.clone()) -//! .add_system(IOService::new(scheduler.clone(), std_streams)); -//! ``` - -mod bindings; -mod flow; -pub(super) mod instances; -mod service; - -pub use service::{IOService, Sink, Source, Stream}; diff --git a/orchidlang/src/libs/io/service.rs b/orchidlang/src/libs/io/service.rs deleted file mode 100644 index 8fcf230..0000000 --- a/orchidlang/src/libs/io/service.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! Object to pass to [crate::facade::loader::Loader::add_system] to enable the -//! I/O subsystem - -use std::io::{BufReader, Read, Write}; - -use rust_embed::RustEmbed; -use trait_set::trait_set; - -use super::bindings::io_bindings; -use super::flow::{IOCmd, IOCmdHandlePack}; -use super::instances::{ReadCmd, WriteCmd}; -use crate::facade::system::{IntoSystem, System}; -use crate::foreign::cps_box::CPSBox; -use crate::foreign::inert::Inert; -use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::gen::tree::leaf; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::handler::HandlerTable; -use crate::libs::scheduler::system::{SeqScheduler, SharedHandle}; -use crate::location::CodeGenInfo; -use crate::pipeline::load_project::Prelude; -use crate::virt_fs::{DeclTree, EmbeddedFS, PrefixFS, VirtFS}; -use crate::{sym, vname}; - -/// Any type that we can read controlled amounts of data from -pub type Source = BufReader>; -/// Any type that we can write data to -pub type Sink = Box; - -/// A shared type for sinks and sources -pub enum Stream { - /// A Source, aka. a BufReader - Source(Source), - /// A Sink, aka. a Writer - Sink(Sink), -} - -trait_set! { - /// The table of default streams to be overlain on the I/O module, typicially - /// stdin, stdout, stderr. - pub(super) trait StreamTable<'a> = IntoIterator -} - -fn gen() -> CodeGenInfo { CodeGenInfo::no_details(sym!(system::io)) } - -#[derive(RustEmbed)] -#[folder = "src/libs/io"] -#[include = "*.orc"] -struct IOEmbed; - -fn code() -> DeclTree { - DeclTree::ns("system::io", [DeclTree::leaf( - PrefixFS::new(EmbeddedFS::new::(".orc", gen()), "", "io").rc(), - )]) -} - -/// A streaming I/O service for interacting with Rust's [std::io::Write] and -/// [std::io::Read] traits. -pub struct IOService<'a, ST: IntoIterator> { - scheduler: SeqScheduler, - global_streams: ST, -} -impl<'a, ST: IntoIterator> IOService<'a, ST> { - /// Construct a new instance of the service - pub fn new(scheduler: SeqScheduler, global_streams: ST) -> Self { - Self { scheduler, global_streams } - } -} - -impl<'a, ST: IntoIterator> IntoSystem<'static> for IOService<'a, ST> { - fn into_system(self) -> System<'static> { - let scheduler = self.scheduler.clone(); - let mut handlers = HandlerTable::new(); - handlers.register(move |cps: &CPSBox>| { - let (IOCmdHandlePack { cmd, handle }, succ, fail, cont) = cps.unpack3(); - let (cmd, fail1) = (*cmd, fail.clone()); - let result = scheduler.schedule( - handle.clone(), - move |mut stream, cancel| { - let ret = cmd.execute(&mut stream, cancel); - (stream, ret) - }, - move |stream, res, _cancel| (stream, res.dispatch(succ, fail1)), - |stream| (stream, Vec::new()), - ); - match result { - Ok(cancel) => tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))) - .template(nort_gen(cont.location()), [cont]), - Err(e) => tpl::A(tpl::Slot, tpl::V(Inert(e))).template(nort_gen(fail.location()), [fail]), - } - }); - let scheduler = self.scheduler.clone(); - handlers.register(move |cps: &CPSBox>| { - let (IOCmdHandlePack { cmd, handle }, succ, fail, cont) = cps.unpack3(); - let (succ1, fail1, cmd) = (succ, fail.clone(), cmd.clone()); - let result = scheduler.schedule( - handle.clone(), - move |mut stream, cancel| { - let ret = cmd.execute(&mut stream, cancel); - (stream, ret) - }, - move |stream, res, _cancel| (stream, res.dispatch(succ1, fail1)), - |stream| (stream, Vec::new()), - ); - match result { - Ok(cancel) => tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))) - .template(nort_gen(cont.location()), [cont]), - Err(e) => tpl::A(tpl::Slot, tpl::V(Inert(e))).template(nort_gen(fail.location()), [fail]), - } - }); - let streams = self.global_streams.into_iter().map(|(n, stream)| { - let handle = match stream { - Stream::Sink(sink) => leaf(tpl::V(Inert(SharedHandle::wrap(sink)))), - Stream::Source(source) => leaf(tpl::V(Inert(SharedHandle::wrap(source)))), - }; - (n, handle) - }); - System { - handlers, - name: "system::io", - constants: io_bindings(streams), - code: code(), - prelude: vec![Prelude { - target: vname!(system::io::prelude), - exclude: vname!(system::io), - owner: gen(), - }], - lexer_plugins: vec![], - line_parsers: vec![], - } - } -} diff --git a/orchidlang/src/libs/mod.rs b/orchidlang/src/libs/mod.rs deleted file mode 100644 index 2b98abb..0000000 --- a/orchidlang/src/libs/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Constants exposed to usercode by the interpreter -pub mod asynch; -pub mod directfs; -pub mod io; -pub mod parse_custom_line; -pub mod scheduler; -pub mod std; diff --git a/orchidlang/src/libs/parse_custom_line.rs b/orchidlang/src/libs/parse_custom_line.rs deleted file mode 100644 index 5697594..0000000 --- a/orchidlang/src/libs/parse_custom_line.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! A helper for defining custom lines. See [custom_line] -use intern_all::Tok; - -use crate::error::ProjectResult; -use crate::location::SourceRange; -use crate::parse::errors::ParseErrorKind; -use crate::parse::frag::Frag; -use crate::parse::lexer::Lexeme; -use crate::parse::parse_plugin::ParsePluginReq; - -/// An exported line with a name for which the line parser denies exports -pub struct Unexportable(Lexeme); -impl ParseErrorKind for Unexportable { - const DESCRIPTION: &'static str = "this line type cannot be exported"; - fn message(&self) -> String { format!("{} cannot be exported", &self.0) } -} - -/// Parse a line identified by the specified leading keyword. Although not -/// required, plugins are encouraged to prefix their lines with a globally -/// unique keyword which makes or breaks their parsing, to avoid accidental -/// failure to recognize -pub fn custom_line<'a>( - tail: Frag<'a>, - keyword: Tok, - exportable: bool, - req: &dyn ParsePluginReq, -) -> Option, SourceRange)>> { - let line_loc = req.frag_loc(tail); - let (fst, tail) = req.pop(tail).ok()?; - let fst_name = req.expect_name(fst).ok()?; - let (exported, n_ent, tail) = if fst_name == keyword { - (false, fst, tail.trim()) - } else if fst_name.as_str() == "export" { - let (snd, tail) = req.pop(tail).ok()?; - req.expect(Lexeme::Name(keyword), snd).ok()?; - (true, snd, tail.trim()) - } else { - return None; - }; - Some(match exported && !exportable { - true => { - let err = Unexportable(n_ent.lexeme.clone()); - Err(err.pack(req.range_loc(n_ent.range.clone()))) - }, - false => Ok((exported, tail, line_loc)), - }) -} diff --git a/orchidlang/src/libs/scheduler/busy.rs b/orchidlang/src/libs/scheduler/busy.rs deleted file mode 100644 index b21134f..0000000 --- a/orchidlang/src/libs/scheduler/busy.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::any::Any; -use std::collections::VecDeque; - -use super::cancel_flag::CancelFlag; -use crate::interpreter::nort::Expr; - -pub type SyncResult = (T, Box); -/// Output from handlers contains the resource being processed and any Orchid -/// handlers executed as a result of the operation -pub type HandlerRes = (T, Vec); -pub type SyncOperation = Box SyncResult + Send>; -pub type SyncOpResultHandler = - Box, CancelFlag) -> (T, Vec) + Send>; - -struct SyncQueueItem { - cancelled: CancelFlag, - operation: SyncOperation, - handler: SyncOpResultHandler, - early_cancel: Box (T, Vec) + Send>, -} - -pub enum NextItemReportKind { - Free(T), - Next { instance: T, cancelled: CancelFlag, operation: SyncOperation, rest: BusyState }, - Taken, -} - -pub struct NextItemReport { - pub kind: NextItemReportKind, - pub events: Vec, -} - -pub(super) struct BusyState { - handler: SyncOpResultHandler, - queue: VecDeque>, - seal: Option Vec + Send>>, -} -impl BusyState { - pub fn new( - handler: impl FnOnce(T, U, CancelFlag) -> HandlerRes + Send + 'static, - ) -> Self { - BusyState { - handler: Box::new(|t, payload, cancel| { - let u = *payload.downcast().expect("mismatched initial handler and operation"); - handler(t, u, cancel) - }), - queue: VecDeque::new(), - seal: None, - } - } - - /// Add a new operation to the queue. Returns Some if the operation was - /// successfully enqueued and None if the queue is already sealed. - pub fn enqueue( - &mut self, - operation: impl FnOnce(T, CancelFlag) -> (T, U) + Send + 'static, - handler: impl FnOnce(T, U, CancelFlag) -> HandlerRes + Send + 'static, - early_cancel: impl FnOnce(T) -> HandlerRes + Send + 'static, - ) -> Option { - if self.seal.is_some() { - return None; - } - let cancelled = CancelFlag::new(); - self.queue.push_back(SyncQueueItem { - cancelled: cancelled.clone(), - early_cancel: Box::new(early_cancel), - operation: Box::new(|t, c| { - let (t, r) = operation(t, c); - (t, Box::new(r)) - }), - handler: Box::new(|t, u, c| { - let u: Box = u.downcast().expect("mismatched handler and operation"); - handler(t, *u, c) - }), - }); - Some(cancelled) - } - - pub fn seal(&mut self, recipient: impl FnOnce(T) -> Vec + Send + 'static) { - assert!(self.seal.is_none(), "Already sealed"); - self.seal = Some(Box::new(recipient)) - } - - pub fn is_sealed(&self) -> bool { self.seal.is_some() } - - pub fn rotate( - mut self, - instance: T, - result: Box, - cancelled: CancelFlag, - ) -> NextItemReport { - let (mut instance, mut events) = (self.handler)(instance, result, cancelled); - let next_item = loop { - if let Some(candidate) = self.queue.pop_front() { - if candidate.cancelled.is_cancelled() { - let ret = (candidate.early_cancel)(instance); - instance = ret.0; - events.extend(ret.1); - } else { - break candidate; - } - } else if let Some(seal) = self.seal.take() { - seal(instance); - let kind = NextItemReportKind::Taken; - return NextItemReport { events, kind }; - } else { - let kind = NextItemReportKind::Free(instance); - return NextItemReport { events, kind }; - } - }; - self.handler = next_item.handler; - NextItemReport { - events, - kind: NextItemReportKind::Next { - instance, - cancelled: next_item.cancelled, - operation: next_item.operation, - rest: self, - }, - } - } -} diff --git a/orchidlang/src/libs/scheduler/cancel_flag.rs b/orchidlang/src/libs/scheduler/cancel_flag.rs deleted file mode 100644 index 9197f0d..0000000 --- a/orchidlang/src/libs/scheduler/cancel_flag.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Flag for cancelling scheduled operations - -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; - -/// A single-fire thread-safe boolean flag with relaxed ordering -#[derive(Debug, Clone)] -pub struct CancelFlag(Arc); - -impl CancelFlag { - /// Create a new canceller - pub fn new() -> Self { CancelFlag(Arc::new(AtomicBool::new(false))) } - - /// Check whether the operation has been cancelled - pub fn is_cancelled(&self) -> bool { self.0.load(Ordering::Relaxed) } - - /// Cancel the operation - pub fn cancel(&self) { self.0.store(true, Ordering::Relaxed) } -} - -impl Default for CancelFlag { - fn default() -> Self { Self::new() } -} diff --git a/orchidlang/src/libs/scheduler/id_map.rs b/orchidlang/src/libs/scheduler/id_map.rs deleted file mode 100644 index 9ba3fd5..0000000 --- a/orchidlang/src/libs/scheduler/id_map.rs +++ /dev/null @@ -1,49 +0,0 @@ -use hashbrown::HashMap; - -/// A set that automatically assigns a unique ID to every entry. -/// -/// # How unique? -/// -/// If you inserted a new entry every nanosecond, it would take more than -/// 550_000 years to run out of indices. Realistically Orchid might insert a new -/// entry every 10ms, so these 64-bit indices will probably outlast humanity. -#[derive(Clone, Debug)] -pub struct IdMap { - next_id: u64, - data: HashMap, -} -impl IdMap { - /// Create a new empty set - pub fn new() -> Self { Self { next_id: 0, data: HashMap::new() } } - - /// Insert an element with a new ID and return the ID - pub fn insert(&mut self, t: T) -> u64 { - let id = self.next_id; - self.next_id += 1; - (self.data.try_insert(id, t)).unwrap_or_else(|_| panic!("IdMap keys should be unique")); - id - } - - /// Remove the element with the given ID from the set. The ID will not be - /// reused. - pub fn remove(&mut self, id: u64) -> Option { self.data.remove(&id) } -} - -impl Default for IdMap { - fn default() -> Self { Self::new() } -} - -#[cfg(test)] -mod test { - use super::IdMap; - - #[test] - fn basic_test() { - let mut map = IdMap::new(); - let a = map.insert(1); - let b = map.insert(2); - assert_eq!(map.remove(a), Some(1)); - assert_eq!(map.remove(a), None); - assert_eq!(map.data.get(&b), Some(&2)); - } -} diff --git a/orchidlang/src/libs/scheduler/mod.rs b/orchidlang/src/libs/scheduler/mod.rs deleted file mode 100644 index 5b12124..0000000 --- a/orchidlang/src/libs/scheduler/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! A generic utility to sequence long blocking mutations that require a mutable -//! reference to a shared resource. - -mod busy; -pub mod cancel_flag; -mod id_map; -pub mod system; -pub mod thread_pool; diff --git a/orchidlang/src/libs/scheduler/system.rs b/orchidlang/src/libs/scheduler/system.rs deleted file mode 100644 index bfba7c4..0000000 --- a/orchidlang/src/libs/scheduler/system.rs +++ /dev/null @@ -1,344 +0,0 @@ -//! Object to pass to [crate::facade::loader::Loader::add_system] to enable the -//! scheduling subsystem. Other systems also take clones as dependencies. -//! -//! ``` -//! use orchidlang::facade::loader::Loader; -//! use orchidlang::libs::asynch::system::AsynchSystem; -//! use orchidlang::libs::scheduler::system::SeqScheduler; -//! use orchidlang::libs::std::std_system::StdConfig; -//! -//! let mut asynch = AsynchSystem::new(); -//! let scheduler = SeqScheduler::new(&mut asynch); -//! let env = Loader::new() -//! .add_system(StdConfig { impure: false }) -//! .add_system(asynch) -//! .add_system(scheduler.clone()); -//! ``` - -use std::any::{type_name, Any}; -use std::cell::RefCell; -use std::fmt; -use std::rc::Rc; -use std::sync::{Arc, Mutex}; - -use trait_set::trait_set; - -use super::busy::{BusyState, HandlerRes, NextItemReportKind, SyncOperation}; -use super::cancel_flag::CancelFlag; -use super::id_map::IdMap; -use super::thread_pool::ThreadPool; -use crate::facade::system::{IntoSystem, System}; -use crate::foreign::cps_box::CPSBox; -use crate::foreign::error::{AssertionError, RTResult}; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::handler::HandlerTable; -use crate::interpreter::nort::Expr; -use crate::libs::asynch::system::{AsynchSystem, MessagePort}; -use crate::utils::ddispatch::Request; -use crate::utils::take_with_output::take_with_output; -use crate::utils::unwrap_or::unwrap_or; -use crate::virt_fs::DeclTree; - -pub(super) enum SharedResource { - Free(T), - Busy(BusyState), - Taken, -} - -/// Possible states of a shared resource -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub enum SharedState { - /// The resource is ready to be used or taken - Free, - /// The resource is currently in use but operations can be asynchronously - /// scheduled on it - Busy, - /// The resource is currently in use and a consuming seal has already been - /// scheduled, therefore further operations cannot access it and it will - /// transition to [SharedState::Taken] as soon as the currently pending - /// operations finish or are cancelled. - Sealed, - /// The resource has been removed from this location. - Taken, -} - -/// A shared handle for a resource of type `T` that can be used with a -/// [SeqScheduler] to execute mutating operations one by one in worker threads. -pub struct SharedHandle(pub(super) Arc>>); - -impl SharedHandle { - /// Wrap a value to be accessible to a [SeqScheduler]. - pub fn wrap(t: T) -> Self { Self(Arc::new(Mutex::new(SharedResource::Free(t)))) } - - /// Check the state of the handle - pub fn state(&self) -> SharedState { - match &*self.0.lock().unwrap() { - SharedResource::Busy(b) if b.is_sealed() => SharedState::Sealed, - SharedResource::Busy(_) => SharedState::Busy, - SharedResource::Free(_) => SharedState::Free, - SharedResource::Taken => SharedState::Taken, - } - } - - /// Remove the value from the handle if it's free. To interact with a handle - /// you probably want to use a [SeqScheduler], but sometimes this makes - /// sense as eg. an optimization. You can return the value after processing - /// via [SharedHandle::untake]. - pub fn take(&self) -> Option { - take_with_output(&mut *self.0.lock().unwrap(), |state| match state { - SharedResource::Free(t) => (SharedResource::Taken, Some(t)), - _ => (state, None), - }) - } - - /// Return the value to a handle that doesn't have one. The intended use case - /// is to return values synchronously after they have been removed with - /// [SharedHandle::take]. - pub fn untake(&self, value: T) -> Result<(), T> { - take_with_output(&mut *self.0.lock().unwrap(), |state| match state { - SharedResource::Taken => (SharedResource::Free(value), Ok(())), - _ => (state, Err(value)), - }) - } -} -impl Clone for SharedHandle { - fn clone(&self) -> Self { Self(self.0.clone()) } -} -impl fmt::Debug for SharedHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SharedHandle") - .field("state", &self.state()) - .field("type", &type_name::()) - .finish() - } -} -impl InertPayload for SharedHandle { - const TYPE_STR: &'static str = "a SharedHandle"; - fn respond(&self, mut request: Request) { - request.serve_with(|| { - let this = self.clone(); - TakeCmd(Arc::new(move |sch| { - let _ = sch.seal(this.clone(), |_| Vec::new()); - })) - }) - } -} - -#[derive(Clone)] -struct TakeCmd(pub Arc); -impl fmt::Debug for TakeCmd { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "A command to drop a shared resource") - } -} - -/// Error produced when an operation is scheduled or a seal placed on a resource -/// which is either already sealed or taken. -#[derive(Debug, Clone)] -pub struct SealedOrTaken; -impl InertPayload for SealedOrTaken { - const TYPE_STR: &'static str = "SealedOrTaken"; -} - -fn take_and_drop(x: Expr) -> RTResult> { - match x.clause.request() { - Some(t) => Ok(CPSBox::::new(1, t)), - None => AssertionError::fail(x.location(), "SharedHandle", format!("{x}")), - } -} - -fn is_taken_e(x: Expr) -> Inert { Inert(x.downcast::>().is_ok()) } - -trait_set! { - /// The part of processing a blocking I/O task that cannot be done on a remote - /// thread, eg. because it accesses other systems or Orchid code. - trait NonSendFn = FnOnce(Box, SeqScheduler) -> Vec; -} - -struct SyncReply { - opid: u64, - data: Box, -} - -struct CheshireCat { - pool: ThreadPool>, - pending: RefCell>>, - port: MessagePort, -} - -/// A task scheduler that executes long blocking operations that have mutable -/// access to a shared one by one on a worker thread. The resources are -/// held in [SharedHandle]s -#[derive(Clone)] -pub struct SeqScheduler(Rc); -impl SeqScheduler { - /// Creates a new [SeqScheduler]. The new object is also kept alive by a - /// callback in the provided [AsynchSystem]. There should be at most one - pub fn new(asynch: &mut AsynchSystem) -> Self { - let this = Self(Rc::new(CheshireCat { - pending: RefCell::new(IdMap::new()), - pool: ThreadPool::new(), - port: asynch.get_port(), - })); - let this1 = this.clone(); - // referenced by asynch, references this - asynch.register(move |res: Box| { - let callback = this1.0.pending.borrow_mut().remove(res.opid).expect( - "Received reply for task we didn't start. This likely means that \ - there are multiple SequencingContexts attached to the same \ - AsynchSystem.", - ); - callback(res.data, this1.clone()) - }); - this - } - - /// Submit an action to be executed on a worker thread which can own the data - /// in the handle. - /// - /// * handle - data to be transformed - /// * operation - long blocking mutation to execute off-thread. - /// * handler - process the results, talk to other systems, generate and run - /// Orchid code. - /// * early_cancel - clean up in case the task got cancelled before it was - /// scheduled. This is an optimization so that threads aren't spawned if a - /// large batch of tasks is scheduled and then cancelled. - pub fn schedule( - &self, - handle: SharedHandle, - operation: impl FnOnce(T, CancelFlag) -> (T, U) + Send + 'static, - handler: impl FnOnce(T, U, CancelFlag) -> HandlerRes + Send + 'static, - early_cancel: impl FnOnce(T) -> HandlerRes + Send + 'static, - ) -> Result { - take_with_output(&mut *handle.0.lock().unwrap(), { - let handle = handle.clone(); - |state| { - match state { - SharedResource::Taken => (SharedResource::Taken, Err(SealedOrTaken)), - SharedResource::Busy(mut b) => match b.enqueue(operation, handler, early_cancel) { - Some(cancelled) => (SharedResource::Busy(b), Ok(cancelled)), - None => (SharedResource::Busy(b), Err(SealedOrTaken)), - }, - SharedResource::Free(t) => { - let cancelled = CancelFlag::new(); - drop(early_cancel); // cannot possibly be useful - let op_erased: SyncOperation = Box::new(|t, c| { - let (t, u) = operation(t, c); - (t, Box::new(u)) - }); - self.submit(t, handle, cancelled.clone(), op_erased); - (SharedResource::Busy(BusyState::new(handler)), Ok(cancelled)) - }, - } - } - }) - } - - /// Run an operation asynchronously and then process its result in thread, - /// without queuing on any particular data. - pub fn run_orphan( - &self, - operation: impl FnOnce(CancelFlag) -> T + Send + 'static, - handler: impl FnOnce(T, CancelFlag) -> Vec + 'static, - ) -> CancelFlag { - let cancelled = CancelFlag::new(); - let canc1 = cancelled.clone(); - let opid = self.0.pending.borrow_mut().insert(Box::new(|data: Box, _| { - handler(*data.downcast().expect("This is associated by ID"), canc1) - })); - let canc1 = cancelled.clone(); - let mut port = self.0.port.clone(); - self.0.pool.submit(Box::new(move || { - port.send(SyncReply { opid, data: Box::new(operation(canc1)) }); - })); - cancelled - } - - /// Schedule a function that will consume the value. After this the handle is - /// considered sealed and all [SeqScheduler::schedule] calls will fail. - pub fn seal( - &self, - handle: SharedHandle, - seal: impl FnOnce(T) -> Vec + Sync + Send + 'static, - ) -> Result, SealedOrTaken> { - take_with_output(&mut *handle.0.lock().unwrap(), |state| match state { - SharedResource::Busy(mut b) if !b.is_sealed() => { - b.seal(seal); - (SharedResource::Busy(b), Ok(Vec::new())) - }, - SharedResource::Busy(_) => (state, Err(SealedOrTaken)), - SharedResource::Taken => (SharedResource::Taken, Err(SealedOrTaken)), - SharedResource::Free(t) => (SharedResource::Taken, Ok(seal(t))), - }) - } - - /// Asynchronously recursive function to schedule a new task for execution and - /// act upon its completion. The self-reference is passed into the callback - /// from the callback passed to the [AsynchSystem] so that if the task is - /// never resolved but the [AsynchSystem] through which the resolving event - /// would arrive is dropped this [SeqScheduler] is also dropped. - fn submit( - &self, - t: T, - handle: SharedHandle, - cancelled: CancelFlag, - operation: SyncOperation, - ) { - // referenced by self until run, references handle - let opid = self.0.pending.borrow_mut().insert(Box::new({ - let cancelled = cancelled.clone(); - move |data: Box, this: SeqScheduler| { - let (t, u): (T, Box) = *data.downcast().expect("This is associated by ID"); - let handle2 = handle.clone(); - take_with_output(&mut *handle.0.lock().unwrap(), |state| { - let busy = unwrap_or! { state => SharedResource::Busy; - panic!("Handle with outstanding invocation must be busy") - }; - let report = busy.rotate(t, u, cancelled); - match report.kind { - NextItemReportKind::Free(t) => (SharedResource::Free(t), report.events), - NextItemReportKind::Taken => (SharedResource::Taken, report.events), - NextItemReportKind::Next { instance, cancelled, operation, rest } => { - this.submit(instance, handle2, cancelled, operation); - (SharedResource::Busy(rest), report.events) - }, - } - }) - } - })); - let mut port = self.0.port.clone(); - // referenced by thread until run, references port - self.0.pool.submit(Box::new(move || { - port.send(SyncReply { opid, data: Box::new(operation(t, cancelled)) }) - })) - } -} - -impl IntoSystem<'static> for SeqScheduler { - fn into_system(self) -> System<'static> { - let mut handlers = HandlerTable::new(); - handlers.register(|cmd: &CPSBox| { - let (canceller, cont) = cmd.unpack1(); - canceller.cancel(); - cont - }); - handlers.register(move |cmd: &CPSBox| { - let (TakeCmd(cb), cont) = cmd.unpack1(); - cb(self.clone()); - cont - }); - System { - name: "system::scheduler", - prelude: Vec::new(), - code: DeclTree::empty(), - handlers, - lexer_plugins: vec![], - line_parsers: vec![], - constants: ConstTree::ns("system::scheduler", [ConstTree::tree([ - xfn_ent("is_taken_e", [is_taken_e]), - xfn_ent("take_and_drop", [take_and_drop]), - ])]), - } - } -} diff --git a/orchidlang/src/libs/scheduler/thread_pool.rs b/orchidlang/src/libs/scheduler/thread_pool.rs deleted file mode 100644 index 3200b40..0000000 --- a/orchidlang/src/libs/scheduler/thread_pool.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! A thread pool for executing tasks in parallel, spawning threads as workload -//! increases and terminating them as tasks finish. This is not terribly -//! efficient, its main design goal is to parallelize blocking I/O calls. -//! -//! This is the abstract implementation of the scheduler. - -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::{sync_channel, SyncSender}; -use std::sync::{Arc, Mutex}; -use std::thread::spawn; - -/// A trait for a task dispatched on a [ThreadPool]. The task owns all relevant -/// data, is safe to pass between threads and is executed only once. -pub trait Task: Send + 'static { - /// Execute the task. At a minimum, this involves signaling some other thread, - /// otherwise the task has no effect. - fn run(self); -} - -impl Task for F { - fn run(self) { self() } -} - -/// An async unit of work that produces some result, see [Task]. This can be -/// wrapped in a generic reporter to create a task. -pub trait Query: Send + 'static { - /// The value produced by the query - type Result: Send + 'static; - - /// Execute the query, producing some value which can then be sent to another - /// thread - fn run(self) -> Self::Result; - - /// Associate the query with a reporter expressed in a plain function. - /// Note that because every lambda has a distinct type and every thread pool - /// runs exactly one type of task, this can appear only once in the code for - /// a given thread pool. It is practical in a narrow set of cases, most of the - /// time however you are better off defining an explicit reporter. - fn then(self, callback: F) -> QueryTask - where Self: Sized { - QueryTask { query: self, callback } - } -} -impl R + Send + 'static, R: Send + 'static> Query for F { - type Result = R; - - fn run(self) -> Self::Result { self() } -} - -/// A reporter that calls a statically known function with the result of a -/// query. Constructed with [Query::then] -pub struct QueryTask { - query: Q, - callback: F, -} -impl Task for QueryTask { - fn run(self) { (self.callback)(self.query.run()) } -} - -enum Message { - Stop, - Task(T), -} - -struct ThreadPoolData { - rdv_point: Mutex>>>, - stopping: AtomicBool, -} - -/// A thread pool to execute blocking I/O operations in parallel. -/// This thread pool is pretty inefficient for CPU-bound operations because it -/// spawns an unbounded number of concurrent threads and destroys them eagerly. -/// It is assumed that the tasks at hand are substnatially but not incomparably -/// more expensive than spawning a new thread. -/// -/// If multiple threads finish their tasks, one waiting thread is kept, the -/// rest exit. If all threads are busy, new threads are spawned when tasks -/// arrive. To get rid of the last waiting thread, drop the thread pool. -/// -/// ``` -/// use orchidlang::libs::scheduler::thread_pool::{Task, ThreadPool}; -/// -/// struct MyTask(&'static str); -/// impl Task for MyTask { -/// fn run(self) { println!("{}", self.0) } -/// } -/// -/// let pool = ThreadPool::new(); -/// -/// // spawns first thread -/// pool.submit(MyTask("foo")); -/// // probably spawns second thread -/// pool.submit(MyTask("bar")); -/// // either spawns third thread or reuses first -/// pool.submit(MyTask("baz")); -/// ``` -pub struct ThreadPool { - data: Arc>, -} -impl ThreadPool { - /// Create a new thread pool. This just initializes the threadsafe - /// datastructures used to synchronize tasks and doesn't spawn any threads. - /// The first submission spawns the first thread. - pub fn new() -> Self { - Self { - data: Arc::new(ThreadPoolData { - rdv_point: Mutex::new(None), - stopping: AtomicBool::new(false), - }), - } - } - - /// Submit a task to the thread pool. This tries to send the task to the - /// waiting thread, or spawn a new one. If a thread is done with its task - /// and finds that it another thread is already waiting, it exits. - pub fn submit(&self, task: T) { - let mut standby = self.data.rdv_point.lock().unwrap(); - if let Some(port) = standby.take() { - (port.try_send(Message::Task(task))).expect( - "This channel cannot be disconnected unless the receiver crashes - between registering the sender and blocking for receive, and it cannot - be full because it's taken before insertion", - ); - } else { - drop(standby); - let data = self.data.clone(); - // worker thread created if all current ones are busy - spawn(move || { - let mut cur_task = task; - loop { - // Handle the task - cur_task.run(); - // Apply for a new task if no other thread is doing so already - let mut standby_spot = data.rdv_point.lock().unwrap(); - if standby_spot.is_some() { - return; // exit if we would be the second in line - } - let (sender, receiver) = sync_channel(1); - *standby_spot = Some(sender); - drop(standby_spot); - if data.stopping.load(Ordering::SeqCst) { - return; // exit if the pool was dropped before we applied - } - // Wait for the next event on the pool - let msg = (receiver.recv()).expect("We are holding a reference"); - match msg { - // repeat with next task - Message::Task(task) => cur_task = task, - // exit if the pool is dropped - Message::Stop => return, - } - } - }); - } - } -} - -impl Default for ThreadPool { - fn default() -> Self { Self::new() } -} - -impl Drop for ThreadPool { - // Ensure all threads exit properly - fn drop(&mut self) { - self.data.stopping.store(true, Ordering::SeqCst); - let mut rdv_point = self.data.rdv_point.lock().unwrap(); - if let Some(pending) = rdv_point.take() { - // the worker has read the value of `stopping` - let _ = pending.send(Message::Stop); - } - } -} diff --git a/orchidlang/src/libs/std/arithmetic_error.rs b/orchidlang/src/libs/std/arithmetic_error.rs deleted file mode 100644 index 91d20ae..0000000 --- a/orchidlang/src/libs/std/arithmetic_error.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Error produced by numeric opperations - -use std::fmt; - -use crate::foreign::error::RTError; - -/// Various errors produced by arithmetic operations -#[derive(Clone)] -pub enum ArithmeticError { - /// Integer overflow - Overflow, - /// Float overflow - Infinity, - /// Division or modulo by zero - DivByZero, - /// Other, unexpected operation produced NaN - NaN, -} - -impl fmt::Display for ArithmeticError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NaN => write!(f, "Operation resulted in NaN"), - Self::Overflow => write!(f, "Integer overflow"), - Self::Infinity => write!(f, "Operation resulted in Infinity"), - Self::DivByZero => write!(f, "A division by zero was attempted"), - } - } -} - -impl RTError for ArithmeticError {} diff --git a/orchidlang/src/libs/std/binary.rs b/orchidlang/src/libs/std/binary.rs deleted file mode 100644 index 7582dc9..0000000 --- a/orchidlang/src/libs/std/binary.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! `std::binary` Operations on binary buffers. - -use std::fmt; -use std::ops::Deref; -use std::sync::Arc; - -use itertools::Itertools; - -use super::runtime_error::RuntimeError; -use crate::foreign::atom::Atomic; -use crate::foreign::error::RTResult; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; -use crate::interpreter::nort::Clause; -use crate::utils::iter_find::iter_find; -use crate::utils::unwrap_or::unwrap_or; - -const INT_BYTES: usize = usize::BITS as usize / 8; - -/// A block of binary data -#[derive(Clone, Hash, PartialEq, Eq)] -pub struct Binary(pub Arc>); -impl InertPayload for Binary { - const TYPE_STR: &'static str = "a binary blob"; -} - -impl Deref for Binary { - type Target = Vec; - fn deref(&self) -> &Self::Target { &self.0 } -} - -impl fmt::Debug for Binary { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut iter = self.0.iter().copied(); - f.write_str("Binary")?; - for mut chunk in iter.by_ref().take(32).chunks(4).into_iter() { - let a = chunk.next().expect("Chunks cannot be empty"); - let b = unwrap_or!(chunk.next(); return write!(f, "{a:02x}")); - let c = unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}")); - let d = unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}{c:02x}")); - write!(f, "{a:02x}{b:02x}{c:02x}{d:02x}")? - } - if iter.next().is_some() { write!(f, "...") } else { Ok(()) } - } -} - -/// Append two binary data blocks -pub fn concatenate(a: Inert, b: Inert) -> Inert { - let data = (*a).iter().chain(b.0.0.iter()).copied().collect(); - Inert(Binary(Arc::new(data))) -} - -/// Extract a subsection of the binary data -pub fn slice(s: Inert, i: Inert, len: Inert) -> RTResult> { - if i.0 + len.0 < s.0.0.len() { - RuntimeError::fail("Byte index out of bounds".to_string(), "indexing binary")? - } - Ok(Inert(Binary(Arc::new(s.0.0[i.0..i.0 + len.0].to_vec())))) -} - -/// Return the index where the first argument first contains the second, if any -pub fn find(haystack: Inert, needle: Inert) -> Option { - let found = iter_find(haystack.0.0.iter(), needle.0.0.iter()); - found.map(|i| Inert(i).atom_cls()) -} - -/// Split binary data block into two smaller blocks -pub fn split(bin: Inert, i: Inert) -> RTResult<(Inert, Inert)> { - if bin.0.0.len() < i.0 { - RuntimeError::fail("Byte index out of bounds".to_string(), "splitting binary")? - } - let (asl, bsl) = bin.0.0.split_at(i.0); - Ok((Inert(Binary(Arc::new(asl.to_vec()))), Inert(Binary(Arc::new(bsl.to_vec()))))) -} - -/// Read a number from a binary blob -pub fn get_num( - buf: Inert, - loc: Inert, - size: Inert, - is_le: Inert, -) -> RTResult> { - if buf.0.0.len() < (loc.0 + size.0) { - RuntimeError::fail("section out of range".to_string(), "reading number from binary data")? - } - if INT_BYTES < size.0 { - RuntimeError::fail( - "more than std::bin::int_bytes bytes provided".to_string(), - "reading number from binary data", - )? - } - let mut data = [0u8; INT_BYTES]; - let section = &buf.0.0[loc.0..(loc.0 + size.0)]; - let num = if is_le.0 { - data[0..size.0].copy_from_slice(section); - usize::from_le_bytes(data) - } else { - data[INT_BYTES - size.0..].copy_from_slice(section); - usize::from_be_bytes(data) - }; - Ok(Inert(num)) -} - -/// Convert a number into a blob -pub fn from_num( - size: Inert, - is_le: Inert, - data: Inert, -) -> RTResult> { - if INT_BYTES < size.0 { - RuntimeError::fail( - "more than std::bin::int_bytes bytes requested".to_string(), - "converting number to binary", - )? - } - let bytes = match is_le.0 { - true => data.0.to_le_bytes()[0..size.0].to_vec(), - false => data.0.to_be_bytes()[8 - size.0..].to_vec(), - }; - Ok(Inert(Binary(Arc::new(bytes)))) -} - -/// Detect the number of bytes in the blob -pub fn size(b: Inert) -> Inert { Inert(b.0.len()) } - -pub(super) fn bin_lib() -> ConstTree { - ConstTree::ns("std::binary", [ConstTree::tree([ - xfn_ent("concat", [concatenate]), - xfn_ent("slice", [slice]), - xfn_ent("find", [find]), - xfn_ent("split", [split]), - xfn_ent("get_num", [get_num]), - xfn_ent("from_num", [from_num]), - xfn_ent("size", [size]), - atom_ent("int_bytes", [Inert(INT_BYTES)]), - ])]) -} diff --git a/orchidlang/src/libs/std/bool.orc b/orchidlang/src/libs/std/bool.orc deleted file mode 100644 index c538c54..0000000 --- a/orchidlang/src/libs/std/bool.orc +++ /dev/null @@ -1,47 +0,0 @@ -import std::(pmatch, inspect) -import std::known::(=) - -export ::(!=, ==) - -export const not := \bool. if bool then false else true -macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b)) -macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b)) -export macro ...$a and ...$b =0x4p36=> (ifthenelse (...$a) (...$b) false) -export macro ...$a or ...$b =0x4p36=> (ifthenelse (...$a) true (...$b)) -export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> ( - ifthenelse (...$cond) (...$true) (...$false) -) - -( - macro pmatch::request (= ...$other) - =0x1p230=> pmatch::response ( - if pmatch::value == (...$other) - then pmatch::pass - else pmatch::fail - ) - ( pmatch::no_binds ) -) - -( - macro pmatch::request (!= ...$other) - =0x1p230=> pmatch::response ( - if pmatch::value != (...$other) - then pmatch::pass - else pmatch::fail - ) - ( pmatch::no_binds ) -) - -( - macro pmatch::request (true) - =0x1p230=> pmatch::response - (if pmatch::value then pmatch::pass else pmatch::fail) - ( pmatch::no_binds ) -) - -( - macro pmatch::request (false) - =0x1p230=> pmatch::response - (if pmatch::value then pmatch::fail else pmatch::pass) - ( pmatch::no_binds ) -) diff --git a/orchidlang/src/libs/std/bool.rs b/orchidlang/src/libs/std/bool.rs deleted file mode 100644 index 2a398cc..0000000 --- a/orchidlang/src/libs/std/bool.rs +++ /dev/null @@ -1,48 +0,0 @@ -use super::number::Numeric; -use super::string::OrcString; -use crate::foreign::error::{AssertionError, RTResult}; -use crate::foreign::inert::Inert; -use crate::foreign::try_from_expr::WithLoc; -use crate::gen::tpl; -use crate::gen::traits::{Gen, GenClause}; -use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort::Expr; - -const fn left() -> impl GenClause { tpl::L("l", tpl::L("_", tpl::P("l"))) } -const fn right() -> impl GenClause { tpl::L("_", tpl::L("r", tpl::P("r"))) } - -/// Takes a boolean and two branches, runs the first if the bool is true, the -/// second if it's false. -// Even though it's a ternary function, IfThenElse is implemented as an unary -// foreign function, as the rest of the logic can be defined in Orchid. -pub fn if_then_else(WithLoc(loc, b): WithLoc>) -> Expr { - let ctx = nort_gen(loc); - if b.0 { left().template(ctx, []) } else { right().template(ctx, []) } -} - -/// Compares the inner values if -/// -/// - both are string, -/// - both are bool, -/// - both are either uint or num -pub fn equals(WithLoc(loc, a): WithLoc, b: Expr) -> RTResult> { - Ok(Inert(if let Ok(l) = a.clone().downcast::>() { - b.downcast::>().is_ok_and(|r| *l == *r) - } else if let Ok(l) = a.clone().downcast::>() { - b.downcast::>().is_ok_and(|r| *l == *r) - } else if let Some(l) = a.clause.request::() { - b.clause.request::().is_some_and(|r| l.as_float() == r.as_float()) - } else { - AssertionError::fail(loc, "string, bool or numeric", format!("{a}"))? - })) -} - -pub fn bool_lib() -> ConstTree { - ConstTree::ns("std::bool", [ConstTree::tree([ - xfn_ent("ifthenelse", [if_then_else]), - xfn_ent("equals", [equals]), - atom_ent("true", [Inert(true)]), - atom_ent("false", [Inert(false)]), - ])]) -} diff --git a/orchidlang/src/libs/std/conv.rs b/orchidlang/src/libs/std/conv.rs deleted file mode 100644 index cb5dc6c..0000000 --- a/orchidlang/src/libs/std/conv.rs +++ /dev/null @@ -1,45 +0,0 @@ -use ordered_float::NotNan; - -use super::number::Numeric; -use super::string::OrcString; -use crate::foreign::error::{AssertionError, RTResult}; -use crate::foreign::inert::Inert; -use crate::foreign::try_from_expr::WithLoc; -use crate::gen::tpl; -use crate::gen::tree::{leaf, xfn_ent, ConstTree}; -use crate::interpreter::nort::ClauseInst; -use crate::parse::numeric::parse_num; - -fn to_numeric(WithLoc(loc, a): WithLoc) -> RTResult { - if let Some(n) = a.request::() { - return Ok(n); - } - if let Some(s) = a.request::() { - return parse_num(s.as_str()) - .map_err(|e| AssertionError::ext(loc, "number syntax", format!("{e:?}"))); - } - AssertionError::fail(loc, "string or number", format!("{a}")) -} - -/// parse a number. Accepts the same syntax Orchid does. -pub fn to_float(a: WithLoc) -> RTResult>> { - to_numeric(a).map(|n| Inert(n.as_float())) -} - -/// Parse an unsigned integer. Accepts the same formats Orchid does. If the -/// input is a number, floors it. -pub fn to_uint(a: WithLoc) -> RTResult> { - to_numeric(a).map(|n| match n { - Numeric::Float(f) => Inert(f.floor() as usize), - Numeric::Uint(i) => Inert(i), - }) -} - -pub fn conv_lib() -> ConstTree { - ConstTree::ns("std", [ConstTree::tree([ConstTree::tree_ent("conv", [ - xfn_ent("to_float", [to_float]), - xfn_ent("to_uint", [to_uint]), - // conversion logic moved to the string library - ("to_string", leaf(tpl::C("std::string::convert"))), - ])])]) -} diff --git a/orchidlang/src/libs/std/cross_pipeline.rs b/orchidlang/src/libs/std/cross_pipeline.rs deleted file mode 100644 index bdcc49a..0000000 --- a/orchidlang/src/libs/std/cross_pipeline.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::iter; -use std::rc::Rc; - -use itertools::Itertools; - -use crate::foreign::atom::Atomic; -use crate::foreign::fn_bridge::xfn; -use crate::foreign::process::Unstable; -use crate::foreign::to_clause::ToClause; -use crate::foreign::try_from_expr::{TryFromExpr, WithLoc}; -use crate::location::SourceRange; -use crate::parse::parsed::{self, PType}; -use crate::utils::pure_seq::pushed; - -pub trait DeferredRuntimeCallback: - FnOnce(Vec) -> R + Clone + Send + 'static -{ -} -impl) -> R + Clone + Send + 'static> DeferredRuntimeCallback - for F -{ -} - -/// Lazy-recursive function that takes the next value from the interpreter -/// and acts upon it -/// -/// # Panics -/// -/// If the list of remaining keys is empty -fn table_receiver_rec( - results: Vec, - items: usize, - callback: impl DeferredRuntimeCallback, -) -> impl Atomic { - xfn("__table_receiver__", move |WithLoc(loc, t): WithLoc| { - let results: Vec = pushed(results, t); - match items == results.len() { - true => callback(results).to_clause(loc), - false => table_receiver_rec(results, items, callback).atom_cls(), - } - }) -} - -fn table_receiver( - items: usize, - callback: impl DeferredRuntimeCallback, -) -> parsed::Clause { - if items == 0 { - Unstable::new(move |_| callback(Vec::new())).ast_cls() - } else { - Unstable::new(move |_| table_receiver_rec(Vec::new(), items, callback).atom_cls()).ast_cls() - } -} - -/// Defers the execution of the callback to runtime, allowing it to depend on -/// the result of Otchid expressions. -pub fn defer_to_runtime( - range: SourceRange, - exprs: impl Iterator>, - callback: impl DeferredRuntimeCallback, -) -> parsed::Clause { - let argv = exprs.into_iter().map(|v| parsed::Clause::S(PType::Par, Rc::new(v))).collect_vec(); - let items = iter::once(table_receiver(argv.len(), callback)).chain(argv); - parsed::Clause::s('(', items, range) -} diff --git a/orchidlang/src/libs/std/exit_status.rs b/orchidlang/src/libs/std/exit_status.rs deleted file mode 100644 index 9441f23..0000000 --- a/orchidlang/src/libs/std/exit_status.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! `std::exit_status` Exit status of a program or effectful subprogram. -//! -//! There is no support for custom codes, and the success/failure state can be -//! inspected. - -use std::process::ExitCode; - -use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; - -/// An Orchid equivalent to Rust's binary exit status model -/// -/// The "namespaced" name is to easily separate in autocomplete lists from both -/// [std::process::ExitStatus] and [std::process::ExitCode] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum OrcExitStatus { - /// unix exit code 0 - Success, - /// unix exit code 1 - Failure, -} -impl OrcExitStatus { - /// Convert to Rust-land [ExitCode] - pub fn code(self) -> ExitCode { - match self { - Self::Success => ExitCode::SUCCESS, - Self::Failure => ExitCode::FAILURE, - } - } -} - -impl InertPayload for OrcExitStatus { - const TYPE_STR: &'static str = "ExitStatus"; -} - -pub(super) fn exit_status_lib() -> ConstTree { - let is_success = |es: Inert| Inert(es.0 == OrcExitStatus::Success); - ConstTree::ns("std::exit_status", [ConstTree::tree([ - atom_ent("success", [Inert(OrcExitStatus::Success)]), - atom_ent("failure", [Inert(OrcExitStatus::Failure)]), - xfn_ent("is_success", [is_success]), - ])]) -} diff --git a/orchidlang/src/libs/std/fn.orc b/orchidlang/src/libs/std/fn.orc deleted file mode 100644 index c60be0b..0000000 --- a/orchidlang/src/libs/std/fn.orc +++ /dev/null @@ -1,43 +0,0 @@ -import super::known::* -import super::pmatch -import super::pmatch::(match, =>) -import super::macro - ---[ Do nothing. Especially useful as a passive cps operation ]-- -export const identity := \x.x ---[ - Apply the function to the given value. Can be used to assign a - concrete value in a cps assignment statement. -]-- -export const pass := \val. \cont. cont val ---[ - Apply the function to the given pair of values. Mainly useful to assign - a concrete pair of values in a cps multi-assignment statement -]-- -export const pass2 := \a. \b. \cont. cont a b ---[ - A function that returns the given value for any input. Also useful as a - "break" statement in a "do" block. -]-- -export const return := \a. \b.a - -export macro ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix) -export macro ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix - -( macro (..$argv) => ...$body - =0x3p127=> lambda_walker macro::comma_list (..$argv) (...$body) -) -( macro $_arg => ...$body - =0x2p127=> \$_arg. ...$body -) -( macro lambda_walker ( macro::list_item ($_argname) $tail ) $body - =0x2p254=> \$_argname. lambda_walker $tail $body -) -( macro lambda_walker ( macro::list_item (...$head) $tail ) $body - =0x1p254=> \arg. match arg { - ...$head => lambda_walker $tail $body; - } -) -( macro lambda_walker macro::list_end $body - =0x1p254=> $body -) diff --git a/orchidlang/src/libs/std/inspect.rs b/orchidlang/src/libs/std/inspect.rs deleted file mode 100644 index 05f440b..0000000 --- a/orchidlang/src/libs/std/inspect.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::foreign::fn_bridge::Thunk; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::nort::Expr; - -pub fn inspect_lib() -> ConstTree { - ConstTree::ns("std", [ConstTree::tree([ - xfn_ent("inspect", [|x: Thunk| { - eprintln!("{}", x.0); - x.0 - }]), - xfn_ent("tee", [|x: Expr| { - eprintln!("{x}"); - x - }]), - ])]) -} diff --git a/orchidlang/src/libs/std/known.orc b/orchidlang/src/libs/std/known.orc deleted file mode 100644 index 1f26b8e..0000000 --- a/orchidlang/src/libs/std/known.orc +++ /dev/null @@ -1 +0,0 @@ -export ::[, _ ; . =] diff --git a/orchidlang/src/libs/std/list.orc b/orchidlang/src/libs/std/list.orc deleted file mode 100644 index 68b622e..0000000 --- a/orchidlang/src/libs/std/list.orc +++ /dev/null @@ -1,139 +0,0 @@ -import super::(option, tuple, tuple::t, panic, pmatch, pmatch::=>, macro, tee) -import super::(fn::*, procedural::*) -import super::(loop::*, bool::*, known::*, number::*) - -as_type () - -export const cons := \hd. \tl. wrap (option::some t[hd, unwrap tl]) -export const end := wrap option::none -export const pop := \list. \default. \f. ( - pmatch::match (unwrap list) { - option::none => default; - option::some t[hd, tl] => f hd (wrap tl); - } -) - --- Operators - ---[ Fold each element into an accumulator using an `acc -> el -> acc`. #eager ]-- -export const fold := \list. \acc. \f. ( - loop_over (list, acc) { - cps head, list = pop list acc; - let acc = f acc head; - } -) - ---[ Fold each element into an accumulator in reverse order. #eager-notail ]-- -export const rfold := \list. \acc. \f. ( - recursive r (list) - pop list acc \head. \tail. - f (r tail) head -) - ---[ Reverse a list. #eager ]-- -export const reverse := \list. fold list end \tl. \hd. cons hd tl - ---[ Fold each element into a shared element with an `el -> el -> el`. #eager-notail ]-- -export const reduce := \list. \f. do{ - cps head, list = pop list option::none; - option::some $ fold list head f -} - ---[ - Return a new list that contains only the elements from the input list - for which the function returns true. #lazy -]-- -export const filter := \list. \f. ( - pop list end \head. \tail. - if (f head) - then cons head (filter tail f) - else filter tail f -) - ---[ Transform each element of the list with an `el -> any`. #lazy ]-- -export const map := \list. \f. ( - recursive r (list) - pop list end \head. \tail. - cons (f head) (r tail) -) - ---[ Skip `n` elements from the list and return the tail. #lazy ]-- -export const skip := \foo. \n. ( - loop_over (foo, n) { - cps _head, foo = if n <= 0 - then return foo - else pop foo end; - let n = n - 1; - } -) - ---[ Return `n` elements from the list and discard the rest. #lazy ]-- -export const take := \list. \n. ( - recursive r (list, n) - if n == 0 - then end - else pop list end \head. \tail. - cons head $ r tail $ n - 1 -) - ---[ Return the `n`th element from the list. #eager ]-- -export const get := \list. \n. ( - loop_over (list, n) { - cps head, list = pop list option::none; - cps if n == 0 - then return (option::some head) - else identity; - let n = n - 1; - } -) - ---[ Map every element to a pair of the index and the original element. #lazy ]-- -export const enumerate := \list. ( - recursive r (list, n = 0) - pop list end \head. \tail. - cons t[n, head] $ r tail $ n + 1 -) - -export const count := \list. fold list 0 \a. \n. a + 1 - ---[ - Turn a list of CPS commands into a sequence. This is achieved by calling every - element on the return value of the next element with the tail passed to it. - The continuation is passed to the very last argument. #lazy -]-- -export const chain := \list. \cont. loop_over (list) { - cps head, list = pop list cont; - cps head; -} - -macro new[..$items] =0x2p84=> mk_list macro::comma_list (..$items) - -macro mk_list ( macro::list_item $item $tail ) =0x1p254=> (cons $item mk_list $tail) -macro mk_list macro::list_end =0x1p254=> end - -export ::(new) - -( macro pmatch::request (cons $head $tail) - =0x1p230=> await_subpatterns - (pmatch::request ($head)) - (pmatch::request ($tail)) -) -( macro await_subpatterns - (pmatch::response $h_expr ( $h_binds )) - (pmatch::response $t_expr ( $t_binds )) - =0x1p230=> pmatch::response ( - pop - pmatch::value - pmatch::fail - \head. \tail. ( - (\pmatch::pass. (\pmatch::value. $h_expr) head) - (pmatch::take_binds $h_binds ( - (\pmatch::pass. (\pmatch::value. $t_expr) tail) - (pmatch::take_binds $t_binds ( - pmatch::give_binds (pmatch::chain_binds $h_binds $t_binds) pmatch::pass - )) - )) - ) - ) - ( (pmatch::chain_binds $h_binds $t_binds) ) -) diff --git a/orchidlang/src/libs/std/loop.orc b/orchidlang/src/libs/std/loop.orc deleted file mode 100644 index 2f9e57c..0000000 --- a/orchidlang/src/libs/std/loop.orc +++ /dev/null @@ -1,75 +0,0 @@ -import super::procedural::* -import super::bool::* -import super::fn::(return, identity) -import super::known::* - ---[ - Bare fixpoint combinator. Due to its many pitfalls, usercode is - recommended to use one of the wrappers such as [recursive] or - [loop_over] instead. -]-- -export const Y := \f.(\x.f (x x))(\x.f (x x)) - ---[ - A syntax construct that encapsulates the Y combinator and encourages - single tail recursion. It's possible to use this for multiple or - non-tail recursion by using cps statements, but it's more ergonomic - than [Y] and more flexible than [std::list::fold]. - - To break out of the loop, use [std::fn::return] in a cps statement -]-- -export macro loop_over (..$binds) { - ...$body -} =0x5p129=> Y (\r. - def_binds parse_binds (..$binds) do{ - ...$body; - r apply_binds parse_binds (..$binds) - } -) init_binds parse_binds (..$binds) - --- parse_binds builds a conslist -macro parse_binds (...$item, ...$tail:1) =0x2p250=> ( - parse_bind (...$item) - parse_binds (...$tail) -) -macro parse_binds (...$item) =0x1p250=> ( - parse_bind (...$item) - () -) - --- while loop -export macro statement ( - while ..$condition (..$binds) { - ...$body - } -) $next =0x5p129=> loop_over (..$binds) { - cps if (..$condition) then identity else return $next; - ...$body; -} - --- parse_bind converts items to pairs -macro parse_bind ($name) =0x1p250=> ($name bind_no_value) -macro parse_bind ($name = ...$value) =0x1p250=> ($name (...$value)) - --- def_binds creates name bindings for everything -macro def_binds ( ($name $value) $tail ) ...$body =0x1p250=> ( - \$name. def_binds $tail ...$body -) -macro def_binds () ...$body =0x1p250=> ...$body - --- init_binds passes the value for initializers -macro init_binds ( ($name bind_no_value) $tail ) =0x2p250=> $name init_binds $tail -macro init_binds ( ($name $value) $tail ) =0x1p250=> $value init_binds $tail --- avoid empty templates by assuming that there is a previous token -macro $fn init_binds () =0x1p250=> $fn - --- apply_binds passes the name for initializers -macro apply_binds ( ($name $value) $tail ) =0x1p250=> $name apply_binds $tail -macro $fn apply_binds () =0x1p250=> $fn - ---[ - Alias for the Y-combinator to avoid some universal pitfalls -]-- -export macro recursive $name (..$binds) ...$body =0x5p129=> Y (\$name. - def_binds parse_binds (..$binds) ...$body -) init_binds parse_binds (..$binds) diff --git a/orchidlang/src/libs/std/macro.orc b/orchidlang/src/libs/std/macro.orc deleted file mode 100644 index e85bb10..0000000 --- a/orchidlang/src/libs/std/macro.orc +++ /dev/null @@ -1,68 +0,0 @@ -import std::number::add -import std::known::* - --- convert a comma-separated list into a linked list, with support for trailing commas -export ::comma_list -( macro comma_list ( ...$head, ...$tail:1 ) - =0x2p254=> ( await_comma_list ( ...$head ) comma_list ( ...$tail ) ) -) -( macro comma_list (...$only) - =0x1p254=> ( list_item (...$only) list_end ) -) -( macro ( await_comma_list $head $tail ) - =0x2p254=> ( list_item $head $tail ) -) -( macro comma_list () - =0x1p254=> list_end -) -( macro comma_list (...$data,) - =0x3p254=> comma_list (...$data) -) - --- convert a comma-separated list into a linked list, with support for trailing commas -export ::semi_list -( macro semi_list ( ...$head; ...$tail:1 ) - =0x2p254=> ( await_semi_list ( ...$head ) semi_list ( ...$tail ) ) -) -( macro semi_list (...$only) - =0x1p254=> ( list_item (...$only) list_end ) -) -( macro ( await_semi_list $head $tail ) - =0x2p254=> ( list_item $head $tail ) -) -( macro semi_list () - =0x1p254=> list_end -) -( macro semi_list (...$data;) - =0x3p254=> semi_list (...$data) -) - --- calculate the length of a linked list -export ::length -( macro length ( list_item $discard $tail ) - =0x1p254=> await_length ( length $tail ) -) -( macro await_length ( $len ) - =0x1p254=> (add 1 $len) -) -macro length list_end =0x1p254=> (0) - - -export ::error -( macro ( ..$prefix error $details ..$suffix ) - =0x2p255=> error $details -) -( macro [ ..$prefix error $details ..$suffix ] - =0x2p255=> error $details -) -( macro { ..$prefix error $details ..$suffix } - =0x2p255=> error $details -) -( macro error $details - =0x1p255=> -) - -export ::leftover_error -( macro leftover_error $details - =0x1p255=> error ( "Token fails to parse" $details ) -) diff --git a/orchidlang/src/libs/std/map.orc b/orchidlang/src/libs/std/map.orc deleted file mode 100644 index 50c2fa3..0000000 --- a/orchidlang/src/libs/std/map.orc +++ /dev/null @@ -1,94 +0,0 @@ -import super::(bool::*, fn::*, known::*, loop::*, procedural::*, string::*) -import super::(panic, pmatch, macro, option, list, string, tuple, conv, pmatch::[=>]) - -as_type ( - impl string::conversion := \map. "map[" ++ ( - unwrap map - |> list::map ( - (tuple::t[k, v]) => conv::to_string k ++ " = " ++ conv::to_string v - ) - |> list::reduce (\l. \r. l ++ ", " ++ r) - |> option::fallback "" - ) ++ "]" -) - ---[ Constructors ]-- - -const empty := wrap list::end -const add := \m. \k. \v. wrap ( - list::cons - tuple::t[k, v] - (unwrap m) -) - ---[ List constructor ]-- - -export ::new -macro new[..$items] =0x2p84=> mk_map macro::comma_list (..$items) - -macro mk_map macro::list_end =0x1p254=> empty -( macro mk_map ( macro::list_item ( ...$key = ...$value:1 ) $tail ) - =0x1p254=> ( set mk_map $tail (...$key) (...$value) ) -) - ---[ Queries ]-- - --- return the last occurrence of a key if exists -export const get := \m. \key. ( - loop_over (m=unwrap m) { - cps record, m = list::pop m option::none; - cps if tuple::pick record 0 == key - then return $ option::some $ tuple::pick record 1 - else identity; - } -) - ---[ Commands ]-- - --- remove one occurrence of a key -export const del := \m. \k. wrap ( - recursive r (m=unwrap m) - list::pop m list::end \head. \tail. - if tuple::pick head 0 == k then tail - else list::cons head $ r tail -) - --- replace at most one occurrence of a key -export const set := \m. \k. \v. m |> del k |> add k v - -export ::having -( macro pmatch::request (having [..$items]) - =0x1p230=> having_pattern ( - pattern_walker - macro::comma_list ( ..$items ) - ) -) -( macro having_pattern ( tail_result $expr ( $binds ) ) - =0x1p254=> pmatch::response $expr ( $binds ) -) -( macro pattern_walker macro::list_end - =0x1p254=> tail_result pmatch::pass ( pmatch::no_binds ) -) -( macro pattern_walker ( macro::list_item ( ...$key = ...$value:1 ) $tail ) - =0x1p254=> await_pattern ( ...$key ) - ( pmatch::request (...$value) ) - ( pattern_walker $tail ) -) -( macro await_pattern $key - ( pmatch::response $expr ( $binds ) ) - ( tail_result $t_expr ( $t_binds ) ) - =0x1p254=> tail_result ( - option::handle (get pmatch::value $key) - pmatch::fail - \value. (\pmatch::pass. (\pmatch::value. $expr) value) ( - pmatch::take_binds $binds ( - (\pmatch::pass. $t_expr) ( - pmatch::take_binds $t_binds ( - pmatch::give_binds (pmatch::chain_binds $binds $t_binds) pmatch::pass - ) - ) - ) - ) - ) - ( (pmatch::chain_binds $binds $t_binds) ) -) diff --git a/orchidlang/src/libs/std/mod.rs b/orchidlang/src/libs/std/mod.rs deleted file mode 100644 index 809033e..0000000 --- a/orchidlang/src/libs/std/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Basic types and their functions, frequently used tools with no environmental -//! dependencies. -pub mod arithmetic_error; -pub mod binary; -mod bool; -mod conv; -mod cross_pipeline; -pub mod exit_status; -mod inspect; -pub mod number; -mod panic; -pub mod protocol; -pub mod reflect; -pub mod runtime_error; -mod state; -pub mod std_system; -pub mod string; -pub mod tuple; diff --git a/orchidlang/src/libs/std/number.orc b/orchidlang/src/libs/std/number.orc deleted file mode 100644 index 0db56e4..0000000 --- a/orchidlang/src/libs/std/number.orc +++ /dev/null @@ -1,16 +0,0 @@ -import super::bool::* - -export ::(+, -, [*], %, /, <, >, <=, >=) - -const less_than_or_equal := \a. \b. a < b or a == b - -macro ...$a + ...$b =0x2p36=> (add (...$a) (...$b)) -macro ...$a:1 - ...$b =0x2p36=> (subtract (...$a) (...$b)) -macro ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b)) -macro ...$a:1 % ...$b =0x1p36=> (remainder (...$a) (...$b)) -macro ...$a:1 / ...$b =0x1p36=> (divide (...$a) (...$b)) -macro ...$a:1 < ...$b =0x3p36=> (less_than (...$a) (...$b)) -macro ...$a:1 > ...$b =0x3p36=> ((...$b) < (...$a)) -macro ...$a:1 <= ...$b =0x3p36=> (less_than_or_equal (...$a) (...$b)) -macro ...$a:1 >= ...$b =0x3p36=> ((...$b) <= (...$a)) - diff --git a/orchidlang/src/libs/std/number.rs b/orchidlang/src/libs/std/number.rs deleted file mode 100644 index 0a6c583..0000000 --- a/orchidlang/src/libs/std/number.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! `std::number` Numeric operations. - -use ordered_float::NotNan; - -use super::arithmetic_error::ArithmeticError; -use crate::foreign::atom::Atomic; -use crate::foreign::error::{AssertionError, RTError, RTResult}; -use crate::foreign::inert::Inert; -use crate::foreign::to_clause::ToClause; -use crate::foreign::try_from_expr::TryFromExpr; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::nort::{Clause, Expr}; -use crate::location::CodeLocation; - -/// A number, either floating point or unsigned int, visible to Orchid. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Numeric { - /// A nonnegative integer such as a size, index or count - Uint(usize), - /// A float other than NaN. Orchid has no silent errors - Float(NotNan), -} - -impl Numeric { - /// Return the enclosed float, or cast the enclosed int to a float - pub fn as_f64(&self) -> f64 { - match self { - Numeric::Float(n) => **n, - Numeric::Uint(i) => *i as f64, - } - } - - /// Returns the enclosed [NotNan], or casts and wraps the enclosed int - pub fn as_float(&self) -> NotNan { - match self { - Numeric::Float(n) => *n, - Numeric::Uint(i) => NotNan::new(*i as f64).expect("ints cannot cast to NaN"), - } - } - - /// Wrap a f64 in a Numeric - pub fn new(value: f64) -> RTResult { - match value.is_finite() { - false => Err(ArithmeticError::Infinity.pack()), - true => match NotNan::new(value) { - Ok(f) => Ok(Self::Float(f)), - Err(_) => Err(ArithmeticError::NaN.pack()), - }, - } - } -} -impl TryFromExpr for Numeric { - fn from_expr(exi: Expr) -> RTResult { - (exi.clause.request()) - .ok_or_else(|| AssertionError::ext(exi.location(), "a numeric value", format!("{exi}"))) - } -} - -impl ToClause for Numeric { - fn to_clause(self, _: CodeLocation) -> Clause { - match self { - Numeric::Uint(i) => Inert(i).atom_cls(), - Numeric::Float(n) => Inert(n).atom_cls(), - } - } -} - -/// Add two numbers. If they're both uint, the output is uint. If either is -/// number, the output is number. -pub fn add(a: Numeric, b: Numeric) -> RTResult { - match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => - a.checked_add(b).map(Numeric::Uint).ok_or_else(|| ArithmeticError::Overflow.pack()), - (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a + b)), - (Numeric::Float(a), Numeric::Uint(b)) | (Numeric::Uint(b), Numeric::Float(a)) => - Numeric::new(*a + b as f64), - } -} - -/// Subtract a number from another. Always returns Number. -pub fn subtract(a: Numeric, b: Numeric) -> RTResult { - match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::new(a as f64 - b as f64), - (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a - b)), - (Numeric::Float(a), Numeric::Uint(b)) => Numeric::new(*a - b as f64), - (Numeric::Uint(a), Numeric::Float(b)) => Numeric::new(a as f64 - *b), - } -} - -/// Multiply two numbers. If they're both uint, the output is uint. If either -/// is number, the output is number. -pub fn multiply(a: Numeric, b: Numeric) -> RTResult { - match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => - a.checked_mul(b).map(Numeric::Uint).ok_or_else(|| ArithmeticError::Overflow.pack()), - (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a * b)), - (Numeric::Uint(a), Numeric::Float(b)) | (Numeric::Float(b), Numeric::Uint(a)) => - Numeric::new(a as f64 * *b), - } -} - -/// Divide a number by another. Always returns Number. -pub fn divide(a: Numeric, b: Numeric) -> RTResult { - let a: f64 = a.as_f64(); - let b: f64 = b.as_f64(); - if b == 0.0 { - return Err(ArithmeticError::DivByZero.pack()); - } - Numeric::new(a / b) -} - -/// Take the remainder of two numbers. If they're both uint, the output is -/// uint. If either is number, the output is number. -pub fn remainder(a: Numeric, b: Numeric) -> RTResult { - match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => - a.checked_rem(b).map(Numeric::Uint).ok_or_else(|| ArithmeticError::DivByZero.pack()), - (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a % b)), - (Numeric::Uint(a), Numeric::Float(b)) => Numeric::new(a as f64 % *b), - (Numeric::Float(a), Numeric::Uint(b)) => Numeric::new(*a % b as f64), - } -} - -/// Tries to use integer comparison, casts to float otherwise -pub fn less_than(a: Numeric, b: Numeric) -> Inert { - match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => Inert(a < b), - (a, b) => Inert(a.as_f64() < b.as_f64()), - } -} - -pub(super) fn num_lib() -> ConstTree { - ConstTree::ns("std::number", [ConstTree::tree([ - xfn_ent("add", [add]), - xfn_ent("subtract", [subtract]), - xfn_ent("multiply", [multiply]), - xfn_ent("divide", [divide]), - xfn_ent("remainder", [remainder]), - xfn_ent("less_than", [less_than]), - ])]) -} diff --git a/orchidlang/src/libs/std/option.orc b/orchidlang/src/libs/std/option.orc deleted file mode 100644 index b635249..0000000 --- a/orchidlang/src/libs/std/option.orc +++ /dev/null @@ -1,42 +0,0 @@ -import std::(panic, pmatch, string, conv) -import std::(fn::*, string::*) - -as_type ( - impl string::conversion := \opt. ( - handle opt "none" \x. "some(" ++ conv::to_string x ++ ")" - ) -) - -export const some := \v. wrap \d. \f. f v -export const none := wrap \d. \f. d - -export const handle := \t. \d. \f. (unwrap t) d f - -export const map := \option. \f. handle option none \x. some $ f x -export const fallback := \option. \fallback. handle option fallback \data. data -export const flatten := \option. handle option none \opt. wrap $ unwrap opt -- assert type -export const flatmap := \option. \f. handle option none \opt. wrap $ unwrap $ f opt -- assert return -export const assume := \option. handle option (panic "value expected") \x.x - -( - macro pmatch::request ( none ) - =0x1p230=> pmatch::response ( - handle pmatch::value - pmatch::pass - \_. pmatch::fail - ) ( pmatch::no_binds ) -) - -( - macro pmatch::request ( some ...$value ) - =0x1p230=> await_some_subpattern ( pmatch::request (...$value) ) -) - -( - macro await_some_subpattern ( pmatch::response $expr ( $binds ) ) - =0x1p254=> pmatch::response ( - handle pmatch::value - pmatch::fail - \pmatch::value. $expr - ) ( $binds ) -) diff --git a/orchidlang/src/libs/std/panic.rs b/orchidlang/src/libs/std/panic.rs deleted file mode 100644 index 0aa4997..0000000 --- a/orchidlang/src/libs/std/panic.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::fmt; -use std::sync::Arc; - -use never::Never; - -use super::string::OrcString; -use crate::foreign::error::{RTError, RTResult}; -use crate::foreign::inert::Inert; -use crate::gen::tree::{xfn_leaf, ConstTree}; - -/// An unrecoverable error in Orchid land. Because Orchid is lazy, this only -/// invalidates expressions that reference the one that generated it. -#[derive(Clone)] -pub struct OrchidPanic(Arc); - -impl fmt::Display for OrchidPanic { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Orchid code panicked: {}", self.0) - } -} - -impl RTError for OrchidPanic {} - -/// Takes a message, returns an [ExternError] unconditionally. -pub fn orc_panic(msg: Inert) -> RTResult { - // any return value would work, but Clause is the simplest - Err(OrchidPanic(Arc::new(msg.0.get_string())).pack()) -} - -pub fn panic_lib() -> ConstTree { ConstTree::ns("std::panic", [xfn_leaf(orc_panic)]) } diff --git a/orchidlang/src/libs/std/pmatch.orc b/orchidlang/src/libs/std/pmatch.orc deleted file mode 100644 index 5b51e56..0000000 --- a/orchidlang/src/libs/std/pmatch.orc +++ /dev/null @@ -1,105 +0,0 @@ -import std::known::(_, ;) -import std::procedural -import std::bool -import std::macro -import std::panic - ---[ - The protocol: - - Request contains the pattern - Response contains an expression and the list of names -]-- - -export ::(match, value, pass, fail, request, response, =>) - -( - macro ..$prefix:1 match ...$argument:0 { ..$body } ..$suffix:1 - =0x1p130=> ..$prefix ( - (\value. match_walker macro::semi_list ( ..$body ) ) - ( ...$argument ) - ) ..$suffix -) - -macro match_walker macro::list_end =0x1p254=> panic "no arms match" -( macro match_walker ( macro::list_item (...$pattern => ...$handler:1) $tail ) - =0x1p254=> match_await ( request (...$pattern) ) (...$handler) ( match_walker $tail ) -) -( macro match_await ( response $expr ( $binds ) ) $handler $tail - =0x1p254=> (\fail. (\pass. $expr) (take_binds $binds $handler)) $tail -) - -macro request (( ..$pattern )) =0x1p254=> request ( ..$pattern ) - --- bindings list - -export ::(no_binds, add_bind, chain_binds, give_binds, take_binds) - -macro ( add_bind $_new no_binds ) =0x1p254=> ( binds_list $_new no_binds ) -( macro ( add_bind $_new (binds_list ...$tail) ) - =0x1p254=> ( binds_list $_new (binds_list ...$tail) ) -) -macro ( give_binds no_binds $cont ) =0x1p254=> $cont -( macro ( give_binds ( binds_list $_name $tail ) $cont ) - =0x1p254=> (( give_binds $tail $cont ) $_name ) -) -macro ( take_binds no_binds $cont ) =0x1p254=> $cont -( macro ( take_binds ( binds_list $_name $tail ) $cont ) - =0x1p254=> ( take_binds $tail \$_name. $cont ) -) -macro ( chain_binds no_binds $second ) =0x1p254=> $second -( macro ( chain_binds ( binds_list $_head $tail ) $second ) - =0x1p254=> ( add_bind $_head ( chain_binds $tail $second )) -) - ---[ primitive pattern ( _ ) ]-- - -( - macro request ( _ ) - =0x1p230=> response pass ( no_binds ) -) - ---[ primitive name pattern ]-- - -( - macro request ( $_name ) - =0x1p226=> response ( pass value ) ( ( add_bind $_name no_binds ) ) -) - ---[ primitive pattern ( and ) ]-- - -( macro request ( ...$lhs bool::and ...$rhs ) - =0x3p230=> await_and_subpatterns ( request (...$lhs ) ) ( request ( ...$rhs ) ) -) - -( macro await_and_subpatterns ( response $lh_expr ( $lh_binds ) ) ( response $rh_expr ( $rh_binds ) ) - =0x1p254=> response ( - (\pass. $lh_expr) (take_binds $lh_binds ( - (\pass. $rh_expr) (take_binds $rh_binds ( - give_binds (chain_binds $lh_binds $rh_binds) pass - )) - )) - ) - ( (chain_binds $lh_binds $rh_binds) ) -) - ---[ primitive pattern ( or ) ]-- - -( - macro request ( ...$lhs bool::or ...$rhs ) - =0x3p230=> await_or_subpatterns - ( request ( ...$lhs ) ) - ( request ( ...$rhs ) ) -) - -( -- for this to work, lh and rh must produce the same bindings - macro await_or_subpatterns ( response $lh_expr ( $lh_binds) ) ( response $rh_expr ( $rh_binds ) ) - =0x1p254=> response ( - (\fail. $lh_expr) -- lh works with pass directly because its bindings are reported up - ($rh_expr (take_binds $rh_binds -- rh runs if lh cancels - (give_binds $lh_binds pass) -- translate rh binds to lh binds - )) - ) - ( $lh_binds ) -- report lh bindings -) - diff --git a/orchidlang/src/libs/std/prelude.orc b/orchidlang/src/libs/std/prelude.orc deleted file mode 100644 index 6974bf7..0000000 --- a/orchidlang/src/libs/std/prelude.orc +++ /dev/null @@ -1,25 +0,0 @@ -import std::number::* -export ::[+ - * / % < > <= >=] -import std::string::* -export ::[++] -import std::bool::* -export ::([== !=], if, then, else, true, false, and, or, not) -import std::fn::* -export ::([$ |>], identity, pass, pass2, return) -import std::procedural::* -export ::(do, let, cps) -import std::tuple::t -export ::(t) -import std::pmatch::(match, [=>]) -export ::(match, [=>]) -import std::loop::* -export ::(loop_over, recursive, while) - -import std::known::* -export ::[, _ ; . =] - -import std::(tuple, list, map, option, exit_status) -export ::(tuple, list, map, option, exit_status) - -import std -export ::(std) diff --git a/orchidlang/src/libs/std/procedural.orc b/orchidlang/src/libs/std/procedural.orc deleted file mode 100644 index 5dad872..0000000 --- a/orchidlang/src/libs/std/procedural.orc +++ /dev/null @@ -1,36 +0,0 @@ -import super::pmatch::=> -import super::known::* - -export ::(do, statement, [;]) - --- remove duplicate ;-s -macro do { - ...$statement ; ; ...$rest:1 -} =0x3p130=> do { - ...$statement ; ...$rest -} --- modular operation block that returns a value -macro do { - ...$statement ; ...$rest:1 -} =0x2p130=> statement (...$statement) (do { ...$rest }) -macro do { ...$return } =0x1p130=> (...$return) - -export ::let - -macro statement (let $_name = ...$value) (...$next) =0x2p230=> ( - ( \$_name. ...$next) (...$value) -) -macro statement (let ...$pattern = ...$value:1) (...$next) =0x1p230=> ( - ( (...$pattern) => (...$next) ) (...$value) -) - -export ::cps - --- modular operation block that returns a CPS function -macro do cps { ...$body } =0x1p130=> \cont. do { ...$body ; cont } -macro statement (cps ...$names = ...$operation:1) (...$next) =0x2p230=> ( - (...$operation) ( (...$names) => ...$next ) -) -macro statement (cps ...$operation) (...$next) =0x1p230=> ( - (...$operation) (...$next) -) diff --git a/orchidlang/src/libs/std/protocol.orc b/orchidlang/src/libs/std/protocol.orc deleted file mode 100644 index 6e353da..0000000 --- a/orchidlang/src/libs/std/protocol.orc +++ /dev/null @@ -1,8 +0,0 @@ -import std::(map, option, fn::*) - -export const vcall := \proto. \key. \val. ( - resolve proto val - |> map::get key - |> option::assume - $ break val -) diff --git a/orchidlang/src/libs/std/protocol.rs b/orchidlang/src/libs/std/protocol.rs deleted file mode 100644 index c621a11..0000000 --- a/orchidlang/src/libs/std/protocol.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! Polymorphism through Elixir-style protocols associated with type-tagged -//! values. A type-tag seals the value, and can only be unwrapped explicitly by -//! providing the correct type. This ensures that code that uses this explicit -//! polymorphism never uses the implicit polymorphism of dynamic typing. -//! -//! Atoms can also participate in this controlled form of polymorphism by -//! offering a [Tag] in their [crate::utils::ddispatch::Responder] callback. -//! -//! Protocols and types are modules with magic elements that distinguish them -//! from regular modules. - -use std::sync::Arc; -use std::{fmt, iter}; - -use const_format::formatcp; -use hashbrown::HashMap; -use intern_all::{i, Tok}; -use itertools::Itertools; - -use super::cross_pipeline::defer_to_runtime; -use super::reflect::refer_seq; -use super::runtime_error::RuntimeError; -use crate::error::ProjectResult; -use crate::foreign::atom::Atomic; -use crate::foreign::error::RTResult; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::foreign::process::Unstable; -use crate::gen::tpl; -use crate::gen::traits::GenClause; -use crate::gen::tree::{atom_ent, leaf, xfn_ent, ConstTree}; -use crate::interpreter::nort; -use crate::interpreter::nort::ClauseInst; -use crate::libs::parse_custom_line::custom_line; -use crate::location::SourceRange; -use crate::name::{Sym, VName}; -use crate::parse::frag::Frag; -use crate::parse::lexer::Lexeme; -use crate::parse::parse_plugin::{ParseLinePlugin, ParsePluginReq}; -use crate::parse::parsed::{ - self, Constant, Member, MemberKind, ModuleBlock, PType, SourceLine, SourceLineKind, -}; -use crate::utils::ddispatch::Request; - -// TODO: write an example that thoroughly tests this module. Test rust-interop -// with Tuple - -/// Information available both for protocols and for tagged values -#[derive(Clone)] -pub struct TypeData { - /// The full path of the module designated to represent this type or protocol. - /// Also used to key impl tables in the counterpart (tag or protocol) - pub id: Sym, - /// Maps IDs of the counterpart (tag or protocol) to implementations - pub impls: Arc>, -} -impl TypeData { - /// Create a new type data record from a known name and impls - pub fn new(id: Sym, impls: impl IntoIterator) -> Self { - Self { id, impls: Arc::new(impls.into_iter().collect()) } - } -} - -fn mk_mod<'a>( - rest: impl IntoIterator, - impls: HashMap, - profile: ImplsProfile, -) -> ConstTree { - ConstTree::tree(rest.into_iter().chain([ - (profile.own_id, leaf(tpl::A(tpl::C("std::reflect::modname"), tpl::V(Inert(1))))), - atom_ent(TYPE_KEY, [use_wrap(profile.wrap, impls)]), - ])) -} - -fn to_mod<'a>( - rest: impl IntoIterator, - data: TypeData, - profile: ImplsProfile, -) -> ConstTree { - let id = data.id.clone(); - ConstTree::tree(rest.into_iter().chain([ - atom_ent(profile.own_id, [Unstable::new(move |r| { - assert!(r.location.module == id, "Pre-initilaized type lib mounted on wrong prefix"); - Inert(r.location.module) - })]), - atom_ent(TYPE_KEY, [profile.wrap.wrap(data)]), - ])) -} - -/// Key for type data. The value is either [Inert] or [Inert] -const TYPE_KEY: &str = "__type_data__"; - -/// A shared behaviour that may implement itself for types, and may be -/// implemented by types. -#[derive(Clone)] -pub struct Protocol(pub TypeData); -impl Protocol { - /// Name of the member the ID must be assigned to for a module to be - /// recognized as a protocol. - pub const ID_KEY: &'static str = "__protocol_id__"; - const fn profile() -> ImplsProfile { - ImplsProfile { - wrap: |t| Inert(Protocol(t)), - own_id: Protocol::ID_KEY, - other_id: Tag::ID_KEY, - prelude: formatcp!( - "import std - const {} := std::reflect::modname 1 - const resolve := std::protocol::resolve {TYPE_KEY} - const vcall := std::protocol::vcall {TYPE_KEY}", - Protocol::ID_KEY - ), - } - } - - /// Create a new protocol with a pre-determined name - pub fn new(id: Sym, impls: impl IntoIterator) -> Self { - Self(TypeData::new(id, impls)) - } - - /// Attach a pre-existing protocol to the tree. Consider [Protocol::tree]. - pub fn to_tree<'a>(&self, rest: impl IntoIterator) -> ConstTree { - to_mod(rest, self.0.clone(), Self::profile()) - } - - /// Create a new protocol definition - pub fn tree<'a>( - impls: impl IntoIterator, - rest: impl IntoIterator, - ) -> ConstTree { - mk_mod(rest, impls.into_iter().collect(), Self::profile()) - } -} -impl fmt::Debug for Protocol { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Protocol({})", self.0.id) } -} -impl InertPayload for Protocol { - const TYPE_STR: &'static str = "Protocol"; -} - -/// A type marker that can be attached to values to form a [Tagged] -#[derive(Clone)] -pub struct Tag(pub TypeData); -impl Tag { - const ID_KEY: &'static str = "__type_id__"; - const fn profile() -> ImplsProfile { - ImplsProfile { - wrap: |t| Inert(Tag(t)), - own_id: Tag::ID_KEY, - other_id: Protocol::ID_KEY, - prelude: formatcp!( - "import std - const {} := std::reflect::modname 1 - const unwrap := std::protocol::unwrap {TYPE_KEY} - const wrap := std::protocol::wrap {TYPE_KEY}", - Tag::ID_KEY - ), - } - } - - /// Create a new type-tag with a pre-determined name - pub fn new(id: Sym, impls: impl IntoIterator) -> Self { - Self(TypeData::new(id, impls)) - } - - /// Attach a pre-existing type-tag to the tree. Consider [Tag::tree] - pub fn to_tree<'a>(&self, rest: impl IntoIterator) -> ConstTree { - to_mod(rest, self.0.clone(), Self::profile()) - } - - /// Create a new tag - pub fn tree<'a>( - impls: impl IntoIterator, - rest: impl IntoIterator, - ) -> ConstTree { - mk_mod(rest, impls.into_iter().collect(), Self::profile()) - } -} -impl fmt::Debug for Tag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Tag({})", self.0.id) } -} -impl InertPayload for Tag { - const TYPE_STR: &'static str = "Tag"; - fn strict_eq(&self, other: &Self) -> bool { self.0.id == other.0.id } -} - -/// A value with a type [Tag] -#[derive(Clone)] -pub struct Tagged { - /// Type information - pub tag: Tag, - /// Value - pub value: nort::Expr, -} -impl fmt::Debug for Tagged { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Tagged({:?} {})", self.tag, self.value) - } -} -impl InertPayload for Tagged { - const TYPE_STR: &'static str = "Tagged"; - fn respond(&self, mut request: Request) { request.serve_with(|| self.tag.clone()) } -} - -fn parse_impl( - tail: Frag, - req: &dyn ParsePluginReq, -) -> Option> { - custom_line(tail, i!(str: "impl"), false, req).map(|res| { - let (_, tail, _) = res?; - let (name, tail) = req.parse_nsname(tail)?; - let (walrus, tail) = req.pop(tail.trim())?; - req.expect(Lexeme::Walrus, walrus)?; - let (body, empty) = req.parse_exprv(tail, None)?; - req.expect_empty(empty)?; - let value = req.vec_to_single(tail.fallback, body)?; - Ok((name, value)) - }) -} - -struct Impl { - target: Sym, - value: parsed::Expr, -} - -fn extract_impls( - tail: Frag, - req: &dyn ParsePluginReq, - range: SourceRange, - typeid_name: Tok, -) -> ProjectResult<(Vec, Vec)> { - let mut lines = Vec::new(); - let mut impls = Vec::new(); // name1, value1, name2, value2, etc... - for line in req.split_lines(tail) { - match parse_impl(line, req) { - Some(result) => { - let (name, value) = result?; - let target = Sym::new(name.suffix([typeid_name.clone()])).unwrap(); - impls.push(Impl { target, value }); - }, - None => lines.extend((req.parse_line(line)?.into_iter()).map(|k| k.wrap(range.clone()))), - } - } - Ok((lines, impls)) -} - -trait WrapImpl: Clone + Copy + Send + Sync + 'static { - type R: Atomic + Clone + 'static; - fn wrap(&self, data: TypeData) -> Self::R; -} -impl R + Clone + Copy + Send + Sync + 'static> - WrapImpl for F -{ - type R = R; - fn wrap(&self, data: TypeData) -> Self::R { self(data) } -} -fn use_wrap(wrap: impl WrapImpl, impls: HashMap) -> impl Atomic + Clone + 'static { - Unstable::new(move |r| wrap.wrap(TypeData::new(r.location.module, impls))) -} - -#[derive(Debug, Clone)] -struct ImplsProfile { - wrap: W, - own_id: &'static str, - other_id: &'static str, - prelude: &'static str, -} - -fn parse_body_with_impls( - body: Frag, - req: &dyn ParsePluginReq, - range: SourceRange, - profile: ImplsProfile, -) -> ProjectResult> { - let ImplsProfile { other_id, prelude, wrap, .. } = profile.clone(); - let (mut lines, impls) = extract_impls(body, req, range.clone(), i(other_id))?; - let line_loc = range.clone(); - let type_data = defer_to_runtime( - range.clone(), - impls.into_iter().flat_map(move |Impl { target, value }| { - [vec![parsed::Clause::Name(target).into_expr(line_loc.clone())], vec![value]] - }), - move |pairs: Vec| -> RTResult<_> { - debug_assert_eq!(pairs.len() % 2, 0, "key-value pairs"); - let mut impls = HashMap::with_capacity(pairs.len() / 2); - for (name, value) in pairs.into_iter().tuples() { - impls.insert(name.downcast::>()?.0, value); - } - Ok(use_wrap(wrap, impls)) - }, - ); - let type_data_line = Constant { name: i(TYPE_KEY), value: type_data.into_expr(range.clone()) }; - lines.extend(req.parse_entries(prelude, range.clone())); - lines.push(MemberKind::Constant(type_data_line).into_line(true, range)); - Ok(lines) -} - -#[derive(Clone)] -struct ProtocolParser; -impl ParseLinePlugin for ProtocolParser { - fn parse(&self, req: &dyn ParsePluginReq) -> Option>> { - custom_line(req.frag(), i!(str: "protocol"), true, req).map(|res| { - let (exported, tail, line_loc) = res?; - let (name, tail) = req.pop(tail)?; - let name = req.expect_name(name)?; - let tail = req.expect_block(tail, PType::Par)?; - let body = parse_body_with_impls(tail, req, line_loc, Protocol::profile())?; - let kind = MemberKind::Module(ModuleBlock { name, body }); - Ok(vec![SourceLineKind::Member(Member { exported, kind })]) - }) - } -} - -#[derive(Clone)] -struct TypeParser; -impl ParseLinePlugin for TypeParser { - fn parse(&self, req: &dyn ParsePluginReq) -> Option>> { - custom_line(req.frag(), i!(str: "type"), true, req).map(|res| { - let (exported, tail, line_loc) = res?; - let (name, tail) = req.pop(tail)?; - let name = req.expect_name(name)?; - let tail = req.expect_block(tail, PType::Par)?; - let body = parse_body_with_impls(tail, req, line_loc, Tag::profile())?; - let kind = MemberKind::Module(ModuleBlock { name, body }); - Ok(vec![SourceLineKind::Member(Member { exported, kind })]) - }) - } -} - -#[derive(Clone)] -struct AsProtocolParser; -impl ParseLinePlugin for AsProtocolParser { - fn parse(&self, req: &dyn ParsePluginReq) -> Option>> { - custom_line(req.frag(), i!(str: "as_protocol"), false, req).map(|res| { - let (_, tail, line_loc) = res?; - let body = req.expect_block(tail, PType::Par)?; - parse_body_with_impls(body, req, line_loc, Protocol::profile()) - .map(|v| v.into_iter().map(|e| e.kind).collect()) - }) - } -} - -#[derive(Clone)] -struct AsTypeParser; -impl ParseLinePlugin for AsTypeParser { - fn parse(&self, req: &dyn ParsePluginReq) -> Option>> { - custom_line(req.frag(), i!(str: "as_type"), false, req).map(|res| { - let (_, tail, line_loc) = res?; - let body = req.expect_block(tail, PType::Par)?; - parse_body_with_impls(body, req, line_loc, Tag::profile()) - .map(|v| v.into_iter().map(|e| e.kind).collect()) - }) - } -} - -/// Collection of all the parser plugins defined here -pub fn parsers() -> Vec> { - vec![ - Box::new(ProtocolParser), - Box::new(TypeParser), - Box::new(AsTypeParser), - Box::new(AsProtocolParser), - ] -} - -/// Check and remove the type tag from a value -pub fn unwrap(tag: Inert, tagged: Inert) -> RTResult { - if tagged.tag.strict_eq(&tag) { - return Ok(tagged.value.clone()); - } - let msg = format!("expected {:?} but got {:?}", tag, tagged.tag); - RuntimeError::fail(msg, "unwrapping type-tagged value") -} - -/// Attach a type tag to a value -pub fn wrap(tag: Inert, value: nort::Expr) -> Inert { - Inert(Tagged { tag: tag.0, value }) -} - -/// Find the implementation of a protocol for a given value -pub fn resolve(protocol: Inert, value: ClauseInst) -> RTResult { - let tag = value.request::().ok_or_else(|| { - let msg = format!("{value} is not type-tagged"); - RuntimeError::ext(msg, "resolving protocol impl") - })?; - if let Some(implem) = protocol.0.0.impls.get(&tag.0.id) { - Ok(implem.clone()) - } else if let Some(implem) = tag.0.impls.get(&protocol.0.0.id) { - Ok(implem.clone()) - } else { - let message = format!("{tag:?} doesn't implement {protocol:?}"); - RuntimeError::fail(message, "dispatching protocol") - } -} - -/// Generate a call to [resolve] bound to the given protocol -pub const fn gen_resolv(name: &'static str) -> impl GenClause { - tpl::A( - tpl::C("std::protocol::resolve"), - tpl::V(Unstable::new(move |_| refer_seq(name.split("::").chain(iter::once(TYPE_KEY))))), - ) -} - -/// All the functions exposed by the std::protocol library -pub fn protocol_lib() -> ConstTree { - ConstTree::ns("std::protocol", [ConstTree::tree([ - xfn_ent("unwrap", [unwrap]), - xfn_ent("wrap", [wrap]), - xfn_ent("resolve", [resolve]), - xfn_ent("break", [|t: Inert| t.0.value]), - ])]) -} diff --git a/orchidlang/src/libs/std/reflect.rs b/orchidlang/src/libs/std/reflect.rs deleted file mode 100644 index a7cb236..0000000 --- a/orchidlang/src/libs/std/reflect.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! `std::reflect` Abstraction-breaking operations for dynamically constructing -//! [Clause::Constant] references. - -use std::hash::Hash; -use std::sync::atomic::{self, AtomicUsize}; -use std::{cmp, fmt}; - -use intern_all::i; - -use super::runtime_error::RuntimeError; -use super::string::OrcString; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::foreign::try_from_expr::WithLoc; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::nort::{self, Clause}; -use crate::name::Sym; - -impl InertPayload for Sym { - const TYPE_STR: &'static str = "SymbolName"; - fn strict_eq(&self, o: &Self) -> bool { self == o } -} - -/// Generate a constant reference at runtime. Referencing a nonexistent constant -/// is a runtime error. -pub fn refer_seq(name: impl IntoIterator) -> Clause { - Clause::Constant(Sym::new(name.into_iter().map(i)).expect("Empty name")) -} - -/// Generate a constant reference at runtime. Referencing a nonexistent constant -/// is a runtime error. -pub fn refer(name: &'static str) -> Clause { refer_seq(name.split("::")) } - -static COUNTER: AtomicUsize = AtomicUsize::new(0); - -/// A struct that equals its own copies and only its own copies -#[derive(Clone)] -pub struct RefEqual(usize); -impl RefEqual { - /// Create a new [RefEqual] which is initially completely unique - #[allow(clippy::new_without_default)] // new has semantic meaning - pub fn new() -> Self { Self(COUNTER.fetch_add(1, atomic::Ordering::Relaxed)) } - /// Return the unique identifier of this [RefEqual] and its copies - pub fn id(&self) -> usize { self.0 } -} -impl fmt::Debug for RefEqual { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("RefEqual").field(&self.id()).finish() - } -} -impl InertPayload for RefEqual { - const TYPE_STR: &'static str = "RefEqual"; - fn strict_eq(&self, other: &Self) -> bool { self == other } -} -impl Eq for RefEqual {} -impl PartialEq for RefEqual { - fn eq(&self, other: &Self) -> bool { self.id() == other.id() } -} -impl Ord for RefEqual { - fn cmp(&self, other: &Self) -> cmp::Ordering { self.id().cmp(&other.id()) } -} -impl PartialOrd for RefEqual { - fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } -} -impl Hash for RefEqual { - fn hash(&self, state: &mut H) { self.id().hash(state) } -} - -pub(super) fn reflect_lib() -> ConstTree { - ConstTree::ns("std::reflect", [ConstTree::tree([ - xfn_ent("ref_equal", [|l: Inert, r: Inert| Inert(l.0.id() == r.0.id())]), - xfn_ent("modname", [|WithLoc(loc, _): WithLoc| Inert(loc.module)]), - xfn_ent("symbol", [|s: Inert| { - Sym::parse(s.0.as_str()) - .map(Inert) - .map_err(|_| RuntimeError::ext("empty string".to_string(), "converting string to Symbol")) - }]), - ])]) -} diff --git a/orchidlang/src/libs/std/result.orc b/orchidlang/src/libs/std/result.orc deleted file mode 100644 index defb946..0000000 --- a/orchidlang/src/libs/std/result.orc +++ /dev/null @@ -1,12 +0,0 @@ -import std::panic - -as_type () - -export const ok := \v. wrap \fe. \fv. fv v -export const err := \e. wrap \fe. \fv. fe e - -export const map := \result. \fv. unwrap result err fv -export const map_err := \result. \fe. unwrap result fe ok -export const flatten := \result. unwrap result err \res. wrap (unwrap res) -export const and_then := \result. \f. unwrap result err \v. f v -export const assume := \result. unwrap result (\e. panic "value expected") \v.v diff --git a/orchidlang/src/libs/std/runtime_error.rs b/orchidlang/src/libs/std/runtime_error.rs deleted file mode 100644 index dc9503e..0000000 --- a/orchidlang/src/libs/std/runtime_error.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Errors thrown by the standard library in lieu of in-language error handling -//! for runtime errors such as missing files. - -use std::fmt; - -use crate::foreign::error::{RTError, RTErrorObj, RTResult}; - -/// Some external event prevented the operation from succeeding -#[derive(Clone)] -pub struct RuntimeError { - message: String, - operation: &'static str, -} - -impl RuntimeError { - /// Construct, upcast and wrap in a Result that never succeeds for easy - /// short-circuiting - pub fn fail(message: String, operation: &'static str) -> RTResult { - Err(Self { message, operation }.pack()) - } - - /// Construct and upcast to [RTErrorObj] - pub fn ext(message: String, operation: &'static str) -> RTErrorObj { - Self { message, operation }.pack() - } -} - -impl fmt::Display for RuntimeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Error while {}: {}", self.operation, self.message) - } -} - -impl RTError for RuntimeError {} diff --git a/orchidlang/src/libs/std/state.rs b/orchidlang/src/libs/std/state.rs deleted file mode 100644 index 1f31611..0000000 --- a/orchidlang/src/libs/std/state.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::foreign::fn_bridge::Thunk; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::handler::HandlerTable; -use crate::interpreter::nort::Expr; - -#[derive(Debug, Clone)] -pub struct State(Arc>); -impl InertPayload for State { - const TYPE_STR: &'static str = "State"; -} - -#[derive(Debug, Clone)] -struct NewStateCmd(Expr, Expr); -impl InertPayload for NewStateCmd { - const TYPE_STR: &'static str = "NewStateCmd"; - fn strict_eq(&self, _: &Self) -> bool { true } -} -#[derive(Debug, Clone)] -struct SetStateCmd(State, Expr, Expr); -impl InertPayload for SetStateCmd { - const TYPE_STR: &'static str = "SetStateCmd"; -} - -#[derive(Debug, Clone)] -struct GetStateCmd(State, Expr); -impl InertPayload for GetStateCmd { - const TYPE_STR: &'static str = "GetStateCmd"; -} - -fn new_state(default: Thunk, cont: Thunk) -> Inert { - Inert(NewStateCmd(default.0, cont.0)) -} - -fn get_state(s: Inert, cont: Thunk) -> Inert { Inert(GetStateCmd(s.0, cont.0)) } - -fn set_state(s: Inert, value: Thunk, cont: Thunk) -> Inert { - Inert(SetStateCmd(s.0, value.0, cont.0)) -} - -fn new_state_handler(cmd: &Inert) -> Expr { - let Inert(NewStateCmd(default, handler)) = cmd; - let state = State(Arc::new(Mutex::new(default.clone()))); - let tpl = tpl::A(tpl::Slot, tpl::V(Inert(state))); - tpl.template(nort_gen(handler.location()), [handler.clone()]) -} - -fn set_state_handler(cmd: &Inert) -> Expr { - let Inert(SetStateCmd(state, value, handler)) = cmd; - *state.0.lock().unwrap() = value.clone(); - handler.clone() -} - -fn get_state_handler(cmd: &Inert) -> Expr { - let Inert(GetStateCmd(state, handler)) = cmd; - let val = state.0.lock().unwrap().clone(); - let tpl = tpl::A(tpl::Slot, tpl::Slot); - tpl.template(nort_gen(handler.location()), [handler.clone(), val]) -} - -pub fn state_handlers() -> HandlerTable<'static> { - let mut handlers = HandlerTable::new(); - handlers.register(new_state_handler); - handlers.register(get_state_handler); - handlers.register(set_state_handler); - handlers -} - -pub fn state_lib() -> ConstTree { - ConstTree::ns("std::state", [ConstTree::tree([ - xfn_ent("new_state", [new_state]), - xfn_ent("get_state", [get_state]), - xfn_ent("set_state", [set_state]), - ])]) -} diff --git a/orchidlang/src/libs/std/std_system.rs b/orchidlang/src/libs/std/std_system.rs deleted file mode 100644 index af119ea..0000000 --- a/orchidlang/src/libs/std/std_system.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Add the standard library's constants and mcacros to an Orchid environment - -#![allow(non_upper_case_globals)] - -use rust_embed::RustEmbed; - -use super::binary::bin_lib; -use super::bool::bool_lib; -use super::conv::conv_lib; -use super::exit_status::exit_status_lib; -use super::inspect::inspect_lib; -use super::number::num_lib; -use super::panic::panic_lib; -use super::protocol::{parsers, protocol_lib}; -use super::reflect::reflect_lib; -use super::state::{state_handlers, state_lib}; -use super::string::{str_lib, StringLexer}; -use super::tuple::tuple_lib; -use crate::facade::system::{IntoSystem, System}; -use crate::gen::tree::{ConstCombineErr, ConstTree}; -use crate::location::CodeGenInfo; -use crate::pipeline::load_project::Prelude; -use crate::tree::ModEntry; -use crate::utils::combine::Combine; -use crate::virt_fs::{EmbeddedFS, VirtFS}; -use crate::{sym, vname}; - -#[derive(RustEmbed)] -#[folder = "src/libs/std"] -#[include = "*.orc"] -struct StdEmbed; - -/// Feature flags for the STL. -#[derive(Default)] -pub struct StdConfig { - /// Whether impure functions (such as io::debug) are allowed. An embedder - /// would typically disable this flag - pub impure: bool, -} -impl StdConfig { - fn stdlib(&self) -> Result { - let pure_tree = tuple_lib() - .combine(bin_lib())? - .combine(bool_lib())? - .combine(conv_lib())? - .combine(exit_status_lib())? - .combine(num_lib())? - .combine(panic_lib())? - .combine(protocol_lib())? - .combine(reflect_lib())? - .combine(state_lib())? - .combine(str_lib())?; - if !self.impure { - return Ok(pure_tree); - } - pure_tree.combine(inspect_lib()) - } -} - -impl IntoSystem<'static> for StdConfig { - fn into_system(self) -> System<'static> { - System { - name: "stdlib", - constants: self.stdlib().expect("stdlib tree is malformed"), - code: ModEntry::ns("std", [ModEntry::leaf( - EmbeddedFS::new::(".orc", CodeGenInfo::no_details(sym!(std::fs))).rc(), - )]), - prelude: vec![Prelude { - target: vname!(std::prelude), - exclude: vname!(std), - owner: CodeGenInfo::no_details(sym!(std::prelude)), - }], - handlers: state_handlers(), - lexer_plugins: vec![Box::new(StringLexer)], - line_parsers: parsers(), - } - } -} diff --git a/orchidlang/src/libs/std/string.orc b/orchidlang/src/libs/std/string.orc deleted file mode 100644 index bd8be5a..0000000 --- a/orchidlang/src/libs/std/string.orc +++ /dev/null @@ -1,10 +0,0 @@ -import super::(procedural::*, bool::*, panic, inspect, known::*) - -export macro ...$a ++ ...$b =0x4p36=> (concat (...$a) (...$b)) - -export const char_at := \s. \i. do{ - let slc = slice s i 1; - if len slc == 1 - then slc - else panic "Character index out of bounds" -} diff --git a/orchidlang/src/libs/std/string.rs b/orchidlang/src/libs/std/string.rs deleted file mode 100644 index 8ff1c26..0000000 --- a/orchidlang/src/libs/std/string.rs +++ /dev/null @@ -1,401 +0,0 @@ -(//! `std::string` String processing - -use std::fmt; -use std::fmt::Write as _; -use std::hash::Hash; -use std::ops::Deref; -use std::sync::Arc; - -use intern_all::{i, Tok}; -use itertools::Itertools; -use unicode_segmentation::UnicodeSegmentation; - -use super::protocol::{gen_resolv, Protocol}; -use super::runtime_error::RuntimeError; -use crate::error::{ProjectErrorObj, ProjectResult}; -use crate::foreign::atom::{AtomGenerator, Atomic}; -use crate::foreign::error::RTResult; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::foreign::to_clause::ToClause; -use crate::foreign::try_from_expr::{TryFromExpr, WithLoc}; -use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort::{Clause, Expr}; -use crate::location::CodeLocation; -use crate::parse::context::ParseCtx; -use crate::parse::errors::ParseErrorKind; -use crate::parse::lex_plugin::{LexPluginRecur, LexPluginReq, LexerPlugin}; -use crate::parse::lexer::{Entry, LexRes, Lexeme}; -use crate::parse::parsed::PType; -use crate::utils::iter_find::iter_find; - -/// An Orchid string which may or may not be interned -#[derive(Clone, Eq)] -pub enum OrcString { - /// An interned string. Equality-conpared by reference. - Interned(Tok), - /// An uninterned bare string. Equality-compared by character - Runtime(Arc), -} - -impl fmt::Debug for OrcString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Runtime(s) => write!(f, "r\"{s}\""), - Self::Interned(t) => write!(f, "i\"{t}\""), - } - } -} - -impl OrcString { - /// Intern the contained string - pub fn intern(&mut self) { - if let Self::Runtime(t) = self { - *self = Self::Interned(i(t.as_str())) - } - } - /// Clone out the plain Rust [String] - #[must_use] - pub fn get_string(self) -> String { - match self { - Self::Interned(s) => s.as_str().to_owned(), - Self::Runtime(rc) => Arc::unwrap_or_clone(rc), - } - } -} - -impl Deref for OrcString { - type Target = String; - - fn deref(&self) -> &Self::Target { - match self { - Self::Interned(t) => t, - Self::Runtime(r) => r, - } - } -} - -impl Hash for OrcString { - fn hash(&self, state: &mut H) { self.as_str().hash(state) } -} - -impl From for OrcString { - fn from(value: String) -> Self { Self::Runtime(Arc::new(value)) } -} - -impl From<&str> for OrcString { - fn from(value: &str) -> Self { Self::from(value.to_string()) } -} - -impl From> for OrcString { - fn from(value: Tok) -> Self { Self::Interned(value) } -} - -impl PartialEq for OrcString { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Interned(t1), Self::Interned(t2)) => t1 == t2, - _ => **self == **other, - } - } -} - -impl InertPayload for OrcString { - const TYPE_STR: &'static str = "OrcString"; - fn strict_eq(&self, other: &Self) -> bool { self == other } -} - -impl ToClause for String { - fn to_clause(self, _: CodeLocation) -> Clause { Inert(OrcString::from(self)).atom_cls() } -} - -impl TryFromExpr for String { - fn from_expr(exi: Expr) -> RTResult { - Ok(exi.downcast::>()?.0.get_string()) - } -} - -pub(super) fn str_lib() -> ConstTree { - ConstTree::ns("std::string", [ConstTree::tree([ - // String conversion protocol implementable by external types - ("conversion", Protocol::tree([], [])), - xfn_ent("slice", [|s: Inert, i: Inert, len: Inert| { - let graphs = s.0.as_str().graphemes(true); - if i.0 == 0 { - return Ok(graphs.take(len.0).collect::()); - } - let mut prefix = graphs.skip(i.0 - 1); - if prefix.next().is_none() { - return Err(RuntimeError::ext( - "Character index out of bounds".to_string(), - "indexing string", - )); - } - let mut count = 0; - let ret = (prefix.take(len.0)) - .map(|x| { - count += 1; - x - }) - .collect::(); - if count == len.0 { - Ok(ret) - } else { - RuntimeError::fail("Character index out of bounds".to_string(), "indexing string") - } - }]), - xfn_ent("concat", [|a: String, b: Inert| a + b.0.as_str()]), - xfn_ent("find", [|haystack: Inert, needle: Inert| { - let haystack_graphs = haystack.0.as_str().graphemes(true); - iter_find(haystack_graphs, needle.0.as_str().graphemes(true)).map(Inert) - }]), - xfn_ent("split", [|s: String, i: Inert| -> (String, String) { - let mut graphs = s.as_str().graphemes(true); - (graphs.by_ref().take(i.0).collect(), graphs.collect()) - }]), - xfn_ent("len", [|s: Inert| Inert(s.0.graphemes(true).count())]), - xfn_ent("size", [|s: Inert| Inert(s.0.as_bytes().len())]), - xfn_ent("intern", [|s: Inert| { - Inert(match s.0 { - OrcString::Runtime(s) => OrcString::Interned(i(&*s)), - x => x, - }) - }]), - xfn_ent("convert", [|WithLoc(loc, a): WithLoc| match a.clone().downcast() { - Ok(str) => Inert::::atom_expr(str, loc), - Err(_) => match a.clause.request::() { - Some(str) => Inert(str).atom_expr(loc), - None => tpl::a2(gen_resolv("std::string::conversion"), tpl::Slot, tpl::Slot) - .template(nort_gen(loc), [a.clone(), a]), - }, - }]), - ])]) -} - -/// Reasons why [parse_string] might fail. See [StringError] -enum StringErrorKind { - /// A unicode escape sequence wasn't followed by 4 hex digits - NotHex, - /// A unicode escape sequence contained an unassigned code point - BadCodePoint, - /// An unrecognized escape sequence was found - BadEscSeq, -} - -/// Error produced by [parse_string] -struct StringError { - /// Character where the error occured - pos: usize, - /// Reason for the error - kind: StringErrorKind, -} - -impl StringError { - /// Convert into project error for reporting - pub fn into_proj(self, ctx: &dyn ParseCtx, pos: usize) -> ProjectErrorObj { - let start = pos + self.pos; - let location = ctx.range_loc(&(start..start + 1)); - match self.kind { - StringErrorKind::NotHex => NotHex.pack(location), - StringErrorKind::BadCodePoint => BadCodePoint.pack(location), - StringErrorKind::BadEscSeq => BadEscapeSequence.pack(location), - } - } -} - -/// Process escape sequences in a string literal -fn parse_string(str: &str) -> Result { - let mut target = String::new(); - let mut iter = str.char_indices(); - while let Some((_, c)) = iter.next() { - if c != '\\' { - target.push(c); - continue; - } - let (mut pos, code) = iter.next().expect("lexer would have continued"); - let next = match code { - c @ ('\\' | '/' | '"') => c, - 'b' => '\x08', - 'f' => '\x0f', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - '\n' => 'skipws: loop { - match iter.next() { - None => return Ok(target), - Some((_, c)) => - if !c.is_whitespace() { - break 'skipws c; - }, - } - }, - 'u' => { - let acc = ((0..4).rev()) - .map(|radical| { - let (j, c) = (iter.next()).ok_or(StringError { pos, kind: StringErrorKind::NotHex })?; - pos = j; - let b = u32::from_str_radix(&String::from(c), 16) - .map_err(|_| StringError { pos, kind: StringErrorKind::NotHex })?; - Ok(16u32.pow(radical) + b) - }) - .fold_ok(0, u32::wrapping_add)?; - char::from_u32(acc).ok_or(StringError { pos, kind: StringErrorKind::BadCodePoint })? - }, - _ => return Err(StringError { pos, kind: StringErrorKind::BadEscSeq }), - }; - target.push(next); - } - Ok(target) -} - -/// [LexerPlugin] for a string literal that supports interpolateion. -#[derive(Clone)] -pub struct StringLexer; -impl LexerPlugin for StringLexer { - fn lex<'a>(&self, req: &'_ dyn LexPluginReq<'a>) -> Option>> { - req.tail().strip_prefix('\"').map(|mut txt| { - let ctx = req.ctx(); - let mut parts = vec![Entry::new(ctx.range(0, txt), Lexeme::LP(PType::Par))]; - let mut str = String::new(); - let commit_str = |str: &mut String, tail: &str, parts: &mut Vec| -> ProjectResult<_> { - let str_val = parse_string(str).unwrap_or_else(|e| { - ctx.reporter().report(e.into_proj(ctx, ctx.pos(txt))); - String::new() - }); - let ag = AtomGenerator::cloner(Inert(OrcString::from(i(&str_val)))); - parts.push(Entry::new(ctx.range(str.len(), tail), Lexeme::Atom(ag))); - *str = String::new(); - Ok(()) - }; - loop { - if let Some(rest) = txt.strip_prefix('"') { - commit_str(&mut str, txt, &mut parts)?; - parts.push(Entry::new(ctx.range(0, rest), Lexeme::RP(PType::Par))); - if parts.len() == 3 { - return Ok(LexRes { tail: rest, tokens: vec![parts[1].clone()] }); - } - return Ok(LexRes { tail: rest, tokens: parts }); - } - if let Some(rest) = txt.strip_prefix("${") { - let mut depth = 0; - commit_str(&mut str, rest, &mut parts)?; - parts.extend(req.insert("++ std::string::convert (", ctx.source_range(0, rest))); - let res = req.recurse(LexPluginRecur { - tail: rest, - exit: &mut |c| { - match c.chars().next() { - None => return Err(UnclosedInterpolation.pack(ctx.source_range(2, rest))), - Some('{') => depth += 1, - Some('}') if depth == 0 => return Ok(true), - Some('}') => depth -= 1, - _ => (), - } - Ok(false) - }, - })?; - txt = &res.tail[1..]; // account for final } - parts.extend(res.tokens); - parts.extend(req.insert(") ++", ctx.source_range(0, txt))); - } else { - let mut chars = txt.chars(); - match chars.next() { - None => return Err(NoStringEnd.pack(ctx.source_range(req.tail().len(), ""))), - Some('\\') => match chars.next() { - None => write!(str, "\\").expect("writing \\ into string"), - Some(next) => write!(str, "\\{next}").expect("writing \\ and char into string"), - }, - Some(c) => write!(str, "{c}").expect("writing char into string"), - } - txt = chars.as_str(); - } - } - }) - } -} - -/// An interpolated string section started with ${ wasn't closed with a balanced -/// } -pub struct UnclosedInterpolation; -impl ParseErrorKind for UnclosedInterpolation { - const DESCRIPTION: &'static str = "A ${ block within a $-string wasn't closed"; -} - -/// String literal never ends -pub(super) struct NoStringEnd; -impl ParseErrorKind for NoStringEnd { - const DESCRIPTION: &'static str = "A string literal was not closed with `\"`"; -} - -/// A unicode escape sequence contains something other than a hex digit -pub(super) struct NotHex; -impl ParseErrorKind for NotHex { - const DESCRIPTION: &'static str = "Expected a hex digit"; -} - -/// A unicode escape sequence contains a number that isn't a unicode code point. -pub(super) struct BadCodePoint; -impl ParseErrorKind for BadCodePoint { - const DESCRIPTION: &'static str = "\\uXXXX escape sequence does not describe valid code point"; -} - -/// An unrecognized escape sequence occurred in a string. -pub(super) struct BadEscapeSequence; -impl ParseErrorKind for BadEscapeSequence { - const DESCRIPTION: &'static str = "Unrecognized escape sequence"; -} -#[cfg(test)] -mod test { - use intern_all::i; - - use super::StringLexer; - use crate::foreign::atom::Atomic; - use crate::foreign::inert::Inert; - use crate::libs::std::string::OrcString; - use crate::parse::context::MockContext; - use crate::parse::lex_plugin::{LexPlugReqImpl, LexerPlugin}; - use crate::parse::lexer::Lexeme; - use crate::parse::parsed::PType; - - #[test] - fn plain_string() { - let source = r#""Hello world!" - says the programmer"#; - let ctx = MockContext::new(); - let req = LexPlugReqImpl { ctx: &ctx, tail: source }; - let res = (StringLexer.lex(&req)) - .expect("the snippet starts with a quote") - .expect("it contains a valid string"); - let expected = [Inert(OrcString::from("Hello world!")).lexeme()]; - assert_eq!(res.tokens, expected); - assert_eq!(res.tail, " - says the programmer"); - assert!(!ctx.0.failing(), "No errors were generated") - } - - #[test] - #[rustfmt::skip] - fn template_string() { - let source = r#""I <${1 + 2} parsers" - this dev"#; - let ctx = MockContext::new(); - let req = LexPlugReqImpl { ctx: &ctx, tail: source }; - let res = (StringLexer.lex(&req)) - .expect("the snippet starts with a quote") - .expect("it contains a valid string"); - use Lexeme::{Name, LP, NS, RP}; - let expected = [ - LP(PType::Par), - Inert(OrcString::from("I <")).lexeme(), - Name(i!(str: "++")), - // std::string::convert - Name(i!(str: "std")), NS, Name(i!(str: "string")), NS, Name(i!(str: "convert")), - // (1 + 1) - LP(PType::Par), Inert(1).lexeme(), Name(i!(str: "+")), Inert(2).lexeme(), RP(PType::Par), - Name(i!(str: "++")), - Inert(OrcString::from(" parsers")).lexeme(), - RP(PType::Par), - ]; - assert_eq!(res.tokens, expected); - assert_eq!(res.tail, " - this dev"); - assert!(!ctx.0.failing(), "No errors were generated"); - } -} diff --git a/orchidlang/src/libs/std/tuple.orc b/orchidlang/src/libs/std/tuple.orc deleted file mode 100644 index a2080a9..0000000 --- a/orchidlang/src/libs/std/tuple.orc +++ /dev/null @@ -1,76 +0,0 @@ -import super::(known::*, bool::*, number::*, string::*, fn::*) -import super::loop::recursive -import super::(pmatch, macro, panic, conv, list, option) - --- referenced in the impl table in Rust -const to_string_impl := \t. "tuple[" ++ ( - to_list t - |> list::map conv::to_string - |> list::reduce (\l. \r. l ++ ", " ++ r) - |> option::fallback "" -) ++ "]" - -export const to_list := \t. ( - recursive r (n=length t, l=list::end) - if n == 0 then l - else r (n - 1) (list::cons (pick t $ conv::to_uint $ n - 1) l) -) - -macro gen_tuple $tup macro::list_end =0x1p254=> $tup -macro gen_tuple $tup ( macro::list_item $item $tail ) =0x1p254=> (gen_tuple (push $tup $item) $tail) -export macro new ( $list ) =0x1p84=> (gen_tuple empty $list) - -macro t[..$items] =0x2p84=> ( new ( macro::comma_list (..$items) ) ) - -export ::(t, size) - ---[ - request l -> tuple_pattern pattern_walker l - pattern_walker end -> pattern_result - pattern_walker h ++ t -> pattern_await ( request h ) ( pattern_walker t ) - pattern_await response pattern_result -> pattern_result - tuple_pattern pattern_result -> response -]-- - -( macro pmatch::request ( t[ ..$items ] ) - =0x1p230=> tuple_pattern - ( macro::length macro::comma_list ( ..$items ) ) - ( - pattern_walker - macro::comma_list ( ..$items ) -- leftover items - ) -) -( macro tuple_pattern $length ( pattern_result $expr ( $binds ) ) - =0x1p254=> pmatch::response ( - if length pmatch::value == $length - then ((\tuple_idx. $expr ) 0) - else pmatch::fail - ) ( $binds ) -) -( macro pattern_walker macro::list_end - =0x1p254=> pattern_result pmatch::pass ( pmatch::no_binds ) -) -( macro pattern_walker ( macro::list_item $next $tail ) - =0x1p254=> pattern_await - ( pmatch::request $next ) - ( pattern_walker $tail ) -) -( macro pattern_await - ( pmatch::response $expr ( $binds ) ) - ( pattern_result $tail_expr ( $tail_binds ) ) - =0x1p254=> - pattern_result - ( - (\pmatch::pass. (\pmatch::value. $expr) (pick pmatch::value tuple_idx)) ( - pmatch::take_binds $binds ( - (\pmatch::pass. (\tuple_idx. $tail_expr) (tuple_idx + 1)) - ( pmatch::take_binds $tail_binds ( - pmatch::give_binds - (pmatch::chain_binds $binds $tail_binds) - pmatch::pass - )) - ) - ) - ) - ( ( pmatch::chain_binds $binds $tail_binds ) ) -) diff --git a/orchidlang/src/libs/std/tuple.rs b/orchidlang/src/libs/std/tuple.rs deleted file mode 100644 index 54fc024..0000000 --- a/orchidlang/src/libs/std/tuple.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! `std::tuple` A vector-based sequence for storing short sequences. - -use std::fmt; -use std::sync::Arc; - -use once_cell::sync::Lazy; - -use super::protocol::Tag; -use super::reflect::refer; -use crate::foreign::error::{AssertionError, RTResult}; -use crate::foreign::fn_bridge::Thunk; -use crate::foreign::inert::{Inert, InertPayload}; -use crate::foreign::try_from_expr::WithLoc; -use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; -use crate::interpreter::nort::Expr; -use crate::location::{CodeGenInfo, CodeLocation}; -use crate::sym; -use crate::utils::ddispatch::Request; -use crate::utils::pure_seq::pushed; - -static TUPLE_TAG: Lazy = Lazy::new(|| { - let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(std::tuple))); - Tag::new(sym!(std::tuple), [( - sym!(std::string::conversion), - refer("std::tuple::to_string_impl").into_expr(location), - )]) -}); - -/// A short contiquous random access sequence of Orchid values. -#[derive(Clone)] -pub struct Tuple(pub Arc>); -impl InertPayload for Tuple { - const TYPE_STR: &'static str = "tuple"; - fn respond(&self, mut request: Request) { request.serve_with(|| TUPLE_TAG.clone()) } -} -impl fmt::Debug for Tuple { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Tuple")?; - f.debug_list().entries(self.0.iter().map(|e| &e.clause)).finish() - } -} - -fn length(tuple: Inert) -> Inert { Inert(tuple.0.0.len()) } - -fn pick(WithLoc(loc, tuple): WithLoc>, idx: Inert) -> RTResult { - (tuple.0.0.get(idx.0).cloned()).ok_or_else(|| { - let msg = format!("{} <= {idx}", tuple.0.0.len()); - AssertionError::ext(loc, "Tuple index out of bounds", msg) - }) -} - -fn push(Inert(tuple): Inert, item: Thunk) -> Inert { - let items = Arc::unwrap_or_clone(tuple.0); - Inert(Tuple(Arc::new(pushed(items, item.0)))) -} - -pub(super) fn tuple_lib() -> ConstTree { - ConstTree::ns("std::tuple", [TUPLE_TAG.to_tree([ - atom_ent("empty", [Inert(Tuple(Arc::new(Vec::new())))]), - xfn_ent("length", [length]), - xfn_ent("pick", [pick]), - xfn_ent("push", [push]), - ])]) -} diff --git a/orchidlang/src/name.rs b/orchidlang/src/name.rs deleted file mode 100644 index 7f13ee2..0000000 --- a/orchidlang/src/name.rs +++ /dev/null @@ -1,476 +0,0 @@ -//! Various datatypes that all represent namespaced names. - -use std::borrow::Borrow; -use std::hash::Hash; -use std::iter::Cloned; -use std::num::NonZeroUsize; -use std::ops::{Deref, Index}; -use std::path::Path; -use std::{fmt, slice, vec}; - -use intern_all::{i, Tok}; -use itertools::Itertools; -use trait_set::trait_set; - -trait_set! { - /// Traits that all name iterators should implement - pub trait NameIter = Iterator> + DoubleEndedIterator + ExactSizeIterator; -} - -/// A borrowed name fragment which can be empty. See [VPath] for the owned -/// variant. -#[derive(Hash, PartialEq, Eq)] -#[repr(transparent)] -pub struct PathSlice([Tok]); -impl PathSlice { - /// Create a new [PathSlice] - pub fn new(slice: &[Tok]) -> &PathSlice { - // SAFETY: This is ok because PathSlice is #[repr(transparent)] - unsafe { &*(slice as *const [Tok] as *const PathSlice) } - } - /// Convert to an owned name fragment - pub fn to_vpath(&self) -> VPath { VPath(self.0.to_vec()) } - /// Iterate over the tokens - pub fn iter(&self) -> impl NameIter + '_ { self.into_iter() } - /// Iterate over the segments - pub fn str_iter(&self) -> impl Iterator { - Box::new(self.0.iter().map(|s| s.as_str())) - } - /// Find the longest shared prefix of this name and another sequence - pub fn coprefix<'a>(&'a self, other: &PathSlice) -> &'a PathSlice { - &self[0..self.iter().zip(other.iter()).take_while(|(l, r)| l == r).count()] - } - /// Find the longest shared suffix of this name and another sequence - pub fn cosuffix<'a>(&'a self, other: &PathSlice) -> &'a PathSlice { - &self[0..self.iter().zip(other.iter()).take_while(|(l, r)| l == r).count()] - } - /// Remove another - pub fn strip_prefix<'a>(&'a self, other: &PathSlice) -> Option<&'a PathSlice> { - let shared = self.coprefix(other).len(); - (shared == other.len()).then_some(PathSlice::new(&self[shared..])) - } - /// Number of path segments - pub fn len(&self) -> usize { self.0.len() } - /// Whether there are any path segments. In other words, whether this is a - /// valid name - pub fn is_empty(&self) -> bool { self.len() == 0 } - /// Obtain a reference to the held slice. With all indexing traits shadowed, - /// this is better done explicitly - pub fn as_slice(&self) -> &[Tok] { self } - /// Global empty path slice - pub fn empty() -> &'static Self { PathSlice::new(&[]) } -} -impl fmt::Debug for PathSlice { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") } -} -impl fmt::Display for PathSlice { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.str_iter().join("::")) - } -} -impl Borrow<[Tok]> for PathSlice { - fn borrow(&self) -> &[Tok] { &self.0 } -} -impl<'a> IntoIterator for &'a PathSlice { - type IntoIter = Cloned>>; - type Item = Tok; - fn into_iter(self) -> Self::IntoIter { self.0.iter().cloned() } -} - -mod idx_impls { - use std::ops; - - use intern_all::Tok; - - use super::PathSlice; - - impl ops::Index for PathSlice { - type Output = Tok; - fn index(&self, index: usize) -> &Self::Output { &self.0[index] } - } - macro_rules! impl_range_index_for_pathslice { - ($range:ty) => { - impl ops::Index<$range> for PathSlice { - type Output = Self; - fn index(&self, index: $range) -> &Self::Output { Self::new(&self.0[index]) } - } - }; - } - - impl_range_index_for_pathslice!(ops::RangeFull); - impl_range_index_for_pathslice!(ops::RangeFrom); - impl_range_index_for_pathslice!(ops::RangeTo); - impl_range_index_for_pathslice!(ops::Range); - impl_range_index_for_pathslice!(ops::RangeInclusive); - impl_range_index_for_pathslice!(ops::RangeToInclusive); -} - -impl Deref for PathSlice { - type Target = [Tok]; - - fn deref(&self) -> &Self::Target { &self.0 } -} -impl Borrow for [Tok] { - fn borrow(&self) -> &PathSlice { PathSlice::new(self) } -} -impl Borrow for [Tok; N] { - fn borrow(&self) -> &PathSlice { PathSlice::new(&self[..]) } -} -impl Borrow for Vec> { - fn borrow(&self) -> &PathSlice { PathSlice::new(&self[..]) } -} - -/// A token path which may be empty. [VName] is the non-empty, -/// [PathSlice] is the borrowed version -#[derive(Clone, Default, Hash, PartialEq, Eq)] -pub struct VPath(pub Vec>); -impl VPath { - /// Collect segments into a vector - pub fn new(items: impl IntoIterator>) -> Self { - Self(items.into_iter().collect()) - } - /// Number of path segments - pub fn len(&self) -> usize { self.0.len() } - /// Whether there are any path segments. In other words, whether this is a - /// valid name - pub fn is_empty(&self) -> bool { self.len() == 0 } - /// Prepend some tokens to the path - pub fn prefix(self, items: impl IntoIterator>) -> Self { - Self(items.into_iter().chain(self.0).collect()) - } - /// Append some tokens to the path - pub fn suffix(self, items: impl IntoIterator>) -> Self { - Self(self.0.into_iter().chain(items).collect()) - } - /// Partition the string by `::` namespace separators - pub fn parse(s: &str) -> Self { - Self(if s.is_empty() { vec![] } else { s.split("::").map(i).collect() }) - } - /// Walk over the segments - pub fn str_iter(&self) -> impl Iterator { - Box::new(self.0.iter().map(|s| s.as_str())) - } - /// Try to convert into non-empty version - pub fn into_name(self) -> Result { VName::new(self.0) } - /// Add a token to the path. Since now we know that it can't be empty, turn it - /// into a name. - pub fn name_with_prefix(self, name: Tok) -> VName { - VName(self.into_iter().chain([name]).collect()) - } - /// Add a token to the beginning of the. Since now we know that it can't be - /// empty, turn it into a name. - pub fn name_with_suffix(self, name: Tok) -> VName { - VName([name].into_iter().chain(self).collect()) - } - - /// Convert a fs path to a vpath - pub fn from_path(path: &Path) -> Option<(Self, bool)> { - let to_vpath = |p: &Path| p.iter().map(|c| c.to_str().map(i)).collect::>().map(VPath); - match path.extension().map(|s| s.to_str()) { - Some(Some("orc")) => Some((to_vpath(&path.with_extension(""))?, true)), - None => Some((to_vpath(path)?, false)), - Some(_) => None, - } - } -} -impl fmt::Debug for VPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") } -} -impl fmt::Display for VPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.str_iter().join("::")) - } -} -impl FromIterator> for VPath { - fn from_iter>>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} -impl IntoIterator for VPath { - type Item = Tok; - type IntoIter = vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } -} -impl Borrow<[Tok]> for VPath { - fn borrow(&self) -> &[Tok] { self.0.borrow() } -} -impl Borrow for VPath { - fn borrow(&self) -> &PathSlice { PathSlice::new(&self.0[..]) } -} -impl Deref for VPath { - type Target = PathSlice; - fn deref(&self) -> &Self::Target { self.borrow() } -} - -impl Index for VPath -where PathSlice: Index -{ - type Output = >::Output; - - fn index(&self, index: T) -> &Self::Output { &Borrow::::borrow(self)[index] } -} - -/// A mutable representation of a namespaced identifier of at least one segment. -/// -/// These names may be relative or otherwise partially processed. -/// -/// See also [Sym] for the immutable representation, and [VPath] for possibly -/// empty values -#[derive(Clone, Hash, PartialEq, Eq)] -pub struct VName(Vec>); -impl VName { - /// Assert that the sequence isn't empty and wrap it in [VName] to represent - /// this invariant - pub fn new(items: impl IntoIterator>) -> Result { - let data: Vec<_> = items.into_iter().collect(); - if data.is_empty() { Err(EmptyNameError) } else { Ok(Self(data)) } - } - /// Unwrap the enclosed vector - pub fn into_vec(self) -> Vec> { self.0 } - /// Get a reference to the enclosed vector - pub fn vec(&self) -> &Vec> { &self.0 } - /// Mutable access to the underlying vector. To ensure correct results, this - /// must never be empty. - pub fn vec_mut(&mut self) -> &mut Vec> { &mut self.0 } - /// Intern the name and return a [Sym] - pub fn to_sym(&self) -> Sym { Sym(i(&self.0)) } - /// If this name has only one segment, return it - pub fn as_root(&self) -> Option> { self.0.iter().exactly_one().ok().cloned() } - /// Prepend the segments to this name - #[must_use = "This is a pure function"] - pub fn prefix(self, items: impl IntoIterator>) -> Self { - Self(items.into_iter().chain(self.0).collect()) - } - /// Append the segments to this name - #[must_use = "This is a pure function"] - pub fn suffix(self, items: impl IntoIterator>) -> Self { - Self(self.0.into_iter().chain(items).collect()) - } - /// Read a `::` separated namespaced name - pub fn parse(s: &str) -> Result { Self::new(VPath::parse(s)) } - /// Obtain an iterator over the segments of the name - pub fn iter(&self) -> impl Iterator> + '_ { self.0.iter().cloned() } -} -impl fmt::Debug for VName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") } -} -impl fmt::Display for VName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.str_iter().join("::")) - } -} -impl IntoIterator for VName { - type Item = Tok; - type IntoIter = vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } -} -impl Index for VName -where PathSlice: Index -{ - type Output = >::Output; - - fn index(&self, index: T) -> &Self::Output { &self.deref()[index] } -} -impl Borrow<[Tok]> for VName { - fn borrow(&self) -> &[Tok] { self.0.borrow() } -} -impl Borrow for VName { - fn borrow(&self) -> &PathSlice { PathSlice::new(&self.0[..]) } -} -impl Deref for VName { - type Target = PathSlice; - fn deref(&self) -> &Self::Target { self.borrow() } -} - -/// Error produced when a non-empty name [VName] or [Sym] is constructed with an -/// empty sequence -#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct EmptyNameError; -impl TryFrom<&[Tok]> for VName { - type Error = EmptyNameError; - fn try_from(value: &[Tok]) -> Result { - Self::new(value.iter().cloned()) - } -} - -/// An interned representation of a namespaced identifier. -/// -/// These names are always absolute. -/// -/// See also [VName] -#[derive(Clone, Hash, PartialEq, Eq)] -pub struct Sym(Tok>>); -impl Sym { - /// Assert that the sequence isn't empty, intern it and wrap it in a [Sym] to - /// represent this invariant - pub fn new(v: impl IntoIterator>) -> Result { - let items = v.into_iter().collect::>(); - Self::from_tok(i(&items)) - } - /// Read a `::` separated namespaced name. - pub fn parse(s: &str) -> Result { Ok(Sym(i(&VName::parse(s)?.into_vec()))) } - /// Assert that a token isn't empty, and wrap it in a [Sym] - pub fn from_tok(t: Tok>>) -> Result { - if t.is_empty() { Err(EmptyNameError) } else { Ok(Self(t)) } - } - /// Grab the interner token - pub fn tok(&self) -> Tok>> { self.0.clone() } - /// Get a number unique to this name suitable for arbitrary ordering. - pub fn id(&self) -> NonZeroUsize { self.0.id() } - /// Extern the sym for editing - pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) } -} -impl fmt::Debug for Sym { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Sym({self})") } -} -impl fmt::Display for Sym { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.str_iter().join("::")) - } -} -impl Index for Sym -where PathSlice: Index -{ - type Output = >::Output; - - fn index(&self, index: T) -> &Self::Output { &self.deref()[index] } -} -impl Borrow<[Tok]> for Sym { - fn borrow(&self) -> &[Tok] { &self.0[..] } -} -impl Borrow for Sym { - fn borrow(&self) -> &PathSlice { PathSlice::new(&self.0[..]) } -} -impl Deref for Sym { - type Target = PathSlice; - fn deref(&self) -> &Self::Target { self.borrow() } -} - -/// An abstraction over tokenized vs non-tokenized names so that they can be -/// handled together in datastructures. The names can never be empty -#[allow(clippy::len_without_is_empty)] // never empty -pub trait NameLike: - 'static + Clone + Eq + Hash + fmt::Debug + fmt::Display + Borrow -{ - /// Convert into held slice - fn as_slice(&self) -> &[Tok] { Borrow::::borrow(self) } - /// Get iterator over tokens - fn iter(&self) -> impl NameIter + '_ { self.as_slice().iter().cloned() } - /// Get iterator over string segments - fn str_iter(&self) -> impl Iterator + '_ { - self.as_slice().iter().map(|t| t.as_str()) - } - /// Fully resolve the name for printing - #[must_use] - fn to_strv(&self) -> Vec { self.iter().map(|s| s.to_string()).collect() } - /// Format the name as an approximate filename - fn as_src_path(&self) -> String { format!("{}.orc", self.iter().join("/")) } - /// Return the number of segments in the name - fn len(&self) -> NonZeroUsize { - NonZeroUsize::try_from(self.iter().count()).expect("NameLike never empty") - } - /// Like slice's `split_first` except we know that it always returns Some - fn split_first(&self) -> (Tok, &PathSlice) { - let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty"); - (foot.clone(), PathSlice::new(torso)) - } - /// Like slice's `split_last` except we know that it always returns Some - fn split_last(&self) -> (Tok, &PathSlice) { - let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty"); - (foot.clone(), PathSlice::new(torso)) - } - /// Get the first element - fn first(&self) -> Tok { self.split_first().0 } - /// Get the last element - fn last(&self) -> Tok { self.split_last().0 } -} - -impl NameLike for Sym {} -impl NameLike for VName {} - -/// Create a [Sym] literal. -/// -/// Both the name and its components will be cached in a thread-local static so -/// that subsequent executions of the expression only incur an Arc-clone for -/// cloning the token. -#[macro_export] -macro_rules! sym { - ($seg1:tt $( :: $seg:tt)*) => { - $crate::name::Sym::from_tok(intern_all::i!([intern_all::Tok]: &[ - intern_all::i!(str: stringify!($seg1)) - $( , intern_all::i!(str: stringify!($seg)) )* - ][..])).unwrap() - }; -} - -/// Create a [VName] literal. -/// -/// The components are interned much like in [sym]. -#[macro_export] -macro_rules! vname { - ($seg1:tt $( :: $seg:tt)*) => { - $crate::name::VName::new([ - intern_all::i!(str: stringify!($seg1)) - $( , intern_all::i!(str: stringify!($seg)) )* - ]).unwrap() - }; -} - -/// Create a [VPath] literal. -/// -/// The components are interned much like in [sym]. -#[macro_export] -macro_rules! vpath { - ($seg1:tt $( :: $seg:tt)+) => { - $crate::name::VPath(vec![ - intern_all::i!(str: stringify!($seg1)) - $( , intern_all::i!(str: stringify!($seg)) )+ - ]) - }; - () => { - $crate::name::VPath(vec![]) - } -} - -/// Create a &[PathSlice] literal. -/// -/// The components are interned much like in [sym] -#[macro_export] -macro_rules! path_slice { - ($seg1:tt $( :: $seg:tt)+) => { - $crate::name::PathSlice::new(&[ - intern_all::i!(str: stringify!($seg1)) - $( , intern_all::i!(str: stringify!($seg)) )+ - ]) - }; - () => { - $crate::name::PathSlice::new(&[]) - } -} - -#[cfg(test)] -mod test { - use std::borrow::Borrow; - - use intern_all::{i, Tok}; - - use super::{PathSlice, Sym, VName}; - use crate::name::VPath; - - #[test] - fn recur() { - let myname = vname!(foo::bar); - let _borrowed_slice: &[Tok] = myname.borrow(); - let _borrowed_pathslice: &PathSlice = myname.borrow(); - let _deref_pathslice: &PathSlice = &myname; - let _as_slice_out: &[Tok] = myname.as_slice(); - } - - #[test] - fn literals() { - assert_eq!(sym!(foo::bar::baz), Sym::new([i("foo"), i("bar"), i("baz")]).unwrap()); - assert_eq!(vname!(foo::bar::baz), VName::new([i("foo"), i("bar"), i("baz")]).unwrap()); - assert_eq!(vpath!(foo::bar::baz), VPath::new([i("foo"), i("bar"), i("baz")])); - assert_eq!(path_slice!(foo::bar::baz), PathSlice::new(&[i("foo"), i("bar"), i("baz")])); - } -} diff --git a/orchidlang/src/parse/context.rs b/orchidlang/src/parse/context.rs deleted file mode 100644 index 136498e..0000000 --- a/orchidlang/src/parse/context.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! Definition and implementations of the parsing context, which is used - -use std::ops::Range; -use std::sync::Arc; - -use super::lex_plugin::LexerPlugin; -use super::parse_plugin::ParseLinePlugin; -use crate::error::Reporter; -use crate::location::{SourceCode, SourceRange}; -use crate::utils::boxed_iter::{box_empty, BoxedIter}; -use crate::utils::sequence::Sequence; - -/// Trait enclosing all context features -/// -/// The main implementation is [ParseCtxImpl] -pub trait ParseCtx { - /// Get an object describing the file this source code comes from - #[must_use] - fn code_info(&self) -> SourceCode; - /// Get the list of all lexer plugins - #[must_use] - fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin>; - /// Get the list of all parser plugins - #[must_use] - fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin>; - /// Error reporter - #[must_use] - fn reporter(&self) -> &Reporter; - /// Find our position in the text given the text we've yet to parse - #[must_use] - fn pos(&self, tail: &str) -> usize { - let tail_len = tail.len(); - let source_len = self.source().len(); - (self.source().len().checked_sub(tail.len())).unwrap_or_else(|| { - panic!("tail.len()={tail_len} greater than self.source().len()={source_len}; tail={tail:?}") - }) - } - /// Generate a location given the length of a token and the unparsed text - /// after it. See also [ParseCtx::range_loc] if the maths gets complex. - #[must_use] - fn range(&self, len: usize, tl: &str) -> Range { - match self.pos(tl).checked_sub(len) { - Some(start) => start..self.pos(tl), - None => { - panic!("len={len} greater than tail.len()={}; tail={tl:?}", tl.len()) - }, - } - } - /// Create a contextful location for error reporting - #[must_use] - fn source_range(&self, len: usize, tl: &str) -> SourceRange { - self.range_loc(&self.range(len, tl)) - } - /// Create a contentful location from a range directly. - #[must_use] - fn range_loc(&self, range: &Range) -> SourceRange { - SourceRange { code: self.code_info(), range: range.clone() } - } - /// Get a reference to the full source text. This should not be used for - /// position math. - #[must_use] - fn source(&self) -> Arc { self.code_info().text.clone() } -} - -impl<'a, C: ParseCtx + 'a + ?Sized> ParseCtx for &'a C { - fn reporter(&self) -> &Reporter { (*self).reporter() } - fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { (*self).lexers() } - fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { (*self).line_parsers() } - fn pos(&self, tail: &str) -> usize { (*self).pos(tail) } - fn code_info(&self) -> SourceCode { (*self).code_info() } - fn source(&self) -> Arc { (*self).source() } - fn range(&self, l: usize, t: &str) -> Range { (*self).range(l, t) } -} - -/// Struct implementing context -#[derive(Clone)] -pub struct ParseCtxImpl<'a, 'b> { - /// File to be parsed; where it belongs in the tree and its text - pub code: SourceCode, - /// Error aggregator - pub reporter: &'b Reporter, - /// Lexer plugins for parsing custom literals - pub lexers: Sequence<'a, &'a (dyn LexerPlugin + 'a)>, - /// Parser plugins for parsing custom line structures - pub line_parsers: Sequence<'a, &'a dyn ParseLinePlugin>, -} -impl<'a, 'b> ParseCtx for ParseCtxImpl<'a, 'b> { - fn reporter(&self) -> &Reporter { self.reporter } - // Rust doesn't realize that this lifetime is covariant - #[allow(clippy::map_identity)] - fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { Box::new(self.lexers.iter().map(|r| r)) } - #[allow(clippy::map_identity)] - fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { - Box::new(self.line_parsers.iter().map(|r| r)) - } - fn code_info(&self) -> SourceCode { self.code.clone() } -} - -/// Context instance for testing. Implicitly provides a reporter and panics if -/// any errors are reported -pub struct MockContext(pub Reporter); -impl MockContext { - /// Create a new mock - pub fn new() -> Self { Self(Reporter::new()) } -} -impl Default for MockContext { - fn default() -> Self { Self::new() } -} -impl ParseCtx for MockContext { - fn reporter(&self) -> &Reporter { &self.0 } - fn pos(&self, tail: &str) -> usize { usize::MAX / 2 - tail.len() } - // these are expendable - fn code_info(&self) -> SourceCode { SourceRange::mock().code() } - fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { box_empty() } - fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { box_empty() } -} -impl Drop for MockContext { - fn drop(&mut self) { self.0.assert() } -} - -/// Context that assigns the same location to every subset of the source code. -/// Its main use case is to process source code that was dynamically generated -/// in response to some user code. See also [ReporterContext] -pub struct FlatLocContext<'a, C: ParseCtx + ?Sized> { - sub: &'a C, - range: &'a SourceRange, -} -impl<'a, C: ParseCtx + ?Sized> FlatLocContext<'a, C> { - /// Create a new context that will use the same provided range for every - /// parsed token - pub fn new(sub: &'a C, range: &'a SourceRange) -> Self { Self { sub, range } } -} -impl<'a, C: ParseCtx + ?Sized> ParseCtx for FlatLocContext<'a, C> { - fn reporter(&self) -> &Reporter { self.sub.reporter() } - fn pos(&self, _: &str) -> usize { 0 } - fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { self.sub.lexers() } - fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { self.sub.line_parsers() } - fn code_info(&self) -> SourceCode { self.range.code() } - fn range(&self, _: usize, _: &str) -> Range { self.range.range() } -} - -/// Context that forwards everything to a wrapped context except for error -/// reporting. See also [FlatLocContext] -pub struct ReporterContext<'a, C: ParseCtx + ?Sized> { - sub: &'a C, - reporter: &'a Reporter, -} -impl<'a, C: ParseCtx + ?Sized> ReporterContext<'a, C> { - /// Create a new context that will collect errors separately and forward - /// everything else to an enclosed context - pub fn new(sub: &'a C, reporter: &'a Reporter) -> Self { Self { sub, reporter } } -} -impl<'a, C: ParseCtx + ?Sized> ParseCtx for ReporterContext<'a, C> { - fn reporter(&self) -> &Reporter { self.reporter } - fn pos(&self, tail: &str) -> usize { self.sub.pos(tail) } - fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { self.sub.lexers() } - fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { self.sub.line_parsers() } - fn code_info(&self) -> SourceCode { self.sub.code_info() } - fn range(&self, len: usize, tl: &str) -> Range { self.sub.range(len, tl) } - fn range_loc(&self, range: &Range) -> SourceRange { self.sub.range_loc(range) } - fn source(&self) -> Arc { self.sub.source() } - fn source_range(&self, len: usize, tl: &str) -> SourceRange { self.sub.source_range(len, tl) } -} diff --git a/orchidlang/src/parse/errors.rs b/orchidlang/src/parse/errors.rs deleted file mode 100644 index 268845c..0000000 --- a/orchidlang/src/parse/errors.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! Errors produced by the parser. Plugins are encouraged to reuse these where -//! applicable. - -use intern_all::Tok; -use itertools::Itertools; - -use super::context::ParseCtx; -use super::frag::Frag; -use super::lexer::{Entry, Lexeme}; -use crate::error::{ProjectError, ProjectErrorObj, ProjectResult}; -use crate::location::{CodeOrigin, SourceRange}; -use crate::parse::parsed::PType; - -/// Parse error information without a location. Location data is added by the -/// parser. -pub trait ParseErrorKind: Sized + Send + Sync + 'static { - /// A general description of the error condition - const DESCRIPTION: &'static str; - /// A specific description of the error with concrete text sections - fn message(&self) -> String { Self::DESCRIPTION.to_string() } - /// Convert this error to a type-erased [ProjectError] to be handled together - /// with other Orchid errors. - fn pack(self, range: SourceRange) -> ProjectErrorObj { ParseError { kind: self, range }.pack() } -} - -struct ParseError { - pub range: SourceRange, - pub kind: T, -} -impl ProjectError for ParseError { - const DESCRIPTION: &'static str = T::DESCRIPTION; - fn one_position(&self) -> CodeOrigin { self.range.origin() } - fn message(&self) -> String { self.kind.message() } -} - -/// A line does not begin with an identifying keyword. Raised on the first token -pub(super) struct LineNeedsPrefix(pub Lexeme); -impl ParseErrorKind for LineNeedsPrefix { - const DESCRIPTION: &'static str = "This linetype requires a prefix"; - fn message(&self) -> String { format!("{} cannot appear at the beginning of a line", self.0) } -} - -/// The line ends abruptly. Raised on the last token -pub(super) struct UnexpectedEOL(pub Lexeme); -impl ParseErrorKind for UnexpectedEOL { - const DESCRIPTION: &'static str = "The line ended abruptly"; - fn message(&self) -> String { - "In Orchid, all line breaks outside parentheses start a new declaration".to_string() - } -} - -/// The line should have ended. Raised on last valid or first excess token -pub(super) struct ExpectedEOL; -impl ParseErrorKind for ExpectedEOL { - const DESCRIPTION: &'static str = "Expected the end of the line"; -} - -/// A name was expected. -pub(super) struct ExpectedName(pub Lexeme); -impl ParseErrorKind for ExpectedName { - const DESCRIPTION: &'static str = "A name was expected"; - fn message(&self) -> String { format!("Expected a name, found {}", self.0) } -} - -/// Unwrap a name or operator. -pub(super) fn expect_name( - Entry { lexeme, range }: &Entry, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult> { - match lexeme { - Lexeme::Name(n) => Ok(n.clone()), - lex => Err(ExpectedName(lex.clone()).pack(ctx.range_loc(range))), - } -} - -/// A specific lexeme was expected -pub(super) struct Expected { - /// The lexemes that would have been acceptable - pub expected: Vec, - /// Whether a name would also have been acceptable (multiname) - pub or_name: bool, - /// What was actually found - pub found: Lexeme, -} -impl ParseErrorKind for Expected { - const DESCRIPTION: &'static str = "A concrete token was expected"; - fn message(&self) -> String { - let list = match &self.expected[..] { - &[] => return "Unsatisfiable expectation".to_string(), - [only] => only.to_string(), - [a, b] => format!("either {a} or {b}"), - [variants @ .., last] => { - format!("any of {} or {last}", variants.iter().join(", ")) - }, - }; - let or_name = if self.or_name { " or a name" } else { "" }; - format!("Expected {list}{or_name} but found {}", self.found) - } -} -/// Assert that the entry contains exactly the specified lexeme -pub(super) fn expect(l: Lexeme, e: &Entry, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<()> { - if e.lexeme.strict_eq(&l) { - return Ok(()); - } - let found = e.lexeme.clone(); - let kind = Expected { expected: vec![l], or_name: false, found }; - Err(kind.pack(ctx.range_loc(&e.range))) -} - -/// A token reserved for future use was found in the code -pub(super) struct ReservedToken(pub Lexeme); -impl ParseErrorKind for ReservedToken { - const DESCRIPTION: &'static str = "Syntax reserved for future use"; - fn message(&self) -> String { format!("{} is a reserved token", self.0) } -} - -/// A token was found where it doesn't belong -pub(super) struct BadTokenInRegion { - /// What was found - pub lexeme: Lexeme, - /// Human-readable name of the region where it should not appear - pub region: &'static str, -} -impl ParseErrorKind for BadTokenInRegion { - const DESCRIPTION: &'static str = "An unexpected token was found"; - fn message(&self) -> String { format!("{} cannot appear in {}", self.lexeme, self.region) } -} - -/// Some construct was searched but not found. -pub(super) struct NotFound(pub &'static str); -impl ParseErrorKind for NotFound { - const DESCRIPTION: &'static str = "A specific lexeme was expected"; - fn message(&self) -> String { format!("{} was expected", self.0) } -} - -/// :: found on its own somewhere other than a general export -pub(super) struct LeadingNS; -impl ParseErrorKind for LeadingNS { - const DESCRIPTION: &'static str = ":: can only follow a name token"; -} - -/// Parens don't pair up -pub(super) struct MisalignedParen(pub Lexeme); -impl ParseErrorKind for MisalignedParen { - const DESCRIPTION: &'static str = "(), [] and {} must always pair up"; - fn message(&self) -> String { format!("This {} has no pair", self.0) } -} - -/// Export line contains a complex name -pub(super) struct NamespacedExport; -impl ParseErrorKind for NamespacedExport { - const DESCRIPTION: &'static str = "Only local names may be exported"; -} - -/// Export line contains * -pub(super) struct GlobExport; -impl ParseErrorKind for GlobExport { - const DESCRIPTION: &'static str = "Globstars are not allowed in exports"; -} - -/// Comment never ends -pub(super) struct NoCommentEnd; -impl ParseErrorKind for NoCommentEnd { - const DESCRIPTION: &'static str = "a comment was not closed with `]--`"; -} - -/// A placeholder's priority is a floating point number -pub(super) struct FloatPlacehPrio; -impl ParseErrorKind for FloatPlacehPrio { - const DESCRIPTION: &'static str = - "a placeholder priority has a decimal point or a negative exponent"; -} - -/// A number literal decodes to NaN -pub(super) struct NaNLiteral; -impl ParseErrorKind for NaNLiteral { - const DESCRIPTION: &'static str = "float literal decoded to NaN"; -} - -/// A sequence of digits in a number literal overflows [usize]. -pub(super) struct LiteralOverflow; -impl ParseErrorKind for LiteralOverflow { - const DESCRIPTION: &'static str = "number literal described number greater than usize::MAX"; -} - -/// A digit was expected but something else was found -pub(super) struct ExpectedDigit; -impl ParseErrorKind for ExpectedDigit { - const DESCRIPTION: &'static str = "expected a digit"; -} - -/// Expected a parenthesized block at the end of the line -pub(super) struct ExpectedBlock; -impl ParseErrorKind for ExpectedBlock { - const DESCRIPTION: &'static str = "Expected a parenthesized block"; -} -/// Remove two parentheses from the ends of the cursor -pub(super) fn expect_block<'a>( - tail: Frag<'a>, - typ: PType, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult> { - let (lp, tail) = tail.trim().pop(ctx)?; - expect(Lexeme::LP(typ), lp, ctx)?; - let (rp, tail) = tail.pop_back(ctx)?; - expect(Lexeme::RP(typ), rp, ctx)?; - Ok(tail.trim()) -} - -/// A namespaced name was expected but a glob pattern or a branching multiname -/// was found. -pub(super) struct ExpectedSingleName; -impl ParseErrorKind for ExpectedSingleName { - const DESCRIPTION: &'static str = "expected a single name, no wildcards, no branches"; -} diff --git a/orchidlang/src/parse/facade.rs b/orchidlang/src/parse/facade.rs deleted file mode 100644 index 50d5a6e..0000000 --- a/orchidlang/src/parse/facade.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Entrypoints to the parser that combine lexing and parsing - -use never::Never; - -use super::context::{FlatLocContext, ParseCtx, ReporterContext}; -use super::frag::Frag; -use super::lexer::lex; -use super::sourcefile::parse_module_body; -use crate::error::Reporter; -use crate::location::SourceRange; -use crate::parse::parsed::SourceLine; -use crate::parse::sourcefile::{parse_line, split_lines}; - -/// Parse a file -pub fn parse_file(ctx: &impl ParseCtx) -> Vec { - let tokens = lex(vec![], ctx.source().as_str(), ctx, |_| Ok::<_, Never>(false)) - .unwrap_or_else(|e| match e {}) - .tokens; - if tokens.is_empty() { Vec::new() } else { parse_module_body(Frag::from_slice(&tokens), ctx) } -} - -/// Parse a statically defined line sequence -/// -/// # Panics -/// -/// On any parse error, which is why it only accepts a string literal -pub fn parse_entries( - ctx: &dyn ParseCtx, - text: &'static str, - range: SourceRange, -) -> Vec { - let reporter = Reporter::new(); - let flctx = FlatLocContext::new(ctx, &range); - let ctx = ReporterContext::new(&flctx, &reporter); - let res = lex(vec![], text, &ctx, |_| Ok::<_, Never>(false)).unwrap_or_else(|e| match e {}); - let out = split_lines(Frag::from_slice(&res.tokens), &ctx) - .flat_map(|tokens| parse_line(tokens, &ctx).expect("pre-specified source")) - .map(|kind| kind.wrap(range.clone())) - .collect(); - reporter.assert(); - out -} diff --git a/orchidlang/src/parse/frag.rs b/orchidlang/src/parse/frag.rs deleted file mode 100644 index 6be9abf..0000000 --- a/orchidlang/src/parse/frag.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! The [Frag] is the main input datastructure of parsers. Beyond the slice of -//! tokens, it contains a fallback value that can be used for error reporting if -//! the fragment is empty. - -use std::ops::Range; - -use super::context::ParseCtx; -use super::errors::{ExpectedEOL, NotFound, ParseErrorKind, UnexpectedEOL}; -use super::lexer::{Entry, Lexeme}; -use crate::error::ProjectResult; - -/// Represents a slice which may or may not contain items, and a fallback entry -/// used for error reporting whenever the errant fragment is empty. -#[must_use = "fragment of code should not be discarded implicitly"] -#[derive(Clone, Copy)] -pub struct Frag<'a> { - /// Entry to place in errors if the fragment contains no tokens - pub fallback: &'a Entry, - /// Tokens to parse - pub data: &'a [Entry], -} -impl<'a> Frag<'a> { - /// Create a new fragment - pub fn new(fallback: &'a Entry, data: &'a [Entry]) -> Self { Self { fallback, data } } - - /// Remove comments and line breaks from both ends of the text - pub fn trim(self) -> Self { - let Self { data, fallback } = self; - let front = data.iter().take_while(|e| e.is_filler()).count(); - let (_, right) = data.split_at(front); - let back = right.iter().rev().take_while(|e| e.is_filler()).count(); - let (data, _) = right.split_at(right.len() - back); - Self { fallback, data } - } - - /// Discard the first entry - pub fn step(self, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult { - let Self { data, fallback: Entry { lexeme, range } } = self; - match data.split_first() { - Some((fallback, data)) => Ok(Frag { data, fallback }), - None => Err(UnexpectedEOL(lexeme.clone()).pack(ctx.range_loc(range))), - } - } - - /// Get the first entry - pub fn pop(self, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<(&'a Entry, Self)> { - Ok((self.get(0, ctx)?, self.step(ctx)?)) - } - - /// Retrieve an index from a slice or raise an error if it isn't found. - pub fn get(self, idx: usize, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<&'a Entry> { - self.data.get(idx).ok_or_else(|| { - let entry = self.data.last().unwrap_or(self.fallback).clone(); - UnexpectedEOL(entry.lexeme).pack(ctx.range_loc(&entry.range)) - }) - } - - /// Area covered by this fragment - #[must_use] - pub fn range(self) -> Range { - self.data.first().map_or_else( - || self.fallback.range.clone(), - |f| f.range.start..self.data.last().unwrap().range.end, - ) - } - - /// Find a given token, split the fragment there and read some value from the - /// separator. See also [Frag::find] - pub fn find_map( - self, - msg: &'static str, - ctx: &(impl ParseCtx + ?Sized), - mut f: impl FnMut(&'a Lexeme) -> Option, - ) -> ProjectResult<(Self, T, Self)> { - let Self { data, fallback } = self; - let (dot_idx, output) = skip_parenthesized(data.iter()) - .find_map(|(i, e)| f(&e.lexeme).map(|t| (i, t))) - .ok_or_else(|| NotFound(msg).pack(ctx.range_loc(&self.range())))?; - let (left, not_left) = data.split_at(dot_idx); - let (middle_ent, right) = not_left.split_first().unwrap(); - Ok((Self::new(fallback, left), output, Self::new(middle_ent, right))) - } - - /// Split the fragment at a token and return just the two sides. - /// See also [Frag::find_map]. - pub fn find( - self, - descr: &'static str, - ctx: &(impl ParseCtx + ?Sized), - mut f: impl FnMut(&Lexeme) -> bool, - ) -> ProjectResult<(Self, Self)> { - let (l, _, r) = self.find_map(descr, ctx, |l| Some(l).filter(|l| f(l)))?; - Ok((l, r)) - } - - /// Remove the last item from the fragment - pub fn pop_back(self, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<(&'a Entry, Self)> { - let Self { data, fallback } = self; - let (last, data) = (data.split_last()) - .ok_or_else(|| UnexpectedEOL(fallback.lexeme.clone()).pack(ctx.range_loc(&fallback.range)))?; - Ok((last, Self { fallback, data })) - } - - /// # Panics - /// - /// If the slice is empty - pub fn from_slice(data: &'a [Entry]) -> Self { - let fallback = (data.first()).expect("Empty slice cannot be converted into a parseable"); - Self { data, fallback } - } - - /// Assert that the fragment is empty. - pub fn expect_empty(self, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<()> { - match self.data.first() { - Some(x) => Err(ExpectedEOL.pack(ctx.range_loc(&x.range))), - None => Ok(()), - } - } -} - -fn skip_parenthesized<'a>( - it: impl Iterator, -) -> impl Iterator { - let mut paren_lvl = 1; - it.enumerate().filter(move |(_, e)| { - match e.lexeme { - Lexeme::LP(_) => paren_lvl += 1, - Lexeme::RP(_) => paren_lvl -= 1, - _ => (), - } - paren_lvl <= 1 - }) -} diff --git a/orchidlang/src/parse/lex_plugin.rs b/orchidlang/src/parse/lex_plugin.rs deleted file mode 100644 index c301638..0000000 --- a/orchidlang/src/parse/lex_plugin.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Abstractions for dynamic extensions to the lexer to parse custom literals - -use dyn_clone::DynClone; -use never::Never; - -use super::context::{FlatLocContext, ParseCtx}; -use super::lexer::{lex, Entry, LexRes}; -use crate::error::ProjectResult; -use crate::location::SourceRange; - -/// Data passed to the recursive sub-lexer -pub struct LexPluginRecur<'a, 'b> { - /// Text to tokenize - pub tail: &'a str, - /// Callback that will be called between lexemes on the leftover text. - /// When it returns true, the lexer exits and leaves the remaining text for - /// you. - pub exit: &'b mut dyn for<'c> FnMut(&'c str) -> ProjectResult, -} - -/// Data and actions available to a lexer plugin -pub trait LexPluginReq<'a> { - /// Text to tokenize - fn tail(&self) -> &'a str; - /// [ParseCtx] instance for calculating locations and such - fn ctx(&self) -> &dyn ParseCtx; - /// Start a child lexer that calls back between lexemes and exits on your - /// command. You can combine this with custom atoms to create holes for - /// expressions in your literals like the template strings of most languages - /// other than Rust. - fn recurse(&self, req: LexPluginRecur<'a, '_>) -> ProjectResult>; - /// Lex an inserted piece of text, especially when translating custom syntax - /// into multiple lexemes. - /// - /// # Panics - /// - /// If tokenization fails - fn insert(&self, data: &str, range: SourceRange) -> Vec; -} - -/// External plugin that parses a literal into recognized Orchid lexemes, most -/// likely atoms. -pub trait LexerPlugin: Send + Sync + DynClone { - /// Run the lexer - fn lex<'a>(&self, req: &'_ dyn LexPluginReq<'a>) -> Option>>; -} - -/// Implementation of [LexPluginReq] -pub struct LexPlugReqImpl<'a, 'b, TCtx: ParseCtx> { - /// Text to be lexed - pub tail: &'a str, - /// Context data - pub ctx: &'b TCtx, -} -impl<'a, 'b, TCtx: ParseCtx> LexPluginReq<'a> for LexPlugReqImpl<'a, 'b, TCtx> { - fn tail(&self) -> &'a str { self.tail } - fn ctx(&self) -> &dyn ParseCtx { self.ctx } - fn recurse(&self, req: LexPluginRecur<'a, '_>) -> ProjectResult> { - lex(Vec::new(), req.tail, self.ctx, |s| (req.exit)(s)) - } - fn insert(&self, data: &str, range: SourceRange) -> Vec { - let ctx = FlatLocContext::new(self.ctx as &dyn ParseCtx, &range); - lex(Vec::new(), data, &ctx, |_| Ok::<_, Never>(false)).unwrap_or_else(|e| match e {}).tokens - } -} diff --git a/orchidlang/src/parse/lexer.rs b/orchidlang/src/parse/lexer.rs deleted file mode 100644 index eb7370f..0000000 --- a/orchidlang/src/parse/lexer.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! Convert source text into a sequence of tokens. Newlines and comments are -//! included, but spacing is converted into numerical ranges on the elements. -//! -//! Literals lose their syntax form here and are handled in an abstract -//! representation hence - -use std::fmt; -use std::ops::Range; -use std::sync::Arc; - -use intern_all::{i, Tok}; -use itertools::Itertools; -use ordered_float::NotNan; - -use super::context::ParseCtx; -use super::errors::{FloatPlacehPrio, NoCommentEnd}; -use super::lex_plugin::LexerPlugin; -use super::numeric::{numstart, parse_num, print_nat16}; -use crate::foreign::atom::AtomGenerator; -use crate::libs::std::number::Numeric; -use crate::parse::errors::ParseErrorKind; -use crate::parse::lex_plugin::LexPlugReqImpl; -use crate::parse::numeric::{numchar, NumericLexer}; -use crate::parse::parsed::{PHClass, PType, Placeholder}; - -/// A lexeme and the location where it was found -#[derive(Clone, Debug)] -pub struct Entry { - /// the lexeme - pub lexeme: Lexeme, - /// the range in bytes - pub range: Range, -} -impl Entry { - /// Checks if the lexeme is a comment or line break - #[must_use] - pub fn is_filler(&self) -> bool { matches!(self.lexeme, Lexeme::Comment(_) | Lexeme::BR) } - - /// Create a new entry - #[must_use] - pub fn new(range: Range, lexeme: Lexeme) -> Self { Self { lexeme, range } } -} - -impl fmt::Display for Entry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.lexeme.fmt(f) } -} - -impl PartialEq for Entry { - fn eq(&self, other: &Lexeme) -> bool { self.lexeme == *other } -} - -/// A unit of syntax -#[derive(Clone, Debug, PartialEq)] -pub enum Lexeme { - /// Atoms parsed by plugins - Atom(AtomGenerator), - /// Keyword or name - Name(Tok), - /// Macro operator `=`number`=>` - Arrow(NotNan), - /// `:=` - Walrus, - /// Line break - BR, - /// `::` - NS, - /// Left paren `([{` - LP(PType), - /// Right paren `)]}` - RP(PType), - /// `\` - BS, - /// `@`` - At, - /// `:` - Type, - /// comment - Comment(Arc), - /// placeholder in a macro. - Placeh(Placeholder), -} - -impl fmt::Display for Lexeme { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Atom(a) => write!(f, "{a:?}"), - Self::Name(token) => write!(f, "{}", **token), - Self::Walrus => write!(f, ":="), - Self::Arrow(prio) => write!(f, "={}=>", print_nat16(*prio)), - Self::NS => write!(f, "::"), - Self::LP(t) => write!(f, "{}", t.l()), - Self::RP(t) => write!(f, "{}", t.r()), - Self::BR => writeln!(f), - Self::BS => write!(f, "\\"), - Self::At => write!(f, "@"), - Self::Type => write!(f, ":"), - Self::Comment(text) => write!(f, "--[{}]--", text), - Self::Placeh(ph) => write!(f, "{ph}"), - } - } -} - -impl Lexeme { - /// Compare lexemes for equality. It's `strict` because for atoms it uses the - /// strict equality comparison - pub fn strict_eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Arrow(f1), Self::Arrow(f2)) => f1 == f2, - (Self::At, Self::At) | (Self::BR, Self::BR) => true, - (Self::BS, Self::BS) => true, - (Self::NS, Self::NS) | (Self::Type, Self::Type) => true, - (Self::Walrus, Self::Walrus) => true, - (Self::Atom(a1), Self::Atom(a2)) => a1.run().0.parser_eq(&*a2.run().0), - (Self::Comment(c1), Self::Comment(c2)) => c1 == c2, - (Self::LP(p1), Self::LP(p2)) | (Self::RP(p1), Self::RP(p2)) => p1 == p2, - (Self::Name(n1), Self::Name(n2)) => n1 == n2, - (Self::Placeh(ph1), Self::Placeh(ph2)) => ph1 == ph2, - (..) => false, - } - } -} - -/// Data returned from the lexer -pub struct LexRes<'a> { - /// Leftover text. If the bail callback never returned true, this is empty - pub tail: &'a str, - /// Lexemes extracted from the text - pub tokens: Vec, -} - -/// Neatly format source code -#[allow(unused)] -pub fn format(lexed: &[Entry]) -> String { lexed.iter().join(" ") } - -/// Character filter that can appear in a keyword or name -pub fn namechar(c: char) -> bool { c.is_alphanumeric() | (c == '_') } -/// Character filter that can start a name -pub fn namestart(c: char) -> bool { c.is_alphabetic() | (c == '_') } -/// Character filter that can appear in operators. -pub fn opchar(c: char) -> bool { - !namestart(c) && !numstart(c) && !c.is_whitespace() && !"()[]{},'\"\\".contains(c) -} - -/// Split off all characters from the beginning that match a filter -pub fn split_filter(s: &str, mut pred: impl FnMut(char) -> bool) -> (&str, &str) { - s.find(|c| !pred(c)).map_or((s, ""), |i| s.split_at(i)) -} - -fn lit_table() -> impl IntoIterator { - [ - ("\\", Lexeme::BS), - ("@", Lexeme::At), - ("(", Lexeme::LP(PType::Par)), - ("[", Lexeme::LP(PType::Sqr)), - ("{", Lexeme::LP(PType::Curl)), - (")", Lexeme::RP(PType::Par)), - ("]", Lexeme::RP(PType::Sqr)), - ("}", Lexeme::RP(PType::Curl)), - ("\n", Lexeme::BR), - (":=", Lexeme::Walrus), - ("::", Lexeme::NS), - (":", Lexeme::Type), - ] -} - -static BUILTIN_ATOMS: &[&dyn LexerPlugin] = &[&NumericLexer]; - -/// Convert source code to a flat list of tokens. The bail callback will be -/// called between lexemes. When it returns true, the remaining text is -/// returned without processing. -pub fn lex<'a, E>( - mut tokens: Vec, - mut data: &'a str, - ctx: &'_ impl ParseCtx, - mut bail: impl FnMut(&str) -> Result, -) -> Result, E> { - let mut prev_len = data.len() + 1; - 'tail: loop { - if prev_len == data.len() { - panic!("got stuck at {data:?}, parsed {:?}", tokens.last().unwrap()); - } - prev_len = data.len(); - data = data.trim_start_matches(|c: char| c.is_whitespace() && c != '\n'); - if bail(data)? { - return Ok(LexRes { tokens, tail: data }); - } - let mut chars = data.chars(); - let head = match chars.next() { - None => return Ok(LexRes { tokens, tail: data }), - Some(h) => h, - }; - for lexer in ctx.lexers().chain(BUILTIN_ATOMS.iter().copied()) { - let req = LexPlugReqImpl { tail: data, ctx }; - if let Some(res) = lexer.lex(&req) { - let LexRes { tail, tokens: mut new_tokens } = - ctx.reporter().fallback(res, |_| LexRes { tail: "", tokens: vec![] }); - // fallback: no tokens left, no additional tokens parsed - if tail.len() == data.len() { - panic!("lexer plugin consumed 0 characters") - } - tokens.append(&mut new_tokens); - data = tail; - continue 'tail; - } - } - for (prefix, lexeme) in lit_table() { - if let Some(tail) = data.strip_prefix(prefix) { - tokens.push(Entry::new(ctx.range(prefix.len(), tail), lexeme.clone())); - data = tail; - continue 'tail; - } - } - - if let Some(tail) = data.strip_prefix(',') { - tokens.push(Entry::new(ctx.range(1, tail), Lexeme::Name(i!(str: ",")))); - data = tail; - continue 'tail; - } - if let Some(tail) = data.strip_prefix("--[") { - let (note, tail) = tail.split_once("]--").unwrap_or_else(|| { - ctx.reporter().report(NoCommentEnd.pack(ctx.source_range(tail.len(), ""))); - (tail, "") // fallback: the rest of the file is in the comment - }); - let lexeme = Lexeme::Comment(Arc::new(note.to_string())); - tokens.push(Entry::new(ctx.range(note.len() + 3, tail), lexeme)); - data = tail; - continue 'tail; - } - if let Some(tail) = data.strip_prefix("--") { - let (note, tail) = split_filter(tail, |c| c != '\n'); - let lexeme = Lexeme::Comment(Arc::new(note.to_string())); - tokens.push(Entry::new(ctx.range(note.len(), tail), lexeme)); - data = tail; - continue 'tail; - } - // Parse a rule arrow - if let Some(tail) = data.strip_prefix('=') { - if tail.chars().next().map_or(false, numstart) { - let (num, post_num) = split_filter(tail, numchar); - if let Some(tail) = post_num.strip_prefix("=>") { - let prio = parse_num(num).unwrap_or_else(|e| { - ctx.reporter().report(e.into_proj(num.len(), post_num, ctx)); - Numeric::Uint(0) - }); - let lexeme = Lexeme::Arrow(prio.as_float()); - tokens.push(Entry::new(ctx.range(num.len() + 3, tail), lexeme)); - data = tail; - continue 'tail; - } - } - } - // Parse scalar placeholder $_name or $name - if let Some(tail) = data.strip_prefix('$') { - let (nameonly, tail) = tail.strip_prefix('_').map_or((false, tail), |t| (true, t)); - let (name, tail) = split_filter(tail, namechar); - if !name.is_empty() { - let class = if nameonly { PHClass::Name } else { PHClass::Scalar }; - let lexeme = Lexeme::Placeh(Placeholder { name: i(name), class }); - tokens.push(Entry::new(ctx.range(name.len() + 1, tail), lexeme)); - data = tail; - continue 'tail; - } - } - // Parse vectorial placeholder. `..` or `...`, then `$name`, then an optional - // `:n` where n is a number. - if let Some(tail) = data.strip_prefix("..") { - let (nonzero, tail) = tail.strip_prefix('.').map_or((false, tail), |t| (true, t)); - if let Some(tail) = tail.strip_prefix('$') { - let (name, tail) = split_filter(tail, namechar); - if !name.is_empty() { - let (prio, priolen, tail) = tail - .strip_prefix(':') - .map(|tail| split_filter(tail, numchar)) - .filter(|(num, _)| !num.is_empty()) - .map(|(num_str, tail)| { - let p = ctx.reporter().fallback( - parse_num(num_str).map_err(|e| e.into_proj(num_str.len(), tail, ctx)).and_then( - |num| match num { - Numeric::Uint(usize) => Ok(usize), - Numeric::Float(_) => - Err(FloatPlacehPrio.pack(ctx.source_range(num_str.len(), tail))), - }, - ), - |_| 0, - ); - (p, num_str.len() + 1, tail) - }) - .unwrap_or((0, 0, tail)); - let byte_len = if nonzero { 4 } else { 3 } + priolen + name.len(); - let class = PHClass::Vec { nonzero, prio }; - let lexeme = Lexeme::Placeh(Placeholder { name: i(name), class }); - tokens.push(Entry::new(ctx.range(byte_len, tail), lexeme)); - data = tail; - continue 'tail; - } - } - } - if namestart(head) { - let (name, tail) = split_filter(data, namechar); - if !name.is_empty() { - let lexeme = Lexeme::Name(i(name)); - tokens.push(Entry::new(ctx.range(name.len(), tail), lexeme)); - data = tail; - continue 'tail; - } - } - if opchar(head) { - let (name, tail) = split_filter(data, opchar); - if !name.is_empty() { - let lexeme = Lexeme::Name(i(name)); - tokens.push(Entry::new(ctx.range(name.len(), tail), lexeme)); - data = tail; - continue 'tail; - } - } - unreachable!(r#"opchar is pretty much defined as "not namechar" "#) - } -} diff --git a/orchidlang/src/parse/mod.rs b/orchidlang/src/parse/mod.rs deleted file mode 100644 index 8927bb6..0000000 --- a/orchidlang/src/parse/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Parser, and abstractions for interacting with it from language extensions -pub mod context; -pub mod errors; -pub mod facade; -pub mod frag; -pub mod lex_plugin; -pub mod lexer; -pub mod multiname; -pub mod numeric; -pub mod parse_plugin; -pub mod parsed; -mod sourcefile; diff --git a/orchidlang/src/parse/multiname.rs b/orchidlang/src/parse/multiname.rs deleted file mode 100644 index 6983a10..0000000 --- a/orchidlang/src/parse/multiname.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Parse the tree-like name sets used to represent imports - -use std::collections::VecDeque; -use std::ops::Range; - -use intern_all::{i, Tok}; - -use super::context::ParseCtx; -use super::errors::{Expected, ParseErrorKind}; -use super::frag::Frag; -use super::lexer::{Entry, Lexeme}; -use crate::error::ProjectResult; -use crate::location::SourceRange; -use crate::name::VPath; -use crate::parse::parsed::{Import, PType}; -use crate::utils::boxed_iter::{box_chain, box_once, BoxedIter}; - -struct Subresult { - glob: bool, - deque: VecDeque>, - range: Range, -} -impl Subresult { - #[must_use] - fn new_glob(range: &Range) -> Self { - Self { glob: true, deque: VecDeque::new(), range: range.clone() } - } - - #[must_use] - fn new_named(name: Tok, range: &Range) -> Self { - Self { glob: false, deque: VecDeque::from([name]), range: range.clone() } - } - - #[must_use] - fn push_front(mut self, name: Tok) -> Self { - self.deque.push_front(name); - self - } - - #[must_use] - fn finalize(self, ctx: &(impl ParseCtx + ?Sized)) -> Import { - let Self { mut deque, glob, range } = self; - debug_assert!(glob || !deque.is_empty(), "The constructors forbid this"); - let name = if glob { None } else { deque.pop_back() }; - let range = ctx.range_loc(&range); - Import { name, range, path: VPath(deque.into()) } - } -} - -fn parse_multiname_branch<'a>( - cursor: Frag<'a>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult<(BoxedIter<'a, Subresult>, Frag<'a>)> { - let comma = i!(str: ","); - let (subnames, cursor) = parse_multiname_rec(cursor, ctx)?; - let (Entry { lexeme, range }, cursor) = cursor.trim().pop(ctx)?; - match &lexeme { - Lexeme::RP(PType::Par) => Ok((subnames, cursor)), - Lexeme::Name(n) if n == &comma => { - let (tail, cont) = parse_multiname_branch(cursor, ctx)?; - Ok((box_chain!(subnames, tail), cont)) - }, - _ => { - let expected = vec![Lexeme::Name(comma), Lexeme::RP(PType::Par)]; - let err = Expected { expected, or_name: false, found: lexeme.clone() }; - Err(err.pack(SourceRange { range: range.clone(), code: ctx.code_info() })) - }, - } -} - -fn parse_multiname_rec<'a>( - cursor: Frag<'a>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult<(BoxedIter<'a, Subresult>, Frag<'a>)> { - let (head, mut cursor) = cursor.trim().pop(ctx)?; - match &head.lexeme { - Lexeme::LP(PType::Par) => parse_multiname_branch(cursor, ctx), - Lexeme::LP(PType::Sqr) => { - let mut names = Vec::new(); - loop { - let (Entry { lexeme, range }, tail) = cursor.trim().pop(ctx)?; - cursor = tail; - match lexeme { - Lexeme::Name(n) => names.push((n.clone(), range)), - Lexeme::RP(PType::Sqr) => break, - _ => { - let err = Expected { - expected: vec![Lexeme::RP(PType::Sqr)], - or_name: true, - found: head.lexeme.clone(), - }; - return Err(err.pack(ctx.range_loc(range))); - }, - } - } - Ok(( - Box::new( - names.into_iter().map(|(name, location)| Subresult::new_named(name.clone(), location)), - ), - cursor, - )) - }, - Lexeme::Name(n) if *n == i!(str: "*") => - Ok((box_once(Subresult::new_glob(&head.range)), cursor)), - Lexeme::Name(n) if ![i!(str: ","), i!(str: "*")].contains(n) => { - let cursor = cursor.trim(); - if cursor.get(0, ctx).map_or(false, |e| e.lexeme.strict_eq(&Lexeme::NS)) { - let cursor = cursor.step(ctx)?; - let (out, cursor) = parse_multiname_rec(cursor, ctx)?; - let out = Box::new(out.map(|sr| sr.push_front(n.clone()))); - Ok((out, cursor)) - } else { - Ok((box_once(Subresult::new_named(n.clone(), &head.range)), cursor)) - } - }, - _ => { - let expected = vec![Lexeme::LP(PType::Par)]; - let err = Expected { expected, or_name: true, found: head.lexeme.clone() }; - Err(err.pack(ctx.range_loc(&head.range))) - }, - } -} - -/// Parse a tree that describes several names. The tree can be -/// -/// - name (except `,` or `*`) -/// - name (except `,` or `*`) `::` tree -/// - `(` tree `,` tree ... `)` -/// - `*` (wildcard) -/// - `[` name name ... `]` (including `,` or `*`). -/// -/// Examples of valid syntax: -/// -/// ```txt -/// foo -/// foo::bar::baz -/// foo::bar::(baz, quz::quux, fimble::*) -/// foo::bar::[baz quz * +] -/// ``` -pub fn parse_multiname<'a>( - cursor: Frag<'a>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult<(Vec, Frag<'a>)> { - let (output, cont) = parse_multiname_rec(cursor, ctx)?; - Ok((output.map(|sr| sr.finalize(ctx)).collect(), cont)) -} diff --git a/orchidlang/src/parse/numeric.rs b/orchidlang/src/parse/numeric.rs deleted file mode 100644 index 2627425..0000000 --- a/orchidlang/src/parse/numeric.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Parse a float or integer. These functions are also used for the macro -//! priority numbers - -use std::num::IntErrorKind; -use std::ops::Range; - -use ordered_float::NotNan; - -use super::context::ParseCtx; -use super::errors::{ExpectedDigit, LiteralOverflow, NaNLiteral, ParseErrorKind}; -use super::lex_plugin::LexPluginReq; -#[allow(unused)] // for doc -use super::lex_plugin::LexerPlugin; -use super::lexer::{split_filter, Entry, LexRes, Lexeme}; -use crate::error::{ProjectErrorObj, ProjectResult}; -use crate::foreign::atom::AtomGenerator; -use crate::foreign::inert::Inert; -use crate::libs::std::number::Numeric; - -impl NumError { - /// Convert into [ProjectErrorObj] - pub fn into_proj( - self, - len: usize, - tail: &str, - ctx: &(impl ParseCtx + ?Sized), - ) -> ProjectErrorObj { - let start = ctx.source().len() - tail.len() - len + self.range.start; - let location = ctx.range_loc(&(start..start + self.range.len())); - match self.kind { - NumErrorKind::NaN => NaNLiteral.pack(location), - NumErrorKind::InvalidDigit => ExpectedDigit.pack(location), - NumErrorKind::Overflow => LiteralOverflow.pack(location), - } - } -} - - - -/// [LexerPlugin] for a number literal -#[derive(Clone)] -pub struct NumericLexer; -impl LexerPlugin for NumericLexer { - fn lex<'b>(&self, req: &'_ dyn LexPluginReq<'b>) -> Option>> { - req.tail().chars().next().filter(|c| numstart(*c)).map(|_| { - let (num_str, tail) = split_filter(req.tail(), numchar); - let ag = match parse_num(num_str) { - Ok(Numeric::Float(f)) => AtomGenerator::cloner(Inert(f)), - Ok(Numeric::Uint(i)) => AtomGenerator::cloner(Inert(i)), - Err(e) => return Err(e.into_proj(num_str.len(), tail, req.ctx())), - }; - let range = req.ctx().range(num_str.len(), tail); - let entry = Entry { lexeme: Lexeme::Atom(ag), range }; - Ok(LexRes { tail, tokens: vec![entry] }) - }) - } -} diff --git a/orchidlang/src/parse/parse_plugin.rs b/orchidlang/src/parse/parse_plugin.rs deleted file mode 100644 index 65b4e5d..0000000 --- a/orchidlang/src/parse/parse_plugin.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Abstractions for dynamic extensions to the parser that act across entries. -//! Macros are the primary syntax extension mechanism, but they only operate -//! within a constant and can't interfere with name reproject. - -use std::ops::Range; - -use dyn_clone::DynClone; -use intern_all::Tok; - -use super::context::ParseCtx; -use super::errors::{expect, expect_block, expect_name}; -use super::facade::parse_entries; -use super::frag::Frag; -use super::lexer::{Entry, Lexeme}; -use super::parsed::{Constant, Expr, ModuleBlock, PType, Rule, SourceLine, SourceLineKind}; -use super::sourcefile::{ - exprv_to_single, parse_const, parse_exprv, parse_line, parse_module, parse_module_body, - parse_nsname, parse_rule, split_lines, -}; -use crate::error::{ProjectErrorObj, ProjectResult}; -use crate::location::SourceRange; -use crate::name::VName; -use crate::utils::boxed_iter::BoxedIter; - -/// Information and actions exposed to [ParseLinePlugin]. A plugin should never -/// import and call the parser directly because it might be executed in a -/// different version of the parser. -pub trait ParsePluginReq<'t> { - // ################ Frag and ParseCtx ################ - - /// The token sequence this parser must parse - fn frag(&self) -> Frag; - /// Get the location of a fragment - fn frag_loc(&self, f: Frag) -> SourceRange; - /// Convert a numeric byte range into a location - fn range_loc(&self, r: Range) -> SourceRange; - /// Remove the first token of the fragment - fn pop<'a>(&self, f: Frag<'a>) -> ProjectResult<(&'a Entry, Frag<'a>)>; - /// Remove the last element of the fragment - fn pop_back<'a>(&self, f: Frag<'a>) -> ProjectResult<(&'a Entry, Frag<'a>)>; - - // ################ Parser states ################ - - /// Split up the lines in a fragment. The fragment must outlive the iterator - /// and the request itself must outlive both - fn split_lines<'a: 'b, 'b>(&'b self, f: Frag<'a>) -> BoxedIter<'b, Frag<'a>> - where 't: 'b + 'a; - /// Parse a sequence of source lines separated by line breaks - fn parse_module_body(&self, frag: Frag) -> ProjectResult>; - /// Parse a single source line. This returns a vector because plugins can - /// convert a single line into multiple entries - fn parse_line(&self, frag: Frag) -> ProjectResult>; - /// Parse a macro rule ` =prio=> ` - fn parse_rule(&self, frag: Frag) -> ProjectResult; - /// Parse a constant declaration ` := ` - fn parse_const(&self, frag: Frag) -> ProjectResult; - /// Parse a namespaced name `name::name` - fn parse_nsname<'a>(&self, f: Frag<'a>) -> ProjectResult<(VName, Frag<'a>)>; - /// Parse a module declaration. ` ( )` - fn parse_module(&self, frag: Frag) -> ProjectResult; - /// Parse a sequence of expressions. In principle, it never makes sense to - /// parse a single expression because it could always be a macro invocation. - fn parse_exprv<'a>(&self, f: Frag<'a>, p: Option) -> ProjectResult<(Vec, Frag<'a>)>; - /// Parse a prepared string of code - fn parse_entries(&self, t: &'static str, r: SourceRange) -> Vec; - /// Convert a sequence of expressions to a single one by parenthesization if - /// necessary - fn vec_to_single(&self, fallback: &Entry, v: Vec) -> ProjectResult; - - // ################ Assertions ################ - - /// Unwrap a single name token or raise an error - fn expect_name(&self, entry: &Entry) -> ProjectResult>; - /// Assert that the entry contains exactly the specified lexeme - fn expect(&self, l: Lexeme, e: &Entry) -> ProjectResult<()>; - /// Remove two parentheses from the ends of the cursor - fn expect_block<'a>(&self, f: Frag<'a>, p: PType) -> ProjectResult>; - /// Ensure that the fragment is empty - fn expect_empty(&self, f: Frag) -> ProjectResult<()>; - /// Report a fatal error while also producing output to be consumed by later - /// stages for improved error reporting - fn report_err(&self, e: ProjectErrorObj); -} - -/// External plugin that parses an unrecognized source line into lines of -/// recognized types -pub trait ParseLinePlugin: Sync + Send + DynClone { - /// Attempt to parse a line. Returns [None] if the line isn't recognized, - /// [Some][Err] if it's recognized but incorrect. - fn parse(&self, req: &dyn ParsePluginReq) -> Option>>; -} - -/// Implementation of [ParsePluginReq] exposing sub-parsers and data to the -/// plugin via dynamic dispatch -pub struct ParsePlugReqImpl<'a, TCtx: ParseCtx + ?Sized> { - /// Fragment of text to be parsed by the plugin - pub frag: Frag<'a>, - /// Context for recursive commands and to expose to the plugin - pub ctx: &'a TCtx, -} -impl<'ty, TCtx: ParseCtx + ?Sized> ParsePluginReq<'ty> for ParsePlugReqImpl<'ty, TCtx> { - fn frag(&self) -> Frag { self.frag } - fn frag_loc(&self, f: Frag) -> SourceRange { self.range_loc(f.range()) } - fn range_loc(&self, r: Range) -> SourceRange { self.ctx.range_loc(&r) } - fn pop<'a>(&self, f: Frag<'a>) -> ProjectResult<(&'a Entry, Frag<'a>)> { f.pop(self.ctx) } - fn pop_back<'a>(&self, f: Frag<'a>) -> ProjectResult<(&'a Entry, Frag<'a>)> { - f.pop_back(self.ctx) - } - fn split_lines<'a: 'b, 'b>(&'b self, f: Frag<'a>) -> BoxedIter<'b, Frag<'a>> - where - 'ty: 'b, - 'ty: 'a, - { - Box::new(split_lines(f, self.ctx)) - } - fn parse_module_body(&self, f: Frag) -> ProjectResult> { - Ok(parse_module_body(f, self.ctx)) - } - fn parse_line(&self, f: Frag) -> ProjectResult> { parse_line(f, self.ctx) } - fn parse_rule(&self, f: Frag) -> ProjectResult { parse_rule(f, self.ctx) } - fn parse_const(&self, f: Frag) -> ProjectResult { parse_const(f, self.ctx) } - fn parse_nsname<'a>(&self, f: Frag<'a>) -> ProjectResult<(VName, Frag<'a>)> { - parse_nsname(f, self.ctx) - } - fn parse_module(&self, f: Frag) -> ProjectResult { parse_module(f, self.ctx) } - fn parse_exprv<'a>(&self, f: Frag<'a>, p: Option) -> ProjectResult<(Vec, Frag<'a>)> { - parse_exprv(f, p, self.ctx) - } - fn parse_entries(&self, s: &'static str, r: SourceRange) -> Vec { - parse_entries(&self.ctx, s, r) - } - fn vec_to_single(&self, fb: &Entry, v: Vec) -> ProjectResult { - exprv_to_single(fb, v, self.ctx) - } - fn expect_name(&self, e: &Entry) -> ProjectResult> { expect_name(e, self.ctx) } - fn expect(&self, l: Lexeme, e: &Entry) -> ProjectResult<()> { expect(l, e, self.ctx) } - fn expect_block<'a>(&self, f: Frag<'a>, t: PType) -> ProjectResult> { - expect_block(f, t, self.ctx) - } - fn expect_empty(&self, f: Frag) -> ProjectResult<()> { f.expect_empty(self.ctx) } - fn report_err(&self, e: ProjectErrorObj) { self.ctx.reporter().report(e) } -} diff --git a/orchidlang/src/parse/parsed.rs b/orchidlang/src/parse/parsed.rs deleted file mode 100644 index 8f8b8d2..0000000 --- a/orchidlang/src/parse/parsed.rs +++ /dev/null @@ -1,507 +0,0 @@ -//! Datastructures representing the units of macro execution -//! -//! These structures are produced by the pipeline, processed by the macro -//! executor, and then converted to other usable formats. - -use std::fmt; -use std::hash::Hash; -use std::rc::Rc; - -use hashbrown::HashSet; -use intern_all::Tok; -use itertools::Itertools; -use ordered_float::NotNan; - -use crate::foreign::atom::AtomGenerator; -#[allow(unused)] // for doc -use crate::interpreter::nort; -use crate::location::SourceRange; -use crate::name::{Sym, VName, VPath}; -use crate::parse::numeric::print_nat16; - -/// A [Clause] with associated metadata -#[derive(Clone, Debug)] -pub struct Expr { - /// The actual value - pub value: Clause, - /// Information about the code that produced this value - pub range: SourceRange, -} - -impl Expr { - /// Process all names with the given mapper. - /// Return a new object if anything was processed - #[must_use] - pub fn map_names(&self, pred: &mut impl FnMut(Sym) -> Option) -> Option { - (self.value.map_names(pred)).map(|value| Self { value, range: self.range.clone() }) - } - - /// Visit all expressions in the tree. The search can be exited early by - /// returning [Some] - /// - /// See also [crate::interpreter::nort::Expr::search_all] - pub fn search_all(&self, f: &mut impl FnMut(&Self) -> Option) -> Option { - f(self).or_else(|| self.value.search_all(f)) - } -} - -/// Visit all expression sequences including this sequence itself. -pub fn search_all_slcs(this: &[Expr], f: &mut impl FnMut(&[Expr]) -> Option) -> Option { - f(this).or_else(|| this.iter().find_map(|expr| expr.value.search_all_slcs(f))) -} - -impl Expr { - /// Add the specified prefix to every Name - #[must_use] - pub fn prefix(&self, prefix: &[Tok], except: &impl Fn(Tok) -> bool) -> Self { - Self { value: self.value.prefix(prefix, except), range: self.range.clone() } - } -} - -impl fmt::Display for Expr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.value.fmt(f) } -} - -/// Various types of placeholders -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum PHClass { - /// Matches multiple tokens, lambdas or parenthesized groups - Vec { - /// If true, must match at least one clause - nonzero: bool, - /// Greediness in the allocation of tokens - prio: usize, - }, - /// Matches exactly one token, lambda or parenthesized group - Scalar, - /// Matches exactly one name - Name, -} - -/// Properties of a placeholder that matches unknown tokens in macros -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Placeholder { - /// Identifier to pair placeholders in the pattern and template - pub name: Tok, - /// The nature of the token set matched by this placeholder - pub class: PHClass, -} - -impl fmt::Display for Placeholder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let name = &self.name; - match self.class { - PHClass::Scalar => write!(f, "${name}"), - PHClass::Name => write!(f, "$_{name}"), - PHClass::Vec { nonzero, prio } => { - if nonzero { write!(f, "...") } else { write!(f, "..") }?; - write!(f, "${name}:{prio}") - }, - } - } -} - -/// Different types of brackets supported by Orchid -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum PType { - /// () - Par, - /// [] - Sqr, - /// {} - Curl, -} -impl PType { - /// Left paren character for this paren type - pub fn l(self) -> char { - match self { - PType::Curl => '{', - PType::Par => '(', - PType::Sqr => '[', - } - } - - /// Right paren character for this paren type - pub fn r(self) -> char { - match self { - PType::Curl => '}', - PType::Par => ')', - PType::Sqr => ']', - } - } -} - -/// An S-expression as read from a source file -#[derive(Debug, Clone)] -pub enum Clause { - /// An opaque non-callable value, eg. a file handle - Atom(AtomGenerator), - /// A c-style name or an operator, eg. `+`, `i`, `foo::bar` - Name(Sym), - /// A parenthesized expression - /// eg. `(print out "hello")`, `[1, 2, 3]`, `{Some(t) => t}` - S(PType, Rc>), - /// A function expression, eg. `\x. x + 1` - Lambda(Rc>, Rc>), - /// A placeholder for macros, eg. `$name`, `...$body`, `...$lhs:1` - Placeh(Placeholder), -} - -impl Clause { - /// Extract the expressions from an auto, lambda or S - #[must_use] - pub fn body(&self) -> Option>> { - match self { - Self::Lambda(_, body) | Self::S(_, body) => Some(body.clone()), - _ => None, - } - } - - /// Convert with identical meaning - #[must_use] - pub fn into_expr(self, range: SourceRange) -> Expr { - if let Self::S(PType::Par, body) = &self { - if let [wrapped] = &body[..] { - return wrapped.clone(); - } - } - Expr { value: self, range } - } - - /// Convert with identical meaning - #[must_use] - pub fn from_exprs(exprs: &[Expr]) -> Option { - match exprs { - [] => None, - [only] => Some(only.value.clone()), - _ => Some(Self::S(PType::Par, Rc::new(exprs.to_vec()))), - } - } - - /// Convert with identical meaning - #[must_use] - pub fn from_exprv(exprv: &Rc>) -> Option { - if exprv.len() < 2 { Self::from_exprs(exprv) } else { Some(Self::S(PType::Par, exprv.clone())) } - } - - /// Collect all names that appear in this expression. - /// NOTICE: this isn't the total set of unbound names, it's mostly useful to - /// make weak statements for optimization. - #[must_use] - pub fn collect_names(&self) -> HashSet { - if let Self::Name(n) = self { - return HashSet::from([n.clone()]); - } - let mut glossary = HashSet::new(); - let result = self.search_all(&mut |e| { - if let Clause::Name(n) = &e.value { - glossary.insert(n.clone()); - } - None::<()> - }); - assert!(result.is_none(), "Callback never returns Some"); - glossary - } - - /// Process all names with the given mapper. - /// Return a new object if anything was processed - #[must_use] - pub fn map_names(&self, pred: &mut impl FnMut(Sym) -> Option) -> Option { - match self { - Clause::Atom(_) | Clause::Placeh(_) => None, - Clause::Name(name) => pred(name.clone()).map(Clause::Name), - Clause::S(c, body) => { - let mut any_some = false; - let new_body = body - .iter() - .map(|e| { - let val = e.map_names(pred); - any_some |= val.is_some(); - val.unwrap_or_else(|| e.clone()) - }) - .collect(); - if any_some { Some(Clause::S(*c, Rc::new(new_body))) } else { None } - }, - Clause::Lambda(arg, body) => { - let mut any_some = false; - let new_arg = (arg.iter()) - .map(|e| { - let val = e.map_names(pred); - any_some |= val.is_some(); - val.unwrap_or_else(|| e.clone()) - }) - .collect(); - let new_body = (body.iter()) - .map(|e| { - let val = e.map_names(pred); - any_some |= val.is_some(); - val.unwrap_or_else(|| e.clone()) - }) - .collect(); - if any_some { Some(Clause::Lambda(Rc::new(new_arg), Rc::new(new_body))) } else { None } - }, - } - } - - /// Pair of [Expr::search_all] - pub fn search_all(&self, f: &mut impl FnMut(&Expr) -> Option) -> Option { - match self { - Clause::Lambda(arg, body) => - arg.iter().chain(body.iter()).find_map(|expr| expr.search_all(f)), - Clause::Name(_) | Clause::Atom(_) | Clause::Placeh(_) => None, - Clause::S(_, body) => body.iter().find_map(|expr| expr.search_all(f)), - } - } - - /// Visit all expression sequences. Most useful when looking for some pattern - pub fn search_all_slcs(&self, f: &mut impl FnMut(&[Expr]) -> Option) -> Option { - match self { - Clause::Lambda(arg, body) => search_all_slcs(arg, f).or_else(|| search_all_slcs(body, f)), - Clause::Name(_) | Clause::Atom(_) | Clause::Placeh(_) => None, - Clause::S(_, body) => search_all_slcs(body, f), - } - } - - /// Generate a parenthesized expression sequence - pub fn s(delimiter: char, body: impl IntoIterator, range: SourceRange) -> Self { - let ptype = match delimiter { - '(' => PType::Par, - '[' => PType::Sqr, - '{' => PType::Curl, - _ => panic!("not an opening paren"), - }; - let body = body.into_iter().map(|it| it.into_expr(range.clone())).collect(); - Self::S(ptype, Rc::new(body)) - } -} - -impl Clause { - /// Add the specified prefix to every Name - #[must_use] - pub fn prefix(&self, prefix: &[Tok], except: &impl Fn(Tok) -> bool) -> Self { - self - .map_names(&mut |name| match except(name[0].clone()) { - true => None, - false => { - let prefixed = prefix.iter().cloned().chain(name.iter()).collect::>(); - Some(Sym::from_tok(name.tok().interner().i(&prefixed)).unwrap()) - }, - }) - .unwrap_or_else(|| self.clone()) - } -} - -impl fmt::Display for Clause { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Atom(a) => write!(f, "{a:?}"), - Self::Name(name) => write!(f, "{}", name), - Self::S(t, items) => { - let body = items.iter().join(" "); - write!(f, "{}{body}{}", t.l(), t.r()) - }, - Self::Lambda(arg, body) => { - let args = arg.iter().join(" "); - let bodys = body.iter().join(" "); - write!(f, "\\{args}.{bodys}") - }, - Self::Placeh(ph) => ph.fmt(f), - } - } -} - -/// A substitution rule as loaded from source -#[derive(Debug, Clone)] -pub struct Rule { - /// Expressions on the left side of the arrow - pub pattern: Vec, - /// Priority number written inside the arrow - pub prio: NotNan, - /// Expressions on the right side of the arrow - pub template: Vec, -} - -impl fmt::Display for Rule { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "rule {} ={}=> {}", - self.pattern.iter().join(" "), - print_nat16(self.prio), - self.template.iter().join(" ") - ) - } -} - -/// A named constant -#[derive(Debug, Clone)] -pub struct Constant { - /// Used to reference the constant - pub name: Tok, - /// The constant value inserted where the name is found - pub value: Expr, -} - -impl fmt::Display for Constant { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "const {} := {}", *self.name, self.value) - } -} - -/// An import pointing at another module, either specifying the symbol to be -/// imported or importing all available symbols with a globstar (*) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Import { - /// Import path, a sequence of module names. Can either start with - /// - /// - `self` to reference the current module - /// - any number of `super` to reference the parent module of the implied - /// `self` - /// - a root name - pub path: VPath, - /// If name is None, this is a wildcard import - pub name: Option>, - /// Location of the final name segment, which uniquely identifies this name - pub range: SourceRange, -} -impl Import { - /// Constructor - pub fn new( - path: impl IntoIterator>, - name: Option>, - range: SourceRange, - ) -> Self { - let path = VPath(path.into_iter().collect()); - assert!(name.is_some() || !path.0.is_empty(), "import * not allowed"); - Self { range, name, path } - } - - /// Get the preload target space for this import - the prefix below - /// which all files should be included in the compilation - /// - /// Returns the path if this is a glob import, or the path plus the - /// name if this is a specific import - #[must_use] - pub fn nonglob_path(&self) -> VName { - VName::new(self.path.0.iter().chain(&self.name).cloned()) - .expect("Everything import (`import *`) not allowed") - } -} - -impl fmt::Display for Import { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.name { - None => write!(f, "{}::*", self.path), - Some(n) => write!(f, "{}::{}", self.path, n), - } - } -} - -/// A namespace block -#[derive(Debug, Clone)] -pub struct ModuleBlock { - /// Name prefixed to all names in the block - pub name: Tok, - /// Prefixed entries - pub body: Vec, -} - -impl fmt::Display for ModuleBlock { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let bodys = self.body.iter().map(|e| e.to_string()).join("\n"); - write!(f, "module {} {{\n{}\n}}", self.name, bodys) - } -} - -/// see [Member] -#[derive(Debug, Clone)] -pub enum MemberKind { - /// A substitution rule. Rules apply even when they're not in scope, if the - /// absolute names are present eg. because they're produced by other rules - Rule(Rule), - /// A constant (or function) associated with a name - Constant(Constant), - /// A prefixed set of other entries - Module(ModuleBlock), -} -impl MemberKind { - /// Convert to [SourceLine] - pub fn into_line(self, exported: bool, range: SourceRange) -> SourceLine { - SourceLineKind::Member(Member { exported, kind: self }).wrap(range) - } -} - -impl fmt::Display for MemberKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Constant(c) => c.fmt(f), - Self::Module(m) => m.fmt(f), - Self::Rule(r) => r.fmt(f), - } - } -} - -/// Things that may be prefixed with an export -/// see [MemberKind] -#[derive(Debug, Clone)] -pub struct Member { - /// Various members - pub kind: MemberKind, - /// Whether this member is exported or not - pub exported: bool, -} - -impl fmt::Display for Member { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self { exported: true, kind } => write!(f, "export {kind}"), - Self { exported: false, kind } => write!(f, "{kind}"), - } - } -} - -/// See [SourceLine] -#[derive(Debug, Clone)] -pub enum SourceLineKind { - /// Imports one or all names in a module - Import(Vec), - /// Comments are kept here in case dev tooling wants to parse documentation - Comment(String), - /// An element with visibility information - Member(Member), - /// A list of tokens exported explicitly. This can also create new exported - /// tokens that the local module doesn't actually define a role for - Export(Vec<(Tok, SourceRange)>), -} -impl SourceLineKind { - /// Wrap with no location - pub fn wrap(self, range: SourceRange) -> SourceLine { SourceLine { kind: self, range } } -} - -impl fmt::Display for SourceLineKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Comment(s) => write!(f, "--[{s}]--"), - Self::Export(s) => { - write!(f, "export ::({})", s.iter().map(|t| &**t.0).join(", ")) - }, - Self::Member(member) => write!(f, "{member}"), - Self::Import(i) => { - write!(f, "import ({})", i.iter().map(|i| i.to_string()).join(", ")) - }, - } - } -} - -/// Anything the parser might encounter in a file. See [SourceLineKind] -#[derive(Debug, Clone)] -pub struct SourceLine { - /// What we encountered - pub kind: SourceLineKind, - /// Where we encountered it. - pub range: SourceRange, -} - -impl fmt::Display for SourceLine { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.kind.fmt(f) } -} diff --git a/orchidlang/src/parse/sourcefile.rs b/orchidlang/src/parse/sourcefile.rs deleted file mode 100644 index 546f70b..0000000 --- a/orchidlang/src/parse/sourcefile.rs +++ /dev/null @@ -1,313 +0,0 @@ -//! Internal states of the parser. - -use std::iter; -use std::rc::Rc; - -use intern_all::i; -use itertools::Itertools; - -use super::context::ParseCtx; -use super::errors::{ - expect, expect_block, expect_name, BadTokenInRegion, ExpectedSingleName, GlobExport, LeadingNS, - MisalignedParen, NamespacedExport, ParseErrorKind, ReservedToken, UnexpectedEOL, -}; -use super::frag::Frag; -use super::lexer::{Entry, Lexeme}; -use super::multiname::parse_multiname; -use super::parse_plugin::ParsePlugReqImpl; -use crate::error::ProjectResult; -use crate::name::VName; -use crate::parse::parsed::{ - Clause, Constant, Expr, Import, Member, MemberKind, ModuleBlock, PType, Rule, SourceLine, - SourceLineKind, -}; -use crate::sym; - -/// Split the fragment at each line break outside parentheses -pub fn split_lines<'a>( - module: Frag<'a>, - ctx: &'a (impl ParseCtx + ?Sized), -) -> impl Iterator> { - let mut source = module.data.iter().enumerate(); - let mut fallback = module.fallback; - let mut last_slice = 0; - let mut finished = false; - iter::from_fn(move || { - let mut paren_count = 0; - for (i, Entry { lexeme, .. }) in source.by_ref() { - match lexeme { - Lexeme::LP(_) => paren_count += 1, - Lexeme::RP(_) => paren_count -= 1, - Lexeme::BR if paren_count == 0 => { - let begin = last_slice; - last_slice = i + 1; - let cur_prev = fallback; - fallback = &module.data[i]; - return Some(Frag::new(cur_prev, &module.data[begin..i])); - }, - _ => (), - } - } - // Include last line even without trailing newline - if !finished { - finished = true; - return Some(Frag::new(fallback, &module.data[last_slice..])); - } - None - }) - .map(Frag::trim) - .map(|s| { - match s.pop(ctx).and_then(|(f, i)| i.pop_back(ctx).map(|(l, i)| (&f.lexeme, i, &l.lexeme))) { - Ok((Lexeme::LP(PType::Par), inner, Lexeme::RP(PType::Par))) => inner.trim(), - _ => s, - } - }) - .filter(|l| !l.data.is_empty()) -} - -/// Parse linebreak-separated entries -pub fn parse_module_body(cursor: Frag<'_>, ctx: &(impl ParseCtx + ?Sized)) -> Vec { - let mut lines = Vec::new(); - for l in split_lines(cursor, ctx) { - let kinds = ctx.reporter().fallback(parse_line(l, ctx), |_| vec![]); - let r = ctx.range_loc(&l.range()); - lines.extend(kinds.into_iter().map(|kind| SourceLine { range: r.clone(), kind })); - } - lines -} - -/// Parse a single, possibly exported entry -pub fn parse_line( - cursor: Frag<'_>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult> { - let req = ParsePlugReqImpl { ctx, frag: cursor }; - for line_parser in ctx.line_parsers() { - if let Some(result) = line_parser.parse(&req) { - return result; - } - } - let head = cursor.get(0, ctx)?; - match &head.lexeme { - Lexeme::Comment(cmt) => cmt.strip_prefix('|').and_then(|c| c.strip_suffix('|')).map_or_else( - || parse_line(cursor.step(ctx)?, ctx), - |cmt| Ok(vec![SourceLineKind::Comment(cmt.to_string())]), - ), - Lexeme::BR => parse_line(cursor.step(ctx)?, ctx), - Lexeme::Name(n) if **n == "export" => - parse_export_line(cursor.step(ctx)?, ctx).map(|k| vec![k]), - Lexeme::Name(n) if ["const", "macro", "module"].contains(&n.as_str()) => { - let member = Member { exported: false, kind: parse_member(cursor, ctx)? }; - Ok(vec![SourceLineKind::Member(member)]) - }, - Lexeme::Name(n) if **n == "import" => { - let (imports, cont) = parse_multiname(cursor.step(ctx)?, ctx)?; - cont.expect_empty(ctx)?; - Ok(vec![SourceLineKind::Import(imports)]) - }, - lexeme => { - let lexeme = lexeme.clone(); - Err(BadTokenInRegion { lexeme, region: "start of line" }.pack(ctx.range_loc(&head.range))) - }, - } -} - -fn parse_export_line( - cursor: Frag<'_>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult { - let cursor = cursor.trim(); - let head = cursor.get(0, ctx)?; - match &head.lexeme { - Lexeme::NS => { - let (names, cont) = parse_multiname(cursor.step(ctx)?, ctx)?; - cont.expect_empty(ctx)?; - let names = (names.into_iter()) - .map(|Import { name, path, range }| match name { - Some(n) if path.is_empty() => Ok((n, range)), - Some(_) => Err(NamespacedExport.pack(range)), - None => Err(GlobExport.pack(range)), - }) - .collect::, _>>()?; - Ok(SourceLineKind::Export(names)) - }, - Lexeme::Name(n) if ["const", "macro", "module"].contains(&n.as_str()) => - Ok(SourceLineKind::Member(Member { kind: parse_member(cursor, ctx)?, exported: true })), - lexeme => { - let lexeme = lexeme.clone(); - let err = BadTokenInRegion { lexeme, region: "exported line" }; - Err(err.pack(ctx.range_loc(&head.range))) - }, - } -} - -fn parse_member(cursor: Frag<'_>, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult { - let (typemark, cursor) = cursor.trim().pop(ctx)?; - match &typemark.lexeme { - Lexeme::Name(n) if **n == "const" => { - let constant = parse_const(cursor, ctx)?; - Ok(MemberKind::Constant(constant)) - }, - Lexeme::Name(n) if **n == "macro" => { - let rule = parse_rule(cursor, ctx)?; - Ok(MemberKind::Rule(rule)) - }, - Lexeme::Name(n) if **n == "module" => { - let module = parse_module(cursor, ctx)?; - Ok(MemberKind::Module(module)) - }, - lexeme => { - let lexeme = lexeme.clone(); - let err = BadTokenInRegion { lexeme, region: "member type" }; - Err(err.pack(ctx.range_loc(&typemark.range))) - }, - } -} - -/// Parse a macro rule -pub fn parse_rule(cursor: Frag<'_>, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult { - let (pattern, prio, template) = cursor.find_map("arrow", ctx, |a| match a { - Lexeme::Arrow(p) => Some(*p), - _ => None, - })?; - let (pattern, _) = parse_exprv(pattern, None, ctx)?; - let (template, _) = parse_exprv(template, None, ctx)?; - Ok(Rule { pattern, prio, template }) -} - -/// Parse a constant declaration -pub fn parse_const(cursor: Frag<'_>, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult { - let (name_ent, cursor) = cursor.trim().pop(ctx)?; - let name = expect_name(name_ent, ctx)?; - let (walrus_ent, cursor) = cursor.trim().pop(ctx)?; - expect(Lexeme::Walrus, walrus_ent, ctx)?; - let value = ctx.reporter().fallback( - parse_exprv(cursor, None, ctx).and_then(|(body, _)| exprv_to_single(walrus_ent, body, ctx)), - |_| Clause::Name(sym!(__syntax_error__)).into_expr(ctx.range_loc(&cursor.range())), - ); - Ok(Constant { name, value }) -} - -/// Parse a namespaced name. TODO: use this for modules -pub fn parse_nsname<'a>( - cursor: Frag<'a>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult<(VName, Frag<'a>)> { - let (name, tail) = parse_multiname(cursor, ctx)?; - match name.into_iter().exactly_one() { - Ok(Import { name: Some(name), path, .. }) => - Ok((VName::new([name]).unwrap().prefix(path), tail)), - Err(_) | Ok(Import { name: None, .. }) => { - let range = cursor.data[0].range.start..tail.data[0].range.end; - Err(ExpectedSingleName.pack(ctx.range_loc(&range))) - }, - } -} - -/// Parse a submodule declaration -pub fn parse_module( - cursor: Frag<'_>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult { - let (name_ent, cursor) = cursor.trim().pop(ctx)?; - let name = expect_name(name_ent, ctx)?; - let body = expect_block(cursor, PType::Par, ctx)?; - Ok(ModuleBlock { name, body: parse_module_body(body, ctx) }) -} - -/// Parse a sequence of expressions -pub fn parse_exprv<'a>( - mut cursor: Frag<'a>, - paren: Option, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult<(Vec, Frag<'a>)> { - let mut output = Vec::new(); - cursor = cursor.trim(); - while let Ok(current) = cursor.get(0, ctx) { - match ¤t.lexeme { - Lexeme::BR | Lexeme::Comment(_) => unreachable!("Fillers skipped"), - Lexeme::At | Lexeme::Type => { - let err = ReservedToken(current.lexeme.clone()); - return Err(err.pack(ctx.range_loc(¤t.range))); - }, - Lexeme::Atom(a) => { - let value = Clause::Atom(a.clone()); - output.push(Expr { value, range: ctx.range_loc(¤t.range) }); - cursor = cursor.step(ctx)?; - }, - Lexeme::Placeh(ph) => { - output - .push(Expr { value: Clause::Placeh(ph.clone()), range: ctx.range_loc(¤t.range) }); - cursor = cursor.step(ctx)?; - }, - Lexeme::Name(n) => { - let mut range = ctx.range_loc(¤t.range); - let mut fullname = VName::new([n.clone()]).unwrap(); - while cursor.get(1, ctx).is_ok_and(|e| e.lexeme.strict_eq(&Lexeme::NS)) { - let next_seg = cursor.get(2, ctx)?; - range.range.end = next_seg.range.end; - fullname = fullname.suffix([expect_name(next_seg, ctx)?]); - cursor = cursor.step(ctx)?.step(ctx)?; - } - let clause = Clause::Name(fullname.to_sym()); - output.push(Expr { value: clause, range }); - cursor = cursor.step(ctx)?; - }, - Lexeme::NS => return Err(LeadingNS.pack(ctx.range_loc(¤t.range))), - Lexeme::RP(c) => match paren { - Some(exp_c) if exp_c == *c => return Ok((output, cursor.step(ctx)?)), - _ => { - let err = MisalignedParen(current.lexeme.clone()); - return Err(err.pack(ctx.range_loc(¤t.range))); - }, - }, - Lexeme::LP(c) => { - let (result, leftover) = parse_exprv(cursor.step(ctx)?, Some(*c), ctx)?; - let range = current.range.start..leftover.fallback.range.end; - let value = Clause::S(*c, Rc::new(result)); - output.push(Expr { value, range: ctx.range_loc(&range) }); - cursor = leftover; - }, - Lexeme::BS => { - let dot = i!(str: "."); - let (arg, body) = - (cursor.step(ctx))?.find("A '.'", ctx, |l| l.strict_eq(&Lexeme::Name(dot.clone())))?; - let (arg, _) = parse_exprv(arg, None, ctx)?; - let (body, leftover) = parse_exprv(body, paren, ctx)?; - output.push(Expr { - range: ctx.range_loc(&cursor.range()), - value: Clause::Lambda(Rc::new(arg), Rc::new(body)), - }); - return Ok((output, leftover)); - }, - lexeme => { - let lexeme = lexeme.clone(); - let err = BadTokenInRegion { lexeme, region: "expression" }; - return Err(err.pack(ctx.range_loc(¤t.range))); - }, - } - cursor = cursor.trim(); - } - Ok((output, Frag::new(cursor.fallback, &[]))) -} - -/// Wrap an expression list in parentheses if necessary -pub fn exprv_to_single( - fallback: &Entry, - v: Vec, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult { - match v.len() { - 0 => { - let err = UnexpectedEOL(fallback.lexeme.clone()); - Err(err.pack(ctx.range_loc(&fallback.range))) - }, - 1 => Ok(v.into_iter().exactly_one().unwrap()), - _ => { - let f_range = &v.first().unwrap().range; - let l_range = &v.last().unwrap().range; - let range = f_range.map_range(|r| r.start..l_range.end()); - Ok(Expr { range, value: Clause::S(PType::Par, Rc::new(v)) }) - }, - } -} diff --git a/orchidlang/src/pipeline/dealias/mod.rs b/orchidlang/src/pipeline/dealias/mod.rs deleted file mode 100644 index d4c99f8..0000000 --- a/orchidlang/src/pipeline/dealias/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod resolve_aliases; -mod walk_with_links; diff --git a/orchidlang/src/pipeline/dealias/resolve_aliases.rs b/orchidlang/src/pipeline/dealias/resolve_aliases.rs deleted file mode 100644 index 604c4c4..0000000 --- a/orchidlang/src/pipeline/dealias/resolve_aliases.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::iter; - -use hashbrown::HashMap; -use intern_all::Tok; -use itertools::Itertools; - -use super::walk_with_links::walk_with_links; -use crate::error::{ErrorPosition, ErrorSansOrigin, ProjectError, Reporter}; -use crate::location::SourceRange; -use crate::name::{Sym, VPath}; -use crate::parse::parsed::Expr; -use crate::pipeline::project::{ - ItemKind, ProjItem, ProjRule, ProjXEnt, ProjXMod, ProjectMod, SourceModule, -}; -use crate::tree::{ModEntry, ModMember, Module}; -use crate::utils::pure_seq::with_pushed; - -#[derive(Clone)] -struct NotFound { - last_stop: VPath, - bad_step: Tok, -} -impl ErrorSansOrigin for NotFound { - const DESCRIPTION: &'static str = "A path pointed out of the tree"; - fn message(&self) -> String { format!("{} doesn't contain {}", self.last_stop, self.bad_step) } -} - -struct NameErrors(Vec<(NotFound, SourceRange)>); -impl ProjectError for NameErrors { - const DESCRIPTION: &'static str = "Some symbols were missing"; - fn positions(&self) -> impl IntoIterator { - self.0.iter().map(|(nf, sr)| ErrorPosition { origin: sr.origin(), message: Some(nf.message()) }) - } -} - -fn resolve_name( - name: Sym, - root: &ProjectMod, - path: &[Tok], - env: &Module, -) -> Result { - let full_name = path.iter().cloned().chain(&name[..]).collect_vec(); - match walk_with_links(root, full_name.clone().into_iter()) { - Ok(rep) => Ok(rep.abs_path.to_sym()), - Err(mut e) => match e.tail.next() { - // If it got stuck on the very last step, allow it through for - // now in case it is a binding. If the name doesn't get bound, by - // macros it will be raised at the postmacro check. - None => Ok(e.consumed_path().to_sym()), - Some(step) => { - // If there's more, rebuild the last full path after redirects and - // try to resolve it on the env tree. The env tree doesn't contain - // redirects so a plain tree walk is enough. - let fallback_path = (e.abs_path.iter()) - .chain(iter::once(&e.name)) - .cloned() - .chain(iter::once(step)) - .chain(e.tail) - .collect_vec(); - let valid_in_env = env.walk1_ref(&[], &fallback_path, |_| true).is_ok(); - match valid_in_env { - false => Err(NotFound { last_stop: VPath(e.abs_path), bad_step: e.name }), - true => Ok(Sym::new(fallback_path).expect("Not empty by construction")), - } - }, - }, - } -} - -fn process_expr( - expr: &Expr, - root: &ProjectMod, - path: &[Tok], - env: &Module, - reporter: &Reporter, -) -> Expr { - expr - .map_names(&mut |n| { - resolve_name(n, root, path, env) - .inspect_err(|e| reporter.report(e.clone().bundle(&expr.range.origin()))) - .ok() - }) - .unwrap_or_else(|| expr.clone()) -} - -fn resolve_aliases_rec( - root: &ProjectMod, - path: &mut Vec>, - module: &ProjectMod, - env: &Module, - reporter: &Reporter, -) -> ProjectMod { - let module = Module { - x: ProjXMod { - src: module.x.src.as_ref().map(|s| SourceModule { - range: s.range.clone(), - rules: (s.rules.iter()) - .map(|ProjRule { pattern, prio, template, comments }| ProjRule { - pattern: pattern.iter().map(|e| process_expr(e, root, path, env, reporter)).collect(), - template: template.iter().map(|e| process_expr(e, root, path, env, reporter)).collect(), - comments: comments.clone(), - prio: *prio, - }) - .collect(), - }), - }, - entries: (module.entries.iter()) - .map(|(k, v)| { - (k.clone(), ModEntry { - x: ProjXEnt { - exported: v.x.exported, - comments: v.x.comments.clone(), - locations: v.x.locations.clone(), - }, - member: match &v.member { - ModMember::Sub(module) => { - let (_, m) = with_pushed(path, k.clone(), |p| { - resolve_aliases_rec(root, p, module, env, reporter) - }); - ModMember::Sub(m) - }, - ModMember::Item(item) => ModMember::Item(ProjItem { - kind: match &item.kind { - ItemKind::Const(v) => { - let v = process_expr(v, root, path, env, reporter); - ItemKind::Const(v) - }, - // this is an absolute path so we set the path to empty - ItemKind::Alias(n) => match resolve_name(n.clone(), root, &[], env) { - Ok(n) => ItemKind::Alias(n), - Err(e) => { - let location = v.x.locations.first().expect("Aliases always have a location"); - reporter.report(e.bundle(&location.origin)); - ItemKind::Alias(n.clone()) - }, - }, - _ => item.kind.clone(), - }, - }), - }, - }) - }) - .collect::>(), - }; - module -} - -pub fn resolve_aliases( - project: ProjectMod, - env: &Module, - reporter: &Reporter, -) -> ProjectMod { - resolve_aliases_rec(&project, &mut Vec::new(), &project, env, reporter) -} diff --git a/orchidlang/src/pipeline/dealias/walk_with_links.rs b/orchidlang/src/pipeline/dealias/walk_with_links.rs deleted file mode 100644 index 16bb878..0000000 --- a/orchidlang/src/pipeline/dealias/walk_with_links.rs +++ /dev/null @@ -1,110 +0,0 @@ -use intern_all::{i, Tok}; - -use crate::name::{VName, VPath}; -use crate::pipeline::project::{ItemKind, ProjectMemberRef, ProjectMod}; -use crate::tree::ModMember; -use crate::utils::boxed_iter::{box_chain, BoxedIter}; -use crate::utils::unwrap_or::unwrap_or; - -pub struct WalkReport<'a> { - pub target: ProjectMemberRef<'a>, - pub abs_path: VName, -} - -pub struct LinkWalkError<'a> { - /// The last known valid path - pub abs_path: Vec>, - /// The name that wasn't found - pub name: Tok, - /// Leftover steps - pub tail: BoxedIter<'a, Tok>, -} -impl<'a> LinkWalkError<'a> { - pub fn consumed_path(self) -> VName { VPath::new(self.abs_path).name_with_prefix(self.name) } -} - -fn walk_with_links_rec<'a: 'b, 'b>( - mut abs_path: Vec>, - root: &'a ProjectMod, - cur: &'a ProjectMod, - prev_tgt: ProjectMemberRef<'a>, - mut path: impl Iterator> + 'b, -) -> Result, LinkWalkError<'b>> { - let name = match path.next() { - Some(sup) if sup == i!(str: "super") => { - if abs_path.pop().is_none() { - return Err(LinkWalkError { abs_path, name: sup, tail: Box::new(path) }); - } - let path_acc = Vec::with_capacity(abs_path.len()); - let new_path = box_chain!(abs_path.into_iter(), path); - let tgt = ProjectMemberRef::Mod(root); - return walk_with_links_rec(path_acc, root, root, tgt, new_path); - }, - Some(sup) if sup == i!(str: "self") => { - let tgt = ProjectMemberRef::Mod(cur); - return walk_with_links_rec(abs_path, root, cur, tgt, path); - }, - Some(name) => name, - // ends on this module - None => { - let abs_path = VName::new(abs_path).expect("Aliases are never empty"); - return Ok(WalkReport { target: prev_tgt, abs_path }); - }, - }; - let entry = unwrap_or! {cur.entries.get(&name); { - // leads into a missing branch - return Err(LinkWalkError{ abs_path, name, tail: Box::new(path) }) - }}; - match &entry.member { - ModMember::Sub(m) => { - // leads into submodule - abs_path.push(name); - let tgt = ProjectMemberRef::Mod(m); - walk_with_links_rec(abs_path, root, m, tgt, path) - }, - ModMember::Item(item) => match &item.kind { - ItemKind::Alias(alias) => { - // leads into alias (reset acc, cur, cur_entry) - let path_acc = Vec::with_capacity(alias.len()); - let new_path = box_chain!(alias.iter(), path); - let tgt = ProjectMemberRef::Mod(root); - walk_with_links_rec(path_acc, root, root, tgt, new_path) - }, - ItemKind::Const(_) | ItemKind::None => { - abs_path.push(name); - match path.next() { - Some(name) => { - // leads into leaf - let tail = Box::new(path); - Err(LinkWalkError { abs_path, name, tail }) - }, - None => { - // ends on leaf - let target = ProjectMemberRef::Item(item); - let abs_path = VName::new(abs_path).expect("pushed just above"); - Ok(WalkReport { target, abs_path }) - }, - } - }, - }, - } -} - -/// Execute a walk down the tree, following aliases. -/// If the path ends on an alias, that alias is also resolved. -/// If the path leads out of the tree, the shortest failing path is returned -pub fn walk_with_links<'a: 'b, 'b>( - root: &'a ProjectMod, - path: impl Iterator> + 'b, -) -> Result, LinkWalkError<'b>> { - let path_acc = path.size_hint().1.map_or_else(Vec::new, Vec::with_capacity); - let tgt = ProjectMemberRef::Mod(root); - let mut result = walk_with_links_rec(path_acc, root, root, tgt, path); - // cut off excess preallocated space within normal vector growth policy - let abs_path = match &mut result { - Ok(rep) => rep.abs_path.vec_mut(), - Err(err) => &mut err.abs_path, - }; - abs_path.shrink_to(abs_path.len().next_power_of_two()); - result -} diff --git a/orchidlang/src/pipeline/load_project.rs b/orchidlang/src/pipeline/load_project.rs deleted file mode 100644 index c3bd152..0000000 --- a/orchidlang/src/pipeline/load_project.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! Load an Orchid project by starting from one or more entry points and -//! following the imports - -use std::collections::VecDeque; - -use hashbrown::{HashMap, HashSet}; -use intern_all::{sweep_t, Tok}; - -use super::dealias::resolve_aliases::resolve_aliases; -use super::process_source::{process_ns, resolve_globs, GlobImports}; -use super::project::{ItemKind, ProjItem, ProjXEnt, ProjectMod, ProjectTree}; -use crate::error::{ErrorPosition, ProjectError, Reporter}; -use crate::location::{CodeGenInfo, CodeOrigin, SourceCode, SourceRange}; -use crate::name::{NameLike, PathSlice, Sym, VName, VPath}; -use crate::parse::context::ParseCtxImpl; -use crate::parse::facade::parse_file; -use crate::parse::lex_plugin::LexerPlugin; -use crate::parse::parse_plugin::ParseLinePlugin; -use crate::tree::{ModEntry, ModMember, Module}; -use crate::utils::combine::Combine; -use crate::utils::sequence::Sequence; -use crate::virt_fs::{DeclTree, Loaded, VirtFS}; - -// apply layer: -// 1. build trees -// Question: what if a file is not found? -// - raising an error would risk failing on a std module -// - moving on could obscure very simple errors -// can we get rid of layers and show system sources alongside user sources? -// what would break? Can we break it? -// the project moves into a prefix, imports are either super:: or user:: -// custom support for root:: specifier -// virtual file tree is back on -// systems get free reign on their subtree, less jank -// would also solve some weird accidental private member aliasing issues - -/// Split off the longest prefix accepted by the validator -fn split_max_prefix<'a, T, U>( - path: &'a [T], - is_valid: &impl Fn(&[T]) -> Option, -) -> Option<(&'a [T], &'a [T], U)> { - (0..=path.len()) - .rev() - .map(|i| path.split_at(i)) - .find_map(|(file, dir)| Some((file, dir, is_valid(file)?))) -} - -/// Represents a prelude / implicit import requested by a library. -/// A prelude extends any module with a glob import from the target module -/// unless its path begins with exclude. -#[derive(Debug, Clone)] -pub struct Prelude { - /// Path the glob imports will point to - pub target: VName, - /// subtree to exclude (typically the region the prelude collates items from) - pub exclude: VName, - /// Location data attached to the aliases - pub owner: CodeGenInfo, -} - -/// Hooks and extensions to the source loading process -#[derive(Clone)] -pub struct ProjectContext<'a, 'b> { - /// Callbacks from the lexer to support literals of custom datatypes - pub lexer_plugins: Sequence<'a, &'a (dyn LexerPlugin + 'a)>, - /// Callbacks from the parser to support custom module tree elements - pub line_parsers: Sequence<'a, &'a (dyn ParseLinePlugin + 'a)>, - /// Lines prepended to various modules to import "global" values - pub preludes: Sequence<'a, &'a Prelude>, - /// Error aggregator - pub reporter: &'b Reporter, -} -impl<'a, 'b> ProjectContext<'a, 'b> { - /// Derive context for the parser - pub fn parsing<'c>(&'c self, code: SourceCode) -> ParseCtxImpl<'a, 'b> { - ParseCtxImpl { - code, - reporter: self.reporter, - lexers: self.lexer_plugins.clone(), - line_parsers: self.line_parsers.clone(), - } - } -} - -/// Load source files from a source tree and parse them starting from the -/// specified targets and following imports. An in-memory environment tree is -/// used to allow imports from modules that are defined by other loading steps -/// and later merged into this source code. -pub fn load_project( - ctx: &ProjectContext<'_, '_>, - targets: impl IntoIterator, - env: &Module, - fs: &DeclTree, -) -> ProjectTree { - let mut queue = VecDeque::<(Sym, CodeOrigin)>::new(); - queue.extend(targets.into_iter()); - queue.extend(ctx.preludes.iter().map(|p| (p.target.to_sym(), CodeOrigin::Gen(p.owner.clone())))); - let mut known_files = HashSet::new(); - let mut tree_acc: ProjectMod = Module::wrap([]); - let mut glob_acc: GlobImports = Module::wrap([]); - while let Some((target, referrer)) = queue.pop_front() { - let path_parts = split_max_prefix(&target, &|p| match fs.read(PathSlice::new(p)) { - Ok(Loaded::Code(c)) => Some(c), - _ => None, - }); - if let Some((file, _, source)) = path_parts { - let path = Sym::new(file.iter().cloned()).expect("loading from a DeclTree"); - if known_files.contains(&path) { - continue; - } - known_files.insert(path.clone()); - let code = SourceCode::new(path, source); - let full_range = SourceRange { range: 0..code.text.len(), code: code.clone() }; - let lines = parse_file(&ctx.parsing(code.clone())); - let report = process_ns(code.path(), lines, full_range, ctx.reporter); - queue.extend((report.ext_refs.into_iter()).map(|(k, v)| (k, CodeOrigin::Source(v)))); - let mut comments = Some(report.comments); - let mut module = report.module; - let mut glob = report.glob_imports; - for i in (0..file.len()).rev() { - // i over valid indices of filename - let key = file[i].clone(); // last segment - let comments = comments.take().into_iter().flatten().collect(); - glob = Module::wrap([(key.clone(), ModEntry::wrap(ModMember::Sub(glob)))]); - module = Module::wrap([(key, ModEntry { - member: ModMember::Sub(module), - x: ProjXEnt { comments, ..Default::default() }, - })]); - } - glob_acc = (glob_acc.combine(glob)).expect("source code loaded for two nested paths"); - tree_acc = (tree_acc.combine(module)).expect("source code loaded for two nested paths"); - } else { - known_files.insert(target.clone()); - // If the path is not within a file, load it as directory - match fs.read(&target) { - Ok(Loaded::Collection(c)) => queue.extend(c.iter().map(|e| { - (VPath::new(target.iter()).name_with_prefix(e.clone()).to_sym(), referrer.clone()) - })), - Ok(Loaded::Code(_)) => unreachable!("Should have split to self and []"), - // Ignore error if the path is walkable in the const tree - Err(_) if env.walk1_ref(&[], &target[..], |_| true).is_ok() => (), - // Otherwise raise error - Err(e) => ctx.reporter.report(e.bundle(&referrer)), - } - } - } - let mut contention = HashMap::new(); - resolve_globs(glob_acc, ctx, &mut tree_acc, env, &mut contention); - let ret = resolve_aliases(tree_acc, env, ctx.reporter); - for ((glob, original), locations) in contention { - let (glob_val, _) = - ret.walk1_ref(&[], &glob[..], |_| true).expect("Should've emerged in dealias"); - let (original_val, _) = - ret.walk1_ref(&[], &original[..], |_| true).expect("Should've emerged in dealias"); - let glob_real = match &glob_val.member { - ModMember::Item(ProjItem { kind: ItemKind::Alias(glob_tgt) }) => glob_tgt, - _ => &glob, - }; - let original_real = match &original_val.member { - ModMember::Item(ProjItem { kind: ItemKind::Alias(orig_tgt) }) => orig_tgt, - _ => &original, - }; - if glob_real != original_real { - let real = original_real.clone(); - let glob_real = glob_real.clone(); - let err = ConflictingGlobs { real, glob_real, original, glob, origins: locations }; - ctx.reporter.report(err.pack()); - } - } - sweep_t::(); - sweep_t::>>(); - ProjectTree(ret) -} - -/// Produced when a stage that deals specifically with code encounters -/// a path that refers to a directory -#[derive(Debug)] -struct UnexpectedDirectory { - /// Path to the offending collection - pub path: VPath, -} -impl ProjectError for UnexpectedDirectory { - const DESCRIPTION: &'static str = "A stage that deals specifically with code \ - encountered a path that refers to a directory"; - fn message(&self) -> String { format!("{} was expected to be a file", self.path) } - fn positions(&self) -> impl IntoIterator { [] } -} - -#[derive(Debug)] -struct ConflictingGlobs { - original: Sym, - real: Sym, - glob: Sym, - glob_real: Sym, - origins: Vec, -} -impl ProjectError for ConflictingGlobs { - const DESCRIPTION: &'static str = "A symbol from a glob import conflicts with an existing name"; - fn message(&self) -> String { - let Self { glob, glob_real, original, real, .. } = self; - format!( - "glob import included {glob} which resolved to {glob_real}. \ - This conflicts with {original} which resolved to {real}" - ) - } - fn positions(&self) -> impl IntoIterator { - (self.origins.iter()).map(|l| ErrorPosition { origin: l.clone(), message: None }) - } -} diff --git a/orchidlang/src/pipeline/mod.rs b/orchidlang/src/pipeline/mod.rs deleted file mode 100644 index 6ef2006..0000000 --- a/orchidlang/src/pipeline/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Loading Orchid projects from source -mod dealias; -pub mod load_project; -mod path; -mod process_source; -pub mod project; diff --git a/orchidlang/src/pipeline/path.rs b/orchidlang/src/pipeline/path.rs deleted file mode 100644 index 1abbcf0..0000000 --- a/orchidlang/src/pipeline/path.rs +++ /dev/null @@ -1,59 +0,0 @@ -use intern_all::{i, Tok}; - -use crate::error::{ErrorSansOrigin, ResultSansOrigin}; -use crate::name::VName; - -/// Turn a relative (import) path into an absolute path. -/// If the import path is empty, the return value is also empty. -/// -/// # Errors -/// -/// if the relative path contains as many or more `super` segments than the -/// length of the absolute path. -pub(super) fn absolute_path(cwd: &[Tok], rel: &[Tok]) -> ResultSansOrigin { - absolute_path_rec(cwd, rel) - .ok_or_else(|| TooManySupers { path: rel.try_into().expect("At least one super") }.pack()) - .and_then(|v| VName::new(v).map_err(|_| ImportAll.pack())) -} - -#[must_use = "this could be None which means that there are too many supers"] -fn absolute_path_rec(mut cwd: &[Tok], mut rel: &[Tok]) -> Option>> { - let mut relative = false; - if rel.first().cloned() == Some(i!(str: "self")) { - relative = true; - rel = rel.split_first().expect("checked above").1; - } else { - while rel.first().cloned() == Some(i!(str: "super")) { - match cwd.split_last() { - Some((_, torso)) => cwd = torso, - None => return None, - }; - rel = rel.split_first().expect("checked above").1; - relative = true; - } - } - match relative { - true => Some(cwd.iter().chain(rel).cloned().collect()), - false => Some(rel.to_vec()), - } -} - -/// Error produced when an import path starts with more `super` segments -/// than the current module's absolute path -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct TooManySupers { - /// The offending import path - pub path: VName, -} -impl ErrorSansOrigin for TooManySupers { - const DESCRIPTION: &'static str = "an import path starts with more \ - `super` segments than the current module's absolute path"; - fn message(&self) -> String { format!("path {} contains too many `super` steps.", self.path) } -} - -/// Error produced for the statement `import *` -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct ImportAll; -impl ErrorSansOrigin for ImportAll { - const DESCRIPTION: &'static str = "`import *` is forbidden"; -} diff --git a/orchidlang/src/pipeline/process_source.rs b/orchidlang/src/pipeline/process_source.rs deleted file mode 100644 index dae5606..0000000 --- a/orchidlang/src/pipeline/process_source.rs +++ /dev/null @@ -1,328 +0,0 @@ -use std::mem; -use std::sync::Arc; - -use hashbrown::HashMap; -use intern_all::Tok; -use itertools::Itertools; -use never::Never; - -use super::load_project::{Prelude, ProjectContext}; -use super::path::absolute_path; -use super::project::{ - ItemKind, ProjItem, ProjRule, ProjXEnt, ProjXMod, ProjectEntry, ProjectMod, SourceModule, -}; -use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj, Reporter}; -use crate::location::{CodeLocation, CodeOrigin, SourceRange}; -use crate::name::{Sym, VName, VPath}; -use crate::parse::parsed::{ - Constant, Member, MemberKind, ModuleBlock, Rule, SourceLine, SourceLineKind, -}; -use crate::tree::{ModEntry, ModMember, Module, WalkError}; -use crate::utils::combine::Combine; -use crate::utils::get_or::get_or_make; -use crate::utils::sequence::Sequence; - -// Problem: import normalization -// -// Imports aren't explicitly present in the tree we're currently producing. -// Named imports can be placed in Aliases, but glob imports should -// not be included in the Project Tree. A separate Glob Import Tree -// should be produced, which preferably utilizes the existing [Module] -// tree. Then a postprocessing step can use the GIT to both look up the exports -// and write them back into &mut PT. - -/// This tree contains the absolute path of glob imports. - -#[derive(Debug, Clone)] -pub(super) struct GlobImpReport { - pub target: VName, - pub location: SourceRange, -} - -#[derive(Debug, Clone, Default)] -pub(super) struct GlobImpXMod(pub Vec); -impl Combine for GlobImpXMod { - type Error = Never; - fn combine(self, other: Self) -> Result { - Ok(GlobImpXMod(self.0.into_iter().chain(other.0).collect())) - } -} - -pub(super) type GlobImports = Module; - -pub(super) struct FileReport { - /// Absolute path of values outside the file - pub ext_refs: HashMap, - pub comments: Vec>, - pub module: ProjectMod, - pub glob_imports: GlobImports, -} - -fn default_entry() -> ProjectEntry { - ProjectEntry { - member: ModMember::Item(ProjItem::default()), - x: ProjXEnt { comments: vec![], locations: vec![], exported: false }, - } -} - -pub(super) fn process_ns( - path: Sym, - lines: Vec, - ns_location: SourceRange, - reporter: &Reporter, -) -> FileReport { - let mut file_comments = Vec::new(); - let mut new_comments = Vec::new(); - let mut entries = HashMap::new(); - let mut external_references = HashMap::new(); - let mut rules = Vec::new(); - let wrap = Module::wrap([]); - let mut glob_imports: GlobImports = wrap; - for SourceLine { kind: line_kind, range } in lines { - match line_kind { - SourceLineKind::Comment(comment) => new_comments.push(Arc::new(comment)), - SourceLineKind::Export(names) => { - let comments = (names.len() == 1).then(|| mem::take(&mut new_comments)); - for (name, name_loc) in names { - let entry = get_or_make(&mut entries, &name, default_entry); - entry.x.locations.push(CodeLocation::new_src(name_loc, path.clone())); - if entry.x.exported { - reporter.report(MultipleExports::new(path.clone(), name.clone(), entry).pack()); - } - entry.x.exported = true; - entry.x.comments.extend(comments.iter().flatten().cloned()); - } - }, - SourceLineKind::Import(imports) => { - file_comments.append(&mut new_comments); - for import in imports { - let nonglob_path = import.nonglob_path(); - let origin = CodeOrigin::Source(range.clone()); - match absolute_path(&path[..], &nonglob_path[..]) { - Err(e) => reporter.report(e.bundle(&origin)), - Ok(abs) => { - if !abs[..].starts_with(&path[..]) { - external_references.insert(abs.to_sym(), import.range.clone()); - } - match import.name { - None => - (glob_imports.x.0).push(GlobImpReport { target: abs, location: import.range }), - Some(name) => { - let entry = get_or_make(&mut entries, &name, default_entry); - entry.x.locations.push(CodeLocation::new_src(import.range, path.clone())); - if let ModMember::Item(ProjItem { kind: old @ ItemKind::None }) = - &mut entry.member - { - *old = ItemKind::Alias(abs.to_sym()) - } else { - reporter.report(MultipleDefinitions::new(path.clone(), name, entry).pack()); - } - }, - } - }, - } - } - }, - SourceLineKind::Member(Member { exported, kind }) => match kind { - MemberKind::Constant(Constant { name, value }) => { - let entry = get_or_make(&mut entries, &name, default_entry); - entry.x.locations.push(CodeLocation::new_src(range, path.clone())); - if let ModMember::Item(ProjItem { kind: old @ ItemKind::None }) = &mut entry.member { - *old = ItemKind::Const(value) - } else { - reporter.report(MultipleDefinitions::new(path.clone(), name.clone(), entry).pack()); - } - entry.x.exported |= exported; - entry.x.comments.append(&mut new_comments); - }, - MemberKind::Rule(Rule { pattern, prio, template }) => { - let prule = ProjRule { pattern, prio, template, comments: new_comments }; - new_comments = Vec::new(); - for name in prule.collect_root_names() { - let entry = get_or_make(&mut entries, &name, default_entry); - entry.x.locations.push(CodeLocation::new_src(range.clone(), path.clone())); - if entry.x.exported && exported { - reporter.report(MultipleExports::new(path.clone(), name.clone(), entry).pack()); - } - entry.x.exported |= exported; - } - rules.push(prule); - }, - MemberKind::Module(ModuleBlock { name, body }) => { - let entry = get_or_make(&mut entries, &name, default_entry); - (entry.x.locations).push(CodeLocation::new_src(range.clone(), path.clone())); - if !matches!(entry.member, ModMember::Item(ProjItem { kind: ItemKind::None })) { - reporter.report(MultipleDefinitions::new(path.clone(), name.clone(), entry).pack()); - } - if entry.x.exported && exported { - reporter.report(MultipleExports::new(path.clone(), name.clone(), entry).pack()); - } - let subpath = path.to_vname().suffix([name.clone()]).to_sym(); - let mut report = process_ns(subpath, body, range, reporter); - entry.x.comments.append(&mut new_comments); - entry.x.comments.extend(report.comments); - entry.x.exported |= exported; - if let ModMember::Sub(module) = &entry.member { - // This is an error state. - report.module.entries.extend(module.entries.clone()); - } - entry.member = ModMember::Sub(report.module); - // record new external references - external_references - .extend(report.ext_refs.into_iter().filter(|(r, _)| !r[..].starts_with(&path[..]))); - // add glob_imports subtree to own tree - glob_imports - .entries - .insert(name, ModEntry { x: (), member: ModMember::Sub(report.glob_imports) }); - }, - }, - } - } - FileReport { - ext_refs: external_references, - comments: file_comments, - glob_imports, - module: Module { - entries, - x: ProjXMod { src: Some(SourceModule { range: ns_location, rules }) }, - }, - } -} - -fn walk_at_path(e: WalkError, root: &ProjectMod, path: &[Tok]) -> ProjectErrorObj { - let submod = - (root.walk_ref(&[], path, |_| true)).expect("Invalid source path in walk error populator"); - let src = submod.x.src.as_ref().expect("Import cannot appear in implied module"); - e.at(&src.range.origin()) -} - -pub fn resolve_globs_rec( - // must exist in project_root - path: VPath, - globtree: GlobImports, - preludes: Sequence<&Prelude>, - project_root: &mut ProjectMod, - env: &Module, - contention: &mut HashMap<(Sym, Sym), Vec>, - reporter: &Reporter, -) { - // All glob imports in this module - let all = - (globtree.x.0.into_iter()).map(|gir| (gir.target, CodeOrigin::Source(gir.location))).chain( - preludes - .iter() - .filter(|&pre| !path.0.starts_with(&pre.exclude[..])) - .map(|Prelude { target, owner, .. }| (target.clone(), CodeOrigin::Gen(owner.clone()))), - ); - if !path[..].is_empty() { - for (target, imp_loc) in all { - let pub_keys = match project_root.inner_walk(&path.0, &target[..], |e| e.x.exported) { - Err(e) => { - reporter.report(walk_at_path(e, project_root, &path.0)); - continue; - }, - Ok((ModEntry { member: ModMember::Item(_), .. }, parent)) => { - use crate::tree::ErrKind::NotModule; - let options = Sequence::new(|| parent.keys(|e| e.x.exported)); - let e = WalkError::last(&target[..], NotModule, options); - reporter.report(walk_at_path(e, project_root, &path.0)); - continue; - }, - // All public keys in this module and, if walkable, the environment. - Ok((ModEntry { member: ModMember::Sub(module), .. }, _)) => - (env.walk_ref(&[], &target[..], |_| true).into_iter()) - .flat_map(|m| m.keys(|_| true)) - .chain(module.keys(|e| e.x.exported)) - .collect_vec(), - }; - // Reference to the module to be modified - let mut_mod = path.0.iter().fold(&mut *project_root, |m, k| { - let entry = m.entries.get_mut(k).expect("this is a source path"); - if let ModMember::Sub(s) = &mut entry.member { s } else { panic!("This is a source path") } - }); - // Walk errors for the environment are suppressed because leaf-node - // conflicts will emerge when merging modules, and walking off the tree - // is valid. - for key in pub_keys { - let entry = get_or_make(&mut mut_mod.entries, &key, default_entry); - entry.x.locations.push(CodeLocation { - origin: imp_loc.clone(), - module: path.clone().into_name().expect("Checked above").to_sym(), - }); - let alias_tgt = target.clone().suffix([key.clone()]).to_sym(); - if let ModMember::Item(ProjItem { kind: kref @ ItemKind::None }) = &mut entry.member { - *kref = ItemKind::Alias(alias_tgt) - } else { - let local_name = path.clone().name_with_prefix(key.clone()).to_sym(); - contention.insert((alias_tgt, local_name), entry.x.origins().collect()); - } - } - } - } - for (key, entry) in globtree.entries { - match entry.member { - ModMember::Item(n) => match n {}, - ModMember::Sub(module) => { - resolve_globs_rec( - // Submodules in globtree must correspond to submodules in project - path.clone().suffix([key]), - module, - preludes.clone(), - project_root, - env, - contention, - reporter, - ) - }, - } - } -} - -/// Resolve the glob tree separately produced by [process_ns] by looking up the -/// keys of the referenced module and creating an [ItemKind::Alias] for each of -/// them. Supports a prelude table which is applied to each module, and an -/// environment whose keys are combined with those from within the [ProjectMod]. -pub fn resolve_globs( - globtree: GlobImports, - ctx: &ProjectContext, - project_root: &mut ProjectMod, - env: &Module, - contentions: &mut HashMap<(Sym, Sym), Vec>, -) { - let preludes = ctx.preludes.clone(); - resolve_globs_rec(VPath(vec![]), globtree, preludes, project_root, env, contentions, ctx.reporter) -} - -struct MultipleExports { - path: Sym, - locations: Vec, -} -impl MultipleExports { - fn new(mpath: Sym, name: Tok, entry: &'_ ProjectEntry) -> Self { - Self { path: mpath.to_vname().suffix([name]).to_sym(), locations: entry.x.origins().collect() } - } -} -impl ProjectError for MultipleExports { - const DESCRIPTION: &'static str = "A symbol was exported in multiple places"; - fn message(&self) -> String { format!("{} exported multiple times", self.path) } - fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).map(|l| ErrorPosition { origin: l.clone(), message: None }) - } -} - -pub(super) struct MultipleDefinitions { - pub(super) path: Sym, - pub(super) locations: Vec, -} -impl MultipleDefinitions { - fn new(mpath: Sym, name: Tok, entry: &'_ ProjectEntry) -> Self { - Self { path: mpath.to_vname().suffix([name]).to_sym(), locations: entry.x.origins().collect() } - } -} -impl ProjectError for MultipleDefinitions { - const DESCRIPTION: &'static str = "Symbol defined twice"; - fn message(&self) -> String { format!("{} refers to multiple conflicting items", self.path) } - fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).map(|l| ErrorPosition { origin: l.clone(), message: None }) - } -} diff --git a/orchidlang/src/pipeline/project.rs b/orchidlang/src/pipeline/project.rs deleted file mode 100644 index 33e1bb0..0000000 --- a/orchidlang/src/pipeline/project.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! Datastructures used to define an Orchid project - -use std::fmt; -use std::sync::Arc; - -use hashbrown::{HashMap, HashSet}; -use intern_all::Tok; -use itertools::Itertools; -use never::Never; -use ordered_float::NotNan; - -use crate::location::{CodeLocation, CodeOrigin, SourceRange}; -use crate::name::{Sym, VPath}; -use crate::parse::numeric::print_nat16; -use crate::parse::parsed::{Clause, Expr}; -use crate::tree::{ModEntry, ModMember, ModMemberRef, Module, TreeTransforms}; -use crate::utils::combine::Combine; - -/// Different elements that can appear in a module other than submodules -#[derive(Debug, Clone)] -pub enum ItemKind { - /// An imported symbol or module. The value is the absolute path of - /// the symbol that should be used instead of this one. - Alias(Sym), - /// This name is only used in macros - None, - /// This name has a value associated with it - Const(Expr), -} - -impl Default for ItemKind { - fn default() -> Self { Self::None } -} - -/// Element in a module -#[derive(Debug, Clone)] -pub struct ProjItem { - /// The nature of the element - pub kind: ItemKind, -} - -impl Default for ProjItem { - fn default() -> Self { Self { kind: ItemKind::None } } -} - -impl fmt::Display for ProjItem { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.kind { - ItemKind::None => write!(f, "keyword"), - ItemKind::Const(c) => write!(f, "constant {c}"), - ItemKind::Alias(alias) => write!(f, "alias to {alias}"), - } - } -} - -impl Combine for ProjItem { - type Error = Never; - fn combine(self, _: Self) -> Result { - unimplemented!("Only implied project modules can be merged, not items") - } -} - -/// A substitution rule as stored in the tree -#[derive(Debug, Clone)] -pub struct ProjRule { - /// Tree fragment in the source code that activates this rule - pub pattern: Vec, - /// Influences the order in which rules are checked - pub prio: NotNan, - /// Tree fragment generated by this rule - pub template: Vec, - /// Comments associated with this rule - pub comments: Vec>, -} - -impl ProjRule { - /// Namespace all tokens in the rule - #[must_use] - pub fn prefix(self, prefix: &[Tok], except: &impl Fn(Tok) -> bool) -> Self { - let Self { comments, prio, mut pattern, mut template } = self; - (pattern.iter_mut()).chain(template.iter_mut()).for_each(|e| *e = e.prefix(prefix, except)); - Self { prio, comments, pattern, template } - } - - /// Return a list of all names that don't contain a namespace separator `::`. - /// These are exported when the rule is exported - #[must_use] - pub fn collect_root_names(&self) -> HashSet> { - let mut names = HashSet::new(); - for e in self.pattern.iter() { - e.search_all(&mut |e| { - if let Clause::Name(ns_name) = &e.value { - names.extend(ns_name[..].iter().exactly_one().ok()) - } - None::<()> - }); - } - names - } -} - -impl fmt::Display for ProjRule { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}rule {} ={}=> {}", - self.comments.iter().map(|s| format!("--[{s}]--\n")).join(""), - self.pattern.iter().join(" "), - print_nat16(self.prio), - self.template.iter().join(" ") - ) - } -} - -/// Information about a module that is defined in a source file -#[derive(Clone, Debug)] -pub struct SourceModule { - /// All rules defined in this module, exported or not - pub rules: Vec, - /// Location of this module. - pub range: SourceRange, -} - -/// Additional data about a loaded module beyond the list of constants and -/// submodules -#[derive(Clone, Debug, Default)] -pub struct ProjXMod { - /// Details only available for a module loaded from a source file - pub src: Option, -} - -impl Combine for ProjXMod { - type Error = Never; - fn combine(self, other: Self) -> Result { - match (self.src, other.src) { - (None, None) => Ok(Self { src: None }), - (..) => panic!("Only implied modules can be merged"), - } - } -} - -/// Information about a module entry -#[derive(Clone, Debug)] -pub struct ProjXEnt { - /// All comments appearing above the item or submodule - pub comments: Vec>, - /// Whether the member is visible to modules other than the parent - pub exported: bool, - /// Location of this item - pub locations: Vec, -} -impl Default for ProjXEnt { - fn default() -> Self { Self { comments: vec![], exported: true, locations: vec![] } } -} -impl ProjXEnt { - /// Implied modules can be merged easily. It's difficult to detect whether - /// a module is implied so we just assert that it doesn't have an associated - /// source location - pub fn is_default(&self) -> bool { self.locations.is_empty() } - /// Take only the syntactic locations from the recorded location list for - /// error reporting - pub fn origins(&self) -> impl Iterator + '_ { - self.locations.iter().map(|l| l.origin.clone()) - } -} -impl Combine for ProjXEnt { - type Error = MergingFiles; - fn combine(self, other: Self) -> Result { - (self.is_default() && other.is_default()).then_some(self).ok_or(MergingFiles) - } -} - -/// Error produced when a module defined in a file has an alternate definition -/// in another file or as a folder. This is explicitly banned because -/// implementing a good version of it would require undue complexity -#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq)] -pub struct MergingFiles; - -impl fmt::Display for ProjXMod { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut dbg = f.debug_struct("ProjectExt"); - match &self.src { - None => dbg.finish_non_exhaustive(), - Some(SourceModule { rules, range }) => - dbg.field("rules", &rules).field("range", &range).finish(), - } - } -} - -/// A child to a [ProjectMod] -pub type ProjectEntry = ModEntry; -/// A node in the tree describing the project -pub type ProjectMod = Module; -/// A reference to an item or module in the project -pub type ProjectMemberRef<'a> = ModMemberRef<'a, ProjItem, ProjXMod, ProjXEnt>; - -fn collect_rules_rec(bag: &mut Vec, module: &ProjectMod) { - bag.extend(module.x.src.iter().flat_map(|s| &s.rules).cloned()); - for item in module.entries.values() { - if let ModMember::Sub(module) = &item.member { - collect_rules_rec(bag, module); - } - } -} - -/// Module corresponding to the root of a project -#[derive(Debug, Clone)] -pub struct ProjectTree(pub ProjectMod); -impl ProjectTree { - /// Collect the complete list of rules to be used by the rule repository - #[must_use] - pub fn all_rules(&self) -> Vec { - let mut rules = Vec::new(); - collect_rules_rec(&mut rules, &self.0); - rules - } - - /// Extract the symbol table - #[must_use] - pub fn all_consts(&self) -> HashMap { - let mut consts = HashMap::new(); - self.0.search_all((), |path, mem, ()| { - if let ModMemberRef::Mod(m) = mem { - for (name, ent) in m.entries.iter() { - if let ModMember::Item(ProjItem { kind: ItemKind::Const(c) }) = &ent.member { - let name = VPath::new(path.unreverse()).name_with_prefix(name.clone()).to_sym(); - let comments = ent.x.comments.clone(); - let range = match ent.x.locations.first() { - Some(CodeLocation { origin: CodeOrigin::Source(sr), .. }) => sr.clone(), - _ => panic!("Constants must have a location and must only have range locations"), - }; - consts.insert(name.clone(), ConstReport { comments, name, range, value: c.clone() }); - } - } - } - }); - consts - } -} - -/// Information about a constant -#[derive(Clone, Debug)] -pub struct ConstReport { - /// Operational comments - pub comments: Vec>, - /// Value assigned to the constant - pub value: Expr, - /// Source location this constant was parsed from - pub range: SourceRange, - /// Name of the constant. - pub name: Sym, -} diff --git a/orchidlang/src/rule/matcher.rs b/orchidlang/src/rule/matcher.rs deleted file mode 100644 index 92473d1..0000000 --- a/orchidlang/src/rule/matcher.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Abstract definition of a rule matcher, so that the implementation can -//! eventually be swapped out for a different one. - -use std::rc::Rc; - -use super::state::State; -use crate::name::Sym; -use crate::parse::parsed::Expr; - -/// The same as [Expr], just extracted for flexibility -pub type RuleExpr = Expr; - -/// Cacheable optimized structures for matching patterns on slices. This is -/// injected to allow experimentation in the matcher implementation. -pub trait Matcher { - /// Build matcher for a pattern - #[must_use] - fn new(pattern: Rc>) -> Self; - /// Apply matcher to a token sequence - #[must_use] - fn apply<'a>(&self, source: &'a [RuleExpr], save_loc: &impl Fn(Sym) -> bool) - -> Option>; -} diff --git a/orchidlang/src/rule/matcher_vectree/any_match.rs b/orchidlang/src/rule/matcher_vectree/any_match.rs deleted file mode 100644 index d3c05f7..0000000 --- a/orchidlang/src/rule/matcher_vectree/any_match.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::scal_match::scalv_match; -use super::shared::AnyMatcher; -use super::vec_match::vec_match; -use crate::name::Sym; -use crate::rule::matcher::RuleExpr; -use crate::rule::state::State; - -#[must_use] -pub fn any_match<'a>( - matcher: &AnyMatcher, - seq: &'a [RuleExpr], - save_loc: &impl Fn(Sym) -> bool, -) -> Option> { - match matcher { - AnyMatcher::Scalar(scalv) => scalv_match(scalv, seq, save_loc), - AnyMatcher::Vec { left, mid, right } => { - if seq.len() < left.len() + right.len() { - return None; - }; - let left_split = left.len(); - let right_split = seq.len() - right.len(); - Some( - scalv_match(left, &seq[..left_split], save_loc)? - .combine(scalv_match(right, &seq[right_split..], save_loc)?) - .combine(vec_match(mid, &seq[left_split..right_split], save_loc)?), - ) - }, - } -} diff --git a/orchidlang/src/rule/matcher_vectree/build.rs b/orchidlang/src/rule/matcher_vectree/build.rs deleted file mode 100644 index ca5c3d7..0000000 --- a/orchidlang/src/rule/matcher_vectree/build.rs +++ /dev/null @@ -1,149 +0,0 @@ -use intern_all::Tok; -use itertools::Itertools; - -use super::shared::{AnyMatcher, ScalMatcher, VecMatcher}; -use crate::parse::parsed::{Clause, PHClass, Placeholder}; -use crate::rule::matcher::RuleExpr; -use crate::rule::vec_attrs::vec_attrs; -use crate::utils::side::Side; - -pub type MaxVecSplit<'a> = (&'a [RuleExpr], (Tok, usize, bool), &'a [RuleExpr]); - -/// Derive the details of the central vectorial and the two sides from a -/// slice of Expr's -#[must_use] -fn split_at_max_vec(pattern: &[RuleExpr]) -> Option { - let rngidx = pattern - .iter() - .position_max_by_key(|expr| vec_attrs(expr).map(|attrs| attrs.1 as i64).unwrap_or(-1))?; - let (left, not_left) = pattern.split_at(rngidx); - let (placeh, right) = - not_left.split_first().expect("The index of the greatest element must be less than the length"); - vec_attrs(placeh).map(|attrs| (left, attrs, right)) -} - -#[must_use] -fn scal_cnt<'a>(iter: impl Iterator) -> usize { - iter.take_while(|expr| vec_attrs(expr).is_none()).count() -} - -#[must_use] -pub fn mk_any(pattern: &[RuleExpr]) -> AnyMatcher { - let left_split = scal_cnt(pattern.iter()); - if pattern.len() <= left_split { - return AnyMatcher::Scalar(mk_scalv(pattern)); - } - let (left, not_left) = pattern.split_at(left_split); - let right_split = not_left.len() - scal_cnt(pattern.iter().rev()); - let (mid, right) = not_left.split_at(right_split); - AnyMatcher::Vec { left: mk_scalv(left), mid: mk_vec(mid), right: mk_scalv(right) } -} - -/// Pattern MUST NOT contain vectorial placeholders -#[must_use] -fn mk_scalv(pattern: &[RuleExpr]) -> Vec { pattern.iter().map(mk_scalar).collect() } - -/// Pattern MUST start and end with a vectorial placeholder -#[must_use] -fn mk_vec(pattern: &[RuleExpr]) -> VecMatcher { - debug_assert!(!pattern.is_empty(), "pattern cannot be empty"); - debug_assert!(pattern.first().map(vec_attrs).is_some(), "pattern must start with a vectorial"); - debug_assert!(pattern.last().map(vec_attrs).is_some(), "pattern must end with a vectorial"); - let (left, (key, _, nonzero), right) = split_at_max_vec(pattern) - .expect("pattern must have vectorial placeholders at least at either end"); - let r_sep_size = scal_cnt(right.iter()); - let (r_sep, r_side) = right.split_at(r_sep_size); - let l_sep_size = scal_cnt(left.iter().rev()); - let (l_side, l_sep) = left.split_at(left.len() - l_sep_size); - let main = VecMatcher::Placeh { key: key.clone(), nonzero }; - match (left, right) { - (&[], &[]) => VecMatcher::Placeh { key, nonzero }, - (&[], _) => VecMatcher::Scan { - direction: Side::Left, - left: Box::new(main), - sep: mk_scalv(r_sep), - right: Box::new(mk_vec(r_side)), - }, - (_, &[]) => VecMatcher::Scan { - direction: Side::Right, - left: Box::new(mk_vec(l_side)), - sep: mk_scalv(l_sep), - right: Box::new(main), - }, - (..) => { - let mut key_order = - l_side.iter().chain(r_side.iter()).filter_map(vec_attrs).collect::>(); - key_order.sort_by_key(|(_, prio, _)| -(*prio as i64)); - VecMatcher::Middle { - left: Box::new(mk_vec(l_side)), - left_sep: mk_scalv(l_sep), - mid: Box::new(main), - right_sep: mk_scalv(r_sep), - right: Box::new(mk_vec(r_side)), - key_order: key_order.into_iter().map(|(n, ..)| n).collect(), - } - }, - } -} - -/// Pattern MUST NOT be a vectorial placeholder -#[must_use] -fn mk_scalar(pattern: &RuleExpr) -> ScalMatcher { - match &pattern.value { - Clause::Atom(a) => ScalMatcher::Atom(a.clone()), - Clause::Name(n) => ScalMatcher::Name(n.clone()), - Clause::Placeh(Placeholder { name, class }) => match class { - PHClass::Vec { .. } => { - panic!("Scalar matcher cannot be built from vector pattern") - }, - PHClass::Scalar | PHClass::Name => - ScalMatcher::Placeh { key: name.clone(), name_only: class == &PHClass::Name }, - }, - Clause::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body))), - Clause::Lambda(arg, body) => ScalMatcher::Lambda(Box::new(mk_any(arg)), Box::new(mk_any(body))), - } -} - -#[cfg(test)] -mod test { - use std::rc::Rc; - - use intern_all::i; - - use super::mk_any; - use crate::location::SourceRange; - use crate::parse::parsed::{Clause, PHClass, PType, Placeholder}; - use crate::sym; - - #[test] - fn test_scan() { - let ex = |c: Clause| c.into_expr(SourceRange::mock()); - let pattern = vec![ - ex(Clause::Placeh(Placeholder { - class: PHClass::Vec { nonzero: false, prio: 0 }, - name: i!(str: "::prefix"), - })), - ex(Clause::Name(sym!(prelude::do))), - ex(Clause::S( - PType::Par, - Rc::new(vec![ - ex(Clause::Placeh(Placeholder { - class: PHClass::Vec { nonzero: false, prio: 0 }, - name: i!(str: "expr"), - })), - ex(Clause::Name(sym!(prelude::;))), - ex(Clause::Placeh(Placeholder { - class: PHClass::Vec { nonzero: false, prio: 1 }, - name: i!(str: "rest"), - })), - ]), - )), - ex(Clause::Placeh(Placeholder { - class: PHClass::Vec { nonzero: false, prio: 0 }, - name: i!(str: "::suffix"), - })), - ]; - let matcher = mk_any(&pattern); - println!("{matcher}"); - } -} diff --git a/orchidlang/src/rule/matcher_vectree/mod.rs b/orchidlang/src/rule/matcher_vectree/mod.rs deleted file mode 100644 index 5f3d73c..0000000 --- a/orchidlang/src/rule/matcher_vectree/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! Optimized form of macro pattern that can be quickly tested against the AST. -//! -//! # Construction -//! -//! convert pattern into hierarchy of plain, scan, middle -//! - plain: accept any sequence or any non-empty sequence -//! - scan: a single scalar pattern moves LTR or RTL, submatchers on either -//! side -//! - middle: two scalar patterns walk over all permutations of matches -//! while getting progressively closer to each other -//! -//! # Application -//! -//! walk over the current matcher's valid options and poll the submatchers -//! for each of them - -mod any_match; -mod build; -mod scal_match; -pub mod shared; -mod vec_match; diff --git a/orchidlang/src/rule/matcher_vectree/scal_match.rs b/orchidlang/src/rule/matcher_vectree/scal_match.rs deleted file mode 100644 index 3c23f9e..0000000 --- a/orchidlang/src/rule/matcher_vectree/scal_match.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::any_match::any_match; -use super::shared::ScalMatcher; -use crate::name::Sym; -use crate::parse::parsed::Clause; -use crate::rule::matcher::RuleExpr; -use crate::rule::state::{State, StateEntry}; - -#[must_use] -pub fn scal_match<'a>( - matcher: &ScalMatcher, - expr: &'a RuleExpr, - save_loc: &impl Fn(Sym) -> bool, -) -> Option> { - match (matcher, &expr.value) { - (ScalMatcher::Atom(a1), Clause::Atom(a2)) if a1.run().0.parser_eq(&*a2.run().0) => - Some(State::default()), - (ScalMatcher::Name(n1), Clause::Name(n2)) if n1 == n2 => Some(match save_loc(n1.clone()) { - true => State::from_name(n1.clone(), expr.range.clone()), - false => State::default(), - }), - (ScalMatcher::Placeh { key, name_only: true }, Clause::Name(n)) => - Some(State::from_ph(key.clone(), StateEntry::Name(n, &expr.range))), - (ScalMatcher::Placeh { key, name_only: false }, _) => - Some(State::from_ph(key.clone(), StateEntry::Scalar(expr))), - (ScalMatcher::S(c1, b_mat), Clause::S(c2, body)) if c1 == c2 => - any_match(b_mat, &body[..], save_loc), - (ScalMatcher::Lambda(arg_mat, b_mat), Clause::Lambda(arg, body)) => - Some(any_match(arg_mat, arg, save_loc)?.combine(any_match(b_mat, body, save_loc)?)), - _ => None, - } -} - -#[must_use] -pub fn scalv_match<'a>( - matchers: &[ScalMatcher], - seq: &'a [RuleExpr], - save_loc: &impl Fn(Sym) -> bool, -) -> Option> { - if seq.len() != matchers.len() { - return None; - } - let mut state = State::default(); - for (matcher, expr) in matchers.iter().zip(seq.iter()) { - state = state.combine(scal_match(matcher, expr, save_loc)?); - } - Some(state) -} diff --git a/orchidlang/src/rule/matcher_vectree/shared.rs b/orchidlang/src/rule/matcher_vectree/shared.rs deleted file mode 100644 index 581f917..0000000 --- a/orchidlang/src/rule/matcher_vectree/shared.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! Datastructures for cached pattern - -use std::fmt; -use std::rc::Rc; - -use intern_all::Tok; -use itertools::Itertools; - -use super::any_match::any_match; -use super::build::mk_any; -use crate::foreign::atom::AtomGenerator; -use crate::name::Sym; -use crate::parse::parsed::PType; -use crate::rule::matcher::{Matcher, RuleExpr}; -use crate::rule::state::State; -use crate::utils::side::Side; - -pub(super) enum ScalMatcher { - Atom(AtomGenerator), - Name(Sym), - S(PType, Box), - Lambda(Box, Box), - Placeh { key: Tok, name_only: bool }, -} - -pub(super) enum VecMatcher { - Placeh { - key: Tok, - nonzero: bool, - }, - Scan { - left: Box, - sep: Vec, - right: Box, - /// The separator traverses the sequence towards this side - direction: Side, - }, - Middle { - /// Matches the left outer region - left: Box, - /// Matches the left separator - left_sep: Vec, - /// Matches the middle - can only ever be a plain placeholder - mid: Box, - /// Matches the right separator - right_sep: Vec, - /// Matches the right outer region - right: Box, - /// Order of significance for sorting equally good projects based on - /// the length of matches on either side. - /// - /// Vectorial keys that appear on either side, in priority order - key_order: Vec>, - }, -} - -pub(super) enum AnyMatcher { - Scalar(Vec), - Vec { left: Vec, mid: VecMatcher, right: Vec }, -} -impl Matcher for AnyMatcher { - fn new(pattern: Rc>) -> Self { mk_any(&pattern) } - - fn apply<'a>( - &self, - source: &'a [RuleExpr], - save_loc: &impl Fn(Sym) -> bool, - ) -> Option> { - any_match(self, source, save_loc) - } -} - -// ################ Display ################ - -impl fmt::Display for ScalMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Atom(a) => write!(f, "{a:?}"), - Self::Placeh { key, name_only } => match name_only { - false => write!(f, "${key}"), - true => write!(f, "$_{key}"), - }, - Self::Name(n) => write!(f, "{n}"), - Self::S(t, body) => write!(f, "{}{body}{}", t.l(), t.r()), - Self::Lambda(arg, body) => write!(f, "\\{arg}.{body}"), - } - } -} - -impl fmt::Display for VecMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Placeh { key, nonzero: true } => write!(f, "...${key}"), - Self::Placeh { key, nonzero: false } => write!(f, "..${key}"), - Self::Scan { left, sep, right, direction } => { - let arrow = if direction == &Side::Left { "<==" } else { "==>" }; - write!(f, "Scan{{{left} {arrow} {} {arrow} {right}}}", sep.iter().join(" ")) - }, - Self::Middle { left, left_sep, mid, right_sep, right, .. } => { - let left_sep_s = left_sep.iter().join(" "); - let right_sep_s = right_sep.iter().join(" "); - write!(f, "Middle{{{left}|{left_sep_s}|{mid}|{right_sep_s}|{right}}}") - }, - } - } -} - -impl fmt::Display for AnyMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Scalar(s) => { - write!(f, "({})", s.iter().join(" ")) - }, - Self::Vec { left, mid, right } => { - let lefts = left.iter().join(" "); - let rights = right.iter().join(" "); - write!(f, "[{lefts}|{mid}|{rights}]") - }, - } - } -} - -// ################ External ################ - -/// A [Matcher] implementation that builds a priority-order tree of the -/// vectorial placeholders and handles the scalars on leaves. -pub struct VectreeMatcher(AnyMatcher); -impl Matcher for VectreeMatcher { - fn new(pattern: Rc>) -> Self { Self(AnyMatcher::new(pattern)) } - - fn apply<'a>( - &self, - source: &'a [RuleExpr], - save_loc: &impl Fn(Sym) -> bool, - ) -> Option> { - self.0.apply(source, save_loc) - } -} -impl fmt::Display for VectreeMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } -} diff --git a/orchidlang/src/rule/matcher_vectree/vec_match.rs b/orchidlang/src/rule/matcher_vectree/vec_match.rs deleted file mode 100644 index 984b664..0000000 --- a/orchidlang/src/rule/matcher_vectree/vec_match.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::cmp::Ordering; - -use itertools::Itertools; - -use super::scal_match::scalv_match; -use super::shared::VecMatcher; -use crate::name::Sym; -use crate::rule::matcher::RuleExpr; -use crate::rule::state::{State, StateEntry}; - -#[must_use] -pub fn vec_match<'a>( - matcher: &VecMatcher, - seq: &'a [RuleExpr], - save_loc: &impl Fn(Sym) -> bool, -) -> Option> { - match matcher { - VecMatcher::Placeh { key, nonzero } => { - if *nonzero && seq.is_empty() { - return None; - } - return Some(State::from_ph(key.clone(), StateEntry::Vec(seq))); - }, - VecMatcher::Scan { left, sep, right, direction } => { - if seq.len() < sep.len() { - return None; - } - for lpos in direction.walk(0..=seq.len() - sep.len()) { - let rpos = lpos + sep.len(); - let state = vec_match(left, &seq[..lpos], save_loc) - .and_then(|s| Some(s.combine(scalv_match(sep, &seq[lpos..rpos], save_loc)?))) - .and_then(|s| Some(s.combine(vec_match(right, &seq[rpos..], save_loc)?))); - if let Some(s) = state { - return Some(s); - } - } - None - }, - // XXX predict heap space usage and allocation count - VecMatcher::Middle { left, left_sep, mid, right_sep, right, key_order } => { - if seq.len() < left_sep.len() + right_sep.len() { - return None; - } - // Valid locations for the left separator - let lposv = seq[..seq.len() - right_sep.len()] - .windows(left_sep.len()) - .enumerate() - .filter_map(|(i, window)| scalv_match(left_sep, window, save_loc).map(|s| (i, s))) - .collect::>(); - // Valid locations for the right separator - let rposv = seq[left_sep.len()..] - .windows(right_sep.len()) - .enumerate() - .filter_map(|(i, window)| scalv_match(right_sep, window, save_loc).map(|s| (i, s))) - .collect::>(); - // Valid combinations of locations for the separators - let mut pos_pairs = lposv - .into_iter() - .cartesian_product(rposv) - .filter(|((lpos, _), (rpos, _))| lpos + left_sep.len() <= *rpos) - .map(|((lpos, lstate), (rpos, rstate))| (lpos, rpos, lstate.combine(rstate))) - .collect::>(); - // In descending order of size - pos_pairs.sort_by_key(|(l, r, _)| -((r - l) as i64)); - let eql_clusters = pos_pairs.into_iter().group_by(|(al, ar, _)| ar - al); - for (_gap_size, cluster) in eql_clusters.into_iter() { - let best_candidate = cluster - .into_iter() - .filter_map(|(lpos, rpos, state)| { - Some( - state - .combine(vec_match(left, &seq[..lpos], save_loc)?) - .combine(vec_match(mid, &seq[lpos + left_sep.len()..rpos], save_loc)?) - .combine(vec_match(right, &seq[rpos + right_sep.len()..], save_loc)?), - ) - }) - .max_by(|a, b| { - for key in key_order { - let alen = a.ph_len(key).expect("key_order references scalar or missing"); - let blen = b.ph_len(key).expect("key_order references scalar or missing"); - match alen.cmp(&blen) { - Ordering::Equal => (), - any => return any, - } - } - Ordering::Equal - }); - if let Some(state) = best_candidate { - return Some(state); - } - } - None - }, - } -} diff --git a/orchidlang/src/rule/mod.rs b/orchidlang/src/rule/mod.rs deleted file mode 100644 index 43db211..0000000 --- a/orchidlang/src/rule/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Substitution rule processing -pub mod matcher; -pub mod matcher_vectree; -mod prepare_rule; -pub mod repository; -pub mod rule_error; -mod state; -mod update_first_seq; -mod vec_attrs; diff --git a/orchidlang/src/rule/prepare_rule.rs b/orchidlang/src/rule/prepare_rule.rs deleted file mode 100644 index 05a3db1..0000000 --- a/orchidlang/src/rule/prepare_rule.rs +++ /dev/null @@ -1,107 +0,0 @@ -use hashbrown::HashMap; -use intern_all::{i, Tok}; -use itertools::Itertools; - -use super::matcher::RuleExpr; -use super::rule_error::RuleError; -use super::vec_attrs::vec_attrs; -use crate::parse::parsed::{Clause, PHClass, Placeholder}; -use crate::pipeline::project::ProjRule; - -/// Ensure that the rule's source begins and ends with a vectorial without -/// changing its meaning -#[must_use] -fn pad(rule: ProjRule) -> ProjRule { - let prefix_name = i!(str: "__gen__orchid__rule__prefix"); - let suffix_name = i!(str: "__gen__orchid__rule__suffix"); - let class: PHClass = PHClass::Vec { nonzero: false, prio: 0 }; - let ProjRule { comments, pattern, prio, template } = rule; - let rule_head = pattern.first().expect("Pattern can never be empty!"); - let rule_tail = pattern.last().unwrap(); - let prefix = vec_attrs(rule_head).is_none().then(|| { - Clause::Placeh(Placeholder { name: prefix_name, class }) - .into_expr(rule_head.range.map_range(|r| r.start..r.start)) - }); - let suffix = vec_attrs(rule_tail).is_none().then(|| { - Clause::Placeh(Placeholder { name: suffix_name, class }) - .into_expr(rule_tail.range.map_range(|r| r.start..r.start)) - }); - let pattern = prefix.iter().cloned().chain(pattern).chain(suffix.clone()).collect(); - let template = prefix.into_iter().chain(template).chain(suffix).collect(); - ProjRule { comments, prio, pattern, template } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum PHType { - Scalar, - Name, - Vec { nonzero: bool }, -} -impl From for PHType { - fn from(value: PHClass) -> Self { - match value { - PHClass::Scalar => Self::Scalar, - PHClass::Vec { nonzero, .. } => Self::Vec { nonzero }, - PHClass::Name => Self::Name, - } - } -} - -fn check_rec_expr( - expr: &RuleExpr, - types: &mut HashMap, PHType>, - in_template: bool, -) -> Result<(), RuleError> { - match &expr.value { - Clause::Name(_) | Clause::Atom(_) => Ok(()), - Clause::Placeh(Placeholder { name, class }) => { - let typ = (*class).into(); - // in a template, the type must be known and identical - // outside template (in pattern) the type must be unknown - if let Some(known) = types.insert(name.clone(), typ) { - if !in_template { - Err(RuleError::Multiple(name.clone())) - } else if known != typ { - Err(RuleError::ArityMismatch(name.clone())) - } else { - Ok(()) - } - } else if in_template { - Err(RuleError::Missing(name.clone())) - } else { - Ok(()) - } - }, - Clause::Lambda(arg, body) => { - check_rec_exprv(arg, types, in_template)?; - check_rec_exprv(body, types, in_template) - }, - Clause::S(_, body) => check_rec_exprv(body, types, in_template), - } -} - -fn check_rec_exprv( - exprv: &[RuleExpr], - types: &mut HashMap, PHType>, - in_template: bool, -) -> Result<(), RuleError> { - for (l, r) in exprv.iter().tuple_windows::<(_, _)>() { - check_rec_expr(l, types, in_template)?; - if !in_template { - // in a pattern vectorials cannot follow each other - if let (Some(ld), Some(rd)) = (vec_attrs(l), vec_attrs(r)) { - return Err(RuleError::VecNeighbors(ld.0, rd.0)); - } - } - } - if let Some(e) = exprv.last() { check_rec_expr(e, types, in_template) } else { Ok(()) } -} - -pub fn prepare_rule(rule: ProjRule) -> Result { - // Dimension check - let mut types = HashMap::new(); - check_rec_exprv(&rule.pattern, &mut types, false)?; - check_rec_exprv(&rule.template, &mut types, true)?; - // Padding - Ok(pad(rule)) -} diff --git a/orchidlang/src/rule/repository.rs b/orchidlang/src/rule/repository.rs deleted file mode 100644 index e5c59d6..0000000 --- a/orchidlang/src/rule/repository.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! Collects, prioritizes and executes rules. - -use std::fmt; -use std::rc::Rc; - -use hashbrown::HashSet; -use itertools::Itertools; -use ordered_float::NotNan; - -use super::matcher::{Matcher, RuleExpr}; -use super::matcher_vectree::shared::VectreeMatcher; -use super::prepare_rule::prepare_rule; -use super::state::apply_exprv; -use super::update_first_seq; -use crate::error::Reporter; -use crate::name::Sym; -use crate::parse::numeric::print_nat16; -use crate::pipeline::project::ProjRule; - -#[derive(Debug)] -pub(super) struct CachedRule { - matcher: M, - pattern: Vec, - pat_glossary: HashSet, - template: Vec, - save_location: HashSet, -} - -impl fmt::Display for CachedRule { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let patterns = self.pattern.iter().join(" "); - write!( - f, - "{patterns} is matched by {} and generates {}", - self.matcher, - self.template.iter().map(|e| e.to_string()).join(" ") - ) - } -} - -/// Substitution rule scheduler -/// -/// Manages a priority queue of rules and offers functions to apply them. The -/// rules are stored in an optimized structure but the repository is generic -/// over the implementation of this optimized form. -/// -/// If you don't know what to put in the generic parameter, use [Repo] -pub struct Repository { - cache: Vec<(CachedRule, NotNan)>, -} -impl Repository { - /// Build a new repository to hold the given set of rules - pub fn new(mut rules: Vec, reporter: &Reporter) -> Self { - rules.sort_by_key(|r| -r.prio); - let cache = rules - .into_iter() - .filter_map(|r| { - let ProjRule { pattern, prio, template, comments: _ } = prepare_rule(r.clone()) - .inspect_err(|e| reporter.report(e.clone().into_project(&r))) - .ok()?; - let mut pat_glossary = HashSet::new(); - pat_glossary.extend(pattern.iter().flat_map(|e| e.value.collect_names().into_iter())); - let mut tpl_glossary = HashSet::new(); - tpl_glossary.extend(template.iter().flat_map(|e| e.value.collect_names().into_iter())); - let save_location = pat_glossary.intersection(&tpl_glossary).cloned().collect(); - let matcher = M::new(Rc::new(pattern.clone())); - let prep = CachedRule { matcher, pattern, template, pat_glossary, save_location }; - Some((prep, prio)) - }) - .collect::>(); - Self { cache } - } - - /// Attempt to run each rule in priority order once - #[must_use] - pub fn step(&self, code: &RuleExpr) -> Option { - let glossary = code.value.collect_names(); - for (rule, _) in self.cache.iter() { - if !rule.pat_glossary.is_subset(&glossary) { - continue; - } - let product = update_first_seq::expr(code, &mut |exprv| { - let save_loc = |n| rule.save_location.contains(&n); - let state = rule.matcher.apply(exprv.as_slice(), &save_loc)?; - let result = apply_exprv(&rule.template, &state); - Some(Rc::new(result)) - }); - if let Some(newcode) = product { - return Some(newcode); - } - } - None - } - - /// Keep running the matching rule with the highest priority until no - /// rules match. WARNING: this function might not terminate - #[must_use] - pub fn pass(&self, code: &RuleExpr) -> Option { - if let Some(mut processed) = self.step(code) { - while let Some(out) = self.step(&processed) { - processed = out - } - Some(processed) - } else { - None - } - } - - /// Attempt to run each rule in priority order `limit` times. Returns - /// the final tree and the number of iterations left to the limit. - #[must_use] - pub fn long_step(&self, code: &RuleExpr, mut limit: usize) -> (RuleExpr, usize) { - if limit == 0 { - return (code.clone(), 0); - } - if let Some(mut processed) = self.step(code) { - limit -= 1; - if limit == 0 { - return (processed, 0); - } - while let Some(out) = self.step(&processed) { - limit -= 1; - if limit == 0 { - return (out, 0); - } - processed = out; - } - (processed, limit) - } else { - (code.clone(), limit) - } - } -} - -impl fmt::Debug for Repository { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for rule in self.cache.iter() { - writeln!(f, "{rule:?}")? - } - Ok(()) - } -} - -impl fmt::Display for Repository { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Repository[")?; - for (rule, p) in self.cache.iter() { - let prio = print_nat16(*p); - let deps = rule.pat_glossary.iter().join(", "); - writeln!(f, " priority: {prio}\tdependencies: [{deps}]")?; - writeln!(f, " {rule}")?; - } - write!(f, "]") - } -} - -/// Repository with the default matcher implementation -pub type Repo = Repository; diff --git a/orchidlang/src/rule/rule_error.rs b/orchidlang/src/rule/rule_error.rs deleted file mode 100644 index 88d6c94..0000000 --- a/orchidlang/src/rule/rule_error.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Error conditions encountered by the rule processor - -use std::fmt; - -use hashbrown::HashSet; -use intern_all::Tok; - -use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj}; -use crate::location::{CodeOrigin, SourceRange}; -use crate::parse::parsed::{search_all_slcs, Clause, PHClass, Placeholder}; -use crate::pipeline::project::ProjRule; - -/// Various reasons why a substitution rule may be invalid -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum RuleError { - /// A key is present in the template but not the pattern - Missing(Tok), - /// A key uses a different arity in the template and in the pattern - ArityMismatch(Tok), - /// Multiple occurences of a placeholder in a pattern - Multiple(Tok), - /// Two vectorial placeholders are next to each other - VecNeighbors(Tok, Tok), -} -impl RuleError { - /// Convert into a unified error trait object shared by all Orchid errors - #[must_use] - pub fn into_project(self, rule: &ProjRule) -> ProjectErrorObj { - match self { - Self::Missing(name) => Missing::new(rule, name).pack(), - Self::Multiple(name) => Multiple::new(rule, name).pack(), - Self::ArityMismatch(name) => ArityMismatch::new(rule, name).pack(), - Self::VecNeighbors(n1, n2) => VecNeighbors::new(rule, n1, n2).pack(), - } - } -} - -impl fmt::Display for RuleError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Missing(key) => write!(f, "Key {key} not in match pattern"), - Self::ArityMismatch(key) => { - write!(f, "Key {key} used inconsistently with and without ellipsis") - }, - Self::Multiple(key) => { - write!(f, "Key {key} appears multiple times in match pattern") - }, - Self::VecNeighbors(left, right) => { - write!(f, "vectorials {left} and {right} are next to each other") - }, - } - } -} - -/// A key is present in the template but not the pattern of a rule -#[derive(Debug)] -struct Missing { - locations: HashSet, - name: Tok, -} -impl Missing { - #[must_use] - pub fn new(rule: &ProjRule, name: Tok) -> Self { - let mut locations = HashSet::new(); - for expr in rule.template.iter() { - expr.search_all(&mut |e| { - if let Clause::Placeh(ph) = &e.value { - if ph.name == name { - locations.insert(e.range.clone()); - } - } - None::<()> - }); - } - Self { locations, name } - } -} -impl ProjectError for Missing { - const DESCRIPTION: &'static str = "A key appears in the template but not the pattern of a rule"; - fn message(&self) -> String { - format!("The key {} appears in the template but not the pattern of this rule", self.name) - } - fn positions(&self) -> impl IntoIterator { - self.locations.iter().map(|range| CodeOrigin::Source(range.clone()).into()) - } -} - -/// A key is present multiple times in the pattern of a rule -#[derive(Debug)] -struct Multiple { - locations: HashSet, - name: Tok, -} -impl Multiple { - #[must_use] - pub fn new(rule: &ProjRule, name: Tok) -> Self { - let mut locations = HashSet::new(); - for expr in rule.template.iter() { - expr.search_all(&mut |e| { - if let Clause::Placeh(ph) = &e.value { - if ph.name == name { - locations.insert(e.range.clone()); - } - } - None::<()> - }); - } - Self { locations, name } - } -} -impl ProjectError for Multiple { - const DESCRIPTION: &'static str = "A key appears multiple times in the pattern of a rule"; - fn message(&self) -> String { - format!("The key {} appears multiple times in this pattern", self.name) - } - fn positions(&self) -> impl IntoIterator { - self.locations.iter().map(|range| CodeOrigin::Source(range.clone()).into()) - } -} - -/// A key is present multiple times in the pattern of a rule -#[derive(Debug)] -struct ArityMismatch { - locations: HashSet<(SourceRange, PHClass)>, - name: Tok, -} -impl ArityMismatch { - #[must_use] - pub fn new(rule: &ProjRule, name: Tok) -> Self { - let mut locations = HashSet::new(); - for expr in rule.template.iter() { - expr.search_all(&mut |e| { - if let Clause::Placeh(ph) = &e.value { - if ph.name == name { - locations.insert((e.range.clone(), ph.class)); - } - } - None::<()> - }); - } - Self { locations, name } - } -} -impl ProjectError for ArityMismatch { - const DESCRIPTION: &'static str = "A key appears with different arities in a rule"; - fn message(&self) -> String { - format!("The key {} appears multiple times with different arities in this rule", self.name) - } - fn positions(&self) -> impl IntoIterator { - self.locations.iter().map(|(origin, class)| ErrorPosition { - origin: CodeOrigin::Source(origin.clone()), - message: Some( - "This instance represents ".to_string() - + match class { - PHClass::Scalar => "one clause", - PHClass::Name => "one name", - PHClass::Vec { nonzero: true, .. } => "one or more clauses", - PHClass::Vec { nonzero: false, .. } => "any number of clauses", - }, - ), - }) - } -} - -/// Two vectorial placeholders appear next to each other -#[derive(Debug)] -struct VecNeighbors { - locations: HashSet, - n1: Tok, - n2: Tok, -} -impl VecNeighbors { - #[must_use] - pub fn new(rule: &ProjRule, n1: Tok, n2: Tok) -> Self { - let mut locations = HashSet::new(); - search_all_slcs(&rule.template[..], &mut |ev| { - for pair in ev.windows(2) { - let (a, b) = (&pair[0], &pair[1]); - let a_vec = matches!(&a.value, Clause::Placeh( - Placeholder{ class: PHClass::Vec { .. }, name } - ) if name == &n1); - let b_vec = matches!(&b.value, Clause::Placeh( - Placeholder{ class: PHClass::Vec { .. }, name } - ) if name == &n2); - if a_vec && b_vec { - locations.insert(a.range.clone()); - locations.insert(b.range.clone()); - } - } - None::<()> - }); - Self { locations, n1, n2 } - } -} -impl ProjectError for VecNeighbors { - const DESCRIPTION: &'static str = "Two vectorial placeholders appear next to each other"; - fn message(&self) -> String { - format!("The keys {} and {} appear next to each other with a vectorial arity", self.n1, self.n2) - } - fn positions(&self) -> impl IntoIterator { - self.locations.iter().map(|range| CodeOrigin::Source(range.clone()).into()) - } -} diff --git a/orchidlang/src/rule/state.rs b/orchidlang/src/rule/state.rs deleted file mode 100644 index aa2c375..0000000 --- a/orchidlang/src/rule/state.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::rc::Rc; - -use hashbrown::HashMap; -use intern_all::Tok; - -use super::matcher::RuleExpr; -use crate::location::SourceRange; -use crate::name::Sym; -use crate::parse::parsed::{Clause, Expr, PHClass, Placeholder}; -use crate::utils::join::join_maps; -use crate::utils::unwrap_or::unwrap_or; - -#[derive(Clone, Copy, Debug)] -pub enum StateEntry<'a> { - Vec(&'a [RuleExpr]), - Scalar(&'a RuleExpr), - Name(&'a Sym, &'a SourceRange), -} -#[derive(Clone)] -pub struct State<'a> { - placeholders: HashMap, StateEntry<'a>>, - name_locations: HashMap>, -} -impl<'a> State<'a> { - pub fn from_ph(key: Tok, entry: StateEntry<'a>) -> Self { - Self { placeholders: HashMap::from([(key, entry)]), name_locations: HashMap::new() } - } - pub fn combine(self, s: Self) -> Self { - Self { - placeholders: self.placeholders.into_iter().chain(s.placeholders).collect(), - name_locations: join_maps(self.name_locations, s.name_locations, |_, l, r| { - l.into_iter().chain(r).collect() - }), - } - } - pub fn ph_len(&self, key: &Tok) -> Option { - match self.placeholders.get(key)? { - StateEntry::Vec(slc) => Some(slc.len()), - _ => None, - } - } - pub fn from_name(name: Sym, location: SourceRange) -> Self { - Self { name_locations: HashMap::from([(name, vec![location])]), placeholders: HashMap::new() } - } -} -impl Default for State<'static> { - fn default() -> Self { Self { name_locations: HashMap::new(), placeholders: HashMap::new() } } -} - -#[must_use] -pub fn apply_exprv(template: &[RuleExpr], state: &State) -> Vec { - template.iter().map(|e| apply_expr(e, state)).flat_map(Vec::into_iter).collect() -} - -#[must_use] -pub fn apply_expr(template: &RuleExpr, state: &State) -> Vec { - let Expr { range, value } = template; - match value { - Clause::Name(n) => match state.name_locations.get(n) { - None => vec![template.clone()], - Some(locs) => vec![Expr { value: value.clone(), range: locs[0].clone() }], - }, - Clause::Atom(_) => vec![template.clone()], - Clause::S(c, body) => vec![Expr { - range: range.clone(), - value: Clause::S(*c, Rc::new(apply_exprv(body.as_slice(), state))), - }], - Clause::Placeh(Placeholder { name, class }) => { - let value = *unwrap_or!(state.placeholders.get(name); - panic!("Placeholder does not have a value in state") - ); - match (class, value) { - (PHClass::Scalar, StateEntry::Scalar(item)) => vec![item.clone()], - (PHClass::Vec { .. }, StateEntry::Vec(chunk)) => chunk.to_vec(), - (PHClass::Name, StateEntry::Name(n, r)) => { - vec![RuleExpr { value: Clause::Name(n.clone()), range: r.clone() }] - }, - _ => panic!("Type mismatch between template and state"), - } - }, - Clause::Lambda(arg, body) => vec![Expr { - range: range.clone(), - value: Clause::Lambda( - Rc::new(apply_exprv(arg, state)), - Rc::new(apply_exprv(&body[..], state)), - ), - }], - } -} diff --git a/orchidlang/src/rule/update_first_seq.rs b/orchidlang/src/rule/update_first_seq.rs deleted file mode 100644 index 312599e..0000000 --- a/orchidlang/src/rule/update_first_seq.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::iter; -use std::rc::Rc; - -use super::matcher::RuleExpr; -use crate::parse::parsed::{Clause, Expr}; - -/// Traverse the tree, calling pred on every sibling list until it returns -/// some vec then replace the sibling list with that vec and return true -/// return false if pred never returned some -#[must_use] -pub fn exprv>) -> Option>>>( - input: Rc>, - pred: &mut F, -) -> Option>> { - if let Some(v) = pred(input.clone()) { - return Some(v); - } - replace_first(input.as_ref(), |ex| expr(ex, pred)).map(|i| Rc::new(i.collect())) -} - -#[must_use] -pub fn expr>) -> Option>>>( - input: &RuleExpr, - pred: &mut F, -) -> Option { - clause(&input.value, pred).map(|value| Expr { value, range: input.range.clone() }) -} - -#[must_use] -pub fn clause>) -> Option>>>( - c: &Clause, - pred: &mut F, -) -> Option { - match c { - Clause::Atom(_) | Clause::Placeh { .. } | Clause::Name { .. } => None, - Clause::Lambda(arg, body) => - if let Some(arg) = exprv(arg.clone(), pred) { - Some(Clause::Lambda(arg, body.clone())) - } else { - exprv(body.clone(), pred).map(|body| Clause::Lambda(arg.clone(), body)) - }, - Clause::S(c, body) => Some(Clause::S(*c, exprv(body.clone(), pred)?)), - } -} - -/// Iterate over a sequence with the first element updated for which the -/// function returns Some(), but only if there is such an element. -pub fn replace_first Option>( - slice: &[T], - mut f: F, -) -> Option + '_> { - for i in 0..slice.len() { - if let Some(new) = f(&slice[i]) { - let subbed_iter = - slice[0..i].iter().cloned().chain(iter::once(new)).chain(slice[i + 1..].iter().cloned()); - return Some(subbed_iter); - } - } - None -} diff --git a/orchidlang/src/rule/vec_attrs.rs b/orchidlang/src/rule/vec_attrs.rs deleted file mode 100644 index 4b38328..0000000 --- a/orchidlang/src/rule/vec_attrs.rs +++ /dev/null @@ -1,15 +0,0 @@ -use intern_all::Tok; - -use super::matcher::RuleExpr; -use crate::parse::parsed::{Clause, PHClass, Placeholder}; - -/// Returns the name, priority and nonzero of the expression if it is -/// a vectorial placeholder -#[must_use] -pub fn vec_attrs(expr: &RuleExpr) -> Option<(Tok, usize, bool)> { - match expr.value.clone() { - Clause::Placeh(Placeholder { class: PHClass::Vec { prio, nonzero }, name }) => - Some((name, prio, nonzero)), - _ => None, - } -} diff --git a/orchidlang/src/tree.rs b/orchidlang/src/tree.rs deleted file mode 100644 index 6a65594..0000000 --- a/orchidlang/src/tree.rs +++ /dev/null @@ -1,625 +0,0 @@ -//! Generic module tree structure -//! -//! Used by various stages of the pipeline with different parameters -use std::fmt; - -use hashbrown::HashMap; -use intern_all::{ev, i, Tok}; -use never::Never; -use substack::Substack; -use trait_set::trait_set; - -use crate::error::{ProjectError, ProjectErrorObj}; -use crate::location::CodeOrigin; -use crate::name::{VName, VPath}; -use crate::utils::boxed_iter::BoxedIter; -use crate::utils::combine::Combine; -use crate::utils::join::try_join_maps; -use crate::utils::sequence::Sequence; - -/// An umbrella trait for operations you can carry out on any part of the tree -/// structure -pub trait TreeTransforms: Sized { - /// Data held at the leaves of the tree - type Item; - /// Data associated with modules - type XMod; - /// Data associated with entries inside modules - type XEnt; - /// Recursive type to enable [TreeTransforms::map_data] to transform the whole - /// tree - type SelfType: TreeTransforms; - - /// Implementation for [TreeTransforms::map_data] - fn map_data_rec( - self, - item: &mut impl FnMut(Substack>, Self::Item) -> T, - module: &mut impl FnMut(Substack>, Self::XMod) -> U, - entry: &mut impl FnMut(Substack>, Self::XEnt) -> V, - path: Substack>, - ) -> Self::SelfType; - - /// Transform all the data in the tree without changing its structure - fn map_data( - self, - mut item: impl FnMut(Substack>, Self::Item) -> T, - mut module: impl FnMut(Substack>, Self::XMod) -> U, - mut entry: impl FnMut(Substack>, Self::XEnt) -> V, - ) -> Self::SelfType { - self.map_data_rec(&mut item, &mut module, &mut entry, Substack::Bottom) - } - - /// Visit all elements in the tree. This is like [TreeTransforms::search] but - /// without the early exit - /// - /// * init - can be used for reduce, otherwise pass `()` - /// * callback - a callback applied on every module. - /// * [`Substack>`] - the walked path - /// * [Module] - the current module - /// * `T` - data for reduce. - fn search_all<'a, T>( - &'a self, - init: T, - mut callback: impl FnMut( - Substack>, - ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, - T, - ) -> T, - ) -> T { - let res = - self.search(init, |stack, member, state| Ok::(callback(stack, member, state))); - res.unwrap_or_else(|e| match e {}) - } - - /// Visit elements in the tree depth first with the provided function - /// - /// * init - can be used for reduce, otherwise pass `()` - /// * callback - a callback applied on every module. Can return [Err] to - /// short-circuit the walk - /// * [`Substack>`] - the walked path - /// * [Module] - the current module - /// * `T` - data for reduce. - fn search<'a, T, E>( - &'a self, - init: T, - mut callback: impl FnMut( - Substack>, - ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, - T, - ) -> Result, - ) -> Result { - self.search_rec(init, Substack::Bottom, &mut callback) - } - - /// Internal version of [TreeTransforms::search_all] - fn search_rec<'a, T, E>( - &'a self, - state: T, - stack: Substack>, - callback: &mut impl FnMut( - Substack>, - ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, - T, - ) -> Result, - ) -> Result; -} - -/// The member in a [ModEntry] which is associated with a name in a [Module] -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ModMember { - /// Arbitrary data - Item(Item), - /// A child module - Sub(Module), -} - -impl TreeTransforms for ModMember { - type Item = Item; - type XEnt = XEnt; - type XMod = XMod; - type SelfType = ModMember; - - fn map_data_rec( - self, - item: &mut impl FnMut(Substack>, Item) -> T, - module: &mut impl FnMut(Substack>, XMod) -> U, - entry: &mut impl FnMut(Substack>, XEnt) -> V, - path: Substack>, - ) -> Self::SelfType { - match self { - Self::Item(it) => ModMember::Item(item(path, it)), - Self::Sub(sub) => ModMember::Sub(sub.map_data_rec(item, module, entry, path)), - } - } - - fn search_rec<'a, T, E>( - &'a self, - state: T, - stack: Substack>, - callback: &mut impl FnMut( - Substack>, - ModMemberRef<'a, Item, XMod, XEnt>, - T, - ) -> Result, - ) -> Result { - match self { - Self::Item(it) => callback(stack, ModMemberRef::Item(it), state), - Self::Sub(m) => m.search_rec(state, stack, callback), - } - } -} - -/// Reasons why merging trees might fail -pub enum ConflictKind { - /// Error during the merging of items - Item(Item::Error), - /// Error during the merging of module metadata - Module(XMod::Error), - /// Error during the merging of entry metadata - XEnt(XEnt::Error), - /// An item appeared in one tree where the other contained a submodule - ItemModule, -} - -macro_rules! impl_for_conflict { - ($target:ty, ($($deps:tt)*), $for:ty, $body:tt) => { - impl $target - for $for - where - Item::Error: $($deps)*, - XMod::Error: $($deps)*, - XEnt::Error: $($deps)*, - $body - }; -} - -impl_for_conflict!(Clone, (Clone), ConflictKind, { - fn clone(&self) -> Self { - match self { - ConflictKind::Item(it_e) => ConflictKind::Item(it_e.clone()), - ConflictKind::Module(mod_e) => ConflictKind::Module(mod_e.clone()), - ConflictKind::XEnt(ent_e) => ConflictKind::XEnt(ent_e.clone()), - ConflictKind::ItemModule => ConflictKind::ItemModule, - } - } -}); - -impl_for_conflict!(fmt::Debug, (fmt::Debug), ConflictKind, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ConflictKind::Item(it_e) => - f.debug_tuple("TreeCombineErr::Item").field(it_e).finish(), - ConflictKind::Module(mod_e) => - f.debug_tuple("TreeCombineErr::Module").field(mod_e).finish(), - ConflictKind::XEnt(ent_e) => - f.debug_tuple("TreeCombineErr::XEnt").field(ent_e).finish(), - ConflictKind::ItemModule => write!(f, "TreeCombineErr::Item2Module"), - } - } -}); - -/// Error produced when two trees cannot be merged -pub struct TreeConflict { - /// Which subtree caused the failure - pub path: VPath, - /// What type of failure occurred - pub kind: ConflictKind, -} -impl TreeConflict { - fn new(kind: ConflictKind) -> Self { Self { path: VPath::new([]), kind } } - - fn push(self, seg: Tok) -> Self { - Self { path: self.path.prefix([seg]), kind: self.kind } - } -} - -impl_for_conflict!(Clone, (Clone), TreeConflict, { - fn clone(&self) -> Self { - Self { path: self.path.clone(), kind: self.kind.clone() } - } -}); - -impl_for_conflict!(fmt::Debug, (fmt::Debug), TreeConflict, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TreeConflict") - .field("path", &self.path) - .field("kind", &self.kind) - .finish() - } -}); - -impl Combine for ModMember { - type Error = TreeConflict; - - fn combine(self, other: Self) -> Result { - match (self, other) { - (Self::Item(i1), Self::Item(i2)) => match i1.combine(i2) { - Ok(i) => Ok(Self::Item(i)), - Err(e) => Err(TreeConflict::new(ConflictKind::Item(e))), - }, - (Self::Sub(m1), Self::Sub(m2)) => m1.combine(m2).map(Self::Sub), - (..) => Err(TreeConflict::new(ConflictKind::ItemModule)), - } - } -} - -/// Data about a name in a [Module] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ModEntry { - /// The submodule or item - pub member: ModMember, - /// Additional fields - pub x: XEnt, -} -impl Combine for ModEntry { - type Error = TreeConflict; - fn combine(self, other: Self) -> Result { - match self.x.combine(other.x) { - Err(e) => Err(TreeConflict::new(ConflictKind::XEnt(e))), - Ok(x) => Ok(Self { x, member: self.member.combine(other.member)? }), - } - } -} -impl ModEntry { - /// Returns the item in this entry if it contains one. - #[must_use] - pub fn item(&self) -> Option<&Item> { - match &self.member { - ModMember::Item(it) => Some(it), - ModMember::Sub(_) => None, - } - } -} - -impl TreeTransforms for ModEntry { - type Item = Item; - type XEnt = XEnt; - type XMod = XMod; - type SelfType = ModEntry; - - fn map_data_rec( - self, - item: &mut impl FnMut(Substack>, Item) -> T, - module: &mut impl FnMut(Substack>, XMod) -> U, - entry: &mut impl FnMut(Substack>, XEnt) -> V, - path: Substack>, - ) -> Self::SelfType { - ModEntry { - member: self.member.map_data_rec(item, module, entry, path.clone()), - x: entry(path, self.x), - } - } - - fn search_rec<'a, T, E>( - &'a self, - state: T, - stack: Substack>, - callback: &mut impl FnMut( - Substack>, - ModMemberRef<'a, Item, XMod, XEnt>, - T, - ) -> Result, - ) -> Result { - self.member.search_rec(state, stack, callback) - } -} -impl ModEntry { - /// Wrap a member directly with trivial metadata - pub fn wrap(member: ModMember) -> Self { Self { member, x: XEnt::default() } } - /// Wrap an item directly with trivial metadata - pub fn leaf(item: Item) -> Self { Self::wrap(ModMember::Item(item)) } -} -impl ModEntry { - /// Create an empty submodule - pub fn empty() -> Self { Self::wrap(ModMember::Sub(Module::wrap([]))) } - - /// Create a module - #[must_use] - pub fn tree>(arr: impl IntoIterator) -> Self { - Self::wrap(ModMember::Sub(Module::wrap(arr.into_iter().map(|(k, v)| (i(k.as_ref()), v))))) - } - - /// Create a record in the list passed to [ModEntry#tree] which describes a - /// submodule. This mostly exists to deal with strange rustfmt block - /// breaking behaviour - pub fn tree_ent>(key: K, arr: impl IntoIterator) -> (K, Self) { - (key, Self::tree(arr)) - } - - /// Namespace the tree with the list of names - /// - /// The unarray is used to trick rustfmt into breaking the sub-item - /// into a block without breaking anything else. - #[must_use] - pub fn ns(name: impl AsRef, [mut end]: [Self; 1]) -> Self { - let elements = name.as_ref().split("::").collect::>(); - for name in elements.into_iter().rev() { - end = Self::tree([(name, end)]); - } - end - } - - fn not_mod_panic() -> T { panic!("Expected module but found leaf") } - - /// Return the wrapped module. Panic if the entry wraps an item - pub fn unwrap_mod(self) -> Module { - if let ModMember::Sub(m) = self.member { m } else { Self::not_mod_panic() } - } - - /// Return the wrapped module. Panic if the entry wraps an item - pub fn unwrap_mod_ref(&self) -> &Module { - if let ModMember::Sub(m) = &self.member { m } else { Self::not_mod_panic() } - } -} - -/// A module, containing imports, -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Module { - /// Submodules and items by name - pub entries: HashMap, ModEntry>, - /// Additional fields - pub x: XMod, -} - -trait_set! { - /// A filter applied to a module tree - pub trait Filter<'a, Item, XMod, XEnt> = - for<'b> Fn(&'b ModEntry) -> bool + Clone + 'a -} - -/// A line in a [Module] -pub type Record = (Tok, ModEntry); - -impl Module { - /// Returns child names for which the value matches a filter - #[must_use] - pub fn keys<'a>( - &'a self, - filter: impl for<'b> Fn(&'b ModEntry) -> bool + 'a, - ) -> BoxedIter> { - Box::new((self.entries.iter()).filter(move |(_, v)| filter(v)).map(|(k, _)| k.clone())) - } - - /// Return the module at the end of the given path - pub fn walk_ref<'a: 'b, 'b>( - &'a self, - prefix: &'b [Tok], - path: &'b [Tok], - filter: impl Filter<'b, Item, XMod, XEnt>, - ) -> Result<&'a Self, WalkError<'b>> { - let mut module = self; - for (pos, step) in path.iter().enumerate() { - let kind = match module.entries.get(step) { - None => ErrKind::Missing, - Some(ent) if !filter(ent) => ErrKind::Filtered, - Some(ModEntry { member: ModMember::Item(_), .. }) => ErrKind::NotModule, - Some(ModEntry { member: ModMember::Sub(next), .. }) => { - module = next; - continue; - }, - }; - let options = Sequence::new(move || module.keys(filter.clone())); - return Err(WalkError { kind, prefix, path, pos, options }); - } - Ok(module) - } - - /// Return the member at the end of the given path - /// - /// # Panics - /// - /// if path is empty, since the reference cannot be forwarded that way - pub fn walk1_ref<'a: 'b, 'b>( - &'a self, - prefix: &'b [Tok], - path: &'b [Tok], - filter: impl Filter<'b, Item, XMod, XEnt>, - ) -> Result<(&'a ModEntry, &'a Self), WalkError<'b>> { - let (last, parent) = path.split_last().expect("Path cannot be empty"); - let pos = path.len() - 1; - let module = self.walk_ref(prefix, parent, filter.clone())?; - let err_kind = match &module.entries.get(last) { - Some(entry) if filter(entry) => return Ok((entry, module)), - Some(_) => ErrKind::Filtered, - None => ErrKind::Missing, - }; - let options = Sequence::new(move || module.keys(filter.clone())); - Err(WalkError { kind: err_kind, options, prefix, path, pos }) - } - - /// Walk from one node to another in a tree, asserting that the origin can see - /// the target. - /// - /// # Panics - /// - /// If the target is the root node - pub fn inner_walk<'a: 'b, 'b>( - &'a self, - origin: &[Tok], - target: &'b [Tok], - is_exported: impl for<'c> Fn(&'c ModEntry) -> bool + Clone + 'b, - ) -> Result<(&'a ModEntry, &'a Self), WalkError<'b>> { - let ignore_vis_len = 1 + origin.iter().zip(target).take_while(|(a, b)| a == b).count(); - if target.len() <= ignore_vis_len { - return self.walk1_ref(&[], target, |_| true); - } - let (ignore_vis_path, hidden_path) = target.split_at(ignore_vis_len); - let first_divergence = self.walk_ref(&[], ignore_vis_path, |_| true)?; - first_divergence.walk1_ref(ignore_vis_path, hidden_path, is_exported) - } - - /// Wrap entry table in a module with trivial metadata - pub fn wrap(entries: impl IntoIterator>) -> Self - where XMod: Default { - Self { entries: entries.into_iter().collect(), x: XMod::default() } - } -} - -impl TreeTransforms for Module { - type Item = Item; - type XEnt = XEnt; - type XMod = XMod; - type SelfType = Module; - - fn map_data_rec( - self, - item: &mut impl FnMut(Substack>, Item) -> T, - module: &mut impl FnMut(Substack>, XMod) -> U, - entry: &mut impl FnMut(Substack>, XEnt) -> V, - path: Substack>, - ) -> Self::SelfType { - Module { - x: module(path.clone(), self.x), - entries: (self.entries.into_iter()) - .map(|(k, e)| (k.clone(), e.map_data_rec(item, module, entry, path.push(k)))) - .collect(), - } - } - - fn search_rec<'a, T, E>( - &'a self, - mut state: T, - stack: Substack>, - callback: &mut impl FnMut( - Substack>, - ModMemberRef<'a, Item, XMod, XEnt>, - T, - ) -> Result, - ) -> Result { - state = callback(stack.clone(), ModMemberRef::Mod(self), state)?; - for (key, value) in &self.entries { - state = value.search_rec(state, stack.push(key.clone()), callback)?; - } - Ok(state) - } -} - -impl Combine for Module { - type Error = TreeConflict; - fn combine(self, Self { entries, x }: Self) -> Result { - let entries = - try_join_maps(self.entries, entries, |k, l, r| l.combine(r).map_err(|e| e.push(k.clone())))?; - let x = (self.x.combine(x)).map_err(|e| TreeConflict::new(ConflictKind::Module(e)))?; - Ok(Self { x, entries }) - } -} - -impl fmt::Display - for Module -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "module {{")?; - for (name, ModEntry { member, x: extra }) in &self.entries { - match member { - ModMember::Sub(module) => write!(f, "\n{name} {extra} = {module}"), - ModMember::Item(item) => write!(f, "\n{name} {extra} = {item}"), - }?; - } - write!(f, "\n\n{}\n}}", &self.x) - } -} - -/// A non-owning version of [ModMember]. Either an item-ref or a module-ref. -pub enum ModMemberRef<'a, Item, XMod, XEnt> { - /// Leaf - Item(&'a Item), - /// Node - Mod(&'a Module), -} - -/// Possible causes why the path could not be walked -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ErrKind { - /// `require_exported` was set to `true` and a module wasn't exported - Filtered, - /// A module was not found - Missing, - /// The path leads into a leaf node - NotModule, -} - -#[derive(Clone)] -/// All details about a failed tree-walk -pub struct WalkError<'a> { - /// Failure mode - kind: ErrKind, - /// Path to the module where the walk started - prefix: &'a [Tok], - /// Planned walk path - path: &'a [Tok], - /// Index into walked path where the error occurred - pos: usize, - /// Alternatives to the failed steps - options: Sequence<'a, Tok>, -} -impl<'a> WalkError<'a> { - /// Total length of the path represented by this error - #[must_use] - pub fn depth(&self) -> usize { self.prefix.len() + self.pos + 1 } - - /// Attach a location to the error and convert into trait object for reporting - #[must_use] - pub fn at(self, origin: &CodeOrigin) -> ProjectErrorObj { - let details = WalkErrorDetails { - origin: origin.clone(), - path: VName::new((self.prefix.iter()).chain(self.path.iter().take(self.pos + 1)).cloned()) - .expect("empty paths don't cause an error"), - options: self.options.iter().collect(), - }; - match self.kind { - ErrKind::Filtered => FilteredError(details).pack(), - ErrKind::Missing => MissingError(details).pack(), - ErrKind::NotModule => NotModuleError(details).pack(), - } - } - /// Construct an error for the very last item in a slice. This is often done - /// outside [super::tree] so it gets a function rather than exposing the - /// fields of [WalkError] - pub fn last(path: &'a [Tok], kind: ErrKind, options: Sequence<'a, Tok>) -> Self { - WalkError { kind, path, options, pos: path.len() - 1, prefix: &[] } - } -} -impl<'a> fmt::Debug for WalkError<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("WalkError") - .field("kind", &self.kind) - .field("prefix", &self.prefix) - .field("path", &self.path) - .field("pos", &self.pos) - .finish_non_exhaustive() - } -} - -struct WalkErrorDetails { - path: VName, - options: Vec>, - origin: CodeOrigin, -} -impl WalkErrorDetails { - fn print_options(&self) -> String { format!("options are {}", ev(&self.options).join(", ")) } -} - -struct FilteredError(WalkErrorDetails); -impl ProjectError for FilteredError { - const DESCRIPTION: &'static str = "The path leads into a private module"; - fn one_position(&self) -> CodeOrigin { self.0.origin.clone() } - fn message(&self) -> String { format!("{} is private, {}", self.0.path, self.0.print_options()) } -} - -struct MissingError(WalkErrorDetails); -impl ProjectError for MissingError { - const DESCRIPTION: &'static str = "Nonexistent path"; - fn one_position(&self) -> CodeOrigin { self.0.origin.clone() } - fn message(&self) -> String { - format!("{} does not exist, {}", self.0.path, self.0.print_options()) - } -} - -struct NotModuleError(WalkErrorDetails); -impl ProjectError for NotModuleError { - const DESCRIPTION: &'static str = "The path leads into a leaf"; - fn one_position(&self) -> CodeOrigin { self.0.origin.clone() } - fn message(&self) -> String { - format!("{} is not a module, {}", self.0.path, self.0.print_options()) - } -} diff --git a/orchidlang/src/utils/clonable_iter.rs b/orchidlang/src/utils/clonable_iter.rs deleted file mode 100644 index 0f334d7..0000000 --- a/orchidlang/src/utils/clonable_iter.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::utils::take_with_output::take_with_output; - -enum State { - End, - Head(I), - Cont(Clonable, I::Item), -} - -/// Wraps a regular iterator and buffers previously emitted elements, to ensure -/// that clones of this iterator emit the same sequence of elements -/// independently of each other. Note that this ruins pretty much all of Rust's -/// iterator-related optimizations and allocates each buffered element on the -/// heap. -pub struct Clonable(Arc>>); -impl Clonable -where - I: Iterator, - I::Item: Clone, -{ - pub fn new(iter: impl IntoIterator) -> Self { - Self::wrap(State::Head(iter.into_iter())) - } - - fn wrap(s: State) -> Self { Self(Arc::new(Mutex::new(s))) } -} - -impl Iterator for Clonable -where - I: Iterator, - I::Item: Clone, -{ - type Item = I::Item; - fn next(&mut self) -> Option { - take_with_output(self, |Self(arc)| match Arc::try_unwrap(arc) { - Ok(mutex) => match mutex.into_inner().unwrap() { - State::End => (Self::wrap(State::End), None), - State::Cont(next, data) => (next, Some(data)), - State::Head(mut iter) => match iter.next() { - None => (Self::wrap(State::End), None), - Some(data) => (Self::wrap(State::Head(iter)), Some(data)), - }, - }, - Err(arc) => take_with_output(&mut *arc.lock().unwrap(), |s| match s { - State::End => (State::End, (Self::wrap(State::End), None)), - State::Cont(next, data) => (State::Cont(next.clone(), data.clone()), (next, Some(data))), - State::Head(mut iter) => match iter.next() { - None => (State::End, (Self::wrap(State::End), None)), - Some(data) => { - let head = Self::wrap(State::Head(iter)); - (State::Cont(head.clone(), data.clone()), (head, Some(data))) - }, - }, - }), - }) - } - fn size_hint(&self) -> (usize, Option) { - let mut steps = 0; - let mut cur = self.0.clone(); - loop { - let guard = cur.lock().unwrap(); - match &*guard { - State::End => break (steps, Some(steps)), - State::Head(i) => { - let (min, max) = i.size_hint(); - break (min + steps, max.map(|s| s + steps)); - }, - State::Cont(next, _) => { - let tmp = next.0.clone(); - drop(guard); - cur = tmp; - steps += 1; - }, - } - } - } -} - -impl Clone for Clonable { - fn clone(&self) -> Self { Self(self.0.clone()) } -} diff --git a/orchidlang/src/utils/combine.rs b/orchidlang/src/utils/combine.rs deleted file mode 100644 index fd344e1..0000000 --- a/orchidlang/src/utils/combine.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! The concept of a fallible merger - -use never::Never; - -/// Fallible, type-preserving variant of [std::ops::Add] implemented by a -/// variety of types for different purposes. Very broadly, if the operation -/// succeeds, the result should represent _both_ inputs. -pub trait Combine: Sized { - /// Information about the failure - type Error; - - /// Merge two values into a value that represents both, if this is possible. - fn combine(self, other: Self) -> Result; -} - -impl Combine for Never { - type Error = Never; - fn combine(self, _: Self) -> Result { match self {} } -} - -impl Combine for () { - type Error = Never; - fn combine(self, (): Self) -> Result { Ok(()) } -} diff --git a/orchidlang/src/utils/ddispatch.rs b/orchidlang/src/utils/ddispatch.rs deleted file mode 100644 index febc9ec..0000000 --- a/orchidlang/src/utils/ddispatch.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! A simplified, stable variant of `std::any::Provider`. - -use std::any::Any; - -/// A request for a value of an unknown type -pub struct Request<'a>(&'a mut dyn Any); -impl<'a> Request<'a> { - /// Checks if a value of the given type would serve the request, and the - /// request had not yet been served - pub fn can_serve(&self) -> bool { - self.0.downcast_ref::>().map_or(false, Option::is_none) - } - - /// Serve a value if it's the correct type - pub fn serve(&mut self, value: T) { self.serve_with::(|| value) } - - /// Invoke the callback to serve the request only if the return type matches - pub fn serve_with(&mut self, provider: impl FnOnce() -> T) { - if let Some(slot) = self.0.downcast_mut::>() { - if slot.is_none() { - *slot = Some(provider()); - } - } - } -} - -/// Trait for objects that can respond to type-erased commands. This trait is -/// a dependency of `Atomic` but the implementation can be left empty. -pub trait Responder { - /// Try to provide as many types as we support - fn respond(&self, _request: Request) {} -} - -/// Request a specific contract type from a responder -pub fn request(responder: &(impl Responder + ?Sized)) -> Option { - let mut slot = None; - responder.respond(Request(&mut slot)); - slot -} diff --git a/orchidlang/src/utils/get_or.rs b/orchidlang/src/utils/get_or.rs deleted file mode 100644 index b899a4d..0000000 --- a/orchidlang/src/utils/get_or.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::hash::Hash; - -use hashbrown::HashMap; - -/// Get the given value from the map or initialize it with the callback if it -/// doesn't exist, then return a mutable reference. -pub fn get_or_make<'a, K: Eq + Hash + Clone, V>( - map: &'a mut HashMap, - k: &K, - make: impl FnOnce() -> V, -) -> &'a mut V { - map.raw_entry_mut().from_key(k).or_insert_with(|| (k.clone(), make())).1 -} diff --git a/orchidlang/src/utils/iter_find.rs b/orchidlang/src/utils/iter_find.rs deleted file mode 100644 index a321665..0000000 --- a/orchidlang/src/utils/iter_find.rs +++ /dev/null @@ -1,44 +0,0 @@ -/// Check if the finite sequence produced by a clonable iterator (`haystack`) -/// contains the finite sequence produced by another clonable iterator -/// (`needle`) -pub fn iter_find( - mut haystack: impl Iterator + Clone, - needle: impl Iterator + Clone, -) -> Option { - let mut start = 0; - loop { - match iter_starts_with(haystack.clone(), needle.clone()) { - ISWResult::StartsWith => return Some(start), - ISWResult::Shorter => return None, - ISWResult::Difference => (), - } - haystack.next(); - start += 1; - } -} - -/// Value returned by iter_starts_with -enum ISWResult { - /// The first iterator starts with the second - StartsWith, - /// The values of the two iterators differ - Difference, - /// The first iterator ends before the second - Shorter, -} - -/// Checks that an iterator starts with another -fn iter_starts_with( - mut a: impl Iterator, - b: impl Iterator, -) -> ISWResult { - // if a starts with b then for every element in b - for item in b { - match a.next() { - Some(comp) if item == comp => (), - Some(_) => return ISWResult::Difference, - None => return ISWResult::Shorter, - } - } - ISWResult::StartsWith -} diff --git a/orchidlang/src/utils/join.rs b/orchidlang/src/utils/join.rs deleted file mode 100644 index 16ec461..0000000 --- a/orchidlang/src/utils/join.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Join hashmaps with a callback for merging or failing on conflicting keys. - -use std::hash::Hash; - -use hashbrown::HashMap; -use never::Never; - -/// Combine two hashmaps via an infallible value merger. See also -/// [try_join_maps] -pub fn join_maps( - left: HashMap, - right: HashMap, - mut merge: impl FnMut(&K, V, V) -> V, -) -> HashMap { - try_join_maps(left, right, |k, l, r| Ok(merge(k, l, r))).unwrap_or_else(|e: Never| match e {}) -} - -/// Combine two hashmaps via a fallible value merger. See also [join_maps] -pub fn try_join_maps( - left: HashMap, - mut right: HashMap, - mut merge: impl FnMut(&K, V, V) -> Result, -) -> Result, E> { - let mut mixed = HashMap::with_capacity(left.len() + right.len()); - for (key, lval) in left { - let val = match right.remove(&key) { - None => lval, - Some(rval) => merge(&key, lval, rval)?, - }; - mixed.insert(key, val); - } - mixed.extend(right); - Ok(mixed) -} diff --git a/orchidlang/src/utils/mod.rs b/orchidlang/src/utils/mod.rs deleted file mode 100644 index 3ba572c..0000000 --- a/orchidlang/src/utils/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Utilities that don't necessarily have a well-defined role in the -//! problem-domain of Orchid but are rather designed to fulfill abstract -//! project-domain tasks. -//! -//! An unreferenced util should be either moved out to a package or deleted - -pub(crate) mod boxed_iter; -pub(crate) mod clonable_iter; -pub mod combine; -pub mod ddispatch; -pub(crate) mod get_or; -pub(crate) mod iter_find; -pub mod join; -pub mod pure_seq; -pub mod sequence; -pub mod side; -pub mod string_from_charset; -pub mod take_with_output; -pub(crate) mod unwrap_or; diff --git a/orchidlang/src/utils/pure_seq.rs b/orchidlang/src/utils/pure_seq.rs deleted file mode 100644 index 1717c94..0000000 --- a/orchidlang/src/utils/pure_seq.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Methods to operate on Rust vectors in a declarative manner - -use std::iter; - -/// Pure version of [Vec::push] -/// -/// Create a new vector consisting of the provided vector with the -/// element appended. See [pushed_ref] to use it with a slice -pub fn pushed>(vec: I, t: I::Item) -> C { - vec.into_iter().chain(iter::once(t)).collect() -} - -/// Pure version of [Vec::push] -/// -/// Create a new vector consisting of the provided slice with the -/// element appended. See [pushed] for the owned version -pub fn pushed_ref<'a, T: Clone + 'a, C: FromIterator>( - vec: impl IntoIterator, - t: T, -) -> C { - vec.into_iter().cloned().chain(iter::once(t)).collect() -} - -/// Push an element on the adhoc stack, pass it to the callback, then pop the -/// element out again. -pub fn with_pushed( - vec: &mut Vec, - item: T, - cb: impl for<'a> FnOnce(&'a mut Vec) -> U, -) -> (T, U) { - vec.push(item); - let out = cb(vec); - let item = vec.pop().expect("top element stolen by callback"); - (item, out) -} diff --git a/orchidlang/src/utils/sequence.rs b/orchidlang/src/utils/sequence.rs deleted file mode 100644 index 4fe162b..0000000 --- a/orchidlang/src/utils/sequence.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! An alternative to `Iterable` in many languages, a [Fn] that returns an -//! iterator. - -use std::rc::Rc; - -use trait_set::trait_set; - -use super::boxed_iter::BoxedIter; - -trait_set! { - trait Payload<'a, T> = Fn() -> BoxedIter<'a, T> + 'a; -} - -/// Dynamic iterator building callback. Given how many trait objects this -/// involves, it may actually be slower than C#. -pub struct Sequence<'a, T: 'a>(Rc>); -impl<'a, T: 'a> Sequence<'a, T> { - /// Construct from a concrete function returning a concrete iterator - pub fn new + 'a>(f: impl Fn() -> I + 'a) -> Self { - Self(Rc::new(move || Box::new(f().into_iter()))) - } - /// Get an iterator from the function - pub fn iter(&self) -> impl Iterator + '_ { (self.0)() } -} -impl<'a, T: 'a> Clone for Sequence<'a, T> { - fn clone(&self) -> Self { Self(self.0.clone()) } -} diff --git a/orchidlang/src/utils/string_from_charset.rs b/orchidlang/src/utils/string_from_charset.rs deleted file mode 100644 index 53809b5..0000000 --- a/orchidlang/src/utils/string_from_charset.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Generate valid names from numbers and a character set. For small numbers, -//! the results should be substantially more memorable than the plain numbers. - -fn string_from_charset_rec(val: u64, digits: &str) -> String { - let radix = digits.len() as u64; - let mut prefix = - if val > radix { string_from_charset_rec(val / radix, digits) } else { String::new() }; - let digit = digits - .chars() - .nth(val as usize - 1) - .unwrap_or_else(|| panic!("Overindexed digit set \"{}\" with {}", digits, val - 1)); - prefix.push(digit); - prefix -} - -/// Generate alphabetized names from numbers using a set of permitted -/// characters. Especially practical in combination with De Bruijn indices -pub fn string_from_charset(val: u64, digits: &str) -> String { - string_from_charset_rec(val + 1, digits) -} diff --git a/orchidlang/src/utils/take_with_output.rs b/orchidlang/src/utils/take_with_output.rs deleted file mode 100644 index eebcf3d..0000000 --- a/orchidlang/src/utils/take_with_output.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Map over a `&mut` and return an additional value - -/// A variation on [take_mut::take] that allows the callback to return a value -pub fn take_with_output(src: &mut T, cb: impl FnOnce(T) -> (T, U)) -> U { - take_mut::scoped::scope(|scope| { - let (old, hole) = scope.take(src); - let (new, out) = cb(old); - hole.fill(new); - out - }) -} diff --git a/orchidlang/src/utils/unwrap_or.rs b/orchidlang/src/utils/unwrap_or.rs deleted file mode 100644 index bacf999..0000000 --- a/orchidlang/src/utils/unwrap_or.rs +++ /dev/null @@ -1,27 +0,0 @@ -/// A macro version of [Option::unwrap_or_else] which supports flow -/// control statements such as `return` and `break` in the "else" branch. -/// -/// ```ignore -/// crate::unwrap_or!(Some(1); return) -/// ``` -/// -/// It also supports unwrapping concrete variants of other enums -/// -/// ```ignore -/// use crate::Literal; -/// -/// crate::unwrap_or!(Literal::Usize(2) => Literal::Number; return) -/// ``` -/// -/// Note: this macro influences the control flow of the surrounding code -/// without an `if`, which can be misleading. It should only be used for small, -/// straightforward jumps. -macro_rules! unwrap_or { - ($m:expr; $fail:expr) => {{ if let Some(res) = ($m) { res } else { $fail } }}; - ($m:expr => $pattern:path; $fail:expr) => { - // rustfmt keeps inlining this and then complaining about its length - { if let $pattern(res) = ($m) { res } else { $fail } } - }; -} - -pub(crate) use unwrap_or; diff --git a/orchidlang/src/virt_fs/common.rs b/orchidlang/src/virt_fs/common.rs deleted file mode 100644 index c902ad6..0000000 --- a/orchidlang/src/virt_fs/common.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::rc::Rc; -use std::sync::Arc; - -use intern_all::Tok; - -use crate::error::{ErrorSansOrigin, ErrorSansOriginObj}; -use crate::name::{PathSlice, VPath}; - -/// Represents the result of loading code from a string-tree form such -/// as the file system. Cheap to clone. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Loaded { - /// Conceptually equivalent to a sourcefile - Code(Arc), - /// Conceptually equivalent to the list of *.orc files in a folder, without - /// the extension - Collection(Arc>>), -} -impl Loaded { - /// Is the loaded item source code (not a collection)? - pub fn is_code(&self) -> bool { matches!(self, Loaded::Code(_)) } - /// Collect the elements in a collection rreport - pub fn collection(items: impl IntoIterator>) -> Self { - Self::Collection(Arc::new(items.into_iter().collect())) - } -} - -/// Returned by any source loading callback -pub type FSResult = Result; - -/// Type that indicates the type of an entry without reading the contents -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub enum FSKind { - /// Invalid path or read error - None, - /// Source code - Code, - /// Internal tree node - Collection, -} - -/// Distinguished error for missing code -#[derive(Clone, PartialEq, Eq)] -pub struct CodeNotFound(pub VPath); -impl CodeNotFound { - /// Instantiate error - pub fn new(path: VPath) -> Self { Self(path) } -} -impl ErrorSansOrigin for CodeNotFound { - const DESCRIPTION: &'static str = "No source code for path"; - fn message(&self) -> String { format!("{} not found", self.0) } -} - -/// A simplified view of a file system for the purposes of source code loading. -/// This includes the real FS and source code, but also various in-memory -/// formats and other sources for libraries and dependencies. -pub trait VirtFS { - /// Implementation of [VirtFS::read] - fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult; - /// Discover information about a path without reading it. - /// - /// Implement this if your vfs backend can do expensive operations - fn kind(&self, path: &PathSlice) -> FSKind { - match self.read(path) { - Err(_) => FSKind::None, - Ok(Loaded::Code(_)) => FSKind::Code, - Ok(Loaded::Collection(_)) => FSKind::Collection, - } - } - /// Convert a path into a human-readable string that is meaningful in the - /// target context. - fn display(&self, path: &[Tok]) -> Option; - /// Convert the FS handler into a type-erased version of itself for packing in - /// a tree. - fn rc(self) -> Rc - where Self: Sized + 'static { - Rc::new(self) - } - /// Read a path, returning either a text file, a directory listing or an - /// error. Wrapper for [VirtFS::get] - fn read(&self, path: &PathSlice) -> FSResult { self.get(path, path) } -} - -impl VirtFS for &dyn VirtFS { - fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { - (*self).get(path, full_path) - } - fn display(&self, path: &[Tok]) -> Option { (*self).display(path) } -} - -impl VirtFS for Rc { - fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { - (**self).get(path, full_path) - } - fn display(&self, path: &[Tok]) -> Option { (**self).display(path) } -} diff --git a/orchidlang/src/virt_fs/decl.rs b/orchidlang/src/virt_fs/decl.rs deleted file mode 100644 index 69daf1a..0000000 --- a/orchidlang/src/virt_fs/decl.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::rc::Rc; -use std::sync::Arc; - -use intern_all::Tok; - -use super::common::CodeNotFound; -use super::{FSResult, Loaded, VirtFS}; -use crate::error::ErrorSansOrigin; -use crate::name::PathSlice; -use crate::tree::{ModEntry, ModMember}; -use crate::utils::combine::Combine; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConflictingTrees; - -impl Combine for Rc { - type Error = ConflictingTrees; - fn combine(self, _: Self) -> Result { Err(ConflictingTrees) } -} - -impl Combine for Arc { - type Error = ConflictingTrees; - fn combine(self, _: Self) -> Result { Err(ConflictingTrees) } -} - -impl<'a> Combine for &'a dyn VirtFS { - type Error = ConflictingTrees; - fn combine(self, _: Self) -> Result { Err(ConflictingTrees) } -} - -/// A declarative in-memory tree with [VirtFS] objects for leaves. Paths are -/// followed to a leaf and the leftover handled by it. -pub type DeclTree = ModEntry, (), ()>; - -impl VirtFS for DeclTree { - fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { - match &self.member { - ModMember::Item(it) => it.get(path, full_path), - ModMember::Sub(module) => match path.split_first() { - None => Ok(Loaded::collection(module.keys(|_| true))), - Some((head, tail)) => (module.entries.get(head)) - .ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack()) - .and_then(|ent| ent.get(tail, full_path)), - }, - } - } - - fn display(&self, path: &[Tok]) -> Option { - let (head, tail) = path.split_first()?; - match &self.member { - ModMember::Item(it) => it.display(path), - ModMember::Sub(module) => module.entries.get(head)?.display(tail), - } - } -} - -impl VirtFS for String { - fn display(&self, _: &[Tok]) -> Option { None } - fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { - (path.is_empty().then(|| Loaded::Code(Arc::new(self.as_str().to_string())))) - .ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack()) - } -} - -impl<'a> VirtFS for &'a str { - fn display(&self, _: &[Tok]) -> Option { None } - fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { - (path.is_empty().then(|| Loaded::Code(Arc::new(self.to_string())))) - .ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack()) - } -} - -/// Insert a file by cleartext contents in the [DeclTree]. -pub fn decl_file(s: &str) -> DeclTree { DeclTree::leaf(Rc::new(s.to_string())) } diff --git a/orchidlang/src/virt_fs/dir.rs b/orchidlang/src/virt_fs/dir.rs deleted file mode 100644 index f67a7b5..0000000 --- a/orchidlang/src/virt_fs/dir.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::cell::RefCell; -use std::fs::File; -use std::io; -use std::io::{ErrorKind, Read}; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; - -use hashbrown::HashMap; -use intern_all::{i, Tok}; - -use super::common::CodeNotFound; -use super::{FSResult, Loaded, VirtFS}; -use crate::error::{ErrorSansOrigin, ErrorSansOriginObj}; -use crate::name::PathSlice; - -#[derive(Clone)] -struct OpenError { - file: Arc>, - dir: Arc>, -} -impl OpenError { - pub fn wrap(file: io::Error, dir: io::Error) -> ErrorSansOriginObj { - Self { dir: Arc::new(Mutex::new(dir)), file: Arc::new(Mutex::new(file)) }.pack() - } -} -impl ErrorSansOrigin for OpenError { - const DESCRIPTION: &'static str = "A file system error occurred"; - fn message(&self) -> String { - let Self { dir, file } = self; - format!( - "File system errors other than not found occurred\n\ - as a file: {}\nas a directory: {}", - file.lock().unwrap(), - dir.lock().unwrap() - ) - } -} - -#[derive(Clone)] -struct IOError(Arc>); -impl IOError { - pub fn wrap(inner: io::Error) -> ErrorSansOriginObj { Self(Arc::new(Mutex::new(inner))).pack() } -} -impl ErrorSansOrigin for IOError { - const DESCRIPTION: &'static str = "an I/O error occured"; - fn message(&self) -> String { format!("File read error: {}", self.0.lock().unwrap()) } -} - -#[derive(Clone)] -struct NotUtf8(PathBuf); -impl NotUtf8 { - pub fn wrap(path: &Path) -> ErrorSansOriginObj { Self(path.to_owned()).pack() } -} -impl ErrorSansOrigin for NotUtf8 { - const DESCRIPTION: &'static str = "Source files must be UTF-8"; - fn message(&self) -> String { - format!("{} is a source file but contains invalid UTF-8", self.0.display()) - } -} - -/// A real file system directory linked into the virtual FS -pub struct DirNode { - cached: RefCell>, - root: PathBuf, - suffix: &'static str, -} -impl DirNode { - /// Reference a real file system directory in the virtual FS - pub fn new(root: PathBuf, suffix: &'static str) -> Self { - assert!(suffix.starts_with('.'), "Extension must begin with ."); - Self { cached: RefCell::default(), root, suffix } - } - - fn ext(&self) -> &str { self.suffix.strip_prefix('.').expect("Checked in constructor") } - - fn load_file(&self, fpath: &Path, orig_path: &PathSlice) -> FSResult { - match fpath.read_dir() { - Err(dir_e) => { - let fpath = fpath.with_extension(self.ext()); - let mut file = - File::open(&fpath).map_err(|file_e| match (dir_e.kind(), file_e.kind()) { - (ErrorKind::NotFound, ErrorKind::NotFound) => - CodeNotFound::new(orig_path.to_vpath()).pack(), - _ => OpenError::wrap(file_e, dir_e), - })?; - let mut buf = Vec::new(); - file.read_to_end(&mut buf).map_err(IOError::wrap)?; - let text = String::from_utf8(buf).map_err(|_| NotUtf8::wrap(&fpath))?; - Ok(Loaded::Code(Arc::new(text))) - }, - Ok(dir) => Ok(Loaded::collection(dir.filter_map(|ent_r| { - let ent = ent_r.ok()?; - let name = ent.file_name().into_string().ok()?; - match ent.metadata().ok()?.is_dir() { - false => Some(i(name.strip_suffix(self.suffix)?)), - true => Some(i(&name)), - } - }))), - } - } - - fn mk_pathbuf(&self, path: &[Tok]) -> PathBuf { - let mut fpath = self.root.clone(); - path.iter().for_each(|seg| fpath.push(seg.as_str())); - fpath - } -} -impl VirtFS for DirNode { - fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { - let fpath = self.mk_pathbuf(path); - let mut binding = self.cached.borrow_mut(); - let (_, res) = (binding.raw_entry_mut().from_key(&fpath)) - .or_insert_with(|| (fpath.clone(), self.load_file(&fpath, full_path))); - res.clone() - } - - fn display(&self, path: &[Tok]) -> Option { - let pathbuf = self.mk_pathbuf(path).with_extension(self.ext()); - Some(pathbuf.to_string_lossy().to_string()) - } -} diff --git a/orchidlang/src/virt_fs/embed.rs b/orchidlang/src/virt_fs/embed.rs deleted file mode 100644 index 7f9e0c5..0000000 --- a/orchidlang/src/virt_fs/embed.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::sync::Arc; - -use intern_all::{ev, i, Tok}; -use rust_embed::RustEmbed; - -use super::common::CodeNotFound; -use super::{FSResult, Loaded, VirtFS}; -use crate::error::ErrorSansOrigin; -use crate::location::CodeGenInfo; -use crate::name::PathSlice; -use crate::tree::{ModEntry, ModMember, Module}; - -/// An in-memory FS tree for libraries managed internally by the interpreter -pub struct EmbeddedFS { - tree: Module, (), ()>, - suffix: &'static str, - gen: CodeGenInfo, -} -impl EmbeddedFS { - /// Expose a directory embedded in a binary wiht [RustEmbed] to the - /// interpreter - pub fn new(suffix: &'static str, gen: CodeGenInfo) -> Self { - let mut tree = Module::wrap([]); - for path in T::iter() { - let data_buf = T::get(&path).expect("path from iterator").data.to_vec(); - let data = String::from_utf8(data_buf).expect("embed must be utf8"); - let mut cur_node = &mut tree; - let path_no_suffix = path.strip_suffix(suffix).expect("embed filtered for suffix"); - let mut segments = path_no_suffix.split('/').map(i); - let mut cur_seg = segments.next().expect("Embed is a directory"); - for next_seg in segments { - if !cur_node.entries.contains_key(&cur_seg) { - let ent = ModEntry::wrap(ModMember::Sub(Module::wrap([]))); - cur_node.entries.insert(cur_seg.clone(), ent); - } - let ent = cur_node.entries.get_mut(&cur_seg).expect("just constructed"); - match &mut ent.member { - ModMember::Sub(submod) => cur_node = submod, - _ => panic!("Aliased file and folder"), - }; - cur_seg = next_seg; - } - let data_ent = ModEntry::wrap(ModMember::Item(Arc::new(data))); - let prev = cur_node.entries.insert(cur_seg, data_ent); - debug_assert!(prev.is_none(), "file name unique"); - } - // if gen.generator == "std" { - // panic!( - // "{:?}", - // tree.map_data(&|_, s| (), &|_, x| x, &|_, x| x, Substack::Bottom) - // ); - // }; - Self { gen, suffix, tree } - } -} - -impl VirtFS for EmbeddedFS { - fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { - if path.is_empty() { - return Ok(Loaded::collection(self.tree.keys(|_| true))); - } - let entry = (self.tree.walk1_ref(&[], path, |_| true)) - .map_err(|_| CodeNotFound::new(full_path.to_vpath()).pack())?; - Ok(match &entry.0.member { - ModMember::Item(text) => Loaded::Code(text.clone()), - ModMember::Sub(sub) => Loaded::collection(sub.keys(|_| true)), - }) - } - fn display(&self, path: &[Tok]) -> Option { - let Self { gen, suffix, .. } = self; - Some(format!("{}{suffix} in {gen}", ev(path).join("/"))) - } -} diff --git a/orchidlang/src/virt_fs/mod.rs b/orchidlang/src/virt_fs/mod.rs deleted file mode 100644 index fc3d537..0000000 --- a/orchidlang/src/virt_fs/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Abstractions and primitives to help define the namespace tree used by -//! Orchid. -//! -//! Although this may make it seem like the namespace tree is very flexible, -//! libraries are generally permitted and expected to hardcode their own -//! location in the tree, so it's up to the embedder to ensure that the flexible -//! structure retains the assumed location of all code. -mod common; -mod decl; -mod dir; -mod embed; -mod prefix; - -pub use common::{CodeNotFound, FSKind, FSResult, Loaded, VirtFS}; -pub use decl::{decl_file, DeclTree}; -pub use dir::DirNode; -pub use embed::EmbeddedFS; -pub use prefix::PrefixFS; diff --git a/orchidlang/src/virt_fs/prefix.rs b/orchidlang/src/virt_fs/prefix.rs deleted file mode 100644 index 93bdaba..0000000 --- a/orchidlang/src/virt_fs/prefix.rs +++ /dev/null @@ -1,38 +0,0 @@ -use intern_all::Tok; -use itertools::Itertools; - -use super::common::CodeNotFound; -use super::VirtFS; -use crate::error::ErrorSansOrigin; -use crate::name::{PathSlice, VPath}; - -/// Modify the prefix of a nested file tree -pub struct PrefixFS<'a> { - remove: VPath, - add: VPath, - wrapped: Box, -} -impl<'a> PrefixFS<'a> { - /// Modify the prefix of a file tree - pub fn new(wrapped: impl VirtFS + 'a, remove: impl AsRef, add: impl AsRef) -> Self { - Self { - wrapped: Box::new(wrapped), - remove: VPath::parse(remove.as_ref()), - add: VPath::parse(add.as_ref()), - } - } - fn proc_path(&self, path: &[Tok]) -> Option>> { - let path = path.strip_prefix(self.remove.as_slice())?; - Some(self.add.0.iter().chain(path).cloned().collect_vec()) - } -} -impl<'a> VirtFS for PrefixFS<'a> { - fn get(&self, path: &[Tok], full_path: &PathSlice) -> super::FSResult { - let path = - (self.proc_path(path)).ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())?; - self.wrapped.get(&path, full_path) - } - fn display(&self, path: &[Tok]) -> Option { - self.wrapped.display(&self.proc_path(path)?) - } -} diff --git a/orcx/src/main.rs b/orcx/src/main.rs index d97a735..fd65523 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -116,7 +116,7 @@ async fn main() -> io::Result { let mut file = File::open(file.as_std_path()).unwrap(); let mut buf = String::new(); file.read_to_string(&mut buf).unwrap(); - let lexemes = lex(i.i(&buf).await, sym!(usercode; i).await, &systems, ctx).await.unwrap(); + let lexemes = lex(i.i(&buf).await, sym!(usercode; i), &systems, ctx).await.unwrap(); println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).await, true)) }, Commands::Parse { file } => { @@ -124,7 +124,7 @@ async fn main() -> io::Result { let mut file = File::open(file.as_std_path()).unwrap(); let mut buf = String::new(); file.read_to_string(&mut buf).unwrap(); - let lexemes = lex(i.i(&buf).await, sym!(usercode; i).await, &systems, ctx).await.unwrap(); + let lexemes = lex(i.i(&buf).await, sym!(usercode; i), &systems, ctx).await.unwrap(); let Some(first) = lexemes.first() else { println!("File empty!"); return; @@ -134,7 +134,7 @@ async fn main() -> io::Result { rep: &reporter, systems: &systems, ctx: ctx.clone(), - src: sym!(usercode; i).await, + src: sym!(usercode; i), }; let snip = Snippet::new(first, &lexemes); let ptree = parse_items(&pctx, Substack::Bottom, snip).await.unwrap(); @@ -155,7 +155,7 @@ async fn main() -> io::Result { Commands::Repl => { let mut counter = 0; let mut imports = Vec::new(); - let usercode_path = sym!(usercode; i).await; + let usercode_path = sym!(usercode; i); let mut stdin = BufReader::new(stdin()); loop { counter += 1; @@ -238,7 +238,7 @@ async fn main() -> io::Result { let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap(); if let Some(proj_path) = proj { let path = proj_path.into_std_path_buf(); - match parse_folder(&root, path, sym!(src; i).await, &reporter, ctx.clone()).await { + match parse_folder(&root, path, sym!(src; i), &reporter, ctx.clone()).await { Ok(r) => root = r, Err(e) => { eprintln!("{e}"); @@ -282,12 +282,12 @@ async fn main() -> io::Result { }, Commands::Exec { proj, code } => { let reporter = Reporter::new(); - let path = sym!(usercode; i).await; + let path = sym!(usercode; i); let prefix_sr = SrcRange::zw(path.clone(), 0); let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap(); if let Some(proj_path) = proj { let path = proj_path.into_std_path_buf(); - match parse_folder(&root, path, sym!(src; i).await, &reporter, ctx.clone()).await { + match parse_folder(&root, path, sym!(src; i), &reporter, ctx.clone()).await { Ok(r) => root = r, Err(e) => { eprintln!("{e}"); @@ -321,7 +321,7 @@ async fn main() -> io::Result { }; let reporter = Reporter::new(); let root = root.add_parsed(&entrypoint, path.clone(), &reporter).await; - let expr = ExprKind::Const(sym!(usercode::entrypoint; i).await).at(prefix_sr.pos()); + let expr = ExprKind::Const(sym!(usercode::entrypoint; i)).at(prefix_sr.pos()); let mut xctx = ExecCtx::new(ctx.clone(), logger.clone(), root, expr).await; xctx.set_gas(Some(1000)); xctx.execute().await; diff --git a/orcx/src/parse_folder.rs b/orcx/src/parse_folder.rs index e8f50ac..658f0a2 100644 --- a/orcx/src/parse_folder.rs +++ b/orcx/src/parse_folder.rs @@ -72,7 +72,7 @@ pub async fn parse_folder( let hpctx = HostParseCtxImpl { ctx: ctx.clone(), rep, src: ns.clone(), systems: &systems }; let Some(fst) = lexemes.first() else { return Ok(Some(ParsedModule::new(false, []))) }; let items = parse_items(&hpctx, Substack::Bottom, Snippet::new(fst, &lexemes)).await?; - Ok(Some(ParsedModule::new(false, items))) + Ok(Some(ParsedModule::new(true, items))) } else { Ok(None) }