diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 89b937e..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "rudus" -version = "0.0.1" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -ariadne = { git = "https://github.com/zesterer/ariadne" } -chumsky = { git = "https://github.com/zesterer/chumsky", features = ["label"] } -imbl = "3.0.0" -ran = "2.0.1" -num-derive = "0.4.2" -num-traits = "0.2.19" -regex = "1.11.1" -wasm-bindgen = "0.2" -# struct_scalpel = "0.1.1" -# rust-embed = "8.5.0" -# boxing = "0.1.2" -# ordered-float = "4.5.0" -# index_vec = "0.1.4" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b945e7c..0000000 --- a/LICENSE +++ /dev/null @@ -1,235 +0,0 @@ -GNU AFFERO GENERAL PUBLIC LICENSE -Version 3, 19 November 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 Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. - -The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are 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. - -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. - -Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. - -A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. - -The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. - -An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. - -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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. - -Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. - -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 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 work with which it is combined will remain governed by version 3 of the GNU General Public License. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of the GNU Affero 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 Affero 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 Affero 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 Affero 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. - - rudus - Copyright (C) 2024 scott - - This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. - -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 AGPL, see . diff --git a/README.md b/README.md deleted file mode 100644 index bf252a2..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# rudus - -A Rust implementation of Ludus. \ No newline at end of file diff --git a/assets/prelude.ld b/assets/prelude.ld deleted file mode 100644 index d1c5b4c..0000000 --- a/assets/prelude.ld +++ /dev/null @@ -1,1474 +0,0 @@ -& this file, uniquely, gets `base` loaded as context. See src/base.janet for exports - -& let base = base - -& some forward declarations -& TODO: fix this so that we don't need (as many of) them -& fn and -fn append -fn apply_command -fn assoc -fn atan/2 -fn deg/rad -fn dict -fn first -fn floor -fn get -fn join -fn mod -fn neg? -fn print! -fn some? -fn store! -fn string -fn turn/rad -fn unbox -fn update! - -fn type { - "Returns a keyword representing the type of the value passed in." - (x) -> base :type (x) -} - -& some helper type functions -fn coll? { - "Returns true if a value is a collection: dict, list, tuple, or set." - (coll as :dict) -> true - (coll as :list) -> true - (coll as :tuple) -> true - & (coll as :set) -> true - (_) -> false -} - -fn ordered? { - "Returns true if a value is an indexed collection: list or tuple." - (coll as :list) -> true - (coll as :tuple) -> true - (coll as :string) -> true - (_) -> false -} - -fn assoc? { - "Returns true if a value is an associative collection: a dict or a pkg." - (d as :dict) -> true - (_) -> false -} - -&&& nil: working with nothing - -fn nil? { - "Returns true if a value is nil." - (nil) -> true - (_) -> false -} - -fn some? { - "Returns true if a value is not nil." - (nil) -> false - (_) -> true -} - -fn some { - "Takes a possibly nil value and a default value. Returns the value if it's not nil, returns the default if it's nil." - (nil, default) -> default - (value, _) -> value -} - -& ...and if two things are the same -fn eq? { - "Returns true if all arguments have the same value." - (x) -> true - (x, y) -> base :eq? (x, y) - (x, y, ...zs) -> if eq? (x, y) - then loop (y, zs) with { - (a, []) -> eq? (a, x) - (a, [b, ...cs]) -> if eq? (a, x) - then recur (b, cs) - else false - } - else false -} - -&&& true & false: boolean logic (part the first) -fn bool? { - "Returns true if a value is of type :boolean." - (false) -> true - (true) -> true - (_) -> false -} - -fn true? { - "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else." - (true) -> true - (_) -> false -} - -fn false? { - "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`." - (false) -> true - (_) -> false -} - -fn bool { - "Returns false if a value is nil or false, otherwise returns true." - (nil) -> false - (false) -> false - (_) -> true -} - -fn not { - "Returns false if a value is truthy, true if a value is falsy." - (nil) -> true - (false) -> true - (_) -> false -} - -& fn neq? { -& "Returns true if none of the arguments have the same value." -& (x) -> false -& (x, y) -> not (eq? (x, y)) -& (x, y, ...zs) -> if eq? (x, y) -& then false -& else loop (y, zs) with { -& (a, []) -> neq? (a, x) -& (a, [b, ...cs]) -> if neq? (a, x) -& then recur (b, cs) -& else false -& } -& } - -& tuples: not a lot you can do with them functionally -fn tuple? { - "Returns true if a value is a tuple." - (tuple as :tuple) -> true - (_) -> false -} - -&&& functions: getting things done -fn fn? { - "Returns true if an argument is a function." - (f as :fn) -> true - (_) -> false -} - -& what we need for some very basic list manipulation -fn rest { - "Returns all but the first element of a list or tuple, as a list." - ([]) -> [] - (()) -> () - (xs as :list) -> base :rest (xs) - (xs as :tuple) -> base :rest (xs) - (xs as :string) -> base :str_slice (xs, 1) -} - -fn inc { - "Increments a number." - (x as :number) -> base :inc (x) -} - -fn dec { - "Decrements a number." - (x as :number) -> base :dec (x) -} - -fn count { - "Returns the number of elements in a collection (including string)." - (xs as :list) -> base :count (xs) - (xs as :tuple) -> base :count (xs) - (xs as :dict) -> base :count (xs) - (xs as :string) -> base :count (xs) - & (xs as :set) -> base :count (xs) -} - -fn empty? { - "Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)." - ([]) -> true - (#{}) -> true - & (s as :set) -> eq? (s, ${}) - (()) -> true - ("") -> true - (_) -> false -} - -fn any? { - "Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers)." - ([...]) -> true - (#{...}) -> true - & (s as :set) -> not (empty? (s)) - ((...)) -> true - (s as :string) -> not (empty? (s)) - (_) -> false -} - -fn list? { - "Returns true if the value is a list." - (l as :list) -> true - (_) -> false -} - -fn list { - "Takes a value and returns it as a list. For values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order: sets and dicts don't have predictable or stable ordering in output. Dicts return lists of (key, value) tuples." - (x) -> base :to_list (x) -} - -fn fold { - "Folds a list." - (f as :fn, []) -> [] - (f as :fn, xs as :list) -> fold (f, xs, f ()) - (f as :fn, [], root) -> [] - (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { - (prev, curr, []) -> f (prev, curr) - (prev, curr, remaining) -> recur ( - f (prev, curr) - first (remaining) - rest (remaining) - ) - } -} - -fn map { - "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away." - (f as :fn) -> map (f, _) - (kw as :keyword) -> map (kw, _) - (f as :fn, xs) -> { - fn mapper (prev, curr) -> append (prev, f (curr)) - fold (mapper, xs, []) - } - (kw as :keyword, xs) -> { - fn mapper (prev, curr) -> append (prev, kw (curr)) - fold (mapper, xs, []) - } -} - -fn filter { - "Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`." - (p? as :fn) -> filter (p?, _) - (p? as :fn, xs) -> { - fn filterer (filtered, x) -> if p? (x) - then append (filtered, x) - else filtered - fold (filterer, xs, []) - } -} - -fn keep { - "Takes a list and returns a new list with any `nil` values omitted." - (xs) -> filter (some?, xs) -} - -fn append { - "Adds an element to a list." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :conj (xs, x) - & (xs as :set) -> xs - & (xs as :set, x) -> base :conj (xs, x) -} - -fn append! { - "Adds an element to a list, modifying it." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :conj! (xs, x) -} - -fn concat { - "Combines two lists, strings, or sets." - (x as :string, y as :string) -> "{x}{y}" - (xs as :list, ys as :list) -> base :concat (xs, ys) - & (xs as :set, ys as :set) -> base :concat (xs, ys) - (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) -} - -& fn set { -& "Takes an ordered collection--list or tuple--and turns it into a set. Returns sets unharmed." -& (xs as :list) -> fold (append, xs, ${}) -& (xs as :tuple) -> do xs > list > set -& (xs as :set) -> xs -& } - -& fn set? { -& "Returns true if a value is a set." -& (xs as :set) -> true -& (_) -> false -& } - -fn contains? { - "Returns true if a set or list contains a value." - & (value, s as :set) -> bool (base :get (s, value)) - (value, l as :list) -> loop (l) with { - ([]) -> false - ([x]) -> eq? (x, value) - ([x, ...xs]) -> if eq? (x, value) - then true - else recur (xs) - } -} - -& fn omit { -& "Returns a new set with the value omitted." -& (value, s as :set) -> base :disj (s, value) -& } - -fn print! { - "Sends a text representation of Ludus values to the console." - (...args) -> { - base :print! (args) - :ok - } -} - -fn show { - "Returns a text representation of a Ludus value as a string." - (x) -> base :show (x) -} - -fn report! { - "Prints a value, then returns it." - (x) -> { - print! (x) - x - } - (msg as :string, x) -> { - print! (concat ("{msg} ", show (x))) - x - } -} - -fn doc! { - "Prints the documentation of a function to the console." - (f as :fn) -> do f > base :doc > print! - (_) -> :none -} - -&&& strings: harder than they look! -fn string? { - "Returns true if a value is a string." - (x as :string) -> true - (_) -> false -} - -fn string { - "Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of different kinds of values." - (x as :string) -> x - (x) -> show (x) - (x, ...xs) -> loop (x, xs) with { - (out, [y]) -> concat (out, show (y)) - (out, [y, ...ys]) -> recur (concat (out, show (y)), ys) - } -} - -fn join { - "Takes a list of strings, and joins them into a single string, interposing an optional separator." - ([]) -> "" - ([str as :string]) -> str - (strs as :list) -> join (strs, "") - ([], separator as :string) -> "" - ([str as :string], separator as :string) -> str - ([str, ...strs], separator as :string) -> fold ( - fn (joined, to_join) -> concat (joined, separator, to_join) - strs - str - ) -} - -fn split { - "Takes a string, and turns it into a list of strings, breaking on the separator." - (str as :string, break as :string) -> base :split (break, str) -} - -fn trim { - "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." - (str as :string) -> base :trim (str) - (str as :string, :left) -> base :triml (str) - (str as :string, :right) -> base :trimr (str) -} - -fn upcase { - "Takes a string and returns it in all uppercase. Works only for ascii characters." - (str as :string) -> base :upcase (str) -} - -fn downcase { - "Takes a string and returns it in all lowercase. Works only for ascii characters." - (str as :string) -> base :downcase (str) -} - -fn chars { - "Takes a string and returns its characters as a list. Works only for strings with only ascii characters. Panics on any non-ascii characters." - (str as :string) -> match base :chars (str) with { - (:ok, chrs) -> chrs - (:err, msg) -> panic! msg - } -} - -fn chars/safe { - "Takes a string and returns its characters as a list, wrapped in a result tuple. Works only for strings with only ascii characters. Returns an error tuple on any non-ascii characters." - (str as :string) -> base :chars (str) -} - -fn ws? { - "Tells if a string is a whitespace character." - (" ") -> true - ("\n") -> true - ("\t") -> true - (_) -> false -} - -fn strip { - "Removes punctuation from a string, removing all instances of ,.;:?!" - ("{x},{y}") -> strip ("{x}{y}") - ("{x}.{y}") -> strip ("{x}{y}") - ("{x};{y}") -> strip ("{x}{y}") - ("{x}:{y}") -> strip ("{x}{y}") - ("{x}?{y}") -> strip ("{x}{y}") - ("{x}!{y}") -> strip ("{x}{y}") - (x) -> x -} - -fn words { - "Takes a string and returns a list of the words in the string. Strips all whitespace." - (str as :string) -> { - let no_punct = strip (str) - let strs = split (no_punct, " ") - fn worder (l, s) -> if empty? (s) - then l - else append (l, s) - fold (worder, strs, []) - } -} - -fn sentence { - "Takes a list of words and turns it into a sentence." - (strs as :list) -> join (strs, " ") -} - -fn to_number { - "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." - (num as :string) -> base :to_number (num) -} - -&&& boxes: mutable state and state changes - -fn box? { - "Returns true if a value is a box." - (b as :box) -> true - (_) -> false -} - -fn unbox { - "Returns the value that is stored in a box." - (b as :box) -> base :unbox (b) -} - -fn store! { - "Stores a value in a box, replacing the value that was previously there. Returns the value." - (b as :box, value) -> { - base :store! (b, value) - value - } -} - -fn update! { - "Updates a box by applying a function to its value. Returns the new value." - (b as :box, f as :fn) -> { - let current = unbox (b) - let new = f (current) - store! (b, new) - } -} - -&&& numbers, basically: arithmetic and not much else, yet -& TODO: add nan?, -fn number? { - "Returns true if a value is a number." - (x as :number) -> true - (_) -> false -} - -fn add { - "Adds numbers or vectors." - () -> 0 - (x as :number) -> x - (x as :number, y as :number) -> base :add (x, y) - (x, y, ...zs) -> fold (add, zs, base :add (x, y)) - & add vectors - ((x1, y1), (x2, y2)) -> (add (x1, x2), add (y1, y2)) -} - -fn sub { - "Subtracts numbers or vectors." - () -> 0 - (x as :number) -> x - (x as :number, y as :number) -> base :sub (x, y) - (x, y, ...zs) -> fold (sub, zs, base :sub (x, y)) - ((x1, y1), (x2, y2)) -> (base :sub (x1, x2), base :sub (y1, y2)) -} - -fn mult { - "Multiplies numbers or vectors." - () -> 1 - (x as :number) -> x - (x as :number, y as :number) -> base :mult (x, y) - (x, y, ...zs) -> fold (mult, zs, mult (x, y)) - (scalar as :number, (x, y)) -> (mult (x, scalar), mult (y, scalar)) - ((x, y), scalar as :number) -> mult (scalar, (x, y)) -} - -fn div { - "Divides numbers. Panics on division by zero." - (x as :number) -> x - (_, 0) -> panic! "Division by zero." - (x as :number, y as :number) -> base :div (x, y) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div (x, divisor) - } -} - -fn div/0 { - "Divides numbers. Returns 0 on division by zero." - (x as :number) -> x - (_, 0) -> 0 - (x as :number, y as :number) -> base :div (x, y) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div/0 (x, divisor) - } -} - -fn div/safe { - "Divides a number. Returns a result tuple." - (x as :number) -> (:ok, x) - (_, 0) -> (:err, "Division by zero") - (x, y) -> (:ok, div (x, y)) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div/safe (x, divisor) - } -} - -fn inv { - "Returns the inverse of a number: 1/n or `div (1, n)`. Panics on division by zero." - (x as :number) -> div (1, x) -} - -fn inv/0 { - "Returns the inverse of a number: 1/n or `div/0 (1, n)`. Returns 0 on division by zero." - (x as :number) -> div/0 (1, x) -} - -fn inv/safe { - "Returns the inverse of a number: 1/n or `div/safe (1, n)`. Returns a result tuple." - (x as :number) -> div/safe (1, x) -} - -fn abs { - "Returns the absolute value of a number." - (0) -> 0 - (n as :number) -> if neg? (n) then mult (-1, n) else n -} - -fn neg { - "Multiplies a number by -1, negating it." - (n as :number) -> mult (n, -1) -} - -fn angle { - "Calculates the angle between two vectors." - (v1, v2) -> sub (atan/2 (v2), atan/2 (v1)) -} - -fn zero? { - "Returns true if a number is 0." - (0) -> true - (_) -> false -} - -fn gt? { - "Returns true if numbers are in decreasing order." - (x as :number) -> true - (x as :number, y as :number) -> base :gt? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :gt? (a, b) - (a, [b, ...cs]) -> if base :gt? (a, b) - then recur (b, cs) - else false - } -} - -fn gte? { - "Returns true if numbers are in decreasing or flat order." - (x as :number) -> true - (x as :number, y as :number) -> base :gte? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :gte? (a, b) - (a, [b, ...cs]) -> if base :gte? (a, b) - then recur (b, cs) - else false - } -} - -fn lt? { - "Returns true if numbers are in increasing order." - (x as :number) -> true - (x as :number, y as :number) -> base :lt? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :lt? (a, b) - (a, [b, ...cs]) -> if base :lt? (a, b) - then recur (b, cs) - else false - } -} - -fn lte? { - "Returns true if numbers are in increasing or flat order." - (x as :number) -> true - (x as :number, y as :number) -> base :lte? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :lte? (a, b) - (a, [b, ...cs]) -> if base :lte? (a, b) - then recur (b, cs) - else false - } -} - -fn between? { - "Returns true if a number is in the range [lower, higher): greater than or equal to the lower number, less than the higher." - (lower as :number, higher as :number, x as :number) -> and ( - gte? (x, lower) - lt? (x, higher) - ) -} - -fn neg? { - "Returns true if a value is a negative number, otherwise returns false." - (x as :number) if lt? (x, 0) -> true - (_) -> false -} - -fn pos? { - "Returns true if a value is a positive number, otherwise returns false." - (x as :number) if gt? (x, 0) -> true - (_) -> false -} - -fn even? { - "Returns true if a value is an even number, otherwise returns false." - (x as :number) if eq? (0, mod (x, 2)) -> true - (_) -> false -} - -fn odd? { - "Returns true if a value is an odd number, otherwise returns false." - (x as :number) if eq? (1, mod (x, 2)) -> true - (_) -> false -} - -fn min { - "Returns the number in its arguments that is closest to negative infinity." - (x as :number) -> x - (x as :number, y as :number) -> if base :lt? (x, y) then x else y - (x, y, ...zs) -> fold (min, zs, min (x, y)) -} - -fn max { - "Returns the number in its arguments that is closest to positive infinity." - (x as :number) -> x - (x as :number, y as :number) -> if gt? (x, y) then x else y - (x, y, ...zs) -> fold (max, zs, max (x, y)) -} - -& additional list operations now that we have comparitors -fn at { - "Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string." - (xs as :list, n as :number) -> base :nth (n, xs) - (xs as :tuple, n as :number) -> base :nth (n, xs) - (str as :string, n as :number) -> { - let raw = base :nth (n, str) - when { - nil? (raw) -> nil - gte? (raw, 128) -> panic! "not an ASCII char" - true -> base :str_slice (str, n, inc (n)) - } - } - (_) -> nil -} - -fn first { - "Returns the first element of a list or tuple." - (xs) if ordered? -> at (xs, 0) -} - -fn second { - "Returns the second element of a list or tuple." - (xs) if ordered? (xs) -> at (xs, 1) -} - -fn last { - "Returns the last element of a list or tuple." - (xs) if ordered? (xs) -> at (xs, dec (count (xs))) -} - -fn butlast { - "Returns a list, omitting the last element." - (xs as :list) -> base :slice (xs, 0, dec (count (xs))) -} - -fn slice { - "Returns a slice of a list or a string, representing a sub-list or sub-string." - (xs as :list, end as :number) -> slice (xs, 0, end) - (xs as :list, start as :number, end as :number) -> when { - gte? (start, end) -> [] - gt? (end, count (xs)) -> slice (xs, start, count (xs)) - neg? (start) -> slice (xs, 0, end) - true -> base :slice (xs, start, end) - } - (str as :string, end as :number) -> base :str_slice (str, 0, end) - (str as :string, start as :number, end as :number) -> base :str_slice (str, start, end) -} - -&&& keywords: funny names -fn keyword? { - "Returns true if a value is a keyword, otherwise returns false." - (kw as :keyword) -> true - (_) -> false -} - -& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc. - -& & TODO: make `and` and `or` special forms which lazily evaluate arguments -& fn and { -& "Returns true if all values passed in are truthy. Note that this does not short-circuit: all arguments are evaulated before they are passed in." -& () -> true -& (x) -> bool (x) -& (x, y) -> base :and (x, y) -& (x, y, ...zs) -> fold (and, zs, base :and (x, y)) -& } - -& fn or { -& "Returns true if any value passed in is truthy. Note that this does not short-circuit: all arguments are evaluated before they are passed in." -& () -> true -& (x) -> bool (x) -& (x, y) -> base :or (x, y) -& (x, y, ...zs) -> fold (or, zs, base :or (x, y)) -& } - -fn assoc { - "Takes a dict, key, and value, and returns a new dict with the key set to value." - () -> #{} - (d as :dict) -> d - (d as :dict, k as :keyword, val) -> base :assoc (d, k, val) - (d as :dict, (k as :keyword, val)) -> base :assoc (d, k, val) -} - -fn dissoc { - "Takes a dict and a key, and returns a new dict with the key and associated value omitted." - (d as :dict) -> d - (d as :dict, k as :keyword) -> base :dissoc (d, k) -} - -fn update { - "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." - (d as :dict) -> d - (d as :dict, k as :keyword, updater as :fn) -> base :assoc (d, k, updater (get (k, d))) -} - -fn keys { - "Takes a dict and returns a list of keys in that dict." - (d as :dict) -> do d > list > map (first, _) -} - -fn values { - "Takes a dict and returns a list of values in that dict." - (d as :dict) -> do d > list > map (second, _) -} - -& fn diff { -& "Takes two dicts and returns a dict describing their differences. Does this shallowly, offering diffs only for keys in the original dict." -& (d1 as :dict, d2 as :dict) -> { -& let key1 = keys (d1) -& let key2 = keys (d2) -& let all = do concat (d1, d2) > set > list -& let diffs = loop (all, []) with { -& & TODO: reduce this redundancy? -& ([k, ...ks], diffs) -> { -& let v1 = get (k, d1) -& let v2 = get (k, d2) -& if eq? (v1, v2) -& then recur (ks, diffs) -& else recur (ks, append (diffs, (k, (v1, v2)))) -& } -& ([k], diffs) -> { -& let v1 = get (k, d1) -& let v2 = get (k, d2) -& if eq? (v1, v2) -& then diffs -& else append (diffs, (k, (v1, v2))) -& } -& } -& dict (diffs) -& } -& } - -& TODO: consider merging `get` and `at` -fn get { - "Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value." - (k as :keyword) -> get (k, _) - (k as :keyword, d as :dict) -> get (k, d, nil) - (k as :keyword, d as :dict, default) -> base :get (k, d, default) -} - -& TODO: add sets to this? -fn has? { - "Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key." - (k as :keyword) -> has? (k, _) - (k as :keyword, d as :dict) -> do d> k > nil? -} - -fn dict { - "Takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed." - (d as :dict) -> d - (l as :list) -> fold (assoc, l) - (t as :tuple) -> do t > list > dict -} - -fn dict? { - "Returns true if a value is a dict." - (d as :dict) -> true - (_) -> false -} - -fn each! { - "Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." - (f! as :fn, []) -> nil - (f! as :fn, [x]) -> { f! (x); nil } - (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } -} - -&&& Trigonometry functions - -& Ludus uses turns as its default unit to measure angles -& However, anything that takes an angle can also take a -& units argument, that's a keyword of :turns, :degrees, or :radians - -let pi = base :pi - -let tau = mult (2, pi) - -fn sin { - "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :sin - (a as :number, :turns) -> do a > turn/rad > base :sin - (a as :number, :degrees) -> do a > deg/rad > base :sin - (a as :number, :radians) -> base :sin (a) -} - -fn cos { - "Returns the cosine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :cos - (a as :number, :turns) -> do a > turn/rad > base :cos - (a as :number, :degrees) -> do a > deg/rad > base :cos - (a as :number, :radians) -> base :cos (a) -} - -fn tan { - "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :tan - (a as :number, :turns) -> do a > turn/rad > base :tan - (a as :number, :degrees) -> do a > deg/rad > base :tan - (a as :number, :radians) -> base :tan (a) -} - -fn rotate { - "Rotates a vector by an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - ((x, y), a) -> rotate ((x, y), a, :turns) - ((x, y), a, units as :keyword) -> ( - sub (mult (x, cos (a, units)), mult (y, sin (a, units))) - add (mult (x, sin (a, units)), mult (y, cos (a, units))) - ) -} - -fn turn/deg { - "Converts an angle in turns to an angle in degrees." - (a as :number) -> mult (a, 360) -} - -fn deg/turn { - "Converts an angle in degrees to an angle in turns." - (a as :number) -> div (a, 360) -} - -fn turn/rad { - "Converts an angle in turns to an angle in radians." - (a as :number) -> mult (a, tau) -} - -fn rad/turn { - "Converts an angle in radians to an angle in turns." - (a as :number) -> div (a, tau) -} - -fn deg/rad { - "Converts an angle in degrees to an angle in radians." - (a as :number) -> mult (tau, div (a, 360)) -} - -fn rad/deg { - "Converts an angle in radians to an angle in degrees." - (a as :number) -> mult (360, div (a, tau)) -} - -fn atan/2 { - "Returns an angle from a slope. Takes an optional keyword argument to specify units. Takes either two numbers or a vector tuple." - (x as :number, y as :number) -> do base :atan_2 (x, y) > rad/turn - (x, y, :turns) -> atan/2 (x, y) - (x, y, :radians) -> base :atan_2 (x, y) - (x, y, :degrees) -> do base :atan_2 (x, y) > rad/deg - ((x, y)) -> atan/2 (x, y) - ((x, y), units as :keyword) -> atan/2 (x, y, units) -} - -fn mod { - "Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0." - (x as :number, 0) -> panic! "Division by zero." - (x as :number, y as :number) -> base :mod (x, y) -} - -fn mod/0 { - "Returns the modulus of x and y. Truncates towards negative infinity. Returns 0 if y is 0." - (x as :number, 0) -> 0 - (x as :number, y as :number) -> base :mod (x, y) -} - -fn mod/safe { - "Returns the modulus of x and y in a result tuple, or an error if y is 0. Truncates towards negative infinity." - (x as :number, 0) -> (:err, "Division by zero.") - (x as :number, y as :number) -> (:ok, base :mod (x, y)) -} - -fn square { - "Squares a number." - (x as :number) -> mult (x, x) -} - -fn sqrt { - "Returns the square root of a number. Panics if the number is negative." - (x as :number) if not (neg? (x)) -> base :sqrt (x) -} - -fn sqrt/safe { - "Returns a result containing the square root of a number, or an error if the number is negative." - (x as :number) -> if not (neg? (x)) - then (:ok, base :sqrt (x)) - else (:err, "sqrt of negative number") -} - -fn sum_of_squares { - "Returns the sum of squares of numbers." - () -> 0 - (x as :number) -> square (x) - (x as :number, y as :number) -> add (square (x), square (y)) - (x, y, ...zs) -> fold ( - fn (sum, z) -> add (sum, square (z)) - zs - sum_of_squares (x, y)) -} - -fn dist { - "Returns the distance from the origin to a point described by x and y, or by the vector (x, y)." - (x as :number, y as :number) -> sqrt (sum_of_squares (x, y)) - ((x, y)) -> dist (x, y) -} - -fn heading/vector { - "Takes a turtle heading, and returns a unit vector of that heading." - (heading) -> { - & 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º/0.75T, 0.75 is 0º/0T - let a = add (neg (heading), 0.25) - (cos (a), sin (a)) - } -} - -&&& more number functions -fn random { - "Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection." - () -> base :random () - (n as :number) -> mult (n, random ()) - (m as :number, n as :number) -> add (m, random (sub (n, m))) - (l as :list) -> { - let i = do l > count > random > floor - at (l, i) - } - (t as :tuple) -> { - let i = do t > count > random > floor - at (t, i) - } - (d as :dict) -> { - let key = do d > keys > random - get (key, d) - } - & (s as :set) -> do s > list > random -} - -fn random_int { - "Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them." - (n as :number) -> do n > random > floor - (m as :number, n as :number) -> floor (random (m, n)) -} - -fn floor { - "Truncates a number towards negative infinity. With positive numbers, it returns the integer part. With negative numbers, returns the next more-negative integer." - (n as :number) -> base :floor (n) -} - -fn ceil { - "Truncates a number towards positive infinity. With negative numbers, it returns the integer part. With positive numbers, returns the next more-positive integer." - (n as :number) -> base :ceil (n) -} - -fn round { - "Rounds a number to the nearest integer." - (n as :number) -> base :round (n) -} - -fn range { - "Returns the set of integers between start (inclusive) and end (exclusive) as a list: [start, end). With one argument, starts at 0. If end is less than start, returns an empty list." - (end as :number) -> base :range (0, end) - (start as :number, end as :number) -> base :range (start, end) -} - -&&& Results, errors and other unhappy values - -fn ok { - "Takes a value and wraps it in an :ok result tuple." - (value) -> (:ok, value) -} - -fn ok? { - "Takes a value and returns true if it is an :ok result tuple." - ((:ok, _)) -> true - (_) -> false -} - -fn err { - "Takes a value and wraps it in an :err result tuple, presumably as an error message." - (msg) -> (:err, msg) -} - -fn err? { - "Takes a value and returns true if it is an :err result tuple." - ((:err, _)) -> true - (_) -> false -} - -fn unwrap! { - "Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics." - ((:ok, value)) -> value - ((:err, msg)) -> panic! string ("Unwrapped :err! ", msg) - (_) -> panic! "Cannot unwrap something that's not an error tuple." -} - -fn unwrap_or { - "Takes a value that is a result tuple and a default value. If it's :ok, then it returns the value. If it's :err, returns the default value." - ((:ok, value), _) -> value - ((:err, _), default) -> default -} - -fn assert! { - "Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message." - (value) -> if value - then value - else panic! "Assert failed: {value}" - (msg, value) -> if value - then value - else panic! "Assert failed: {msg} with {value}" -} - -&&& Turtle & other graphics - -& some basic colors -& these are the "basic" css colors -& https://developer.mozilla.org/en-US/docs/Web/CSS/named-color -let colors = #{ - :black (0, 0, 0, 255) - :silver (192, 192, 192, 255) - :gray (128, 128, 128, 255) - :white (255, 255, 255, 255) - :maroon (128, 0, 0, 255) - :red (255, 0, 0, 255) - :purple (128, 0, 128, 255) - :fuchsia (255, 0, 255, 255) - :green (0, 128, 0, 255) - :lime (0, 255, 0, 255) - :olive (128, 128, 0, 255) - :yellow (255, 255, 0, 255) - :navy (0, 0, 128, 255) - :blue (0, 0, 255, 255) - :teal (0, 128, 128, 255) - :aqua (0, 255, 25, 255) -} - -& the initial turtle state -let turtle_init = #{ - :position (0, 0) & let's call this the origin for now - :heading 0 & this is straight up - :pendown? true - :pencolor :white - :penwidth 1 - :visible? true -} - -& turtle states: refs that get modified by calls -& turtle_commands is a list of commands, expressed as tuples -box turtle_commands = [] -box turtle_state = turtle_init - -fn add_command! (command) -> { - update! (turtle_commands, append! (_, command)) - let prev = unbox (turtle_state) - let curr = apply_command (prev, command) - store! (turtle_state, curr) - :ok -} - -fn forward! { - "Moves the turtle forward by a number of steps. Alias: fd!" - (steps as :number) -> add_command! ((:forward, steps)) -} - -let fd! = forward! - -fn back! { - "Moves the turtle backward by a number of steps. Alias: bk!" - (steps as :number) -> add_command! ((:back, steps)) -} - -let bk! = back! - -fn left! { - "Rotates the turtle left, measured in turns. Alias: lt!" - (turns as :number) -> add_command! ((:left, turns)) -} - -let lt! = left! - -fn right! { - "Rotates the turtle right, measured in turns. Alias: rt!" - (turns as :number) -> add_command! ((:right, turns)) -} - -let rt! = right! - -fn penup! { - "Lifts the turtle's pen, stopping it from drawing. Alias: pu!" - () -> add_command! ((:penup)) -} - -let pu! = penup! - -fn pendown! { - "Lowers the turtle's pen, causing it to draw. Alias: pd!" - () -> add_command! ((:pendown)) -} - -let pd! = pendown! - -fn pencolor! { - "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!" - (color as :keyword) -> add_command! ((:pencolor, color)) - (gray as :number) -> add_command! ((:pencolor, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:pencolor, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:pencolor, (r, g, b, a))) -} - -let pc! = pencolor! - -fn penwidth! { - "Sets the width of the turtle's pen, measured in pixels. Alias: pw!" - (width as :number) -> add_command! ((:penwidth, width)) -} - -let pw! = penwidth! - -fn background! { - "Sets the background color behind the turtle and path. Alias: bg!" - (color as :keyword) -> add_command! ((:background, color)) - (gray as :number) -> add_command! ((:background, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:background, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, (r, g, b, a))) -} - -let bg! = background! - -fn home! { - "Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home." - () -> add_command! ((:home)) -} - -fn clear! { - "Clears the canvas and sends the turtle home." - () -> add_command! ((:clear)) -} - -fn goto! { - "Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location." - (x as :number, y as :number) -> add_command! ((:goto, (x, y))) - ((x, y)) -> goto! (x, y) -} - -fn setheading! { - "Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise." - (heading as :number) -> add_command! ((:setheading, heading)) -} - -fn showturtle! { - "If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing." - () -> add_command! ((:show)) -} - -fn hideturtle! { - "If the turtle is visible, hides it. If the turtle is already hidden, does nothing." - () -> add_command! ((:hide)) -} - -fn loadstate! { - "Sets the turtle state to a previously saved state." - (state) -> { - let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! ((:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) - } -} - -fn apply_command { - "Takes a turtle state and a command and calculates a new state." - (state, command) -> match command with { - (:goto, (x, y)) -> assoc (state, :position, (x, y)) - (:home) -> do state > - assoc (_, :position, (0, 0)) > - assoc (_, :heading, 0) - (:clear) -> do state > - assoc (state, :position, (0, 0)) > - assoc (_, :heading, 0) - (:right, turns) -> update (state, :heading, add (_, turns)) - (:left, turns) -> update (state, :heading, sub (_, turns)) - (:forward, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, add (vect, _)) - } - (:back, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, sub (_, vect)) - } - (:penup) -> assoc (state, :pendown?, false) - (:pendown) -> assoc (state, :pendown?, true) - (:penwidth, pixels) -> assoc (state, :penwidth, pixels) - (:pencolor, color) -> assoc (state, :pencolor, color) - (:setheading, heading) -> assoc (state, :heading, heading) - (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} - (:show) -> assoc (state, :visible?, true) - (:hide) -> assoc (state, :visible?, false) - (:background, _) -> state - } -} - -& position () -> (x, y) -fn position { - "Returns the turtle's current position." - () -> do turtle_state > unbox > :position -} - -fn heading { - "Returns the turtle's current heading." - () -> do turtle_state > unbox > :heading -} - -fn pendown? { - "Returns the turtle's pen state: true if the pen is down." - () -> do turtle_state > unbox > :pendown? -} - -fn pencolor { - "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword." - () -> do turtle_state > unbox > :pencolor -} - -fn penwidth { - "Returns the turtle's pen width in pixels." - () -> do turtle_state > unbox > :penwidth -} - -box state = nil - -#{ - abs & math - add & math - & and & bool - angle & math - any? & dicts lists strings sets tuples - append & lists sets - assert! & errors - assoc & dicts - assoc? & dicts - at & lists strings - atan/2 & math - back! & turtles - background! & turtles - between? & math - bg! & turtles - bk! & turtles - bool & bool - bool? & bool - box? & boxes - butlast & lists strings tuples - ceil & math - chars & strings - clear! & turtles - coll? & dicts lists sets tuples - colors & turtles - concat & string list set - contains? & list set - cos & math - count & string list set tuple dict - dec & math - deg/rad & math - deg/turn & math - dict & dict - dict? & dict - & diff & dict - dissoc & dict - dist & math - div & math - div/0 & math - div/safe & math - doc! & env - downcase & string - each! & list - empty? & list dict set string tuple - eq? & values - err & result - err? & result - even? & math - false? & bool - fd! & turtles - filter & list - first & list tuple - floor & math - fn? & functions - fold & lists - forward! & turtles - get & dicts - goto! & turtles - gt? & math - gte? & math - heading & turtles - heading/vector & math - hideturtle! & turtles - home! & turtles - inc & math - inv & math - inv/0 & math - inv/safe & math - join & lists strings - keep & lists - keys & dicts - keyword? & keywords - last & lists tuples - left! & turtles - list & lists - list? & lists - loadstate! & turtles - lt! & turtles - lt? & math - lte? & math - map & lists - max & math - min & math - mod & math - mod/0 - mod/safe - mult & math - neg & math - neg? & math - neq? & values - nil? & nil - not & bool - odd? & math - ok & results - ok? & results - & omit & set - & or & bool - ordered? & lists tuples strings - pc! & turtles - pd! & turtles - pencolor & turtles - pencolor! & turtles - pendown! & turtles - pendown? & turtles - penup! & turtles - penwidth & turtles - penwidth! & turtles - pi & math - pos? & math - position & turtles - print! & environment - pu! & turtles - pw! & turtles - rad/deg & math - rad/turn & math - random & math dicts lists tuples sets - random_int & math - range & math lists - report! & environment - rest & lists tuples - right! & turtles - round & math - rt! & turtles - second & lists tuples - sentence & lists strings - & set & sets - & set? & sets - setheading! & turtles - show & strings - showturtle! & turtles - sin & math - slice & lists tuples strings - some & values - some? & values - split & strings - sqrt & math - sqrt/safe & math - square & math - state & environment - store! & boxes - string & strings - string? & strings - strip & strings - sub & math - sum_of_squares & math - tan & math - tau & math - to_number & strings numbers - trim & strings - tuple? & tuples - turn/deg & math - turn/rad & math - turtle_commands & turtles - turtle_init & turtles - turtle_state & turtles - type & values - unbox & boxes - unwrap! & results - unwrap_or & results - upcase & strings - update & dicts - update! & boxes - values & dicts - words & strings lists - ws? & strings - zero? & math -} diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld deleted file mode 100644 index dfcb9f3..0000000 --- a/assets/test_prelude.ld +++ /dev/null @@ -1,1380 +0,0 @@ -& the very base: know something's type -fn type { - "Returns a keyword representing the type of the value passed in." - (x) -> base :type (x) -} - -& & some helper type functions -fn coll? { - "Returns true if a value is a collection: dict, list, tuple, or set." - (coll as :dict) -> true - (coll as :list) -> true - (coll as :tuple) -> true - & (coll as :set) -> true - (_) -> false -} - -fn ordered? { - "Returns true if a value is an indexed collection: list or tuple." - (coll as :list) -> true - (coll as :tuple) -> true - (coll as :string) -> true - (_) -> false -} - -fn assoc? { - "Returns true if a value is an associative collection: a dict or a pkg." - (d as :dict) -> true - (_) -> false -} - -& &&& nil: working with nothing - -fn nil? { - "Returns true if a value is nil." - (nil) -> true - (_) -> false -} - -fn some? { - "Returns true if a value is not nil." - (nil) -> false - (_) -> true -} - -fn some { - "Takes a possibly nil value and a default value. Returns the value if it's not nil, returns the default if it's nil." - (nil, default) -> default - (value, _) -> value -} - -& ...and if two things are the same -fn eq? { - "Returns true if all arguments have the same value." - (x) -> true - (x, y) -> base :eq? (x, y) - (x, y, ...zs) -> if eq? (x, y) - then loop (y, zs) with { - (a, [b]) -> and (eq? (a, x), eq? (b, x)) - (a, [b, ...cs]) -> if eq? (a, x) - then recur (b, cs) - else false - } - else false -} - -& &&& true & false: boolean logic (part the first) -fn bool? { - "Returns true if a value is of type :boolean." - (false) -> true - (true) -> true - (_) -> false -} - -fn true? { - "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else." - (true) -> true - (_) -> false -} - -fn false? { - "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`." - (false) -> true - (_) -> false -} - -fn bool { - "Returns false if a value is nil or false, otherwise returns true." - (nil) -> false - (false) -> false - (_) -> true -} - -fn not { - "Returns false if a value is truthy, true if a value is falsy." - (nil) -> true - (false) -> true - (_) -> false -} - -& & tuples: not a lot you can do with them functionally -fn tuple? { - "Returns true if a value is a tuple." - (tuple as :tuple) -> true - (_) -> false -} - -& &&& functions: getting things done -fn fn? { - "Returns true if an argument is a function." - (f as :fn) -> true - (_) -> false -} - -& what we need for some very basic list manipulation -fn first { - "Retrieves the first element of an ordered collection--a tuple or a list. If the collection is empty, returns nil." - ([]) -> nil - (()) -> nil - & ("") -> nil - (xs as :list) -> base :first (xs) - (xs as :tuple) -> base :first (xs) - & (str as :string) -> base :slice (str, 0, 1) -} - -fn rest { - "Returns all but the first element of a list or tuple, as a list." - ([]) -> [] - (()) -> () - (xs as :list) -> base :rest (xs) - (xs as :tuple) -> base :rest (xs) - & (str as :string) -> base :rest (str) -} - -fn inc { - "Increments a number." - (x as :number) -> base :inc (x) -} - -fn dec { - "Decrements a number." - (x as :number) -> base :dec (x) -} - -fn count { - "Returns the number of elements in a collection (including string)." - (xs as :list) -> base :count (xs) - (xs as :tuple) -> base :count (xs) - (xs as :dict) -> base :count (xs) - (xs as :string) -> base :count (xs) - & (xs as :set) -> base :count (xs) -} - -fn empty? { - "Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)." - ([]) -> true - (#{}) -> true - & (s as :set) -> eq? (s, ${}) - (()) -> true - ("") -> true - (_) -> false -} - -fn any? { - "Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers)." - ([...]) -> true - (#{...}) -> true - & (s as :set) -> not (empty? (s)) - ((...)) -> true - (s as :string) -> not (empty? (s)) - (_) -> false -} - -fn list? { - "Returns true if the value is a list." - (l as :list) -> true - (_) -> false -} - -fn list { - "Takes a value and returns it as a list. For values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order: sets and dicts don't have predictable or stable ordering in output. Dicts return lists of (key, value) tuples." - (x) -> base :list (x) -} - -fn append { - "Adds an element to a list." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :append (xs, x) -} - -fn fold { - "Folds a list." - (f as :fn, []) -> [] - (f as :fn, xs as :list) -> fold (f, xs, f ()) - (f as :fn, [], root) -> [] - (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { - (prev, curr, []) -> f (prev, curr) - (prev, curr, remaining) -> recur ( - f (prev, curr) - first (remaining) - rest (remaining) - ) - } -} - -fn foldr { - "Folds a list, right-associatively." - (f as :fn, []) -> [] - (f as :fn, xs as :list) -> foldr(f, xs, f ()) - (f as :fn, [], root) -> [] - (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { - (prev, curr, []) -> f (curr, prev) - (prev, curr, remaining) -> recur ( - f (curr, prev) - first (remaining) - rest (remaining) - ) - } -} - -fn map { - "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away." - (f as :fn) -> map (f, _) - (kw as :keyword) -> map (kw, _) - (f as :fn, xs) -> { - fn mapper (prev, curr) -> append (prev, f (curr)) - fold (mapper, xs, []) - } - (kw as :keyword, xs) -> { - fn mapper (prev, curr) -> append (prev, kw (curr)) - fold (mapper, xs, []) - } -} - -fn filter { - "Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`." - (p? as :fn) -> filter (p?, _) - (p? as :fn, xs) -> { - fn filterer (filtered, x) -> if p? (x) - then append (filtered, x) - else filtered - fold (filterer, xs, []) - } -} - -fn keep { - "Takes a list and returns a new list with any `nil` values omitted." - (xs) -> filter (some?, xs) -} - -fn concat { - "Combines two lists, strings, or sets." - (x as :string, y as :string) -> "{x}{y}" - (xs as :list, ys as :list) -> base :concat (xs, ys) - & (xs as :set, ys as :set) -> base :concat (xs, ys) - (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) -} - -fn contains? { - "Returns true if a set or list contains a value." - & (value, s as :set) -> bool (base :get (s, value)) - (value, l as :list) -> loop (l) with { - ([]) -> false - ([...xs]) -> if eq? (first(xs), value) - then true - else recur (rest (xs)) - } -} - -&&& boxes: mutable state and state changes -fn box? { - "Returns true if a value is a box." - (b as :box) -> true - (_) -> false -} - -fn unbox { - "Returns the value that is stored in a box." - (b as :box) -> base :unbox (b) -} - -fn store! { - "Stores a value in a box, replacing the value that was previously there. Returns the value." - (b as :box, value) -> { - base :store! (b, value) - value - } -} - -fn update! { - "Updates a box by applying a function to its value. Returns the new value." - (b as :box, f as :fn) -> { - let current = unbox (b) - let new = f (current) - store! (b, new) - } -} - -&&& strings: harder than they look! -fn string? { - "Returns true if a value is a string." - (x as :string) -> true - (_) -> false -} - -fn show { - "Returns a text representation of a Ludus value as a string." - (x) -> base :show (x) -} - -fn string { - "Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of different kinds of values." - (x as :string) -> x - (x) -> show (x) - (x, ...xs) -> loop (string (x), xs) with { - (out, [y]) -> concat (out, show (y)) - (out, [y, ...ys]) -> recur (concat (out, show (y)), ys) - } -} - -fn join { - "Takes a list of strings, and joins them into a single string, interposing an optional separator." - ([]) -> "" - ([str as :string]) -> str - (strs as :list) -> join (strs, "") - ([], separator as :string) -> "" - ([str as :string], separator as :string) -> str - ([str, ...strs], separator as :string) -> fold ( - fn (joined, to_join) -> concat (joined, separator, to_join) - strs - str - ) -} - -fn split { - "Takes a string, and turns it into a list of strings, breaking on the separator." - (str as :string, splitter as :string) -> base :split (str, splitter) -} - -fn trim { - "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." - (str as :string) -> base :trim (str) - (str as :string, :left) -> base :triml (str) - (str as :string, :right) -> base :trimr (str) -} - -fn upcase { - "Takes a string and returns it in all uppercase. Works only for ascii characters." - (str as :string) -> base :upcase (str) -} - -fn downcase { - "Takes a string and returns it in all lowercase. Works only for ascii characters." - (str as :string) -> base :downcase (str) -} - -fn chars { - "Takes a string and returns its characters as a list. Works only for strings with only ascii characters. Panics on any non-ascii characters." - (str as :string) -> match base :chars (str) with { - (:ok, chrs) -> chrs - (:err, msg) -> panic! msg - } -} - -fn chars/safe { - "Takes a string and returns its characters as a list, wrapped in a result tuple. Works only for strings with only ascii characters. Returns an error tuple on any non-ascii characters." - (str as :string) -> base :chars (str) -} - -fn ws? { - "Tells if a string is a whitespace character." - (" ") -> true - ("\n") -> true - ("\t") -> true - (_) -> false -} - -fn strip { - "Removes punctuation from a string, removing all instances of ,.;:?!" - ("{x},{y}") -> strip ("{x}{y}") - ("{x}.{y}") -> strip ("{x}{y}") - ("{x};{y}") -> strip ("{x}{y}") - ("{x}:{y}") -> strip ("{x}{y}") - ("{x}?{y}") -> strip ("{x}{y}") - ("{x}!{y}") -> strip ("{x}{y}") - (x) -> x -} - -fn words { - "Takes a string and returns a list of the words in the string. Strips all whitespace." - (str as :string) -> { - let no_punct = strip (str) - let strs = split (no_punct, " ") - fn worder (l, s) -> if empty? (s) - then l - else append (l, s) - fold (worder, strs, []) - } -} - -fn sentence { - "Takes a list of words and turns it into a sentence." - (strs as :list) -> join (strs, " ") -} - -fn to_number { - "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." - (num as :string) -> base :number (num) -} - -box console = [] - -fn print! { - "Sends a text representation of Ludus values to the console." - (...args) -> { - let line = do args > map (string, _) > join (_, " ") - update! (console, append (_, line)) - :ok - } -} - -fn report! { - "Prints a value, then returns it." - (x) -> { - print! (x) - x - } - (msg as :string, x) -> { - print! (concat ("{msg} ", show (x))) - x - } -} - -fn doc! { - "Prints the documentation of a function to the console." - (f as :fn) -> do f > base :doc! > print! - (_) -> :none -} - -&&& numbers, basically: arithmetic and not much else, yet -& TODO: add nan?, -fn number? { - "Returns true if a value is a number." - (x as :number) -> true - (_) -> false -} - -fn add { - "Adds numbers or vectors." - () -> 0 - (x as :number) -> x - (x as :number, y as :number) -> base :add (x, y) - (x, y, ...zs) -> fold (add, zs, base :add (x, y)) - & add vectors - ((x1, y1), (x2, y2)) -> (add (x1, x2), add (y1, y2)) -} - -fn sub { - "Subtracts numbers or vectors." - () -> 0 - (x as :number) -> x - (x as :number, y as :number) -> base :sub (x, y) - (x, y, ...zs) -> fold (sub, zs, base :sub (x, y)) - ((x1, y1), (x2, y2)) -> (base :sub (x1, x2), base :sub (y1, y2)) -} - -fn mult { - "Multiplies numbers or vectors." - () -> 1 - (x as :number) -> x - (x as :number, y as :number) -> base :mult (x, y) - (x, y, ...zs) -> fold (mult, zs, mult (x, y)) - (scalar as :number, (x, y)) -> (mult (x, scalar), mult (y, scalar)) - ((x, y), scalar as :number) -> mult (scalar, (x, y)) -} - -fn div { - "Divides numbers. Panics on division by zero." - (x as :number) -> x - (_, 0) -> panic! "Division by zero." - (x as :number, y as :number) -> base :div (x, y) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div (x, divisor) - } -} - -fn div/0 { - "Divides numbers. Returns 0 on division by zero." - (x as :number) -> x - (_, 0) -> 0 - (x as :number, y as :number) -> base :div (x, y) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div/0 (x, divisor) - } -} - -fn div/safe { - "Divides a number. Returns a result tuple." - (x as :number) -> (:ok, x) - (_, 0) -> (:err, "Division by zero") - (x, y) -> (:ok, div (x, y)) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div/safe (x, divisor) - } -} - -fn inv { - "Returns the inverse of a number: 1/n or `div (1, n)`. Panics on division by zero." - (x as :number) -> div (1, x) -} - -fn inv/0 { - "Returns the inverse of a number: 1/n or `div/0 (1, n)`. Returns 0 on division by zero." - (x as :number) -> div/0 (1, x) -} - -fn inv/safe { - "Returns the inverse of a number: 1/n or `div/safe (1, n)`. Returns a result tuple." - (x as :number) -> div/safe (1, x) -} - -fn neg { - "Multiplies a number by -1, negating it." - (n as :number) -> mult (n, -1) -} - -fn zero? { - "Returns true if a number is 0." - (0) -> true - (_) -> false -} - -fn gt? { - "Returns true if numbers are in decreasing order." - (x as :number) -> true - (x as :number, y as :number) -> base :gt? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :gt? (a, b) - (a, [b, ...cs]) -> if base :gt? (a, b) - then recur (b, cs) - else false - } -} - -fn gte? { - "Returns true if numbers are in decreasing or flat order." - (x as :number) -> true - (x as :number, y as :number) -> base :gte? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :gte? (a, b) - (a, [b, ...cs]) -> if base :gte? (a, b) - then recur (b, cs) - else false - } -} - -fn lt? { - "Returns true if numbers are in increasing order." - (x as :number) -> true - (x as :number, y as :number) -> base :lt? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :lt? (a, b) - (a, [b, ...cs]) -> if base :lt? (a, b) - then recur (b, cs) - else false - } -} - -fn lte? { - "Returns true if numbers are in increasing or flat order." - (x as :number) -> true - (x as :number, y as :number) -> base :lte? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :lte? (a, b) - (a, [b, ...cs]) -> if base :lte? (a, b) - then recur (b, cs) - else false - } -} - -fn between? { - "Returns true if a number is in the range [lower, higher): greater than or equal to the lower number, less than the higher." - (lower as :number, higher as :number, x as :number) -> and ( - gte? (x, lower) - lt? (x, higher) - ) -} - -fn neg? { - "Returns true if a value is a negative number, otherwise returns false." - (x as :number) if lt? (x, 0) -> true - (_) -> false -} - -fn pos? { - "Returns true if a value is a positive number, otherwise returns false." - (x as :number) if gt? (x, 0) -> true - (_) -> false -} - -fn abs { - "Returns the absolute value of a number." - (0) -> 0 - (n as :number) -> if neg? (n) then mult (-1, n) else n -} - -&&& Trigonometry functions - -& Ludus uses turns as its default unit to measure angles -& However, anything that takes an angle can also take a -& units argument, that's a keyword of :turns, :degrees, or :radians - -let pi = base :pi - -let tau = mult (2, pi) - -fn turn/deg { - "Converts an angle in turns to an angle in degrees." - (a as :number) -> mult (a, 360) -} - -fn deg/turn { - "Converts an angle in degrees to an angle in turns." - (a as :number) -> div (a, 360) -} - -fn turn/rad { - "Converts an angle in turns to an angle in radians." - (a as :number) -> mult (a, tau) -} - -fn rad/turn { - "Converts an angle in radians to an angle in turns." - (a as :number) -> div (a, tau) -} - -fn deg/rad { - "Converts an angle in degrees to an angle in radians." - (a as :number) -> mult (tau, div (a, 360)) -} - -fn rad/deg { - "Converts an angle in radians to an angle in degrees." - (a as :number) -> mult (360, div (a, tau)) -} - -fn sin { - "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :sin - (a as :number, :turns) -> do a > turn/rad > base :sin - (a as :number, :degrees) -> do a > deg/rad > base :sin - (a as :number, :radians) -> base :sin (a) -} - -fn cos { - "Returns the cosine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :cos - (a as :number, :turns) -> do a > turn/rad > base :cos - (a as :number, :degrees) -> do a > deg/rad > base :cos - (a as :number, :radians) -> base :cos (a) -} - -fn tan { - "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :tan - (a as :number, :turns) -> do a > turn/rad > base :tan - (a as :number, :degrees) -> do a > deg/rad > base :tan - (a as :number, :radians) -> base :tan (a) -} - -fn rotate { - "Rotates a vector by an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - ((x, y), a) -> rotate ((x, y), a, :turns) - ((x, y), a, units as :keyword) -> ( - sub (mult (x, cos (a, units)), mult (y, sin (a, units))) - add (mult (x, sin (a, units)), mult (y, cos (a, units))) - ) -} - - -fn atan/2 { - "Returns an angle from a slope. Takes an optional keyword argument to specify units. Takes either two numbers or a vector tuple." - (x as :number, y as :number) -> do base :atan_2 (x, y) > rad/turn - (x, y, :turns) -> atan/2 (x, y) - (x, y, :radians) -> base :atan_2 (x, y) - (x, y, :degrees) -> do base :atan_2 (x, y) > rad/deg - ((x, y)) -> atan/2 (x, y) - ((x, y), units as :keyword) -> atan/2 (x, y, units) -} - -fn angle { - "Calculates the angle between two vectors." - (v1, v2) -> sub (atan/2 (v2), atan/2 (v1)) -} - -fn mod { - "Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0." - (x as :number, 0) -> panic! "Division by zero." - (x as :number, y as :number) -> base :mod (x, y) -} - -fn mod/0 { - "Returns the modulus of x and y. Truncates towards negative infinity. Returns 0 if y is 0." - (x as :number, 0) -> 0 - (x as :number, y as :number) -> base :mod (x, y) -} - -fn mod/safe { - "Returns the modulus of x and y in a result tuple, or an error if y is 0. Truncates towards negative infinity." - (x as :number, 0) -> (:err, "Division by zero.") - (x as :number, y as :number) -> (:ok, base :mod (x, y)) -} - -fn even? { - "Returns true if a value is an even number, otherwise returns false." - (x as :number) if eq? (0, mod (x, 2)) -> true - (_) -> false -} - -fn odd? { - "Returns true if a value is an odd number, otherwise returns false." - (x as :number) if eq? (1, mod (x, 2)) -> true - (_) -> false -} - -fn square { - "Squares a number." - (x as :number) -> mult (x, x) -} - -fn sqrt { - "Returns the square root of a number. Panics if the number is negative." - (x as :number) if not (neg? (x)) -> base :sqrt (x) -} - -fn sqrt/safe { - "Returns a result containing the square root of a number, or an error if the number is negative." - (x as :number) -> if not (neg? (x)) - then (:ok, base :sqrt (x)) - else (:err, "sqrt of negative number") -} - -fn sum_of_squares { - "Returns the sum of squares of numbers." - () -> 0 - (x as :number) -> square (x) - (x as :number, y as :number) -> add (square (x), square (y)) - (x, y, ...zs) -> fold ( - fn (sum, z) -> add (sum, square (z)) - zs - sum_of_squares (x, y)) -} - -fn dist { - "Returns the distance from the origin to a point described by x and y, or by the vector (x, y)." - (x as :number, y as :number) -> sqrt (sum_of_squares (x, y)) - ((x, y)) -> dist (x, y) -} - -fn heading/vector { - "Takes a turtle heading, and returns a unit vector of that heading." - (heading) -> { - & 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º/0.75T, 0.75 is 0º/0T - let a = add (neg (heading), 0.25) - (cos (a), sin (a)) - } -} - -fn floor { - "Truncates a number towards negative infinity. With positive numbers, it returns the integer part. With negative numbers, returns the next more-negative integer." - (n as :number) -> base :floor (n) -} - -fn ceil { - "Truncates a number towards positive infinity. With negative numbers, it returns the integer part. With positive numbers, returns the next more-positive integer." - (n as :number) -> base :ceil (n) -} - -fn round { - "Rounds a number to the nearest integer." - (n as :number) -> base :round (n) -} - -fn range { - "Returns the set of integers between start (inclusive) and end (exclusive) as a list: [start, end). With one argument, starts at 0. If end is less than start, returns an empty list." - (end as :number) -> base :range (0, end) - (start as :number, end as :number) -> base :range (start, end) -} - -& additional list operations now that we have comparitors -fn at { - "Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string." - (xs as :list, n as :number) -> base :at (xs, n) - (xs as :tuple, n as :number) -> base :at (xs, n) - & (str as :string, n as :number) -> { - & let raw = base :at (str, n) - & when { - & nil? (raw) -> nil - & gte? (raw, 128) -> panic! "not an ASCII char" - & true -> base :str_slice (str, n, inc (n)) - & } - & } - (_) -> nil -} - -& fn first { -& "Returns the first element of a list or tuple." -& (xs) if ordered? -> at (xs, 0) -& } - -fn second { - "Returns the second element of a list or tuple." - (xs) if ordered? (xs) -> at (xs, 1) -} - -fn last { - "Returns the last element of a list or tuple." - (xs) if ordered? (xs) -> at (xs, dec (count (xs))) -} - -fn slice { - "Returns a slice of a list or a string, representing a sub-list or sub-string." - (xs as :list, end as :number) -> slice (xs, 0, end) - (xs as :list, start as :number, end as :number) -> when { - gte? (start, end) -> [] - gt? (end, count (xs)) -> slice (xs, start, count (xs)) - neg? (start) -> slice (xs, 0, end) - true -> base :slice (xs, start, end) - } - (str as :string, end as :number) -> base :str_slice (str, 0, end) - (str as :string, start as :number, end as :number) -> base :str_slice (str, start, end) -} - -fn butlast { - "Returns a list, omitting the last element." - (xs as :list) -> slice (xs, 0, dec (count (xs))) -} - -&&& keywords: funny names -fn keyword? { - "Returns true if a value is a keyword, otherwise returns false." - (kw as :keyword) -> true - (_) -> false -} - -& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc. - -fn assoc { - "Takes a dict, key, and value, and returns a new dict with the key set to value." - () -> #{} - (d as :dict) -> d - (d as :dict, k as :keyword, val) -> base :assoc (d, k, val) - (d as :dict, (k as :keyword, val)) -> base :assoc (d, k, val) -} - -fn dissoc { - "Takes a dict and a key, and returns a new dict with the key and associated value omitted." - (d as :dict) -> d - (d as :dict, k as :keyword) -> base :dissoc (d, k) -} - -& TODO: consider merging `get` and `at` -fn get { - "Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value." - (k as :keyword) -> get (k, _) - (k as :keyword, d as :dict) -> base :get (d, k) - (k as :keyword, d as :dict, default) -> match base :get (d, k) with { - nil -> default - val -> val - } -} - -fn update { - "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." - (d as :dict) -> d - (d as :dict, k as :keyword, updater as :fn) -> assoc (d, k, updater (get (k, d))) -} - -fn keys { - "Takes a dict and returns a list of keys in that dict." - (d as :dict) -> do d > list > map (first, _) -} - -fn values { - "Takes a dict and returns a list of values in that dict." - (d as :dict) -> do d > list > map (second, _) -} - -& TODO: add sets to this? -fn has? { - "Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key." - (k as :keyword) -> has? (k, _) - (k as :keyword, d as :dict) -> do d > get (k) > some? -} - -fn dict { - "Takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed." - (d as :dict) -> d - (l as :list) -> fold (assoc, l) - (t as :tuple) -> do t > list > dict -} - -fn dict? { - "Returns true if a value is a dict." - (d as :dict) -> true - (_) -> false -} - -fn each! { - "Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." - (f! as :fn, []) -> nil - (f! as :fn, [x]) -> { f! (x); nil } - (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } -} - -fn random { - "Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection." - () -> base :random () - (n as :number) -> mult (n, random ()) - (m as :number, n as :number) -> add (m, random (sub (n, m))) - (l as :list) -> { - let i = do l > count > random > floor - at (l, i) - } - (t as :tuple) -> { - let i = do t > count > random > floor - at (t, i) - } - (d as :dict) -> { - let key = do d > keys > random - get (key, d) - } - & (s as :set) -> do s > list > random -} - -fn random_int { - "Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them." - (n as :number) -> do n > random > floor - (m as :number, n as :number) -> floor (random (m, n)) -} - - -&&& Results, errors and other unhappy values - -fn ok { - "Takes a value and wraps it in an :ok result tuple." - (value) -> (:ok, value) -} - -fn ok? { - "Takes a value and returns true if it is an :ok result tuple." - ((:ok, _)) -> true - (_) -> false -} - -fn err { - "Takes a value and wraps it in an :err result tuple, presumably as an error message." - (msg) -> (:err, msg) -} - -fn err? { - "Takes a value and returns true if it is an :err result tuple." - ((:err, _)) -> true - (_) -> false -} - -fn unwrap! { - "Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics." - ((:ok, value)) -> value - ((:err, msg)) -> panic! "Unwrapped :err! {msg}" -} - -fn unwrap_or { - "Takes a value that is a result tuple and a default value. If it's :ok, then it returns the value. If it's :err, returns the default value." - ((:ok, value), _) -> value - ((:err, _), default) -> default -} - -fn assert! { - "Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message." - (value) -> if value - then value - else panic! "Assert failed: {value}" - (msg, value) -> if value - then value - else panic! "Assert failed: {msg} with {value}" -} - -&&& Turtle & other graphics - -& some basic colors -& these are the "basic" css colors -& https://developer.mozilla.org/en-US/docs/Web/CSS/named-color -let colors = #{ - :black (0, 0, 0, 255) - :silver (192, 192, 192, 255) - :gray (128, 128, 128, 255) - :white (255, 255, 255, 255) - :maroon (128, 0, 0, 255) - :red (255, 0, 0, 255) - :purple (128, 0, 128, 255) - :fuchsia (255, 0, 255, 255) - :green (0, 128, 0, 255) - :lime (0, 255, 0, 255) - :olive (128, 128, 0, 255) - :yellow (255, 255, 0, 255) - :navy (0, 0, 128, 255) - :blue (0, 0, 255, 255) - :teal (0, 128, 128, 255) - :aqua (0, 255, 25, 255) -} - -& the initial turtle state -let turtle_init = #{ - :position (0, 0) & let's call this the origin for now - :heading 0 & this is straight up - :pendown? true - :pencolor :white - :penwidth 1 - :visible? true -} - -& turtle states: refs that get modified by calls -& turtle_commands is a list of commands, expressed as tuples -box turtle_commands = [] -box turtle_state = turtle_init - -fn apply_command - -fn add_command! (command) -> { - update! (turtle_commands, append (_, command)) - let prev = unbox (turtle_state) - let curr = apply_command (prev, command) - store! (turtle_state, curr) - :ok -} - -fn forward! { - "Moves the turtle forward by a number of steps. Alias: fd!" - (steps as :number) -> add_command! ((:forward, steps)) -} - -let fd! = forward! - -fn back! { - "Moves the turtle backward by a number of steps. Alias: bk!" - (steps as :number) -> add_command! ((:back, steps)) -} - -let bk! = back! - -fn left! { - "Rotates the turtle left, measured in turns. Alias: lt!" - (turns as :number) -> add_command! ((:left, turns)) -} - -let lt! = left! - -fn right! { - "Rotates the turtle right, measured in turns. Alias: rt!" - (turns as :number) -> add_command! ((:right, turns)) -} - -let rt! = right! - -fn penup! { - "Lifts the turtle's pen, stopping it from drawing. Alias: pu!" - () -> add_command! ((:penup)) -} - -let pu! = penup! - -fn pendown! { - "Lowers the turtle's pen, causing it to draw. Alias: pd!" - () -> add_command! ((:pendown)) -} - -let pd! = pendown! - -fn pencolor! { - "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!" - (color as :keyword) -> add_command! ((:pencolor, color)) - (gray as :number) -> add_command! ((:pencolor, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:pencolor, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:pencolor, (r, g, b, a))) -} - -let pc! = pencolor! - -fn penwidth! { - "Sets the width of the turtle's pen, measured in pixels. Alias: pw!" - (width as :number) -> add_command! ((:penwidth, width)) -} - -let pw! = penwidth! - -fn background! { - "Sets the background color behind the turtle and path. Alias: bg!" - (color as :keyword) -> add_command! ((:background, color)) - (gray as :number) -> add_command! ((:background, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:background, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, (r, g, b, a))) -} - -let bg! = background! - -fn home! { - "Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home." - () -> add_command! ((:home)) -} - -fn clear! { - "Clears the canvas and sends the turtle home." - () -> add_command! ((:clear)) -} - -fn goto! { - "Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location." - (x as :number, y as :number) -> add_command! ((:goto, (x, y))) - ((x, y)) -> goto! (x, y) -} - -fn setheading! { - "Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise." - (heading as :number) -> add_command! ((:setheading, heading)) -} - -fn showturtle! { - "If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing." - () -> add_command! ((:show)) -} - -fn hideturtle! { - "If the turtle is visible, hides it. If the turtle is already hidden, does nothing." - () -> add_command! ((:hide)) -} - -fn loadstate! { - "Sets the turtle state to a previously saved state." - (state) -> { - let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! ((:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) - } -} - -fn apply_command { - "Takes a turtle state and a command and calculates a new state." - (state, command) -> { - match command with { - (:goto, (x, y)) -> assoc (state, :position, (x, y)) - (:home) -> do state > - assoc (_, :position, (0, 0)) > - assoc (_, :heading, 0) - & (:clear) -> do state > - & assoc (state, :position, (0, 0)) > - & assoc (_, :heading, 0) - (:right, turns) -> update (state, :heading, add (_, turns)) - (:left, turns) -> update (state, :heading, sub (_, turns)) - (:forward, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, add (vect, _)) - } - (:back, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, sub (_, vect)) - } - (:penup) -> assoc (state, :pendown?, false) - (:pendown) -> assoc (state, :pendown?, true) - (:penwidth, pixels) -> assoc (state, :penwidth, pixels) - (:pencolor, color) -> assoc (state, :pencolor, color) - (:setheading, heading) -> assoc (state, :heading, heading) - (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} - (:show) -> assoc (state, :visible?, true) - (:hide) -> assoc (state, :visible?, false) - (:background, _) -> state - }} -} - -& position () -> (x, y) -fn position { - "Returns the turtle's current position." - () -> do turtle_state > unbox > :position -} - -fn heading { - "Returns the turtle's current heading." - () -> do turtle_state > unbox > :heading -} - -fn pendown? { - "Returns the turtle's pen state: true if the pen is down." - () -> do turtle_state > unbox > :pendown? -} - -fn pencolor { - "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword." - () -> do turtle_state > unbox > :pencolor -} - -fn penwidth { - "Returns the turtle's pen width in pixels." - () -> do turtle_state > unbox > :penwidth -} - -box state = nil - -#{ - abs - abs - add - angle - any? - append - assert! - assoc - assoc? - at - atan/2 - back! - background! - between? - bg! - bk! - bool - bool? - box? - butlast - ceil - chars - chars/safe - clear! - coll? - colors - concat - console - contains? - cos - count - dec - deg/rad - deg/turn - dict - dict? - dissoc - dist - div - div/0 - div/safe - doc! - downcase - each! - empty? - eq? - err - err? - even? - false? - fd! - filter - first - first - first - floor - fn? - fold - foldr - forward! - get - goto! - gt? - gte? - has? - heading - heading/vector - hideturtle! - home! - inc - inv - inv/0 - inv/safe - join - keep - keys - keyword? - last - left! - list - list? - loadstate! - lt! - lt? - lte? - map - mod - mod/0 - mod/safe - mult - neg - neg? - nil? - not - odd? - ok - ok? - ordered? - pc! - pd! - pencolor - pencolor! - pendown! - pendown? - penup! - penwidth - penwidth! - pi - pos? - position - print! - pu! - pw! - rad/deg - rad/turn - random - random_int - range - report! - rest - right! - rotate - round - rt! - second - sentence - setheading! - show - showturtle! - sin - slice - some - some? - split - sqrt - sqrt/safe - square - state - store! - string - string? - strip - sub - tan - tau - to_number - trim - true? - tuple? - turn/deg - turn/rad - turtle_commands - turtle_init - turtle_state - type - unbox - unwrap! - unwrap_or - upcase - update - update! - values - words - ws? - zero? -} diff --git a/bytecode_thoughts.md b/bytecode_thoughts.md deleted file mode 100644 index bb36454..0000000 --- a/bytecode_thoughts.md +++ /dev/null @@ -1,255 +0,0 @@ -# Working notes on bytecode stuff - -### 2024-12-15 -So far, I've done the easy stuff: constants, and ifs. - -There's still some easy stuff left: -* [ ] lists -* [ ] dicts -* [ ] when -* [ ] panic - -So I'll do those next. - -But then we've got two doozies: patterns and bindings, and tuples. - -#### Tuples make things hard -In fact, it's tuples that make things hard. -The idea is that, when possible, tuples should be stored on the stack. -That makes them a different creature than anything else. -But the goal is to be able, in a function call, to just push a tuple onto the stack, and then match against it. -Because a tuple _isn't_ just another `Value`, that makes things challenging. -BUT: matching against all other `Values` should be straightforward enough? - -I think that the way to do this is to reify patterns. -Rather than try to emit bytecodes to embody patterns, the patterns are some kind of data that get compiled and pushed onto a stack like keywords and interned strings and whatnot. -And then you can push a pattern onto the stack right behind a value, and then have a `match` opcode that pops them off. - -Things get a bit gnarly since patterns can be nested. I'll start with the basic cases and run from there. - -But when things get *very* gnarly is considering tuples on the stack. -How do you pop off a tuple? - -Two thoughts: -1. Just put tuples on the heap. And treat function arguments/matching differently. -2. Have a "register" that stages values to be pattern matched. - -##### Regarding the first option -I recall seeing somebody somewhere make a comment that trying to represent function arguments as tuples caused tons of pain. -I can see why that would be the case, from an implementation standpoint. -We should have _values_, and don't do fancy bookkeeping if we don't have to. - -_Conceptually_, it makes a great deal of sense to think of tuples as being deeply the same as function invocation. -But _practically_, they are different things, especially with Rust underneath. - -This feels like this cuts along the grain, and so this is what I will try. - -I suspect that I'll end up specializing a lot around function arguments and calling, but that feels more tractable than the bookkeeping around stack-based tuples. - -### 2024-12-17 -Next thoughts: take some things systematically rather than choosing an approach first. - -#### Things that always match -* Placeholder. - - I _think_ this is just a no-op. A `let` expression leaves its rhs pushed on the stack. - -* Word: put something on the stack, and bind a name. - - This should follow the logic of locals as articulated in _Crafting Interpreters_. - -In both of these cases, there's no conditional logic, simply a bind. - -#### Things that never bind -* Atomic values: put the rhs on the stack, then do an equality check, and panic if it fails. Leave the thing on the stack. - -#### Analysis -In terms of bytecode, I think one thing to do, in the simple case, is to do the following: -* `push` a `pattern` onto the stack -* `match`--pops the pattern and the value off the stack, and then applies the pattern to the value. It leaves the value on the stack, and pushes a special value onto the stack representing a match, or not. - - We'll probably want `match-1`, `match-2`, `match-3`, etc., opcodes for matching a value that's that far back in the stack. E.g., `match-1` matches against not the top element, but the `top - 1` element. - - This is _specifically_ for matching function arguments and `loop` forms. -* There are a few different things we might do from here: - - `panic_if_no_match`: panic if the last thing is a `no_match`, or just keep going if not. - - `jump_if_no_match`: in a `match` form or a function, we'll want to move to the next clause if there's no match, so jump to the next clause's `pattern` `push` code. -* Compound patterns are going to be more complex. - - I think, for example, what you're going to need to do is to get opcodes that work on our data structures, so, for example, when you have a `match_compound` opcode and you start digging into the pattern. -* Compound patterns are specifically _data structures_. So simple structures should be stack-allocated, and and complex structures should be pointers to something on the heap. Maybe? - -#### A little note -For instructions that need more than 256 possibilities, we'll need to mush two `u8`s together into a `u16`. The one liner for this is: - -```rust -let number = ((first as u16) << 8) | second as u16; -``` - -#### Oy, stacks and expressions -One thing that's giving me grief is when to pop and when to note on the value stack. - -So, like, we need to make sure that a line of code leaves the stack exactly where it was before it ran, with the exception of binding forms: `let`, `fn`, `box`, etc. Those leave one (or more!) items on the stack. - -In the simplest case, we have a line of code that's just a constant: - -``` -false -``` -This should emit the bytecode instructions (more or less): -``` -push false -pop -``` -The push comes from the `false` value. -The pop comes from the end of a (nonbinding) line. - -The problem is that there's no way (at all, in Ludus) to distinguish between an expression that's just a constant and a line that is a complete line of code that's an expression. - -So if we have the following: -``` -let foo = false -``` -We want: -``` -push false -``` -Or, rather, given that `foo` is a word pattern, what we actually want is: -``` -push false # constant -push pattern/word # load pattern -pop -pop # compare -push false # for the binding -``` - -But it's worth it here to explore Ludus's semantics. -It's the case that there are actually only three binding forms (for now): `let`, `fn`, and `box`. -Figuring out `let` will help a great deal. -Match also binds things, but at the very least, match doesn't bind with expressions on the rhs, but a single value. - -Think, too about expressions: everything comes down to a single value (of course), even tuples (especially now that I'm separating function calls from tuple values (probably)). -So: anything that *isn't* a binding form should, before the `pop` from the end of a line, only leave a single value on the stack. -Which suggests that, as odd as it is, pushing a single `nil` onto the stack, just to pop it, might make sense. -Or, perhaps the thing to do is to peek: if the line in question is binding or not, then emit different bytecode. -That's probably the thing to do. Jesus, Scott. - -And **another** thing worth internalizing: every single instruction that's not an explicit push or pop should leave the stack length unchanged. -So store and load need always to swap in a `nil` - -### 2024-12-23 -Compiling functions. - -So I'm working through the functions chapter of _CI_, and there are a few things that I'm trying to wrap my head around. - -First, I'm thinking that since we're not using raw pointers, we'll need some functional indirection to get our current byte. - -So one of the hard things here is that, unlike with Lox, Ludus doesn't have fixed-arity functions. That means that the bindings for function calls can't be as dead simple as in Lox. More to the point, because we don't know everything statically, we'll need to do some dynamic magic. - -The Bob Nystrom program uses three useful auxiliary constructs to make functions straightforward: - -* `CallFrame`s, which know which function is being called, has their own instruction pointer, and an offset for the first stack slot that can be used by the function. - -```c -typedef struct { - ObjFunction* function; - uint8_t* ip; - Value* slots; -} CallFrame; - -``` - -Or the Rust equivalent: -```rust -struct CallFrame { - function: LFn, - ip: usize, - stack_root: usize, -} -``` - -* `Closure`s, which are actual objects that live alongside functions. They have a reference to a function and to an array of "upvalues"... -* `Upvalue`s, which are ways of pointing to values _below_ the `stack_root` of the call frame. - -##### Digression: Prelude -I decided to skip the Prelude resolution in the compiler and only work with locals. But actually, closures, arguments, and the prelude are kind of the same problem: referring to values that aren't currently available on the stack. - -We do, however, know at compile time the following: -* If a binding's target is on the stack, in a closure, or in the prelude. -* This does, however, require that the function arguments work in a different way. - -The way to do this, I reckon, is this: -* Limit arguments (to, say, no more than 7). -* A `CallFrame` includes an arity field. -* It also includes an array of length 7. -* Each `match` operation in function arguments clones from the call frame, and the first instruction for any given body (i.e. once we've done the match) is to clear the arguments registers in the `CallFrame`, thus decrementing all the refcounts of all the heap-allocated objects. -* And the current strategy of scoping and popping in the current implementation of `match` will work just fine! - -Meanwhile, we don't actually need upvalues, because bindings cannot change in Ludus. So instead of upvalues and their indirection, we can just emit a bunch of instructions to have a `values` field on a closure. The compiler, meanwhile, will know how to extract and emit instructions both to emit those values *and* to offer correct offsets. - -The only part I haven't figured out quite yet is how to encode access to what's stored in a closure. - -Also, I'm not certain we need the indirection of a closure object in Ludus. The function object itself can do the work, no? - -And the compiler knows which function it's closing over, and we can emit a bunch of instructions to close stuff over easily, after compiling the function and putting it in the constants table. The way to do this is to yank the value to the top of the stack using normal name resolution procedures, and then use a two-byte operand, `Op::Close` + index of the function in the constants table. - -##### End of digression. -And, because we know exactly is bound in a given closure, we can actually emit instructions to close over a given value easily. - -#### A small optimization -The lifetimes make things complicated; but I'm not sure that I would want to actually manage them manually, given how much they make my head hurt with Rust. I do get the sense that we will, at some point, need some lifetimes. A `Chunk` right now is chunky, with lots of owned `vec`s. - -Uncle Bob separates `Chunk`s and `Compiler`s, which, yes! But then we have a problem: all of the information to climb back to source code is in the `Compiler` and not in the `Chunk`. How to manage that encoding? - -(Also the keyword and string intern tables should be global, and not only in a single compiler, since we're about to get nested compilers...) - -### 2024-12-24 -Other interesting optimizations abound: -* `add`, `sub`, `inc`, `dec`, `type`, and other extremely frequently used, simple functions can be compiled directly to built-in opcodes. We still need functions for them, with the same arities, for higher order function use. - - The special-case logic is in the `Synthetic` compiler branch, rather than anywhere else. - - It's probably best to disallow re-binding these names anywhere _except_ Prelude, where we'll want them shadowed. - - We can enforce this in `Validator` rather than `Compiler`. -* `or` and `and` are likewise built-in, but because they don't evaluate their arguments eagerly, that's another, different special case that's a series of eval, `jump_if_false`, eval, `jump_if_false`, instructions. -* More to the point, the difference between `or` and `and` here and the built-ins is that `or` and `and` are variadic, where I was originally thinking about `and` and co. as fixed-arity, with variadic behaviours defined by a shadowing/backing Ludus function. That isn't necessary, I don't think. -* Meanwhile, `and` and `or` will also, of necessity, have backing shadowing functions. - -#### More on CallFrames and arg passing -* We don't actually need the arguments register! I was complicating things. The stack between the `stack_root` and the top will be _exactly_ the same as an arguments register would have been in my imagination. So we can determine the number of arguments passed in with `stack.len() - stack_root`, and we can access argument positions with `stack_root + n`, since the first argument is at `stack_root`. - - This has the added benefit of not having to do any dances to keep the refcount of any heap-allocated objects as low as possible. No extra `Clone`s here. -* In addition, we need two `check_arity` ops: one for fixed-arity clauses, and one for clauses with splatterns. Easily enough done. Remember: opcodes are for special cases! - -#### Tail calls -* The way to implement tail calls is actually now really straightforward! The idea is to simply have a `TailCall` rather than a `Call` opcode. In place of creating a new stack frame and pushing it to the call stack on top of the old call frame, you pop the old call frame, then push the new one to the call stack. -* That does mean the `Compiler` will need to keep track of tail calls. This should be pretty straightforward, actually, and the logic is already there in `Validator`. -* The thing here is that the new stack frame simply requires the same return location as the old one it's replacing. -* That reminds me that there's an issue in terms of keeping track of not just the IP, but the chunk. In Lox, the IP is a pointer to a `u8`, which works great in C. But in Rust, we can't use a raw pointer like that, but an index into a `vec`. Which means the return location needs both a chunk and an index, not just a `u8` pointer: -```rust -struct StackFrame<'a> { - function: LFn, - stack_root: usize, - return: (&'a Chunk, usize), -} -``` -(I hate that there's a lifetime here.) - -This gives us a way to access everything we need: where to return to, the root of the stack, the chunk (function->chunk), the closures (function->closures). - -### 2024-12-26 -One particular concern here, which needs some work: recursion is challenging. - -In particular, the issue is that if, as I have been planning, a function closes over all its values at the moment it is compiled, the only value type that requires updating is a function. A function can be declared but not yet defined, and then when another function that uses that function is defined, the closed-over value will be to the declaration but not the definition. - -One way to handle this, I think is using `std::cell::OnceCell`. Rather than a `RefCell`, `OnceCell` has no runtime overhead. Instead, what happens is you effectively put a `None` in the cell. Then, once you have the value you want to put in there, you call `set` on the `OnceCell`, and it does what it needs to. - -This allows for the closures to be closed over right after compilation. - -### 2024-12-27 -Working on `loop` and `recur`, rather than `fn`--this is the gentler slope. -And discovering that we actually need a `[Value; 15]` return register. -`recur` needs to push all the arguments to the stack, then yank them off into the return register, then pop back to the loop root, then push all the things back onto the stack, then jump to the beginning of the loop. -And that also means I need a different `Value` variant that's a true `Nothing`, not even `nil`, which will _never_ end up anywhere other than a placeholder value in the register and on the stack. - -So, next steps: -1. Add `Value::Nothing` and fix all the compiler errors -2. Make the return register `[Value; 15]`, populated with `Value::Nothing`s at initialization. -3. Update `load` and `store` to work with the array rather than a single value. -4. Create `store_n` and `load_n` to work with multiple values. -5. Create a `Vm.arity` method that computes how many non-nothings were passed into the register. -6. Then, implement `recur` -7. And, then, fix up jump indexes in `loop` -8. Fix all the off-by-one errors in the jumps diff --git a/pkg/ludus.js b/ludus.js similarity index 100% rename from pkg/ludus.js rename to ludus.js diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md deleted file mode 100644 index dcf9c47..0000000 --- a/may_2025_thoughts.md +++ /dev/null @@ -1,777 +0,0 @@ -# Catching back up -## May 2025 - -### Bugs - -#### `match` is not popping things correctly -``` -=== source code === - -let foo = match :foo with { - :foo -> 1 - :bar -> 2 - :baz -> 3 -} -foo - -=== chunk: test === -IDX | CODE | INFO -0000: reset_match -0001: constant 0000: :foo -0003: match_constant 0000: :foo -0005: jump_if_no_match 0006 -0007: constant 0001: 1 -0009: store -0010: pop -0011: jump 0023 -0013: match_constant 0002: :bar -0015: jump_if_no_match 0006 -0017: constant 0003: 2 -0019: store -0020: pop -0021: jump 0013 -0023: match_constant 0004: :baz -0025: jump_if_no_match 0006 -0027: constant 0005: 3 -0029: store -0030: pop -0031: jump 0003 -0033: panic_no_match -0034: load -0035: match_word -0036: panic_if_no_match -0037: push_binding 0000 -0039: store -0040: pop -0041: pop -0042: load - - - -=== vm run: test === -0000: [] nil -0000: reset_match -0001: [] nil -0001: constant 0000: :foo -0003: [:10] nil -0003: match_constant 0000: :foo -0005: [:10] nil -0005: jump_if_no_match 0006 -0007: [:10] nil -0007: constant 0001: 1 -0009: [:10|1] nil -0009: store -0010: [:10|nil] 1 -0010: pop -0011: [:10] 1 -0011: jump 0023 -0036: [:10] 1 -0036: panic_if_no_match -0037: [:10] 1 <== Should "return" from match here -0037: push_binding 0000 -0039: [:10|:10] 1 -0039: store -0040: [:10|nil] :10 -0040: pop -0041: [:10] :10 -0041: pop -0042: [] :10 -0042: load -:foo -``` -Should return `1`. - -Instruction `0037` is where it goes off the rails. - -### Things left undone -Many. But things that were actively under development and in a state of unfinishedness: - -1. Tuple patterns -2. Loops -3. Function calls - -#### Tuple patterns -This is the blocking issue for loops, function calls, etc. -You need tuple pattern matching to get proper looping and function calls. -Here are some of the issues I'm having: -* Is it possible to represent tuples on the stack? Right now they're allocated on the heap, which isn't great for function calls. -* How to represent complex patterns? There are a few possibilities: - - Hard-coded into the bytecode (this is probably the thing to do?) - - Represented as a data structure, which itself would have to be allocated on the heap - - Some hybrid of the two: - * Easy scalar values are hard-coded: `nil`, `true`, `:foo`, `10` are all built into the bytecode + constants table - * Perhaps dict patterns will have to be data structures - -#### Patterns, generally -On reflection, I think the easiest (perhaps not simplest!) way to go is to model the patterns as separate datatypes stored per-chunk in a vec. -The idea is that we push a value onto the stack, and then have a `match` instruction that takes an index into the pattern vec. -We don't even need to store all pattern types in that vec: constants (which already get stored in the constant vec), interpolations, and compound patterns. -`nil`, `false`, etc. are singletons and can be handled like (but not exactly as) the `nil` and `false` _values_. -This also means we can outsource the pattern matching mechanics to Rust, which means we don't have to fuss with "efficient compiling of pattern matching" titchiness. -This also has the benefit, while probably being fast _enough_, of reflecting the conceptual domain of Ludus, in which patterns and values are different DSLs within the language. -So: model it that way. - -### Now that we've got tuple patterns -#### May 23, 2025 -A few thoughts: -* Patterns aren't _things_, they're complex conditional forms. I had not really learned that; note that the "compiling pattern matching efficiently" paper is about how to optimize those conditionals. The tuple pattern compilation more closely resembles an `if` or `when` form. -* Tuple patterns break the nice stack-based semantics of binding. So do other patterns. That means I had to separate out bindings and the stack. I did this by introducing a representation of the stack into the compiler (it's just a stack-depth counter). - - This ended up being rather titchy. I think there's a lot of room to simplify things by avoiding manipulating this counter directly. My sense is that I should probably move a lot of the `emit_op` calls into methods that ensure all the bookkeeping happens automatically. -* `when` is much closer to `if` than `match`; remember that! -* Function calls should be different from tuple pattern matching. Tuples are currently (and maybe forever?) allocated on the heap. Function calls should *not* have to pass through the heap. The good news: `Arguments` is already a different AST node type than `Tuple`; we'll want an `ArgumentsPattern` pattern node type that's different from (and thus compiled differently than) `TuplePattern`. They'll be similar--the matching logic is the same, after all--but the arguments will be on the stack already, and won't need to be unpacked in the same way. - - One difficulty will be matching against different arities? But actually, we should compile these arities as different functions. - - Given splats, can we actually compile functions into different arities? Consider the following: - ``` - fn foo { - (x) -> & arity 1 - (y, z) -> & arity 2 - (x, y, 2) -> & arity 3 - (...z) -> & arity 0+ - } - ``` - `fn(1, 2, 3)` and `fn(1, 2, 4)` would invoke different "arities" of the function, 3 and 0+, respectively. I suspect the simpler thing really is to just compile each function as a singleton, and just keep track of the number of arguments you're matching. -* Before we get to function calls, `loop`/`recur` make sense as the starting ground. I had started that before, and there's some code in those branches of the compiler. But I ran into tuple pattern matching. That's now done, although actually, the `loop`/`recur` situation probably needs a rewrite from the ground up. -* Just remember: we're not aiming for *fast*, we're aiming for *fast enough*. And I don't have a ton of time. So the thing to do is to be as little clever as possible. -* I suspect the dominoes will fall reasonably quickly from here through the following: - - [x] `list` and `dict` patterns - - [x] updating `repeat` - - [x] standing up `loop`/`recur` - - [x] standing up functions - - [x] more complex synthetic expressions - - [x] `do` expressions -* That will get me a lot of the way there. What's left after that which might be challenging? - - [x] string interpolation - - [x] splats - - [ ] splatterns - - [x] string patterns - - [x] partial application - - [ ] tail calls - - [ ] stack traces in panics - - [ ] actually good lexing, parsing, and validation errors. I got some of the way there in the fall, but everything needs to be "good enough." -* After that, we're in integration hell: taking this thing and putting it together for Computer Class 1. Other things that I want (e.g., `test` forms) are for later on. -* There's then a whole host of things I'll need to get done for CC2: - - some kind of actual parsing strategy (that's good enough for "Dissociated Press"/Markov chains) - - actors - - animation in the frontend -* In addition to a lot of this, I think I need some kind of testing solution. The Janet interpreter is pretty well-behaved. - -### Now that we've got some additional opcodes and loop/recur working -#### 2025-05-27 -The `loop` compilation is _almost_ the same as a function body. That said, the thing that's different is that we don't know the arity of the function that's called. - -A few possibilities: -* Probably the best option: enforce a new requirement that splat patterns in function clauses *must* be longer than any explicit arity of the function. So, taking the above: - ``` - fn foo { - (x) -> & arity 1 - (y, z) -> & arity 2 - (x, y, 2) -> & arity 3 - (...z) -> & arity 0+ - } - ``` -This would give you a validation error that splats must be longer than any other arity. -Similarly, we could enforce this: -``` -fn foo { - (x) -> & arity 1 - (x, y) -> & arity 2 - (x, ...) & arity n > 1; error! too short - (x, y, ...) & arity n > 2; ok! -} -``` -The algorithm for compiling functions ends up being a little bit titchy, because we'll have to store multiple functions (i.e. chunks) with different arities. -Each arity gets a different chunk. -And the call function opcode comes with a second argument that specifies the number of arguments, 0 to 7. -(That means we only get a max of 7 arguments, unless I decide to give the call opcode two bytes, and make the call/return register much bigger.) - -Todo, then: -* [x] reduce the call/return register to 7 -* [x] implement single-arity compilation -* [x] implement single-arity calls, but with two bytes -* [x] compile multiple-arity functions -* [x] add closures - -### Some thoughts while chattin w/ MNL -On `or` and `and`: these should be reserved words with special casing in the parser. -You can't pass them as functions, because that would change their semantics. -So they *look* like functions, but they don't *behave* like functions. -In Clojure, if you try `(def foo or)`, you get an error that you "can't take the value of a macro." -I'll need to change Ludus so that `or` and `and` expressions actually generate different AST nodes, and then compile them from there. - -AND: done. - -### Implementing functions & calls, finally -#### 2025-06-01 -Just to explain where I am to myself: -* I have a rough draft (probably not yet fully functional) of function compilation in the compiler. -* Now I have to implement two op codes: `Call` and `Return`. -* I now need to implement call frames. -* A frame in _Crafting Interpreters_ has: a pointer to a Lox function object, an ip, and an index into the value stack that indicates the stack bottom for this function call. Taking each of these in turn: -* The lifetime for the pointer to the function: - - The pointer to the function object cannot, I think, be an explicit lifetime reference, since I don't think I know how to prove to the Rust borrow checker that the function will live long enough, especially since it's inside an `Rc`. - - That suggests that I actually need the whole `Value::Fn` struct and not just the inner `LFn` struct, so I can borrow it. -* The ip and stack bottom are just placeholders and don't change. - -### Partially applied functions -#### 2025-06-05 -Partially applied functions are a little complicated, because they involve both the compiler and the VM. -Maybe. -My sense is that, perhaps, the way to do them is actually to create a different value branch. -They .... - -### Jumping! Numbers! And giving up the grind -#### 2025-06-05 -Ok! So. -This won't be ready for next week. -That's clear enough now--even though I've made swell progress! -One thing I just discovered, which, well, it feels silly I haven't found this before. -Jump instructions, all of them, need 16 bits, not 8. - -That means some fancy bit shifting, and likely some refactoring of the compiler & vm to make them easier to work with. - -For reference, here's the algorithm for munging u8s and u16s: - -```rust -let a: u16 = 14261; -let b_high: u8 = (a >> 8) as u8; -let b_low: u8 = a as u8; -let c: u16 = ((b_high as u16) << 8) + b_low as u16; -println!("{a} // {b_high}/{b_low} // {c}"); -``` - -To reiterate the punch list that *I would have needed for Computer Class 1*: -* [x] jump instructions need 16 bits of operand - - Whew, that took longer than I expected -* [x] splatterns - - [ ] validator should ensure splatterns are the longest patterns in a form -* [ ] improve validator - - [ ] Tuples may not be longer than n members - - [ ] Loops may not have splatterns - - [ ] Identify others -* [x] add guards to loop forms -* [x] check loop forms against function calls: do they still work the way we want them to? -* [x] tail call elimination -* [x] stack traces in panics - -* [ ] actually good error messages - - [ ] parsing - - [ ] my memory is that validator messages are already good? - - [ ] panics, esp. no match panics -* [ ] getting to prelude - - [ ] `base` should load into Prelude - - [ ] prelude should run properly - - [ ] prelude should be loaded into every context -* [ ] packaging things up - - [ ] add a `to_json` method for values - - [ ] teach Rudus to speak our protocols (stdout and turtle graphics) - - [ ] there should be a Rust function that takes Ludus source and returns valid Ludus status json - - [ ] compile Rust to WASM - - [ ] wire Rust-based WASM into JS - - [ ] FINALLY, test Rudus against Ludus test cases - -So this is the work of the week of June 16, maybe? - -Just trying to get a sense of what needs to happen for CC2: -* [ ] Actor model (objects, Spacewar!) -* [ ] Animation hooked into the web frontend (Spacewar!) -* [ ] Text input (Spacewar!) - - [ ] Makey makey for alternate input? -* [ ] Saving and loading data into Ludus (perceptrons, dissociated press) -* [ ] Finding corpuses for Dissociated Press - -### Final touches on semantics, or lots of bugs -#### 2025-06-19 -* Main code is fucking up bindings in functions -* Why? -* In the middle of doing TCO, looks like it works for `do` forms based on the bytecode, but I ran into these weird binding problems while trying to test that in the vm -* Once that's put to bed, I believe TCO fully works - -_Edited to add: all the above is, I think, fixed._ - -* But then I need to test it actually works in its intended use case: recursive calls -* Which means I need to test recursive calls -* And, once that's done, I think I have a COMPLETE SEMANTICALLY CORRECT INTERPRETER. -* After that, jesus, it's time for base > prelude > test cases - -#### Later -So this is my near-term TODO: - -* [x] recursive calls - - [x] direct recursive calls - * [x] stash a function declaration before compiling the function, hang onto that declaration - * [x] update that declaration to a definition after compiling the function - - [x] mutual recursive - * [x] check to make sure a function has already been declared, hang onto that declaration - * [x] update that declaration to a definition after compiling the function... - * [x] but BEFORE we close over any upvalues, so the function will be an upvalue for itself - - I suspect this can be done not using anything other than an index into a chunk's `constants` vec--no fancy memory swapping or anything; and also--done in the compiler rather than the VM. -* [x] getting to prelude - - [x] `base` should load into Prelude - - [x] write a mock prelude with a few key functions from real prelude - - [x] a prelude should be loaded into every context - - [ ] the full prelude should run properly -* [ ] packaging things up - - [ ] add a `to_json` method for values - - [ ] teach Rudus to speak our protocols (stdout and turtle graphics) - - [ ] there should be a Rust function that takes Ludus source and returns valid Ludus status json - - [ ] compile Rust to WASM - - [ ] wire Rust-based WASM into JS - - [ ] FINALLY, test Rudus against Ludus test cases - -And then: quality of life improvements: -* [ ] refactor messes - - [ ] The compiler should abstract over some of the very titchy bytecode instruction code - - [ ] Pull apart some gargantuan modules into smaller chunks: e.g., `Op` and `Chunk` should be their own modules - - [ ] Identify code smells - - [ ] Fix some of them -* [ ] improve validator - - [ ] Tuples may not be longer than n members - - [ ] Loops may not have splatterns - - [ ] Identify others - - [ ] Splats in functions must be the same arity, and greater than any explicit arity -* [ ] actually good error messages - - [ ] parsing - - [ ] my memory is that validator messages are already good? - - [ ] panics, esp. no match panics - * [ ] panics should be able to refernce the line number where they fail - * [ ] that suggests that we need a mapping from bytecodes to AST nodes - * The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow. - -### Bugs discovered while trying to compile prelude -#### 2025-06-20 -Consider the following code: -``` -fn one { - (x as :number) -> { - fn two () -> :number - two - } - (x as :bool) -> { - fn two () -> :bool - two - } -} - -``` -The second clause causes a panic in the compiler. -I'm not entirely sure what's going on. -That said, I'm pretty sure the root of it is that the fact that `two` was already bound in the first clause means that it's in the constants vector in that chunk under the name "two." - -... - -So that's fixed! - -But: -Prelude functions are not properly closing over their upvalues. -Which is not right. -We get: `get_upvalue 000` and a panic: `index out of bounds: the len is 0 but the index is 0`. -The vec in question is the LFn::Defined.closed. - -... - -Just patched up the `if` alternative branch unconditional jump, which was jumping too far. - -Now it really is just some pretty systematic testing of prelude functions, including the problems with upvalues, which I haven't yet been able to recreate. - -### On proceeding from here -#### 2025-06-021 -Rather than doing everything by hand right now, I think the way to go about things is to figure out how to do as much automated bugfixing as possible. -That means reprioritizing some things. -So here's a short punch list of things to do in that register: -* [x] Hook validator back in to both source AND prelude code - - [x] Validator should know about the environment for global/prelude function - - [x] Run validator on current prelude to fix current known errors -* [ ] Do what it takes to compile this interpreter into Ludus's JS environment - - [ ] JSONify Ludus values - - [ ] Write a function that's source code to JSON result - - [ ] Expose this to a WASM compiler - - [ ] Patch this into a JS file - - [ ] Automate this build process -* [ ] Start testing against the cases in `ludus-test` -* [ ] Systematically debug prelude - - [ ] Bring it in function by function, testing each in turn - -*** -I've started working on systematically going through the Prelude. -I've found a closure error. -This is in `map`/`mapping`. -What's happening is that the inner function, `mapping`, is closing over values directly from a stack that no longer exists. -What we need to have happen is that if a function is closing over values _inside_ a function, it needs to capture not the upvalues directly from the stack, but _from the enclosing closure_. -I think I need to consult Uncle Bob Nystrom to get a sense of what to do here. -*** -So I found the minimal test case: -``` -let foo = { - let thing = :thing - let bar = :bar - let baz = :baz - fn quux () -> { - fn frobulate () -> (bar, baz) - } -} - -foo () -``` -`frobulate` is closed over when `quux` is called, when the stack looks completely different than it does when `quux` is defined. -If you remove line 2, binding `thing`, then you don't get a panic, but when you call `foo () ()`, the result is `(fn quux, fn frobulate)`. -The problem is that `frobulate`'s upvalues are indexes into the stack, rather than having some relation to `quux`'s upvalues. -What needs to happen is that an enclosing function needs to capture, define, and pass down the upvalues for its enclosed functions. - -I'm having an exact problem that Uncle Bob is describing at -https://craftinginterpreters.com/closures.html#flattening-upvalues. -I need to study and adapt this exact set of problems. -I believe I need to take the strategy he uses with closures being different from functions, etc. -So: rework the closures strategy here. - -*** - -Closures strategy mostly unfucked. -Now I'm having some difficulty with bindings, again? -Current situation: still trying to get `map` and `fold` to work properly. -The bindings inside of non-trivial functions goes weird. -The scope depths are all out of whack. -And the stack positions on stuff are also totally weird. -One thing: the way we are now resolving upvalues means that nothing should ever reach back further in the stack than the stack base in the vm. -So now we'll do all bindings relative to the stack base. -UGH. - -*** - -So now I have a bug that I'm dying to figure out. -But it's time to go to bed. - -When `recur` is in the alternatve branch of an if, it thinks there's one more value on the stack than there really is. -To the best of my ability to tell, `if` has proper stack behaviour. -So the question is what's happening in the interaction between the `jump_if_false` instruction and `recur`. - -To wit, the following code works just fine: -``` -fn not { - (false) -> true - (nil) -> true - (_) -> false -} - -let test = 2 -loop ([1, 2, 3]) with { - ([]) -> false - ([x]) -> eq? (x, test) - ([x, ...xs]) -> if not(eq? (x, test)) - then recur (xs) - else true -} -``` - -But the following code does not: -``` -let test = 2 -loop ([1, 2, 3]) with { - ([]) -> false - ([x]) -> eq? (x, test) - ([x, ...xs]) -> if eq? (x, test) - then true - else recur (xs) -} -``` - -Meanwhile, other `loop`/`recur` forms seem to work okay for me. -So: ugh. - -### Grinding and grinding -#### 2025-06-22 -Got 'er done. -Fixed `loop`/`recur` and many other stack shananigans. -I don't believe I've fixed everything yet. -I may be surprised, though. - -Currently fixing little bugs in prelude. -Here's a list of things that need doing: -* [ ] Escape characters in strings: \n, \t, and \{, \}. -* [ ] `doc!` needs to print the patterns of a function. -* [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. -* [ ] Original implementation of `butlast` is breaking stack discipline; I don't know why. It ends up returning from evaluating one of the arguments straight into a `load` instruction. Something about tail calls and ternary synthetic expressions and base functions. (For now, I can call `slice` instead of `base :slice` and it works.) - - Original version of `update` also had this same problem with `assoc`; fixed it by calling the Ludus, rather than Rust, function. - - I need this fixed for optimization reasons. - - I _think_ I just fixed this by fixing tail position tracking in collections - - [ ] test this - - I did not fix it. -* [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard? -* [ ] This is in the service of getting turtle graphics working -* Other forms in the language need help: - * [ ] repeat needs its stack discipline updated, it currently crashes the compiler - -### More closure problems -#### 2025-06-23 -My solution to closures wasn't quite right. -I can't use Uncle Bob's strategy of the recursive call, since Rust's ownership semantics make this onerous at best. -My solution: introduce the concept of a "compiler depth," with 0 being the global scope. -If the compiler's at 0 depth, we can pull it out of the environment. -If the compiler's at a depth > 0, then we can ask the enclosing compiler to stash the upvalue. -And thus we get what we need. - -But: some functions in prelude aren't properly getting their closures, and I don't know why, since they *are* getting them properly in user scripts. -Take `apply_command`. - -Next step: sort out if any other functions aren't getting things closed over properly. - -PROBLEM: forward-declared functions weren't at the top of the stack when `Op::SetUpvalue` was called. -So all of `apply_command`'s upvalues were being attached to the function declared before it (which was sitting right there at the top of the stack.) - -SOLUTION: test to see if the function has been forward-declared, and if it has, bring it to the top fo the stack. - -NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls. - -NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack. -<<<<<<< HEAD -<<<<<<< Updated upstream -This is the thing I am about to do -||||||| Stash base -This is the thing I am about to do. - -### I think the interpreter, uh, works? -#### 2025-06-24 -I'm sure I'll find some small problems. -But right now the thing works. -At the moment, I'm thinking about how to get good error messages. -Panics are difficult. -And I'm worried about ariadne as the error reporting crate. -Since it writes to stdout, it has all kinds of escape codes. -I need a plain ass string, at least for the web frontend. -So. - -Current task, however, is how to get reasonable panic error messages. -Let's simplify the problem. - -First, let's get tracebacks and line numbers. -We use Chumsky spanned ASTs. -The span is just a range into an str. -What I can do is pretty stupidly just generate line numbers from the spans in the compiler, and from there, get a reasonable traceback. -So instead of using Ariadne's fancy report builder, let's just do something more what we already have here: - -``` -Ludus panicked! no match - on line 1 in input, - calling: add - with arguments: ("foo") - expected match with one of: - () - (x as :number) - (x as :number, y as :number) - (x, y, ...zs) - ((x1, y1), (x2, y2)) - >>> add ("foo") -......^ -``` -We need: -* the location of the function call in terms of the line number -* the arguments used -* the patterns expected to match (special cases: `let` vs `match` vs `fn`) - -That means, for bookkeeping, we need: -* In the compiler, line number -* In the VM, the arguments -* In the VM, the pattern AST node. - -In Janet-Ludus, there are only a few types of panic: -* `fn-no-match`: no match against a function call -* `let-no-match`: no match against a `let` binding -* `match-no-match`: no match against a `match` form -* `generic-panic`: everything else -* `runtime-error`: an internal Ludus error - -The first three are simply formatting differences. -There are no tracebacks. - -Tracebacks should be easy enough, although there's some fiddly bits. -While it's nice to have the carret, the brutalist attempt here should be just to give us the line--since the carret isn't exactly precise in the Janet interpereter. -And the traceback should look something like: - -``` - calling foo with (:bar, :baz) - at line 12 in input - calling bar with () - at line 34 in prelude - calling baz with (1, 2, 3) - at line 12 in input -``` - -Which means, again: function names, ip->line conversion, and arguments. - -The runtime needs a representation of the patterns in _any_ matching form. -The speed is so much greater now that I'm not so concerned about little optimizations. -So: a chunk needs a vec of patterns-representations. (I'm thinking simply a `Vec`.) -So does a function, for `doc!`. -Same same re: `Vec`. -A VM needs a register for the scrutinee (which with function calls is just the arguments, already captured). -A VM also needs a register for the pattern. -So when there's a no match, we just yank the pattern and the scrutinee out of these registers. - -This all seems very straightforward compared to the compiling & VM stuff. - -Here's some stub code I wrote for dealing with ranges, source, line numbers: - -```rust -let str = "foo bar baz\nquux frob\nthing thing thing"; -let range = 0..4; - -println!("{}", str.get(range).unwrap()); - -let lines: Vec<&str> = str.split_terminator("\n").collect(); - -println!("{:?}", lines); - -let idx = 20; - -let mut line_no = 1; -for i in 0..idx { - if str.chars().nth(i).unwrap() == '\n' { - line_no += 1; - } -} - -println!("line {line_no}: {}", lines[line_no - 1]); -``` - -This is the thing I am about to do. - -### I think the interpreter, uh, works? -#### 2025-06-24 -I'm sure I'll find some small problems. -But right now the thing works. -At the moment, I'm thinking about how to get good error messages. -Panics are difficult. -And I'm worried about ariadne as the error reporting crate. -Since it writes to stdout, it has all kinds of escape codes. -I need a plain ass string, at least for the web frontend. -So. - -Current task, however, is how to get reasonable panic error messages. -Let's simplify the problem. - -First, let's get tracebacks and line numbers. -We use Chumsky spanned ASTs. -The span is just a range into an str. -What I can do is pretty stupidly just generate line numbers from the spans in the compiler, and from there, get a reasonable traceback. -So instead of using Ariadne's fancy report builder, let's just do something more what we already have here: - -``` -Ludus panicked! no match - on line 1 in input, - calling: add - with arguments: ("foo") - expected match with one of: - () - (x as :number) - (x as :number, y as :number) - (x, y, ...zs) - ((x1, y1), (x2, y2)) - >>> add ("foo") -......^ -``` -We need: -* the location of the function call in terms of the line number -* the arguments used -* the patterns expected to match (special cases: `let` vs `match` vs `fn`) - -That means, for bookkeeping, we need: -* In the compiler, line number -* In the VM, the arguments -* In the VM, the pattern AST node. - -In Janet-Ludus, there are only a few types of panic: -* `fn-no-match`: no match against a function call -* `let-no-match`: no match against a `let` binding -* `match-no-match`: no match against a `match` form -* `generic-panic`: everything else -* `runtime-error`: an internal Ludus error - -The first three are simply formatting differences. -There are no tracebacks. - -Tracebacks should be easy enough, although there's some fiddly bits. -While it's nice to have the carret, the brutalist attempt here should be just to give us the line--since the carret isn't exactly precise in the Janet interpereter. -And the traceback should look something like: - -``` - calling foo with (:bar, :baz) - at line 12 in input - calling bar with () - at line 34 in prelude - calling baz with (1, 2, 3) - at line 12 in input -``` - -Which means, again: function names, ip->line conversion, and arguments. - -The runtime needs a representation of the patterns in _any_ matching form. -The speed is so much greater now that I'm not so concerned about little optimizations. -So: a chunk needs a vec of patterns-representations. (I'm thinking simply a `Vec`.) -So does a function, for `doc!`. -Same same re: `Vec`. -A VM needs a register for the scrutinee (which with function calls is just the arguments, already captured). -A VM also needs a register for the pattern. -So when there's a no match, we just yank the pattern and the scrutinee out of these registers. - -This all seems very straightforward compared to the compiling & VM stuff. - -Here's some stub code I wrote for dealing with ranges, source, line numbers: - -```rust -let str = "foo bar baz\nquux frob\nthing thing thing"; -let range = 0..4; - -println!("{}", str.get(range).unwrap()); - -let lines: Vec<&str> = str.split_terminator("\n").collect(); - -println!("{:?}", lines); - -let idx = 20; - -let mut line_no = 1; -for i in 0..idx { - if str.chars().nth(i).unwrap() == '\n' { - line_no += 1; - } -} - -println!("line {line_no}: {}", lines[line_no - 1]); -``` - -### Integration meeting with mnl -#### 2025-06-25 -* Web workers -* My javascript wrapper needs to execute WASM in its own thread (ugh) - - [ ] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? -* Top priorities: - - [ ] Get a node package out - - [ ] Stand up actors + threads, etc. - - [ ] How to model keyboard input from p5? - * [ ] Model after the p5 keyboard input API - * [ ] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict) - - Assets: - * We don't (for now) need to worry about serialization formats, since we're not doing perceptrons - * We do need to read from URLs, which need in a *.ludus.dev. - * Users can create their own (public) repos and put stuff in there. - * We still want saving text output from web Ludus - * Later, with perceptrons & the book, we'll need additional solutions. - -#### Integration hell -As predicted, Javascript is the devil. - -wasm-pack has several targets: -* nodejs -> this should be what we want -* web -> this could be what we want -* bundler -> webpack confuses me - -The simplest, shortest route should be to create a viable nodejs library. -It works. -I can wire up the wasm-pack output with a package.json, pull it down from npm, and it work. -However, because of course, vite, which svelte uses, doesn't like this. -We get an error that `TextEncoder is not a constructor`. -This, apparently, has something to do with the way that vite packages up node libraries? -See https://github.com/vitejs/vite/discussions/12826. - -Web, in some ways, is even more straightforward. -It produces an ESM that just works in the browser. -And diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 59190e0..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1594 +0,0 @@ -{ - "name": "@ludus/rudus", - "version": "0.1.2", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@ludus/rudus", - "version": "0.1.2", - "license": "GPL-3.0", - "devDependencies": { - "@wasm-tool/wasm-pack-plugin": "^1.7.0", - "webpack": "^5.99.9", - "webpack-cli": "^6.0.1" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.0.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", - "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.8.0" - } - }, - "node_modules/@wasm-tool/wasm-pack-plugin": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@wasm-tool/wasm-pack-plugin/-/wasm-pack-plugin-1.7.0.tgz", - "integrity": "sha512-WikzYsw7nTd5CZxH75h7NxM/FLJAgqfWt+/gk3EL3wYKxiIlpMIYPja+sHQl3ARiicIYy4BDfxkbAVjRYlouTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.4.1", - "command-exists": "^1.2.7", - "watchpack": "^2.1.1", - "which": "^2.0.2" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", - "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", - "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", - "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.174", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.174.tgz", - "integrity": "sha512-HE43yYdUUiJVjewV2A9EP8o89Kb4AqMKplMQP2IxEPUws1Etu/ZkdsgUDabUZ/WmbP4ZbvJDOcunvbBUPPIfmw==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", - "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", - "dev": true, - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.43.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.99.9", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", - "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", - "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.6.1", - "@webpack-cli/configtest": "^3.0.1", - "@webpack-cli/info": "^3.0.1", - "@webpack-cli/serve": "^3.0.1", - "colorette": "^2.0.14", - "commander": "^12.1.0", - "cross-spawn": "^7.0.3", - "envinfo": "^7.14.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^6.0.1" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.82.0" - }, - "peerDependenciesMeta": { - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 5303728..0000000 --- a/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@ludus/rudus", - "version": "0.1.3", - "description": "A Rust-based Ludus bytecode interpreter.", - "type": "module", - "main": "pkg/ludus.js", - "directories": {}, - "keywords": [], - "author": "Scott Richmond", - "license": "GPL-3.0", - "files": [ - "pkg/rudus.js", - "pkg/ludus.js", - "pkg/rudus_bg.wasm", - "pkg/rudus_bg.wasm.d.ts", - "pkg/rudus.d.ts" - ], - "devDependencies": { - } -} diff --git a/pkg/README.md b/pkg/README.md deleted file mode 100644 index bf252a2..0000000 --- a/pkg/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# rudus - -A Rust implementation of Ludus. \ No newline at end of file diff --git a/pkg/index.html b/pkg/index.html deleted file mode 100644 index 74f29a2..0000000 --- a/pkg/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Testing Ludus/WASM integration - - - - -

- Open the console. All the action's in there. -

- - - diff --git a/pkg/package.json b/pkg/package.json deleted file mode 100644 index b958f88..0000000 --- a/pkg/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "rudus", - "type": "module", - "version": "0.0.1", - "files": [ - "rudus_bg.wasm", - "rudus.js", - "rudus.d.ts" - ], - "main": "rudus.js", - "types": "rudus.d.ts", - "sideEffects": [ - "./snippets/*" - ] -} \ No newline at end of file diff --git a/pkg/test.js b/pkg/test.js deleted file mode 100644 index 91953f6..0000000 --- a/pkg/test.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as mod from "./ludus.js"; - -console.log(mod.run(` - :foobar - `)); diff --git a/pkg/rudus.d.ts b/rudus.d.ts similarity index 100% rename from pkg/rudus.d.ts rename to rudus.d.ts diff --git a/pkg/rudus.js b/rudus.js similarity index 100% rename from pkg/rudus.js rename to rudus.js diff --git a/pkg/rudus_bg.wasm b/rudus_bg.wasm similarity index 100% rename from pkg/rudus_bg.wasm rename to rudus_bg.wasm diff --git a/pkg/rudus_bg.wasm.d.ts b/rudus_bg.wasm.d.ts similarity index 100% rename from pkg/rudus_bg.wasm.d.ts rename to rudus_bg.wasm.d.ts diff --git a/sandbox.ld b/sandbox.ld deleted file mode 100644 index 08f11f5..0000000 --- a/sandbox.ld +++ /dev/null @@ -1 +0,0 @@ -print! ("foobar") diff --git a/sandbox_run.txt b/sandbox_run.txt deleted file mode 100644 index 8d99c9c..0000000 --- a/sandbox_run.txt +++ /dev/null @@ -1,917 +0,0 @@ -{ - - box foos = [] - fn foo! { - () -> update! (foos, append (_, :foo) ) -} - fn foos! { - () -> repeat 4 { -{ - - foo! () -} -} -} - foos! () - unbox (foos) -} -closing over in type at 1: #{:list fn list/base... -closing over in eq? at 1: #{:list fn list/base... -closing over in eq? at 2: fn eq? -closing over in first at 1: #{:list fn list/base... -closing over in rest at 1: #{:list fn list/base... -closing over in inc at 1: #{:list fn list/base... -closing over in dec at 1: #{:list fn list/base... -closing over in count at 1: #{:list fn list/base... -closing over in any? at 1: fn empty? -closing over in any? at 2: fn not -closing over in list at 1: #{:list fn list/base... -closing over in append at 1: #{:list fn list/base... -closing over in fold at 1: fn fold -closing over in fold at 2: fn first -closing over in fold at 3: fn rest -closing over in foldr at 1: fn foldr -closing over in foldr at 2: fn first -closing over in foldr at 3: fn rest -closing over in map at 1: fn map -closing over in map at 2: fn append -closing over in map at 3: fn fold -closing over in filter at 1: fn filter -closing over in filter at 2: fn append -closing over in filter at 3: fn fold -closing over in keep at 1: fn some? -closing over in keep at 2: fn filter -closing over in concat at 1: #{:list fn list/base... -closing over in concat at 2: fn concat -closing over in concat at 3: fn fold -closing over in contains? at 1: fn first -closing over in contains? at 2: fn eq? -closing over in contains? at 3: fn rest -closing over in print! at 1: #{:list fn list/base... -closing over in show at 1: #{:list fn list/base... -closing over in report! at 1: fn print! -closing over in report! at 2: fn show -closing over in report! at 3: fn concat -closing over in doc! at 1: #{:list fn list/base... -closing over in doc! at 2: fn print! -closing over in string at 1: fn show -closing over in string at 2: fn string -closing over in string at 3: fn concat -closing over in join at 1: fn join -closing over in join at 2: fn concat -closing over in join at 3: fn fold -closing over in split at 1: #{:list fn list/base... -closing over in trim at 1: #{:list fn list/base... -closing over in upcase at 1: #{:list fn list/base... -closing over in downcase at 1: #{:list fn list/base... -closing over in chars at 1: #{:list fn list/base... -closing over in chars/safe at 1: #{:list fn list/base... -closing over in strip at 1: fn strip -closing over in words at 1: fn strip -closing over in words at 2: fn split -closing over in words at 3: fn empty? -closing over in words at 4: fn append -closing over in words at 5: fn fold -closing over in sentence at 1: fn join -closing over in to_number at 1: #{:list fn list/base... -closing over in unbox at 1: #{:list fn list/base... -closing over in store! at 1: #{:list fn list/base... -closing over in update! at 1: fn unbox -closing over in update! at 2: fn store! -closing over in add at 1: #{:list fn list/base... -closing over in add at 2: fn add -closing over in add at 3: fn fold -closing over in sub at 1: #{:list fn list/base... -closing over in sub at 2: fn sub -closing over in sub at 3: fn fold -closing over in mult at 1: #{:list fn list/base... -closing over in mult at 2: fn mult -closing over in mult at 3: fn fold -closing over in div at 1: #{:list fn list/base... -closing over in div at 2: fn mult -closing over in div at 3: fn fold -closing over in div at 4: fn div -closing over in div/0 at 1: #{:list fn list/base... -closing over in div/0 at 2: fn mult -closing over in div/0 at 3: fn fold -closing over in div/0 at 4: fn div/0 -closing over in div/safe at 1: fn div -closing over in div/safe at 2: fn mult -closing over in div/safe at 3: fn fold -closing over in div/safe at 4: fn div/safe -closing over in inv at 1: fn div -closing over in inv/0 at 1: fn div/0 -closing over in inv/safe at 1: fn div/safe -closing over in neg at 1: fn mult -closing over in gt? at 1: #{:list fn list/base... -closing over in gte? at 1: #{:list fn list/base... -closing over in lt? at 1: #{:list fn list/base... -closing over in lte? at 1: #{:list fn list/base... -closing over in between? at 1: fn gte? -closing over in between? at 2: fn lt? -closing over in neg? at 1: fn lt? -closing over in pos? at 1: fn gt? -closing over in abs at 1: fn neg? -closing over in abs at 2: fn mult -closing over in turn/deg at 1: fn mult -closing over in deg/turn at 1: fn div -closing over in turn/rad at 1: 6.283185307179586 -closing over in turn/rad at 2: fn mult -closing over in rad/turn at 1: 6.283185307179586 -closing over in rad/turn at 2: fn div -closing over in deg/rad at 1: 6.283185307179586 -closing over in deg/rad at 2: fn div -closing over in deg/rad at 3: fn mult -closing over in rad/deg at 1: 6.283185307179586 -closing over in rad/deg at 2: fn div -closing over in rad/deg at 3: fn mult -closing over in sin at 1: fn turn/rad -closing over in sin at 2: #{:list fn list/base... -closing over in sin at 3: fn deg/rad -closing over in cos at 1: fn turn/rad -closing over in cos at 2: #{:list fn list/base... -closing over in cos at 3: fn deg/rad -closing over in tan at 1: fn turn/rad -closing over in tan at 2: #{:list fn list/base... -closing over in tan at 3: fn deg/rad -closing over in rotate at 1: fn rotate -closing over in rotate at 2: fn cos -closing over in rotate at 3: fn mult -closing over in rotate at 4: fn sin -closing over in rotate at 5: fn sub -closing over in rotate at 6: fn add -closing over in atan/2 at 1: #{:list fn list/base... -closing over in atan/2 at 2: fn rad/turn -closing over in atan/2 at 3: fn atan/2 -closing over in atan/2 at 4: fn rad/deg -closing over in angle at 1: fn atan/2 -closing over in angle at 2: fn sub -closing over in mod at 1: #{:list fn list/base... -closing over in mod/0 at 1: #{:list fn list/base... -closing over in mod/safe at 1: #{:list fn list/base... -closing over in even? at 1: fn mod -closing over in even? at 2: fn eq? -closing over in odd? at 1: fn mod -closing over in odd? at 2: fn eq? -closing over in square at 1: fn mult -closing over in sqrt at 1: fn neg? -closing over in sqrt at 2: fn not -closing over in sqrt at 3: #{:list fn list/base... -closing over in sqrt/safe at 1: fn neg? -closing over in sqrt/safe at 2: fn not -closing over in sqrt/safe at 3: #{:list fn list/base... -closing over in sum_of_squares at 1: fn square -closing over in sum_of_squares at 2: fn add -closing over in sum_of_squares at 3: fn sum_of_squares -closing over in sum_of_squares at 4: fn fold -closing over in dist at 1: fn sum_of_squares -closing over in dist at 2: fn sqrt -closing over in dist at 3: fn dist -closing over in heading/vector at 1: fn neg -closing over in heading/vector at 2: fn add -closing over in heading/vector at 3: fn cos -closing over in heading/vector at 4: fn sin -closing over in floor at 1: #{:list fn list/base... -closing over in ceil at 1: #{:list fn list/base... -closing over in round at 1: #{:list fn list/base... -closing over in range at 1: #{:list fn list/base... -closing over in at at 1: #{:list fn list/base... -closing over in second at 1: fn ordered? -closing over in second at 2: fn at -closing over in last at 1: fn ordered? -closing over in last at 2: fn count -closing over in last at 3: fn dec -closing over in last at 4: fn at -closing over in slice at 1: fn slice -closing over in slice at 2: fn gte? -closing over in slice at 3: fn count -closing over in slice at 4: fn gt? -closing over in slice at 5: fn neg? -closing over in slice at 6: #{:list fn list/base... -closing over in butlast at 1: fn count -closing over in butlast at 2: fn dec -closing over in butlast at 3: fn slice -closing over in assoc at 1: #{:list fn list/base... -closing over in dissoc at 1: #{:list fn list/base... -closing over in get at 1: fn get -closing over in get at 2: #{:list fn list/base... -closing over in update at 1: fn get -closing over in update at 2: fn assoc -closing over in keys at 1: fn list -closing over in keys at 2: fn first -closing over in keys at 3: fn map -closing over in values at 1: fn list -closing over in values at 2: fn second -closing over in values at 3: fn map -closing over in has? at 1: fn has? -closing over in has? at 2: fn get -closing over in has? at 3: fn some? -closing over in dict at 1: fn assoc -closing over in dict at 2: fn fold -closing over in dict at 3: fn list -closing over in dict at 4: fn dict -closing over in each! at 1: fn each! -closing over in random at 1: #{:list fn list/base... -closing over in random at 2: fn random -closing over in random at 3: fn mult -closing over in random at 4: fn sub -closing over in random at 5: fn add -closing over in random at 6: fn count -closing over in random at 7: fn floor -closing over in random at 8: fn at -closing over in random at 9: fn keys -closing over in random at 10: fn get -closing over in random_int at 1: fn random -closing over in random_int at 2: fn floor -closing over in add_command! at 1: box { [] } -closing over in add_command! at 2: fn append -closing over in add_command! at 3: fn update! -closing over in add_command! at 4: box { #{:position (0... -closing over in add_command! at 5: fn unbox -closing over in add_command! at 6: fn apply_command -closing over in add_command! at 7: fn store! -closing over in forward! at 1: fn add_command! -closing over in back! at 1: fn add_command! -closing over in left! at 1: fn add_command! -closing over in right! at 1: fn add_command! -closing over in penup! at 1: fn add_command! -closing over in pendown! at 1: fn add_command! -closing over in pencolor! at 1: fn add_command! -closing over in penwidth! at 1: fn add_command! -closing over in background! at 1: fn add_command! -closing over in home! at 1: fn add_command! -closing over in clear! at 1: fn add_command! -closing over in goto! at 1: fn add_command! -closing over in goto! at 2: fn goto! -closing over in setheading! at 1: fn add_command! -closing over in showturtle! at 1: fn add_command! -closing over in hideturtle! at 1: fn add_command! -closing over in loadstate! at 1: fn add_command! -closing over in apply_command at 1: fn assoc -closing over in apply_command at 2: fn add -closing over in apply_command at 3: fn update -closing over in apply_command at 4: fn sub -closing over in apply_command at 5: fn heading/vector -closing over in apply_command at 6: fn mult -closing over in position at 1: box { #{:position (0... -closing over in position at 2: fn unbox -closing over in heading at 1: box { #{:position (0... -closing over in heading at 2: fn unbox -closing over in pendown? at 1: box { #{:position (0... -closing over in pendown? at 2: fn unbox -closing over in pencolor at 1: box { #{:position (0... -closing over in pencolor at 2: fn unbox -closing over in penwidth at 1: box { #{:position (0... -closing over in penwidth at 2: fn unbox -binding `foos` in sandbox -stack depth: 1; match depth: 0 -at stack index: 0 -new locals: foos@0//0 -binding `foo!` in sandbox -stack depth: 2; match depth: 0 -at stack index: 1 -new locals: foos@0//0|foo!@1//0 -***function clause matching: : () -***calling function update! stack depth: 0 -resolving binding `foos` in foo! -locals: -as enclosing upvalue 0 -***calling function append stack depth: 1 -resolving binding `append` in foo! -locals: -as enclosing upvalue 1 -resolving binding `update!` in foo! -locals: -as enclosing upvalue 2 -***after 2 args stack depth: 3 -=== function chuncktion: foo!/0 === -IDX | CODE | INFO -0000: reset_match -0001: ***function clause matching: : () -0003: match -0004: jump 00000 -0007: jump_if_no_match 00034 -0010: ***calling function update! stack depth: 0 -0012: resolving binding `foos` in foo! -locals: -0014: as enclosing upvalue 0 -0016: get_upvalue 000 -0018: ***calling function append stack depth: 1 -0020: nothing -0021: constant 00000: :foo -0024: resolving binding `append` in foo! -locals: -0026: as enclosing upvalue 1 -0028: get_upvalue 001 -0030: partial 002 -0032: resolving binding `update!` in foo! -locals: -0034: as enclosing upvalue 2 -0036: get_upvalue 002 -0038: ***after 2 args stack depth: 3 -0040: tail_call 002 -0042: store -0043: return -0044: panic_no_match -resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0 -at locals position 0 -resolving binding `append` in sandbox -locals: foos@0//0|foo!@1//0 -as global -resolving binding `update!` in sandbox -locals: foos@0//0|foo!@1//0 -as global -binding `foos!` in sandbox -stack depth: 3; match depth: 0 -at stack index: 2 -new locals: foos@0//0|foo!@1//0|foos!@2//0 -***function clause matching: : () -***calling function foo! stack depth: 1 -resolving binding `foo!` in foos! -locals: -as enclosing upvalue 0 -***after 0 args stack depth: 2 -leaving scope 1 -***leaving block before pop stack depth: 1 -popping back from 1 to 1 -=== function chuncktion: foos!/0 === -IDX | CODE | INFO -0000: reset_match -0001: ***function clause matching: : () -0003: match -0004: jump 00000 -0007: jump_if_no_match 00042 -0010: constant 00000: 4 -0013: truncate -0014: jump 00001 -0017: decrement -0018: duplicate -0019: jump_if_zero 00024 -0022: ***calling function foo! stack depth: 1 -0024: resolving binding `foo!` in foos! -locals: -0026: as enclosing upvalue 0 -0028: get_upvalue 000 -0030: ***after 0 args stack depth: 2 -0032: tail_call 000 -0034: store -0035: leaving scope 1 -0037: ***leaving block before pop stack depth: 1 -0039: popping back from 1 to 1 -0041: load -0042: pop -0043: jump_back 00026 -0046: pop -0047: constant 00001: nil -0050: store -0051: return -0052: panic_no_match -resolving binding `foo!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -at locals position 1 -***calling function foos! stack depth: 3 -resolving binding `foos!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -at locals position 2 -***after 0 args stack depth: 4 -***calling function unbox stack depth: 3 -resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -at locals position 0 -resolving binding `unbox` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -as global -***after 1 args stack depth: 5 -leaving scope 0 -releasing binding foos!@2//0 -releasing binding foo!@1//0 -releasing binding foos@0//0 -***leaving block before pop stack depth: 3 -popping back from 3 to 0 -=== source code === -box foos = [] - -fn foo! () -> update! (foos, append (_, :foo)) - -fn foos! () -> repeat 4 { - foo! () -} - -foos! () - -unbox (foos) - -=== chunk: sandbox === -IDX | CODE | INFO -0000: push_list -0001: push_box 082 -0003: noop -0004: stack depth: 1; match depth: 0 -0006: at stack index: 0 -0008: new locals: foos@0//0 -0010: constant 00000: fn foo! -0013: binding `foo!` in sandbox -0015: stack depth: 2; match depth: 0 -0017: at stack index: 1 -0019: new locals: foos@0//0|foo!@1//0 -0021: resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0 -0023: at locals position 0 -0025: push_binding 000 -0027: set_upvalue -0028: resolving binding `append` in sandbox -locals: foos@0//0|foo!@1//0 -0030: as global -0032: constant 00001: :append -0035: push_global -0036: set_upvalue -0037: resolving binding `update!` in sandbox -locals: foos@0//0|foo!@1//0 -0039: as global -0041: constant 00002: :update! -0044: push_global -0045: set_upvalue -0046: constant 00003: fn foos! -0049: binding `foos!` in sandbox -0051: stack depth: 3; match depth: 0 -0053: at stack index: 2 -0055: new locals: foos@0//0|foo!@1//0|foos!@2//0 -0057: resolving binding `foo!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0059: at locals position 1 -0061: push_binding 001 -0063: set_upvalue -0064: ***calling function foos! stack depth: 3 -0066: resolving binding `foos!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0068: at locals position 2 -0070: push_binding 002 -0072: ***after 0 args stack depth: 4 -0074: call 000 -0076: pop -0077: ***calling function unbox stack depth: 3 -0079: resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0081: at locals position 0 -0083: push_binding 000 -0085: resolving binding `unbox` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0087: as global -0089: constant 00004: :unbox -0092: push_global -0093: ***after 1 args stack depth: 5 -0095: call 001 -0097: store -0098: leaving scope 0 -0100: releasing binding foos!@2//0 -0102: releasing binding foo!@1//0 -0104: releasing binding foos@0//0 -0106: ***leaving block before pop stack depth: 3 -0108: popping back from 3 to 0 -0110: pop_n 003 -0112: load - - - -=== vm run === -0000: [] (_,_,_,_,_,_,_,_) -0000: push_list -0001: [->[]<-] (_,_,_,_,_,_,_,_) -0001: push_box 082 -0002: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0002: binding `foos` in sandbox -0004: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0004: stack depth: 1; match depth: 0 -0006: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0006: at stack index: 0 -0008: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0008: new locals: foos@0//0 -0010: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0010: constant 00000: fn foo! -0013: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0013: binding `foo!` in sandbox -0015: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0015: stack depth: 2; match depth: 0 -0017: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0017: at stack index: 1 -0019: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0019: new locals: foos@0//0|foo!@1//0 -0021: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0021: resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0 -0023: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0023: at locals position 0 -0025: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0025: push_binding 000 -0027: [->box { [] }<-|fn foo!|box { [] }] (_,_,_,_,_,_,_,_) -0027: set_upvalue -closing over in foo! at 1: box { [] } -0028: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0028: resolving binding `append` in sandbox -locals: foos@0//0|foo!@1//0 -0030: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0030: as global -0032: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0032: constant 00001: :append -0035: [->box { [] }<-|fn foo!|:append] (_,_,_,_,_,_,_,_) -0035: push_global -0036: [->box { [] }<-|fn foo!|fn append] (_,_,_,_,_,_,_,_) -0036: set_upvalue -closing over in foo! at 2: fn append -0037: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0037: resolving binding `update!` in sandbox -locals: foos@0//0|foo!@1//0 -0039: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0039: as global -0041: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0041: constant 00002: :update! -0044: [->box { [] }<-|fn foo!|:update!] (_,_,_,_,_,_,_,_) -0044: push_global -0045: [->box { [] }<-|fn foo!|fn update!] (_,_,_,_,_,_,_,_) -0045: set_upvalue -closing over in foo! at 3: fn update! -0046: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0046: constant 00003: fn foos! -0049: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0049: binding `foos!` in sandbox -0051: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0051: stack depth: 3; match depth: 0 -0053: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0053: at stack index: 2 -0055: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0055: new locals: foos@0//0|foo!@1//0|foos!@2//0 -0057: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0057: resolving binding `foo!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0059: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0059: at locals position 1 -0061: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0061: push_binding 001 -0063: [->box { [] }<-|fn foo!|fn foos!|fn foo!] (_,_,_,_,_,_,_,_) -0063: set_upvalue -closing over in foos! at 1: fn foo! -0064: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0064: ***calling function foos! stack depth: 3 -0066: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0066: resolving binding `foos!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0068: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0068: at locals position 2 -0070: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0070: push_binding 002 -0072: [->box { [] }<-|fn foo!|fn foos!|fn foos!] (_,_,_,_,_,_,_,_) -0072: ***after 0 args stack depth: 4 -0074: [->box { [] }<-|fn foo!|fn foos!|fn foos!] (_,_,_,_,_,_,_,_) -0074: call 000 -=== calling into fn foos!/0 === -0000: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0001: ***function clause matching: : () -0003: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0003: match -0004: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0004: jump 00000 -0007: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00042 -0010: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0010: constant 00000: 4 -0013: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0013: truncate -0014: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0014: jump 00001 -0018: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0018: duplicate -0019: [box { [] }|fn foo!|fn foos!|->4<-|4] (_,_,_,_,_,_,_,_) -0019: jump_if_zero 00024 -0022: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0022: ***calling function foo! stack depth: 1 -0024: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0024: resolving binding `foo!` in foos! -locals: -0026: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0026: as enclosing upvalue 0 -0028: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0028: get_upvalue 000 -0030: [box { [] }|fn foo!|fn foos!|->4<-|fn foo!] (_,_,_,_,_,_,_,_) -0030: ***after 0 args stack depth: 2 -0032: [box { [] }|fn foo!|fn foos!|->4<-|fn foo!] (_,_,_,_,_,_,_,_) -0032: tail_call 000 -=== tail call into fn foo!/0 from foos! === -0000: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0001: ***function clause matching: : () -0003: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0003: match -0004: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0004: jump 00000 -0007: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00034 -0010: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0010: ***calling function update! stack depth: 0 -0012: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0012: resolving binding `foos` in foo! -locals: -0014: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0014: as enclosing upvalue 0 -0016: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0016: get_upvalue 000 -0018: [box { [] }|fn foo!|fn foos!|->box { [] }<-] (_,_,_,_,_,_,_,_) -0018: ***calling function append stack depth: 1 -0020: [box { [] }|fn foo!|fn foos!|->box { [] }<-] (_,_,_,_,_,_,_,_) -0020: nothing -0021: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_] (_,_,_,_,_,_,_,_) -0021: constant 00000: :foo -0024: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_) -0024: resolving binding `append` in foo! -locals: -0026: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_) -0026: as enclosing upvalue 1 -0028: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_) -0028: get_upvalue 001 -0030: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo|fn append] (_,_,_,_,_,_,_,_) -0030: partial 002 -0032: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0032: resolving binding `update!` in foo! -locals: -0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0034: as enclosing upvalue 2 -0036: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0036: get_upvalue 002 -0038: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|fn update!] (_,_,_,_,_,_,_,_) -0038: ***after 2 args stack depth: 3 -0040: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|fn update!] (_,_,_,_,_,_,_,_) -0040: tail_call 002 -=== tail call into fn update!/2 from foo! === -0000: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0003: constant 00000: :box -0006: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|:box] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00012 -0010: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0010: match_depth 000 -0012: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0012: constant 00001: :fn -0015: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|:fn] (_,_,_,_,_,_,_,_) -0015: match_type -0016: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0016: jump_if_no_match 00003 -0019: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0019: jump 00000 -0022: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0022: jump_if_no_match 00034 -0025: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0025: push_binding 000 -0027: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|box { [] }] (_,_,_,_,_,_,_,_) -0027: get_upvalue 000 -0029: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|box { [] }|fn unbox] (_,_,_,_,_,_,_,_) -0029: call 001 -=== calling into fn unbox/1 === -0000: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0001: match_depth 000 -0003: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0003: constant 00000: :box -0006: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|:box] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00003 -0010: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0010: jump 00000 -0013: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0013: jump_if_no_match 00015 -0016: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0016: get_upvalue 000 -0018: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|#{:list fn list/base...] (_,_,_,_,_,_,_,_) -0018: constant 00001: :unbox -0021: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|#{:list fn list/base...|:unbox] (_,_,_,_,_,_,_,_) -0021: get_key -0022: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|fn unbox/base] (_,_,_,_,_,_,_,_) -0022: store -0023: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (fn unbox/base,_,_,_,_,_,_,_) -0023: push_binding 000 -0025: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|box { [] }] (fn unbox/base,_,_,_,_,_,_,_) -0025: load -0026: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|box { [] }|fn unbox/base] (_,_,_,_,_,_,_,_) -0026: tail_call 001 -=== tail call into fn unbox/base/1 from unbox === -0028: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|[]] (_,_,_,_,_,_,_,_) -0028: store -0029: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] ([],_,_,_,_,_,_,_) -0029: pop -0030: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial] ([],_,_,_,_,_,_,_) -0030: return -== returning from fn unbox == -0031: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_) -0031: reset_match -0032: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_) -0032: match -0033: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_) -0033: panic_if_no_match -0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_) -0034: push_binding 002 -0036: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[]] (_,_,_,_,_,_,_,_) -0036: push_binding 001 -0038: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[]|fn append/partial] (_,_,_,_,_,_,_,_) -0038: call 001 -=== calling into fn append/partial/1 === -0000: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0003: constant 00000: :list -0006: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|:list] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00009 -0010: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0010: match_depth 000 -0012: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0012: match -0013: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0013: jump_if_no_match 00003 -0016: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0016: jump 00000 -0019: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0019: jump_if_no_match 00018 -0022: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0022: get_upvalue 000 -0024: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|#{:list fn list/base...] (_,_,_,_,_,_,_,_) -0024: constant 00001: :append -0027: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|#{:list fn list/base...|:append] (_,_,_,_,_,_,_,_) -0027: get_key -0028: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|fn append/base] (_,_,_,_,_,_,_,_) -0028: store -0029: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (fn append/base,_,_,_,_,_,_,_) -0029: push_binding 000 -0031: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]] (fn append/base,_,_,_,_,_,_,_) -0031: push_binding 001 -0033: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]|:foo] (fn append/base,_,_,_,_,_,_,_) -0033: load -0034: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]|:foo|fn append/base] (_,_,_,_,_,_,_,_) -0034: tail_call 002 -=== tail call into fn append/base/2 from append === -0036: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[:foo]] (_,_,_,_,_,_,_,_) -0036: store -0037: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] ([:foo],_,_,_,_,_,_,_) -0037: pop_n 002 -0039: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]] ([:foo],_,_,_,_,_,_,_) -0039: return -== returning from fn append == -0040: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_) -0040: reset_match -0041: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_) -0041: match -0042: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_) -0042: panic_if_no_match -0043: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_) -0043: push_binding 000 -0045: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }] (_,_,_,_,_,_,_,_) -0045: push_binding 003 -0047: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }|[:foo]] (_,_,_,_,_,_,_,_) -0047: get_upvalue 001 -0049: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }|[:foo]|fn store!] (_,_,_,_,_,_,_,_) -0049: tail_call 002 -=== tail call into fn store!/2 from update! === -0000: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0003: constant 00000: :box -0006: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|:box] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00009 -0010: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0010: match_depth 000 -0012: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0012: match -0013: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0013: jump_if_no_match 00003 -0016: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0016: jump 00000 -0019: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0019: jump_if_no_match 00023 -0022: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0022: get_upvalue 000 -0024: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|#{:list fn list/base...] (_,_,_,_,_,_,_,_) -0024: constant 00001: :store! -0027: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|#{:list fn list/base...|:store!] (_,_,_,_,_,_,_,_) -0027: get_key -0028: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|fn store!/base] (_,_,_,_,_,_,_,_) -0028: store -0029: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (fn store!/base,_,_,_,_,_,_,_) -0029: push_binding 000 -0031: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }] (fn store!/base,_,_,_,_,_,_,_) -0031: push_binding 001 -0033: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }|[:foo]] (fn store!/base,_,_,_,_,_,_,_) -0033: load -0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }|[:foo]|fn store!/base] (_,_,_,_,_,_,_,_) -0034: call 002 -=== calling into fn store!/base/2 === -0036: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_) -0036: pop -0037: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0037: push_binding 001 -0039: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_) -0039: store -0040: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] ([:foo],_,_,_,_,_,_,_) -0040: load -0041: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_) -0041: store -0042: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] ([:foo],_,_,_,_,_,_,_) -0042: pop_n 002 -0044: [box { [:foo] }|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0044: return -== returning from fn store! == -0076: [->box { [:foo] }<-|fn foo!|fn foos!|[:foo]] (_,_,_,_,_,_,_,_) -0076: pop -0077: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0077: ***calling function unbox stack depth: 3 -0079: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0079: resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0081: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0081: at locals position 0 -0083: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0083: push_binding 000 -0085: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_) -0085: resolving binding `unbox` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0087: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_) -0087: as global -0089: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_) -0089: constant 00004: :unbox -0092: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|:unbox] (_,_,_,_,_,_,_,_) -0092: push_global -0093: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|fn unbox] (_,_,_,_,_,_,_,_) -0093: ***after 1 args stack depth: 5 -0095: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|fn unbox] (_,_,_,_,_,_,_,_) -0095: call 001 -=== calling into fn unbox/1 === -0000: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0001: match_depth 000 -0003: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0003: constant 00000: :box -0006: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|:box] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00003 -0010: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0010: jump 00000 -0013: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0013: jump_if_no_match 00015 -0016: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0016: get_upvalue 000 -0018: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|#{:list fn list/base...] (_,_,_,_,_,_,_,_) -0018: constant 00001: :unbox -0021: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|#{:list fn list/base...|:unbox] (_,_,_,_,_,_,_,_) -0021: get_key -0022: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|fn unbox/base] (_,_,_,_,_,_,_,_) -0022: store -0023: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (fn unbox/base,_,_,_,_,_,_,_) -0023: push_binding 000 -0025: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|box { [:foo] }] (fn unbox/base,_,_,_,_,_,_,_) -0025: load -0026: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|box { [:foo] }|fn unbox/base] (_,_,_,_,_,_,_,_) -0026: tail_call 001 -=== tail call into fn unbox/base/1 from unbox === -0028: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0028: store -0029: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] ([:foo],_,_,_,_,_,_,_) -0029: pop -0030: [box { [:foo] }|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0030: return -== returning from fn unbox == -0097: [->box { [:foo] }<-|fn foo!|fn foos!|[:foo]] (_,_,_,_,_,_,_,_) -0097: store -0098: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0098: leaving scope 0 -0100: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0100: releasing binding foos!@2//0 -0102: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0102: releasing binding foo!@1//0 -0104: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0104: releasing binding foos@0//0 -0106: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0106: ***leaving block before pop stack depth: 3 -0108: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0108: popping back from 3 to 0 -0110: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0110: pop_n 003 -0112: [] ([:foo],_,_,_,_,_,_,_) -0112: load -0112: [] (_,_,_,_,_,_,_,_) -[:foo] diff --git a/scratch/first.txt b/scratch/first.txt deleted file mode 100644 index 26c0300..0000000 --- a/scratch/first.txt +++ /dev/null @@ -1,249 +0,0 @@ -=== vm run: test === -0000: [] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [] (_,_,_,_,_,_,_,_) -0001: constant 00000: 2 -0004: [->2<-] (_,_,_,_,_,_,_,_) -0004: match -0005: [->2<-] (_,_,_,_,_,_,_,_) -0005: panic_if_no_match -0006: [->2<-] (_,_,_,_,_,_,_,_) -0006: push_list -0007: [->2<-|[]] (_,_,_,_,_,_,_,_) -0007: constant 00001: 1 -0010: [->2<-|[]|1] (_,_,_,_,_,_,_,_) -0010: append_list -0011: [->2<-|[1]] (_,_,_,_,_,_,_,_) -0011: constant 00000: 2 -0014: [->2<-|[1]|2] (_,_,_,_,_,_,_,_) -0014: append_list -0015: [->2<-|[1, 2]] (_,_,_,_,_,_,_,_) -0015: constant 00002: 3 -0018: [->2<-|[1, 2]|3] (_,_,_,_,_,_,_,_) -0018: append_list -0019: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0019: ***entering loop with stack depth of 2 -0021: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0021: store_n 001 -0023: [->2<-] ([1, 2, 3],_,_,_,_,_,_,_) -0023: ***after store, stack depth is now 2 -0025: [->2<-] ([1, 2, 3],_,_,_,_,_,_,_) -0025: load -0026: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0026: ***after load, stack depth is now 2 -0028: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0028: reset_match -0029: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0029: match_depth 000 -0031: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0031: match_list 000 -0033: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0033: jump_if_no_match 00006 -0042: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0042: jump_if_no_match 00010 -0055: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0055: reset_match -0056: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0056: match_depth 000 -0058: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0058: match_list 001 -0060: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0060: jump_if_no_match 00012 -0075: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0075: jump_if_no_match 00030 -0108: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0108: reset_match -0109: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0109: match_depth 000 -0111: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0111: match_splatted_list 002 -0113: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0113: jump_if_no_match 00019 -0116: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0116: load_splatted_list 002 -0118: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0118: match_depth 001 -0120: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0120: match -0121: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0121: jump_if_no_match 00010 -0124: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0124: match_depth 000 -0126: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0126: match -0127: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0127: jump_if_no_match 00004 -0130: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0130: jump 00002 -0135: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0135: jump_if_no_match 00068 -0138: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0138: ***before visiting body, the stack depth is 4 -0140: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0140: ***calling function eq? stack depth: 4 -0142: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0142: ***calling function first stack depth: 4 -0144: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0144: resolving binding `xs` in test -0146: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0146: push_binding 003 -0148: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0148: resolving binding `first` in test -0150: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0150: constant 00004: :first -0153: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|:first] (_,_,_,_,_,_,_,_) -0153: push_global -0154: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_) -0154: ***after 1 args stack depth: 6 -0156: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_) -0156: call 001 -=== calling into fn first/1 === -0000: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0001: match_depth 000 -0003: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0003: match_list 000 -0005: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0005: jump_if_no_match 00006 -0014: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0014: jump_if_no_match 00003 -0020: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0020: jump_if_no_match 00005 -0028: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0028: match_depth 000 -0030: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0030: constant 00000: :list -0033: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|:list] (_,_,_,_,_,_,_,_) -0033: match_type -0034: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0034: jump_if_no_match 00003 -0037: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0037: jump 00000 -0040: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0040: jump_if_no_match 00024 -0043: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0043: ***accessing keyword: base :first stack depth: 1 -0045: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0045: resolving binding `base` in first -0047: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0047: get_upvalue 000 -0049: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:rest fn rest/base...] (_,_,_,_,_,_,_,_) -0049: constant 00001: :first -0052: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:rest fn rest/base...|:first] (_,_,_,_,_,_,_,_) -0052: get_key -0053: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_) -0053: ***after keyword access stack depth: 2 -0055: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_) -0055: stash -0056: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (fn first/base,_,_,_,_,_,_,_) -0056: pop -0057: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_) -0057: resolving binding `xs` in first -0059: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_) -0059: push_binding 000 -0061: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]] (fn first/base,_,_,_,_,_,_,_) -0061: load -0062: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]|fn first/base] (_,_,_,_,_,_,_,_) -0062: tail_call 001 -=== tail call into fn first/base/1 from first === -0158: [->2<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_) -0158: resolving binding `test` in test -0160: [->2<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_) -0160: push_binding 000 -0162: [->2<-|[1, 2, 3]|1|[2, 3]|2|2] (_,_,_,_,_,_,_,_) -0162: resolving binding `eq?` in test -0164: [->2<-|[1, 2, 3]|1|[2, 3]|2|2] (_,_,_,_,_,_,_,_) -0164: constant 00003: :eq? -0167: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|:eq?] (_,_,_,_,_,_,_,_) -0167: push_global -0168: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|fn eq?] (_,_,_,_,_,_,_,_) -0168: ***after 2 args stack depth: 7 -0170: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|fn eq?] (_,_,_,_,_,_,_,_) -0170: call 002 -=== calling into fn eq?/2 === -0000: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0003: match -0004: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0004: jump_if_no_match 00009 -0007: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0007: match_depth 000 -0009: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0009: match -0010: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0010: jump_if_no_match 00003 -0013: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0013: jump 00000 -0016: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0016: jump_if_no_match 00029 -0019: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0019: ***accessing keyword: base :eq? stack depth: 2 -0021: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0021: resolving binding `base` in eq? -0023: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0023: get_upvalue 000 -0025: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|#{:rest fn rest/base...] (_,_,_,_,_,_,_,_) -0025: constant 00000: :eq? -0028: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|#{:rest fn rest/base...|:eq?] (_,_,_,_,_,_,_,_) -0028: get_key -0029: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (_,_,_,_,_,_,_,_) -0029: ***after keyword access stack depth: 3 -0031: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (_,_,_,_,_,_,_,_) -0031: stash -0032: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (fn eq?/base,_,_,_,_,_,_,_) -0032: pop -0033: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (fn eq?/base,_,_,_,_,_,_,_) -0033: resolving binding `x` in eq? -0035: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (fn eq?/base,_,_,_,_,_,_,_) -0035: push_binding 000 -0037: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2] (fn eq?/base,_,_,_,_,_,_,_) -0037: resolving binding `y` in eq? -0039: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2] (fn eq?/base,_,_,_,_,_,_,_) -0039: push_binding 001 -0041: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2|2] (fn eq?/base,_,_,_,_,_,_,_) -0041: load -0042: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2|2|fn eq?/base] (_,_,_,_,_,_,_,_) -0042: tail_call 002 -=== tail call into fn eq?/base/2 from eq? === -0172: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_) -0172: jump_if_false 00004 -0175: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0175: true -0176: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_) -0176: jump 00018 -0197: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_) -0197: ***after visiting loop body, the stack depth is 5 -0199: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_) -0199: store -0200: [->2<-|[1, 2, 3]|1|[2, 3]|_] (true,_,_,_,_,_,_,_) -0200: pop -0201: [->2<-|[1, 2, 3]|1|[2, 3]] (true,_,_,_,_,_,_,_) -0201: pop -0202: [->2<-|[1, 2, 3]|1] (true,_,_,_,_,_,_,_) -0202: pop -0203: [->2<-|[1, 2, 3]] (true,_,_,_,_,_,_,_) -0203: jump 00001 -0207: [->2<-|[1, 2, 3]] (true,_,_,_,_,_,_,_) -0207: load -0208: [->2<-|[1, 2, 3]|true] (_,_,_,_,_,_,_,_) -0208: store -0209: [->2<-|[1, 2, 3]|_] (true,_,_,_,_,_,_,_) -0209: pop_n 002 -0211: [->2<-] (true,_,_,_,_,_,_,_) -0211: load -0212: [->2<-] (_,_,_,_,_,_,_,_) -true - -********** -********** - - - - - - - diff --git a/scratch/second.txt b/scratch/second.txt deleted file mode 100644 index f327df0..0000000 --- a/scratch/second.txt +++ /dev/null @@ -1,291 +0,0 @@ -=== vm run: test === -0000: [] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [] (_,_,_,_,_,_,_,_) -0001: constant 00000: 4 -0004: [->4<-] (_,_,_,_,_,_,_,_) -0004: match -0005: [->4<-] (_,_,_,_,_,_,_,_) -0005: panic_if_no_match -0006: [->4<-] (_,_,_,_,_,_,_,_) -0006: push_list -0007: [->4<-|[]] (_,_,_,_,_,_,_,_) -0007: constant 00001: 1 -0010: [->4<-|[]|1] (_,_,_,_,_,_,_,_) -0010: append_list -0011: [->4<-|[1]] (_,_,_,_,_,_,_,_) -0011: constant 00002: 2 -0014: [->4<-|[1]|2] (_,_,_,_,_,_,_,_) -0014: append_list -0015: [->4<-|[1, 2]] (_,_,_,_,_,_,_,_) -0015: constant 00003: 3 -0018: [->4<-|[1, 2]|3] (_,_,_,_,_,_,_,_) -0018: append_list -0019: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0019: ***entering loop with stack depth of 2 -0021: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0021: store_n 001 -0023: [->4<-] ([1, 2, 3],_,_,_,_,_,_,_) -0023: ***after store, stack depth is now 2 -0025: [->4<-] ([1, 2, 3],_,_,_,_,_,_,_) -0025: load -0026: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0026: ***after load, stack depth is now 2 -0028: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0028: reset_match -0029: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0029: match_depth 000 -0031: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0031: match_list 000 -0033: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0033: jump_if_no_match 00006 -0042: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0042: jump_if_no_match 00010 -0055: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0055: reset_match -0056: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0056: match_depth 000 -0058: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0058: match_list 001 -0060: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0060: jump_if_no_match 00012 -0075: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0075: jump_if_no_match 00030 -0108: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0108: reset_match -0109: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0109: match_depth 000 -0111: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0111: match_splatted_list 002 -0113: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0113: jump_if_no_match 00019 -0116: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0116: load_splatted_list 002 -0118: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0118: match_depth 001 -0120: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0120: match -0121: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0121: jump_if_no_match 00010 -0124: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0124: match_depth 000 -0126: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0126: match -0127: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0127: jump_if_no_match 00004 -0130: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0130: jump 00002 -0135: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0135: jump_if_no_match 00068 -0138: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0138: ***before visiting body, the stack depth is 4 -0140: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0140: ***calling function eq? stack depth: 4 -0142: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0142: ***calling function first stack depth: 4 -0144: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0144: resolving binding `xs` in test -0146: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0146: push_binding 003 -0148: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0148: resolving binding `first` in test -0150: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0150: constant 00005: :first -0153: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|:first] (_,_,_,_,_,_,_,_) -0153: push_global -0154: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_) -0154: ***after 1 args stack depth: 6 -0156: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_) -0156: call 001 -=== calling into fn first/1 === -0000: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0001: match_depth 000 -0003: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0003: match_list 000 -0005: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0005: jump_if_no_match 00006 -0014: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0014: jump_if_no_match 00003 -0020: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0020: jump_if_no_match 00005 -0028: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0028: match_depth 000 -0030: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0030: constant 00000: :list -0033: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|:list] (_,_,_,_,_,_,_,_) -0033: match_type -0034: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0034: jump_if_no_match 00003 -0037: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0037: jump 00000 -0040: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0040: jump_if_no_match 00024 -0043: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0043: ***accessing keyword: base :first stack depth: 1 -0045: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0045: resolving binding `base` in first -0047: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0047: get_upvalue 000 -0049: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:append fn append/...] (_,_,_,_,_,_,_,_) -0049: constant 00001: :first -0052: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:append fn append/...|:first] (_,_,_,_,_,_,_,_) -0052: get_key? -0053: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_) -0053: ***after keyword access stack depth: 2 -0055: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_) -0055: stash -0056: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (fn first/base,_,_,_,_,_,_,_) -0056: pop -0057: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_) -0057: resolving binding `xs` in first -0059: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_) -0059: push_binding 000 -0061: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]] (fn first/base,_,_,_,_,_,_,_) -0061: load -0062: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]|fn first/base] (_,_,_,_,_,_,_,_) -0062: tail_call 001 -=== tail call into fn first/base/1 from first === -0158: [->4<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_) -0158: resolving binding `test` in test -0160: [->4<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_) -0160: push_binding 000 -0162: [->4<-|[1, 2, 3]|1|[2, 3]|2|4] (_,_,_,_,_,_,_,_) -0162: resolving binding `eq?` in test -0164: [->4<-|[1, 2, 3]|1|[2, 3]|2|4] (_,_,_,_,_,_,_,_) -0164: constant 00004: :eq? -0167: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|:eq?] (_,_,_,_,_,_,_,_) -0167: push_global -0168: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|fn eq?] (_,_,_,_,_,_,_,_) -0168: ***after 2 args stack depth: 7 -0170: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|fn eq?] (_,_,_,_,_,_,_,_) -0170: call 002 -=== calling into fn eq?/2 === -0000: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0003: match -0004: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0004: jump_if_no_match 00009 -0007: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0007: match_depth 000 -0009: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0009: match -0010: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0010: jump_if_no_match 00003 -0013: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0013: jump 00000 -0016: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0016: jump_if_no_match 00029 -0019: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0019: ***accessing keyword: base :eq? stack depth: 2 -0021: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0021: resolving binding `base` in eq? -0023: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0023: get_upvalue 000 -0025: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|#{:append fn append/...] (_,_,_,_,_,_,_,_) -0025: constant 00000: :eq? -0028: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|#{:append fn append/...|:eq?] (_,_,_,_,_,_,_,_) -0028: get_key -0029: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (_,_,_,_,_,_,_,_) -0029: ***after keyword access stack depth: 3 -0031: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (_,_,_,_,_,_,_,_) -0031: stash -0032: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (fn eq?/base,_,_,_,_,_,_,_) -0032: pop -0033: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (fn eq?/base,_,_,_,_,_,_,_) -0033: resolving binding `x` in eq? -0035: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (fn eq?/base,_,_,_,_,_,_,_) -0035: push_binding 000 -0037: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2] (fn eq?/base,_,_,_,_,_,_,_) -0037: resolving binding `y` in eq? -0039: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2] (fn eq?/base,_,_,_,_,_,_,_) -0039: push_binding 001 -0041: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2|4] (fn eq?/base,_,_,_,_,_,_,_) -0041: load -0042: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2|4|fn eq?/base] (_,_,_,_,_,_,_,_) -0042: tail_call 002 -=== tail call into fn eq?/base/2 from eq? === -0172: [->4<-|[1, 2, 3]|1|[2, 3]|false] (_,_,_,_,_,_,_,_) -0172: jump_if_false 00004 -0179: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0179: before visiting recur args the compiler thinks the stack depth is 5 -0181: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0181: recur arg: 0 -0183: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0183: resolving binding `xs` in test -0185: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0185: push_binding 003 -0187: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0187: after visiting recur args the compiler thinks the stack depth is 6 -0189: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0189: store_n 001 -0191: [->4<-|[1, 2, 3]|1|[2, 3]] ([2, 3],_,_,_,_,_,_,_) -0191: pop_n 004 -0193: [] ([2, 3],_,_,_,_,_,_,_) -0193: load -0194: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0194: jump_back 00168 -0026: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0026: ***after load, stack depth is now 2 -0028: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0028: reset_match -0029: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0029: match_depth 000 -0031: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0031: match_list 000 -0033: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0033: jump_if_no_match 00006 -0042: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0042: jump_if_no_match 00010 -0055: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0055: reset_match -0056: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0056: match_depth 000 -0058: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0058: match_list 001 -0060: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0060: jump_if_no_match 00012 -0075: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0075: jump_if_no_match 00030 -0108: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0108: reset_match -0109: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0109: match_depth 000 -0111: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0111: match_splatted_list 002 -0113: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0113: jump_if_no_match 00019 -0116: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0116: load_splatted_list 002 -0118: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0118: match_depth 001 -0120: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0120: match -0121: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0121: jump_if_no_match 00010 -0124: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0124: match_depth 000 -0126: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0126: match -0127: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0127: jump_if_no_match 00004 -0130: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0130: jump 00002 -0135: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0135: jump_if_no_match 00068 -0138: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0138: ***before visiting body, the stack depth is 4 -0140: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0140: ***calling function eq? stack depth: 4 -0142: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0142: ***calling function first stack depth: 4 -0144: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0144: resolving binding `xs` in test -0146: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0146: push_binding 003 -thread 'main' panicked at src/vm.rs:313:51: -index out of bounds: the len is 3 but the index is 3 diff --git a/src/base.rs b/src/base.rs deleted file mode 100644 index 9a579b1..0000000 --- a/src/base.rs +++ /dev/null @@ -1,632 +0,0 @@ -use crate::value::*; -use imbl::*; -use ran::ran_f64; -use std::rc::Rc; - -#[derive(Clone, Debug)] -pub enum BaseFn { - Nullary(&'static str, fn() -> Value), - Unary(&'static str, fn(&Value) -> Value), - Binary(&'static str, fn(&Value, &Value) -> Value), - Ternary(&'static str, fn(&Value, &Value, &Value) -> Value), -} - -pub fn eq(x: &Value, y: &Value) -> Value { - if x == y { - Value::True - } else { - Value::False - } -} - -pub fn add(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x + y), - _ => unreachable!("internal Ludus error: wrong arguments to base add: {x}, {y}"), - } -} - -pub fn sub(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x - y), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn unbox(x: &Value) -> Value { - match x { - Value::Box(cell) => cell.as_ref().borrow().clone(), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn store(b: &Value, val: &Value) -> Value { - if let Value::Box(cell) = b { - cell.replace(val.clone()); - val.clone() - } else { - unreachable!("internal Ludus error") - } -} - -// TODO: do better than returning just the docstr -// name, patterns, AND docstring -pub fn doc(f: &Value) -> Value { - match f { - Value::Fn(f) => f.as_ref().doc(), - _ => Value::Interned("no documentation found"), - } -} - -pub fn assoc(dict: &Value, key: &Value, value: &Value) -> Value { - match (dict, key) { - (Value::Dict(d), Value::Keyword(k)) => Value::Dict(Box::new(d.update(k, value.clone()))), - _ => unreachable!("internal Ludus error calling assoc with ({dict}, {key}, {value})"), - } -} - -pub fn r#bool(x: &Value) -> Value { - match x { - Value::Nil | Value::False => Value::False, - _ => Value::True, - } -} - -pub fn chars(x: &Value) -> Value { - match x { - Value::Interned(s) => { - let chars = s.chars(); - - let mut charlist = vector![]; - for char in chars { - if char.is_ascii() { - charlist.push_back(Value::String(Rc::new(char.to_string()))) - } else { - return Value::Tuple(Rc::new(vec![ - Value::Keyword("err"), - Value::String(Rc::new(format!("{char} is not an ascii character"))), - ])); - } - } - Value::Tuple(Rc::new(vec![ - Value::Keyword("ok"), - Value::List(Box::new(charlist)), - ])) - } - Value::String(s) => { - let chars = s.chars(); - - let mut charlist = vector![]; - for char in chars { - if char.is_ascii() { - charlist.push_back(Value::String(Rc::new(char.to_string()))) - } else { - return Value::Tuple(Rc::new(vec![ - Value::Keyword("err"), - Value::String(Rc::new(format!("{char} is not an ascii character"))), - ])); - } - } - Value::Tuple(Rc::new(vec![ - Value::Keyword("ok"), - Value::List(Box::new(charlist)), - ])) - } - _ => unreachable!("internal Ludus error"), - } -} - -// TODO: figure out how to get to opportunistic mutation here -pub fn concat(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Interned(x), Value::Interned(y)) => Value::String(Rc::new(format!("{x}{y}"))), - (Value::String(x), Value::String(y)) => Value::String(Rc::new(format!("{x}{y}"))), - (Value::String(x), Value::Interned(y)) => Value::String(Rc::new(format!("{x}{y}"))), - (Value::Interned(x), Value::String(y)) => Value::String(Rc::new(format!("{x}{y}"))), - (Value::List(x), Value::List(y)) => { - let mut newlist = *x.clone(); - newlist.append(*y.clone()); - Value::List(Box::new(newlist)) - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn append(x: &Value, y: &Value) -> Value { - match x { - Value::List(list) => { - let mut newlist = list.clone(); - newlist.push_back(y.clone()); - Value::List(newlist) - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn dec(x: &Value) -> Value { - match x { - Value::Number(n) => Value::Number(n - 1.0), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn inc(x: &Value) -> Value { - match x { - Value::Number(n) => Value::Number(n + 1.0), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn div(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x / y), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn mult(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x * y), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn dissoc(dict: &Value, key: &Value) -> Value { - match (dict, key) { - (Value::Dict(dict), Value::Keyword(key)) => { - let mut new = dict.clone(); - new.remove(key); - Value::Dict(new) - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn first(ordered: &Value) -> Value { - match ordered { - Value::List(list) => match list.front() { - Some(n) => n.clone(), - None => Value::Nil, - }, - Value::Tuple(tuple) => match tuple.first() { - Some(n) => n.clone(), - None => Value::Nil, - }, - _ => unreachable!("internal Ludus error"), - } -} - -// TODO: figure out how to handle negative numbers -// the cast from f64 to usize discards sign info -pub fn at(ordered: &Value, i: &Value) -> Value { - match (ordered, i) { - (Value::List(list), Value::Number(n)) => { - let i = *n as usize; - match list.get(i) { - Some(n) => n.clone(), - None => Value::Nil, - } - } - (Value::Tuple(tuple), Value::Number(n)) => { - let i = *n as usize; - match tuple.get(i) { - Some(n) => n.clone(), - None => Value::Nil, - } - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn get(dict: &Value, key: &Value) -> Value { - match (dict, key) { - (Value::Dict(dict), Value::Keyword(key)) => match dict.get(key) { - Some(x) => x.clone(), - None => Value::Nil, - }, - _ => unreachable!("internal Ludus error"), - } -} - -pub fn last(ordered: &Value) -> Value { - match ordered { - Value::List(list) => match list.last() { - Some(x) => x.clone(), - None => Value::Nil, - }, - Value::Tuple(tuple) => match tuple.last() { - Some(x) => x.clone(), - None => Value::Nil, - }, - _ => unreachable!("internal Ludus error"), - } -} - -// TODO: fix this: x is a list of all the args passed to Ludus's print! -pub fn print(x: &Value) -> Value { - let Value::List(args) = x else { - unreachable!("internal Ludus error") - }; - let out = args - .iter() - .map(|val| format!("{val}")) - .collect::>() - .join(" "); - println!("{out}"); - Value::Keyword("ok") -} - -pub fn show(x: &Value) -> Value { - Value::String(Rc::new(format!("{x}"))) -} - -pub fn rest(ordered: &Value) -> Value { - match ordered { - Value::List(list) => Value::List(Box::new(list.clone().split_at(1).1)), - Value::Tuple(tuple) => { - Value::List(Box::new(Vector::from_iter(tuple.iter().next().cloned()))) - } - Value::Interned(str) => Value::String(Rc::new(str.get(1..).unwrap_or("").to_string())), - Value::String(str) => Value::String(Rc::new( - str.clone().as_str().get(1..).unwrap_or("").to_string(), - )), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn count(coll: &Value) -> Value { - match coll { - Value::Dict(d) => Value::Number(d.len() as f64), - Value::List(l) => Value::Number(l.len() as f64), - Value::Tuple(t) => Value::Number(t.len() as f64), - Value::String(s) => Value::Number(s.len() as f64), - Value::Interned(s) => Value::Number(s.len() as f64), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn range(start: &Value, end: &Value) -> Value { - match (start, end) { - (Value::Number(start), Value::Number(end)) => { - let start = *start as isize; - let end = *end as isize; - let mut range = Vector::new(); - for n in start..end { - range.push_back(Value::Number(n as f64)) - } - Value::List(Box::new(range)) - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value { - match (ordered, start, end) { - (Value::List(list), Value::Number(start), Value::Number(end)) => { - let mut newlist = list.clone(); - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, list.len()); - Value::List(Box::new(newlist.slice(start..end))) - } - // TODO: figure out something better to do than return an empty string on a bad slice - (Value::String(string), Value::Number(start), Value::Number(end)) => { - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, string.len()); - Value::String(Rc::new( - string - .clone() - .as_str() - .get(start..end) - .unwrap_or("") - .to_string(), - )) - } - (Value::Interned(string), Value::Number(start), Value::Number(end)) => { - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, string.len()); - Value::String(Rc::new(string.get(start..end).unwrap_or("").to_string())) - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn list(x: &Value) -> Value { - match x { - Value::List(_) => x.clone(), - Value::Tuple(t) => Value::List(Box::new(Vector::from_iter(t.iter().cloned()))), - Value::Dict(d) => { - let kvs = d.iter(); - let mut list = vector![]; - for (key, value) in kvs { - let kv = Value::Tuple(Rc::new(vec![Value::Keyword(key), value.clone()])); - list.push_back(kv); - } - Value::List(Box::new(list)) - } - _ => Value::List(Box::new(vector![x.clone()])), - } -} - -pub fn number(x: &Value) -> Value { - match x { - Value::Interned(string) => match string.parse::() { - Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])), - Err(_) => Value::Tuple(Rc::new(vec![ - Value::Keyword("err"), - Value::String(Rc::new(format!("could not parse `{string}` as a number"))), - ])), - }, - Value::String(string) => match string.parse::() { - Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])), - Err(_) => Value::Tuple(Rc::new(vec![ - Value::Keyword("err"), - Value::String(Rc::new(format!("could not parse `{string}` as a number"))), - ])), - }, - _ => unreachable!("internal Ludus error"), - } -} - -pub fn r#type(x: &Value) -> Value { - match x { - Value::Nil => Value::Keyword("nil"), - Value::Number(_) => Value::Keyword("number"), - Value::True | Value::False => Value::Keyword("bool"), - Value::Keyword(_) => Value::Keyword("keyword"), - Value::Tuple(_) => Value::Keyword("tuple"), - Value::Interned(_) => Value::Keyword("string"), - Value::String(_) => Value::Keyword("string"), - Value::List(_) => Value::Keyword("list"), - Value::Dict(_) => Value::Keyword("dict"), - Value::Fn(_) => Value::Keyword("fn"), - Value::Box(_) => Value::Keyword("box"), - Value::BaseFn(_) => Value::Keyword("fn"), - Value::Partial(_) => Value::Keyword("fn"), - Value::Nothing => unreachable!(), - } -} - -pub fn split(source: &Value, splitter: &Value) -> Value { - match (source, splitter) { - (Value::String(source), Value::String(splitter)) => { - println!("splitting {source} with {splitter}"); - let parts = source.split_terminator(splitter.as_str()); - let mut list = vector![]; - for part in parts { - list.push_back(Value::String(Rc::new(part.to_string()))); - } - Value::List(Box::new(list)) - } - (Value::String(_), Value::Interned(splitter)) => { - split(source, &Value::String(Rc::new(splitter.to_string()))) - } - (Value::Interned(source), Value::String(_)) => { - split(&Value::String(Rc::new(source.to_string())), splitter) - } - (Value::Interned(source), Value::Interned(splitter)) => { - let source = Value::String(Rc::new(source.to_string())); - let splitter = Value::String(Rc::new(splitter.to_string())); - split(&source, &splitter) - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn upcase(string: &Value) -> Value { - match string { - Value::String(string) => Value::String(Rc::new(string.to_uppercase())), - Value::Interned(string) => Value::String(Rc::new(string.to_uppercase())), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn downcase(string: &Value) -> Value { - match string { - Value::String(string) => Value::String(Rc::new(string.to_lowercase())), - Value::Interned(string) => Value::String(Rc::new(string.to_lowercase())), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn trim(string: &Value) -> Value { - match string { - Value::String(string) => Value::String(Rc::new(string.trim().to_string())), - Value::Interned(string) => Value::String(Rc::new(string.trim().to_string())), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn triml(string: &Value) -> Value { - match string { - Value::String(string) => Value::String(Rc::new(string.trim_start().to_string())), - Value::Interned(string) => Value::String(Rc::new(string.trim_start().to_string())), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn trimr(string: &Value) -> Value { - match string { - Value::String(string) => Value::String(Rc::new(string.trim_end().to_string())), - Value::Interned(string) => Value::String(Rc::new(string.trim_end().to_string())), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn atan_2(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn ceil(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.ceil()), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn cos(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.cos()), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn floor(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.floor()), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn random() -> Value { - Value::Number(ran_f64()) -} - -pub fn round(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.round()), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn sin(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.sin()), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn sqrt(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.sqrt()), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn tan(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.tan()), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn gt(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => { - if x > y { - Value::True - } else { - Value::False - } - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn gte(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => { - if x >= y { - Value::True - } else { - Value::False - } - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn lt(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => { - if x < y { - Value::True - } else { - Value::False - } - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn lte(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => { - if x <= y { - Value::True - } else { - Value::False - } - } - _ => unreachable!("internal Ludus error"), - } -} - -pub fn r#mod(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x % y), - _ => unreachable!("internal Ludus error"), - } -} - -pub fn make_base() -> Value { - let members = vec![ - ("add", Value::BaseFn(BaseFn::Binary("add", add))), - ("append", Value::BaseFn(BaseFn::Binary("append", append))), - ("assoc", Value::BaseFn(BaseFn::Ternary("assoc", assoc))), - ("at", Value::BaseFn(BaseFn::Binary("at", at))), - ("atan_2", Value::BaseFn(BaseFn::Binary("atan_2", atan_2))), - ("bool", Value::BaseFn(BaseFn::Unary("bool", r#bool))), - ("ceil", Value::BaseFn(BaseFn::Unary("ceil", ceil))), - ("chars", Value::BaseFn(BaseFn::Unary("chars", chars))), - ("concat", Value::BaseFn(BaseFn::Binary("concat", concat))), - ("cos", Value::BaseFn(BaseFn::Unary("cos", cos))), - ("count", Value::BaseFn(BaseFn::Unary("count", count))), - ("dec", Value::BaseFn(BaseFn::Unary("dec", dec))), - ("dissoc", Value::BaseFn(BaseFn::Binary("dissoc", dissoc))), - ("div", Value::BaseFn(BaseFn::Binary("div", div))), - ("doc!", Value::BaseFn(BaseFn::Unary("doc!", doc))), - ( - "downcase", - Value::BaseFn(BaseFn::Unary("downcase", downcase)), - ), - ("eq?", Value::BaseFn(BaseFn::Binary("eq?", eq))), - ("first", Value::BaseFn(BaseFn::Unary("first", first))), - ("floor", Value::BaseFn(BaseFn::Unary("floor", floor))), - ("get", Value::BaseFn(BaseFn::Binary("get", get))), - ("gt?", Value::BaseFn(BaseFn::Binary("gt?", gt))), - ("gte?", Value::BaseFn(BaseFn::Binary("gte?", gte))), - ("inc", Value::BaseFn(BaseFn::Unary("inc", inc))), - ("last", Value::BaseFn(BaseFn::Unary("last", last))), - ("list", Value::BaseFn(BaseFn::Unary("list", list))), - ("lt?", Value::BaseFn(BaseFn::Binary("lt?", lt))), - ("lte?", Value::BaseFn(BaseFn::Binary("lte?", lte))), - ("mod", Value::BaseFn(BaseFn::Binary("mod", r#mod))), - ("mult", Value::BaseFn(BaseFn::Binary("mult", mult))), - ("number", Value::BaseFn(BaseFn::Unary("number", number))), - ("pi", Value::Number(std::f64::consts::PI)), - ("print!", Value::BaseFn(BaseFn::Unary("print!", print))), - ("random", Value::BaseFn(BaseFn::Nullary("random", random))), - ("range", Value::BaseFn(BaseFn::Binary("range", range))), - ("rest", Value::BaseFn(BaseFn::Unary("rest", rest))), - ("round", Value::BaseFn(BaseFn::Unary("round", round))), - ("show", Value::BaseFn(BaseFn::Unary("show", show))), - ("sin", Value::BaseFn(BaseFn::Unary("sin", sin))), - ("slice", Value::BaseFn(BaseFn::Ternary("slice", slice))), - ("split", Value::BaseFn(BaseFn::Binary("split", split))), - ("sqrt", Value::BaseFn(BaseFn::Unary("sqrt", sqrt))), - ("sqrt_2", Value::Number(std::f64::consts::SQRT_2)), - ("store!", Value::BaseFn(BaseFn::Binary("store!", store))), - ("sub", Value::BaseFn(BaseFn::Binary("sub", sub))), - ("tan", Value::BaseFn(BaseFn::Unary("tan", tan))), - ("trim", Value::BaseFn(BaseFn::Unary("trim", trim))), - ("triml", Value::BaseFn(BaseFn::Unary("triml", triml))), - ("trimr", Value::BaseFn(BaseFn::Unary("trimr", trimr))), - ("type", Value::BaseFn(BaseFn::Unary("type", r#type))), - ("unbox", Value::BaseFn(BaseFn::Unary("unbox", unbox))), - ("upcase", Value::BaseFn(BaseFn::Unary("upcase", upcase))), - ]; - Value::Dict(Box::new(HashMap::from(members))) -} diff --git a/src/chunk.rs b/src/chunk.rs deleted file mode 100644 index 6551278..0000000 --- a/src/chunk.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::op::Op; -use crate::value::Value; -use imbl::HashMap; -use num_traits::FromPrimitive; -use regex::Regex; - -#[derive(Clone, Debug)] -pub struct StrPattern { - pub words: Vec, - pub re: Regex, -} - -#[derive(Clone, Debug)] -pub struct Chunk { - pub constants: Vec, - pub bytecode: Vec, - pub keywords: Vec<&'static str>, - pub string_patterns: Vec, - pub env: HashMap<&'static str, Value>, - pub msgs: Vec, -} - -impl Chunk { - pub fn dissasemble_instr(&self, i: &mut usize) { - let op = Op::from_u8(self.bytecode[*i]).unwrap(); - use Op::*; - match op { - Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse - | PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf - | Duplicate | Decrement | ToInt | Noop | LoadTuple | LoadList | Eq | Add | Sub - | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString - | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print - | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing - | PushGlobal | SetUpvalue => { - println!("{i:04}: {op}") - } - Constant | MatchConstant => { - let high = self.bytecode[*i + 1]; - let low = self.bytecode[*i + 2]; - let idx = ((high as usize) << 8) + low as usize; - let value = &self.constants[idx].show(); - println!("{i:04}: {:16} {idx:05}: {value}", op.to_string()); - *i += 2; - } - Msg => { - let msg_idx = self.bytecode[*i + 1]; - let msg = &self.msgs[msg_idx as usize]; - println!("{i:04}: {msg}"); - *i += 1; - } - PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList - | MatchSplattedList | LoadSplattedList | MatchDict | MatchSplattedDict - | DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN - | Call | GetUpvalue | Partial | MatchString | PushStringMatches | TailCall | LoadN => { - let next = self.bytecode[*i + 1]; - println!("{i:04}: {:16} {next:03}", op.to_string()); - *i += 1; - } - Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | JumpBack - | JumpIfZero => { - let high = self.bytecode[*i + 1]; - let low = self.bytecode[*i + 2]; - let len = ((high as u16) << 8) + low as u16; - println!("{i:04}: {:16} {len:05}", op.to_string()); - *i += 2; - } - } - } - - pub fn dissasemble(&self) { - println!("IDX | CODE | INFO"); - let mut i = 0; - while i < self.bytecode.len() { - self.dissasemble_instr(&mut i); - i += 1; - } - } - - // pub fn kw_from(&self, kw: &str) -> Option { - // self.kw_index_from(kw).map(Value::Keyword) - // } - - // pub fn kw_index_from(&self, kw: &str) -> Option { - // self.keywords.iter().position(|s| *s == kw) - // } -} diff --git a/src/compiler.rs b/src/compiler.rs deleted file mode 100644 index 7a03288..0000000 --- a/src/compiler.rs +++ /dev/null @@ -1,1441 +0,0 @@ -use crate::chunk::{Chunk, StrPattern}; -use crate::op::Op; -use crate::parser::Ast; -use crate::parser::StringPart; -use crate::spans::Spanned; -use crate::value::*; -use chumsky::prelude::SimpleSpan; -use regex::Regex; -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; - -#[derive(Clone, Debug, PartialEq)] -pub struct Binding { - name: &'static str, - depth: isize, - stack_pos: usize, -} - -impl std::fmt::Display for Binding { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}@{}//{}", self.name, self.stack_pos, self.depth) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Upvalue { - name: &'static str, - stack_pos: usize, -} - -#[derive(Debug, Clone, PartialEq)] -struct LoopInfo { - start: usize, - stack_root: usize, -} - -impl LoopInfo { - fn new(start: usize, stack_root: usize) -> LoopInfo { - LoopInfo { start, stack_root } - } -} - -fn get_builtin(name: &str, arity: usize) -> Option { - // match (name, arity) { - // ("type", 1) => Some(Op::TypeOf), - // ("eq?", 2) => Some(Op::Eq), - // ("add", 2) => Some(Op::Add), - // ("sub", 2) => Some(Op::Sub), - // ("mult", 2) => Some(Op::Mult), - // ("div", 2) => Some(Op::Div), - // ("unbox", 1) => Some(Op::Unbox), - // ("store!", 2) => Some(Op::BoxStore), - // ("assert!", 1) => Some(Op::Assert), - // ("get", 2) => Some(Op::Get), - // ("at", 2) => Some(Op::At), - // ("not", 1) => Some(Op::Not), - // ("print!", 1) => Some(Op::Print), - - // _ => None, - // } - None -} - -#[derive(Debug, Clone)] -pub struct Compiler { - pub chunk: Chunk, - pub bindings: Vec, - pub scope_depth: isize, - pub match_depth: usize, - pub stack_depth: usize, - pub spans: Vec, - pub nodes: Vec<&'static Ast>, - pub ast: &'static Ast, - pub span: SimpleSpan, - pub src: &'static str, - pub name: &'static str, - pub depth: usize, - pub upvalues: Vec<&'static str>, - loop_info: Vec, - tail_pos: bool, - debug: bool, -} - -fn is_binding(expr: &Spanned) -> bool { - let (ast, _) = expr; - use Ast::*; - match ast { - Let(..) | LBox(..) | FnDeclaration(..) => true, - Fn(name, ..) => !name.is_empty(), - _ => false, - } -} - -fn has_placeholder(args: &[Spanned]) -> bool { - args.iter().any(|arg| matches!(arg, (Ast::Placeholder, _))) -} - -impl Compiler { - pub fn new( - ast: &'static Spanned, - name: &'static str, - src: &'static str, - depth: usize, - env: imbl::HashMap<&'static str, Value>, - debug: bool, - ) -> Compiler { - let chunk = Chunk { - constants: vec![], - bytecode: vec![], - keywords: vec![], - string_patterns: vec![], - env, - msgs: vec![], - }; - Compiler { - chunk, - bindings: vec![], - depth, - scope_depth: -1, - match_depth: 0, - stack_depth: 0, - spans: vec![], - nodes: vec![], - ast: &ast.0, - span: ast.1, - loop_info: vec![], - upvalues: vec![], - src, - name, - tail_pos: false, - debug, - } - } - - pub fn visit(&mut self, node: &'static Spanned) { - let root_node = self.ast; - let root_span = self.span; - let (ast, span) = node; - self.ast = ast; - self.span = *span; - self.compile(); - self.ast = root_node; - self.span = root_span; - } - - fn jump(&mut self, op: Op, len: usize) { - let low = len as u8; - let high = (len >> 8) as u8; - self.emit_op(op); - self.chunk.bytecode.push(high); - self.chunk.bytecode.push(low); - } - - fn stub_jump(&mut self, op: Op) -> usize { - use Op::*; - match op { - JumpIfFalse | JumpIfTrue | JumpIfZero => self.stack_depth -= 1, - _ => (), - } - let out = self.chunk.bytecode.len(); - self.emit_op(op); - self.emit_byte(0xff); - self.emit_byte(0xff); - out - } - - fn patch_jump(&mut self, i: usize, len: usize) { - let low = len as u8; - let high = (len >> 8) as u8; - self.chunk.bytecode[i + 1] = high; - self.chunk.bytecode[i + 2] = low; - } - - pub fn emit_constant(&mut self, val: Value) { - let const_idx = if let Some(idx) = self.chunk.constants.iter().position(|v| *v == val) { - idx - } else { - self.chunk.constants.push(val); - self.chunk.constants.len() - 1 - }; - - if const_idx > u16::MAX as usize { - panic!( - "internal Ludus compiler error: too many constants in chunk:{}:: {}", - self.span, self.ast - ) - } - self.emit_op(Op::Constant); - let low = const_idx as u8; - let high = (const_idx >> 8) as u8; - self.chunk.bytecode.push(high); - self.chunk.bytecode.push(low); - self.stack_depth += 1; - } - - fn reset_match(&mut self) { - self.emit_op(Op::ResetMatch); - self.match_depth = 0; - } - - fn match_constant(&mut self, val: Value) { - let const_idx = match self.chunk.constants.iter().position(|v| *v == val) { - Some(idx) => idx, - None => { - self.chunk.constants.push(val); - self.chunk.constants.len() - 1 - } - }; - if const_idx > u16::MAX as usize { - panic!( - "internal Ludus compiler error: too many constants in chunk:{}:: {}", - self.span, self.ast - ) - } - self.emit_op(Op::MatchConstant); - let low = const_idx as u8; - let high = (const_idx >> 8) as u8; - self.chunk.bytecode.push(high); - self.chunk.bytecode.push(low); - } - - fn emit_op(&mut self, op: Op) { - self.chunk.bytecode.push(op as u8); - self.spans.push(self.span); - } - - fn emit_byte(&mut self, byte: usize) { - self.chunk.bytecode.push(byte as u8); - self.spans.push(self.span); - } - - fn len(&self) -> usize { - self.chunk.bytecode.len() - } - - pub fn bind(&mut self, name: &'static str) { - self.msg(format!("binding `{name}` in {}", self.name)); - self.msg(format!( - "stack depth: {}; match depth: {}", - self.stack_depth, self.match_depth - )); - self.msg(format!( - "at stack index: {}", - self.stack_depth - self.match_depth - 1 - )); - let binding = Binding { - name, - depth: self.scope_depth, - stack_pos: self.stack_depth - self.match_depth - 1, - }; - self.bindings.push(binding); - self.msg(format!( - "new locals: {}", - self.bindings - .iter() - .map(|binding| format!("{binding}")) - .collect::>() - .join("|") - )); - } - - fn resolve_local(&self, name: &'static str) -> Option { - for binding in self.bindings.iter() { - if binding.name == name { - return Some(binding.stack_pos); - } - } - None - } - - fn resolve_upvalue(&self, name: &'static str) -> Option { - self.upvalues.iter().position(|uv| *uv == name) - } - - fn resolve_binding(&mut self, name: &'static str) { - self.msg(format!( - "resolving binding `{name}` in {}\nlocals: {}", - self.name, - self.bindings - .iter() - .map(|binding| format!("{binding}")) - .collect::>() - .join("|") - )); - if let Some(pos) = self.resolve_local(name) { - self.msg(format!("at locals position {pos}")); - self.emit_op(Op::PushBinding); - self.emit_byte(pos); - self.stack_depth += 1; - return; - } - if let Some(pos) = self.resolve_upvalue(name) { - self.msg(format!("as upvalue {pos}")); - self.emit_op(Op::GetUpvalue); - self.emit_byte(pos); - self.stack_depth += 1; - return; - } - if self.depth == 0 { - self.msg("as global".to_string()); - self.emit_constant(Value::Keyword(name)); - self.emit_op(Op::PushGlobal); - } else { - self.msg(format!("as enclosing upvalue {}", self.upvalues.len())); - self.emit_op(Op::GetUpvalue); - self.emit_byte(self.upvalues.len()); - self.upvalues.push(name); - self.stack_depth += 1; - } - } - - fn duplicate(&mut self) { - self.emit_op(Op::Duplicate); - self.stack_depth += 1; - } - - fn pop(&mut self) { - self.emit_op(Op::Pop); - self.stack_depth -= 1; - } - - fn pop_n(&mut self, n: usize) { - match n { - 0 => (), - 1 => self.pop(), - n => { - self.emit_op(Op::PopN); - self.emit_byte(n); - self.stack_depth -= n; - } - } - } - - fn store(&mut self) { - self.emit_op(Op::Store); - self.stack_depth -= 1; - } - - fn store_n(&mut self, n: usize) { - self.emit_op(Op::StoreN); - self.emit_byte(n); - self.stack_depth -= n; - } - - fn load(&mut self) { - self.emit_op(Op::Load); - self.stack_depth += 1; - } - - fn load_n(&mut self, n: usize) { - self.emit_op(Op::LoadN); - self.emit_byte(n); - self.stack_depth += n; - } - - fn enter_scope(&mut self) { - self.scope_depth += 1; - } - - fn leave_scope(&mut self) { - self.msg(format!("leaving scope {}", self.scope_depth)); - while let Some(binding) = self.bindings.last() { - if binding.depth == self.scope_depth { - let unbound = self.bindings.pop(); - self.msg(format!("releasing binding {}", unbound.unwrap())); - } else { - break; - } - } - self.scope_depth -= 1; - } - - fn enter_loop(&mut self, arity: usize) { - self.loop_info - .push(LoopInfo::new(self.len(), self.stack_depth - arity)); - } - - fn leave_loop(&mut self) { - self.loop_info.pop(); - } - - fn loop_info(&self) -> LoopInfo { - self.loop_info.last().unwrap().clone() - } - - fn loop_idx(&self) -> usize { - self.loop_info.last().unwrap().start - } - - fn loop_root(&self) -> usize { - self.loop_info.last().unwrap().stack_root - } - - fn msg(&mut self, str: String) { - if self.debug { - self.emit_op(Op::Msg); - self.emit_byte(self.chunk.msgs.len()); - println!("{str}"); - self.chunk.msgs.push(str); - } - } - - fn report_depth(&mut self, label: &'static str) { - self.msg(format!("***{label} stack depth: {}", self.stack_depth)); - } - - fn report_depth_str(&mut self, label: String) { - self.msg(format!("***{label} stack depth: {}", self.stack_depth)); - } - - fn report_ast(&mut self, label: String, node: &'static Spanned) { - self.msg(format!("***{label}: {}", node.0.show())) - } - - pub fn compile(&mut self) { - use Ast::*; - match self.ast { - Error => unreachable!(), - Nil => { - self.emit_op(Op::Nil); - self.stack_depth += 1; - } - Number(n) => self.emit_constant(Value::Number(*n)), - Boolean(b) => { - self.emit_op(if *b { Op::True } else { Op::False }); - self.stack_depth += 1; - } - String(s) => { - self.emit_constant(Value::Interned(s)); - } - Keyword(s) => self.emit_constant(Value::Keyword(s)), - Block(lines) => { - let tail_pos = self.tail_pos; - self.tail_pos = false; - // increase the scope - self.enter_scope(); - // stash the stack depth - let stack_depth = self.stack_depth; - // evaluate all the lines but the last - for expr in lines.iter().take(lines.len() - 1) { - // evaluate the expression - self.visit(expr); - // if it doesn't bind a name, pop the result from the stack - if !is_binding(expr) { - self.pop(); - } - } - - // now, evaluate the last expression in the block - let last_expr = lines.last().unwrap(); - match last_expr { - // if the last expression is a let form, - // return the evaluated rhs instead of whatever is last on the stack - // we do this by pretending it's a binding - (Let(patt, expr), _) => { - // self.match_depth = 0; - self.visit(expr); - let expr_pos = self.stack_depth - 1; - self.report_ast("let binding: matching".to_string(), patt); - self.reset_match(); - self.visit(patt); - self.emit_op(Op::PanicIfNoMatch); - self.emit_op(Op::PushBinding); - self.emit_byte(expr_pos); - self.stack_depth += 1; - } - // otherwise, just evaluate it and leave the value on the stack - _ => { - self.tail_pos = tail_pos; - self.visit(last_expr); - } - } - - // store the value in the return register - self.store(); - - // reset the scope - self.leave_scope(); - // reset the stack - self.report_depth("leaving block before pop"); - self.msg(format!( - "popping back from {} to {}", - self.stack_depth, stack_depth, - )); - self.pop_n(self.stack_depth - stack_depth); - // load the value from the return register - self.load(); - } - If(cond, then, r#else) => { - let tail_pos = self.tail_pos; - self.tail_pos = false; - self.visit(cond); - self.report_depth("after condition"); - let jif_idx = self.stub_jump(Op::JumpIfFalse); - // self.stack_depth -= 1; - self.tail_pos = tail_pos; - self.visit(then); - self.report_depth("after consequent"); - let jump_idx = self.stub_jump(Op::Jump); - self.stack_depth -= 1; - self.visit(r#else); - self.report_depth("after alternative"); - // self.stack_depth += 1; - let end_idx = self.len(); - let jif_offset = jump_idx - jif_idx; - let jump_offset = end_idx - jump_idx - 3; - self.patch_jump(jif_idx, jif_offset); - self.patch_jump(jump_idx, jump_offset); - } - Let(patt, expr) => { - self.report_depth("before let binding"); - // self.match_depth = 0; - // self.emit_op(Op::ResetMatch); - self.visit(expr); - self.report_depth("after let expr"); - self.report_ast("let binding: matching".to_string(), patt); - self.reset_match(); - self.visit(patt); - self.emit_op(Op::PanicIfNoMatch); - self.report_depth("after let binding"); - } - WordPattern(name) => { - self.emit_op(Op::UnconditionalMatch); - self.bind(name); - } - Word(name) | Splat(name) => self.resolve_binding(name), - PlaceholderPattern => { - self.emit_op(Op::UnconditionalMatch); - } - NilPattern => { - self.emit_op(Op::MatchNil); - } - BooleanPattern(b) => { - if *b { - self.emit_op(Op::MatchTrue); - } else { - self.emit_op(Op::MatchFalse); - } - } - NumberPattern(n) => { - self.match_constant(Value::Number(*n)); - } - KeywordPattern(s) => { - let existing_kw = self.chunk.keywords.iter().position(|kw| kw == s); - let kw_index = match existing_kw { - Some(index) => index, - None => self.chunk.keywords.len(), - }; - if kw_index == self.chunk.keywords.len() { - self.chunk.keywords.push(s); - } - self.match_constant(Value::Keyword(s)); - } - AsPattern(word, typ) => { - self.emit_constant(Value::Keyword(typ)); - self.emit_op(Op::MatchType); - self.stack_depth -= 1; - self.bind(word); - } - StringPattern(s) => { - self.match_constant(Value::Interned(s)); - } - TuplePattern(members) => { - // first, test the tuple against length - // check if we're splatted - // different opcodes w/ splats, but same logic - let mut is_splatted = false; - if let Some((Splattern(_), _)) = members.last() { - is_splatted = true; - self.emit_op(Op::MatchSplattedTuple); - } else { - self.emit_op(Op::MatchTuple); - } - - self.emit_byte(members.len()); - // skip everything if tuple lengths don't match - let before_load_tup_idx = self.stub_jump(Op::JumpIfNoMatch); - - // set up the per-member conditional logic - let mut jump_idxes = vec![]; - // stash match_depth, and set it to the tuple len - let match_depth = self.match_depth; - self.match_depth = members.len(); - - // load the tuple and update the stack len - if is_splatted { - self.emit_op(Op::LoadSplattedTuple); - self.emit_byte(members.len()); - } else { - self.emit_op(Op::LoadTuple); - } - self.stack_depth += members.len(); - - // visit each member - for member in members { - // reduce the match depth to start - self.match_depth -= 1; - self.emit_op(Op::MatchDepth); - self.emit_byte(self.match_depth); - // visit the pattern member - self.visit(member); - // and jump if there's no match - jump_idxes.push(self.stub_jump(Op::JumpIfNoMatch)); - } - - // if we get here--not having jumped on no match--we're matched; jump the "no match" code - let jump_idx = self.stub_jump(Op::Jump); - - // patch up the previous no match jumps to jump to clean-up code - for idx in jump_idxes { - self.patch_jump(idx, self.len() - idx - 3) - } - // pop everything that was pushed - // don't change the compiler stack representation, tho - // we need this as cleanup code with no matches - // the compiler should still have access to the bindings in this pattern - self.emit_op(Op::PopN); - self.emit_byte(members.len()); - - // patch up the tuple length match jump - self.patch_jump(before_load_tup_idx, self.len() - before_load_tup_idx - 3); - - // patch up the yes-matches unconditional jump - self.patch_jump(jump_idx, self.len() - jump_idx - 3); - - // finally, for any further matches (e.g. nested lists/tuples) - // add increase the match depth, since we've added a bunch - // of bindings to the stack - self.match_depth = match_depth + members.len(); - } - ListPattern(members) => { - let mut is_splatted = false; - if let Some((Splattern(_), _)) = members.last() { - is_splatted = true; - self.emit_op(Op::MatchSplattedList) - } else { - self.emit_op(Op::MatchList); - } - // TODO: lists must be able to be longer than 256 elements; fix this - self.emit_byte(members.len()); - let before_load_tup_idx = self.stub_jump(Op::JumpIfNoMatch); - - let mut jump_idxes = vec![]; - let match_depth = self.match_depth; - self.match_depth = members.len(); - - if is_splatted { - self.emit_op(Op::LoadSplattedList); - self.emit_byte(members.len()); - } else { - self.emit_op(Op::LoadList); - } - self.stack_depth += members.len(); - - for member in members { - self.match_depth -= 1; - self.emit_op(Op::MatchDepth); - self.emit_byte(self.match_depth); - self.visit(member); - jump_idxes.push(self.stub_jump(Op::JumpIfNoMatch)); - } - - let jump_idx = self.stub_jump(Op::Jump); - - for idx in jump_idxes { - self.patch_jump(idx, self.len() - idx - 3) - } - - self.emit_op(Op::PopN); - self.emit_byte(members.len()); - - self.patch_jump(before_load_tup_idx, self.len() - before_load_tup_idx - 3); - - self.patch_jump(jump_idx, self.len() - jump_idx - 3); - - self.match_depth = match_depth + members.len(); - } - DictPattern(pairs) => { - // here's an algorithm for dealing with splatted dicts - // check len to see it's at least as long as the pattern - // then, match against all the values - // then push the dict itself as last value - // and then emit an opcode and constant/keyword to OMIT that key from the dict - let mut is_splatted = false; - if let Some((Splattern(_), _)) = pairs.last() { - is_splatted = true; - self.emit_op(Op::MatchSplattedDict); - } else { - self.emit_op(Op::MatchDict); - } - self.emit_byte(pairs.len()); - let before_load_dict_idx = self.stub_jump(Op::JumpIfNoMatch); - - let mut jump_idxes = vec![]; - let dict_stack_pos = self.stack_depth - self.match_depth - 1; - let mut splattern = None; - let mut pairs_len = pairs.len(); - if is_splatted { - splattern = pairs.last(); - pairs_len -= 1; - } - - let match_depth = self.match_depth; - self.match_depth = 0; - for pair in pairs.iter().take(pairs_len) { - let (PairPattern(key, pattern), _) = pair else { - unreachable!() - }; - self.emit_constant(Value::Keyword(key)); - self.emit_op(Op::LoadDictValue); - self.emit_byte(dict_stack_pos); - self.visit(pattern); - jump_idxes.push(self.stub_jump(Op::JumpIfNoMatch)); - } - - if is_splatted { - // pull the dict out of the stack - // drop every value in the pattern - self.emit_op(Op::PushBinding); - self.emit_byte(dict_stack_pos); - self.stack_depth += 1; - - for pair in pairs.iter().take(pairs_len) { - let (PairPattern(key, _), _) = pair else { - unreachable!() - }; - self.emit_constant(Value::Keyword(key)); - self.emit_op(Op::DropDictEntry); - self.stack_depth -= 1; - } - - if let Some(splatt) = splattern { - self.visit(splatt); - } - } - - self.match_depth = match_depth + pairs.len(); - - let jump_idx = self.stub_jump(Op::Jump); - - for idx in jump_idxes { - self.patch_jump(idx, self.len() - idx - 3); - } - - // do this explicitly to keep compiler & vm stacks in sync - self.emit_op(Op::Pop); - self.emit_byte(pairs.len()); - - self.patch_jump(before_load_dict_idx, self.len() - before_load_dict_idx - 3); - self.patch_jump(jump_idx, self.len() - jump_idx - 3); - } - Splattern(patt) => self.visit(patt), - InterpolatedPattern(parts, _) => { - // println!("An interpolated pattern of {} parts", parts.len()); - let mut pattern = "".to_string(); - let mut words = vec![]; - for (part, _) in parts { - match part { - StringPart::Word(word) => { - // println!("wordpart: {word}"); - words.push(word.clone()); - pattern.push_str("(.*)"); - } - StringPart::Data(data) => { - // println!("datapart: {data}"); - let data = regex::escape(data); - pattern.push_str(data.as_str()); - } - StringPart::Inline(..) => unreachable!(), - } - } - let re = Regex::new(pattern.as_str()).unwrap(); - let moar_words = words.clone(); - let string_pattern = StrPattern { words, re }; - - let pattern_idx = self.chunk.string_patterns.len(); - self.chunk.string_patterns.push(string_pattern); - - self.emit_op(Op::MatchString); - self.emit_byte(pattern_idx); - - let jnm_idx = self.stub_jump(Op::JumpIfNoMatch); - - self.emit_op(Op::PushStringMatches); - self.emit_byte(pattern_idx); - - for word in moar_words { - let name: &'static str = std::string::String::leak(word); - let binding = Binding { - name, - depth: self.scope_depth, - stack_pos: self.stack_depth, - }; - self.bindings.push(binding); - self.stack_depth += 1; - } - - self.patch_jump(jnm_idx, self.len() - jnm_idx - 3); - } - PairPattern(_, _) => unreachable!(), - Tuple(members) => { - self.tail_pos = false; - for member in members { - self.visit(member); - } - self.emit_op(Op::PushTuple); - self.emit_byte(members.len()); - self.stack_depth = self.stack_depth + 1 - members.len(); - } - List(members) => { - self.tail_pos = false; - self.emit_op(Op::PushList); - self.stack_depth += 1; - for member in members { - self.visit(member); - if matches!(member, (Splat(..), _)) { - self.emit_op(Op::ConcatList); - } else { - self.emit_op(Op::AppendList); - } - self.stack_depth -= 1; - } - } - LBox(name, expr) => { - self.tail_pos = false; - self.visit(expr); - self.emit_op(Op::PushBox); - self.bind(name); - } - Dict(pairs) => { - self.tail_pos = false; - self.emit_op(Op::PushDict); - self.stack_depth += 1; - for pair in pairs.iter().rev() { - self.visit(pair); - if matches!(pair, (Splat(..), _)) { - self.emit_op(Op::ConcatDict); - self.stack_depth -= 1; - } else { - self.emit_op(Op::AppendDict); - self.stack_depth -= 2; - } - } - } - Pair(key, value) => { - self.tail_pos = false; - self.emit_constant(Value::Keyword(key)); - self.visit(value); - } - // TODO: thread tail position through this - Synthetic(first, second, rest) => { - let tail_pos = self.tail_pos; - self.tail_pos = false; - match (&first.0, &second.0) { - (Word(name), Keyword(key)) => { - self.report_depth_str(format!("accessing keyword: {name} :{key}")); - self.visit(first); - self.visit(second); - self.emit_op(Op::GetKey); - self.stack_depth -= 1; - self.report_depth("after keyword access"); - } - (Keyword(_), Arguments(args)) => { - self.visit(&args[0]); - self.visit(first); - self.emit_op(Op::GetKey); - self.stack_depth -= 1; - } - (Or, Arguments(args)) => { - let mut jump_idxes = vec![]; - if !args.is_empty() { - let mut args = args.iter().rev(); - let last = args.next().unwrap(); - for arg in args.rev() { - self.visit(arg); - self.duplicate(); - jump_idxes.push(self.stub_jump(Op::JumpIfTrue)); - self.pop(); - } - self.visit(last); - for idx in jump_idxes { - self.patch_jump(idx, self.len() - idx - 3); - } - } else { - self.emit_op(Op::False); - self.stack_depth += 1; - } - } - (And, Arguments(args)) => { - let mut jump_idxes = vec![]; - if !args.is_empty() { - let mut args = args.iter().rev(); - let last = args.next().unwrap(); - for arg in args.rev() { - self.visit(arg); - self.duplicate(); - jump_idxes.push(self.stub_jump(Op::JumpIfFalse)); - self.pop(); - } - self.visit(last); - for idx in jump_idxes { - self.patch_jump(idx, self.len() - idx - 3); - } - } else { - self.emit_op(Op::True); - self.stack_depth += 1; - } - } - (Word(fn_name), Arguments(args)) => { - self.report_depth_str(format!("calling function {fn_name}")); - if has_placeholder(args) { - let arity = args.len(); - for arg in args { - self.visit(arg); - } - self.resolve_binding(fn_name); - self.emit_op(Op::Partial); - self.emit_byte(arity); - self.stack_depth -= args.len() - 1; - } else { - match get_builtin(fn_name, args.len()) { - Some(code) => { - for arg in args { - self.visit(arg); - } - self.emit_op(code); - self.stack_depth -= args.len() - 1; - } - None => { - let arity = args.len(); - for arg in args { - self.visit(arg); - } - self.resolve_binding(fn_name); - // if we're in tail position AND there aren't any rest args, this should be a tail call (I think) - self.report_depth_str(format!("after {arity} args")); - if rest.is_empty() && tail_pos { - self.emit_op(Op::TailCall); - } else { - self.emit_op(Op::Call); - } - self.emit_byte(arity); - self.stack_depth -= arity; - } - } - } - } - _ => unreachable!(), - } - // the last term in rest should be in tail position if we are in tail position - let num_rest_terms = rest.len(); - for (i, (term, _)) in rest.iter().enumerate() { - match term { - Keyword(str) => { - self.emit_constant(Value::Keyword(str)); - self.emit_op(Op::GetKey); - self.stack_depth -= 1; - } - Arguments(args) => { - self.store(); - let arity = args.len(); - for arg in args { - self.visit(arg); - } - self.load(); - if tail_pos && i == num_rest_terms - 1 { - self.emit_op(Op::TailCall) - } else { - self.emit_op(Op::Call); - } - self.emit_byte(arity); - self.stack_depth -= arity; - } - _ => unreachable!(), - } - } - self.tail_pos = tail_pos; - } - When(clauses) => { - let tail_pos = self.tail_pos; - let mut jump_idxes = vec![]; - let mut clauses = clauses.iter(); - while let Some((WhenClause(cond, body), _)) = clauses.next() { - self.tail_pos = false; - self.visit(cond.as_ref()); - let jif_jump_idx = self.stub_jump(Op::JumpIfFalse); - self.tail_pos = tail_pos; - self.visit(body); - self.stack_depth -= 1; - jump_idxes.push(self.stub_jump(Op::Jump)); - self.patch_jump(jif_jump_idx, self.len() - jif_jump_idx - 3); - } - self.emit_op(Op::PanicNoWhen); - for idx in jump_idxes { - self.patch_jump(idx, self.len() - idx - 3); - } - self.stack_depth += 1; - } - WhenClause(..) => unreachable!(), - Match(scrutinee, clauses) => { - let tail_pos = self.tail_pos; - self.tail_pos = false; - self.visit(scrutinee.as_ref()); - let stack_depth = self.stack_depth; - let mut jump_idxes = vec![]; - let mut clauses = clauses.iter(); - while let Some((MatchClause(pattern, guard, body), _)) = clauses.next() { - self.tail_pos = false; - let mut no_match_jumps = vec![]; - self.report_ast("match clause: ".to_string(), pattern); - // self.scope_depth += 1; - self.enter_scope(); - self.match_depth = 0; - self.visit(pattern); - no_match_jumps.push(self.stub_jump(Op::JumpIfNoMatch)); - if guard.is_some() { - let guard_expr: &'static Spanned = - Box::leak(Box::new(guard.clone().unwrap())); - self.visit(guard_expr); - no_match_jumps.push(self.stub_jump(Op::JumpIfFalse)); - } - self.tail_pos = tail_pos; - self.visit(body); - self.store(); - self.leave_scope(); - self.pop_n(self.stack_depth - stack_depth); - jump_idxes.push(self.stub_jump(Op::Jump)); - for idx in no_match_jumps { - self.patch_jump(idx, self.len() - idx - 3); - } - } - self.emit_op(Op::PanicNoMatch); - - for idx in jump_idxes { - self.patch_jump(idx, self.len() - idx - 3); - } - self.pop_n(self.stack_depth - stack_depth); - self.emit_op(Op::Load); - self.stack_depth += 1; - } - MatchClause(..) => unreachable!(), - Fn(name, body, doc) => { - let is_anon = name.is_empty(); - let mut name = name; - - if !is_anon { - let declared = self.chunk.constants.iter().any(|val| match val { - Value::Fn(lfn) => { - if matches!(lfn.as_ref(), LFn::Declared { .. }) { - lfn.name() == *name - } else { - false - } - } - _ => false, - }); - if !declared { - let declaration = Value::Fn(Rc::new(LFn::Declared { name })); - self.emit_constant(declaration); - self.bind(name); - } - } else { - name = &"_anon"; - } - - let FnBody(fn_body) = &body.as_ref().0 else { - unreachable!() - }; - - let mut compilers: HashMap = HashMap::new(); - - let mut upvalues = vec![]; - - let mut has_splat = false; - - for clause in fn_body { - let MatchClause(pattern, guard, clause_body) = &clause.0 else { - unreachable!() - }; - let full_pattern = pattern; - let TuplePattern(pattern) = &pattern.0 else { - unreachable!() - }; - - if matches!(pattern.last(), Some((Splattern(_), _))) { - has_splat = true; - }; - - let arity = pattern.len() as u8; - - let compiler = match compilers.get_mut(&arity) { - Some(compiler) => compiler, - None => { - let mut compiler = Compiler::new( - clause, - name, - self.src, - self.depth + 1, - self.chunk.env.clone(), - self.debug, - ); - compiler.reset_match(); - // compiler.emit_op(Op::ResetMatch); - compilers.insert(arity, compiler); - compilers.get_mut(&arity).unwrap() - } - }; - compiler.tail_pos = false; - - compiler.stack_depth += arity as usize; - compiler.scope_depth += 1; - compiler.match_depth = arity as usize; - - std::mem::swap(&mut upvalues, &mut compiler.upvalues); - - let mut tup_jump_idxes = vec![]; - compiler.report_ast("function clause matching: ".to_string(), full_pattern); - for member in pattern { - compiler.match_depth -= 1; - compiler.emit_op(Op::MatchDepth); - compiler.emit_byte(compiler.match_depth); - compiler.visit(member); - tup_jump_idxes.push(compiler.stub_jump(Op::JumpIfNoMatch)); - } - if pattern.is_empty() { - compiler.emit_op(Op::UnconditionalMatch); - } - let jump_idx = compiler.stub_jump(Op::Jump); - for idx in tup_jump_idxes { - compiler.patch_jump(idx, compiler.len() - idx - 3); - } - // compiler.emit_op(Op::PopN); - // compiler.emit_byte(arity as usize); - compiler.patch_jump(jump_idx, compiler.len() - jump_idx - 3); - let mut no_match_jumps = vec![]; - no_match_jumps.push(compiler.stub_jump(Op::JumpIfNoMatch)); - if guard.is_some() { - let guard_expr: &'static Spanned = - Box::leak(Box::new(guard.clone().unwrap())); - compiler.visit(guard_expr); - no_match_jumps.push(compiler.stub_jump(Op::JumpIfFalse)); - compiler.stack_depth -= 1; - } - compiler.tail_pos = true; - compiler.visit(clause_body); - compiler.store(); - compiler.scope_depth -= 1; - while let Some(binding) = compiler.bindings.last() { - if binding.depth > compiler.scope_depth { - compiler.bindings.pop(); - } else { - break; - } - } - compiler.pop_n(compiler.stack_depth); - compiler.stack_depth = 0; - compiler.emit_op(Op::Return); - for idx in no_match_jumps { - compiler.patch_jump(idx, compiler.len() - idx - 3); - } - // compiler.scope_depth -= 1; - - std::mem::swap(&mut compiler.upvalues, &mut upvalues); - } - - let mut compilers = compilers.into_iter().collect::>(); - compilers.sort_by(|(a, _), (b, _)| a.cmp(b)); - - let mut arities = vec![]; - let mut chunks = vec![]; - - for (arity, mut compiler) in compilers { - compiler.emit_op(Op::PanicNoMatch); - let chunk = compiler.chunk; - if self.debug { - println!("=== function chuncktion: {name}/{arity} ==="); - chunk.dissasemble(); - } - - arities.push(arity); - chunks.push(chunk); - } - - let splat = if has_splat { - arities.iter().fold(0, |max, curr| max.max(*curr)) - } else { - 0 - }; - - let lfn = crate::value::LFn::Defined { - name, - doc: *doc, - arities, - chunks, - splat, - closed: RefCell::new(vec![]), - }; - - let the_fn = Value::Fn(Rc::new(lfn)); - // self.emit_constant(the_fn); - // self.bind(name); - - if !is_anon { - let declaration_idx = self - .chunk - .constants - .iter() - .position(|val| match val { - Value::Fn(lfn) => { - if matches!(lfn.as_ref(), LFn::Declared { .. }) { - lfn.name() == *name - } else { - false - } - } - _ => false, - }) - .unwrap(); - self.chunk.constants[declaration_idx] = the_fn; - // if the function been forward-declared, bring it to the top of the stack - if declaration_idx < self.chunk.constants.len() - 1 { - self.resolve_binding(name); - } - } else { - self.emit_constant(the_fn) - } - - for upvalue in upvalues { - self.resolve_binding(upvalue); - self.emit_op(Op::SetUpvalue); - self.stack_depth -= 1; - } - } - FnDeclaration(name) => { - let lfn = Value::Fn(Rc::new(LFn::Declared { name })); - self.emit_constant(lfn); - self.bind(name); - } - FnBody(_) => unreachable!(), - Repeat(times, body) => { - let tail_pos = self.tail_pos; - self.tail_pos = false; - self.visit(times); - self.emit_op(Op::ToInt); - // skip the decrement the first time - self.emit_op(Op::Jump); - self.emit_byte(0); - self.emit_byte(1); - // begin repeat - self.emit_op(Op::Decrement); - let repeat_begin = self.len(); - self.duplicate(); - let jiz_idx = self.stub_jump(Op::JumpIfZero); - // compile the body - self.visit(body); - // pop whatever value the body returns - self.pop(); - let jump_back = self.stub_jump(Op::JumpBack); - // set jump points - self.patch_jump(jump_back, self.len() - repeat_begin - 2); - self.patch_jump(jiz_idx, self.len() - repeat_begin - 4); - self.pop(); - self.emit_constant(Value::Nil); - self.tail_pos = tail_pos; - } - Loop(value, clauses) => { - self.report_depth("entering loop"); - let tail_pos = self.tail_pos; - self.tail_pos = false; - //algo: - //first, put the values on the stack - let (Ast::Tuple(members), _) = value.as_ref() else { - unreachable!() - }; - for member in members { - self.visit(member); - } - self.report_depth("after loop args"); - let arity = members.len(); - // self.emit_op(Op::StoreN); - // self.emit_byte(members.len()); - self.store_n(arity); - let stack_depth = self.stack_depth; - self.report_depth("loop: after store"); - //then, save the beginning of the loop - // self.emit_op(Op::Load); - self.load_n(arity); - self.enter_loop(arity); - // self.stack_depth += arity; - //next, compile each clause: - let mut clauses = clauses.iter(); - let mut jump_idxes = vec![]; - while let Some((Ast::MatchClause(pattern, guard, body), _)) = clauses.next() { - self.tail_pos = false; - self.report_depth("loop: after load"); - self.reset_match(); - // self.emit_op(Op::ResetMatch); - self.enter_scope(); - // self.scope_depth += 1; - let (Ast::TuplePattern(members), _) = pattern.as_ref() else { - unreachable!() - }; - self.match_depth = arity; - let mut jnm_idxes = vec![]; - self.report_ast("loop clause matching: ".to_string(), pattern); - for member in members { - self.match_depth -= 1; - self.emit_op(Op::MatchDepth); - self.emit_byte(self.match_depth); - self.visit(member); - jnm_idxes.push(self.stub_jump(Op::JumpIfNoMatch)); - } - if guard.is_some() { - let guard_expr: &'static Spanned = - Box::leak(Box::new(guard.clone().unwrap())); - self.visit(guard_expr); - jnm_idxes.push(self.stub_jump(Op::JumpIfFalse)); - } - self.tail_pos = tail_pos; - self.report_depth("loop: before body"); - self.visit(body); - self.report_depth("loop: after body, before store"); - // self.emit_op(Op::Store); - self.store(); - self.report_depth("loop: after body, after store"); - self.leave_scope(); - self.report_depth_str(format!( - "resetting the stack after loop from {} to {stack_depth}", - self.stack_depth, - )); - self.pop_n(self.stack_depth - stack_depth); - // while self.stack_depth > stack_depth { - // self.pop(); - // } - jump_idxes.push(self.stub_jump(Op::Jump)); - for idx in jnm_idxes { - self.patch_jump(idx, self.len() - idx - 3); - } - self.stack_depth += arity; - } - self.emit_op(Op::PanicNoMatch); - for idx in jump_idxes { - self.patch_jump(idx, self.len() - idx - 3); - } - self.report_depth("before loop arity adjustment"); - self.stack_depth -= arity; - // pop back to the original depth before load - // i.e. clear loop args - // self.pop(); - // self.emit_op(Op::Load); - self.load(); - // self.stack_depth += 1; - self.leave_loop(); - self.report_depth("at very end of loop after load"); - } - Recur(args) => { - // self.emit_op(Op::Nothing); - self.report_depth("recur: before args"); - let tail_pos = self.tail_pos; - self.tail_pos = false; - let mut argnum = 0; - for arg in args { - self.msg(format!("recur arg: {argnum}")); - argnum += 1; - self.visit(arg); - } - self.report_depth("recur: after args"); - self.store_n(args.len()); - self.report_depth("recur: after store"); - self.msg(format!("loop root depth: {}", self.loop_root())); - self.pop_n(self.stack_depth - self.loop_root()); - self.report_depth("recur: after stack reset"); - self.load_n(args.len()); - self.report_depth("recur: after load, end of compilation"); - self.jump(Op::JumpBack, self.len() - self.loop_idx()); - self.tail_pos = tail_pos; - } - Panic(msg) => { - self.visit(msg); - self.emit_op(Op::Panic); - } - Interpolated(parts) => { - self.emit_op(Op::EmptyString); - self.stack_depth += 1; - for part in parts { - let str = &part.0; - match str { - StringPart::Inline(_) => unreachable!(), - StringPart::Data(str) => { - let allocated = Value::String(Rc::new(str.clone())); - self.emit_constant(allocated); - self.emit_op(Op::ConcatStrings); - } - StringPart::Word(word) => { - self.resolve_binding(word); - self.emit_op(Op::Stringify); - self.emit_op(Op::ConcatStrings); - } - } - self.stack_depth -= 1; - } - } - Do(terms) => { - let mut terms = terms.iter(); - let first = terms.next().unwrap(); - let mut terms = terms.rev(); - let last = terms.next().unwrap(); - let terms = terms.rev(); - // put the first value on the stack - let tail_pos = self.tail_pos; - self.tail_pos = false; - self.visit(first); - for term in terms { - self.visit(term); - self.emit_op(Op::Call); - self.emit_byte(1); - self.stack_depth -= 1; - } - self.visit(last); - if tail_pos { - self.emit_op(Op::TailCall) - } else { - self.emit_op(Op::Call); - } - self.emit_byte(1); - self.tail_pos = tail_pos; - self.stack_depth -= 1; - } - Placeholder => { - self.emit_op(Op::Nothing); - } - And | Or | Arguments(..) => unreachable!(), - } - } - - pub fn disassemble(&self) { - println!("=== chunk: {} ===", self.name); - self.chunk.dissasemble(); - } -} diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 22038ed..0000000 --- a/src/errors.rs +++ /dev/null @@ -1,52 +0,0 @@ -// use crate::process::{LErr, Trace}; -use crate::validator::VErr; -use ariadne::{sources, Color, Label, Report, ReportKind}; - -// pub fn report_panic(err: LErr) { -// let mut srcs = HashSet::new(); -// let mut stack = vec![]; -// let mut order = 1; -// for entry in err.trace.iter().rev() { -// let Trace { -// callee, -// caller, -// function, -// arguments, -// input, -// src, -// } = entry; -// let (_, first_span) = callee; -// let (_, second_span) = caller; -// let Value::Fn(f) = function else { -// unreachable!() -// }; -// let fn_name = f.borrow().name.clone(); -// let i = first_span.start; -// let j = second_span.end; -// let label = Label::new((entry.input, i..j)) -// .with_color(Color::Yellow) -// .with_message(format!("({order}) calling `{fn_name}` with `{arguments}`")); -// order += 1; -// stack.push(label); -// srcs.insert((*input, *src)); -// } -// Report::build(ReportKind::Error, (err.input, err.span.into_range())) -// .with_message(format!("Ludus panicked! {}", err.msg)) -// .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Red)) -// .with_labels(stack) -// .with_note(err.extra) -// .finish() -// .print(sources(srcs.iter().copied())) -// .unwrap(); -// } - -pub fn report_invalidation(errs: Vec) { - for err in errs { - Report::build(ReportKind::Error, (err.input, err.span.into_range())) - .with_message(err.msg.to_string()) - .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan)) - .finish() - .print(sources(vec![(err.input, err.src)])) - .unwrap(); - } -} diff --git a/src/lexer.rs b/src/lexer.rs deleted file mode 100644 index 50ca0ec..0000000 --- a/src/lexer.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::spans::*; -use chumsky::prelude::*; -use std::fmt; - -#[derive(Clone, Debug, PartialEq)] -pub enum Token { - Nil, - Number(f64), - Word(&'static str), - Boolean(bool), - Keyword(&'static str), - String(&'static str), - // todo: hard code these types - Reserved(&'static str), - Punctuation(&'static str), -} - -impl fmt::Display for Token { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Token::Number(n) => write!(f, "[Number {}]", n), - Token::Word(w) => write!(f, "[Word {}]", w), - Token::Boolean(b) => write!(f, "[Boolean {}]", b), - Token::Keyword(k) => write!(f, "[Keyword :{}]", k), - Token::String(s) => write!(f, "[String {}]", s), - Token::Reserved(r) => write!(f, "[Reserved {}]", r), - Token::Nil => write!(f, "[nil]"), - Token::Punctuation(p) => write!(f, "[Punctuation {}]", p), - } - } -} - -pub fn lexer( -) -> impl Parser<'static, &'static str, Vec<(Token, Span)>, extra::Err>> { - let number = just('-') - .or_not() - .then(text::int(10).then(just('.').then(text::digits(10)).or_not())) - .to_slice() - .from_str() - .unwrapped() - .map(Token::Number); - - let word = any() - .filter(char::is_ascii_lowercase) - .then( - any() - .filter(char::is_ascii_alphanumeric) - .or(one_of("*/?!_")) - .repeated(), - ) - .to_slice(); - - let reserved_or_word = word.map(|word: &str| match word { - "true" => Token::Boolean(true), - "false" => Token::Boolean(false), - "nil" => Token::Nil, - // todo: hard code these as type constructors - "as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!" - | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" => Token::Reserved(word), - _ => Token::Word(word), - }); - - let keyword = just(':').ignore_then(word).map(Token::Keyword); - - let string = just('"') - .ignore_then(none_of("\"").repeated().to_slice()) - .then_ignore(just('"')) - .map(Token::String); - - // todo: hard code these as type constructors - let punctuation = one_of(",=[]{}()>;\n_") - .to_slice() - .or(just("->")) - .or(just("...")) - .or(just("#{")) - .or(just("${")) - .map(Token::Punctuation); - - let token = number - .or(reserved_or_word) - .or(keyword) - .or(string) - .or(punctuation); - - let comment = just('&') - .ignore_then(any().and_is(just('\n').not()).repeated()) - .repeated(); - - let ludus_ws = just(' ').or(just('\t')).repeated(); - - token - .map_with(|tok, e| (tok, e.span())) - .padded_by(ludus_ws) - .padded_by(comment) - .recover_with(skip_then_retry_until(any().ignored(), end())) - .repeated() - .collect() -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 958b2c2..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,209 +0,0 @@ -use chumsky::{input::Stream, prelude::*}; -use imbl::HashMap; -use wasm_bindgen::prelude::*; - -const DEBUG_SCRIPT_COMPILE: bool = false; -const DEBUG_SCRIPT_RUN: bool = false; -const DEBUG_PRELUDE_COMPILE: bool = false; -const DEBUG_PRELUDE_RUN: bool = false; - -mod base; - -mod spans; -use crate::spans::Spanned; - -mod lexer; -use crate::lexer::lexer; - -mod parser; -use crate::parser::{parser, Ast}; - -mod validator; -use crate::validator::Validator; - -mod errors; -use crate::errors::report_invalidation; - -mod chunk; -mod op; - -mod compiler; -use crate::compiler::Compiler; - -mod value; -use value::Value; - -mod vm; -use vm::Vm; - -const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); - -fn prelude() -> HashMap<&'static str, Value> { - let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap(); - let (parsed, parse_errors) = parser() - .parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s))) - .into_output_errors(); - - if !parse_errors.is_empty() { - println!("ERROR PARSING PRELUDE:"); - println!("{:?}", parse_errors); - panic!(); - } - - let parsed = parsed.unwrap(); - let (ast, span) = &parsed; - - let base = base::make_base(); - let mut base_env = imbl::HashMap::new(); - base_env.insert("base", base.clone()); - - let mut validator = Validator::new(ast, span, "prelude", PRELUDE, base_env); - - validator.validate(); - - if !validator.errors.is_empty() { - println!("VALIDATION ERRORS IN PRLUDE:"); - report_invalidation(validator.errors); - panic!(); - } - - let parsed: &'static Spanned = Box::leak(Box::new(parsed)); - let mut compiler = Compiler::new( - parsed, - "prelude", - PRELUDE, - 0, - HashMap::new(), - DEBUG_PRELUDE_COMPILE, - ); - compiler.emit_constant(base); - compiler.bind("base"); - compiler.compile(); - - let chunk = compiler.chunk; - let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN); - let prelude = vm.run().clone().unwrap(); - match prelude { - Value::Dict(hashmap) => *hashmap, - _ => unreachable!(), - } -} - -#[wasm_bindgen] -pub fn ludus(src: String) -> String { - let src = src.to_string().leak(); - let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); - if !lex_errs.is_empty() { - return format!("{:?}", lex_errs); - } - - let tokens = tokens.unwrap(); - - let (parse_result, parse_errors) = parser() - .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s))) - .into_output_errors(); - if !parse_errors.is_empty() { - return format!("{:?}", parse_errors); - } - - // ::sigh:: The AST should be 'static - // This simplifies lifetimes, and - // in any event, the AST should live forever - let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); - - let prelude = prelude(); - let postlude = prelude.clone(); - // let prelude = imbl::HashMap::new(); - - let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone()); - validator.validate(); - - // TODO: validator should generate a string, not print to the console - if !validator.errors.is_empty() { - report_invalidation(validator.errors); - return "Ludus found some validation errors.".to_string(); - } - - let mut compiler = Compiler::new(parsed, "sandbox", src, 0, prelude, DEBUG_SCRIPT_COMPILE); - // let base = base::make_base(); - // compiler.emit_constant(base); - // compiler.bind("base"); - - compiler.compile(); - if DEBUG_SCRIPT_COMPILE { - println!("=== source code ==="); - println!("{src}"); - compiler.disassemble(); - println!("\n\n") - } - - if DEBUG_SCRIPT_RUN { - println!("=== vm run ==="); - } - - let vm_chunk = compiler.chunk; - - let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN); - let result = vm.run(); - - let console = postlude.get("console").unwrap(); - let Value::Box(console) = console else { - unreachable!() - }; - let Value::List(ref lines) = *console.borrow() else { - unreachable!() - }; - let mut console = lines - .iter() - .map(|line| line.stringify()) - .collect::>() - .join("\n"); - - let turtle_commands = postlude.get("turtle_commands").unwrap(); - let Value::Box(commands) = turtle_commands else { - unreachable!() - }; - let commands = commands.borrow(); - dbg!(&commands); - let commands = commands.to_json().unwrap(); - - let output = match result { - Ok(val) => val.show(), - Err(panic) => { - console = format!("{console}\nLudus panicked! {panic}"); - "".to_string() - } - }; - if DEBUG_SCRIPT_RUN { - vm.print_stack(); - } - - // TODO: use serde_json to make this more robust? - format!( -"{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}" - ) -} - -pub fn fmt(src: &'static str) -> Result { - let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); - if !lex_errs.is_empty() { - println!("{:?}", lex_errs); - return Err(format!("{:?}", lex_errs)); - } - - let tokens = tokens.unwrap(); - - let (parse_result, parse_errors) = parser() - .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s))) - .into_output_errors(); - if !parse_errors.is_empty() { - return Err(format!("{:?}", parse_errors)); - } - - // ::sigh:: The AST should be 'static - // This simplifies lifetimes, and - // in any event, the AST should live forever - let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); - - Ok(parsed.0.show()) -} diff --git a/src/lib.rs.old b/src/lib.rs.old deleted file mode 100644 index d4b7f92..0000000 --- a/src/lib.rs.old +++ /dev/null @@ -1,198 +0,0 @@ -// use ariadne::{sources, Color, Label, Report, ReportKind}; -use chumsky::prelude::*; -use std::fmt; - -pub type Span = SimpleSpan; - -#[derive(Clone, Debug, PartialEq)] -pub enum Token<'src> { - // atomic types - Boolean(bool), - Number(f64), - String(&'src str), - Word(&'src str), - Keyword(&'src str), - Pkgkeyword(&'src str), - Ignored(&'src str), - - // reserved words - As, - Box, - Do, - Else, - Fn, - If, - Import, - Let, - Loop, - Match, - Nil, - Ns, - Panic, - Pkg, - Recur, - Repeat, - Test, - Then, - Use, - When, - With, - - // punctuation - Arrow, - Comma, - Equals, - Lbrace, - Lbracket, - Lparen, - Newline, - Pipeline, - Placeholder, - Rbrace, - Rbracket, - Rparen, - Semi, - Splat, - Startdict, - Startset, -} - -impl<'src> fmt::Display for Token<'src> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Token::Boolean(b) => write!(f, "{}", b), - Token::Number(n) => write!(f, "{}", n), - Token::String(s) => write!(f, "{}", s), - Token::Word(w) => write!(f, "{}", w), - Token::Keyword(k) => write!(f, ":{}", k), - Token::Ignored(i) => write!(f, "_{}", i), - Token::Pkgkeyword(k) => write!(f, ":{}", k), - - Token::As => write!(f, "as"), - Token::Box => write!(f, "box"), - Token::Do => write!(f, "do"), - Token::Else => write!(f, "else"), - Token::Fn => write!(f, "fn"), - Token::If => write!(f, "if"), - Token::Import => write!(f, "import"), - Token::Let => write!(f, "let"), - Token::Loop => write!(f, "loop"), - Token::Match => write!(f, "match"), - Token::Nil => write!(f, "nil"), - Token::Ns => write!(f, "ns"), - Token::Panic => write!(f, "panic!"), - Token::Pkg => write!(f, "pkg"), - Token::Recur => write!(f, "recur"), - Token::Repeat => write!(f, "repeat"), - Token::Test => write!(f, "test"), - Token::Then => write!(f, "then"), - Token::Use => write!(f, "use"), - Token::When => write!(f, "when"), - Token::With => write!(f, "with"), - - Token::Arrow => write!(f, "->"), - Token::Comma => write!(f, ","), - Token::Equals => write!(f, "="), - Token::Lbrace => write!(f, "{{"), - Token::Lbracket => write!(f, "["), - Token::Lparen => write!(f, "("), - Token::Newline => write!(f, "\\n"), - Token::Pipeline => write!(f, ">"), - Token::Placeholder => write!(f, "_"), - Token::Rbrace => write!(f, "}}"), - Token::Rbracket => write!(f, "]"), - Token::Rparen => write!(f, ")"), - Token::Semi => write!(f, ";"), - Token::Splat => write!(f, "..."), - Token::Startdict => write!(f, "#{{"), - Token::Startset => write!(f, "${{"), - } - } -} - -pub fn lexer<'src>( -) -> impl Parser<'src, &'src str, Vec<(Token<'src>, Span)>, extra::Err>> { - let string = just('"') - .ignore_then(none_of('"').repeated().to_slice()) - .then_ignore(just('"')) - .map(Token::String); - - let word = any() - .filter(char::is_ascii_lowercase) - .then( - any() - .filter(char::is_ascii_alphanumeric) - .or(one_of("*_/!?")), - ) - .repeated() - .to_slice(); - - let keyword = just(':').ignore_then(word.clone()).map(Token::Keyword); - - let number = just('-') - .or_not() - .then(text::int(10).then(just('.').then(text::digits(10)).or_not())) - .to_slice() - .from_str() - .unwrapped() - .map(Token::Number); - - let reserved_or_word = word.map(|word: &str| match word { - "as" => Token::As, - "box" => Token::Box, - "do" => Token::Do, - "else" => Token::Else, - "false" => Token::Boolean(false), - "fn" => Token::Fn, - "if" => Token::If, - "import" => Token::Import, - "let" => Token::Let, - "loop" => Token::Loop, - "match" => Token::Match, - "nil" => Token::Nil, - "ns" => Token::Ns, - "panic!" => Token::Panic, // won't match until C-style ident -> Ludus word - "pkg" => Token::Pkg, - "recur" => Token::Recur, - "repeat" => Token::Repeat, - "test" => Token::Test, - "then" => Token::Then, - "true" => Token::Boolean(true), - "use" => Token::Use, - "when" => Token::When, - "with" => Token::With, - _ => Token::Word(word), - }); - - let arrow = just("->").to(Token::Arrow); - let comma = just(',').to(Token::Comma); - let semicolon = just(';').to(Token::Semi); - let placeholder = just('_').to(Token::Placeholder); - - let control = arrow.or(comma).or(semicolon).or(placeholder); - - let comment = just('&') - .then(any().and_is(just('\n').not()).repeated()) - .padded(); - - let atom = number.or(string).or(keyword).or(reserved_or_word); - - atom.or(control) - .map_with(|tok, e| (tok, e.span())) - .padded_by(comment.repeated()) - .padded() -} - -#[cfg(test)] -mod tests { - use crate::lexer; - use crate::Token; - use chumsky::{container::Seq, prelude::*}; - - #[test] - fn it_works() { - let toks = lexer().parse("42").unwrap(); - let (tok, _) = toks[0].clone(); - assert_eq!(tok, Token::Number(42.0)); - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index b2bbc6b..0000000 --- a/src/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -use rudus::ludus; -use std::env; -use std::fs; - -pub fn main() { - env::set_var("RUST_BACKTRACE", "1"); - let src = fs::read_to_string("sandbox.ld").unwrap(); - let json = ludus(src); - println!("{json}"); -} diff --git a/src/memory_sandbox.rs b/src/memory_sandbox.rs deleted file mode 100644 index 15e7563..0000000 --- a/src/memory_sandbox.rs +++ /dev/null @@ -1,58 +0,0 @@ -use imbl::{HashMap, Vector}; -use index_vec::Idx; -use std::cell::RefCell; -use std::ops::Range; -use std::rc::Rc; - -struct Word(&'static str); - -struct Keyword(&'static str); - -struct Interned(&'static str); - -enum StringPart { - Word(&'static str), - Data(&'static str), - Inline(&'static str), -} - -#[derive(Clone, Debug, PartialEq)] -struct LBox { - name: usize, - cell: RefCell, -} - -#[derive(Clone, Debug, PartialEq)] -struct Fn { - name: &'static str, - body: Vec, - //...etc -} - -#[derive(Clone, Debug, PartialEq)] -enum Value { - Nil, - Placeholder, - Boolean(bool), - Keyword(usize), - Interned(usize), - FnDecl(usize), - String(Rc), - Number(f64), - Tuple(Rc>), - List(Box>), - Dict(Box>), - Box(Rc), - Fn(Rc>), -} - -fn futz() { - let foo: &'static str = "foo"; - let baz: Vec = vec![]; - let bar: Range = 1..3; - let quux: Vector = Vector::new(); - let fuzz = Rc::new(quux); - let blah = Box::new(foo); - let val = Value::Number(12.09); - let foo: f64 = 12.0; -} diff --git a/src/old_main.rs b/src/old_main.rs deleted file mode 100644 index 15750cc..0000000 --- a/src/old_main.rs +++ /dev/null @@ -1,212 +0,0 @@ -// an implementation of Ludus - -// curently left undone (and not adding for a while yet): -// * sets -// * interpolated strings & string patterns -// * pkgs, namespaces, imports, `use` forms -// * with forms -// * test forms -// * ignored words - -// todo: -// * [x] rewrite fn parser to use chumsky::Recursive::declare/define -// - [x] do this to extract/simplify/DRY things like tuple patterns, fn clauses, etc. -// * [x] Work around chumsky::Stream::from_iter().spanned disappearing in most recent version -// * [x] investigate using labels (which is behind a compiler flag, somehow) -// * [ ] write parsing errors -// * [ ] wire up Ariadne parsing errors -// * [x] add stack traces and code locations to panics -// * [x] validation -// * [x] break this out into multiple files -// * [x] write a tree-walk VM -// - [x] learn how to deal with lifetimes -// - [x] with stack mechanics and refcounting -// - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM) -// - [ ] with all the necessary forms for current Ludus -// * [x] guards in match clauses -// * [x] `as` patterns -// * [x] splat patterns in tuples, lists, dicts -// * [x] splats in list and dict literals -// * [x] `loop` and `recur` -// * [x] string patterns -// * [x] string interpolation -// * [x] docstrings -// * [x] write `base` in Rust -// * [ ] turn this into a library function -// * [ ] compile this into WASM -// * [ ] perf testing - -use chumsky::{input::Stream, prelude::*}; -use rust_embed::Embed; - -mod spans; - -mod lexer; -use crate::lexer::*; - -mod value; -use crate::value::*; - -mod parser; -use crate::parser::*; - -mod base; -use crate::base::*; - -mod validator; -use crate::validator::*; - -mod process; -use crate::process::*; - -mod errors; -use crate::errors::*; - -mod byte_values; -mod compiler; -mod memory_sandbox; - -#[derive(Embed)] -#[folder = "assets/"] -struct Asset; - -pub fn prelude<'src>() -> ( - Vec<(String, Value<'src>)>, - std::collections::HashMap<*const Ast, FnInfo>, -) { - let prelude = Asset::get("prelude.ld").unwrap().data.into_owned(); - // we know for sure Prelude should live through the whole run of the program - let leaked = Box::leak(Box::new(prelude)); - let prelude = std::str::from_utf8(leaked).unwrap(); - - let (ptoks, perrs) = lexer().parse(prelude).into_output_errors(); - if !perrs.is_empty() { - println!("Errors lexing Prelude"); - println!("{:?}", perrs); - panic!(); - } - - let ptoks = ptoks.unwrap(); - - let (p_ast, perrs) = parser() - .parse(Stream::from_iter(ptoks).map((0..prelude.len()).into(), |(t, s)| (t, s))) - .into_output_errors(); - if !perrs.is_empty() { - println!("Errors parsing Prelude"); - println!("{:?}", perrs); - panic!(); - } - - let prelude_parsed = Box::leak(Box::new(p_ast.unwrap())); - let base_pkg = base(); - - let mut v6or = Validator::new( - &prelude_parsed.0, - prelude_parsed.1, - "prelude", - prelude, - &base_pkg, - ); - v6or.validate(); - - if !v6or.errors.is_empty() { - report_invalidation(v6or.errors); - panic!("interal Ludus error: invalid prelude") - } - - let mut base_ctx = Process::<'src> { - input: "prelude", - src: prelude, - locals: base_pkg.clone(), - ast: &prelude_parsed.0, - span: prelude_parsed.1, - prelude: vec![], - fn_info: v6or.fn_info, - }; - - let prelude = base_ctx.eval(); - - let mut p_ctx = vec![]; - - match prelude { - Ok(Value::Dict(p_dict)) => { - for (key, value) in p_dict.iter() { - p_ctx.push((key.to_string(), value.clone())) - } - } - Ok(_) => { - println!("Bad Prelude export"); - panic!(); - } - Err(LErr { msg, .. }) => { - println!("Error running Prelude"); - println!("{:?}", msg); - panic!(); - } - }; - - (p_ctx, base_ctx.fn_info) -} - -pub fn run(src: &'static str) { - let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); - if !lex_errs.is_empty() { - println!("{:?}", lex_errs); - return; - } - let tokens = tokens.unwrap(); - let to_parse = tokens.clone(); - - let (parse_result, parse_errors) = parser() - .parse(Stream::from_iter(to_parse).map((0..src.len()).into(), |(t, s)| (t, s))) - .into_output_errors(); - if !parse_errors.is_empty() { - println!("{:?}", parse_errors); - return; - } - - let parsed = parse_result.unwrap(); - - let (prelude_ctx, mut prelude_fn_info) = prelude(); - - let mut v6or = Validator::new(&parsed.0, parsed.1, "script", src, &prelude_ctx); - - v6or.validate(); - - if !v6or.errors.is_empty() { - report_invalidation(v6or.errors); - return; - } - - prelude_fn_info.extend(&mut v6or.fn_info.into_iter()); - - let mut proc = Process { - input: "script", - src, - locals: vec![], - prelude: prelude_ctx, - ast: &parsed.0, - span: parsed.1, - fn_info: prelude_fn_info, - }; - - let result = proc.eval(); - - match result { - Ok(result) => println!("{}", result), - Err(err) => report_panic(err), - } -} - -pub fn main() { - let src = " -loop (100000, 1) with { - (1, acc) -> acc - (n, acc) -> recur (dec (n), add (n, acc)) -} - "; - run(src); - // struct_scalpel::print_dissection_info::() - // struct_scalpel::print_dissection_info::(); - // println!("{}", std::mem::size_of::()) -} diff --git a/src/old_value.rs b/src/old_value.rs deleted file mode 100644 index 5b4aec1..0000000 --- a/src/old_value.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::base::*; -use crate::parser::*; -use crate::spans::*; -use imbl::*; -use std::cell::RefCell; -use std::fmt; -use std::rc::Rc; -use struct_scalpel::Dissectible; - -#[derive(Clone, Debug)] -pub struct Fn<'src> { - pub name: String, - pub body: &'src Vec>, - pub doc: Option, - pub enclosing: Vec<(String, Value<'src>)>, - pub has_run: bool, - pub input: &'static str, - pub src: &'static str, -} - -#[derive(Debug, Dissectible)] -pub enum Value<'src> { - Nil, - Placeholder, - Boolean(bool), - Number(f64), - Keyword(&'static str), - InternedString(&'static str), - AllocatedString(Rc), - // on the heap for now - Tuple(Rc>), - Args(Rc>), - List(Vector), - Dict(HashMap<&'static str, Self>), - Box(&'static str, Rc>), - Fn(Rc>>), - FnDecl(&'static str), - Base(BaseFn<'src>), - Recur(Vec), - // Set(HashSet), - // Sets are hard - // Sets require Eq - // Eq is not implemented on f64, because NaNs - // We could use ordered_float::NotNan - // Let's defer that - // We're not really using sets in Ludus - - // Other things we're not implementing yet: - // pkgs, nses, tests -} - -impl<'src> Clone for Value<'src> { - fn clone(&self) -> Value<'src> { - match self { - Value::Nil => Value::Nil, - Value::Boolean(b) => Value::Boolean(*b), - Value::InternedString(s) => Value::InternedString(s), - Value::AllocatedString(s) => Value::AllocatedString(s.clone()), - Value::Keyword(s) => Value::Keyword(s), - Value::Number(n) => Value::Number(*n), - Value::Tuple(t) => Value::Tuple(t.clone()), - Value::Args(a) => Value::Args(a.clone()), - Value::Fn(f) => Value::Fn(f.clone()), - Value::FnDecl(name) => Value::FnDecl(name), - Value::List(l) => Value::List(l.clone()), - Value::Dict(d) => Value::Dict(d.clone()), - Value::Box(name, b) => Value::Box(name, b.clone()), - Value::Placeholder => Value::Placeholder, - Value::Base(b) => Value::Base(b.clone()), - Value::Recur(..) => unreachable!(), - } - } -} - -impl fmt::Display for Value<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Value::Nil => write!(f, "nil"), - Value::Boolean(b) => write!(f, "{b}"), - Value::Number(n) => write!(f, "{n}"), - Value::Keyword(k) => write!(f, ":{k}"), - Value::InternedString(s) => write!(f, "\"{s}\""), - Value::AllocatedString(s) => write!(f, "\"{s}\""), - Value::Fn(fun) => write!(f, "fn {}", fun.borrow().name), - Value::FnDecl(name) => write!(f, "fn {name}"), - Value::Tuple(t) | Value::Args(t) => write!( - f, - "({})", - t.iter() - .map(|x| x.to_string()) - .collect::>() - .join(", ") - ), - Value::List(l) => write!( - f, - "[{}]", - l.iter() - .map(|x| x.to_string()) - .collect::>() - .join(", ") - ), - Value::Dict(d) => write!( - f, - "#{{{}}}", - d.iter() - .map(|(k, v)| format!(":{k} {v}")) - .collect::>() - .join(", ") - ), - Value::Box(name, value) => { - write!( - f, - "box {}: [{}]", - name, - &value.try_borrow().unwrap().to_string() - ) - } - Value::Placeholder => write!(f, "_"), - Value::Base(..) => unreachable!(), - Value::Recur(..) => unreachable!(), - } - } -} - -impl Value<'_> { - pub fn bool(&self) -> bool { - !matches!(self, Value::Nil | Value::Boolean(false)) - } -} - -impl<'src> PartialEq for Value<'src> { - fn eq(&self, other: &Value<'src>) -> bool { - match (self, other) { - // value equality types - (Value::Nil, Value::Nil) => true, - (Value::Boolean(x), Value::Boolean(y)) => x == y, - (Value::Number(x), Value::Number(y)) => x == y, - (Value::InternedString(x), Value::InternedString(y)) => x == y, - (Value::AllocatedString(x), Value::AllocatedString(y)) => x == y, - (Value::InternedString(x), Value::AllocatedString(y)) => *x == **y, - (Value::AllocatedString(x), Value::InternedString(y)) => **x == *y, - (Value::Keyword(x), Value::Keyword(y)) => x == y, - (Value::Tuple(x), Value::Tuple(y)) => x == y, - (Value::List(x), Value::List(y)) => x == y, - (Value::Dict(x), Value::Dict(y)) => x == y, - // reference equality types - (Value::Fn(x), Value::Fn(y)) => { - Rc::>>::as_ptr(x) == Rc::>>::as_ptr(y) - } - (Value::Box(_, x), Value::Box(_, y)) => { - Rc::>>::as_ptr(x) == Rc::>>::as_ptr(y) - } - _ => false, - } - } -} - -impl Eq for Value<'_> {} - -impl Value<'_> { - pub fn interpolate(&self) -> String { - match self { - Value::Nil => String::new(), - Value::Boolean(b) => format!("{b}"), - Value::Number(n) => format!("{n}"), - Value::Keyword(k) => format!(":{k}"), - Value::AllocatedString(s) => format!("{s}"), - Value::InternedString(s) => s.to_string(), - Value::Box(_, x) => x.borrow().interpolate(), - Value::Tuple(xs) => xs - .iter() - .map(|x| x.interpolate()) - .collect::>() - .join(", "), - Value::List(xs) => xs - .iter() - .map(|x| x.interpolate()) - .collect::>() - .join(", "), - Value::Dict(xs) => xs - .iter() - .map(|(k, v)| format!(":{} {}", k, v.interpolate())) - .collect::>() - .join(", "), - Value::Fn(x) => format!("fn {}", x.borrow().name), - Value::FnDecl(name) => format!("fn {name}"), - Value::Placeholder => unreachable!(), - Value::Args(_) => unreachable!(), - Value::Recur(_) => unreachable!(), - Value::Base(_) => unreachable!(), - } - } -} diff --git a/src/old_vm.rs b/src/old_vm.rs deleted file mode 100644 index 2e108b9..0000000 --- a/src/old_vm.rs +++ /dev/null @@ -1,533 +0,0 @@ -use crate::base::*; -use crate::parser::*; -use crate::value::*; -use imbl::HashMap; -use imbl::Vector; -use std::cell::RefCell; -use std::rc::Rc; - -#[derive(Clone, Debug)] -pub struct LudusError { - pub msg: String, -} - -// oy -// lifetimes are a mess -// I need 'src kind of everywhere -// But (maybe) using 'src in eval -// for ctx -// means I can't borrow it mutably -// I guess the question is how to get -// the branches for Ast::Block and Ast::If -// to work with a mutable borrow of ctx - -// pub struct Ctx<'src> { -// pub locals: Vec<(&'src str, Value<'src>)>, -// // pub names: Vec<&'src str>, -// // pub values: Vec>, -// } - -// impl<'src> Ctx<'src> { -// pub fn resolve(&self, name: &'src str) -> Value { -// if let Some((_, val)) = self.locals.iter().rev().find(|(bound, _)| *bound == name) { -// val.clone() -// } else { -// unreachable!() -// } -// } - -// pub fn store(&mut self, name: &'src str, value: Value<'src>) { -// self.locals.push((name, value)); -// } -// } - -type Context<'src> = Vec<(String, Value<'src>)>; - -pub fn match_eq(x: T, y: T, z: U) -> Option -where - T: PartialEq, -{ - if x == y { - Some(z) - } else { - None - } -} - -pub fn match_pattern<'src, 'a>( - patt: &Pattern, - val: &Value<'src>, - ctx: &'a mut Context<'src>, -) -> Option<&'a mut Context<'src>> { - match (patt, val) { - (Pattern::Nil, Value::Nil) => Some(ctx), - (Pattern::Placeholder, _) => Some(ctx), - (Pattern::Number(x), Value::Number(y)) => match_eq(x, y, ctx), - (Pattern::Boolean(x), Value::Boolean(y)) => match_eq(x, y, ctx), - (Pattern::Keyword(x), Value::Keyword(y)) => match_eq(x, y, ctx), - (Pattern::String(x), Value::InternedString(y)) => match_eq(x, y, ctx), - (Pattern::String(x), Value::AllocatedString(y)) => match_eq(&x.to_string(), y, ctx), - (Pattern::Interpolated(_, StringMatcher(matcher)), Value::InternedString(y)) => { - match matcher(y.to_string()) { - Some(matches) => { - let mut matches = matches - .iter() - .map(|(word, string)| { - ( - word.clone(), - Value::AllocatedString(Rc::new(string.clone())), - ) - }) - .collect::>(); - ctx.append(&mut matches); - Some(ctx) - } - None => None, - } - } - (Pattern::Word(w), val) => { - ctx.push((w.to_string(), val.clone())); - Some(ctx) - } - (Pattern::As(word, type_str), value) => { - let ludus_type = r#type(value); - let type_kw = Value::Keyword(type_str); - if type_kw == ludus_type { - ctx.push((word.to_string(), value.clone())); - Some(ctx) - } else { - None - } - } - // todo: add splats to these match clauses - (Pattern::Tuple(x), Value::Tuple(y)) => { - let has_splat = x - .iter() - .any(|patt| matches!(patt, (Pattern::Splattern(_), _))); - if x.len() > y.len() || (!has_splat && x.len() != y.len()) { - return None; - }; - let to = ctx.len(); - for i in 0..x.len() { - if let Pattern::Splattern(patt) = &x[i].0 { - let mut list = Vector::new(); - for i in i..y.len() { - list.push_back(y[i].clone()) - } - let list = Value::List(list); - match_pattern(&patt.0, &list, ctx); - } else if match_pattern(&x[i].0, &y[i], ctx).is_none() { - while ctx.len() > to { - ctx.pop(); - } - return None; - } - } - Some(ctx) - } - (Pattern::List(x), Value::List(y)) => { - let has_splat = x - .iter() - .any(|patt| matches!(patt, (Pattern::Splattern(_), _))); - if x.len() > y.len() || (!has_splat && x.len() != y.len()) { - return None; - }; - let to = ctx.len(); - for (i, (patt, _)) in x.iter().enumerate() { - if let Pattern::Splattern(patt) = &patt { - let list = Value::List(y.skip(i)); - match_pattern(&patt.0, &list, ctx); - } else if match_pattern(patt, y.get(i).unwrap(), ctx).is_none() { - while ctx.len() > to { - ctx.pop(); - } - return None; - } - } - Some(ctx) - } - // TODO: optimize this on several levels - // - [ ] opportunistic mutation - // - [ ] get rid of all the pointer indirection in word splats - (Pattern::Dict(x), Value::Dict(y)) => { - let has_splat = x - .iter() - .any(|patt| matches!(patt, (Pattern::Splattern(_), _))); - if x.len() > y.len() || (!has_splat && x.len() != y.len()) { - return None; - }; - let to = ctx.len(); - let mut matched = vec![]; - for (pattern, _) in x { - match pattern { - Pattern::Pair(key, patt) => { - if let Some(val) = y.get(key) { - if match_pattern(&patt.0, val, ctx).is_none() { - while ctx.len() > to { - ctx.pop(); - } - return None; - } else { - matched.push(key); - } - } else { - return None; - }; - } - Pattern::Splattern(pattern) => match pattern.0 { - Pattern::Word(w) => { - // TODO: find a way to take ownership - // this will ALWAYS make structural changes, because of this clone - // we want opportunistic mutation if possible - let mut unmatched = y.clone(); - for key in matched.iter() { - unmatched.remove(*key); - } - ctx.push((w.to_string(), Value::Dict(unmatched))); - } - Pattern::Placeholder => (), - _ => unreachable!(), - }, - _ => unreachable!(), - } - } - Some(ctx) - } - _ => None, - } -} - -pub fn match_clauses<'src>( - value: &Value<'src>, - clauses: &'src [MatchClause], - ctx: &mut Context<'src>, -) -> Result, LudusError> { - let to = ctx.len(); - for MatchClause { patt, body, guard } in clauses.iter() { - if let Some(ctx) = match_pattern(&patt.0, value, ctx) { - let pass_guard = match guard { - None => true, - Some((ast, _)) => { - let guard_res = eval(ast, ctx); - match &guard_res { - Err(_) => return guard_res, - Ok(val) => val.bool(), - } - } - }; - if !pass_guard { - while ctx.len() > to { - ctx.pop(); - } - continue; - } - let res = eval(&body.0, ctx); - while ctx.len() > to { - ctx.pop(); - } - return res; - } - } - Err(LudusError { - msg: "no match".to_string(), - }) -} - -pub fn apply<'src>( - callee: Value<'src>, - caller: Value<'src>, - ctx: &mut Context, -) -> Result, LudusError> { - match (callee, caller) { - (Value::Keyword(kw), Value::Dict(dict)) => { - if let Some(val) = dict.get(kw) { - Ok(val.clone()) - } else { - Ok(Value::Nil) - } - } - (Value::Dict(dict), Value::Keyword(kw)) => { - if let Some(val) = dict.get(kw) { - Ok(val.clone()) - } else { - Ok(Value::Nil) - } - } - (Value::Fn(f), Value::Tuple(args)) => { - let args = Value::Tuple(args); - match_clauses(&args, f.body, ctx) - } - (Value::Fn(_f), Value::Args(_args)) => todo!(), - (_, Value::Keyword(_)) => Ok(Value::Nil), - (_, Value::Args(_)) => Err(LudusError { - msg: "you may only call a function".to_string(), - }), - (Value::Base(f), Value::Tuple(args)) => match f { - Base::Nullary(f) => { - if args.len() != 0 { - Err(LudusError { - msg: "wrong arity: expected 0 arguments".to_string(), - }) - } else { - Ok(f()) - } - } - Base::Unary(f) => { - if args.len() != 1 { - Err(LudusError { - msg: "wrong arity: expected 1 argument".to_string(), - }) - } else { - Ok(f(&args[0])) - } - } - Base::Binary(r#fn) => { - if args.len() != 2 { - Err(LudusError { - msg: "wrong arity: expected 2 arguments".to_string(), - }) - } else { - Ok(r#fn(&args[0], &args[1])) - } - } - Base::Ternary(f) => { - if args.len() != 3 { - Err(LudusError { - msg: "wrong arity: expected 3 arguments".to_string(), - }) - } else { - Ok(f(&args[0], &args[1], &args[2])) - } - } - }, - _ => unreachable!(), - } -} - -pub fn eval<'src, 'a>( - ast: &'src Ast, - ctx: &'a mut Vec<(String, Value<'src>)>, -) -> Result, LudusError> { - match ast { - Ast::Nil => Ok(Value::Nil), - Ast::Boolean(b) => Ok(Value::Boolean(*b)), - Ast::Number(n) => Ok(Value::Number(*n)), - Ast::Keyword(k) => Ok(Value::Keyword(k)), - Ast::String(s) => Ok(Value::InternedString(s)), - Ast::Interpolated(parts) => { - let mut interpolated = String::new(); - for part in parts { - match &part.0 { - StringPart::Data(s) => interpolated.push_str(s.as_str()), - StringPart::Word(w) => { - let val = if let Some((_, value)) = - ctx.iter().rev().find(|(name, _)| w == name) - { - value.clone() - } else { - return Err(LudusError { - msg: format!("unbound name {w}"), - }); - }; - interpolated.push_str(val.interpolate().as_str()) - } - StringPart::Inline(_) => unreachable!(), - } - } - Ok(Value::AllocatedString(Rc::new(interpolated))) - } - Ast::Block(exprs) => { - let to = ctx.len(); - let mut result = Value::Nil; - for (expr, _) in exprs { - result = eval(expr, ctx)?; - } - while ctx.len() > to { - ctx.pop(); - } - Ok(result) - } - Ast::If(cond, if_true, if_false) => { - let truthy = eval(&cond.0, ctx)?.bool(); - if truthy { - eval(&if_true.0, ctx) - } else { - eval(&if_false.0, ctx) - } - } - Ast::List(members) => { - let mut vect = Vector::new(); - for member in members { - if let Ast::Splat(_) = member.0 { - let to_splat = eval(&member.0, ctx)?; - match to_splat { - Value::List(list) => vect.append(list), - _ => { - return Err(LudusError { - msg: "only lists may be splatted into lists".to_string(), - }) - } - } - } else { - vect.push_back(eval(&member.0, ctx)?) - } - } - Ok(Value::List(vect)) - } - Ast::Tuple(members) => { - let mut vect = Vec::new(); - for member in members { - vect.push(eval(&member.0, ctx)?); - } - Ok(Value::Tuple(Rc::new(vect))) - } - Ast::Word(w) | Ast::Splat(w) => { - let val = if let Some((_, value)) = ctx.iter().rev().find(|(name, _)| w == name) { - value.clone() - } else { - return Err(LudusError { - msg: format!("unbound name {w}"), - }); - }; - Ok(val) - } - Ast::Let(patt, expr) => { - let val = eval(&expr.0, ctx)?; - match match_pattern(&patt.0, &val, ctx) { - Some(_) => Ok(val), - None => Err(LudusError { - msg: "No match".to_string(), - }), - } - } - Ast::Placeholder => Ok(Value::Placeholder), - Ast::Error => unreachable!(), - Ast::Arguments(a) => { - let mut args = vec![]; - for (arg, _) in a.iter() { - let arg = eval(arg, ctx)?; - args.push(arg); - } - if args.iter().any(|arg| matches!(arg, Value::Placeholder)) { - Ok(Value::Args(Rc::new(args))) - } else { - Ok(Value::Tuple(Rc::new(args))) - } - } - Ast::Dict(terms) => { - let mut dict = HashMap::new(); - for term in terms { - let (term, _) = term; - match term { - Ast::Pair(key, value) => { - let value = eval(&value.0, ctx)?; - dict.insert(*key, value); - } - Ast::Splat(_) => { - let resolved = eval(term, ctx)?; - let Value::Dict(to_splat) = resolved else { - return Err(LudusError { - msg: "cannot splat non-dict into dict".to_string(), - }); - }; - dict = to_splat.union(dict); - } - _ => unreachable!(), - } - } - Ok(Value::Dict(dict)) - } - Ast::Box(name, expr) => { - let val = eval(&expr.0, ctx)?; - let boxed = Value::Box(name, Rc::new(RefCell::new(val))); - ctx.push((name.to_string(), boxed.clone())); - Ok(boxed) - } - Ast::Synthetic(root, first, rest) => { - let root = eval(&root.0, ctx)?; - let first = eval(&first.0, ctx)?; - let mut curr = apply(root, first, ctx)?; - for term in rest.iter() { - let next = eval(&term.0, ctx)?; - curr = apply(curr, next, ctx)?; - } - Ok(curr) - } - Ast::When(clauses) => { - for clause in clauses.iter() { - let WhenClause { cond, body } = &clause.0; - if eval(&cond.0, ctx)?.bool() { - return eval(&body.0, ctx); - }; - } - Err(LudusError { - msg: "no match".to_string(), - }) - } - Ast::Match(value, clauses) => { - let value = eval(&value.0, ctx)?; - match_clauses(&value, clauses, ctx) - } - Ast::Fn(name, clauses, doc) => { - let doc = doc.map(|s| s.to_string()); - let the_fn = Value::Fn::<'src>(Rc::new(Fn::<'src> { - name: name.to_string(), - body: clauses, - doc, - })); - ctx.push((name.to_string(), the_fn.clone())); - Ok(the_fn) - } - Ast::FnDeclaration(_name) => todo!(), - Ast::Panic(msg) => { - let msg = eval(&msg.0, ctx)?; - Err(LudusError { - msg: msg.to_string(), - }) - } - Ast::Repeat(times, body) => { - let times_num = match eval(×.0, ctx) { - Ok(Value::Number(n)) => n as usize, - _ => { - return Err(LudusError { - msg: "repeat may only take numbers".to_string(), - }) - } - }; - for _ in 0..times_num { - eval(&body.0, ctx)?; - } - Ok(Value::Nil) - } - Ast::Do(terms) => { - let mut result = eval(&terms[0].0, ctx)?; - for (term, _) in terms.iter().skip(1) { - let next = eval(term, ctx)?; - let arg = Value::Tuple(Rc::new(vec![result])); - result = apply(next, arg, ctx)?; - } - Ok(result) - } - Ast::Pair(..) => { - unreachable!() - } - Ast::Loop(init, clauses) => { - let mut args = eval(&init.0, ctx)?; - loop { - let result = match_clauses(&args, clauses, ctx)?; - if let Value::Recur(recur_args) = result { - args = Value::Tuple(Rc::new(recur_args)); - } else { - return Ok(result); - } - } - } - Ast::Recur(args) => { - let mut vect = Vec::new(); - for arg in args { - vect.push(eval(&arg.0, ctx)?); - } - Ok(Value::Recur(vect)) - } - } -} diff --git a/src/op.rs b/src/op.rs deleted file mode 100644 index bfe0252..0000000 --- a/src/op.rs +++ /dev/null @@ -1,226 +0,0 @@ -use num_derive::{FromPrimitive, ToPrimitive}; - -#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum Op { - Noop, - Nothing, - Nil, - True, - False, - Constant, - Jump, - JumpIfFalse, - JumpIfTrue, - Pop, - PopN, - PushBinding, - PushGlobal, - Store, - StoreN, - Stash, - Load, - LoadN, - ResetMatch, - UnconditionalMatch, - MatchNil, - MatchTrue, - MatchFalse, - PanicIfNoMatch, - MatchConstant, - MatchString, - PushStringMatches, - MatchType, - MatchTuple, - MatchSplattedTuple, - PushTuple, - LoadTuple, - LoadSplattedTuple, - MatchList, - MatchSplattedList, - LoadList, - LoadSplattedList, - PushList, - AppendList, - ConcatList, - PushDict, - AppendDict, - ConcatDict, - LoadDictValue, - MatchDict, - MatchSplattedDict, - DropDictEntry, - PushBox, - GetKey, - PanicNoWhen, - JumpIfNoMatch, - JumpIfMatch, - PanicNoMatch, - TypeOf, - JumpBack, - JumpIfZero, - Duplicate, - Decrement, - ToInt, - MatchDepth, - Panic, - EmptyString, - ConcatStrings, - Stringify, - - Call, - TailCall, - Return, - Partial, - - Eq, - Add, - Sub, - Mult, - Div, - Unbox, - BoxStore, - Assert, - Get, - At, - - Not, - Print, - SetUpvalue, - GetUpvalue, - - Msg, - // Inc, - // Dec, - // Gt, - // Gte, - // Lt, - // Lte, - // Mod, - // Round, - // Ceil, - // Floor, - // Random, - // Sqrt, - - // Assoc, - // Concat, - // Conj, - // Count, - // Disj, - // Dissoc, - // Range, - // Rest, - // Slice, - - // "atan_2" math/atan2 - // "chars" chars - // "cos" math/cos - // "doc" doc - // "downcase" string/ascii-lower - // "pi" math/pi - // "show" show - // "sin" math/sin - // "split" string/split - // "str_slice" string/slice - // "tan" math/tan - // "trim" string/trim - // "triml" string/triml - // "trimr" string/trimr - // "upcase" string/ascii-upper -} - -impl std::fmt::Display for Op { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use Op::*; - let rep = match self { - Msg => "msg", - Noop => "noop", - Nothing => "nothing", - Nil => "nil", - True => "true", - False => "false", - Constant => "constant", - Jump => "jump", - JumpIfFalse => "jump_if_false", - JumpIfTrue => "jump_if_true", - Pop => "pop", - PopN => "pop_n", - PushBinding => "push_binding", - PushGlobal => "push_global", - Store => "store", - StoreN => "store_n", - Stash => "stash", - Load => "load", - LoadN => "load_n", - UnconditionalMatch => "match", - MatchNil => "match_nil", - MatchTrue => "match_true", - MatchFalse => "match_false", - ResetMatch => "reset_match", - PanicIfNoMatch => "panic_if_no_match", - MatchConstant => "match_constant", - MatchString => "match_string", - PushStringMatches => "push_string_matches", - MatchType => "match_type", - MatchTuple => "match_tuple", - MatchSplattedTuple => "match_splatted_tuple", - PushTuple => "push_tuple", - LoadTuple => "load_tuple", - LoadSplattedTuple => "load_splatted_tuple", - MatchList => "match_list", - MatchSplattedList => "match_splatted_list", - LoadList => "load_list", - LoadSplattedList => "load_splatted_list", - PushList => "push_list", - AppendList => "append_list", - ConcatList => "concat_list", - PushDict => "push_dict", - AppendDict => "append_dict", - ConcatDict => "concat_dict", - LoadDictValue => "load_dict_value", - MatchDict => "match_dict", - MatchSplattedDict => "match_splatted_dict", - DropDictEntry => "drop_dict_entry", - PushBox => "push_box", - GetKey => "get_key", - PanicNoWhen => "panic_no_when", - JumpIfNoMatch => "jump_if_no_match", - JumpIfMatch => "jump_if_match", - PanicNoMatch => "panic_no_match", - TypeOf => "type_of", - JumpBack => "jump_back", - JumpIfZero => "jump_if_zero", - Decrement => "decrement", - ToInt => "truncate", - Duplicate => "duplicate", - MatchDepth => "match_depth", - Panic => "panic", - EmptyString => "empty_string", - ConcatStrings => "concat_strings", - Stringify => "stringify", - Print => "print", - - Eq => "eq", - Add => "add", - Sub => "sub", - Mult => "mult", - Div => "div", - Unbox => "unbox", - BoxStore => "box_store", - Assert => "assert", - Get => "get", - At => "at", - - Not => "not", - - Call => "call", - Return => "return", - Partial => "partial", - TailCall => "tail_call", - - SetUpvalue => "set_upvalue", - GetUpvalue => "get_upvalue", - }; - write!(f, "{rep}") - } -} diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index e13bc5c..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,1150 +0,0 @@ -// TODO: move AST to its own module -// TODO: remove StringMatcher cruft -// TODO: good error messages? - -use crate::lexer::*; -use crate::spans::*; -use chumsky::{input::ValueInput, prelude::*, recursive::Recursive}; -use std::fmt; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum StringPart { - Data(String), - Word(String), - Inline(String), -} - -impl fmt::Display for StringPart { - fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result { - let rep = match self { - StringPart::Word(s) => format!("{{{s}}}"), - StringPart::Data(s) => s.to_string(), - StringPart::Inline(s) => s.to_string(), - }; - write!(f, "{}", rep) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Ast { - // a special Error node - // may come in handy? - Error, - - And, - Or, - - // expression nodes - Placeholder, - Nil, - Boolean(bool), - Number(f64), - Keyword(&'static str), - Word(&'static str), - String(&'static str), - Interpolated(Vec>), - Block(Vec>), - If(Box>, Box>, Box>), - Tuple(Vec>), - Arguments(Vec>), - List(Vec>), - Dict(Vec>), - Let(Box>, Box>), - LBox(&'static str, Box>), - Synthetic(Box>, Box>, Vec>), - When(Vec>), - WhenClause(Box>, Box>), - Match(Box>, Vec>), - MatchClause( - Box>, - Box>>, - Box>, - ), - Fn(&'static str, Box>, Option<&'static str>), - FnBody(Vec>), - FnDeclaration(&'static str), - Panic(Box>), - Do(Vec>), - Repeat(Box>, Box>), - Splat(&'static str), - Pair(&'static str, Box>), - Loop(Box>, Vec>), - Recur(Vec>), - - // pattern nodes - NilPattern, - BooleanPattern(bool), - NumberPattern(f64), - StringPattern(&'static str), - InterpolatedPattern(Vec>, StringMatcher), - KeywordPattern(&'static str), - WordPattern(&'static str), - AsPattern(&'static str, &'static str), - Splattern(Box>), - PlaceholderPattern, - TuplePattern(Vec>), - ListPattern(Vec>), - PairPattern(&'static str, Box>), - DictPattern(Vec>), -} - -impl Ast { - pub fn show(&self) -> String { - use Ast::*; - match self { - And => "and".to_string(), - Or => "or".to_string(), - Error => unreachable!(), - Nil | NilPattern => "nil".to_string(), - String(s) | StringPattern(s) => format!("\"{s}\""), - Interpolated(strs) | InterpolatedPattern(strs, _) => { - let mut out = "".to_string(); - out = format!("\"{out}"); - for (part, _) in strs { - out = format!("{out}{part}"); - } - format!("{out}\"") - } - Boolean(b) | BooleanPattern(b) => b.to_string(), - Number(n) | NumberPattern(n) => n.to_string(), - Keyword(k) | KeywordPattern(k) => format!(":{k}"), - Word(w) | WordPattern(w) => w.to_string(), - Block(lines) => { - let mut out = "{\n".to_string(); - for (line, _) in lines { - out = format!("{out}\n {}", line.show()); - } - format!("{out}\n}}") - } - If(cond, then, r#else) => format!( - "if {}\n then {}\n else {}", - cond.0.show(), - then.0.show(), - r#else.0.show() - ), - Let(pattern, expression) => { - format!("let {} = {}", pattern.0.show(), expression.0.show()) - } - Dict(entries) | DictPattern(entries) => { - format!( - "#{{{}}}", - entries - .iter() - .map(|(pair, _)| pair.show()) - .collect::>() - .join(", ") - ) - } - List(members) | ListPattern(members) => format!( - "[{}]", - members - .iter() - .map(|(member, _)| member.show()) - .collect::>() - .join(", ") - ), - Arguments(members) => format!( - "({})", - members - .iter() - .map(|(member, _)| member.show()) - .collect::>() - .join(", ") - ), - Tuple(members) | TuplePattern(members) => format!( - "({})", - members - .iter() - .map(|(member, _)| member.show()) - .collect::>() - .join(", ") - ), - Synthetic(root, first, rest) => format!( - "{} {} {}", - root.0.show(), - first.0.show(), - rest.iter() - .map(|(term, _)| term.show()) - .collect::>() - .join(" ") - ), - When(clauses) => format!( - "when {{\n {}\n}}", - clauses - .iter() - .map(|(clause, _)| clause.show()) - .collect::>() - .join("\n ") - ), - Placeholder | PlaceholderPattern => "_".to_string(), - LBox(name, rhs) => format!("box {name} = {}", rhs.0.show()), - Match(scrutinee, clauses) => format!( - "match {} with {{\n {}\n}}", - scrutinee.0.show(), - clauses - .iter() - .map(|(clause, _)| clause.show()) - .collect::>() - .join("\n ") - ), - FnBody(clauses) => clauses - .iter() - .map(|(clause, _)| clause.show()) - .collect::>() - .join("\n "), - Fn(name, body, doc) => { - let mut out = format!("fn {name} {{\n"); - if let Some(doc) = doc { - out = format!("{out} {doc}\n"); - } - format!("{out} {}\n}}", body.0.show()) - } - FnDeclaration(name) => format!("fn {name}"), - Panic(expr) => format!("panic! {}", expr.0.show()), - Do(terms) => { - format!( - "do {}", - terms - .iter() - .map(|(term, _)| term.show()) - .collect::>() - .join(" > ") - ) - } - Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()), - Splat(word) => format!("...{}", word), - Splattern(pattern) => format!("...{}", pattern.0.show()), - AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"), - Pair(key, value) | PairPattern(key, value) => format!(":{key} {}", value.0.show()), - Loop(init, body) => format!( - "loop {} with {{\n {}\n}}", - init.0.show(), - body.iter() - .map(|(clause, _)| clause.show()) - .collect::>() - .join("\n ") - ), - Recur(args) => format!( - "recur ({})", - args.iter() - .map(|(arg, _)| arg.show()) - .collect::>() - .join(", ") - ), - MatchClause(pattern, guard, body) => { - let mut out = pattern.0.show(); - if let Some(guard) = guard.as_ref() { - out = format!("{out} if {}", guard.0.show()); - } - format!("{out} -> {}", body.0.show()) - } - WhenClause(cond, body) => format!("{} -> {}", cond.0.show(), body.0.show()), - } - } -} - -impl fmt::Display for Ast { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Ast::*; - match self { - And => write!(f, "And"), - Or => write!(f, "Or"), - Error => write!(f, "Error"), - Nil => write!(f, "nil"), - String(s) => write!(f, "String: \"{}\"", s), - Interpolated(strs) => { - write!( - f, - "Interpolated: \"{}\"", - strs.iter() - .map(|(s, _)| s.to_string()) - .collect::>() - .join("") - ) - } - Boolean(b) => write!(f, "Boolean: {}", b), - Number(n) => write!(f, "Number: {}", n), - Keyword(k) => write!(f, "Keyword: :{}", k), - Word(w) => write!(f, "Word: {}", w), - Block(b) => write!( - f, - "Block: <{}>", - b.iter() - .map(|(line, _)| line.to_string()) - .collect::>() - .join("\n") - ), - If(cond, then_branch, else_branch) => write!( - f, - "If: {} Then: {} Else: {}", - cond.0, then_branch.0, else_branch.0 - ), - Let(pattern, expression) => { - write!(f, "Let: {} = {}", pattern.0, expression.0) - } - Dict(entries) => write!( - f, - "#{{{}}}", - entries - .iter() - .map(|pair| pair.0.to_string()) - .collect::>() - .join(", ") - ), - List(l) => write!( - f, - "List: [{}]", - l.iter() - .map(|(line, _)| line.to_string()) - .collect::>() - .join("\n") - ), - Arguments(a) => write!( - f, - "Arguments: ({})", - a.iter() - .map(|(line, _)| line.to_string()) - .collect::>() - .join("\n") - ), - Tuple(t) => write!( - f, - "Tuple: ({})", - t.iter() - .map(|(line, _)| line.to_string()) - .collect::>() - .join("\n") - ), - Synthetic(root, first, rest) => write!( - f, - "Synth: [{}, {}, {}]", - root.0, - first.0, - rest.iter() - .map(|(term, _)| term.to_string()) - .collect::>() - .join("\n") - ), - When(clauses) => write!( - f, - "When: [{}]", - clauses - .iter() - .map(|clause| clause.0.to_string()) - .collect::>() - .join("\n") - ), - Placeholder => write!(f, "Placeholder"), - LBox(_name, _rhs) => todo!(), - Match(value, clauses) => { - write!( - f, - "match: {} with {}", - &value.0.to_string(), - clauses - .iter() - .map(|clause| clause.0.to_string()) - .collect::>() - .join("\n") - ) - } - FnBody(clauses) => { - write!( - f, - "{}", - clauses - .iter() - .map(|clause| clause.0.to_string()) - .collect::>() - .join("\n") - ) - } - Fn(name, body, ..) => { - write!(f, "fn: {name}\n{}", body.0) - } - FnDeclaration(_name) => todo!(), - Panic(_expr) => todo!(), - Do(terms) => { - write!( - f, - "do: {}", - terms - .iter() - .map(|(term, _)| term.to_string()) - .collect::>() - .join(" > ") - ) - } - Repeat(_times, _body) => todo!(), - Splat(word) => { - write!(f, "splat: {}", word) - } - Pair(k, v) => { - write!(f, "pair: {} {}", k, v.0) - } - Loop(init, body) => { - write!( - f, - "loop: {} with {}", - init.0, - body.iter() - .map(|clause| clause.0.to_string()) - .collect::>() - .join("\n") - ) - } - Recur(args) => { - write!( - f, - "recur: {}", - args.iter() - .map(|(arg, _)| arg.to_string()) - .collect::>() - .join(", ") - ) - } - MatchClause(pattern, guard, body) => { - write!( - f, - "match clause: {} if {:?} -> {}", - pattern.0, guard, body.0 - ) - } - WhenClause(cond, body) => { - write!(f, "when clause: {} -> {}", cond.0, body.0) - } - - NilPattern => write!(f, "nil"), - BooleanPattern(b) => write!(f, "{}", b), - NumberPattern(n) => write!(f, "{}", n), - StringPattern(s) => write!(f, "{}", s), - KeywordPattern(k) => write!(f, ":{}", k), - WordPattern(w) => write!(f, "{}", w), - AsPattern(w, t) => write!(f, "{} as :{}", w, t), - Splattern(p) => write!(f, "...{}", p.0), - PlaceholderPattern => write!(f, "_"), - TuplePattern(t) => write!( - f, - "({})", - t.iter() - .map(|x| x.0.to_string()) - .collect::>() - .join(", ") - ), - ListPattern(l) => write!( - f, - "({})", - l.iter() - .map(|x| x.0.to_string()) - .collect::>() - .join(", ") - ), - DictPattern(entries) => write!( - f, - "#{{{}}}", - entries - .iter() - .map(|(pair, _)| pair.to_string()) - .collect::>() - .join(", ") - ), - PairPattern(key, value) => write!(f, ":{} {}", key, value.0), - InterpolatedPattern(strprts, _) => write!( - f, - "interpolated: \"{}\"", - strprts - .iter() - .map(|part| part.0.to_string()) - .collect::>() - .join("") - ), - } - } -} - -pub struct StringMatcher(pub Box Option>>); - -impl PartialEq for StringMatcher { - fn eq(&self, _other: &StringMatcher) -> bool { - true - } -} - -impl Clone for StringMatcher { - fn clone(&self) -> StringMatcher { - unreachable!() - } -} - -impl fmt::Display for StringMatcher { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "string matcher") - } -} - -impl fmt::Debug for StringMatcher { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "string matcher") - } -} - -fn is_word_char(c: char) -> bool { - if c.is_ascii_alphanumeric() { - return true; - }; - matches!(c, '_' | '/' | '?' | '!') -} - -fn parse_string(s: &'static str, span: SimpleSpan) -> Result>, String> { - // println!("parsing string pattern: {s}"); - let mut parts = vec![]; - let mut current_part = String::new(); - let mut start = span.start; - let mut is_word = false; - - let mut chars = s.char_indices(); - while let Some((i, char)) = chars.next() { - match char { - '{' => { - if is_word { - return Err("interpolations must only contain words".to_string()); - }; - match chars.next() { - None => return Err("unclosed brace".to_string()), - Some((_, '{')) => current_part.push('{'), - Some((i, c)) => { - if !current_part.is_empty() { - parts.push(( - StringPart::Data(current_part), - SimpleSpan::new(start, start + i), - )); - }; - current_part = String::new(); - start = i; - is_word = true; - if c.is_ascii_lowercase() { - current_part.push(c); - } else { - return Err("interpolations must only contain words".to_string()); - } - } - } - } - '}' => { - if is_word { - parts.push(( - StringPart::Word(current_part.clone()), - SimpleSpan::new(start, start + i), - )); - current_part = String::new(); - start = i; - is_word = false; - } else { - match chars.next() { - None => return Err("unclosed brace".to_string()), - Some((_, c)) => current_part.push(c), - } - } - } - _ => { - if is_word { - if is_word_char(char) { - current_part.push(char) - } else { - return Err("interpolations must only contain words".to_string()); - } - } else { - current_part.push(char) - } - } - } - } - - if current_part == s { - parts.push(( - StringPart::Inline(current_part), - SimpleSpan::new(start, span.end), - )) - } else if !current_part.is_empty() { - let part_len = current_part.len(); - parts.push(( - StringPart::Data(current_part), - SimpleSpan::new(start, part_len), - )) - } - - Ok(parts) -} - -pub fn compile_string_pattern(parts: Vec>) -> StringMatcher { - StringMatcher(Box::new(move |scrutinee| { - let mut last_match = 0; - let mut parts_iter = parts.iter(); - let mut matches = vec![]; - while let Some((part, _)) = parts_iter.next() { - match part { - StringPart::Data(string) => match scrutinee.find(string.as_str()) { - Some(i) => { - // if i = 0, we're at the beginning - if i == 0 && last_match == 0 { - last_match = i + string.len(); - continue; - } - // in theory, we only hit this branch if the first part is Data - unreachable!("internal Ludus error: bad string pattern") - } - None => return None, - }, - StringPart::Word(word) => { - let to_test = scrutinee.get(last_match..scrutinee.len()).unwrap(); - match parts_iter.next() { - None => matches.push((word.clone(), to_test.to_string())), - Some(part) => { - let (StringPart::Data(part), _) = part else { - unreachable!("internal Ludus error: bad string pattern") - }; - match to_test.find(part) { - None => return None, - Some(i) => { - matches.push(( - word.clone(), - to_test.get(last_match..i).unwrap().to_string(), - )); - last_match = i + part.len(); - continue; - } - } - } - } - } - _ => unreachable!("internal Ludus error"), - } - } - Some(matches) - })) -} - -pub fn parser( -) -> impl Parser<'static, I, Spanned, extra::Err>> + Clone -where - I: ValueInput<'static, Token = Token, Span = Span>, -{ - use Ast::*; - - let mut expr = Recursive::declare(); - - let mut pattern = Recursive::declare(); - - let mut simple = Recursive::declare(); - - let mut nonbinding = Recursive::declare(); - - let separators = recursive(|separators| { - just(Token::Punctuation(",")) - .or(just(Token::Punctuation("\n"))) - .then(separators.clone().repeated()) - }); - - let terminators = recursive(|terminators| { - just(Token::Punctuation(";")) - .or(just(Token::Punctuation("\n"))) - .then(terminators.clone().repeated()) - }); - - let placeholder_pattern = - select! {Token::Punctuation("_") => PlaceholderPattern}.map_with(|p, e| (p, e.span())); - - let word_pattern = select! { Token::Word(w) => WordPattern(w) }.map_with(|w, e| (w, e.span())); - - let atom_pattern = select! { - Token::Nil => NilPattern, - Token::Boolean(b) => BooleanPattern(b), - Token::Number(n) => NumberPattern(n), - Token::Keyword(k) => KeywordPattern(k), - } - .map_with(|a, e| (a, e.span())); - - let string_pattern = select! {Token::String(s) => s}.try_map_with(|s, e| { - let parsed = parse_string(s, e.span()); - match parsed { - Ok(parts) => match parts[0] { - (StringPart::Inline(_), _) => Ok((StringPattern(s), e.span())), - _ => Ok(( - InterpolatedPattern(parts.clone(), compile_string_pattern(parts)), - e.span(), - )), - }, - Err(msg) => Err(Rich::custom(e.span(), msg)), - } - }); - - let bare_splat = just(Token::Punctuation("...")).map_with(|_, e| { - ( - Splattern(Box::new((PlaceholderPattern, e.span()))), - e.span(), - ) - }); - - let splattable = word_pattern.or(placeholder_pattern); - - let patt_splat = just(Token::Punctuation("...")) - .ignore_then(splattable) - .map_with(|x, e| (Splattern(Box::new(x)), e.span())); - - let splattern = patt_splat.or(bare_splat); - - let tuple_pattern = pattern - .clone() - .or(splattern.clone()) - .separated_by(separators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")"))) - .map_with(|tuple, e| (TuplePattern(tuple), e.span())) - .labelled("tuple pattern"); - - let list_pattern = pattern - .clone() - .or(splattern.clone()) - .separated_by(separators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by(just(Token::Punctuation("[")), just(Token::Punctuation("]"))) - .map_with(|list, e| (ListPattern(list), e.span())); - - let pair_pattern = select! {Token::Keyword(k) => k} - .then(pattern.clone()) - .map_with(|(key, patt), e| (PairPattern(key, Box::new(patt)), e.span())); - - let shorthand_pattern = select! {Token::Word(w) => w}.map_with(|w, e| { - ( - PairPattern(w, Box::new((WordPattern(w), e.span()))), - e.span(), - ) - }); - - let dict_pattern = pair_pattern - .or(shorthand_pattern) - .or(splattern.clone()) - .separated_by(separators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by( - just(Token::Punctuation("#{")), - just(Token::Punctuation("}")), - ) - .map_with(|dict, e| (DictPattern(dict), e.span())); - - let keyword = select! {Token::Keyword(k) => Keyword(k),}.map_with(|k, e| (k, e.span())); - - let as_pattern = select! {Token::Word(w) => w} - .then_ignore(just(Token::Reserved("as"))) - .then(select! {Token::Keyword(k) => k}) - .map_with(|(w, t), e| (AsPattern(w, t), e.span())); - - pattern.define( - atom_pattern - .or(string_pattern) - .or(as_pattern) - .or(word_pattern) - .or(placeholder_pattern) - .or(tuple_pattern.clone()) - .or(list_pattern) - .or(dict_pattern) - .labelled("pattern"), - ); - - let placeholder = - select! {Token::Punctuation("_") => Placeholder}.map_with(|p, e| (p, e.span())); - - let word = select! { Token::Word(w) => Word(w) } - .map_with(|w, e| (w, e.span())) - .labelled("word"); - - let value = select! { - Token::Nil => Nil, - Token::Boolean(b) => Boolean(b), - Token::Number(n) => Number(n), - } - .map_with(|v, e| (v, e.span())); - - let string = select! {Token::String(s) => s}.try_map_with(|s, e| { - let parsed = parse_string(s, e.span()); - match parsed { - Ok(parts) => match parts[0] { - (StringPart::Inline(_), _) => Ok((String(s), e.span())), - _ => Ok((Interpolated(parts), e.span())), - }, - Err(msg) => Err(Rich::custom(e.span(), msg)), - } - }); - - let tuple = simple - .clone() - .separated_by(separators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")"))) - .map_with(|tuple, e| (Tuple(tuple), e.span())); - - let args = simple - .clone() - .or(placeholder) - .separated_by(separators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")"))) - .map_with(|args, e| (Arguments(args), e.span())); - - let or = just(Token::Reserved("or")).map_with(|_, e| (Or, e.span())); - - let and = just(Token::Reserved("and")).map_with(|_, e| (And, e.span())); - - let synth_root = or.or(and).or(word).or(keyword); - - let synth_term = keyword.or(args); - - let synthetic = synth_root - .then(synth_term.clone()) - .then(synth_term.clone().repeated().collect()) - .map_with(|((root, first), rest), e| { - (Synthetic(Box::new(root), Box::new(first), rest), e.span()) - }); - - let splat = just(Token::Punctuation("...")) - .ignore_then(word) - .map_with(|(w, _), e| { - ( - Splat(if let Word(w) = w { w } else { unreachable!() }), - e.span(), - ) - }); - - let list = simple - .clone() - .or(splat.clone()) - .separated_by(separators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by(just(Token::Punctuation("[")), just(Token::Punctuation("]"))) - .map_with(|list, e| (List(list), e.span())); - - let pair = select! {Token::Keyword(k) => k} - .then(simple.clone()) - .map_with(|(key, value), e| (Pair(key, Box::new(value)), e.span())); - - let shorthand = select! {Token::Word(w) => w} - .map_with(|w, e| (Pair(w, Box::new((Word(w), e.span()))), e.span())); - - let dict = pair - .or(shorthand) - .or(splat.clone()) - .separated_by(separators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by( - just(Token::Punctuation("#{")), - just(Token::Punctuation("}")), - ) - .map_with(|dict, e| (Dict(dict), e.span())); - - let recur = just(Token::Reserved("recur")) - .ignore_then(tuple.clone()) - .map_with(|args, e| { - let (Tuple(args), _) = args else { - unreachable!() - }; - (Recur(args), e.span()) - }); - - let block = expr - .clone() - .separated_by(terminators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))) - .map_with(|block, e| (Block(block), e.span())) - .recover_with(via_parser(nested_delimiters( - Token::Punctuation("{"), - Token::Punctuation("}"), - [ - (Token::Punctuation("("), Token::Punctuation(")")), - (Token::Punctuation("["), Token::Punctuation("]")), - ], - |span| (Error, span), - ))); - - let if_ = just(Token::Reserved("if")) - .ignore_then(simple.clone()) - .then_ignore(terminators.clone().or_not()) - .then_ignore(just(Token::Reserved("then"))) - .then(expr.clone()) - .then_ignore(terminators.clone().or_not()) - .then_ignore(just(Token::Reserved("else"))) - .then(expr.clone()) - .map_with(|((condition, then_branch), else_branch), e| { - ( - If( - Box::new(condition), - Box::new(then_branch), - Box::new(else_branch), - ), - e.span(), - ) - }); - - let when_clause = simple - .clone() - .then_ignore(just(Token::Punctuation("->"))) - .then(expr.clone()) - .map_with(|(cond, body), e| (WhenClause(Box::new(cond), Box::new(body)), e.span())); - - let when = just(Token::Reserved("when")) - .ignore_then( - when_clause - .separated_by(terminators.clone()) - .allow_trailing() - .allow_leading() - .collect() - .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))), - ) - .map_with(|clauses, e| (When(clauses), e.span())); - - let guarded_clause = pattern - .clone() - .then_ignore(just(Token::Reserved("if"))) - .then(simple.clone()) - .then_ignore(just(Token::Punctuation("->"))) - .then(expr.clone()) - .map_with(|((patt, guard), body), e| { - ( - MatchClause(Box::new(patt), Box::new(Some(guard)), Box::new(body)), - e.span(), - ) - }); - - let match_clause = pattern - .clone() - .then_ignore(just(Token::Punctuation("->"))) - .then(expr.clone()) - .map_with(|(patt, body), e| { - ( - MatchClause(Box::new(patt), Box::new(None), Box::new(body)), - e.span(), - ) - }); - - let r#match = just(Token::Reserved("match")) - .ignore_then(simple.clone()) - .then_ignore(just(Token::Reserved("with"))) - .then( - match_clause - .clone() - .or(guarded_clause) - .separated_by(terminators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))), - ) - .map_with(|(expr, clauses), e| (Match(Box::new(expr), clauses), e.span())); - - let conditional = when.or(if_).or(r#match); - - let panic = just(Token::Reserved("panic!")) - .ignore_then(nonbinding.clone()) - .map_with(|expr, e| (Panic(Box::new(expr)), e.span())); - - let do_ = just(Token::Reserved("do")) - .ignore_then( - nonbinding - .clone() - .separated_by( - just(Token::Punctuation(">")).then(just(Token::Punctuation("\n")).repeated()), - ) - .collect(), - ) - .map_with(|exprs, e| (Do(exprs), e.span())); - - let repeat = just(Token::Reserved("repeat")) - .ignore_then(simple.clone()) - .then(block.clone()) - .map_with(|(count, body), e| (Repeat(Box::new(count), Box::new(body)), e.span())); - - let fn_guarded = tuple_pattern - .clone() - .then_ignore(just(Token::Reserved("if"))) - .then(simple.clone()) - .then_ignore(just(Token::Punctuation("->"))) - .then(nonbinding.clone()) - .map_with(|((patt, guard), body), e| { - ( - MatchClause(Box::new(patt), Box::new(Some(guard)), Box::new(body)), - e.span(), - ) - }) - .labelled("function clause"); - - let fn_unguarded = tuple_pattern - .clone() - .then_ignore(just(Token::Punctuation("->"))) - .then(nonbinding.clone()) - .map_with(|(patt, body), e| { - ( - MatchClause(Box::new(patt), Box::new(None), Box::new(body)), - e.span(), - ) - }) - .labelled("function clause"); - - let fn_clause = fn_guarded.clone().or(fn_unguarded.clone()); - - let lambda = just(Token::Reserved("fn")) - .ignore_then(fn_unguarded.clone()) - .map_with(|clause, e| { - ( - Fn("", Box::new((Ast::FnBody(vec![clause]), e.span())), None), - e.span(), - ) - }); - - let fn_clauses = fn_clause - .clone() - .separated_by(terminators.clone()) - .allow_leading() - .allow_trailing() - .collect(); - - let loop_multiclause = fn_clauses - .clone() - .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))); - - let fn_single_clause = fn_clause.clone().map_with(|c, _| vec![c]); - - let r#loop = just(Token::Reserved("loop")) - .ignore_then(tuple.clone()) - .then_ignore(just(Token::Reserved("with"))) - .then(loop_multiclause.clone().or(fn_single_clause.clone())) - .map_with(|(init, body), e| (Loop(Box::new(init), body), e.span())); - - simple.define( - synthetic - .or(recur) - .or(word) - .or(keyword) - .or(value) - .or(tuple.clone()) - .or(list) - .or(dict) - .or(string) - .or(lambda.clone()) - .labelled("simple expression"), - ); - - nonbinding.define( - simple - .clone() - .or(conditional) - .or(block) - .or(panic) - .or(do_) - .or(repeat) - .or(r#loop) - .labelled("nonbinding expression"), - ); - - let let_ = just(Token::Reserved("let")) - .ignore_then(pattern.clone()) - .then_ignore(just(Token::Punctuation("="))) - .then(nonbinding.clone()) - .map_with(|(pattern, expression), e| { - (Let(Box::new(pattern), Box::new(expression)), e.span()) - }); - - let box_ = just(Token::Reserved("box")) - .ignore_then(word) - .then_ignore(just(Token::Punctuation("="))) - .then(nonbinding.clone()) - .map_with(|(word, expr), e| { - let name = if let Word(w) = word.0 { - w - } else { - unreachable!() - }; - (LBox(name, Box::new(expr)), e.span()) - }); - - let fn_decl = just(Token::Reserved("fn")) - .ignore_then(word) - .map_with(|(word, _), e| { - let name = if let Word(w) = word { - w - } else { - unreachable!() - }; - (FnDeclaration(name), e.span()) - }); - - let fn_named = just(Token::Reserved("fn")) - .ignore_then(word) - .then(fn_unguarded.clone()) - .map_with(|(word, clause), e| { - let name = if let Word(word) = word.0 { - word - } else { - unreachable!() - }; - ( - Fn(name, Box::new((Ast::FnBody(vec![clause]), e.span())), None), - e.span(), - ) - }); - - let docstr = select! {Token::String(s) => s}; - - let fn_multiclause = separators - .clone() - .or_not() - .ignore_then(docstr.or_not()) - .then(fn_clauses.clone()) - .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))) - .map_with(|(docstr, clauses), e| (docstr, clauses, e.span())); - - let fn_compound = just(Token::Reserved("fn")) - .ignore_then(word) - .then(fn_multiclause) - .map_with(|(word, (docstr, clauses, _)), e| { - let name = if let Word(word) = word.0 { - word - } else { - unreachable!() - }; - ( - Fn(name, Box::new((Ast::FnBody(clauses), e.span())), docstr), - e.span(), - ) - }); - - let fn_ = fn_named.or(fn_compound).or(fn_decl); - - let binding = let_.or(box_).or(fn_); - - expr.define(binding.or(nonbinding)); - - let script = expr - .separated_by(terminators.clone()) - .allow_trailing() - .allow_leading() - .collect() - .map_with(|exprs, e| (Block(exprs), e.span())); - - script -} diff --git a/src/spans.rs b/src/spans.rs deleted file mode 100644 index 04bd4c3..0000000 --- a/src/spans.rs +++ /dev/null @@ -1,4 +0,0 @@ -use chumsky::prelude::*; - -pub type Span = SimpleSpan; -pub type Spanned = (T, Span); diff --git a/src/validator.rs b/src/validator.rs deleted file mode 100644 index c8467e9..0000000 --- a/src/validator.rs +++ /dev/null @@ -1,592 +0,0 @@ -// TODO: -// * [ ] ensure `or` and `and` never get passed by reference -// * [ ] ensure no placeholder in `or` and `and` args -// * [ ] ensure loops have fixed arity (no splats) -// * [ ] ensure fn pattern splats are always highest (and same) arity - -use crate::parser::*; -use crate::spans::{Span, Spanned}; -use crate::value::Value; -use std::collections::{HashMap, HashSet}; - -#[derive(Clone, Debug, PartialEq)] -pub struct VErr<'a> { - pub msg: String, - pub span: &'a Span, - pub input: &'static str, - pub src: &'static str, -} - -impl<'a> VErr<'a> { - pub fn new(msg: String, span: &'a Span, input: &'static str, src: &'static str) -> VErr<'a> { - VErr { - msg, - span, - input, - src, - } - } -} - -#[derive(Clone, Debug, PartialEq)] -struct VStatus { - tail_position: bool, - in_loop: bool, - loop_arity: u8, - last_term: bool, - has_placeholder: bool, - used_bindings: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Arity { - Fixed(u8), - Splat(u8), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum FnInfo { - Declared, - Defined(String, HashSet, HashSet), - Unknown, -} - -fn match_arities(arities: &HashSet, num_args: u8) -> bool { - arities.iter().any(|arity| match arity { - Arity::Fixed(n) => *n == num_args, - Arity::Splat(n) => *n <= num_args, - }) -} - -#[derive(Debug, PartialEq)] -pub struct Validator<'a> { - pub locals: Vec<(String, &'a Span, FnInfo)>, - pub prelude: imbl::HashMap<&'static str, Value>, - pub input: &'static str, - pub src: &'static str, - pub ast: &'a Ast, - pub span: &'a Span, - pub errors: Vec>, - pub fn_info: HashMap<*const Ast, FnInfo>, - status: VStatus, -} - -impl<'a> Validator<'a> { - pub fn new( - ast: &'a Ast, - span: &'a Span, - input: &'static str, - src: &'static str, - prelude: imbl::HashMap<&'static str, Value>, - ) -> Validator<'a> { - Validator { - input, - src, - locals: vec![], - prelude, - ast, - span, - fn_info: std::collections::HashMap::new(), - errors: vec![], - status: VStatus { - tail_position: false, - in_loop: false, - loop_arity: 0, - last_term: false, - has_placeholder: false, - used_bindings: vec![], - }, - } - } - - fn bind(&mut self, name: String) { - self.locals.push((name, self.span, FnInfo::Unknown)); - } - - fn declare_fn(&mut self, name: String) { - self.locals.push((name, self.span, FnInfo::Declared)); - } - - fn define_fn(&mut self, name: String, info: FnInfo) { - let i = self.locals.iter().position(|(n, ..)| *n == name).unwrap(); - let new_binding = (name, self.locals[i].1, info); - self.locals[i] = new_binding; - } - - fn resolved(&self, name: &str) -> bool { - self.locals.iter().any(|(bound, ..)| name == bound.as_str()) - || self.prelude.iter().any(|(bound, _)| name == *bound) - } - - fn bound(&self, name: &str) -> Option<&(String, &Span, FnInfo)> { - match self.locals.iter().rev().find(|(bound, ..)| name == bound) { - Some(binding) => Some(binding), - None => None, - } - } - - fn err(&mut self, msg: String) { - self.errors - .push(VErr::new(msg, self.span, self.input, self.src)) - } - - fn use_name(&mut self, name: String) { - self.status.used_bindings.push(name); - } - - fn arity(&mut self) -> Arity { - let Ast::MatchClause(pattern, ..) = self.ast else { - unreachable!("internal Ludus error") - }; - let (Ast::TuplePattern(members), _) = pattern.as_ref() else { - unreachable!("internal Ludus error"); - }; - let last_member = members.last(); - match last_member { - None => Arity::Fixed(0), - Some((Ast::Splattern(..), _)) => Arity::Splat(members.len() as u8), - Some(_) => Arity::Fixed(members.len() as u8), - } - } - - fn visit(&mut self, node: &'a Spanned) { - let (expr, span) = node; - self.ast = expr; - self.span = span; - self.validate(); - } - - pub fn validate(&mut self) { - use Ast::*; - let root = self.ast; - match root { - Error => unreachable!(), - Word(name) | Splat(name) => { - if !self.resolved(name) { - self.err(format!("unbound name `{name}`")) - } else { - self.use_name(name.to_string()) - } - } - Interpolated(parts) => { - for part in parts { - if let (StringPart::Word(name), span) = part { - self.span = span; - if !self.resolved(name.as_str()) { - self.err(format!("unbound name `{name}`")); - } else { - self.use_name(name.to_string()); - } - } - } - } - // validate each line - // ensure it's not empty - // pass through tail position validation - // check if there are any declared but undefined functions - // pop all the bindings off the local stack - Block(block) => { - if block.is_empty() { - self.err("blocks must have at least one expression".to_string()); - return; - } - let to = self.locals.len(); - let tailpos = self.status.tail_position; - for line in block.iter().take(block.len() - 1) { - self.status.tail_position = false; - self.visit(line); - } - - self.status.tail_position = tailpos; - self.visit(block.last().unwrap()); - - let block_bindings = self.locals.split_off(to); - - for binding in block_bindings { - let (name, _, fn_info) = binding; - if matches!(fn_info, FnInfo::Declared) { - self.err(format!("fn `{name}` is declared but not defined")) - } - } - } - // if in tail position, pass through tail position validation - // no unbound names - If(cond, then, r#else) => { - let tailpos = self.status.tail_position; - self.status.tail_position = false; - - self.visit(cond.as_ref()); - - // pass through tailpos only to then/else - self.status.tail_position = tailpos; - self.visit(then.as_ref()); - self.visit(r#else.as_ref()); - } - Tuple(members) => { - if members.is_empty() { - return; - } - let tailpos = self.status.tail_position; - self.status.tail_position = false; - for member in members { - self.visit(member); - } - self.status.tail_position = tailpos; - } - // no more than one placeholder - Arguments(args) => { - if args.is_empty() { - return; - } - let tailpos = self.status.tail_position; - self.status.tail_position = false; - for arg in args { - self.visit(arg); - } - self.status.has_placeholder = false; - self.status.tail_position = tailpos; - } - Placeholder => { - if self.status.has_placeholder { - self.err( - "you may only use one placeholder when partially applying functions" - .to_string(), - ); - } - self.status.has_placeholder = true; - } - List(list) => { - if list.is_empty() { - return; - } - let tailpos = self.status.tail_position; - self.status.tail_position = false; - for member in list { - self.visit(member); - } - - self.status.tail_position = tailpos; - } - Pair(_, value) => self.visit(value.as_ref()), - Dict(dict) => { - if dict.is_empty() { - return; - } - let tailpos = self.status.tail_position; - self.status.tail_position = false; - for pair in dict { - self.visit(pair) - } - self.status.tail_position = tailpos; - } - - // TODO! - // check arity against fn info if first term is word and second term is args - Synthetic(first, second, rest) => { - match (&first.0, &second.0) { - (Ast::And, Ast::Arguments(_)) | (Ast::Or, Ast::Arguments(_)) => { - self.visit(second.as_ref()) - } - (Ast::Word(_), Ast::Keyword(_)) => self.visit(first.as_ref()), - (Ast::Keyword(_), Ast::Arguments(args)) => { - if args.len() != 1 { - self.err("called keywords may only take one argument".to_string()) - } - self.visit(second.as_ref()); - } - (Ast::Word(name), Ast::Arguments(args)) => { - self.visit(first.as_ref()); - self.visit(second.as_ref()); - - //TODO: check arities of prelude fns, too - let fn_binding = self.bound(name); - if let Some((_, _, FnInfo::Defined(_, arities, _))) = fn_binding { - let num_args = args.len(); - if !match_arities(arities, num_args as u8) { - self.err(format!("arity mismatch: no clause in function `{name}` with {num_args} argument(s)")) - } - } - } - _ => unreachable!( - "malformed synthetic root with\nfirst: {}\nsecond: {}", - first.0, second.0 - ), - } - for term in rest { - self.visit(term); - } - } - WhenClause(cond, body) => { - let tailpos = self.status.tail_position; - self.status.tail_position = false; - self.visit(cond.as_ref()); - //pass through tail position for when bodies - self.status.tail_position = tailpos; - self.visit(body.as_ref()); - } - When(clauses) => { - for clause in clauses { - self.visit(clause); - } - } - - // binding forms - // TODO: set up errors to include original binding - LBox(name, boxed) => { - if self.bound(name).is_some() { - self.err(format!("box name `{name}` is already bound")); - } else { - self.bind(name.to_string()); - } - self.visit(boxed.as_ref()); - } - Let(lhs, rhs) => { - self.visit(rhs.as_ref()); - self.visit(lhs.as_ref()); - } - MatchClause(pattern, guard, body) => { - let to = self.locals.len(); - - self.visit(pattern.as_ref()); - - if let Some(guard) = guard.as_ref() { - self.visit(guard); - } - - self.visit(body.as_ref()); - - self.locals.truncate(to); - } - Match(scrutinee, clauses) => { - self.visit(scrutinee.as_ref()); - - for clause in clauses { - self.visit(clause); - } - } - FnDeclaration(name) => { - let tailpos = self.status.tail_position; - self.status.tail_position = false; - if self.bound(name).is_some() { - self.err(format!("fn name `{name}` is already bound")); - return; - } - self.declare_fn(name.to_string()); - self.status.tail_position = tailpos; - } - FnBody(..) => unreachable!(), - Fn(name, body, ..) => { - let mut is_declared = false; - match self.bound(name) { - Some((_, _, FnInfo::Declared)) => is_declared = true, - None => (), - _ => { - self.err(format!("name `{name}` is already bound")); - } - } - - // TODO: devise a placeholder binding for recursive functions - if !is_declared { - self.declare_fn(name.to_string()); - } - - let from = self.status.used_bindings.len(); - let mut arities = HashSet::new(); - - let (Ast::FnBody(clauses), _) = body.as_ref() else { - unreachable!() - }; - - for clause in clauses { - // we have to do this explicitly here because of arity checking - let (expr, span) = clause; - self.ast = expr; - self.span = span; - // add clause arity to arities - arities.insert(self.arity()); - self.validate(); - } - - // collect info about what the function closes over - let mut closed_over = HashSet::new(); - for binding in self.status.used_bindings.iter().skip(from) { - if self.bound(binding.as_str()).is_some() { - // println!("{name} closing over {binding}"); - closed_over.insert(binding.clone()); - } - } - - let info = FnInfo::Defined(name.to_string(), arities, closed_over); - - let root_ptr: *const Ast = root; - - self.fn_info.insert(root_ptr, info.clone()); - - self.define_fn(name.to_string(), info); - } - - Panic(msg) => { - let tailpos = self.status.tail_position; - self.status.tail_position = false; - self.visit(msg.as_ref()); - self.status.tail_position = tailpos; - } - // TODO: fix the tail call here? - Do(terms) => { - if terms.len() < 2 { - return self.err("do expressions must have at least two terms".to_string()); - } - for term in terms.iter().take(terms.len() - 1) { - self.visit(term); - } - let last = terms.last().unwrap(); - self.visit(last); - if matches!(last.0, Ast::Recur(_)) { - self.err("`recur` may not be used in `do` forms".to_string()); - } - } - Repeat(times, body) => { - self.status.tail_position = false; - self.visit(times.as_ref()); - self.visit(body.as_ref()); - } - Loop(with, body) => { - self.visit(with.as_ref()); - - let Ast::Tuple(input) = &with.0 else { - unreachable!() - }; - - // dbg!(&input); - - let tailpos = self.status.tail_position; - self.status.tail_position = true; - let in_loop = self.status.in_loop; - let outer_arity = self.status.loop_arity; - self.status.in_loop = true; - let loop_arity = input.len() as u8; - self.status.loop_arity = loop_arity; - - for clause in body { - let (expr, span) = clause; - self.ast = expr; - self.span = span; - let arity = self.arity(); - // dbg!(&arity); - match arity { - Arity::Fixed(clause_arity) => { - if clause_arity != loop_arity { - self.err(format!("mismatched arity: expected {loop_arity} arguments in `loop` clause; got {clause_arity}")) - } - } - Arity::Splat(clause_arity) => { - if clause_arity > loop_arity { - self.err(format!("mismathced arity: expected {loop_arity} arguments in `loop` clause; this clause takes {clause_arity} or more")) - } - } - }; - self.validate(); - } - - self.status.tail_position = tailpos; - self.status.in_loop = in_loop; - self.status.loop_arity = outer_arity; - } - Recur(args) => { - if !self.status.in_loop { - self.err("you may only use `recur` in a `loop` form".to_string()); - return; - } - if !self.status.tail_position { - self.err("you may only use `recur` in tail position".to_string()); - } - - let num_args = args.len() as u8; - let loop_arity = self.status.loop_arity; - if num_args != loop_arity { - self.err(format!("loop arity mismatch: loop has arity of {loop_arity}; `recur` called with {num_args} arguments")) - } - - self.status.tail_position = false; - for arg in args { - self.visit(arg); - } - } - WordPattern(name) => match self.bound(name) { - Some((name, _span, _)) => { - self.err(format!("name `{name}` is already bound")); - } - None => { - self.bind(name.to_string()); - } - }, - InterpolatedPattern(parts, _) => { - for (part, span) in parts { - if let StringPart::Word(name) = part { - self.span = span; - match self.bound(name) { - Some(_) => self.err(format!("name `{name}` is already bound")), - None => self.bind(name.to_string()), - } - } - } - } - AsPattern(name, r#type) => { - match self.bound(name) { - Some((name, _span, _)) => { - self.err(format!("name `{name}` is already bound")); - } - None => { - self.bind(name.to_string()); - } - } - let as_type = *r#type; - match as_type { - "nil" | "bool" | "number" | "keyword" | "string" | "tuple" | "dict" - | "list" | "fn" | "box" => (), - _ => self.err(format!("unknown type `:{as_type}`")), - } - } - Splattern(splatted) => { - if !self.status.last_term { - self.err("splats in patterns must come last".to_string()); - } - match splatted.as_ref() { - (PlaceholderPattern, _) => (), - (WordPattern(name), span) => match self.bound(name) { - Some(_) => { - self.span = span; - self.err(format!("name `{name}` is already bound")) - } - None => self.bind(name.to_string()), - }, - _ => { - println!("internal Ludus error: unexpected splat pattern"); - // dbg!(splatted); - unreachable!() - } - } - } - TuplePattern(terms) | ListPattern(terms) | DictPattern(terms) => { - if terms.is_empty() { - return; - } - for term in terms.iter().take(terms.len() - 1) { - self.visit(term); - } - - self.status.last_term = true; - let last = terms.last().unwrap(); - self.visit(last); - self.status.last_term = false; - } - PairPattern(_, patt) => self.visit(patt.as_ref()), - // terminals can never be invalid - Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or => (), - // terminal patterns can never be invalid - NilPattern | BooleanPattern(..) | NumberPattern(..) | StringPattern(..) - | KeywordPattern(..) | PlaceholderPattern => (), - }; - self.ast = root; - } -} diff --git a/src/value.rs b/src/value.rs deleted file mode 100644 index c5fdaa5..0000000 --- a/src/value.rs +++ /dev/null @@ -1,381 +0,0 @@ -use crate::base::BaseFn; -use crate::chunk::Chunk; -// use crate::parser::Ast; -// use crate::spans::Spanned; -use imbl::{HashMap, Vector}; -use std::cell::RefCell; -use std::rc::Rc; - -#[derive(Clone, Debug)] -pub enum LFn { - Declared { - name: &'static str, - }, - Defined { - name: &'static str, - doc: Option<&'static str>, - arities: Vec, - chunks: Vec, - splat: u8, - closed: RefCell>, - }, -} - -impl LFn { - pub fn close(&self, value: Value) { - match self { - LFn::Declared { .. } => unreachable!(), - LFn::Defined { closed, .. } => { - let shown = value.show(); - closed.borrow_mut().push(value); - let pos = closed.borrow().len(); - if crate::DEBUG_SCRIPT_RUN { - println!("closing over in {} at {pos}: {shown}", self.name(),); - } - } - } - } - - pub fn doc(&self) -> Value { - match self { - LFn::Declared { name } => { - Value::String(Rc::new(format!("fn {name}: undefined function"))) - } - LFn::Defined { - name, - doc: Some(doc), - .. - } => Value::String(Rc::new(format!("fn {name}\n{doc}"))), - LFn::Defined { name, .. } => { - Value::String(Rc::new(format!("fn {name}: no documentation"))) - } - } - } - - pub fn accepts(&self, arity: u8) -> bool { - match self { - LFn::Defined { arities, splat, .. } => { - if arities.contains(&arity) { - return true; - } - if *splat == 0 { - return false; - } - let max_arity = arities.iter().fold(0, |a, b| a.max(*b)); - arity > max_arity - } - LFn::Declared { .. } => unreachable!(), - } - } - - pub fn splat_arity(&self) -> u8 { - match self { - LFn::Defined { splat, .. } => *splat, - LFn::Declared { .. } => unreachable!(), - } - } - - pub fn name(&self) -> &'static str { - match self { - LFn::Declared { name } | LFn::Defined { name, .. } => name, - } - } - - pub fn chunk(&self, arity: u8) -> &Chunk { - match self { - LFn::Declared { .. } => unreachable!(), - LFn::Defined { - arities, - splat, - chunks, - .. - } => { - let chunk_pos = arities.iter().position(|a| arity == *a); - match chunk_pos { - Some(pos) => &chunks[pos], - None => &chunks[*splat as usize], - } - } - } - } - - pub fn upvalue(&self, idx: u8) -> Value { - match self { - LFn::Declared { .. } => unreachable!(), - LFn::Defined { closed, .. } => closed.borrow()[idx as usize].clone(), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Partial { - pub args: Vec, - pub name: &'static str, - pub function: Value, -} - -#[derive(Clone, Debug)] -pub enum Value { - Nothing, - Nil, - True, - False, - Keyword(&'static str), - Interned(&'static str), - String(Rc), - Number(f64), - Tuple(Rc>), - List(Box>), - Dict(Box>), - Box(Rc>), - Fn(Rc), - BaseFn(BaseFn), - Partial(Rc), -} - -impl PartialEq for Value { - fn eq(&self, other: &Value) -> bool { - use Value::*; - match (self, other) { - (Nothing, Nothing) | (Nil, Nil) | (True, True) | (False, False) => true, - (Keyword(str1), Keyword(str2)) | (Interned(str1), Interned(str2)) => str1 == str2, - (String(x), String(y)) => x == y, - (String(x), Interned(y)) => x.as_ref() == y, - (Interned(x), String(y)) => x == y.as_ref(), - (Number(x), Number(y)) => x == y, - (Tuple(x), Tuple(y)) => x == y, - (List(x), List(y)) => x == y, - (Dict(x), Dict(y)) => x == y, - (Box(x), Box(y)) => std::ptr::eq(x.as_ref().as_ptr(), y.as_ref().as_ptr()), - (Fn(x), Fn(y)) => std::ptr::eq(x, y), - (BaseFn(x), BaseFn(y)) => std::ptr::eq(x, y), - (Partial(x), Partial(y)) => x == y, - _ => false, - } - } -} - -impl std::fmt::Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use Value::*; - match self { - Nothing => write!(f, "_"), - Nil => write!(f, "nil"), - True => write!(f, "true"), - False => write!(f, "false"), - Keyword(str) => write!(f, ":{str}"), - Interned(str) => write!(f, "\"{str}\""), - String(str) => write!(f, "\"{str}\""), - Number(n) => write!(f, "{n}"), - Tuple(members) => write!( - f, - "({})", - members - .iter() - .map(|x| x.to_string()) - .collect::>() - .join(", ") - ), - List(members) => write!( - f, - "[{}]", - members - .iter() - .map(|x| x.to_string()) - .collect::>() - .join(", ") - ), - Dict(members) => write!( - f, - "#{{{}}}", - members - .iter() - .map(|(k, v)| format!("{k} {v}")) - .collect::>() - .join(", ") - ), - Box(value) => write!(f, "box {{ {} }}", value.as_ref().borrow()), - Fn(lfn) => write!(f, "fn {}", lfn.name()), - BaseFn(inner) => { - let name = match inner { - crate::base::BaseFn::Nullary(name, _) - | crate::base::BaseFn::Unary(name, _) - | crate::base::BaseFn::Binary(name, _) - | crate::base::BaseFn::Ternary(name, _) => name, - }; - write!(f, "fn {name}/base") - } - Partial(partial) => write!(f, "fn {}/partial", partial.name), - } - } -} - -impl Value { - pub fn show(&self) -> String { - use Value::*; - let mut out = match &self { - Nil => "nil".to_string(), - True => "true".to_string(), - False => "false".to_string(), - Number(n) => format!("{n}"), - Interned(str) => format!("\"{str}\""), - String(str) => format!("\"{str}\""), - Keyword(str) => format!(":{str}"), - Tuple(t) => { - let members = t.iter().map(|e| e.show()).collect::>().join(", "); - format!("({members})") - } - List(l) => { - let members = l.iter().map(|e| e.show()).collect::>().join(", "); - format!("[{members}]") - } - Dict(d) => { - let members = d - .iter() - .map(|(k, v)| { - let key_show = Value::Keyword(k).show(); - let value_show = v.show(); - format!("{key_show} {value_show}") - }) - .collect::>() - .join(", "); - format!("#{{{members}}}") - } - Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show()), - Fn(lfn) => format!("fn {}", lfn.name()), - Partial(partial) => format!("fn {}/partial", partial.name), - BaseFn(_) => format!("{self}"), - Nothing => "_".to_string(), - }; - if out.len() > 20 { - out.truncate(20); - format!("{out}...") - } else { - out - } - } - - pub fn to_json(&self) -> Option { - use Value::*; - match self { - True | False | String(..) | Interned(..) | Number(..) => Some(self.show()), - Keyword(str) => Some(format!("\"{str}\"")), - List(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some(member) = members.next() { - joined = member.to_json()?; - } - for member in members { - let json = member.to_json()?; - joined = format!("{joined},{json}"); - } - Some(format!("[{joined}]")) - } - Tuple(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some(member) = members.next() { - joined = member.to_json()?; - } - for member in members { - let json = member.to_json()?; - joined = format!("{joined},{json}"); - } - Some(format!("[{joined}]")) - } - Dict(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some((key, value)) = members.next() { - let json = value.to_json()?; - joined = format!("\"{key}\":{json}") - } - for (key, value) in members { - let json = value.to_json()?; - joined = format!("{joined},\"{key}\": {json}"); - } - Some(format!("{{{joined}}}")) - } - not_serializable => { - println!("Cannot convert to json:"); - dbg!(not_serializable); - None - } - } - } - - pub fn stringify(&self) -> String { - use Value::*; - match &self { - Nil => "nil".to_string(), - True => "true".to_string(), - False => "false".to_string(), - Number(n) => format!("{n}"), - Interned(str) => str.to_string(), - Keyword(str) => format!(":{str}"), - Tuple(t) => { - let members = t - .iter() - .map(|e| e.stringify()) - .collect::>() - .join(", "); - format!("({members})") - } - List(l) => { - let members = l - .iter() - .map(|e| e.stringify()) - .collect::>() - .join(", "); - format!("[{members}]") - } - Dict(d) => { - let members = d - .iter() - .map(|(k, v)| { - let key_show = Value::Keyword(k).stringify(); - let value_show = v.stringify(); - format!("{key_show} {value_show}") - }) - .collect::>() - .join(", "); - format!("#{{{members}}}") - } - String(s) => s.as_ref().clone(), - Box(x) => x.as_ref().borrow().stringify(), - Fn(lfn) => format!("fn {}", lfn.name()), - Partial(partial) => format!("fn {}/partial", partial.name), - BaseFn(_) => format!("{self}"), - Nothing => unreachable!(), - } - } - - pub fn type_of(&self) -> &'static str { - use Value::*; - match self { - Nothing => unreachable!(), - Nil => "nil", - True => "bool", - False => "bool", - Keyword(..) => "keyword", - Interned(..) => "string", - String(..) => "string", - Number(..) => "number", - Tuple(..) => "tuple", - List(..) => "list", - Dict(..) => "dict", - Box(..) => "box", - Fn(..) => "fn", - BaseFn(..) => "fn", - Partial(..) => "fn", - } - } - - pub fn as_fn(&self) -> &LFn { - match self { - Value::Fn(inner) => inner.as_ref(), - _ => unreachable!(), - } - } -} diff --git a/src/vm.rs b/src/vm.rs deleted file mode 100644 index e2260fe..0000000 --- a/src/vm.rs +++ /dev/null @@ -1,1044 +0,0 @@ -use crate::base::BaseFn; -use crate::chunk::Chunk; -use crate::op::Op; -use crate::parser::Ast; -use crate::spans::Spanned; -use crate::value::{LFn, Value}; -use imbl::{HashMap, Vector}; -use num_traits::FromPrimitive; -use std::cell::RefCell; -use std::fmt; -use std::mem::swap; -use std::rc::Rc; - -#[derive(Debug, Clone, PartialEq)] -// pub struct Panic { -// pub input: &'static str, -// pub src: &'static str, -// pub msg: String, -// pub span: SimpleSpan, -// pub trace: Vec, -// pub extra: String, -// } -pub enum Panic { - Str(&'static str), - String(String), -} - -impl fmt::Display for Panic { - fn fmt(self: &Panic, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Panic::Str(msg) => write!(f, "{msg}"), - Panic::String(msg) => write!(f, "{msg}"), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Trace { - pub callee: Spanned, - pub caller: Spanned, - pub function: Value, - pub arguments: Value, - pub input: &'static str, - pub src: &'static str, -} - -pub struct CallFrame { - pub function: Value, - pub arity: u8, - pub stack_base: usize, - pub ip: usize, -} - -impl CallFrame { - pub fn chunk(&self) -> &Chunk { - let Value::Fn(ref function) = self.function else { - unreachable!() - }; - function.chunk(self.arity) - } -} - -impl fmt::Display for CallFrame { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Value::Fn(ref function) = self.function else { - unreachable!() - }; - write!( - f, - "CallFrame: {}/{} @ {}", - function.name(), - self.arity, - self.ip - ) - } -} - -fn combine_bytes(high: u8, low: u8) -> usize { - let out = ((high as u16) << 8) + low as u16; - out as usize -} - -pub struct Vm { - pub stack: Vec, - pub call_stack: Vec, - pub frame: CallFrame, - pub ip: usize, - pub return_register: [Value; 8], - pub matches: bool, - pub match_depth: u8, - pub result: Option>, - debug: bool, - last_code: usize, -} - -impl Vm { - pub fn new(chunk: Chunk, debug: bool) -> Vm { - let lfn = LFn::Defined { - name: "user script", - doc: None, - chunks: vec![chunk], - arities: vec![0], - splat: 0, - closed: RefCell::new(vec![]), - }; - let base_fn = Value::Fn(Rc::new(lfn)); - let base_frame = CallFrame { - function: base_fn.clone(), - stack_base: 0, - ip: 0, - arity: 0, - }; - Vm { - stack: vec![], - call_stack: Vec::with_capacity(64), - frame: base_frame, - ip: 0, - return_register: [const { Value::Nothing }; 8], - matches: false, - match_depth: 0, - result: None, - debug, - last_code: 0, - } - } - - pub fn chunk(&self) -> &Chunk { - self.frame.chunk() - } - - pub fn push(&mut self, value: Value) { - self.stack.push(value); - } - - pub fn pop(&mut self) -> Value { - self.stack.pop().unwrap() - } - - pub fn peek(&self) -> &Value { - self.stack.last().unwrap() - } - - pub fn print_stack(&self) { - let mut inner = vec![]; - for (i, value) in self.stack.iter().enumerate() { - if i == self.frame.stack_base { - inner.push(format!("->{}<-", value.show())) - } else { - inner.push(value.show()) - } - } - let inner = inner.join("|"); - let register = self - .return_register - .iter() - .map(|val| val.to_string()) - .collect::>() - .join(","); - println!("{:04}: [{inner}] ({register})", self.last_code); - } - - fn print_debug(&self) { - self.print_stack(); - let mut ip = self.last_code; - self.chunk().dissasemble_instr(&mut ip); - } - - pub fn run(&mut self) -> &Result { - while self.result.is_none() { - self.interpret(); - } - self.result.as_ref().unwrap() - } - - pub fn call_stack(&mut self) -> String { - let mut stack = format!(" calling {}", self.frame.function.show()); - for frame in self.call_stack.iter().rev() { - let mut name = frame.function.show(); - name = if name == "fn user script" { - "user script".to_string() - } else { - name - }; - stack = format!("{stack}\n from {name}"); - } - stack - } - - pub fn panic(&mut self, msg: &'static str) { - let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); - self.result = Some(Err(Panic::String(msg))); - } - - pub fn panic_with(&mut self, msg: String) { - let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); - self.result = Some(Err(Panic::String(msg))); - } - - fn get_value_at(&mut self, idx: u8) -> Value { - let idx = idx as usize; - let idx = idx + self.frame.stack_base; - self.stack[idx].clone() - } - - fn get_scrutinee(&mut self) -> Value { - let idx = self.stack.len() - self.match_depth as usize - 1; - self.stack[idx].clone() - } - - fn read(&mut self) -> u8 { - let code = self.chunk().bytecode[self.ip]; - self.ip += 1; - code - } - - fn read2(&mut self) -> usize { - let high = self.read(); - let low = self.read(); - combine_bytes(high, low) - } - - fn at_end(&mut self) -> bool { - self.ip >= self.chunk().bytecode.len() - } - - pub fn interpret(&mut self) { - loop { - if self.at_end() { - self.result = Some(Ok(self.stack.pop().unwrap())); - return; - } - let code = self.read(); - if self.debug { - self.last_code = self.ip - 1; - self.print_debug(); - } - let op = Op::from_u8(code).unwrap(); - use Op::*; - match op { - Noop => (), - Nil => self.push(Value::Nil), - Nothing => self.push(Value::Nothing), - True => self.push(Value::True), - False => self.push(Value::False), - Msg => { - let _ = self.read(); - } - Constant => { - let const_idx = self.read2(); - let value = self.chunk().constants[const_idx].clone(); - self.push(value); - } - Jump => { - let jump_len = self.read2(); - self.ip += jump_len; - } - JumpBack => { - let jump_len = self.read2(); - self.ip -= jump_len + 3; - } - JumpIfFalse => { - let jump_len = self.read2(); - let cond = self.pop(); - match cond { - Value::Nil | Value::False => self.ip += jump_len, - _ => (), - } - } - JumpIfTrue => { - let jump_len = self.read2(); - let cond = self.pop(); - match cond { - Value::Nil | Value::False => (), - _ => self.ip += jump_len, - } - } - JumpIfZero => { - let jump_len = self.read2(); - let cond = self.pop(); - match cond { - Value::Number(x) if x <= 0.0 => self.ip += jump_len, - Value::Number(..) => (), - _ => return self.panic("repeat requires a number"), - } - } - Pop => { - self.pop(); - } - PopN => { - let n = self.read() as usize; - self.stack.truncate(self.stack.len() - n); - } - PushBinding => { - let idx = self.read(); - let value = self.get_value_at(idx); - self.push(value); - } - PushGlobal => { - let key = self.pop(); - let Value::Keyword(name) = key else { - unreachable!("internal Ludus error: expected key for global resolution") - }; - let value = self.chunk().env.get(name).unwrap(); - self.push(value.clone()); - } - Store => { - self.return_register[0] = self.pop(); - } - StoreN => { - let n = self.read() as usize; - for i in (0..n).rev() { - self.return_register[i] = self.pop(); - } - } - Stash => { - self.return_register[0] = self.peek().clone(); - } - Load => { - let mut value = Value::Nothing; - swap(&mut self.return_register[0], &mut value); - self.push(value); - } - LoadN => { - let n = self.read() as usize; - for i in 0..n { - let mut value = Value::Nothing; - swap(&mut self.return_register[i], &mut value); - self.push(value); - } - } - ResetMatch => { - self.matches = false; - self.match_depth = 0; - } - UnconditionalMatch => { - self.matches = true; - } - MatchType => { - let as_type = self.pop(); - let Value::Keyword(as_type) = as_type else { - unreachable!() - }; - let value = self.get_scrutinee(); - let val_type = value.type_of(); - self.matches = val_type == as_type; - } - MatchNil => { - let value = self.get_scrutinee(); - self.matches = value == Value::Nil; - } - MatchTrue => { - let value = self.get_scrutinee(); - self.matches = value == Value::True; - } - MatchFalse => { - let value = self.get_scrutinee(); - self.matches = value == Value::False; - } - PanicIfNoMatch => { - if !self.matches { - return self.panic("no match"); - } - } - MatchConstant => { - let const_idx = self.read2(); - let scrutinee = self.get_scrutinee(); - // let idx = self.stack.len() - self.match_depth as usize - 1; - self.matches = scrutinee == self.chunk().constants[const_idx]; - } - MatchString => { - let pattern_idx = self.read(); - let scrutinee = self.get_scrutinee(); - self.matches = match scrutinee { - Value::String(str) => self.chunk().string_patterns[pattern_idx as usize] - .re - .is_match(str.as_str()), - Value::Interned(str) => self.chunk().string_patterns[pattern_idx as usize] - .re - .is_match(str), - _ => false, - }; - } - PushStringMatches => { - let pattern_idx = self.read(); - let pattern_len = self.chunk().string_patterns[pattern_idx as usize] - .words - .len(); - // let scrutinee_idx = self.stack.len() - self.match_depth as usize - 1; - let scrutinee = self.get_scrutinee(); - let scrutinee = match scrutinee { - Value::String(str) => str.as_ref().clone(), - Value::Interned(str) => str.to_string(), - _ => unreachable!(), - }; - let captures = self.chunk().string_patterns[pattern_idx as usize] - .re - .captures(scrutinee.as_str()) - .unwrap(); - for cap in 0..pattern_len { - self.push(Value::String(Rc::new(captures[cap + 1].to_string()))) - } - self.match_depth += pattern_len as u8; - } - MatchTuple => { - // let idx = self.stack.len() - self.match_depth as usize - 1; - let tuple_len = self.read() as usize; - let scrutinee = self.get_scrutinee(); - match scrutinee { - Value::Tuple(members) => self.matches = members.len() == tuple_len, - _ => self.matches = false, - }; - } - MatchSplattedTuple => { - let patt_len = self.read() as usize; - let scrutinee = self.get_scrutinee(); - match scrutinee { - Value::Tuple(members) => self.matches = members.len() >= patt_len, - _ => self.matches = false, - } - } - PushTuple => { - let tuple_len = self.read() as usize; - let tuple_members = self.stack.split_off(self.stack.len() - tuple_len); - let tuple = Value::Tuple(Rc::new(tuple_members)); - self.push(tuple); - } - LoadTuple => { - // let idx = self.stack.len() - self.match_depth as usize - 1; - // let tuple = self.stack[idx].clone(); - let tuple = self.get_scrutinee(); - match tuple { - Value::Tuple(members) => { - for member in members.iter() { - self.push(member.clone()); - } - } - _ => return self.panic("internal error: expected tuple"), - }; - } - LoadSplattedTuple => { - let load_len = self.read() as usize; - let tuple = self.get_scrutinee(); - let Value::Tuple(members) = tuple else { - return self.panic("internal error: expected tuple"); - }; - for i in 0..load_len - 1 { - self.push(members[i].clone()); - } - let mut splatted = Vector::new(); - for i in load_len - 1..members.len() { - splatted.push_back(members[i].clone()); - } - self.push(Value::List(Box::new(splatted))); - } - PushList => { - self.push(Value::List(Box::new(Vector::new()))); - } - AppendList => { - let value = self.pop(); - let list = self.pop(); - let Value::List(mut list) = list else { - return self.panic("only lists may be splatted into lists"); - }; - list.push_back(value); - self.push(Value::List(list)); - } - ConcatList => { - let splatted = self.pop(); - let target = self.pop(); - let Value::List(mut target) = target else { - unreachable!() - }; - let Value::List(splatted) = splatted else { - return self.panic("only lists may be splatted into lists"); - }; - target.append(*splatted); - self.push(Value::List(target)); - } - MatchList => { - // let idx = self.stack.len() - self.match_depth as usize - 1; - let list_len = self.read() as usize; - let scrutinee = self.get_scrutinee(); - match scrutinee { - Value::List(members) => self.matches = members.len() == list_len, - _ => self.matches = false, - }; - } - MatchSplattedList => { - // let idx = self.stack.len() - self.match_depth as usize - 1; - let patt_len = self.read() as usize; - let scrutinee = self.get_scrutinee(); - match scrutinee { - Value::List(members) => self.matches = members.len() >= patt_len, - _ => self.matches = false, - } - } - LoadList => { - // let idx = self.stack.len() - self.match_depth as usize - 1; - let list = self.get_scrutinee(); - match list { - Value::List(members) => { - for member in members.iter() { - self.push(member.clone()); - } - } - _ => return self.panic("internal error: expected list"), - }; - } - LoadSplattedList => { - let loaded_len = self.read() as usize; - let list = self.get_scrutinee(); - let Value::List(members) = list else { - return self.panic("internal error: expected list"); - }; - for i in 0..loaded_len - 1 { - self.push(members[i].clone()); - } - let splatted = Value::List(Box::new(members.skip(loaded_len - 1))); - self.push(splatted); - } - PushDict => { - self.push(Value::Dict(Box::new(HashMap::new()))); - } - AppendDict => { - let value = self.pop(); - let Value::Keyword(key) = self.pop() else { - unreachable!() - }; - let Value::Dict(mut dict) = self.pop() else { - unreachable!() - }; - dict.insert(key, value); - self.push(Value::Dict(dict)); - } - ConcatDict => { - let Value::Dict(splatted) = self.pop() else { - return self.panic("only dicts may be splatted into dicts"); - }; - let Value::Dict(target) = self.pop() else { - unreachable!() - }; - let union = splatted.union(*target); - self.push(Value::Dict(Box::new(union))); - } - LoadDictValue => { - let dict_idx = self.read(); - let dict = match self.get_value_at(dict_idx) { - Value::Dict(dict) => dict, - value => { - println!( - "internal Ludus error in function {}", - self.frame.function.as_fn().name() - ); - unreachable!("expected dict, got {value}") - } - }; - let Value::Keyword(key) = self.pop() else { - unreachable!("expected keyword, got something else") - }; - let value = dict.get(&key).unwrap_or(&Value::Nil); - self.push(value.clone()); - } - MatchDict => { - // let idx = self.stack.len() - self.match_depth as usize - 1; - let dict_len = self.read(); - let scrutinee = self.get_scrutinee(); - match scrutinee { - Value::Dict(members) => self.matches = members.len() == dict_len as usize, - _ => self.matches = false, - }; - } - MatchSplattedDict => { - // let idx = self.stack.len() - self.match_depth as usize - 1; - let patt_len = self.read() as usize; - let scrutinee = self.get_scrutinee(); - match scrutinee { - Value::Dict(members) => self.matches = members.len() >= patt_len, - _ => self.matches = false, - } - } - DropDictEntry => { - let Value::Keyword(key_to_drop) = self.pop() else { - unreachable!() - }; - let Value::Dict(mut dict) = self.pop() else { - unreachable!() - }; - dict.remove(key_to_drop); - self.push(Value::Dict(dict)); - } - PushBox => { - let val = self.pop(); - self.push(Value::Box(Rc::new(RefCell::new(val)))); - } - GetKey => { - let key = self.pop(); - let Value::Keyword(idx) = key else { - unreachable!() - }; - let dict = self.pop(); - let value = match dict { - Value::Dict(d) => d.as_ref().get(&idx).unwrap_or(&Value::Nil).clone(), - _ => Value::Nil, - }; - self.push(value); - } - JumpIfNoMatch => { - let jump_len = self.read2(); - // let jump_len = self.chunk().bytecode[self.ip + 1] as usize; - if !self.matches { - self.ip += jump_len - } - } - JumpIfMatch => { - let jump_len = self.read2(); - if self.matches { - self.ip += jump_len; - } - } - TypeOf => { - let val = self.pop(); - let type_of = Value::Keyword(val.type_of()); - self.push(type_of); - } - ToInt => { - let val = self.pop(); - if let Value::Number(x) = val { - self.push(Value::Number(x as usize as f64)); - } else { - return self.panic("repeat requires a number"); - } - } - Decrement => { - let val = self.pop(); - if let Value::Number(x) = val { - self.push(Value::Number(x - 1.0)); - } else { - return self.panic("you may only decrement a number"); - } - } - Duplicate => { - self.push(self.peek().clone()); - } - MatchDepth => { - self.match_depth = self.read(); - } - PanicNoWhen | PanicNoMatch => { - return self.panic("no match"); - } - Eq => { - let first = self.pop(); - let second = self.pop(); - if first == second { - self.push(Value::True) - } else { - self.push(Value::False) - } - } - Add => { - let first = self.pop(); - let second = self.pop(); - if let (Value::Number(x), Value::Number(y)) = (first, second) { - self.push(Value::Number(x + y)) - } else { - return self.panic("`add` requires two numbers"); - } - } - Sub => { - let first = self.pop(); - let second = self.pop(); - if let (Value::Number(x), Value::Number(y)) = (first, second) { - self.push(Value::Number(y - x)) - } else { - return self.panic("`sub` requires two numbers"); - } - } - Mult => { - let first = self.pop(); - let second = self.pop(); - if let (Value::Number(x), Value::Number(y)) = (first, second) { - self.push(Value::Number(x * y)) - } else { - return self.panic("`mult` requires two numbers"); - } - } - Div => { - let first = self.pop(); - let second = self.pop(); - if let (Value::Number(x), Value::Number(y)) = (first, second) { - if x == 0.0 { - return self.panic("division by 0"); - } - self.push(Value::Number(y / x)) - } else { - return self.panic("`div` requires two numbers"); - } - } - Unbox => { - let the_box = self.pop(); - let inner = if let Value::Box(b) = the_box { - b.borrow().clone() - } else { - return self.panic("`unbox` requires a box"); - }; - self.push(inner); - } - BoxStore => { - let new_value = self.pop(); - let the_box = self.pop(); - if let Value::Box(b) = the_box { - b.replace(new_value.clone()); - } else { - return self.panic("`store` requires a box"); - } - self.push(new_value); - } - Assert => { - let value = self.stack.last().unwrap(); - if let Value::Nil | Value::False = value { - return self.panic("asserted falsy value"); - } - } - Get => { - let key = self.pop(); - let dict = self.pop(); - let value = match (key, dict) { - (Value::Keyword(k), Value::Dict(d)) => { - d.as_ref().get(&k).unwrap_or(&Value::Nil).clone() - } - (Value::Keyword(_), _) => Value::Nil, - _ => return self.panic("keys must be keywords"), - }; - self.push(value); - } - At => { - let idx = self.pop(); - let ordered = self.pop(); - let value = match (ordered, idx) { - (Value::List(l), Value::Number(i)) => { - l.get(i as usize).unwrap_or(&Value::Nil).clone() - } - (Value::Tuple(t), Value::Number(i)) => { - t.get(i as usize).unwrap_or(&Value::Nil).clone() - } - (_, Value::Number(_)) => Value::Nil, - _ => return self.panic("indexes must be numbers"), - }; - self.push(value); - } - Not => { - let value = self.pop(); - let negated = match value { - Value::Nil | Value::False => Value::True, - _ => Value::False, - }; - self.push(negated); - } - Panic => { - let msg = self.pop().show(); - return self.panic_with(msg); - } - EmptyString => { - self.push(Value::String(Rc::new("".to_string()))); - } - //TODO: don't use the schlemiel's algo here - ConcatStrings => { - let second = self.pop(); - let first = self.pop(); - let combined = match (first, second) { - (Value::String(first), Value::String(second)) => { - let mut new = first.as_ref().clone(); - new.push_str(second.as_str()); - Value::String(Rc::new(new)) - } - _ => unreachable!(), - }; - self.push(combined); - } - Stringify => { - let to_stringify = self.pop(); - let the_string = to_stringify.stringify(); - let stringified = Value::String(Rc::new(the_string)); - self.push(stringified); - } - Partial => { - let arity = self.read(); - let the_fn = self.pop(); - let Value::Fn(ref inner) = the_fn else { - return self.panic("only functions may be partially applied"); - }; - let args = self.stack.split_off(self.stack.len() - arity as usize); - let partial = crate::value::Partial { - args, - name: inner.name(), - function: the_fn, - }; - self.push(Value::Partial(Rc::new(partial))); - } - TailCall => { - let arity = self.read(); - - let called = self.pop(); - - if self.debug { - println!( - "=== tail call into {called}/{arity} from {} ===", - self.frame.function.as_fn().name() - ); - } - - match called { - Value::Fn(_) => { - if !called.as_fn().accepts(arity) { - return self.panic_with(format!( - "wrong number of arguments to {} passing {arity} args", - called.show() - )); - } - // first put the arguments in the register - for i in 0..arity as usize { - self.return_register[arity as usize - i - 1] = self.pop(); - } - // self.print_stack(); - - // then pop everything back to the current stack frame - self.stack.truncate(self.frame.stack_base); - // then push the arguments back on the stack - let mut i = 0; - while i < 8 && self.return_register[i] != Value::Nothing { - let mut value = Value::Nothing; - swap(&mut self.return_register[i], &mut value); - self.push(value); - i += 1; - } - - let splat_arity = called.as_fn().splat_arity(); - if splat_arity > 0 && arity >= splat_arity { - let splatted_args = self.stack.split_off( - self.stack.len() - (arity - splat_arity) as usize - 1, - ); - let gathered_args = Vector::from(splatted_args); - self.push(Value::List(Box::new(gathered_args))); - } - let arity = if splat_arity > 0 { - splat_arity.min(arity) - } else { - arity - }; - - let mut frame = CallFrame { - function: called, - arity, - stack_base: self.stack.len() - arity as usize, - ip: 0, - }; - - swap(&mut self.frame, &mut frame); - frame.ip = self.ip; - - self.ip = 0; - } - Value::BaseFn(base_fn) => { - let value = match (arity, base_fn) { - (0, BaseFn::Nullary(_, f)) => f(), - (1, BaseFn::Unary(_, f)) => f(&self.pop()), - (2, BaseFn::Binary(_, f)) => { - let y = &self.pop(); - let x = &self.pop(); - f(x, y) - } - (3, BaseFn::Ternary(_, f)) => { - let z = &self.pop(); - let y = &self.pop(); - let x = &self.pop(); - f(x, y, z) - } - _ => return self.panic("internal ludus error"), - }; - // // algo: - // // clear the stack - // self.stack.truncate(self.frame.stack_base); - // // then pop back out to the enclosing stack frame - // self.frame = self.call_stack.pop().unwrap(); - // self.ip = self.frame.ip; - // // finally, throw the value on the stack - self.push(value); - // println!( - // "=== returning to {} ===", - // self.frame.function.as_fn().name() - // ); - } - Value::Partial(partial) => { - let last_arg = self.pop(); - let args = &partial.args; - for arg in args { - if *arg == Value::Nothing { - self.push(last_arg.clone()); - } else { - self.push(arg.clone()); - } - } - let the_fn = partial.function.clone(); - let mut frame = CallFrame { - function: the_fn, - arity: args.len() as u8, - stack_base: self.stack.len() - args.len(), - ip: 0, - }; - - swap(&mut self.frame, &mut frame); - frame.ip = self.ip; - - self.call_stack.push(frame); - self.ip = 0; - } - _ => { - return self.panic_with(format!("{} is not a function", called.show())) - } - } - } - Call => { - let arity = self.read(); - - let called = self.pop(); - - if self.debug { - println!("=== calling into {called}/{arity} ==="); - } - - match called { - Value::Fn(_) => { - if !called.as_fn().accepts(arity) { - return self.panic_with(format!( - "wrong number of arguments to {} passing {arity} args", - called.show() - )); - } - let splat_arity = called.as_fn().splat_arity(); - if splat_arity > 0 && arity >= splat_arity { - let splatted_args = self.stack.split_off( - self.stack.len() - (arity - splat_arity) as usize - 1, - ); - let gathered_args = Vector::from(splatted_args); - self.push(Value::List(Box::new(gathered_args))); - } - let arity = if splat_arity > 0 { - splat_arity.min(arity) - } else { - arity - }; - let mut frame = CallFrame { - function: called, - arity, - stack_base: self.stack.len() - arity as usize, - ip: 0, - }; - - swap(&mut self.frame, &mut frame); - frame.ip = self.ip; - - self.call_stack.push(frame); - self.ip = 0; - } - Value::BaseFn(base_fn) => { - let value = match (arity, base_fn) { - (0, BaseFn::Nullary(_, f)) => f(), - (1, BaseFn::Unary(_, f)) => f(&self.pop()), - (2, BaseFn::Binary(_, f)) => { - let y = &self.pop(); - let x = &self.pop(); - f(x, y) - } - (3, BaseFn::Ternary(_, f)) => { - let z = &self.pop(); - let y = &self.pop(); - let x = &self.pop(); - f(x, y, z) - } - _ => return self.panic("internal ludus error"), - }; - self.push(value); - } - Value::Partial(partial) => { - let last_arg = self.pop(); - let args = &partial.args; - for arg in args { - if *arg == Value::Nothing { - self.push(last_arg.clone()); - } else { - self.push(arg.clone()); - } - } - let the_fn = partial.function.clone(); - let mut frame = CallFrame { - function: the_fn, - arity: args.len() as u8, - stack_base: self.stack.len() - args.len(), - ip: 0, - }; - - swap(&mut self.frame, &mut frame); - frame.ip = self.ip; - - self.call_stack.push(frame); - self.ip = 0; - } - _ => { - return self.panic_with(format!("{} is not a function", called.show())) - } - } - } - Return => { - if self.debug { - println!("== returning from {} ==", self.frame.function.show()) - } - self.frame = self.call_stack.pop().unwrap(); - self.ip = self.frame.ip; - let mut value = Value::Nothing; - swap(&mut self.return_register[0], &mut value); - self.push(value); - } - Print => { - println!("{}", self.pop().show()); - self.push(Value::Keyword("ok")); - } - SetUpvalue => { - let value = self.pop(); - let Value::Fn(lfn) = self.peek() else { - panic!("expected function closing over value, got {}", self.peek()); - }; - lfn.close(value); - } - GetUpvalue => { - let idx = self.read(); - if let Value::Fn(ref inner) = self.frame.function { - self.push(inner.as_ref().upvalue(idx)); - } else { - unreachable!(); - } - } - } - } - } -} diff --git a/thoughts.md b/thoughts.md deleted file mode 100644 index 01c4dca..0000000 --- a/thoughts.md +++ /dev/null @@ -1,92 +0,0 @@ -# VM thoughts - -### Initial thoughts -We want numbers and bools as unboxed as possible. - -Nil is a singleton, and should be static. - -Strings come in two flavours: -* String literals, which are static/interned. -* Constructed strings, which should be `Rc` - -Keywords are static/interned. - -Tuples should be refcounted for now. - -### Optimization and other thoughts -2024-11-09 -* To put tuples on the stack, we need to know both how long they are (number of members) and how big they are (amount of memory), since tuples can contain other tuples. - - All other values must be one stack cell: - * `nil` is its own thing - * numbers are a wrapped `f64` (at least until we get to NaN boxed values) - * booleans are a wrapped `bool` - * keywords are a wrapped `u16` or `u32`, which is an index into a vec of `&str`s, which can be read back into a string when printed - * strings are a `&str` or an `Rc` (with two possible wrappers: `Value::Str` or `Value::String`) - * dicts are `imbl::HashMap`, with the hash generated on the index of the keyword - * sets are `imbl::HashSet`, with the caveat that `f64` isn't `Eq`, which means that we can't use it for a hash key. The way around this, I think, is to implement `Eq` for `Value`, with a panic if we try to put NaN in a set - * functions are `Rc` - * boxes are `Rc` - * That means everything is either a wrapped `Copy` (`:nil`, `:number`, `:bool`), an interned reference (`:keyword`, `:string`), `Rc` reference types (`:string`, `:box`, `:fn`), or persistent reference types that have their own `clone` (`:list`, `:dict`, `:set`) - * This doesn't cover everything, yet. But other reference types will be `Rc`ed structs: to wit, processes and packages. - - Tuples, meanwhile, have a special representation on the stack. - * They start with a `Value::TupleStart(len: u8, size: u8)`. - * They then have a number of members. - * They end with a `Value::TupleEnd(len: u8, size: u8)`. - * `len` indicates the number of members in the tuple; `size` indicates the size of the tuple on the stack, including the `TupleStart` and `TupleEnd` cells. For `()`, `len` is `0`, and `size` is `2`. Nesting tuples will lead to larger divergences, and will increase `size` but not `len`. - * If sombody tries to stuff more than 255 members in a tuple, nested or not, we get a validation error to tell them to use a list. - - Or promote it to be a reference type? The natural encoding of a list in Ludus is using a `(car, cdr)` encoding (or `(data, next)`). I believe the way to get this out of a scope (block or function) is to expand the tuple fully, which could lead very quickly to very large tuples. - - But we can easily distinguish between argument tuples and value tuples, and promote value tuples with a size larger than 255 to a `Value::BigTuple(Rc>)`. - - But in no case should we allow arguments to get bigger than 255. - - Keeping small value tuples on the stack is worthwhile, especially given the importance of result tuples, which should stay on the stack. -* This naturally leads to questions about pattern matching, especially when we get to a stack-based bytecode VM. - - A pattern, like a tuple, is a series of cells. - - The goal is to keep pattern sizes and lengths identical to the tuple data representation. - - That means that, like data representations, a pattern has to include both a set of bytecode instructions and a data representation on the stack. - - In fact, I suspect that the fastest way to encode this will be to push the data representation of the scrutinee on the stack, and then to push the pattern, and to then compare within the stack, at different offsets. - -### Let's not reinvent the wheel -#### Or, crates we will use -* `chumsky` for parsing -* `ariadne` for parsing errors -* `imbl` for persistent data structures -* `boxing` for NaN boxing (eventually?) -* ~~`tailcall` for tail recursion~~ This only works for simple recursion, and we need mutual recursion. - -We additionally might want crates for: -* processes/actors, although given that Ludus will be single-threaded for the forseeable future, it may be lighter weight to just write my own `process` abstraction -* in that case, we will need a ringbuffer, `ringbuf` - -### On string interpolation -#### Which is proving rather harder to handle than I expected -I'm trying to use Chumsky to do this, but it's weirdly much harder to model with Chumsky's parer combinators than I expected. -I suspect the thing to do is to just brute force it in much the same way that I do in the Janet-based scanner: loop through the things and push things onto vectors in the correct ways. -This won't be a one-for-one translation, but I suspect it will be easier to manage than banging my head against, especially, the terrible error messages Chumsky's elaborate types give me. - -This makes interpolated strings easy enough to work with. - -That said, interpolation patterns are harder. -In particular, I worry whether I'll be able to compile a Chumsky parser with strings that aren't interned/`'static`. -Because the pattern match will actually have to be a little Chumsky parser guy (doo dah), or some equivalent. -(In the Janet-based interpreter, I used Janet's built-in PEGs.) - -### On performance -The Rust tree-walk interpreter is something like two orders of magnitude faster than the Janet interpreter. -So in that sense, I think it's a worthwhile middle ground to effectively publish this first, easier-to-develop approach, and then to work on a bytecode VM later. - -It's worth noting that my approach to this first tree-walk interpreter still leaves a lot on the table for optimization: the `Value` enum is 64 _bytes_. -This is because `imbl::Vector`s are 64 bytes. -I'm trying to ensure opportunistic mutation throughout, but I have found it hard with dicts. -This sort of thing. - -Finally, it's clear that some perf testing will be necessary to determine the final arrangement of things. -Will `box`ing things to get heap pointers help? -Or will the extra indirection cost more speed than even if we squeeze `Value`'s size down to 8 bytes? -Will `box`ing lists, etc., mung up how `imbl` does refcounting and opportunistic mutation? -There are things like `tinyvec` which does some of the dark magic around allocating that might make using tuples easier to manage? - -### On parsing in Ludus -I've been thinking about Ludus's built-in parsing capabilities. -Using the interpolition-style string pattern matching parsing for ELIZA makes a lot of sense, but we need something more robust for, say, a Lisp. -Looking at this, I think that Janet's builtin PEG parsing might be a much more interesting solution than just about anything else. -I'm pretty sure I can make a slow, but user-friendly-enough version of that that works in Ludus. -(Famous last words.)