A => LICENSE +674 -0
@@ 1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
A => README.md +17 -0
@@ 1,17 @@
+# The Gek Programming Language
+
+Gek is a simple programming language for the simple life.
+
+The website is https://geklang.org and there's a tutorial at https://geklang.org/tutorial.html
+
+## Install From Source
+
+On Unix, all you need is Git and the Go programming language compiler.<br>
+Then run:
+ `git clone https://git.sr.ht/~geb/gek && gek/install.sh`
+
+## Chat
+
+This repository hosts the initial implementation of the `gek` interpreter as well as user documentation.
+The current focus is simplicity and the language design, greater efficiency will come later.
+It does compile on Windows but it's not prioritized.
A => doc/chat/description +13 -0
@@ 1,13 @@
+The Gek language is useful for manipulating files and program output, and conducting programs.
+Gek programs often perform an operation on each line of input, making them applicable to ongoing pipelines as well as input with a definite end.
+
+Programs can be entered on the command line, or read from a file with the -f option.
+Input is read from the list of files passed on the command line, or from stdin if no files are passed.
+
+Gek programs consist of "words" that each do an action and string literals.
+These are executed in order for each record of input, which is by default a line.
+Words take their arguments from "the stack" and put their return values onto it.
+String literals put themselves on the stack.
+All values are strings but words might take their arguments to represent text, a number or regular expression.
+
+There is a comprehensive tutorial at: https://geklang.org/tutorial.html
A => doc/chat/notation +44 -0
@@ 1,44 @@
+Each word listing has a pictorial showing the word's effect on the stack.
+These "stack effects" follow the format: ( <arguments> -- <return values> )
+
+The word `swap` has the listing:
+ swap ( s1 s2 -- s2 s1 )
+ Swaps the elements.
+It shows it takes two strings and returns two strings.
+All values are strings so the term element is used interchangeably when they don't represent a number or the like.
+As always, the arguments are taken from the top of the stack, and the return values are put on the top of the stack.
+
+The word `+` has the listing:
+ + ( n1 n2 -- n3 )
+ Returns the two numbers added.
+It shows it takes two numbers and returns a numbers.
+As all values are strings, numbers are really characters representing numbers.
+
+Some words like `READ?` use a | to show alternative outcomes:
+ READ? ( s1 -- s2 1 | 0 )
+ Returns the contents of the file at filepath s1 and 1, or just returns 0.
+This shows `READ?` returns either (s2 and 1) or just 0.
+
+Some words like `mapping` steal a token after them, which is denoted in angle brackets:
+ mapping <name> ( -- )
+ Create a new mapping.
+
+
+All values are strings but words might take them their arguments to represent text, a number, or regular expression.
+Symbols:
+ s - a string
+ n - a number, potentially fractional
+ c - a the whole number, any fractional part is disregarded, a negative number will throw an error
+ i - an integer number, any fractional part is disregarded
+ f - a boolean flag number where non-zero is true and zero is false
+ re - a regular expression
+ s... - zero or more strings
+ s... c - zero or more strings and a count of how many
+
+Words determine the value of number arguments after skipping whitespace and giving up after a character that's not a sign, point or digit.
+
+If integer arguments specify the <i>th something we call them indices.
+Indices start from zero, so 0 for the first, 1 for the second, and so on.
+If they're negative they go from the other end, so -1 for the last, -2 for the second last, and so on.
+
+Boolean flags returned by a word will always be 0 or 1.
A => doc/chat/snippets +12 -0
@@ 1,12 @@
+Print the stack without touching it:
+ Stack #$
+
+Filter a list:
+ "A" "b" "C" 3 foreach x x upper is if x then loop @
+
+Skip unreadable files:
+ FB fileless? if apologize nextfile then ;
+
+Filter output through an external command:
+ B withhold ;
+ E withheld "sort | uniq" $shell ;
A => doc/chat/topics +25 -0
@@ 1,25 @@
+Chat
+:description
+:notation
+:snippets
+
+Word Categories
+:general
+:input
+:stack
+:printing
+:string
+:math
+:events
+:branching
+:loops
+:error
+:args
+:files
+:system
+:random
+:state
+:variables
+:mappings
+:stacks
+:processes
A => doc/words/args +38 -0
@@ 1,38 @@
+The command-line arguments passed to gek are initially put on a special stack called Args.
+Before each FB event, the bottom element is used as the next input file filepath and removed from the stack.
+
+
+
+optparse <Map name> ( s... c s1 -- s... c )
+Parses a list of strings for GNU-style command-line options (like -v and --version) with s1 specifying the allowed options.
+Option results are assigned to the map and any positional arguments are returned.
+s1 should be the option names separated by whitespace, each with a trailing ? or :.
+? specifies a boolean flag option, the result will be 0 or 1.
+: specifies an option that takes an argument, the result will be the argument.
+One-character names specify short options like -v, multi-character names specify long options like --version.
+Long boolean flags in the arguments can have an optional argument like --version=true, they may be: 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False
+Throws an error if it parses an unknown option or invalid boolean, or s1 is invalid.
+
+optparse? <Map name> ( s... c s1 -- s... c 1 | 0 )
+Same as `optparse` but returns whether successful rather than throwing an error if it parses an unknown option.
+
+
+optclean ( s... c s1 -- s... c )
+Tidies a list of GNU-style command-line options (like -v and --version) and positional arguments, with s1 specifying the allowed options.
+s1 should be the option names separated by whitespace, each with a trailing ? or :.
+? specifies a boolean flag option.
+: specifies an option that takes an argument.
+It returns each short option joined with its argument if it takes one, like "-fmyfile".
+It returns each long option assigned to its argument or to true/false if it doesn't take one, like "--file=myfile" or "--version=true".
+The options are returned in order, then a "--" and the positional arguments.
+Long boolean flags in the arguments can have an optional argument like --version=true, they may be: 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False
+Throws an error if it parses an unknown option or invalid boolean, or s1 is invalid.
+
+optclean? <Map name> ( s... c s1 -- s... c 1 | 0 )
+Same as `optclean` but returns whether successful rather than throwing an error if it parses an unknown option.
+
+
+varparse <Map name> ( s... c -- s... c )
+Parses a list of strings for the form "some key=some value", assigning results to the map and returning non-matches.
+They match the form be if they have an =.
+If they match, the substring before the first = is used as the key, the substring after the first = is used as the value.
A => doc/words/branching +44 -0
@@ 1,44 @@
+If statements conditionally execute code.
+
+
+
+<flag> if <code> then
+If flag is true, code is executed.
+
+<flag> if <code1> else <code2> then
+If flag is true, code1 is executed, else code2 is executed.
+
+<flag> if <code1> otherwise <flag2> if <code2> otherwise <flag3> if <code3> ... then
+If flag is true, code1 is executed, otherwise if flag2 is true code2 is executed, otherwise if flag3 is true code3 is executed ...
+
+<flag> if <code1> otherwise <flag2> if <code2> otherwise <flag3> if <code3> ... else <code4> then
+Same as above but if none of the flags are true, code4 is executed.
+
+
+if ( f -- )
+Starts an if statement.
+If f is false execution skips to the next `otherwise`, `else`, or `then` not nested in another if statement.
+
+otherwise ( -- )
+Used instead of `then` to chain if statements similar to "else if" in other languages.
+If reached (not skipped by `if`), execution continues after the next `then` not nested in another if statement.
+
+else ( -- )
+Starts the optional clause in an if statement that executes if the first clause didn't.
+If reached (not skipped by `if`), execution continues after the next `then` not nested by another if statement.
+
+then ( -- )
+Delimits an if statement.
+
+
+and ( n1 n2 -- n1 | n2 )
+Returns n2 if n1 is true else false.
+
+#and ( n... c -- n1 )
+Returns false if any are false else the last number.
+
+or ( n1 n2 -- n1 | n2 )
+Returns n1 if n1 is true else n2.
+
+#or ( n... c -- n1 )
+Returns the first true number else false.
A => doc/words/error +17 -0
@@ 1,17 @@
+Some words throw an error terminating the program if they are unsuccessful.
+Some words return whether they were successful and set `apologize` to print why they were unsuccessful.
+
+
+
+apology ( -- s )
+Some words have this return an error message if they're unsuccessful or an empty string if they're successful.
+
+apologize ( -- )
+Some words have this print an error message to stderr if they're unsuccessful or do nothing if they're successful.
+
+assert ( f -- )
+Throws an error if f is false.
+Useful for sanity checks and low effort error handling.
+
+error ( s -- )
+Throws an error with the message s.
A => doc/words/events +64 -0
@@ 1,64 @@
+Each event word lets you define code to run at it's specific event.
+The event words are: B, E, FB, FE, PE, PRENUM, POSTNUM, TOKEN, and :.
+
+All event words take this code from after themselves until a terminating `;`.
+Any ; in string literals or commented by `#` or `#!` are ignored.
+Occurrences of event word names increase nesting ignoring a future `;`.
+So do occurrences of sort, another word that takes a `;` block.
+
+Multiple instances of the same event word can append more code.
+Each instance instance only executes on its first encounter, their code blocks are skipped on future encounters.
+Event words in macros or evaluated by `eval` are taken to be unique instances and always execute.
+
+Gek does a first pass where it evaluates the uninterrupted event words and `#!` and `#` comments at the start of each program text.
+If this covers all the words and no E/FB/FE events were defined the program terminates.
+This first pass is so events can be defined and `B` events executed before any input is read, and so lone `B` blocks terminate without reading input.
+
+
+There is a special Mapping of the Macros called Macro.
+Assignments to it assign Macros indirectly.
+
+
+
+B <code> ; ( -- )
+Defines code to run at the beginning of the program, before any input is read.
+It must be at the start of the program as described above.
+
+E <code> ; ( -- )
+Defines code to run at the end of the program, unless the program is terminated with `EXIT` or by an operating system signal.
+
+FB <code> ; ( -- )
+Defines code to run before processing each input file.
+
+FE <code> ; ( -- )
+Defines code to run after processing each input file.
+
+PE <code> ; ( -- )
+Defines code to run at the end of the process, even if terminated by an error or by a non-forceful operating system signal.
+
+PRENUM <code> ; ( -- )
+Defines code to run before each number argument is taken from the stack.
+Number arguments in `PRENUM` or `POSTNUM` events don't trigger more `PRENUM` events.
+
+POSTNUM <code> ; ( -- )
+Defines code to run after each number is put on the stack, including number literals.
+Number arguments in `PRENUM` or `POSTNUM` events don't trigger more `POSTNUM` events.
+
+TOKEN <code> ; ( -- )
+Defines code to run on each token before they're evaluated.
+In this case, tokens are each non-whitespace part or string literal of the program.
+Before each token is evaluated, the token is put on the stack, `TOKEN` events execute, and the new top value is taken to be the token.
+Tokens in the `TOKEN` code don't trigger `TOKEN` events.
+
+: <name> <code> ; ( -- )
+Defines a word that expands to code terminated by `;` but the first token is used for the word's name.
+The name can't start or end with " or `.
+We call a word defined by `:` a macro.
+
+
+eval ( s -- <depends on s> )
+Executes s as code.
+
+yank ( -- s )
+Steals the next token after the macro it is in.
+In this case, tokens are each non-whitespace part of the program.
A => doc/words/files +110 -0
@@ 1,110 @@
+read ( s1 -- s2 )
+Returns the contents of the file at filepath s1 having removed any trailing newline and anything after, or throws an error.
+
+read? ( s1 -- s2 1 | 0 )
+Tries to return the contents of the file at filepath s1 having removed any trailing newline and anything after.
+Returns whether successful and sets `apologize`.
+
+READ ( s1 -- s2 )
+Returns the contents of the file at filepath s1, or throws an error.
+
+READ? ( s1 -- s2 1 | 0 )
+Returns the contents of the file at filepath s1 and 1, or just returns 0.
+Sets `apologize`.
+
+
+write ( s1 s2 -- )
+Makes s1 plus a newline the contents of the file at filepath s2, creating it if necessary, or throws an error.
+
+WRITE ( s1 s2 -- )
+Same as `write` but doesn't add a newline.
+
+write? ( s1 s2 -- f )
+Tries to make s1 plus a newline the contents of the file at filepath s2, creating it if necessary.
+Returns whether successful and sets `apologize`.
+
+WRITE? ( s1 s2 -- f )
+Same as `write?` but doesn't add a newline.
+
+append ( s1 s2 -- )
+Appends s1 plus a newline to the file at filepath s2, creating it if necessary, or throws an error.
+
+APPEND ( s1 s2 -- )
+Same as `append` but doesn't add a newline.
+
+append? ( s1 s2 -- f )
+Tries to append s1 plus a newline to the file at filepath s2, creating it if necessary.
+Returns whether successful and sets `apologize`.
+
+APPEND? ( s1 s2 -- f )
+Same as `append?` but doesn't add a newline.
+
+
+
+unlink ( s -- )
+Removes the file or empty directory at filepath s, or throws an error but not if it didn't exist.
+
+unlink? ( s -- f )
+Tries to remove the file or empty directory s.
+Returns 1 if it was removed or didn't exist, else 0.
+Sets `apologize`.
+
+UNLINK ( s -- )
+Removes the file or directory and its children at filepath s, or throws an error but not if it didn't exist.
+
+UNLINK? ( s -- f )
+Tries to remove the file or directory and its children at filepath s.
+Returns 1 if everything was removed or it didn't exist, else 0.
+Sets `apologize`.
+
+rename ( s1 s2 -- )
+Renames/moves the file or directory at filepath s1 to s2, or throws an error.
+It throws an error if s2 is a directory.
+
+rename? ( s1 s2 -- f )
+Tries to rename/move the file or directory at filepath s1 to s2, and returns whether successful.
+It is unsuccessful if s2 is a directory.
+Sets `apologize`.
+
+mkdir ( s -- )
+Creates a new directory at the filepath s, or throws an error.
+
+mkdir? ( s -- f )
+Tries to create a new directory at the filepath s, and returns whether successful.
+Sets `apologize`.
+
+entries ( s1 -- s... c )
+Returns the children's filepaths of the directory at filepath s1, or throws error.
+The filepaths will be prefixed with s1 and directories will have a trailing / (or \ on Windows).
+
+entries? ( s1 -- s... c 1 | 0 )
+Returns the list of entries of the directory s1 and 1, or just returns 0.
+The filepaths will be prefixed with s1 and directories will have a trailing / (or \ on Windows).
+Sets `apologize`.
+
+ENTRIES ( s1 -- s... c )
+Same as `entries` but doesn't prefix the filepaths with s1.
+
+ENTRIES? ( s1 -- s... c 1 | 0 )
+Same as `entries?` but doesn't prefix the filepaths with s1.
+
+
+cleanpath ( s1 -- s2 )
+Returns the shortest filepath equivalent to s1 by purely lexical processing.
+
+relatepath ( s1 s2 -- s3 )
+Returns the filepath s1 relative to the filepath s2 by purely lexical processing, or throws error.
+(Returned paths are clean, not requiring `cleanpath`)
+
+relatepath? ( s1 s2 -- s3 1 | 0 )
+Returns the filepath s1 relative to the filepath s2 by purely lexical processing and 1, or just returns 0.
+(Returned paths are clean, not requiring `cleanpath`)
+Sets `apologize`.
+
+resolvepath ( s1 -- s2 )
+Returns the real filepath of s1 having followed any symbolic links, or throws error.
+
+resolvepath? ( s1 -- s2 1 | 0 )
+Returns the real filepath of s1 having followed any symbolic links and 1, or just returns 0.
+(Returned paths are clean, not requiring `cleanpath`)
+Sets `apologize`.
A => doc/words/general +11 -0
@@ 1,11 @@
+#! ( -- )
+Skips the rest of line.
+Good for shebang lines.
+
+# ( -- )
+Skips the rest of line.
+Good for commenting code.
+
+? ( -- )
+Shows the stack's contents unambiguously for debugging.
+It doesn't affect the value of `@`.
A => doc/words/input +87 -0
@@ 1,87 @@
+Gek programs execute for every record of the input files.
+A record is a line by default.
+
+
+
+fileless? ( -- f )
+Returns whether the input file opened successfully (or false in `B`).
+Useful in `FB` to skip bad files with `nextfile`.
+Sets `apologize` regarding the opening.
+
+file ( -- s )
+Returns the filepath of the lastest input file whether it was opened successfully or not (or an empty string in `B`).
+
+
+r ( -- s )
+Returns the record.
+Used after the end of an input file it returns the "not quite a record" that wasn't unterminated by a separator.
+
+R ( s -- )
+Sets the record to s, recalculating fields.
+
+n ( -- c )
+Returns the number of input file records read in.
+
+N ( -- c )
+Returns the number of input file records read in with the current input file.
+
+f ( i -- s )
+Returns the field at i, or an empty string.
+
+F ( s i -- )
+Sets the field at i to s.
+If new territory, the in-between field delimiters each become a space, the in-between fields become empty strings.
+
+fields ( -- s... c )
+Returns the fields.
+
+c ( -- c )
+Returns the number of fields.
+
+d ( i -- s )
+Returns the field delimiter at i, or an empty string.
+
+D ( s i -- )
+Sets the field delimiter at i to s.
+If new territory, the in-between field delimiters each become a space, the in-between fields become empty strings.
+
+delims ( -- s... c )
+Returns the field delimiters.
+
+sep ( -- s )
+Returns the record separator, which is either the text that terminated the record (using `separator`), or the text that was terminated by the record (using `record`).
+Used after the end of an input file it returns the "not quite a separator" that wasn't terminated by a record.
+
+
+record ( re -- )
+Defines a record to be the regular expression re for future records.
+The default is `"\n" separator`.
+
+separator ( re -- )
+Defines a record to be what's terminated by the regular expression re for future records.
+The default is `"\n" separator`.
+
+field ( re -- )
+Defines a field to be the regular expression re for future records.
+The default is `"\S+" field`.
+
+delim ( re -- )
+Defines a field to be what's terminated by the regular expression re for future records.
+The default is `"\S+" field`.
+
+
+bump ( -- )
+Tries to prematurely read in the next record of the input file or of the next input file if fileless.
+If there wasn't one, the record will be an empty string or the "not quite a record" that wasn't terminated by a separator.
+
+bump? ( -- f )
+Same as `bump` but returns whether it read in a record.
+
+next ( -- )
+Stops executing the current program cycle or event.
+Useful for finishing with a record early and going on to the next record.
+
+nextfile ( -- )
+Stops executing the current program cycle or event, and stops processing the input file if any.
+Unless used in FB, FE will still be triggered.
+Useful for finishing with an input file early and going on to the next input file.
A => doc/words/loops +56 -0
@@ 1,56 @@
+for ( n -- )
+Starts a for loop that terminates when the index, that starts at zero, equals or crosses the limit n.
+
+#for ( n1 n2 -- )
+Starts a for loop that terminates when the index, that starts at n2, equals or crosses the limit n1.
+
+foreach ( s... c -- )
+Starts a foreach loop that terminates when each list element has had it's turn.
+
+forever ( -- )
+Starts a forever loop that loops indefinitely.
+
+loop ( -- )
+Increments the innermost nested loop's index by one and conditionally jumps back to the start of it.
+If a for loop: it jumps back if the index hasn't equaled or crossed the limit.
+If a foreach loop: if there's another element, it proceeds onto it and jumps back.
+If a forever loop: it unconditionally jumps back.
+
+#loop ( n -- )
+Same as `loop` but increments the index by n.
+
+
+i ( -- n )
+Returns the index of the innermost loop.
+
+j ( -- n )
+Returns the index of the second innermost loop.
+
+x ( -- s )
+Returns the element of the innermost foreach loop.
+
+X ( -- s )
+Has the innermost foreach loop proceed onto its next element or an empty string if out of elements, and returns it.
+
+y ( -- s )
+Returns the element of the second innermost foreach loop.
+
+Y ( -- s )
+Has the second innermost foreach loop proceed onto its next element or an empty string if out of elements, and returns it.
+
+remnant ( -- n )
+If in a for loop: returns limit - index - 1, which is how many iterations remain if incrementing by one.
+If in a foreach loop: returns how many elements remain.
+
+
+break ( -- )
+Unconditionally breaks out of the innermost nested loop.
+Execution continues after the next `loop` or `#loop` not nested by another loop.
+
+while ( f -- )
+Breaks out of a loop if f is false.
+Equivalent to `0 = if break then`.
+
+until ( f -- )
+Breaks out of a loop if f is true.
+Equivalent to `if break then`.
A => doc/words/mappings +44 -0
@@ 1,44 @@
+Mappings are collections of keys and corresponding values.
+
+
+
+mapping <name> ( -- )
+Create a new mapping.
+
+map <Mapping name> ( s1 s2 -- )
+Assigns the value s1 to the key s2 of the mapping.
+
++map <Mapping name> ( n s -- )
+Increments by n the value of the key s of the mapping.
+The value will be n if the key was unassigned.
+
+-map <Mapping name> ( n s -- )
+Decrements by n the value of the key s of the mapping.
+The value will be -n if the key was unassigned.
+
+unmap <Mapping name> ( s -- )
+Unassigns the key s of the mapping if it is assigned.
+
+<Mapping name> ( s1 -- s2 )
+Returns the corresponding value of the key s1 of the mapping, or an empty string if it's not assigned.
+
+of <Mapping name> ( s1 -- s2 )
+Returns the corresponding value of the key s1 of the mapping, or throws an error if it's not assigned.
+
+of? <Mapping name> ( s1 -- s2 1 | 0 )
+Returns the corresponding value of the key s1 of the mapping and 1, or just 0 if it's not assigned.
+
+tot <Mapping name> ( -- c )
+Returns the number of keys the mapping has.
+
+in <Mapping name> ( s -- f )
+Returns whether the mapping has a key s.
+
+keysof <Mapping name> ( -- s... c )
+Returns the keys of the mapping.
+
+valuesof <Mapping name> ( -- s... c )
+Returns the values of the mapping.
+
+itemsof <Mapping name> ( -- s... c )
+Returns key value pairs of the mapping.
A => doc/words/math +142 -0
@@ 1,142 @@
++ ( n1 n2 -- n3 )
+Returns the two numbers added.
+- ( n1 n2 -- n3 )
+Returns n1 subtracted by n2.
+* ( n1 n2 -- n3 )
+Returns the two numbers multiplied.
+/ ( n1 n2 -- n3 )
+Returns n1 divided by n2.
+
+#+ ( n... c n... c -- n... )
+Does addition on the corresponding numbers of the lists, padding the shorter list with zeroes.
+#- ( n... c n... c -- n... )
+Does subtraction on the corresponding numbers of the lists, padding the shorter list with zeroes.
+#* ( n... c n... c -- n... )
+Does multiplication on the corresponding numbers of the lists, padding the shorter list with zeroes.
+#/ ( n... c n... c -- n... )
+Does division on the corresponding numbers of the lists, padding the shorter list with zeroes.
+
+< ( n1 n2 -- f )
+Returns whether n1 is less than n2.
+<= ( n1 n2 -- f )
+Returns whether n1 is less than or equal to n2.
+> ( n1 n2 -- f )
+Returns whether n1 is greater than n2.
+>= ( n1 n2 -- f )
+Returns whether n1 is greater than or equal to n2.
+= ( n1 n2 -- f )
+Returns whether the numbers are equal.
+!= ( n1 n2 -- f )
+Returns whether the numbers are unequal.
+
+
+mod ( n1 n2 -- n3 )
+Returns the modulo of the numbers.
+
+`10 3 mod` returns 1 because the best you can fill up 10 in chunks of 3 is 9 with a remainder of 1.
+`9 3 mod` returns 0 because you can perfectly fill up 9 in chunks of 3.
+
+It lets you wrap numbers into a given range.
+`<number> 12 mod` wraps the number in the range [0,12).
+`17 12 mod` returns 5, like how 17:00 is five o'clock.
+`-2 12 mod` returns 10, like you're turning a clock back.
+
+A number line of `<number> 3 mod`:
+-7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7
+ 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1
+
+
+MOD ( n1 n2 -- n3 )
+Returns the remainder of the numbers.
+This differs from `mod` when it comes to negative arguments.
+
+A number line of `<number> 3 MOD`:
+-7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7
+-1 0 -2 -1 0 -2 -1 0 1 2 0 1 2 0 1
+
+
+sum ( n... c -- n )
+Returns the sum of the list.
+
+mean ( n... c -- n )
+Returns the mean of the list.
+
+min ( n1 n2 -- n1 | n2 )
+Returns the lesser of the two numbers.
+
+#min ( n... c -- n )
+Returns the least of a list of numbers.
+
+max ( n1 n2 -- n1 | n2 )
+Returns the greater of the two numbers.
+
+#max ( n... c -- n )
+Returns the greatest of a list of numbers.
+
+abs ( n1 -- n2 )
+Returns the absolute value the of the number (the number with the sign dropped).
+
+floor ( n1 -- n2 )
+Returns the greatest integer less than or equal to the number.
+
+ceil ( n1 -- n2 )
+Returns the least integer greater than or equal to the number.
+
+round ( n1 -- n2 )
+Returns the nearest integer to the number.
+
+pow ( n1 n2 -- n3 )
+Returns n1 to the power of n2.
+
+sqrt ( n1 -- n2 )
+Returns the square root of the number.
+
+hypot ( n1 n2 -- n3 )
+Returns the hypotenuse of a right triangle given the other two sides, Sqrt(n1*n1 + n2*n2).
+
+pi ( -- n )
+Returns the mathematical constant pi.
+
+sin ( n1 -- n2 )
+Returns the sine of the radians.
+
+cos ( n1 -- n2 )
+Returns the cosine of the radians.
+
+tan ( n1 -- n2 )
+Returns the tangent of the radians.
+
+asin ( n1 -- n2 )
+Returns the arcsine (inverse sine) in radians of the number.
+
+acos ( n1 -- n2 )
+Returns the arccosine (inverse cosine) in radians of the number.
+
+atan ( n1 -- n2 )
+Returns the arctangent (inverse tangent) in radians of the number.
+
+direction ( nx ny -- n )
+Returns the angle of the coordinate from the positive x-axis.
+Like atan2(y,x) in other languages, with swapped arguments.
+
+degrees ( n1 -- n2 )
+Returns the number of radians converted to degrees.
+
+radians ( n1 -- n2 )
+Returns the number of degrees converted to radians.
+
+log ( n1 -- n2 )
+Returns the natural logarithm of the number.
+
+exp ( n1 -- n2 )
+Returns e (the mathematical constant) raised to the power of the number.
+
+
+que ( n1 n2 -- n3 )
+Returns the bitwise AND of the absolute values of n1 and n2, negated if both are negative.
+
+vel ( n1 n2 -- n3 )
+Returns the bitwise OR of the absolute values of n1 and n2, negated if either or both are negative.
+
+xor ( n1 n2 -- n3 )
+Returns the bitwise XOR of the absolute values of n1 and n2, negated if either but not both are negative.
A => doc/words/printing +53 -0
@@ 1,53 @@
+! ( s -- )
+Prints the string.
+
+. ( s -- )
+Prints the string and a space.
+
+$ ( s -- )
+Prints the string and a newline.
+
+#$ ( s... c -- )
+Prints the list of strings each with a newline.
+
+?. ( s f -- )
+If f is true, prints the string and a space.
+
+?$ ( s f -- )
+If f, prints the string and a newline.
+
+inform ( s -- )
+Prints the string and a newline to stderr, unless the string is empty.
+
+
+to ( s -- )
+Has future prints append to the file at filepath s, creating it if necessary, or throws an error.
+
+to? ( s -- f )
+Tries to have future prints append to the file at filepath s, creating it if necessary.
+Returns whether successful, setting `apologize`.
+
+fret ( -- )
+Redirects future prints to stderr.
+
+chill ( -- )
+Redirects future prints to stdout (the default).
+
+
+withhold ( -- )
+Retains future prints until a corresponding `withheld`.
+
+withheld ( -- s )
+Returns a string of the prints since the last `withhold`.
+Useful for sorting output, giving it to an external program, modifying a file in place, or building a string from printing words.
+
+buffer ( -- )
+Has future prints buffered.
+See also the -b option.
+
+unbuffer ( -- )
+Has future prints unbuffered (the default).
+
+flush ( -- )
+Flushes any buffering prints.
+Does nothing if printing is unbuffered which is the default.
A => doc/words/processes +53 -0
@@ 1,53 @@
+Processes let you run external commands in the background.
+The stdout is continuously stored and retained even after the command terminates.
+The stderr is printed to stderr.
+
+
+
+process <name> ( s -- )
+Creates a Process of the command-line s.
+The shell is invoked immediately with s piped in.
+
+take <Process name> ( -- s )
+Returns the next line of the Process' stdout, or an empty string.
+This can result in a deadlock if the command does not flush its output.
+
+take? <Process name> ( -- s f )
+Returns the next line of the Process' stdout and 1, or just 0 if none or unreadable.
+Sets `apologize` regarding readability.
+
+#take <Process name> ( c -- s... c )
+Returns the Process' next c lines of stdout, or as many as there are if it's finished outputting and there's fewer lines.
+
+send <Process name> ( s -- )
+Writes s plus a newline to the Process' stdin and doesn't do anything if it's unsuccessful.
+
+send? <Process name> ( s -- f )
+Tries to write s plus a newline to the Process' stdin and returns whether successful.
+Sets `apologize`.
+
+SEND <Process name> ( s -- )
+Writes s to the Process' stdin and doesn't do anything if it's unsuccessful.
+
+SEND? <Process name> ( s -- f )
+Tries to write s to the Process' stdin and returns whether successful.
+Sets `apologize`.
+
+shut <Process name> ( -- )
+Closes the Process' stdin.
+Useful for programs that don't output until all the input is read, like the Unix sort.
+
+await <Process name> ( -- )
+Waits for the Process' command to terminate if not already.
+`rc` will give the return code.
+
+conclude <Process name> ( -- s )
+Closes the Process' stdin, waits for the command to terminate if not already, and returns the remaining stdout.
+Sets `apologize` regarding readability.
+
+
+<Process name> ( -- f )
+Returns whether the Process' command has terminated.
+
+tot <Process name> ( -- c )
+Returns how many lines of stdout the Process has.
A => doc/words/random +12 -0
@@ 1,12 @@
+seed ( c -- )
+Sets the seed for the pseudo-random words, which determines their sequence of results.
+The default seed is different every time.
+
+random ( -- c )
+Returns a pseudo-random positive whole number.
+
+RANDOM ( -- n )
+Returns a pseudo-random real number between 0.0 and 1.0 (but never 1.0).
+
+shuffle ( s... c -- s... )
+Returns the elements in a pseudo-random order.
A => doc/words/stack +109 -0
@@ 1,109 @@
+drop ( s -- )
+Discards the element.
+
+#drop ( s... c -- )
+Discards the list.
+
+nip ( s1 s2 -- s2 )
+Discards s1.
+
+swap ( s1 s2 -- s2 s1 )
+Swaps the two elements.
+
+dup ( s -- s s )
+Duplicates the element.
+
+#dup ( s... c -- s... s... )
+Duplicates the elements.
+
+over ( s1 s2 -- s1 s2 s1 )
+Duplicates s1 over s2.
+
+rot ( s1 s2 s3 -- s2 s3 s1 )
+Cycles the three elements.
+
+#rot ( s... c i -- s... )
+Cycles a list i places, with `3 1 #rot` the same as `rot`.
+Cycles the other way if i is negative.
+
+more ( s c -- s... )
+Returns c+1 instances of s.
+
+
+relist ( s... c -- s... c s... c )
+Duplicates the list.
+
+add ( s... c s... c -- s... c )
+Joins the two lists.
+
+exchange ( s... c s... c -- s... c s... c )
+Swaps the two lists.
+
+reverse ( s... c -- s... )
+Reverses the order of the elements.
+
+keep ( s... c i -- s... )
+Returns at most i of the elements.
+If i is positive, it returns elements from the end, else from the start.
+
+KEEP ( s... c i -- s... )
+Returns all but the elements `keep` would return.
+
+kill ( s... c i -- s... )
+Returns the elements except the element at i if there even is one.
+
+#kill ( s... c i... c -- s... )
+Returns the elements of the first list except those at indices in the second list.
+Out of range indices do nothing.
+
+spare ( s... c i -- s | <nothing> )
+Returns the element at i if any.
+
+compose ( s... c i... c -- s... )
+Returns the elements of the first list ordered by the indices of the second list, with repeated indices repeating elements.
+Out of range indices do nothing.
+
+pad ( s... c i -- s... )
+Returns the list and empty strings to make it up to i elements.
+If i is positive, the empty strings are added to the end, else to the start
+
+$pad ( s... c i s2 -- s... )
+Same as `pad` but pads with s2 instead of empty strings.
+
+lace ( s... c s... c -- s... )
+Returns the two lists interlaced, using empty strings where one is longer.
+
+unlace ( s... c -- s... c s... c )
+Returns a list of the even-indexed elements and a list of the odd-indexed elements.
+
+tailor ( s... c s... c -- s... c s... c )
+Returns the two lists with the second truncated to the number of elements of the first.
+
+
+within ( s1 s... c -- f )
+Returns whether s1 occurs in the list.
+If it does, `where` will give the position of the first occurrence, else -1.
+See also `in` <Stack name>.
+
+#within ( s1 s... c i -- f )
+Returns whether s1 occurs enough in the list to refer to an occurrence by i.
+If it does, `where` will give the position of that occurrence, else -1.
+
+among ( s1 s... c -- n )
+Returns the number of occurrences of s1 in the list.
+
+
+depth ( -- c )
+returns the number of elements on the stack.
+
+@ ( -- i )
+Returns the number of elements the last builtin word returned, or after `loop` or `#loop` the change in stack depth the loop caused.
+
+clear ( -- )
+Discards all elements on the stack.
+
+
+sort ... ; ( s... c -- s... c )
+Compares the elements using the code block terminated by `;` and orders them accordingly.
+The code should take two elements and return a flag.
+If the flag is true, the first element will come before the second.
A => doc/words/stacks +53 -0
@@ 1,53 @@
+Additional Stacks are useful to collect values and to hold values to work through.
+
+
+
+stack <name> ( -- )
+Creates a new Stack.
+
+as <Stack name> ( s... c -- )
+Sets the Stack to the elements.
+
+<Stack name> ( -- s... c )
+Returns copies of the elements of the Stack.
+
+of <Stack name> ( i -- s )
+Returns the element at i of the Stack, or throws an error if none.
+
+of? <Stack name> ( i -- s 1 | 0 )
+Returns the element at i of the Stack and 1, or just 0 if none.
+
+
+push <Stack name> ( s -- )
+Sends s to top of the Stack.
+
+#push <Stack name> ( s... c -- ) ( -- s... )
+Sends the elements to top of the Stack.
+
+
+pop <Stack name> ( -- s )
+Returns an element taken from the top of the Stack, or an empty string if none.
+
+pop? <Stack name> ( -- s 1 | 0 )
+Returns an element taken from the top of the Stack and 1, or just 0 if none.
+
+#pop <Stack name> ( c -- s... c ) ( s... -- )
+Takes c elements from the top of the Stack, or as many as there are.
+
+
+POP <Stack name> ( -- s )
+Returns an element taken from the bottom of the Stack, or an empty string if none.
+
+POP? <Stack name> ( -- s 1 | 0 )
+Returns an element taken from the bottom of the Stack and 1, or just 0 if none.
+
+#POP <Stack name> ( c -- s... c )
+Takes c elements from the bottom of the Stack, or as many as there are.
+
+
+tot <Stack name> ( -- c )
+Returns the number of elements in the Stack.
+
+in <Stack name> ( s -- f )
+Returns whether the Stack has an element of s.
+If it does, `where` will give the position of the first occurrence, else -1.
A => doc/words/state +31 -0
@@ 1,31 @@
+There is a special Mapping called GekValue of values used by some builtin words.
+It lets you define macros that set `apologize` and the like.
+
+The keys are:
+ "@"
+ "apology"
+ "match"
+ "where"
+ "subbed"
+ "rc"
+
+
+
+match ( -- s )
+Returns the text matched by `meets`.
+
+where ( -- i )
+Returns the index set by `says`, `meets` and `within`.
+
+rc ( -- n )
+Returns the return code set by one the of the shell words or `await`.
+
+
+builtins ( -- s... c )
+Returns the names of the builtin words.
+
+mappings ( -- s... c )
+Returns the names of the Mappings, including the special Mappings.
+
+processes ( -- s... c )
+Returns the names of the Processes.
A => doc/words/string +290 -0
@@ 1,290 @@
+All values in Gek are strings but these words do string operations in particular.
+Strings are sequences of bytes often taken to represent UTF-8 text.
+
+
+
+nl ( -- s )
+Returns a string that's just a newline.
+
+tab ( -- s )
+Returns a string that's just a tab.
+
+
+is ( s1 s2 -- f )
+Returns whether s1 and s2 are the same.
+
+aint ( s1 s2 -- f )
+Returns whether s1 and s2 are different.
+
+starts ( s1 s2 -- f )
+Returns whether s1 starts with s2.
+
+ends ( s1 s2 -- f )
+Returns whether s1 ends with s2.
+
+cmp ( s1 s2 -- -1 | 0 | 1 )
+Returns a number comparing the two strings lexigraphically.
+Returns 0 if s1 = s2, -1 if s1 < s2, 1 if s1 > s2.
+
+insensitively ( s1 s2 -- s3 s4 )
+Returns s1 and s2 casefolded for case-insensitive comparison.
+
+
+says ( s1 s2 -- f )
+Returns whether s1 contains an occurrence of s2.
+If it does, `where` will give the position of the first occurrence, else -1.
+
+#says ( s1 s2 i -- f )
+Returns whether s1 contains enough occurrences of s2 to refer to an occurrence by i.
+If it does, `where` will give the position of that occurrence, else -1.
+
+sayings ( s1 s2 -- c )
+Returns the number of occurrences of s2 in s1.
+
+meets ( s re -- f )
+Returns whether s matches an occurrence of the regular expression re.
+If it does, `match` will give the matched text of the first occurrence, and `where` its position.
+If it doesn't, `match` will give an empty string, and `where` -1.
+
+#meets ( s re i -- f )
+Returns whether s matches enough occurrences of the regular expression re to refer to an occurrence by i.
+If it does, `match` will give the matched text of that occurrence, and `where` its position.
+If it doesn't, `match` will give an empty string, and `where` -1.
+
+meetings ( s re -- c )
+Returns the number of matches of the regular expression re in s.
+
+
+
+len ( s -- c )
+Returns the number of characters in the string.
+Or more correctly, it returns the number of Unicode code points in the string.
+
+lower ( s -- s )
+Returns the string lowercase.
+
+upper ( s -- s )
+Returns the string uppercase.
+
+title ( s -- s )
+Returns the string lowercase with the first letter of each word uppercase.
+Or more correctly, the first letter of each word mapped to their Unicode title case.
+
+scale ( s i -- s )
+Returns a string repeated i times and reversed if i is negative.
+
+unprefix ( s1 s2 -- s3 )
+Returns s1 with the prefix s2 removed if present.
+
+unsuffix ( s1 s2 -- s3 )
+Returns s1 with the suffix s2 removed if present.
+
+strip ( s1 -- s2 )
+Returns s1 with leading and trailing whitespace removed.
+
+lstrip ( s1 -- s2 )
+Returns s1 with leading whitespace removed.
+
+rstrip ( s1 -- s2 )
+Returns s1 with trailing whitespace removed.
+
+$strip ( s1 s2 -- s3 )
+Returns s1 with any/all leading and trailing characters of s2 removed.
+
+$lstrip ( s1 s2 -- s3 )
+Returns s1 with any/all leading characters of s2 removed.
+
+$rstrip ( s1 s2 -- s3 )
+Returns s1 with any/all trailing characters of s2 removed.
+
+justify ( s i -- s )
+Returns a right/left-justified string padded with spaces to length i.
+If i is positive it's right-justified else left-justified.
+
+$justify ( s1 i s2 -- s3 )
+Same as `justify` except padding is done with s2 repeated.
+
+center ( s i -- s )
+Returns a centered string padded with spaces to length i.
+If i is positive the left side may be padded one less than the right side, else the other way round.
+
+$center ( s1 i s2 -- s3 )
+Same as `center` except padding is done with s2 repeated.
+
+
+
+cut ( s1 i -- s2 )
+Returns s1 with i characters removed, or an empty string.
+If i is positive the characters are removed from the start else from the end.
+
+CUT ( s1 i -- s2 )
+Returns s1 with all but i characters removed, or an empty string.
+If i is positive the characters remain from the start else from the end.
+
+chip ( s1 i -- s2 s3 )
+Returns s1 split after i characters, from the start if i is positive else from the end.
+Splits off an empty string if i is out of range.
+
+snap ( s1 -- s2 s3 )
+Returns s1 split at the first occurrence of whitespace.
+If there is no occurrence, it returns s1 and an empty string.
+
+snap? ( s1 -- s2 s3 1 | s1 0 )
+Returns s1 split at the first occurrence of whitespace and 1, or s1 and 0 if there is no occurrence.
+
+$snap ( s1 s2 -- s3 s4 )
+Returns s1 split at the first occurrence of s2.
+If there is no occurrence, it returns s1 and an empty string.
+
+rsnap ( s1 -- s2 s3 )
+Returns s1 split at the last occurrence of whitespace.
+If there is no occurrence, it returns an empty string and s1.
+
+$snap? ( s1 s2 -- s3 s4 1 | s1 0 )
+Combination of `$snap` and `snap?`.
+rsnap? ( s1 -- s2 s3 1 | s1 0 )
+Combination of `rsnap` and `snap?`.
+$rsnap ( s1 s2 -- s3 s4 )
+Combination of `rsnap` and `$snap`.
+$rsnap? ( s1 s2 -- s3 s4 1 | s1 0 )
+Combination of `rsnap`, `$snap` and `snap?`.
+
+
+
+, ( s1 s2 -- s3 )
+Returns the two strings joined together.
+
+#, ( s... c -- s1 )
+Returns the strings of the list joined together.
+
+join ( s... c -- s )
+Returns the strings of the list joined together with a space between each.
+
+$join ( s... i s1 -- s2 )
+Returns the strings of the list joined together with s1 between each.
+If the list count i is negative, s1 is not inserted between the first and second.
+
+split ( s1 -- s... c )
+Returns a list of s1 split at whitespace.
+If s1 is just whitespace, it returns 0.
+
+#split ( s1 i -- s... c )
+Returns a list of s1 split at whitespace, maxing out at i strings.
+If i is positive splits are done starting from the start of the string else from the end of the string.
+If s1 is just whitespace, it returns 0.
+
+$split ( s1 s2 -- s... c )
+Returns a list of s1 split at occurrences of s2.
+If s2 is empty, it splits between each character.
+If there are no occurrences, it returns s1 and 1.
+
+#$split ( s1 i s2 -- s... c )
+Returns a list of s1 split at occurrences of s2, maxing out at i strings.
+If i is positive splits are done starting from the start of the string else from the end of the string.
+If s2 is empty, it splits between each character.
+If there are no occurrences and i is non-zero, it returns s1 and 1.
+
+quotejoin ( s... i -- s )
+Returns the strings surrounded with " and joined together with a space between each.
+Any instances of " in the strings are escaped with a backslash.
+If the list count i is negative, the first isn't quoted but a space is still inserted between the first and second.
+
+quotesplit ( s1 -- s... c )
+Returns a list of the substrings that are surrounded with ".
+Instances of " escaped with a backslash are ignored.
+
+
+extract ( s1 re -- s... c )
+Returns the matches of the regular expression re in s1.
+If re has capturing groups, just their matches are returned.
+
+#extract ( s1 re c -- s... c )
+Same as `extract` but returns at most c strings.
+
+punch ( s1 re -- s... c )
+Returns the non-matches of the regular expression re in s1.
+If re has capturing groups, their matches are also returned.
+
+#punch ( s1 re c -- s... c )
+Same as `punch` but makes at most c punches.
+That is, ignoring the capturing group matches, it splits s1 at most c times.
+
+
+sub ( s1 re s2 -- s3 )
+Returns s1 with the first match of the regular expression re replaced using s2.
+In the replacement text s2, the sequence ${n}, where n is a number, denotes the text that matched the nth (unnamed) capturing group.
+${0} denotes the entire match text.
+${name} denotes the text that matched the named capturing group with the (?P<name>...) syntax.
+$$ denotes a literal $.
+The braces are not mandatory but names are taken to be as long as possible: $1x is equivalent to ${1x} not ${1}x, and $10 is equivalent to ${10}, not ${1}0.
+Names are non-empty sequences of letters, digits, and underscores.
+
+gsub ( s1 re s2 -- s3 )
+Same as `sub` but replaces all matches.
+
+#sub ( s1 re s2 i -- s3 )
+Same as `sub` but replaces the match specified by the index i if there is one.
+
+SUB ( s1 s2 s3 -- s3 )
+Returns s1 with the first occurrence of s2 replaced with s3.
+
+GSUB ( s1 s2 s3 -- s3 )
+Same as `SUB` but replaces all matches.
+
+#SUB ( s1 s2 s3 n -- s3 )
+Same as `sub` but replaces the occurrence specified by the index i if there is one.
+
+subbed ( -- n )
+Returns the number of replacements performed by the last `sub`/`gsub`/`#sub`/`SUB`/`GSUB`/`#SUB`.
+
+rephrase ( s1 s... c -- s )
+Returns s1 having replaced occurrences of even-indexed strings of the list with the string after them.
+Said another way, the list is pairs of substrings to replace and their replacement.
+
+reword ( s1 s... c -- s )
+Same as `rephrase` but occurrences must be whole words.
+
+
+
+valid? ( re -- f )
+Returns whether the regular expression is valid, setting `apologize`.
+
+escape ( s -- re )
+Returns s with all regular expression metacharacters escaped.
+Said another way, it returns a regular expression to match the literal text s.
+
+
+
+es ( s1 -- s2 )
+Returns s1 with backslash escape sequences substituted.
+Throws an error if there's an invalid or unrecognized sequence after a backslash.
+
+There are four ways to encode arbitrary values:
+\x followed by two hexadecimal digits.
+\ followed by three octal digits, representing no more than 255.
+\u followed by four hexadecimal digits, representing a Unicode code point.
+\U followed by eight hexadecimal digits, representing a Unicode code point.
+
+Some one-character escapes represent special values:
+\a U+0007 alert or bell
+\b U+0008 backspace
+\f U+000C form feed
+\n U+000A line feed or newline
+\r U+000D carriage return
+\t U+0009 horizontal tab
+\v U+000B vertical tab
+\\ U+005C backslash
+
+
+es? ( s1 -- s2 1 | 0 )
+Same as `es` but returns whether successful instead of throwing an error.
+Sets `apologize`.
+
+ord ( s -- c )
+Returns the Unicode code point value for the first Unicode code point of the string.
+So pretty much the number that maps to the first character of the string.
+Throws an error if s is empty.
+
+chr ( c -- s )
+Returns a string of the Unicode code point.
+So pretty much the character the number maps to.
A => doc/words/system +86 -0
@@ 1,86 @@
+The environment variable GEKSHELL sets the shell, defaulting to /bin/sh.
+
+There is a special Mapping for environment variables called Env.
+Assignments to it change the environment for gek and programs spawned by shell words and Processes.
+
+
+
+shell ( s -- )
+Executes the command-line s through the shell and waits for it to finish.
+Any stdout or stderr output prints while it runs.
+`rc` will give the return code.
+
+$shell ( s1 s2 -- )
+Same as `shell` but giving s1 plus a newline as input.
+
+shellout ( s1 -- s2 )
+Executes the command-line s through the shell, waits for it to finish, and returns the stdout output having removed any trailing newline and anything after.
+Any stderr output prints while it runs.
+`rc` will give the return code.
+
+$shellout ( s1 s2 -- s3 )
+Same as `shellout` but giving s1 plus a newline as input.
+
+SHELLOUT ( s1 -- s2 )
+Same as `shellout` but returns the stdout output as is.
+
+$SHELLOUT ( s1 s2 -- s3 )
+Same as `SHELLOUT` but giving s1 plus a newline as input.
+
+
+shelljoin ( s... i -- s )
+Returns the strings shell-escaped and joined together with a space between each.
+If the list count i is negative, the first string is not shell-escaped.
+Only suitable for Unix shells like sh.
+
+shellsplit ( s1 -- s... c )
+Returns s1 split using shell syntax.
+Only suitable for Unix shells like sh.
+
+
+sleep ( n -- )
+Pauses the program for n seconds.
+
+code ( i -- )
+Sets the return code the program will terminate with.
+Conventionally, zero indicates success, non-zero indicates an error.
+Between 0 and 125 inclusive is recommended for portability.
+
+exit ( -- )
+Stops executing the current event or program cycle, and stops processing input files for good.
+So it pretty much runs any E events and terminates the program.
+
+EXIT ( -- )
+Terminates the program immediately.
+
+
+pwd ( -- s )
+Returns the current working directory.
+
+cd ( s -- )
+Changes the current working directory to the filepath s, or throws an error.
+
+cd? ( s -- f )
+Tries to change the current working directory to the filepath s.
+Returns whether successful, setting `apologize` if not.
+
+
+stdin ( -- s )
+Returns the filepath of stdin.
+
+stdout ( -- s )
+Returns the filepath of stdout.
+
+stderr ( -- s )
+Returns the filepath of stderr.
+
+os ( -- s )
+Returns the name of the Operating System in lowercase.
+Perhaps "linux" or "windows".
+
+pathsep ( -- s )
+Returns the path separator the Operating System uses, "/" or "\".
+
+pathlistsep ( -- s )
+Returns the path list separator the Operating System uses.
+Perhaps ":" or ";".
A => doc/words/variables +21 -0
@@ 1,21 @@
+Variables let you assign a value to a name by which you can retrieve copies of it.
+
+There is a special Mapping of the Variables called Variable.
+Assignments to it assign Variables indirectly.
+
+
+
+set <name> ( s -- )
+Assigns the value s to the Variable.
+If the Variable doesn't exist it is created.
+
++set <name> ( n -- )
+Increments the value of the Variable by n.
+If the Variable doesn't exist it is created with the value n.
+
+-set <name> ( n -- )
+Decrement the value of the Variable by n.
+If the Variable doesn't exist it is created with the value n.
+
+<Variable name> ( -- s )
+Returns the value of the Variable.
A => gek.1 +74 -0
@@ 1,74 @@
+.TH GEK 1
+.SH NAME
+gek \- stack-based stream editor and scripting language
+.SH SYNOPSIS
+.B gek
+[\fB\-b\fR]
+[\fB\-i\fR]
+[ [\fB-e\fR] \fI'prog'\fR | \fB\-f\fR \fIprogfile\fR | \fB\-F\fR \fIprogfile\fR ]
+.\" [\fB\-\-bits\fR \fIBITS\fR]
+.IR file ...
+.SH DESCRIPTION
+\fBgek\fR is an interpreter for the Gek programming language.
+The Gek language is useful for manipulating files and program output, and conducting programs.
+Gek programs often perform an operation on each line of input, making them applicable to ongoing pipelines as well as input with a definite end.
+
+Programs can be entered on the command line, or read from a file with the \fB\-f\fR option.
+Input is read from the list of files passed on the command line, or from stdin if no files are passed.
+
+Gek programs consist of "words" that each do an action and string literals.
+These are executed in order for each record of input, which is by default a line.
+Words take their arguments from "the stack" and put their return values onto it.
+String literals put themselves on the stack.
+All values are strings but words might take their arguments to represent text, a number or regular expression.
+
+There is a comprehensive tutorial at: https://geklang.org/tutorial.html
+\fBgek\fR also comes with \fBgekdoc\fR that provides documentation on words and topics.
+.SH OPTIONS
+.TP
+.B \-b
+Buffer the output.
+.TP
+.BI \-e " program-text"
+Read the program text from \fIprogram-text\fR, instead of from the first command line argument.
+Multiple \fB-e\fR options may be used.
+.TP
+.BI \-f " program-file"
+Read the program text from \fIprogram-file\fR, instead of from the first command line argument.
+Multiple \fB-f\fR options may be used.
+.TP
+.BI \-F " program-file"
+Same as \fB-f\fR but following arguments are treated as positional arguments.
+Useful for shebang lines to stop the user passing options or if your program has it's own options.
+.TP
+.B \-i
+Edit the input files in place.
+.P
+If the options \fB-e\fR, \fB-f\fR or \fB-F\fR are used together or multiple times, the program text is the concatenation of them with newlines in between.
+.SH EXAMPLES
+.nf
+Sum each row of a file:
+.RS
+gek 'fields sum $' myfile
+.RE
+
+Join lines of stdin which end with a backslash:
+.RS
+gek -b 'forever r "\\" ends while r -1 cut ! bump loop r $'
+.RE
+
+Reinvent the tail command:
+.RS
+#! /bin/gek -F
+B
+ mapping Opt
+ 10 "n" map Opt
+ Args "n:" optparse Opt as Args
+;
+r depth "n" Opt keep
+FE depth #$ ;
+.RE
+.fi
+.SH SEE ALSO
+.BR sed (1),
+.BR awk (1)
A => gekdoc +55 -0
@@ 1,55 @@
+#! /usr/bin/env -S gek -F
+
+: usage
+ "Usage: gekdoc [-h] [--help] ['word'] [':topic'] ...
+
+gekdoc provides documentation on words and topics of the Gek programming language.
+You can pass a word as an argument like so:
+ gekdoc upper
+Or a topic preceded by a colon like so:
+ gekdoc :printing
+
+The topics are:" $
+ "chat/topics" READ $
+ EXIT
+;
+
+B
+ "/usr/share/doc/gek" cd
+
+ mapping Opt
+ Args "h? help?" optparse? Opt 0 = if apologize 1 code usage then as Args
+ tot Args 0 = "h" Opt or "help" Opt or if usage then
+
+ stack Patterns
+ Args foreach
+ "chat/" x ":" $snap nip , READ? if
+ $
+ otherwise "words/" x ":" $snap nip , READ? if
+ $
+ otherwise x ":" starts x ":" aint and if
+ fret x . "is not a topic" $ chill
+ otherwise x len if
+ "(?i)^#?\$?" x escape "\??\s.*\(.*--.*\)$" 3 #, push Patterns
+ then
+ loop
+ POP? Patterns if set Pat else EXIT then
+
+ stack WordFiles "words" entries as WordFiles WordFiles as Args
+
+ withhold
+;
+
+# If the current line meets the pattern, print lines until the next stack effect, and repeat.
+forever r Pat meets while
+ forever r $ bump? while r "\(.*--.*\)$" meets if nl ! break then loop
+loop
+
+FE
+ # If finished the files, go through them again looking for the next pattern.
+ tot Args 1 = if
+ POP? Patterns if set Pat WordFiles as Args then
+ then
+;
+
+E withheld "\n{2,}" nl nl , gsub ! ;
A => go.mod +9 -0
@@ 1,9 @@
+module git.sr.ht/~geb/gek
+
+go 1.18
+
+require (
+ git.sr.ht/~geb/opt v0.0.0-20220627180516-52214b5b84a1
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
+ golang.org/x/text v0.3.7
+)
A => go.sum +6 -0
@@ 1,6 @@
+git.sr.ht/~geb/opt v0.0.0-20220627180516-52214b5b84a1 h1:bmje0IdPzrY5nX6fAx8KuHP5G8EP4XMedMFcrssfJXc=
+git.sr.ht/~geb/opt v0.0.0-20220627180516-52214b5b84a1/go.mod h1:T5QFtG9s8i/kW5pDVCke6Mt2WmElJCIfTL1HMdpP7Rk=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
A => install.sh +11 -0
@@ 1,11 @@
+#! /bin/sh
+# Compiles gek to /usr/local/bin which is usually in $PATH.
+
+cd "$(dirname "$0")" || exit
+! go version > /dev/null && echo "you need to install go (the Go programming language)" && exit 1
+go mod tidy && go build -o /dev/stdout ./src | sudo tee /usr/local/bin/gek > /dev/null || exit
+sudo chmod +x /usr/local/bin/gek || exit
+sudo cp gekdoc /usr/local/bin/gekdoc
+sudo cp gek.1 /usr/share/man/man1/gek.1
+sudo rm -rf /usr/share/doc/gek
+sudo cp -r doc /usr/share/doc/gek
A => src/FILES +18 -0
@@ 1,18 @@
+test - runs tests
+
+setup.go - parsing the command-line arguments
+builtins.go - big map of the words
+main.go - global variables and the mainloop
+
+flow.go - control flow stack
+stack.go - the stack
+
+parse.go - tokenizing, and parsing literals
+execute.go - executing tokens
+trace.go - execution stack
+
+errors.go - printing tracebacks
+input.go - object for going through the input files
+output.go - object for printing/buffering/redirecting user output
+
+Most of the rest implement words for builtins.go
A => src/builtins.go +1444 -0
@@ 1,1444 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strings"
+ "time"
+ "unicode"
+ "os"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+var PrivateValues = struct {
+ n int
+ N int
+ code int
+}{}
+var PublicValues = map[string]Element{
+ "@": Num(0),
+ "apology": Str(""),
+ "match": Str(""),
+ "where": Num(-1),
+ "subbed": Num(0),
+ "rc": Num(0),
+}
+const (
+ VariableMapName = "Variable"
+ MacroMapName = "Macro"
+ ValueMapName = "GekValue"
+ EnvMapName = "Env"
+)
+var Variables = map[string]Element{}
+var Mappings = map[string]map[string]Element{
+ VariableMapName: Variables,
+ ValueMapName: PublicValues,
+}
+var Processes = map[string]*Process{}
+var Macros = map[string][][]string{}
+
+func forget(word string) {
+ delete(Stacks, word)
+ delete(Variables, word)
+ delete(Mappings, word)
+ delete(Macros, word)
+ delete(Processes, word)
+}
+
+
+func excuse(err error) {
+ if err == nil {
+ PublicValues["apology"] = Str("")
+ } else {
+ PublicValues["apology"] = Str(err.Error())
+ }
+}
+
+func yankProcess() *Process {
+ if !next() { SyntaxFatal("expected process name") }
+ p, in := Processes[tok()]
+ if !in { TypeFatal(tok() + " is not a process name") }
+ return p
+}
+
+func expectStackName() {
+ if !next() { SyntaxFatal("expected stack name") }
+ _, in := Stacks[tok()]
+ if !in { TypeFatal(tok() + " is not a stack name") }
+}
+
+func MappingAssign(mapname, key string, value Element) {
+ if m := Mappings[mapname]; m != nil {
+ m[key] = value
+ } else if mapname == EnvMapName {
+ err := os.Setenv(key, string(value.Str()))
+ if err != nil { Fatal(err.Error()) }
+ } else if mapname == MacroMapName {
+ Macros[key] = TokenizeString(string(value.Str()))
+ } else {
+ TypeFatal(mapname + " is not a mapping name")
+ }
+}
+func MappingUnassign(mapname, key string) {
+ if m := Mappings[mapname]; m != nil {
+ delete(m, key)
+ } else if mapname == EnvMapName {
+ err := os.Unsetenv(key)
+ if err != nil { Fatal(err.Error()) }
+ } else if mapname == MacroMapName {
+ delete(Macros, key)
+ } else {
+ TypeFatal(mapname + " is not a mapping name")
+ }
+}
+func MappingExists(mapname string) bool {
+ return Mappings[mapname] != nil || mapname == EnvMapName || mapname == MacroMapName
+}
+func MappingGet(mapname, key string) (Element, bool) {
+ if m := Mappings[mapname]; m != nil {
+ e, in := m[key]
+ if !in { e = Str("") }
+ return e, in
+ } else if mapname == EnvMapName {
+ s, in := os.LookupEnv(key)
+ return Str(s), in
+ } else if mapname == MacroMapName {
+ tokenlines, in := Macros[key]
+ return Str(Untokenize(tokenlines)), in
+ }
+ panic("unreachable")
+}
+
+
+var Builtins map[string]func()
+func init() {
+ Builtins = map[string]func() {
+ "#!": func() { endline() },
+ "#": func() { endline() },
+ "?": func() {
+ fmt.Fprintf(os.Stderr, "[%d]", len(Stack()))
+ for _, e := range Stack() { fmt.Fprint(os.Stderr, " " + quote(string(e.Str()))) }
+ fmt.Fprint(os.Stderr, "\n")
+ PushCount = int(PublicValues["@"].Num())
+ },
+ "fileless?": func() {
+ excuse(In.Err())
+ pushBool(In.File() == nil)
+ },
+ "file": func() { pushString(In.Name()) },
+ "r": func() { pushString(In.Record()) },
+ "R": func() { In.SetRecord(popString()) },
+ "n": func() { pushInt(PrivateValues.n) },
+ "N": func() { pushInt(PrivateValues.N) },
+ "f": func() { pushString(In.Field(popInt())) },
+ "F": func() { i := popInt(); In.SetField(i, popString()) },
+ "fields": func() { pushStringList(In.Fields()) },
+ "c": func() { pushInt(len(In.Fields())) },
+ "d": func() { pushString(In.Delim(popInt())) },
+ "D": func() { i := popInt(); In.SetDelim(i, popString()) },
+ "delims": func() { pushStringList(In.Delims()) },
+ "sep": func() { pushString(In.Sep()) },
+ "record": func() { In.RecordPattern(popString()) },
+ "separator": func() { In.SeparatorPattern(popString()) },
+ "field": func() { In.FieldPattern(popString()) },
+ "delim": func() { In.DelimPattern(popString()) },
+ "bump": func() {
+ if In.File() == nil { In.NextFile() }
+ In.Scan()
+ },
+ "bump?": func() {
+ if In.File() == nil { In.NextFile() }
+ pushBool(In.Scan())
+ },
+ "next": func() {
+ Trace = []TraceFrame{}
+ ClearFlowStack()
+ },
+ "nextfile": func() {
+ Trace = []TraceFrame{}
+ ClearFlowStack()
+ RedoFB = true
+ In.Bail()
+ },
+ "drop": func() { pop() },
+ "#drop": func() { popList() },
+ "nip": func() { a := pop(); pop(); push(a) },
+ "swap": func() { b := pop(); a := pop(); push(b); push(a) },
+ "dup": func() { a := pop(); push(a); push(a) },
+ "#dup": func() { elems := popList(); pushElements(elems); pushElements(elems) },
+ "over": func() { b := pop(); a := pop(); push(a); push(b); push(a) },
+ "rot": func() { c := pop(); b := pop(); a := pop(); push(b); push(c); push(a) },
+ "#rot": func() {
+ i := popInt()
+ elems := popList()
+ i = mod(i, len(elems))
+ pushElements(elems[i:])
+ pushElements(elems[:i])
+ },
+ "more": func() {
+ n := popCount()
+ if n > 0 {
+ e := pop()
+ push(e)
+ for ; n > 0; n-- { push(e) }
+ }
+ },
+ "relist": func() { elems := popList(); pushList(elems); pushList(elems) },
+ "add": func() {
+ l2 := popList()
+ l1 := popList()
+ pushElements(l1)
+ pushElements(l2)
+ pushInt(len(l1) + len(l2))
+ },
+ "exchange": func() {
+ l2 := popList()
+ l1 := popList()
+ pushList(l2)
+ pushList(l1)
+ },
+ "reverse": func() {
+ elems := popList()
+ for i := len(elems)-1; i >= 0; i-- { push(elems[i]) }
+ },
+ "keep": func() {
+ n := popInt()
+ elems := popList()
+ if n > 0 {
+ pushElements(elems[max(0, len(elems)-n):])
+ } else {
+ pushElements(elems[:min(len(elems), -n)])
+ }
+ },
+ "KEEP": func() {
+ n := popInt()
+ elems := popList()
+ if n > 0 {
+ pushElements(elems[:max(0, len(elems)-n)])
+ } else {
+ pushElements(elems[min(len(elems), -n):])
+ }
+ },
+ "kill": func() {
+ i := popInt()
+ elems := popList()
+ if i < 0 { i += len(elems) }
+ if i >= 0 && i < len(elems) {
+ pushElements(elems[:len(elems)-1-i])
+ pushElements(elems[len(elems)-i:])
+ } else {
+ pushElements(elems)
+ }
+ },
+ "#kill": func() {
+ indices := popIntList()
+ pushElements(kill(popList(), indices))
+ },
+ "spare": func() {
+ i := popInt()
+ elems := popList()
+ if i < 0 { i += len(elems) }
+ if i >= 0 && i < len(elems) {
+ push(elems[len(elems)-1-i])
+ } else {
+ pushString("")
+ }
+ },
+ "compose": func() {
+ indices := popIntList()
+ pushElements(compose(popList(), indices))
+ },
+ "pad": func() {
+ goal := popInt()
+ n := popCount()
+ if goal >= 0 {
+ for i := 0; i < goal-n; i++ { pushString("") }
+ } else {
+ elems := popElements(n)
+ for i := 0; i < -goal-n; i++ { pushString("") }
+ pushElements(elems)
+ }
+ },
+ "$pad": func() {
+ s := popString()
+ goal := popInt()
+ n := popCount()
+ if goal >= 0 {
+ for i := 0; i < goal-n; i++ { pushString(s) }
+ } else {
+ elems := popElements(n)
+ for i := 0; i < -goal-n; i++ { pushString(s) }
+ pushElements(elems)
+ }
+ },
+ "lace": func() {
+ l2 := popList()
+ l1 := popList()
+ for i := 0; i < max(len(l1), len(l2)); i++ {
+ if i < len(l1) { push(l1[i]) } else { pushString("") }
+ if i < len(l2) { push(l2[i]) } else { pushString("") }
+ }
+ },
+ "unlace": func() {
+ n := popCount()
+ if n % 2 != 0 { ValueFatal("unlace expects an even count but received an odd count") }
+ elems := popElements(n)
+ for i := 0; i < n; i += 2 { push(elems[i]) }
+ for i := 1; i < n; i += 2 { push(elems[i]) }
+ },
+ "tailor": func() {
+ l2 := popList(); l1 := popList()
+ pushList(l1)
+ pushList(l2[:min(len(l1), len(l2))])
+ },
+ "within": func() {
+ elems := popStringList()
+ needle := popString()
+ index := -1
+ for i, e := range elems {
+ if e == needle { index = i; break }
+ }
+ PublicValues["where"] = Num(index)
+ pushBool(index != -1)
+ },
+ "#within": func() {
+ hash := popInt()
+ elems := popStringList()
+ needle := popString()
+ where := -1
+ if hash >= 0 {
+ for i := 0; i < len(elems); i++ {
+ if elems[i] == needle {
+ hash--
+ if hash == -1 { where = i; break }
+ }
+ }
+ } else {
+ for i := len(elems)-1; i >= 0; i-- {
+ if elems[i] == needle {
+ hash++
+ if hash == 0 { where = i; break }
+ }
+ }
+ }
+ PublicValues["where"] = Num(where)
+ pushBool(where != -1)
+ },
+ "among": func() {
+ elems := popStringList()
+ needle := popString()
+ count := 0
+ for _, e := range elems {
+ if e == needle { count++ }
+ }
+ pushInt(count)
+ },
+ "depth": func() { pushInt(len(Stack())) },
+ "@": func() { push(PublicValues["@"]) },
+ "clear": func() { ClearStack() },
+ "sort": func() {
+ elems := popList()
+ by := getBlock()
+ Sort(elems, by)
+ pushElements(elems)
+ // The execution of by messes with PushCount.
+ // And it's undefined behavior if by doesn't just return one element.
+ PushCount = len(elems)
+ },
+ "!": func() { Out.Print(popString()) },
+ ".": func() { Out.Print(popString() + " ") },
+ "$": func() { Out.Println(popString()) },
+ "#$": func() { for _, s := range popStringList() { Out.Println(s) } },
+ "?.": func() { if popBool() { Out.Print(popString() + " ") } else { popString() } },
+ "?$": func() { if popBool() { Out.Println(popString()) } else { popString() } },
+ "inform": func() {
+ s := popString()
+ if len(s) != 0 { fmt.Fprintln(os.Stderr, s) }
+ },
+ "to": func() { throw(Out.To(popString())) },
+ "to?": func() {
+ err := Out.To(popString())
+ excuse(err)
+ pushBool(err == nil)
+ },
+ "fret": func() { Out.ToStderr() },
+ "chill": func() { Out.ToStdout() },
+ "withhold": func() { Out.Withhold() },
+ "withheld": func() {
+ s, ok := Out.PopWithheld()
+ if !ok || (Setup.Inplace && Out.WithheldNum() == 0) {
+ Fatal("withheld without corresponding withhold")
+ }
+ pushString(s)
+ },
+ "buffer": func() { Out.Buffer() },
+ "unbuffer": func() { Out.Unbuffer() },
+ "flush": func() { Out.Flush() },
+ "nl": func() { pushString("\n") },
+ "tab": func() { pushString("\t") },
+ "is": func() { pushBool(popString() == popString()) },
+ "aint": func() { pushBool(popString() != popString()) },
+ "starts": func() { p := popString(); pushBool(strings.HasPrefix(popString(), p)) },
+ "ends": func() { s := popString(); pushBool(strings.HasSuffix(popString(), s)) },
+ "cmp": func() { pushInt(-strings.Compare(popString(), popString())) },
+ "insensitively": func() {
+ c := cases.Fold()
+ s2 := popString()
+ s1 := popString()
+ pushString(c.String(s1))
+ pushString(c.String(s2))
+ },
+ "says": func() {
+ needle := popString()
+ s := popString()
+ i := strings.Index(s, needle)
+ PublicValues["where"] = Num(i)
+ pushBool(i != -1)
+ },
+ "#says": func() {
+ at := popInt()
+ needle := popString()
+ s := popString()
+ if at < 0 { at += strings.Count(s, needle) }
+ if at < 0 {
+ PublicValues["where"] = Num(-1)
+ pushInt(0)
+ return
+ }
+
+ s2 := s
+ for ; at >= 0; at-- {
+ i := strings.Index(s2, needle)
+ if i == -1 { pushInt(0); return }
+ s2 = s2[i+len(needle):]
+ }
+ PublicValues["where"] = Num(len(s) - len(s2) - len(needle))
+ pushInt(1)
+ },
+ "sayings": func() {
+ s2 := popString(); s1 := popString()
+ pushInt(strings.Count(s1, s2))
+ },
+ "meets": func() {
+ pat := popString()
+ s := popString()
+ match, i := Meets(s, pat)
+ PublicValues["match"] = Str(match)
+ PublicValues["where"] = Num(i)
+ pushBool(i != -1)
+ },
+ "#meets": func() {
+ at := popInt()
+ pat := popString()
+ s := popString()
+ match, i := Nmeets(s, pat, at)
+ PublicValues["match"] = Str(match)
+ PublicValues["where"] = Num(i)
+ pushBool(i != -1)
+ },
+ "meetings": func() {
+ pat := popString(); s := popString()
+ pushInt(len(mustCompile(pat).FindAllString(s, -1)))
+ },
+ "len": func() { pushInt(len([]rune(popString()))) },
+ "lower": func() { pushString(strings.ToLower(popString())) },
+ "upper": func() { pushString(strings.ToUpper(popString())) },
+ "title": func() { pushString(cases.Title(language.Und).String(popString())) },
+ "scale": func() { n := popInt(); s := popString(); pushString(Scale(s, n)) },
+ "unprefix": func() { prefix := popString(); pushString(strings.TrimPrefix(popString(), prefix)) },
+ "unsuffix": func() { suffix := popString(); pushString(strings.TrimSuffix(popString(), suffix)) },
+ "strip": func() { pushString(strings.TrimSpace(popString())) },
+ "lstrip": func() { pushString(strings_TrimLeftSpace(popString())) },
+ "rstrip": func() { pushString(strings_TrimRightSpace(popString())) },
+ "$strip": func() {
+ cutset := popString()
+ pushString(strings.Trim(popString(), cutset))
+ },
+ "$lstrip": func() {
+ cutset := popString()
+ pushString(strings.TrimLeft(popString(), cutset))
+ },
+ "$rstrip": func() {
+ cutset := popString()
+ pushString(strings.TrimRight(popString(), cutset))
+ },
+ "justify": func() {
+ n := popInt()
+ s := popString()
+ if n >= 0 {
+ pushString(strings.Repeat(" ", max(0, n-len(s))) + s)
+ } else {
+ pushString(s + strings.Repeat(" ", max(0, n-len(s))))
+ }
+ },
+ "$justify": func() {
+ p := popString()
+ n := popInt()
+ s := popString()
+ margin := max(0, abs(n)-len(s))
+ padding := strings.Repeat(p, margin/len(p)) + p[:margin%len(p)]
+ if n >= 0 {
+ pushString(padding + s)
+ } else {
+ pushString(s + reverse(padding))
+ }
+ },
+ "center": func() {
+ n := popInt()
+ s := popString()
+ margin := max(0, abs(n) - len(s))
+ right := margin/2 + (margin & abs(n) & 1)
+ left := margin - right
+ if n < 0 { left, right = right, left }
+ pushString(strings.Repeat(" ", left) + s + strings.Repeat(" ", right))
+ },
+ "$center": func() {
+ p := popString()
+ n := popInt()
+ s := popString()
+ margin := max(0, abs(n) - len(s))
+ right := margin/2 + (margin & abs(n) & 1)
+ left := margin - right
+ if n < 0 { left, right = right, left }
+ pushString(fill(p, left) + s + fill(p, right))
+ },
+ "cut": func() {
+ n := popInt()
+ s := popString()
+ if n >= 0 {
+ pushString(s[min(len(s), n):])
+ } else {
+ pushString(s[:max(0, len(s)+n)])
+ }
+ },
+ "CUT": func() {
+ n := popInt()
+ s := popString()
+ if n >= 0 {
+ pushString(s[:min(len(s), n)])
+ } else {
+ pushString(s[max(0, len(s)+n):])
+ }
+ },
+ "chip": func() {
+ n := popInt()
+ s := popString()
+ if n < 0 { n += len(s) }
+ if n >= len(s) {
+ pushString(s)
+ pushString("")
+ } else if n < 0 {
+ pushString("")
+ pushString(s)
+ } else {
+ pushString(s[:n])
+ pushString(s[n:])
+ }
+ },
+ "snap": func() {
+ left, right, _ := strings_CutFunc(popString(), unicode.IsSpace)
+ pushString(left)
+ pushString(right)
+ },
+ "snap?": func() {
+ left, right, found := strings_CutFunc(popString(), unicode.IsSpace)
+ if found {
+ pushString(left)
+ pushString(right)
+ pushInt(1)
+ } else {
+ pushString(left)
+ pushInt(0)
+ }
+ },
+ "$snap": func() {
+ sep := popString()
+ s := popString()
+ left, right, _ := strings.Cut(s, sep)
+ pushString(left)
+ pushString(right)
+ },
+ "rsnap": func() {
+ left, right, _ := strings_CutRightFunc(popString(), unicode.IsSpace)
+ pushString(left)
+ pushString(right)
+ },
+ "$snap?": func() {
+ sep := popString()
+ s := popString()
+ left, right, found := strings.Cut(s, sep)
+ if found {
+ pushString(left)
+ pushString(right)
+ pushInt(1)
+ } else {
+ pushString(left)
+ pushInt(0)
+ }
+ },
+ "rsnap?": func() {
+ left, right, found := strings_CutRightFunc(popString(), unicode.IsSpace)
+ if found {
+ pushString(left)
+ pushString(right)
+ pushInt(1)
+ } else {
+ pushString(right)
+ pushInt(0)
+ }
+ },
+ "$rsnap": func() {
+ sep := popString()
+ s := popString()
+ left, right, _ := strings_CutRight(s, sep)
+ pushString(left)
+ pushString(right)
+ },
+ "$rsnap?": func() {
+ sep := popString()
+ s := popString()
+ left, right, found := strings_CutRight(s, sep)
+ if found {
+ pushString(left)
+ pushString(right)
+ pushInt(1)
+ } else {
+ pushString(right)
+ pushInt(0)
+ }
+ },
+ ",": func() { b := popString(); pushString(popString() + b) },
+ "#,": func() {
+ n := popCount()
+ pushString(strings.Join(popStrings(n), ""))
+ },
+ "join": func() {
+ pushString(strings.Join(popStringList(), " "))
+ },
+ "$join": func() {
+ sep := popString()
+ n := popInt()
+ if n >= 0 {
+ pushString(strings.Join(popStrings(n), sep))
+ } else {
+ strs := popStrings(-n)
+ pushString(strs[0] + strings.Join(strs[1:], sep))
+ }
+ },
+ "split": func() { strs := strings.Fields(popString()); for _, s := range strs { pushString(s) }; pushInt(len(strs)) },
+ "#split": func() { hash := popInt(); s := popString(); pushStringList(HashSplitSpace(s, hash)) },
+ "$split": func() { sep := popString(); pushStringList(strings.Split(popString(), sep)) },
+ "#$split": func() { sep := popString(); hash := popInt(); s := popString(); pushStringList(HashSplit(s, sep, hash)) },
+ "quotejoin": func() {
+ n := popInt()
+ if n >= 0 {
+ pushString(QuoteJoin(popStrings(n)))
+ } else {
+ strs := popStrings(-n)
+ pushString(QuoteAppend(strs))
+ }
+ },
+ "quotesplit": func() { pushStringList(QuoteSplit(popString())) },
+ "extract": func() {
+ pat := popString(); s := popString()
+ pushStringList(Extract(s, pat, -1))
+ },
+ "#extract": func() {
+ hash := popCount(); pat := popString(); s := popString()
+ pushStringList(Extract(s, pat, hash))
+ },
+ "punch": func() {
+ pat := popString(); s := popString()
+ pushStringList(Punch(s, pat, -1))
+ },
+ "#punch": func() {
+ hash := popCount(); pat := popString(); s := popString()
+ pushStringList(Punch(s, pat, hash))
+ },
+ "sub": func() {
+ repl := popString(); pat := popString(); s := popString()
+ s2, did := Sub(s, pat, repl)
+ if did { PublicValues["subbed"] = Num(1) } else { PublicValues["subbed"] = Num(0) }
+ pushString(s2)
+ },
+ "gsub": func() {
+ repl := popString(); pat := popString(); s := popString()
+ s2, subcount := Gsub(s, pat, repl)
+ PublicValues["subbed"] = Num(subcount)
+ pushString(s2)
+ },
+ "#sub": func() {
+ hash := popInt(); repl := popString(); pat := popString(); s := popString()
+ s2, did := Nsub(s, pat, repl, hash)
+ if did { PublicValues["subbed"] = Num(1) } else { PublicValues["subbed"] = Num(0) }
+ pushString(s2)
+ },
+ "SUB": func() {
+ that := popString(); this := popString(); s := popString()
+ after := strings.Replace(s, this, that, 1)
+ if after == s {
+ PublicValues["subbed"] = Num(0)
+ } else {
+ PublicValues["subbed"] = Num(1)
+ }
+ pushString(after)
+ },
+ "GSUB": func() {
+ that := popString(); this := popString(); s := popString()
+ PublicValues["subbed"] = Num(strings.Count(s, this))
+ pushString(strings.ReplaceAll(s, this, that))
+ },
+ "#SUB": func() {
+ i := popInt(); that := popString(); this := popString(); s := popString()
+ if i < 0 { i += strings.Count(s, this) }
+ if i >= 0 {
+ parts := strings.SplitN(s, this, i+2)
+ if len(parts) == i+2 {
+ PublicValues["subbed"] = Num(1)
+ pushString(strings.Join(parts[:i+1], this) + that + parts[i+1])
+ return
+ }
+ }
+ PublicValues["subbed"] = Num(0)
+ pushString(s)
+ },
+ "subbed": func() { push(PublicValues["subbed"]) },
+ "rephrase": func() { oldnew := popStringList(); pushString(Rephrase(popString(), oldnew)) },
+ "reword": func() { oldnew := popStringList(); pushString(Reword(popString(), oldnew)) },
+ "valid?": func() {
+ err := IsValidRegex(popString())
+ excuse(err)
+ pushBool(err == nil)
+ },
+ "escape": func() { pushString(EscapeRegex(popString())) },
+ "es": func() {
+ s, err := InterpretEscapes(popString())
+ if err != nil { ValueFatal(err.Error()) }
+ pushString(s)
+ },
+ "es?": func() {
+ s, err := InterpretEscapes(popString())
+ excuse(err)
+ if err == nil { pushString(s) }
+ pushBool(err == nil)
+ },
+ "ord": func() {
+ s := popString()
+ if len(s) == 0 { TypeFatal("ord received an empty string") }
+ pushInt(int(s[0]))
+ },
+ "chr": func() { pushString(string(popInt())) },
+ "+": func() { pushFloat(popFloat() + popFloat()) },
+ "-": func() { b := popFloat(); pushFloat(popFloat() - b) },
+ "*": func() { pushFloat(popFloat() * popFloat()) },
+ "/": func() {
+ b := popFloat()
+ if b == 0 { ArithmeticFatal("division by zero attempted") }
+ pushFloat(popFloat() / b)
+ },
+ "#+": func() {
+ s2 := popFloatList(); s1 := popFloatList()
+ pushFloats(elementwise(s1, s2, func(a, b float64) float64 { return a + b }))
+ },
+ "#-": func() {
+ s2 := popFloatList(); s1 := popFloatList()
+ pushFloats(elementwise(s1, s2, func(a, b float64) float64 { return a - b }))
+ },
+ "#*": func() {
+ s2 := popFloatList(); s1 := popFloatList()
+ pushFloats(elementwise(s1, s2, func(a, b float64) float64 { return a * b }))
+ },
+ "#/": func() {
+ s2 := popFloatList(); s1 := popFloatList()
+ pushFloats(elementwise(s1, s2, func(a, b float64) float64 {
+ if b == 0 { ArithmeticFatal("division by zero") }
+ return a / b
+ }))
+ },
+ "<": func() { pushBool(popFloat() > popFloat()) },
+ "<=": func() { pushBool(popFloat() >= popFloat()) },
+ ">": func() { pushBool(popFloat() < popFloat()) },
+ ">=": func() { pushBool(popFloat() <= popFloat()) },
+ "=": func() { pushBool(popFloat() == popFloat()) },
+ "!=": func() { pushBool(popFloat() != popFloat()) },
+ "mod": func() { b := popFloat(); pushFloat(Modulo(popFloat(), b)) },
+ "MOD": func() { b := popFloat(); pushFloat(math.Mod(popFloat(), b)) },
+ "sum": func() { pushFloat(Sum(popFloatList())) },
+ "mean": func() { pushFloat(Mean(popFloatList())) },
+ "min": func() { pushFloat(math.Min(popFloat(), popFloat())) },
+ "#min": func() { pushFloat(SliceMin(popFloatList())) },
+ "max": func() { pushFloat(math.Max(popFloat(), popFloat())) },
+ "#max": func() { pushFloat(SliceMax(popFloatList())) },
+ "abs": func() { pushFloat(math.Abs(popFloat())) },
+ "floor": func() { pushFloat(math.Floor(popFloat())) },
+ "ceil": func() { pushFloat(math.Ceil(popFloat())) },
+ "round": func() { pushFloat(math.Round(popFloat())) },
+ "pow": func() { exp := popFloat(); pushFloat(Pow(popFloat(), exp)) },
+ "sqrt": func() { pushFloat(Sqrt(popFloat())) },
+ "hypot": func() { pushFloat(math.Hypot(popFloat(), popFloat())) },
+ "pi": func() { pushFloat(math.Pi) },
+ "sin": func() { pushFloat(math.Sin(popFloat())) },
+ "cos": func() { pushFloat(math.Cos(popFloat())) },
+ "tan": func() { pushFloat(math.Tan(popFloat())) },
+ "asin": func() { pushFloat(Asin(popFloat())) },
+ "acos": func() { pushFloat(Acos(popFloat())) },
+ "atan": func() { pushFloat(math.Atan(popFloat())) },
+ "direction": func() { pushFloat(math.Atan2(popFloat(), popFloat())) },
+ "degrees": func() { pushFloat(popFloat() * 180 / math.Pi) },
+ "radians": func() { pushFloat(popFloat() * math.Pi / 180) },
+ "log": func() { pushFloat(Log(popFloat())) },
+ "exp": func() { pushFloat(Exp(popFloat())) },
+ "que": func() { pushInt(BitwiseAnd(popInt(), popInt())) },
+ "vel": func() { pushInt(BitwiseOr(popInt(), popInt())) },
+ "xor": func() { pushInt(BitwiseXor(popInt(), popInt())) },
+ "B": func() { SyntaxFatal("unexpected B") },
+ "E": func() { EventDefinition(&Events.E) },
+ "FB": func() { EventDefinition(&Events.FB) },
+ "FE": func() { EventDefinition(&Events.FE) },
+ "PE": func() { EventDefinition(&Events.PE) },
+ "PRENUM": func() { EventDefinition(&Events.PRENUM) },
+ "POSTNUM": func() { EventDefinition(&Events.POSTNUM) },
+ "TOKEN": func() { EventDefinition(&Events.TOKEN) },
+ ":": func() {
+ if !next() { SyntaxFatal("expected name for macro") }
+ if []rune(tok())[0] == '"' { SyntaxFatal(`macro names can't start with "`) }
+ if []rune(tok())[0] == '`' { SyntaxFatal("macro names can't start with `") }
+ forget(tok())
+ Macros[tok()] = getBlock()
+ },
+ "eval": func() { next(); pushframe("eval", TokenizeString(popString())); pushframe("", [][]string{ { "" } }) },
+ "yank": func() {
+ if AtToplevel() { SyntaxFatal("unexpected yank at the toplevel (not in a macro etc.)") }
+ var top TraceFrame
+ Trace, top = Trace[:len(Trace)-1], Trace[len(Trace)-1]
+ if exhausted() {
+ Trace = append(Trace, top)
+ SyntaxFatal("expected token after " + top.Name())
+ }
+ pushString(tok())
+ next()
+ Trace = append(Trace, top)
+ },
+ "if": func() { If() },
+ "otherwise": func() { Otherwise() },
+ "else": func() { Else() },
+ "then": func() { Then() },
+ "and": func() { push(LogicalAnd(popElements(2))) },
+ "#and": func() { push(LogicalAnd(popList())) },
+ "or": func() { push(LogicalOr(popElements(2))) },
+ "#or": func() { push(LogicalOr(popList())) },
+ "for": func() {
+ limit := popFloat()
+ if limit == 0 { SkipLoop(); return }
+ PushFor(0, limit, len(Stack()))
+ },
+ "#for": func() {
+ start := popFloat()
+ limit := popFloat()
+ if start == limit { SkipLoop(); return }
+ PushFor(start, limit, len(Stack()))
+ },
+ "foreach": func() {
+ elems := popList()
+ if len(elems) == 0 { SkipLoop(); return }
+ PushForeach(elems, len(Stack()))
+ },
+ "forever": func() { PushForever(len(Stack())) },
+ "loop": func() {
+ startingDepth, ok := ConditionallyLoop(1)
+ if !ok { SyntaxFatal("unexpected loop") }
+ PushCount = len(Stack()) - startingDepth
+ },
+ "#loop": func() {
+ startingDepth, ok := ConditionallyLoop(popFloat())
+ if !ok { SyntaxFatal("unexpected #loop") }
+ PushCount = len(Stack()) - startingDepth
+ },
+ "i": func() { lp := GetLoop(0); if lp == nil { SyntaxFatal("i not in loop") }; pushFloat(lp.Index()) },
+ "j": func() { lp := GetLoop(1); if lp == nil { SyntaxFatal("j not in loop") }; pushFloat(lp.Index()) },
+ "x": func() {
+ lp := GetForeach(0)
+ if lp == nil { SyntaxFatal("unexpected x not in foreach loop") }
+ push((*lp).Element())
+ },
+ "X": func() {
+ lp := GetForeach(0)
+ if lp == nil { SyntaxFatal("unexpected X not in foreach loop") }
+ lp.Next()
+ push((*lp).Element())
+ },
+ "y": func() {
+ lp := GetForeach(1)
+ if lp == nil { SyntaxFatal("unexpected y not in foreach loop") }
+ push((*lp).Element())
+ },
+ "Y": func() {
+ lp := GetForeach(1)
+ if lp == nil { SyntaxFatal("unexpected Y not in foreach loop") }
+ lp.Next()
+ push((*lp).Element())
+ },
+ "remnant": func() { lp := GetLoop(0); if lp == nil { SyntaxFatal("unexpected remnant not in loop") }; pushFloat(lp.Remnant()) },
+ "break": func() {
+ Break("break")
+ },
+ "while": func() {
+ if popBool() {
+ if GetLoop(0) == nil { SyntaxFatal("unexpected while not in loop") }
+ } else {
+ Break("while")
+ }
+ },
+ "until": func() {
+ if popBool() {
+ Break("until")
+ } else {
+ if GetLoop(0) == nil { SyntaxFatal("unexpected until not in loop") }
+ }
+ },
+ "apology": func() { push(PublicValues["apology"]) },
+ "apologize": func() {
+ s := string(PublicValues["apology"].Str())
+ if len(s) != 0 { fmt.Fprintln(os.Stderr, s) }
+ },
+ "assert": func() {
+ if popInt() == 0 { Fatal("assertion error") }
+ },
+ "error": func() { Fatal(popString()) },
+ "optparse": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ optstring := popString()
+ strs := popStringList()
+ opts, positionals, err := optparse(optstring, strs)
+ throw(err)
+ for k, v := range opts { MappingAssign(tok(), k, v) }
+ pushStringList(positionals)
+ },
+ "optparse?": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ optstring := popString()
+ strs := popStringList()
+ opts, positionals, err := optparse(optstring, strs)
+ if _, ok := err.(*OptstringError); ok { throw(err) }
+ excuse(err)
+ if err == nil {
+ for k, v := range opts { MappingAssign(tok(), k, v) }
+ pushStringList(positionals)
+ pushInt(1)
+ } else {
+ pushInt(0)
+ }
+ },
+ "optclean": func() {
+ optstring := popString()
+ before := popStringList()
+ after, err := optclean(optstring, before)
+ throw(err)
+ pushStringList(after)
+ },
+ "optclean?": func() {
+ optstring := popString()
+ before := popStringList()
+ after, err := optclean(optstring, before)
+ if _, ok := err.(*OptstringError); ok { throw(err) }
+ excuse(err)
+ pushStringList(after)
+ },
+ "varparse": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ strs := popStringList()
+ n := len(strs)
+ for _, s := range strs {
+ if parts := strings.SplitN(s, "=", 2); len(parts) == 2 {
+ MappingAssign(tok(), parts[0], Str(parts[1]))
+ n--
+ } else {
+ pushString(s)
+ }
+ }
+ pushInt(n)
+ },
+ "read": func() {
+ data, err := os.ReadFile(popString())
+ throw(err)
+ pushString(stripNewline(string(data)))
+ },
+ "read?": func() {
+ data, err := os.ReadFile(popString())
+ excuse(err)
+ if err == nil {
+ pushString(stripNewline(string(data)))
+ }
+ pushBool(err == nil)
+ },
+ "READ": func() {
+ data, err := os.ReadFile(popString())
+ throw(err)
+ pushString(string(data))
+ },
+ "READ?": func() {
+ data, err := os.ReadFile(popString())
+ excuse(err)
+ if err == nil { pushString(string(data)) }
+ pushBool(err == nil)
+ },
+ "write": func() { throw(WriteToFile(popString(), popString() + "\n")) },
+ "WRITE": func() { throw(WriteToFile(popString(), popString())) },
+ "write?": func() { err := WriteToFile(popString(), popString() + "\n"); excuse(err); pushBool(err == nil) },
+ "WRITE?": func() { err := WriteToFile(popString(), popString()); excuse(err); pushBool(err == nil) },
+ "append": func() { throw(AppendToFile(popString(), popString() + "\n")) },
+ "APPEND": func() { throw(AppendToFile(popString(), popString())) },
+ "append?": func() { err := AppendToFile(popString(), popString() + "\n"); excuse(err); pushBool(err == nil) },
+ "APPEND?": func() { err := AppendToFile(popString(), popString()); excuse(err); pushBool(err == nil) },
+ "unlink": func() {
+ err := os.Remove(popString())
+ if !errors.Is(err, os.ErrNotExist) { throw(err) }
+ },
+ "unlink?": func() {
+ err := os.Remove(popString())
+ excuse(err)
+ pushBool(err == nil || errors.Is(err, os.ErrNotExist))
+ },
+ "UNLINK": func() { throw(os.RemoveAll(popString())) },
+ "UNLINK?": func() {
+ err := os.RemoveAll(popString())
+ excuse(err)
+ pushBool(err == nil)
+ },
+ "rename": func() {
+ n := popString()
+ o := popString()
+ throw(os.Rename(o, n))
+ },
+ "rename?": func() {
+ n := popString()
+ o := popString()
+ err := os.Rename(o, n)
+ excuse(err)
+ pushBool(err == nil)
+ },
+ "mkdir": func() {
+ throw(os.MkdirAll(popString(), 0755))
+ },
+ "mkdir?": func() {
+ err := os.MkdirAll(popString(), 0755)
+ excuse(err)
+ pushBool(err == nil)
+ },
+ "entries": func() {
+ paths, err := Entries(popString(), true)
+ throw(err)
+ pushStringList(paths)
+ },
+ "entries?": func() {
+ paths, err := Entries(popString(), true)
+ excuse(err)
+ if err == nil { pushStringList(paths) }
+ pushBool(err == nil)
+ },
+ "ENTRIES": func() {
+ paths, err := Entries(popString(), false)
+ throw(err)
+ pushStringList(paths)
+ },
+ "ENTRIES?": func() {
+ paths, err := Entries(popString(), false)
+ excuse(err)
+ if err == nil { pushStringList(paths) }
+ pushBool(err == nil)
+ },
+ "cleanpath": func() { pushString(filepath.Clean(popString())) },
+ "relatepath": func() {
+ from := popString()
+ p := popString()
+ rp, err := filepath.Rel(from, p)
+ throw(err)
+ pushString(rp)
+ },
+ "relatepath?": func() {
+ from := popString()
+ p := popString()
+ rp, err := filepath.Rel(from, p)
+ excuse(err)
+ if err == nil { pushString(rp) }
+ pushBool(err == nil)
+ },
+ "resolvepath": func() {
+ rp, err := filepath.EvalSymlinks(popString())
+ throw(err)
+ pushString(rp)
+ },
+ "resolvepath?": func() {
+ rp, err := filepath.EvalSymlinks(popString())
+ excuse(err)
+ if err == nil { pushString(rp) }
+ pushBool(err == nil)
+ },
+ "shell": func() { PublicValues["rc"] = Num(shell(Out.to, popString(), "", false)) },
+ "$shell": func() { PublicValues["rc"] = Num(shell(Out.to, popString(), popString() + "\n", true)) },
+ "shellout": func() { o, rc := shellout(popString(), "", false); PublicValues["rc"] = Num(rc); pushString(stripNewline(o)) },
+ "$shellout": func() { o, rc := shellout(popString(), popString() + "\n", true); PublicValues["rc"] = Num(rc); pushString(stripNewline(o)) },
+ "SHELLOUT": func() { o, rc := shellout(popString(), "", false); PublicValues["rc"] = Num(rc); pushString(o) },
+ "$SHELLOUT": func() { o, rc := shellout(popString(), popString() + "\n", true); PublicValues["rc"] = Num(rc); pushString(o) },
+ "shelljoin": func() {
+ n := popInt()
+ if n >= 0 {
+ pushString(Shelljoin(popStrings(n)))
+ } else {
+ pushString(ShellAppend(popStrings(-n)))
+ }
+ },
+ "shellsplit": func() { pushStringList(Shellsplit(popString())) },
+ "sleep": func() {
+ n := popFloat()
+ time.Sleep(time.Duration(n*float64(time.Second)))
+ },
+ "code": func() { PrivateValues.code = popInt() },
+ "exit": func() { flowStack = nil; Finish() },
+ "EXIT": func() { flowStack = nil; PrematurelyFinish() },
+ "pwd": func() {
+ wd, err := os.Getwd()
+ throw(err)
+ pushString(wd)
+ },
+ "cd": func() { throw(os.Chdir(popString())) },
+ "cd?": func() {
+ err := os.Chdir(popString())
+ excuse(err)
+ pushBool(err == nil)
+ },
+ "stdin": func() { pushString(os.Stdin.Name()) },
+ "stdout": func() { pushString(os.Stdout.Name()) },
+ "stderr": func() { pushString(os.Stderr.Name()) },
+ "os": func() { pushString(runtime.GOOS) },
+ "pathsep": func() { pushString(string(os.PathSeparator)) },
+ "pathlistsep": func() { pushString(string(os.PathListSeparator)) },
+ "seed": func() { RandomSeed(int64(popInt())) },
+ "random": func() { pushInt(int(RandomInt())) },
+ "RANDOM": func() { pushFloat(RandomFloat()) },
+ "shuffle": func() { elems := popList(); Shuffle(elems); pushElements(elems) },
+ "match": func() { push(PublicValues["match"]) },
+ "where": func() { push(PublicValues["where"]) },
+ "rc": func() { push(PublicValues["rc"]) },
+ "builtins": func() {
+ names := make([]string, 0, len(Builtins))
+ for k := range Builtins { names = append(names, k) }
+ sort.Strings(names)
+ pushStringList(names)
+ },
+ "mappings": func() {
+ names := make([]string, 0, len(Mappings) + 2)
+ names = append(names, "Env")
+ names = append(names, "Macro")
+ for k := range Mappings { names = append(names, k) }
+ sort.Strings(names)
+ pushStringList(names)
+ },
+ "processes": func() {
+ names := make([]string, 0, len(Processes))
+ for k := range Processes { names = append(names, k) }
+ sort.Strings(names)
+ pushStringList(names)
+ },
+ "set": func() { if !next() { SyntaxFatal("expected variable name") }; forget(tok()); Variables[tok()] = pop() },
+ "+set": func() {
+ if !next() { SyntaxFatal("expected variable name") }
+ inc := popFloat()
+ var f float64
+ if value := Variables[tok()]; value != nil { f = float64(value.Num()) }
+ forget(tok())
+ Variables[tok()] = Num(f + inc)
+ },
+ "-set": func() {
+ if !next() { SyntaxFatal("expected variable name") }
+ dec := popFloat()
+ var f float64
+ if value := Variables[tok()]; value != nil { f = float64(value.Num()) }
+ forget(tok())
+ Variables[tok()] = Num(f - dec)
+ },
+ "mapping": func() {
+ if !next() { SyntaxFatal("expected name for mapping name") }
+ forget(tok())
+ Mappings[tok()] = map[string]Element{}
+ },
+ "map": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ key := popString()
+ value := pop()
+ MappingAssign(tok(), key, value)
+ },
+ "+map": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ key := popString()
+ inc := popInt()
+ if MappingExists(tok()) {
+ v, _ := MappingGet(tok(), key)
+ MappingAssign(tok(), key, Num(int(v.Num()) + inc))
+ }
+ },
+ "-map": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ key := popString()
+ dec := popInt()
+ if MappingExists(tok()) {
+ v, _ := MappingGet(tok(), key)
+ MappingAssign(tok(), key, Num(int(v.Num()) - dec))
+ }
+ },
+ "unmap": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ key := popString()
+ MappingUnassign(tok(), key)
+ },
+ "of": func() {
+ if !next() { SyntaxFatal("expected mapping name or stack name") }
+ if _, is := Stacks[tok()]; is {
+ i := popInt()
+ if i < 0 { i += len(Stacks[tok()]) }
+ if i < 0 || i >= len(Stacks[tok()]) { KeyFatal("out of range index") }
+ push(Stacks[tok()][i])
+ } else if MappingExists(tok()) {
+ e, _ := MappingGet(tok(), popString())
+ push(e)
+ } else {
+ TypeFatal(tok() + " is not a mapping name or stack name")
+ }
+ },
+ "of?": func() {
+ if !next() { SyntaxFatal("expected mapping name or stack name") }
+ if _, is := Stacks[tok()]; is {
+ i := popInt()
+ if i < 0 { i += len(Stacks[tok()]) }
+ if i >= 0 && i < len(Stacks[tok()]) {
+ push(Stacks[tok()][i])
+ pushInt(1)
+ } else {
+ pushInt(0)
+ }
+ } else if MappingExists(tok()) {
+ e, in := MappingGet(tok(), popString())
+ push(e)
+ pushBool(in)
+ } else {
+ TypeFatal(tok() + " is not a mapping name or stack name")
+ }
+ },
+ "tot": func() {
+ if !next() { SyntaxFatal("expected mapping name or stack name or process name") }
+ if m, in := Mappings[tok()]; in {
+ pushInt(len(m))
+ } else if s, in := Stacks[tok()]; in {
+ pushInt(len(s))
+ } else if p, in := Processes[tok()]; in {
+ pushInt(p.Len())
+ } else {
+ TypeFatal(tok() + " is not a mapping name or stack name or process name")
+ }
+ },
+ "in": func() {
+ if !next() { SyntaxFatal("expected mapping name or stack name") }
+ key := popString()
+ var in bool
+ if m := Mappings[tok()]; m != nil {
+ _, in = m[key]
+ } else if _, is := Stacks[tok()]; is {
+ PublicValues["where"] = Num(-1)
+ for i, e := range Stacks[tok()] {
+ if e.Str() == Str(key) {
+ in = true
+ PublicValues["where"] = Num(i)
+ break
+ }
+ }
+ } else if tok() == EnvMapName {
+ _, in = os.LookupEnv(key)
+ } else if tok() == MacroMapName {
+ _, in = Macros[key]
+ } else {
+ TypeFatal(tok() + " is not a mapping name or stack name")
+ }
+ pushBool(in)
+ },
+ "keysof": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ if m := Mappings[tok()]; m != nil {
+ for k := range m { pushString(k) }
+ pushInt(len(m))
+ } else if tok() == EnvMapName {
+ for _, s := range os.Environ() { pushString(strings.SplitN(s, "=", 1)[0]) }
+ pushInt(len(os.Environ()))
+ } else if tok() == MacroMapName {
+ for k := range Macros { pushString(k) }
+ pushInt(len(Macros))
+ } else {
+ TypeFatal(tok() + " is not a mapping name")
+ }
+ },
+ "valuesof": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ if m := Mappings[tok()]; m != nil {
+ for _, v := range m { push(v) }
+ pushInt(len(m))
+ } else if tok() == EnvMapName {
+ for _, s := range os.Environ() { pushString(strings.SplitN(s, "=", 1)[1]) }
+ pushInt(len(os.Environ()))
+ } else if tok() == MacroMapName {
+ for _, tokens := range Macros { pushString(Untokenize(tokens)) }
+ pushInt(len(Macros))
+ } else {
+ TypeFatal(tok() + " is not a mapping name")
+ }
+ },
+ "itemsof": func() {
+ if !next() { SyntaxFatal("expected mapping name") }
+ if m := Mappings[tok()]; m != nil {
+ for k, v := range m { pushString(k); push(v) }
+ pushInt(len(m) * 2)
+ } else if tok() == EnvMapName {
+ for _, s := range os.Environ() { pushStringList(strings.SplitN(s, "=", 1)) }
+ pushInt(len(os.Environ()) * 2)
+ } else if tok() == MacroMapName {
+ for k, tokens := range Macros { pushString(k); pushString(Untokenize(tokens)) }
+ pushInt(len(Macros) * 2)
+ } else {
+ TypeFatal(tok() + " is not a mapping name")
+ }
+ },
+ "stack": func() {
+ if !next() { SyntaxFatal("expected name for stack name") }
+ name := tok()
+ forget(name)
+ Stacks[name] = nil
+ },
+ "as": func() {
+ expectStackName()
+ Stacks[tok()] = nil
+ pushElementsToStack(tok(), popList())
+ },
+ "push": func() {
+ expectStackName()
+ pushToStack(tok(), pop())
+ },
+ "#push": func() {
+ expectStackName()
+ pushElementsToStack(tok(), popList())
+ },
+ "pop": func() {
+ expectStackName()
+ if len(Stacks[tok()]) == 0 {
+ pushString("")
+ } else {
+ push(popFromStack(tok()))
+ }
+ },
+ "pop?": func() {
+ expectStackName()
+ if len(Stacks[tok()]) == 0 {
+ pushInt(0)
+ } else {
+ push(popFromStack(tok()))
+ pushInt(1)
+ }
+ },
+ "#pop": func() {
+ expectStackName()
+ n := popCount()
+ elems := Stacks[tok()][max(0, len(Stacks[tok()]) - n):]
+ Stacks[tok()] = Stacks[tok()][:len(Stacks[tok()]) - len(elems)]
+ pushList(elems)
+ },
+ "POP": func() {
+ expectStackName()
+ if len(Stacks[tok()]) == 0 {
+ pushString("")
+ } else {
+ push(Stacks[tok()][0])
+ Stacks[tok()] = Stacks[tok()][1:]
+ }
+ },
+ "POP?": func() {
+ expectStackName()
+ if len(Stacks[tok()]) == 0 {
+ pushInt(0)
+ } else {
+ push(Stacks[tok()][0])
+ Stacks[tok()] = Stacks[tok()][1:]
+ pushInt(1)
+ }
+ },
+ "#POP": func() {
+ expectStackName()
+ n := popCount()
+ elems := Stacks[tok()][:min(len(Stacks[tok()]), n)]
+ Stacks[tok()] = Stacks[tok()][len(elems):]
+ pushList(elems)
+ },
+ "process": func() {
+ if !next() { SyntaxFatal("expected processor word name") }
+ forget(tok())
+ Processes[tok()] = NewProcess(popString())
+ },
+ "take": func() {
+ s, _, _ := yankProcess().Next()
+ pushString(s)
+ },
+ "take?": func() {
+ s, err, was := yankProcess().Next()
+ excuse(err)
+ if was { pushString(s) }
+ pushBool(was)
+ },
+ "#take": func() {
+ p := yankProcess()
+ n := popCount()
+ var i int
+ for ; i < n; i++ {
+ s, _, was := p.Next()
+ if !was { break }
+ pushString(s)
+ }
+ pushInt(i)
+ },
+ "send": func() {
+ p := yankProcess()
+ _ = p.Send(popString() + "\n")
+ },
+ "send?": func() {
+ p := yankProcess()
+ err := p.Send(popString() + "\n")
+ excuse(err)
+ pushBool(err == nil)
+ },
+ "SEND": func() {
+ p := yankProcess()
+ _ = p.Send(popString())
+ },
+ "SEND?": func() {
+ p := yankProcess()
+ err := p.Send(popString())
+ excuse(err)
+ pushBool(err == nil)
+ },
+ "shut": func() {
+ p := yankProcess()
+ p.Close()
+ },
+ "await": func() {
+ p := yankProcess()
+ PublicValues["rc"] = Num(p.Await())
+ },
+ "conclude": func() {
+ p := yankProcess()
+ rc, output, err := p.Conclude()
+ excuse(err)
+ PublicValues["rc"] = Num(rc)
+ pushString(output)
+ },
+ }
+}
A => src/devscripts/documented +13 -0
@@ 1,13 @@
+#! /usr/bin/env -S gek -f
+# Prints the documented word names in topic order.
+
+B
+ "../doc" cd
+ "chat/topics" read "(?s).*Word Categories" "" sub
+ "(?m)^:(.+)" extract
+ foreach "words/" x , loop @ as Args
+;
+
+r "^(\S+)\s.*\(.*--.*\)$" extract #$
+
+E depth 0 = assert ;
A => src/devscripts/implemented +11 -0
@@ 1,11 @@
+#! /usr/bin/env -S gek -f
+# Prints the implemented word names.
+
+B
+ "builtins.go" 1 as Args
+ forever r "Builtins = map[string]func() {" says until bump? while loop
+;
+
+r `"(.*)": func\(\)` extract drop
+
+E depth #$ ;
A => src/devscripts/sorted +19 -0
@@ 1,19 @@
+#! /usr/bin/env -S gek -f
+# Prints the builtins.go map sorted by the documentation order.
+
+B
+ stack Order "./devscripts/documented" shellout nl $split as Order
+ "builtins.go" read ".*: func\(\)(?s:.)+?\}," extract
+
+ # ( s -- i )
+ : position
+ `"(.*?)": func\(\)` 1 #extract if
+ in Order drop where
+ else
+ -1
+ then
+ ;
+
+ sort position swap position > ;
+ depth #$
+;
A => src/errors.go +45 -0
@@ 1,45 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+)
+
+const (
+ ansiReset = "\033[0m"
+ ansiRed = "\033[31m"
+)
+
+func tracebackLine(f *TraceFrame) string {
+ tl := f.tokens[f.li]
+ line := strings.Join(tl[:f.i], "") + ansiRed + tl[f.i] + ansiReset + strings.Join(tl[f.i+1:], "")
+ return fmt.Sprintf("line %d of %s: %s\n", f.li+1, f.Name(), line)
+}
+func printTraceback() {
+ for _, frame := range Trace {
+ fmt.Fprint(os.Stderr, tracebackLine(&frame))
+ }
+}
+
+func FatalWithoutTraceback(message string) {
+ fmt.Fprintln(os.Stderr, message)
+ PrivateValues.code = 1
+ PrematurelyFinish()
+}
+func Fatal(s string) {
+ printTraceback()
+ FatalWithoutTraceback(s)
+}
+func ArithmeticFatal(s string) { Fatal(s) }
+func KeyFatal(s string) { Fatal(s) }
+func SyntaxFatal(s string) { Fatal("syntax error: " + s) }
+func TypeFatal(s string) { Fatal(s) }
+func ValueFatal(s string) { Fatal(s) }
+func StackUnderflow() { Fatal("stack underflow") }
+
+func throw(err error) {
+ if err != nil {
+ Fatal(err.Error())
+ }
+}
A => src/events.go +58 -0
@@ 1,58 @@
+package main
+
+var Events = struct {
+ // B blocks are instantly run
+ E [][]string
+ FB [][]string
+ FE [][]string
+ PE [][]string
+ PRENUM [][]string
+ POSTNUM [][]string
+ TOKEN [][]string
+}{}
+
+var inNumEvent = false
+func Prenum() {
+ if !inNumEvent {
+ inNumEvent = true
+ ExecuteBlock("PRENUM", Events.PRENUM)
+ inNumEvent = false
+ }
+}
+func Postnum() {
+ if !inNumEvent {
+ inNumEvent = true
+ ExecuteBlock("POSTNUM", Events.POSTNUM)
+ inNumEvent = false
+ }
+}
+
+var inTokenEvent = false
+func TokenEvent(token string) string {
+ if Events.TOKEN == nil { return token }
+ if !inTokenEvent {
+ inTokenEvent = true
+ pushString(token)
+ ExecuteBlock("TOKEN", Events.TOKEN)
+ inTokenEvent = false
+ return popString()
+ }
+ return token
+}
+
+
+type toplevelTokenPosition struct {
+ li int
+ i int
+}
+var exhaustedEvents []toplevelTokenPosition
+func EventDefinition(event *[][]string) {
+ pos := toplevelTokenPosition{ Frame().li, Frame().i }
+ if AtToplevel() {
+ for i := range exhaustedEvents {
+ if pos == exhaustedEvents[i] { getBlock(); return }
+ }
+ }
+ *event = append(*event, getBlock()...)
+ exhaustedEvents = append(exhaustedEvents, pos)
+}
A => src/execute.go +122 -0
@@ 1,122 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+)
+
+// doVocab executes the next token if it's in vocab (or whitespace) and returns whether it was.
+func doVocab(token string, vocab map[string]func()) bool {
+ if len(strings.TrimSpace(token)) == 0 { return true }
+ fn, in := vocab[token]
+ if in {
+ PushCount = 0
+ fn()
+ PublicValues["@"] = Num(PushCount)
+ }
+ return in
+}
+
+// DoNextWord skips string literals and ; terminated blocks and executes the next token if it's in vocab (or whitespace) and returns whether it was.
+func DoNextWord(vocab map[string]func()) bool {
+ for {
+ if len(getStringLiteral(_tok())) != 0 { next(); continue }
+ if IsBlockName(tok()) { getBlock(); continue }
+ break
+ }
+ return doVocab(tok(), vocab)
+}
+
+// doLiteral executes the next token if it's a string literal or number literal and returns whether it was.
+func doLiteral(token string) bool {
+ if s := getStringLiteral(token); len(s) != 0 {
+ s = strings_TrimRightSpace(s)
+ pushString(s[1:len(s)-1])
+ return true
+ }
+ if f, ok := ParseNumberLiteral(token); ok {
+ pushFloat(f)
+ return true
+ }
+ return false
+}
+
+// Do executes the next token.
+// If it's a macro it will push tokens.
+// Leaves Trace on the now exhausted token or a pushed guff token.
+// The token is an argument so it can be manipulated beforehand (used to implement TOKEN events).
+func Do(fulltoken string, vocab map[string]func()) bool {
+ token := strings_TrimRightSpace(fulltoken)
+ if e, in := Variables[token]; in {
+ push(e)
+ } else if m, in := Macros[token]; in {
+ name := token
+ next()
+ pushframe(name, m)
+ pushframe("", [][]string{ { "" } })
+ } else if s, in := Stacks[token]; in {
+ pushList(s)
+ } else if p, in := Processes[token]; in {
+ pushBool(p.Running())
+ } else if MappingExists(token) {
+ e, _ := MappingGet(token, popString())
+ push(e)
+ } else {
+ return doVocab(token, vocab) || doLiteral(fulltoken)
+ }
+ return true
+}
+
+// Execute executes and exhausts Trace.
+func Execute() {
+ for !exhausted() {
+ token := _tok()
+ if token[0] == '"' || token[0] == '`' {
+ token = getStringLiteral(token)
+ }
+ token = strings_TrimRightSpace(token)
+ token = TokenEvent(token)
+ if len(token) != 0 {
+ if !Do(token, Builtins) { Fatal("undefined word: " + token) }
+ }
+ next()
+ }
+}
+
+// ExecuteBlock executes the passed tokenlines.
+func ExecuteBlock(name string, tokenlines [][]string) {
+ old := Trace
+ setTraceToBlock(name, tokenlines)
+ Execute()
+ Trace = old
+}
+
+
+func assertNoControlFlow() {
+ if len(flowStack) != 0 {
+ // printTracebackLine(getFlowFrame(flowStack[0]))
+ fmt.Fprint(os.Stderr, tracebackLine(getFlowFrame(flowStack[0])))
+ // This function is used in PrematurelyFinish() so using fatal() would loop.
+ fmt.Fprintln(os.Stderr, "syntax error: unterminated control flow")
+ PrivateValues.code = 1
+ }
+}
+func AssertNoControlFlow() {
+ assertNoControlFlow()
+ if len(flowStack) != 0 { PrematurelyFinish() }
+}
+
+func PrematurelyFinish() {
+ flowStack = nil
+ ExecuteBlock("PE", Events.PE)
+ assertNoControlFlow()
+ Out.Flush()
+ os.Exit(PrivateValues.code)
+}
+
+func Finish() {
+ ExecuteBlock("E", Events.E)
+ AssertNoControlFlow()
+ PrematurelyFinish()
+}
A => src/filewords.go +29 -0
@@ 1,29 @@
+package main
+
+import "os"
+
+func WriteToFile(filepath, s string) error {
+ return os.WriteFile(filepath, []byte(s), 0644)
+}
+func AppendToFile(filepath, s string) error {
+ f, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ if err == nil { _, err = f.WriteString(s) }
+ f.Close()
+ return err
+}
+
+func Entries(filepath string, prefix bool) ([]string, error) {
+ names, err := os.ReadDir(filepath)
+ if err != nil { return nil, err }
+ s := make([]string, len(names))
+ p := ""
+ if prefix { p = filepath + string(os.PathSeparator) }
+ for i := range names {
+ if names[i].IsDir() {
+ s[i] = p + names[i].Name() + string(os.PathSeparator)
+ } else {
+ s[i] = p + names[i].Name()
+ }
+ }
+ return s, nil
+}
A => src/flow.go +290 -0
@@ 1,290 @@
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+type ifClause struct {
+ trace []TraceFrame
+}
+type elseClause struct {
+ trace []TraceFrame
+}
+
+type loop interface {
+ Loop(float64) bool
+ StartingDepth() int
+ Trace() []TraceFrame
+ Index() float64
+ Remnant() float64
+}
+
+type loopcommon struct {
+ startingDepth int
+ trace []TraceFrame
+ index float64
+}
+func (lp loopcommon) StartingDepth() int { return lp.startingDepth }
+func (lp loopcommon) Trace() []TraceFrame { return lp.trace }
+func (lp loopcommon) Index() float64 { return lp.index }
+
+type forloop struct {
+ loopcommon
+ limit float64
+}
+func (lp *forloop) Loop(increment float64) bool {
+ if lp.index < lp.limit {
+ lp.index += increment
+ return lp.index < lp.limit
+ } else {
+ lp.index += increment
+ return lp.index > lp.limit
+ }
+}
+func (lp *forloop) Remnant() float64 {
+ return lp.limit - lp.index - 1
+}
+
+type foreachloop struct {
+ loopcommon
+ elements []Element
+}
+func (lp *foreachloop) Loop(increment float64) bool {
+ lp.index += increment
+ if len(lp.elements) == 0 { return false }
+ lp.elements = lp.elements[1:]
+ return len(lp.elements) != 0
+}
+func (lp *foreachloop) Next() {
+ if len(lp.elements) != 0 { lp.elements = lp.elements[1:] }
+}
+func (lp *foreachloop) Element() Element {
+ if len(lp.elements) == 0 { return Str("") } else { return lp.elements[0] }
+}
+func (lp *foreachloop) Remnant() float64 {
+ return float64(len(lp.elements) - 1)
+}
+
+type foreverloop struct {
+ loopcommon
+}
+func (lp *foreverloop) Loop(increment float64) bool {
+ lp.index += increment
+ return true
+}
+func (lp *foreverloop) Remnant() float64 {
+ SyntaxFatal("unexpected remnant in forever loop")
+ panic("unreachable")
+}
+
+
+var flowStack []interface{}
+
+func getTraceCopy() []TraceFrame {
+ t := make([]TraceFrame, len(Trace))
+ copy(t, Trace)
+ return t
+}
+
+func PushIf() { flowStack = append(flowStack, ifClause{ getTraceCopy() }) }
+func PushElse() { flowStack = append(flowStack, elseClause{ getTraceCopy() }) }
+
+func PushFor(start, limit float64, depth int) {
+ flowStack = append(flowStack, &forloop{ loopcommon{ depth, getTraceCopy(), start }, limit })
+}
+func PushForeach(elems []Element, depth int) {
+ flowStack = append(flowStack, &foreachloop{ loopcommon{ depth, getTraceCopy(), 0 }, elems })
+}
+func PushForever(depth int) {
+ flowStack = append(flowStack, &foreverloop{ loopcommon{ depth, getTraceCopy(), 0 } })
+}
+
+func InIf() bool {
+ if len(flowStack) == 0 { return false }
+ _, is := flowStack[len(flowStack)-1].(ifClause)
+ return is
+}
+func InElse() bool {
+ if len(flowStack) == 0 { return false }
+ _, is := flowStack[len(flowStack)-1].(elseClause)
+ return is
+}
+func getLoop(depth int) int {
+ for i := len(flowStack)-1; i >= 0; i-- {
+ if _, is := flowStack[i].(loop); is {
+ if depth == 0 { return i }
+ depth--
+ }
+ }
+ return -1
+}
+
+func getForeach(depth int) int {
+ for i := len(flowStack)-1; i >= 0; i-- {
+ if _, is := flowStack[i].(*foreachloop); is {
+ if depth == 0 { return i }
+ depth--
+ }
+ }
+ return -1
+}
+
+func GetLoop(depth int) loop {
+ fsi := getLoop(depth)
+ if fsi == -1 { return nil }
+ return flowStack[fsi].(loop)
+}
+func GetForeach(depth int) *foreachloop {
+ fsi := getForeach(depth)
+ if fsi == -1 { return nil }
+ return flowStack[fsi].(*foreachloop)
+}
+
+func conditionallyLoop(increment float64) (int, bool) {
+ fsi := getLoop(0)
+ if fsi == -1 { return 0, false }
+ lp := flowStack[fsi].(loop)
+ if lp.Loop(increment) {
+ Trace = make([]TraceFrame, len(lp.Trace()))
+ copy(Trace, lp.Trace())
+ } else {
+ flowStack = flowStack[:fsi]
+ }
+ return lp.StartingDepth(), true
+}
+func ConditionallyLoop(increment float64) (int, bool) {
+ select {
+ case <-InterruptChannel:
+ PrematurelyFinish()
+ default:
+ return conditionallyLoop(increment)
+ }
+ panic("unreachable")
+}
+
+func ClearFlowStack() { flowStack = nil }
+func DropFlow() { flowStack = flowStack[:len(flowStack)-1] }
+func DropLoop() bool {
+ fsi := getLoop(0)
+ if fsi == -1 { return false }
+ flowStack = flowStack[:fsi]
+ return true
+}
+
+func getFlowFrame(flow any) *TraceFrame {
+ switch f := flow.(type) {
+ case ifClause:
+ trace := f.trace
+ return &trace[len(trace)-1]
+ case elseClause:
+ trace := f.trace
+ return &trace[len(trace)-1]
+ case loop:
+ trace := f.Trace()
+ return &trace[len(trace)-1]
+ }
+ panic("unreachable")
+}
+
+
+func unterminatedControlFlow(traceback string) {
+ fmt.Fprint(os.Stderr, traceback)
+ Fatal("syntax error: unterminated control flow")
+}
+
+func skipIfClause() {
+ emsg := tracebackLine(Frame())
+ nesting := 1
+ vocab := map[string]func() {
+ "else": func() { if nesting == 1 { PushElse(); nesting-- } },
+ "otherwise": func() { nesting-- },
+ "then": func() { nesting-- },
+ "if": func() { nesting++ },
+ }
+ for nesting != 0 && next() { DoNextWord(vocab) }
+ if nesting != 0 { unterminatedControlFlow(emsg) }
+}
+
+func skipIfStatementOrChain() {
+ emsg := tracebackLine(Frame())
+ done := false
+ vocab := map[string]func() {
+ "else": func() { skipElse(); done = true },
+ "otherwise": func() { skipOtherwises(); done = true },
+ "then": func() { done = true },
+ "if": func() { skipIfStatementOrChain() },
+ }
+ for !done && next() { DoNextWord(vocab) }
+ if !done { unterminatedControlFlow(emsg) }
+}
+
+func skipElse() {
+ emsg := tracebackLine(Frame())
+ nesting := 1
+ vocab := map[string]func() {
+ "else": func() { SyntaxFatal("unexpected else") },
+ "otherwise": func() { SyntaxFatal("unexpected otherwise") },
+ "then": func() { nesting-- },
+ "if": func() { skipIfStatementOrChain() },
+ }
+ for nesting != 0 && next() { DoNextWord(vocab) }
+ if nesting != 0 { unterminatedControlFlow(emsg) }
+}
+
+func skipOtherwises() {
+ emsg := tracebackLine(Frame())
+ done := false
+ vocab := map[string]func() {
+ "if": func() { skipIfStatementOrChain(); done = true },
+ "otherwise": func() {
+ SyntaxFatal("unexpected otherwise after otherwise")
+ },
+ "then": func() {
+ SyntaxFatal("unexpected then after otherwise")
+ },
+ }
+ for !done && next() { DoNextWord(vocab) }
+ if !done { unterminatedControlFlow(emsg) }
+}
+
+func If() {
+ if pop().Num() == 0 {
+ skipIfClause()
+ } else {
+ PushIf()
+ }
+}
+func Else() {
+ if !InIf() { SyntaxFatal("unexpected else") }
+ skipElse()
+ DropFlow()
+}
+func Otherwise() {
+ if !InIf() { SyntaxFatal("unexpected otherwise") }
+ skipOtherwises()
+ DropFlow()
+}
+func Then() {
+ if !InIf() && !InElse() { SyntaxFatal("unexpected then") }
+ DropFlow()
+}
+
+func SkipLoop() {
+ emsg := tracebackLine(Frame())
+ nesting := 1
+ vocab := map[string]func() {
+ "for": func() { nesting++ },
+ "#for": func() { nesting++ },
+ "foreach": func() { nesting++ },
+ "forever": func() { nesting++ },
+ "loop": func() { nesting-- },
+ "#loop": func() { nesting-- },
+ }
+ for nesting != 0 && next() { DoNextWord(vocab) }
+ if nesting != 0 { unterminatedControlFlow(emsg) }
+}
+func Break(word string) {
+ if ok := DropLoop(); !ok { SyntaxFatal("unexpected " + word + " outside of loop") }
+ SkipLoop()
+}
A => src/helpers.go +68 -0
@@ 1,68 @@
+package main
+
+import (
+ "errors"
+ "os"
+ "strings"
+ "unicode"
+)
+
+func abs(n int) int {
+ if n > 0 { return n } else { return -n }
+}
+func max(a, b int) int {
+ if a > b { return a } else { return b }
+}
+func min(a, b int) int {
+ if a < b { return a } else { return b }
+}
+func mod(a, b int) int {
+ return (a % b + b) % b
+}
+
+func get[T any](s []T, index int, fallback T) T {
+ if index < 0 { index += len(s) }
+ if index < 0 || index >= len(s) { return fallback }
+ return s[index]
+}
+func getElement(s []Element, index int, fallback Element) Element {
+ if index < 0 { index += len(s) }
+ if index < 0 || index >= len(s) { return fallback }
+ return s[index]
+}
+
+func openRegularFile(name string) (*os.File, error) {
+ f, err := os.Open(name)
+ if info, err := f.Stat(); err == nil && info.IsDir() {
+ return nil, errors.New("open " + name + ": is a directory")
+ }
+ return f, err
+}
+
+func stripNewline(s string) string {
+ i := strings.LastIndex(s, "\n")
+ if i != -1 { return s[:i] }
+ return s
+}
+
+func strings_TrimLeftSpace(s string) string {
+ return strings.TrimLeftFunc(s, unicode.IsSpace)
+}
+func strings_TrimRightSpace(s string) string {
+ return strings.TrimRightFunc(s, unicode.IsSpace)
+}
+func strings_CutRight(s string, sep string) (string, string, bool) {
+ i := strings.LastIndex(s, sep)
+ if i == -1 { return "", s, false }
+ return s[:i], s[i+1:], true
+}
+func strings_CutFunc(s string, f func(rune) bool) (string, string, bool) {
+ i := strings.IndexFunc(s, f)
+ if i == -1 { return s, "", false }
+ return s[:i], s[i+1:], true
+}
+func strings_CutRightFunc(s string, f func(rune) bool) (string, string, bool) {
+ i := strings.LastIndexFunc(s, f)
+ if i == -1 { return "", s, false }
+ return s[:i], s[i+1:], true
+}
A => src/input.go +213 -0
@@ 1,213 @@
+package main
+
+import (
+ "bufio"
+ "os"
+ "regexp"
+ "strings"
+)
+
+type Input struct {
+ pop func() (string, bool) // function to pop filepath
+
+ started bool
+
+ file *os.File
+ name string // rn path
+ err error
+ scanner *MyScanner
+
+ usingSeperator bool
+ recordSplitfunc bufio.SplitFunc
+ fieldPattern *regexp.Regexp
+ delimPattern *regexp.Regexp
+
+ sep []byte
+
+ // whether the record is no longer the scanner text
+ // and to use the interlacement of .fields and .delims
+ overridden bool
+
+ fields []string
+ delims []string
+}
+
+func NewInput(popFilepath func() (string, bool)) *Input {
+ ip := Input{ pop: popFilepath, fieldPattern: regexp.MustCompile(`\S+`) }
+ ip.SeparatorPattern(`\n`)
+ return &ip
+}
+
+func (ip *Input) File() *os.File { return ip.file }
+func (ip *Input) Name() string { return ip.name }
+func (ip *Input) Err() error { return ip.err }
+func (ip *Input) Sep() string { return string(ip.sep) }
+
+func (ip *Input) label(record string) {
+ if ip.fieldPattern != nil {
+ ip.fields = ip.fieldPattern.FindAllString(record, -1)
+ d := ip.fieldPattern.Split(record, -1)
+ ip.delims = d[min(len(d)-1, 1):len(d)-1]
+ } else {
+ ip.delims = ip.delimPattern.FindAllString(record, -1)
+ ip.fields = ip.delimPattern.Split(record, -1)
+ }
+}
+
+func (ip *Input) unlabel() {
+ ip.overridden = false
+ ip.fields = nil
+ ip.delims = nil
+}
+
+func (ip *Input) RecordPattern(pat string) {
+ ip.usingSeperator = false
+ fn := ScanRegex(pat, &ip.sep)
+ ip.recordSplitfunc = fn
+ if ip.scanner != nil {
+ ip.scanner = NewMyScanner(ip.file)
+ ip.scanner.Split(ip.recordSplitfunc)
+ }
+}
+func (ip *Input) SeparatorPattern(pat string) {
+ ip.usingSeperator = true
+ fn := ScanRegexSeparated(pat, &ip.sep)
+ ip.recordSplitfunc = fn
+ if ip.scanner != nil {
+ ip.scanner = NewMyScanner(ip.file)
+ ip.scanner.Split(ip.recordSplitfunc)
+ }
+}
+
+func (ip *Input) FieldPattern(pat string) {
+ re := mustCompile(pat)
+ ip.fieldPattern = re
+ ip.delimPattern = nil
+ ip.label(ip.Record())
+}
+func (ip *Input) DelimPattern(pat string) {
+ re := mustCompile(pat)
+ ip.delimPattern = re
+ ip.fieldPattern = nil
+ ip.label(ip.Record())
+}
+
+// Tries to take an arg from the Args stack and open it as the new input file.
+// Returns true if there was an arg to take.
+// Err() will give any error opening the file.
+func (ip *Input) NextFile() bool {
+ ip.overridden = false
+ ip.err = nil
+ ip.file.Close()
+ if path, ok := ip.pop(); ok {
+ ip.name = path
+ ip.file, ip.err = openRegularFile(path)
+ if ip.err != nil { return true }
+ } else {
+ if ip.started { return false }
+ ip.file = os.Stdin
+ ip.name = os.Stdin.Name()
+ }
+ ip.started = true
+ ip.scanner = NewMyScanner(ip.file)
+ ip.scanner.Split(ip.recordSplitfunc)
+ return true
+}
+
+func (ip *Input) Scan() bool {
+ ip.overridden = false
+ ip.unlabel()
+ if ip.scanner == nil { return false }
+ if ip.scanner.Scan() { return true }
+ if ip.scanner.Err() != nil { FatalWithoutTraceback(ip.scanner.Err().Error()) }
+ ip.file = nil
+ if ip.usingSeperator {
+ // Make the record the skipped text that wasn't terminated by a separator
+ ip.label(string(ip.sep))
+ ip.overridden = true
+ ip.sep = nil
+ }
+ return false
+}
+
+func (ip *Input) joinedParts() string {
+ if len(ip.fields) == 0 { return "" }
+ var sb strings.Builder
+ for i := 0; i < len(ip.delims); i++ {
+ sb.WriteString(ip.fields[i])
+ sb.WriteString(ip.delims[i])
+ }
+ sb.WriteString(ip.fields[len(ip.fields)-1])
+ return sb.String()
+}
+func (ip *Input) Record() string {
+ if ip.overridden { return ip.joinedParts() }
+ if ip.scanner == nil { return "" }
+ return ip.scanner.Text()
+}
+func (ip *Input) SetRecord(s string) {
+ ip.overridden = true
+ ip.label(s)
+}
+
+func rightpadslice(s []string, n int, padding string) []string {
+ for ; n > 0; n-- { s = append(s, padding) }
+ return s
+}
+func leftpadslice(s []string, n int, padding string) []string {
+ for ; n > 0; n-- { s = append([]string{ padding }, s...) }
+ return s
+}
+func (ip *Input) SetField(i int, s string) {
+ if ip.fields == nil { ip.label(ip.Record()) }
+ if i < 0 { i += len(ip.fields) }
+ if i >= 0 {
+ ip.fields = rightpadslice(ip.fields, i-len(ip.delims), "")
+ ip.delims = rightpadslice(ip.delims, i-len(ip.delims), " ")
+ ip.fields[i] = s
+ } else {
+ ip.fields = leftpadslice(ip.fields, -i, "")
+ ip.delims = leftpadslice(ip.delims, -i, " ")
+ ip.fields[0] = s
+ }
+ ip.overridden = true
+}
+func (ip *Input) SetDelim(i int, s string) {
+ if ip.delims == nil { ip.label(ip.Record()) }
+ if i < 0 { i += len(ip.delims) }
+ if i >= 0 {
+ ip.fields = rightpadslice(ip.fields, i-len(ip.delims)+1, "")
+ ip.delims = rightpadslice(ip.delims, i-len(ip.delims)+1, " ")
+ ip.delims[i] = s
+ } else {
+ ip.fields = leftpadslice(ip.fields, -i+1, "")
+ ip.delims = leftpadslice(ip.delims, -i+1, " ")
+ ip.delims[0] = s
+ }
+ ip.overridden = true
+}
+
+func (ip *Input) Fields() []string {
+ if ip.fields == nil { ip.label(ip.Record()) }
+ return ip.fields
+}
+func (ip *Input) Field(i int) string {
+ if i < 0 { i += len(ip.Fields()) }
+ if i < 0 || i >= len(ip.Fields()) { return "" }
+ return ip.Fields()[i]
+}
+func (ip *Input) Delims() []string {
+ if ip.delims == nil { ip.label(ip.Record()) }
+ return ip.delims
+}
+func (ip *Input) Delim(i int) string {
+ if i < 0 { i += len(ip.Delims()) }
+ if i < 0 || i >= len(ip.Delims()) { return "" }
+ return ip.Delims()[i]
+}
+
+func (ip *Input) Bail() {
+ ip.unlabel()
+ ip.file = nil
+ ip.scanner = nil
+}
A => src/main.go +121 -0
@@ 1,121 @@
+package main
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+)
+
+var Tokens [][]string
+var Markers []int
+var MarkerNames map[int]string
+var Setup Settings
+var In *Input
+var Out *Output
+
+
+func init() {
+ var args []string
+ Tokens, Markers, MarkerNames, args, Setup = ParseCommand()
+
+ a := "Args"
+ Stacks[a] = make([]Element, len(args))
+ for i := range args { Stacks[a][i] = Str(args[i]) }
+
+ In = NewInput(func() (string, bool) {
+ if len(Stacks[a]) == 0 { return "", false }
+ var path Element
+ path, Stacks[a] = Stacks[a][0], Stacks[a][1:]
+ return string(path.Str()), true
+ })
+
+ Out = NewOutput()
+ if Setup.Buffered {
+ Out.Buffer()
+ }
+ if Setup.Inplace {
+ Out.Withhold()
+ }
+}
+
+
+var InterruptChannel = make(chan os.Signal, 1)
+var RedoFB bool
+
+func main() {
+
+ var body TraceFrame // the program after the first pass
+ // First pass
+ {
+ words := map[string]func() {
+ "#!": Builtins["#!"],
+ "#": Builtins["#"],
+ ":": Builtins[":"],
+ "B": func() { ExecuteBlock("B", getBlock()) },
+ "E": Builtins["E"],
+ "FB": Builtins["FB"],
+ "FE": Builtins["FE"],
+ "PE": Builtins["PE"],
+ "PRENUM": Builtins["PRENUM"],
+ "POSTNUM": Builtins["POSTNUM"],
+ "TOKEN": Builtins["TOKEN"],
+ }
+ setTraceToBlock("", Tokens)
+ for _, m := range Markers {
+ if Frame().li > m { continue }
+ if Frame().li < m { Frame().li = m }
+ for !exhausted() && doVocab(tok(), words) { next() }
+ }
+
+ AssertNoControlFlow()
+
+ if exhausted() && len(Events.E) == 0 && len(Events.FB) == 0 && len(Events.FE) == 0 {
+ PrematurelyFinish()
+ }
+
+ if !exhausted() { body = *Frame() }
+ }
+
+ signal.Notify(InterruptChannel, os.Interrupt, syscall.SIGTERM)
+ MAINLOOP:
+ for {
+ select {
+ case <-InterruptChannel:
+ PrematurelyFinish()
+ default:
+ if In.File() == nil {
+ if In.started {
+ ExecuteBlock("FE", Events.FE)
+ if Setup.Inplace && Out.WithheldNum() == 1 {
+ s, _ := Out.PopWithheld()
+ if err := os.WriteFile(In.Name(), []byte(s), 0644); err != nil {
+ FatalWithoutTraceback(err.Error())
+ }
+ Out.Withhold()
+ }
+ }
+
+ PrivateValues.N = 0
+ for {
+ if !In.NextFile() { break MAINLOOP }
+ RedoFB = false
+ ExecuteBlock("FB", Events.FB)
+ if !RedoFB { break }
+ }
+ if In.File() == nil {
+ FatalWithoutTraceback(In.Err().Error())
+ }
+ }
+
+ if In.Scan() {
+ Trace = []TraceFrame{ body }
+ PrivateValues.n++
+ PrivateValues.N++
+ Execute()
+ AssertNoControlFlow()
+ }
+ }
+ }
+
+ Finish()
+}
A => src/manipwords.go +38 -0
@@ 1,38 @@
+package main
+
+import "sort"
+
+func kill(elems []Element, indicies []int) []Element {
+ for i := range indicies {
+ if indicies[i] < 0 { indicies[i] = len(elems)+indicies[i] }
+ }
+ var s []Element
+ OUTER:
+ for ei := range elems {
+ for _, index := range indicies {
+ if index == len(elems)-1 - ei { continue OUTER }
+ }
+ s = append(s, elems[ei])
+ }
+ return s
+}
+
+func compose(elems []Element, indicies []int) []Element {
+ s := make([]Element, len(indicies))
+ for i := range indicies {
+ s[i] = getElement(elems, indicies[i], Str(""))
+ }
+ return s
+}
+
+func Sort(elems []Element, by [][]string) {
+ traceBackup := Trace
+ sort.SliceStable(elems, func(i, j int) bool {
+ setTraceToBlock("sort", by)
+ push(elems[i])
+ push(elems[j])
+ Execute()
+ return popBool()
+ })
+ Trace = traceBackup
+}
A => src/mathwords.go +98 -0
@@ 1,98 @@
+package main
+
+import (
+ "math"
+)
+
+func SliceMin(floats []float64) float64 {
+ if len(floats) == 0 { return 0.0 }
+ r := floats[0]
+ for _, f := range floats { r = math.Min(r, f) }
+ return r
+}
+func SliceMax(floats []float64) float64 {
+ if len(floats) == 0 { return 0.0 }
+ r := floats[0]
+ for _, f := range floats { r = math.Max(r, f) }
+ return r
+}
+
+func Sum(floats []float64) float64 {
+ if len(floats) == 0 { return 0.0 } // why?
+ r := 0.0
+ for _, f := range floats { r += f }
+ return r
+}
+func Mean(floats []float64) float64 {
+ return Sum(floats) / float64(len(floats))
+}
+
+func LogicalOr(elems []Element) Element {
+ for _, e := range elems { if e.Num() != 0 { return e } }
+ return Num(0)
+}
+func LogicalAnd(elems []Element) Element {
+ for _, e := range elems { if e.Num() == 0 { return Num(0) } }
+ return elems[len(elems)-1]
+}
+
+// TODO maybe do euclidean modulo
+func Modulo(a, b float64) float64 {
+ if b == 0 { ArithmeticFatal("division by zero") }
+ return a - b * math.Floor(a / b)
+}
+
+func Pow(a, b float64) float64 {
+ if a < 0 && b != math.Floor(b) { ArithmeticFatal("pow received negative number to fractional power") }
+ if a == 0 && b < 0 { ArithmeticFatal("pow received zero to negative power") }
+ return math.Pow(a, b)
+}
+func Sqrt(n float64) float64 {
+ if n < 0 { ArithmeticFatal("sqrt received negative number") }
+ return math.Sqrt(n)
+}
+func Asin(n float64) float64 {
+ if n < -1 || n > 1 { ArithmeticFatal("asin received out of range number") }
+ return math.Asin(n)
+}
+func Acos(n float64) float64 {
+ if n < -1 || n > 1 { ArithmeticFatal("acos received out of range number") }
+ return math.Acos(n)
+}
+func Log(n float64) float64 {
+ if n == 0 { ArithmeticFatal("log received zero") }
+ if n < 0 { ArithmeticFatal("log received negative argument") }
+ return math.Log(n)
+}
+func Exp(n float64) float64 {
+ r := math.Exp(n)
+ if math.IsInf(r, 1) { r = math.MaxFloat64 }
+ return r
+}
+
+func BitwiseAnd(a, b int) int {
+ if (a < 0 && b > 0) { return -a & b }
+ if (b < 0 && a > 0) { return a & -b }
+ if (a < 0 && b < 0) { return -(-a & -b) }
+ return a & b
+}
+func BitwiseOr(a, b int) int {
+ if (a < 0 && b > 0) { return -(-a | b) }
+ if (b < 0 && a > 0) { return -(a | -b) }
+ if (a < 0 && b < 0) { return -(-a | -b) }
+ return a | b
+}
+func BitwiseXor(a, b int) int {
+ if (a < 0 && b > 0) { return -(-a | b) }
+ if (b < 0 && a > 0) { return -(a | -b) }
+ if (a < 0 && b < 0) { return -a | -b }
+ return a | b
+}
+
+func elementwise(s1, s2 []float64, op func(float64,float64) float64) []float64 {
+ s := make([]float64, max(len(s1), len(s2)))
+ for i := range s {
+ s[i] = op(get(s1, i, 0), get(s2, i, 0))
+ }
+ return s
+}
A => src/myscanner.go +28 -0
@@ 1,28 @@
+package main
+
+import (
+ "bufio"
+ "io"
+)
+
+type MyScanner struct {
+ *bufio.Scanner
+}
+
+func NewMyScanner(r io.Reader) *MyScanner {
+ return &MyScanner{ bufio.NewScanner(r) }
+}
+
+func (sc *MyScanner) Scan() bool {
+ c := make(chan bool, 1)
+ go func(){
+ c <- sc.Scanner.Scan()
+ }()
+ select {
+ case <-InterruptChannel:
+ PrematurelyFinish()
+ case result := <-c:
+ return result
+ }
+ panic("unreachable")
+}
A => src/optparse.go +84 -0
@@ 1,84 @@
+package main
+
+import (
+ "strings"
+ "git.sr.ht/~geb/opt"
+)
+
+type OptstringError struct { message string }
+func (e *OptstringError) Error() string { return e.message }
+
+func optparse(optstring string, strs []string) (map[string]Element, []string, error) {
+ o := opt.NewOptionSet()
+ m := map[string]Element{}
+ // TODO don't fatal on repeats (redefinitions)
+ for _, field := range strings.Fields(optstring) {
+ name, symbol := field[:len(field)-1], field[len(field)-1]
+ if symbol == '?' {
+ o.BoolFunc(name, func(b bool) error {
+ if b { m[name] = Num(1) } else { m[name] = Num(0) }
+ return nil
+ })
+ } else if symbol == ':' {
+ o.Func(name, func(s string) error {
+ m[name] = Str(s)
+ return nil
+ })
+ } else {
+ return nil, nil, &OptstringError{"optstring field missing argument specifier: " + field}
+ }
+ }
+
+ if err := o.Parse(true, strs); err != nil {
+ return nil, nil, err
+ }
+
+ return m, o.Args(), nil
+}
+
+func optclean(optstring string, arguments []string) ([]string, error) {