summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Cerqueira <dan.git@lispclub.com>2025-05-28 12:37:55 +0100
committerDaniel Cerqueira <dan.git@lispclub.com>2025-05-28 12:37:55 +0100
commit1c217ee6ac5aacd2f37b1d0e69a71ac94afd5acd (patch)
treea808042b0a0c4ea180c128cc49e2198bb29850a4
Init lali programming language.
-rw-r--r--.gitignore4
-rw-r--r--COPYING674
-rw-r--r--LICENSES/GFDL-1.3-or-later.txt451
-rw-r--r--LICENSES/GPL-3.0-or-later.txt674
-rw-r--r--LICENSES/Unlicense.txt24
-rw-r--r--Makefile94
-rw-r--r--README.md377
-rw-r--r--TAGS106
-rw-r--r--THANKS5
-rw-r--r--TODO38
-rw-r--r--examples/hanoi.lali21
-rw-r--r--examples/mandelbrot.lali30
-rw-r--r--lali.c160
-rw-r--r--liblali.c1404
-rw-r--r--liblali.h362
-rw-r--r--test/main.lali40
-rwxr-xr-xtest/other/executables/emacs.el3
-rwxr-xr-xtest/other/executables/empty.lisp0
-rwxr-xr-xtest/other/executables/script.lali4
-rw-r--r--test/other/lexical.lali15
-rw-r--r--test/other/mundo.lali4
-rw-r--r--test/other/ola.lali3
-rw-r--r--test/other/universal.lali1
-rw-r--r--work-in-progress/Makefile94
-rw-r--r--work-in-progress/TAGS104
-rw-r--r--work-in-progress/lali.c173
-rw-r--r--work-in-progress/liblali.c1339
-rw-r--r--work-in-progress/liblali.h337
28 files changed, 6541 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7c502a1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+lali
+liblali.a
+*.o
+README.html
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/COPYING
@@ -0,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>.
diff --git a/LICENSES/GFDL-1.3-or-later.txt b/LICENSES/GFDL-1.3-or-later.txt
new file mode 100644
index 0000000..857214d
--- /dev/null
+++ b/LICENSES/GFDL-1.3-or-later.txt
@@ -0,0 +1,451 @@
+
+ GNU Free Documentation License
+ Version 1.3, 3 November 2008
+
+
+ Copyright (C) 2000, 2001, 2002, 2007, 2008 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.
+
+0. PREAMBLE
+
+The purpose of this License is to make a manual, textbook, or other
+functional and useful document "free" in the sense of freedom: to
+assure everyone the effective freedom to copy and redistribute it,
+with or without modifying it, either commercially or noncommercially.
+Secondarily, this License preserves for the author and publisher a way
+to get credit for their work, while not being considered responsible
+for modifications made by others.
+
+This License is a kind of "copyleft", which means that derivative
+works of the document must themselves be free in the same sense. It
+complements the GNU General Public License, which is a copyleft
+license designed for free software.
+
+We have designed this License in order to use it for manuals for free
+software, because free software needs free documentation: a free
+program should come with manuals providing the same freedoms that the
+software does. But this License is not limited to software manuals;
+it can be used for any textual work, regardless of subject matter or
+whether it is published as a printed book. We recommend this License
+principally for works whose purpose is instruction or reference.
+
+
+1. APPLICABILITY AND DEFINITIONS
+
+This License applies to any manual or other work, in any medium, that
+contains a notice placed by the copyright holder saying it can be
+distributed under the terms of this License. Such a notice grants a
+world-wide, royalty-free license, unlimited in duration, to use that
+work under the conditions stated herein. The "Document", below,
+refers to any such manual or work. Any member of the public is a
+licensee, and is addressed as "you". You accept the license if you
+copy, modify or distribute the work in a way requiring permission
+under copyright law.
+
+A "Modified Version" of the Document means any work containing the
+Document or a portion of it, either copied verbatim, or with
+modifications and/or translated into another language.
+
+A "Secondary Section" is a named appendix or a front-matter section of
+the Document that deals exclusively with the relationship of the
+publishers or authors of the Document to the Document's overall
+subject (or to related matters) and contains nothing that could fall
+directly within that overall subject. (Thus, if the Document is in
+part a textbook of mathematics, a Secondary Section may not explain
+any mathematics.) The relationship could be a matter of historical
+connection with the subject or with related matters, or of legal,
+commercial, philosophical, ethical or political position regarding
+them.
+
+The "Invariant Sections" are certain Secondary Sections whose titles
+are designated, as being those of Invariant Sections, in the notice
+that says that the Document is released under this License. If a
+section does not fit the above definition of Secondary then it is not
+allowed to be designated as Invariant. The Document may contain zero
+Invariant Sections. If the Document does not identify any Invariant
+Sections then there are none.
+
+The "Cover Texts" are certain short passages of text that are listed,
+as Front-Cover Texts or Back-Cover Texts, in the notice that says that
+the Document is released under this License. A Front-Cover Text may
+be at most 5 words, and a Back-Cover Text may be at most 25 words.
+
+A "Transparent" copy of the Document means a machine-readable copy,
+represented in a format whose specification is available to the
+general public, that is suitable for revising the document
+straightforwardly with generic text editors or (for images composed of
+pixels) generic paint programs or (for drawings) some widely available
+drawing editor, and that is suitable for input to text formatters or
+for automatic translation to a variety of formats suitable for input
+to text formatters. A copy made in an otherwise Transparent file
+format whose markup, or absence of markup, has been arranged to thwart
+or discourage subsequent modification by readers is not Transparent.
+An image format is not Transparent if used for any substantial amount
+of text. A copy that is not "Transparent" is called "Opaque".
+
+Examples of suitable formats for Transparent copies include plain
+ASCII without markup, Texinfo input format, LaTeX input format, SGML
+or XML using a publicly available DTD, and standard-conforming simple
+HTML, PostScript or PDF designed for human modification. Examples of
+transparent image formats include PNG, XCF and JPG. Opaque formats
+include proprietary formats that can be read and edited only by
+proprietary word processors, SGML or XML for which the DTD and/or
+processing tools are not generally available, and the
+machine-generated HTML, PostScript or PDF produced by some word
+processors for output purposes only.
+
+The "Title Page" means, for a printed book, the title page itself,
+plus such following pages as are needed to hold, legibly, the material
+this License requires to appear in the title page. For works in
+formats which do not have any title page as such, "Title Page" means
+the text near the most prominent appearance of the work's title,
+preceding the beginning of the body of the text.
+
+The "publisher" means any person or entity that distributes copies of
+the Document to the public.
+
+A section "Entitled XYZ" means a named subunit of the Document whose
+title either is precisely XYZ or contains XYZ in parentheses following
+text that translates XYZ in another language. (Here XYZ stands for a
+specific section name mentioned below, such as "Acknowledgements",
+"Dedications", "Endorsements", or "History".) To "Preserve the Title"
+of such a section when you modify the Document means that it remains a
+section "Entitled XYZ" according to this definition.
+
+The Document may include Warranty Disclaimers next to the notice which
+states that this License applies to the Document. These Warranty
+Disclaimers are considered to be included by reference in this
+License, but only as regards disclaiming warranties: any other
+implication that these Warranty Disclaimers may have is void and has
+no effect on the meaning of this License.
+
+2. VERBATIM COPYING
+
+You may copy and distribute the Document in any medium, either
+commercially or noncommercially, provided that this License, the
+copyright notices, and the license notice saying this License applies
+to the Document are reproduced in all copies, and that you add no
+other conditions whatsoever to those of this License. You may not use
+technical measures to obstruct or control the reading or further
+copying of the copies you make or distribute. However, you may accept
+compensation in exchange for copies. If you distribute a large enough
+number of copies you must also follow the conditions in section 3.
+
+You may also lend copies, under the same conditions stated above, and
+you may publicly display copies.
+
+
+3. COPYING IN QUANTITY
+
+If you publish printed copies (or copies in media that commonly have
+printed covers) of the Document, numbering more than 100, and the
+Document's license notice requires Cover Texts, you must enclose the
+copies in covers that carry, clearly and legibly, all these Cover
+Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
+the back cover. Both covers must also clearly and legibly identify
+you as the publisher of these copies. The front cover must present
+the full title with all words of the title equally prominent and
+visible. You may add other material on the covers in addition.
+Copying with changes limited to the covers, as long as they preserve
+the title of the Document and satisfy these conditions, can be treated
+as verbatim copying in other respects.
+
+If the required texts for either cover are too voluminous to fit
+legibly, you should put the first ones listed (as many as fit
+reasonably) on the actual cover, and continue the rest onto adjacent
+pages.
+
+If you publish or distribute Opaque copies of the Document numbering
+more than 100, you must either include a machine-readable Transparent
+copy along with each Opaque copy, or state in or with each Opaque copy
+a computer-network location from which the general network-using
+public has access to download using public-standard network protocols
+a complete Transparent copy of the Document, free of added material.
+If you use the latter option, you must take reasonably prudent steps,
+when you begin distribution of Opaque copies in quantity, to ensure
+that this Transparent copy will remain thus accessible at the stated
+location until at least one year after the last time you distribute an
+Opaque copy (directly or through your agents or retailers) of that
+edition to the public.
+
+It is requested, but not required, that you contact the authors of the
+Document well before redistributing any large number of copies, to
+give them a chance to provide you with an updated version of the
+Document.
+
+
+4. MODIFICATIONS
+
+You may copy and distribute a Modified Version of the Document under
+the conditions of sections 2 and 3 above, provided that you release
+the Modified Version under precisely this License, with the Modified
+Version filling the role of the Document, thus licensing distribution
+and modification of the Modified Version to whoever possesses a copy
+of it. In addition, you must do these things in the Modified Version:
+
+A. Use in the Title Page (and on the covers, if any) a title distinct
+ from that of the Document, and from those of previous versions
+ (which should, if there were any, be listed in the History section
+ of the Document). You may use the same title as a previous version
+ if the original publisher of that version gives permission.
+B. List on the Title Page, as authors, one or more persons or entities
+ responsible for authorship of the modifications in the Modified
+ Version, together with at least five of the principal authors of the
+ Document (all of its principal authors, if it has fewer than five),
+ unless they release you from this requirement.
+C. State on the Title page the name of the publisher of the
+ Modified Version, as the publisher.
+D. Preserve all the copyright notices of the Document.
+E. Add an appropriate copyright notice for your modifications
+ adjacent to the other copyright notices.
+F. Include, immediately after the copyright notices, a license notice
+ giving the public permission to use the Modified Version under the
+ terms of this License, in the form shown in the Addendum below.
+G. Preserve in that license notice the full lists of Invariant Sections
+ and required Cover Texts given in the Document's license notice.
+H. Include an unaltered copy of this License.
+I. Preserve the section Entitled "History", Preserve its Title, and add
+ to it an item stating at least the title, year, new authors, and
+ publisher of the Modified Version as given on the Title Page. If
+ there is no section Entitled "History" in the Document, create one
+ stating the title, year, authors, and publisher of the Document as
+ given on its Title Page, then add an item describing the Modified
+ Version as stated in the previous sentence.
+J. Preserve the network location, if any, given in the Document for
+ public access to a Transparent copy of the Document, and likewise
+ the network locations given in the Document for previous versions
+ it was based on. These may be placed in the "History" section.
+ You may omit a network location for a work that was published at
+ least four years before the Document itself, or if the original
+ publisher of the version it refers to gives permission.
+K. For any section Entitled "Acknowledgements" or "Dedications",
+ Preserve the Title of the section, and preserve in the section all
+ the substance and tone of each of the contributor acknowledgements
+ and/or dedications given therein.
+L. Preserve all the Invariant Sections of the Document,
+ unaltered in their text and in their titles. Section numbers
+ or the equivalent are not considered part of the section titles.
+M. Delete any section Entitled "Endorsements". Such a section
+ may not be included in the Modified Version.
+N. Do not retitle any existing section to be Entitled "Endorsements"
+ or to conflict in title with any Invariant Section.
+O. Preserve any Warranty Disclaimers.
+
+If the Modified Version includes new front-matter sections or
+appendices that qualify as Secondary Sections and contain no material
+copied from the Document, you may at your option designate some or all
+of these sections as invariant. To do this, add their titles to the
+list of Invariant Sections in the Modified Version's license notice.
+These titles must be distinct from any other section titles.
+
+You may add a section Entitled "Endorsements", provided it contains
+nothing but endorsements of your Modified Version by various
+parties--for example, statements of peer review or that the text has
+been approved by an organization as the authoritative definition of a
+standard.
+
+You may add a passage of up to five words as a Front-Cover Text, and a
+passage of up to 25 words as a Back-Cover Text, to the end of the list
+of Cover Texts in the Modified Version. Only one passage of
+Front-Cover Text and one of Back-Cover Text may be added by (or
+through arrangements made by) any one entity. If the Document already
+includes a cover text for the same cover, previously added by you or
+by arrangement made by the same entity you are acting on behalf of,
+you may not add another; but you may replace the old one, on explicit
+permission from the previous publisher that added the old one.
+
+The author(s) and publisher(s) of the Document do not by this License
+give permission to use their names for publicity for or to assert or
+imply endorsement of any Modified Version.
+
+
+5. COMBINING DOCUMENTS
+
+You may combine the Document with other documents released under this
+License, under the terms defined in section 4 above for modified
+versions, provided that you include in the combination all of the
+Invariant Sections of all of the original documents, unmodified, and
+list them all as Invariant Sections of your combined work in its
+license notice, and that you preserve all their Warranty Disclaimers.
+
+The combined work need only contain one copy of this License, and
+multiple identical Invariant Sections may be replaced with a single
+copy. If there are multiple Invariant Sections with the same name but
+different contents, make the title of each such section unique by
+adding at the end of it, in parentheses, the name of the original
+author or publisher of that section if known, or else a unique number.
+Make the same adjustment to the section titles in the list of
+Invariant Sections in the license notice of the combined work.
+
+In the combination, you must combine any sections Entitled "History"
+in the various original documents, forming one section Entitled
+"History"; likewise combine any sections Entitled "Acknowledgements",
+and any sections Entitled "Dedications". You must delete all sections
+Entitled "Endorsements".
+
+
+6. COLLECTIONS OF DOCUMENTS
+
+You may make a collection consisting of the Document and other
+documents released under this License, and replace the individual
+copies of this License in the various documents with a single copy
+that is included in the collection, provided that you follow the rules
+of this License for verbatim copying of each of the documents in all
+other respects.
+
+You may extract a single document from such a collection, and
+distribute it individually under this License, provided you insert a
+copy of this License into the extracted document, and follow this
+License in all other respects regarding verbatim copying of that
+document.
+
+
+7. AGGREGATION WITH INDEPENDENT WORKS
+
+A compilation of the Document or its derivatives with other separate
+and independent documents or works, in or on a volume of a storage or
+distribution medium, is called an "aggregate" if the copyright
+resulting from the compilation is not used to limit the legal rights
+of the compilation's users beyond what the individual works permit.
+When the Document is included in an aggregate, this License does not
+apply to the other works in the aggregate which are not themselves
+derivative works of the Document.
+
+If the Cover Text requirement of section 3 is applicable to these
+copies of the Document, then if the Document is less than one half of
+the entire aggregate, the Document's Cover Texts may be placed on
+covers that bracket the Document within the aggregate, or the
+electronic equivalent of covers if the Document is in electronic form.
+Otherwise they must appear on printed covers that bracket the whole
+aggregate.
+
+
+8. TRANSLATION
+
+Translation is considered a kind of modification, so you may
+distribute translations of the Document under the terms of section 4.
+Replacing Invariant Sections with translations requires special
+permission from their copyright holders, but you may include
+translations of some or all Invariant Sections in addition to the
+original versions of these Invariant Sections. You may include a
+translation of this License, and all the license notices in the
+Document, and any Warranty Disclaimers, provided that you also include
+the original English version of this License and the original versions
+of those notices and disclaimers. In case of a disagreement between
+the translation and the original version of this License or a notice
+or disclaimer, the original version will prevail.
+
+If a section in the Document is Entitled "Acknowledgements",
+"Dedications", or "History", the requirement (section 4) to Preserve
+its Title (section 1) will typically require changing the actual
+title.
+
+
+9. TERMINATION
+
+You may not copy, modify, sublicense, or distribute the Document
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense, or distribute it is void, and
+will automatically terminate your rights under this License.
+
+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, receipt of a copy of some or all of the same material does
+not give you any rights to use it.
+
+
+10. FUTURE REVISIONS OF THIS LICENSE
+
+The Free Software Foundation may publish new, revised versions of the
+GNU Free Documentation 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. See
+https://www.gnu.org/licenses/.
+
+Each version of the License is given a distinguishing version number.
+If the Document specifies that a particular numbered version of this
+License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that specified version or
+of any later version that has been published (not as a draft) by the
+Free Software Foundation. If the Document does not specify a version
+number of this License, you may choose any version ever published (not
+as a draft) by the Free Software Foundation. If the Document
+specifies that a proxy can decide which future versions of this
+License can be used, that proxy's public statement of acceptance of a
+version permanently authorizes you to choose that version for the
+Document.
+
+11. RELICENSING
+
+"Massive Multiauthor Collaboration Site" (or "MMC Site") means any
+World Wide Web server that publishes copyrightable works and also
+provides prominent facilities for anybody to edit those works. A
+public wiki that anybody can edit is an example of such a server. A
+"Massive Multiauthor Collaboration" (or "MMC") contained in the site
+means any set of copyrightable works thus published on the MMC site.
+
+"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0
+license published by Creative Commons Corporation, a not-for-profit
+corporation with a principal place of business in San Francisco,
+California, as well as future copyleft versions of that license
+published by that same organization.
+
+"Incorporate" means to publish or republish a Document, in whole or in
+part, as part of another Document.
+
+An MMC is "eligible for relicensing" if it is licensed under this
+License, and if all works that were first published under this License
+somewhere other than this MMC, and subsequently incorporated in whole or
+in part into the MMC, (1) had no cover texts or invariant sections, and
+(2) were thus incorporated prior to November 1, 2008.
+
+The operator of an MMC Site may republish an MMC contained in the site
+under CC-BY-SA on the same site at any time before August 1, 2009,
+provided the MMC is eligible for relicensing.
+
+
+ADDENDUM: How to use this License for your documents
+
+To use this License in a document you have written, include a copy of
+the License in the document and put the following copyright and
+license notices just after the title page:
+
+ Copyright (c) YEAR YOUR NAME.
+ Permission is granted to copy, distribute and/or modify this document
+ under the terms of the GNU Free Documentation License, Version 1.3
+ or any later version published by the Free Software Foundation;
+ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+ A copy of the license is included in the section entitled "GNU
+ Free Documentation License".
+
+If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
+replace the "with...Texts." line with this:
+
+ with the Invariant Sections being LIST THEIR TITLES, with the
+ Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
+
+If you have Invariant Sections without Cover Texts, or some other
+combination of the three, merge those two alternatives to suit the
+situation.
+
+If your document contains nontrivial examples of program code, we
+recommend releasing these examples in parallel under your choice of
+free software license, such as the GNU General Public License,
+to permit their use in free software.
diff --git a/LICENSES/GPL-3.0-or-later.txt b/LICENSES/GPL-3.0-or-later.txt
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/LICENSES/GPL-3.0-or-later.txt
@@ -0,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>.
diff --git a/LICENSES/Unlicense.txt b/LICENSES/Unlicense.txt
new file mode 100644
index 0000000..68a49da
--- /dev/null
+++ b/LICENSES/Unlicense.txt
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f136822
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,94 @@
+# lali (Lali Another Lisp Implementation)
+#
+# Author: Daniel Cerqueira (dan.git@lispclub.com)
+# Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+# Version: 0.0
+# Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+# computer programming language
+# Homepage: https://gitlab.com/alexandre1985/lali
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# Copyright (C) 2025 Daniel Cerqueira
+#
+# This file is part of lali.
+#
+# lali 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/>.
+#
+#
+# lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/> version
+# from 2016, written by Matthias Pirstitz, which is released to public domain
+# under Unlicense <https://unlicense.org/>.
+
+
+PROGRAM := lali
+LIBOBJECTS := liblali.o
+LIB := lib$(PROGRAM).a
+OBJECTS := $(LIBOBJECTS) lali.o
+HTML := README.html
+
+ifeq ($(shell uname -s), Linux)
+CPPFLAGS += -D_DEFAULT_SOURCE -D_BSD_SOURCE
+endif
+
+CFLAGS += -std=c11 -Wall -pedantic
+
+PREFIX ?= /usr/local
+
+.PHONY: native
+native: CPPFLAGS += -DNDEBUG
+native: CFLAGS += -march=native -O2
+native: $(PROGRAM)
+
+.PHONY: generic
+generic: CPPFLAGS += -DNDEBUG
+generic: CFLAGS += -mtune=generic -O2
+generic: $(PROGRAM)
+
+.PHONY: all
+all: CPPFLAGS += -DNDEBUG
+all: CFLAGS += -march=native -O2
+all: $(PROGRAM) $(LIB) $(HTML)
+
+.PHONY: debug
+debug: CPPFLAGS += -DDEBUG
+debug: CFLAGS += -g
+debug: LDFLAGS += -g
+debug: $(PROGRAM)
+
+.PHONY: clean
+clean:
+ $(RM) $(PROGRAM) $(OBJECTS) $(LIB) $(HTML)
+
+.PHONY: lib
+lib: $(LIB)
+
+$(LIB): $(LIBOBJECTS)
+ $(AR) rc $@ $(LIBOBJECTS)
+ ranlib $@
+
+$(PROGRAM): $(OBJECTS)
+ $(CC) $(LDFLAGS) $(OBJECTS) -o $@
+# $(CC) $(LDFLAGS) lali.o -L. -l$(PROGRAM) -o $@
+
+%.o: %.c %.h
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
+
+.PHONY: html
+html: $(HTML)
+
+%.html: %.md
+ md2html -f -o $@ $<
+
+.PHONY: install
+install: native
+ install -D -m 311 -t $(PREFIX)/bin $(PROGRAM)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d193110
--- /dev/null
+++ b/README.md
@@ -0,0 +1,377 @@
+<!--
+lali (Lali Another Lisp Implementation)
+
+Author: Daniel Cerqueira (dan.git@lispclub.com)
+Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+Version: 0.0
+Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+ computer programming language
+Homepage: https://gitlab.com/alexandre1985/lali
+SPDX-License-Identifier: GFDL-1.3-or-later
+
+Copyright (C) 2025 Daniel Cerqueira
+
+This file is part of lali.
+
+Permission is granted to copy, distribute and/or modify this document under the
+terms of the GNU Free Documentation License, Version 1.3 or any later version
+published by the Free Software Foundation; with no Invariant Sections, no
+Front-Cover Texts, and no Back-Cover Texts.
+
+You should have received a copy of the GNU Free Documentation License along with
+this program; if not, see <https://www.gnu.org/licenses/>.
+
+
+lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/> version
+from 2016, written by Matthias Pirstitz, which is released to public domain under
+Unlicense <https://unlicense.org/>.
+-->
+
+# lali (Lali Another Lisp Implementation)
+
+lali is a small implementation of LISP, derived from tiny-lisp, written in
+standard C11. It has features such as macros, tail recursion and a copying garbage
+collector.
+
+lali extends and improves the original LISP. It tries to stay connected to the
+roots, while been greatly powerful. Also, aims at bringing joy to the programmer's
+mind. What you write, becomes part of who you are.
+
+
+## Language features
+
+* read-eval-print loop
+* interpretation from stdin or file arguments
+* numbers, strings, symbols and lists
+* functions and macros
+* lexical scoping
+* scheme-style dotted parameter lists
+* scheme-style tail recursion
+* copying garbage collector
+
+### Numeric operations
+
+lali supports the five arithmetic operations `+`, `-`, `*`, `/`, `%` (with `%` the
+operands are automatically converted to integer) as well as the relational
+operations `!` (different), `<`, `>`:
+
+ (- 5) ; -5
+ (- 5 3.2 .6) ; 1.2
+ (< 2 4 6.8) ; t
+ (> 4 8) ; f
+ (! 4 4) ; f
+
+#### Random number operation
+
+`(random [number])` takes no or one number argument, returns a random number below
+`number` until 0.
+
+ (random) ; 2.1442e+09
+ (random 2) ; returns 0 or 1
+
+### Processing operations
+
+`(quote x)`, or `'x`, returns an expression without being processed:
+
+ (quote a) ; a
+ 'b ; b
+ (quote (a b c)) ; (a b c)
+ '(a b c) ; (a b c)
+
+`(say x)`, or `\x`, returns an expression without being processed, and with the
+first `car` as `()`. This result is useful when processing a list and wanting to
+do something at the beginning, because other cons cells should never have `()` in
+`car`:
+
+ (say a) ; (() . a)
+ \b ; (() . b)
+ (say (a b c)) ; (() a b c)
+ \(a b c) ; (() a b c)
+
+### List operations
+
+`(list ...)` returns a list containing the supplied arguments:
+
+ (list) ; ()
+ (list 1 2 3) ; (1 2 3)
+ (list 1 2 (+ 1 2)) ; (1 2 3)
+
+`(cons x y)` creates a new cons cell, the `car` of which is `x` and the `cdr` of
+which is `y`:
+
+ (cons 1 2) ; (1 . 2)
+ (cons 1 '(2)) ; (1 2)
+
+`(car x)`, `(cdr x)` return the `car` and `cdr` of a list respectively:
+
+ (car '(1 2 3)) ; 1
+ (cdr '(1 2 3)) ; (2 3)
+
+### Predicates
+
+`(dif x y)` returns `t` if `x` and `y` refers to different objects, or if they are
+numbers with different values, or if they are string objects with different
+contents:
+
+ (dif 1 1) ; f
+ (dif 1 2) ; t
+ (dif 'a 'a) ; f
+ (dif 'a 'b) ; t
+ (dif t t) ; f
+ (dif t f) ; t
+ (dif '(1 2) '(1 2)) ; t
+
+`(differ x y)` returns `t` if `x` and `y` are objects of different structure and
+each of their elements satisfy the `dif` predicate, otherwise `f`:
+
+ (differ '(1 2) '(1)) ; t
+ (differ '(1 2) '(1 2)) ; f
+
+`(space x)` returns `t` if `x` is an empty list `()`, otherwise `f`:
+
+ (space ()) ; t
+ (space 1) ; f
+ (space t) ; f
+ (space '(1 2)) ; f
+
+`(ap x)` returns `t` if `x` is "an existing thing" (not an empty list `()`),
+otherwise `f`:
+
+ (ap ()) ; f
+ (ap 1) ; t
+ (ap t) ; t
+ (ap '(1 2)) ; t
+
+`(atom x)` returns `t` if `x` is an atom (and not an empty list `()`), otherwise
+`f`:
+
+ (atom ()) ; f
+ (atom 1) ; t
+ (atom t) ; t
+ (atom '(1 2)) ; f
+
+`(listp x)` returns `t` if `x` is either an empty list `()`, or a list with
+atom(s), otherwise `f`:
+
+ (listp ()) ; t
+ (listp 1) ; f
+ (listp t) ; f
+ (listp '(1 2)) ; t
+
+`(consp x)` returns `t` if `x` is a list with atom(s), otherwise `f`:
+
+ (consp ()) ; f
+ (consp 1) ; f
+ (consp t) ; f
+ (consp '(1 2)) ; t
+
+`(zerop x)` returns `t` if `x` is the number zero, otherwise `f`:
+
+ (zerop 0) ; t
+ (zerop 1) ; f
+ (zerop t) ; error: t is not a number
+
+### Logical operations
+
+`(not x)` returns `t` if `x` is `f`, return `n` if `x` is `n`, otherwise returns
+`f`.
+
+`(and ...)` evaluates its arguments one at a time from left to right. As soon as
+any argument evaluates to `f`, `and` returns `f` without evaluating the remaining
+arguments. Otherwise, it returns the value produced by evaluating its last
+argument. If no arguments are supplied, `and` returns `t`:
+
+ (and) ; t
+ (and 1 2 3) ; 3
+ (and 1 f 3) ; f
+
+`(or ...)` evaluates its arguments one at a time from left to right. As soon as
+any argument does not evaluate to `f`, `or` returns its value without evaluating
+the remaining arguments. Otherwise, it returns `f`:
+
+ (or) ; f
+ (or 1 2 3) ; 1
+ (or f f 3) ; 3
+
+### Conditionals
+
+`(cond ...)` takes zero or more clauses, each of the form `(test [expr...])`.
+`cond` returns the result of evaluating `expr...` of the first clause for which
+`test` does not evaluate to `f` without evaluating the remaining clauses. If a
+clause does not supply `expr...`, the result of evaluating `test` is returned
+instead. If every `test` evaluates to `f`, or no clauses are given, `cond` returns
+`n`:
+
+ (cond) ; n
+ (cond (f 'Hello)
+ (t 'World)) ; World
+ (cond (f 'Hello)
+ ('World)) ; World
+ (cond (t 'Hello)
+ ('World)) ; Hello
+
+`(fill ...)` is similar to `cond`. But `fill` differs in its evaluation of `test`.
+When `test` does evaluate to `f` returns the result of evaluating `expr...`
+without evaluating the remaining clauses:
+
+ (fill) ; n
+ (fill (f 'Hello)
+ (t 'World)) ; Hello
+ (fill (f 'Hello)
+ ('World)) ; Hello
+ (fill (t 'Hello)
+ ('World)) ; World
+
+### Grouping functions
+
+`(prog ...)` takes zero or more expressions, and evaluates each expression, one
+after the other. Returns the evaluation of the last expression. Returns `n`, if no
+expression is given:
+
+ (prog) ; n
+ (prog 'Hello
+ (atom f)) ; t
+ (prog 'Hello
+ n
+ (car 'World)) ; error: World is not a list
+
+`(progs expr ...)` is similar to `prog`. But `progs` differs when the evaluation
+of any expression is equal to the symbol of the evaluation of `expr`, stopping
+evaluation of further expressions and returning the symbol of the evaluation of
+`expr` immediately:
+
+ (progs n) ; n
+ (progs n
+ 'Hello
+ (atom f)) ; t
+ (progs n
+ 'Hello
+ n
+ (car 'World)) ; n
+
+### Defining variables
+
+`(set expr-var-A expr-val-A ...)` binds the result of evaluating `expr-val-A` to
+the symbol of `expr-var-A` evaluated. More than one `expr-var`-`expr-val` pair can
+be given. `set` returns the value bound to the last symbol:
+
+ (set 'a 1
+ 'b 2) ; 2
+ a ; 1
+ b ; 2
+
+### Defining functions
+
+`(lambda params expr...)` creates an anonymous function with parameters `params`
+and body `expr...`. `params` can be `()`, a symbol, or a list of symbols. If
+`params` is a symbol or a dotted list of symbols, the function will accept a
+variable number of arguments:
+
+ ((lambda ()
+ 'Hello)) ; Hello
+ ((lambda (a b)
+ (+ a b)) 1 2) ; 3
+ ((lambda (a b . c)
+ c) 1 2 3 4 5) ; (3 4 5)
+ ((lambda args
+ args) 1 2 3 4 5) ; (1 2 3 4 5)
+
+`(defun name params expr...)` creates a function and binds it to the symbol
+`name`:
+
+ (defun plus1 (x)
+ (+ x 1)) ; #<Lambda (x)>
+ (plus1 2) ; 3
+
+### Defining macros
+
+`(macro params expr...)` creates an anonymous macro with parameters `params` and
+body `expr...`.
+
+`(defmacro name params expr...)` creates a macro and binds it to the symbol
+`name`.
+
+Macros are different from functions in that they do not evaluate their arguments
+when called. Instead, we can think of them as taking expressions as input and
+returning a new expression as output.
+
+Imagine we want to implement `(when test expr...)`:
+
+ (set 'x '(1 2 3)) ; (1 2 3)
+ (when (consp x)
+ (car x)) ; 1
+ (set 'x 'hello)
+ (when (consp x)
+ (car x)) ; n
+
+`when`, if implemented as a function, would not work correctly, since `expr...`
+would be evaluated as soon as `when` is called:
+
+ (set 'x 'Hello)
+ (when (consp x)
+ (car x)) ; error: Hello is not a list
+
+However, we can implement `when` as a macro that wraps `expr...` in a call to
+`if`:
+
+ (defmacro when (test . expr)
+ (list cond (list test (cons 'prog expr))))
+
+`(when (consp x) (car x))` would then produce the expression
+`(cond ((consp x) (prog (car x))))` which yields the expected behaviour.
+
+### Input operation
+
+`(read)` takes zero arguments, and reads an expression from standard input:
+
+ (set 'a (read)) ; type (Hello World!) followed by an enter
+ a ; (Hello World!)
+
+### Eval operation
+
+`(eval expr)` takes one expression, and evaluates `expr`:
+
+ (eval t) ; t
+ (prog
+ (set 'a 'b)
+ (eval a)) ; error: b has no value
+ (prog
+ (set 'a 'b)
+ (eval 'a)) ; b
+ (eval '(atom 1)) ; t
+ (eval '(run it)) ; error: run has no value
+ (prog
+ (set 'a 'b)
+ (eval '(a it))) ; error: b is not a function
+
+### Output operations
+
+`(newline)` prints a newline to the standard output.
+
+`(print x)` prints `x` to the standard output, exactly the way it was entered:
+
+ (print '(Hello World!)) ; prints (Hello World!) followed by a space
+
+`(princ x)` is similar to `print`. But `princ` differs in its handling of lists
+outputting them without the initial/ending parenthesis:
+
+ (princ '(Hello World!)) ; prints Hello World! followed by a space
+
+### Time operation
+
+`(time)` gives a timestamp in (sec minute hour day month year dow dst utcoff).
+
+ (time) ; (23 54 17 13 5 2025 2 t 3600)
+
+## Copyright
+
+You can find the licenses of lali in the LICENSES/ directory.
+
+lali uses year ranges in its copyright text. For example, the year range
+"2025-2027" means years "2025, 2026, and 2027".
+
+
+## Contributions
+
+If you want to contribute, please send me (git) patches over email. My address is
+at the source's copyright notices. Thank you!
diff --git a/TAGS b/TAGS
new file mode 100644
index 0000000..59105de
--- /dev/null
+++ b/TAGS
@@ -0,0 +1,106 @@
+
+lali.c,85
+#define YEARS 37,1298
+void runFile(41,1416
+void runREPL(59,1816
+int main(87,2375
+
+liblali.c,3583
+#define MEMORY_SIZE 37,1298
+static Memory *memory memory39,1338
+Object *symbols symbols41,1389
+Object *nil nil42,1413
+Object *n n43,1469
+Object *t t44,1522
+Object *f f45,1575
+static unsigned int seed 47,1629
+jmp_buf exceptionEnv;49,1660
+void exceptionWithObject(54,1765
+Object *gcMoveObject(gcMoveObject114,4107
+void gc(136,4806
+size_t memoryAlign(183,6199
+Object *memoryAllocObject(memoryAllocObject187,6306
+Object *newObject(newObject216,7294
+Object *newObjectFrom(newObjectFrom220,7399
+Object *newNumber(newNumber226,7586
+Object *newObjectWithString(newObjectWithString232,7731
+Object *newStringWithLength(newStringWithLength239,7974
+Object *newString(newString267,8904
+Object *newCons(newCons271,9015
+Object *newSymbolWithLength(newSymbolWithLength278,9186
+Object *newSymbol(newSymbol293,9696
+Object *newObjectWithClosure(newObjectWithClosure297,9807
+Object *newLambda(newLambda318,10483
+Object *newMacro(newMacro322,10635
+Object *newPrimitive(newPrimitive326,10785
+Object *newEnv(newEnv333,10977
+int streamGetc(372,12345
+Stream *streamSeek(streamSeek440,14380
+int streamPeek(448,14557
+int readNext(457,14763
+int initialReadNext(468,14980
+int peekForward(479,15217
+int peekNext(486,15393
+int readWhile(490,15464
+Object *readUnary(readUnary499,15640
+Object *readString(readString512,16017
+int isSymbolChar(528,16547
+Object *readNumberOrSymbol(readNumberOrSymbol533,16672
+Object *reverseList(reverseList565,17640
+Object *readList(readList578,17840
+Object *readExpr(readExpr610,18830
+void writeObject(633,19549
+#define CASE(635,19637
+#undef CASE642,19948
+#define CASE(678,20949
+#undef CASE687,21348
+Object *envLookup(envLookup713,22169
+Object *envAdd(envAdd728,22526
+Object *envSet(envSet738,22829
+Object *primitiveSpace(primitiveSpace760,23413
+Object *primitiveAtom(primitiveAtom764,23506
+Object *primitiveDif(primitiveDif782,24090
+Object *primitiveCar(primitiveCar793,24495
+Object *primitiveCdr(primitiveCdr804,24731
+Object *primitiveCons(primitiveCons815,24967
+Object *primitivePrint(primitivePrint822,25143
+Object *primitivePrinc(primitivePrinc828,25284
+Object *primitiveNewline(primitiveNewline834,25426
+Object *primitiveRead(primitiveRead839,25516
+Object *primitiveTime(primitiveTime850,25762
+Object *primitiveRandom(primitiveRandom877,26414
+#define DEFINE_PRIMITIVE_ARITHMETIC(890,26794
+DEFINE_PRIMITIVE_ARITHMETIC(920,28871
+#define DEFINE_PRIMITIVE_RELATIONAL(926,29182
+typedef struct Primitive 954,30979
+ char *name;name955,31006
+ int nMinArgs,956,31020
+ int nMinArgs, nMaxArgs;956,31020
+ Object *(* eval)957,31046
+} Primitive;958,31091
+Primitive primitives[primitives960,31105
+ PRIMITIVE_EVAL,1000,32893
+ PRIMITIVE_QUOTE,1001,32911
+ PRIMITIVE_SAY,1002,32930
+ PRIMITIVE_SET,1003,32947
+ PRIMITIVE_PROG,1004,32964
+ PRIMITIVE_PROGS,1005,32982
+ PRIMITIVE_COND,1007,33023
+ PRIMITIVE_FILL,1008,33041
+ PRIMITIVE_LAMBDA,1009,33059
+ PRIMITIVE_MACRO1010,33079
+Object *evalSet(evalSet1021,33432
+Object *evalProg(evalProg1047,34162
+Object *progs1(progs11067,34584
+Object *evalProgs(evalProgs1093,35277
+Object *evalCond(evalCond1119,36027
+Object *evalFill(evalFill1142,36609
+Object *evalLambda(evalLambda1165,37191
+Object *evalMacro(evalMacro1172,37378
+Object *expandMacro(expandMacro1179,37563
+Object *expandMacroTo(expandMacroTo1189,37845
+Object *evalList(evalList1203,38235
+Object *evalExpr(evalExpr1217,38607
+#define LISP(1293,41772
+static char *stdlib stdlib1295,41804
+Object *newRootEnv(newRootEnv1351,43430
diff --git a/THANKS b/THANKS
new file mode 100644
index 0000000..2019932
--- /dev/null
+++ b/THANKS
@@ -0,0 +1,5 @@
+This project is based on tiny-lisp, originally written by Matthias Pirstitz.
+Modified and designed by Daniel Cerqueira. Other people also contributed. Here is
+a list of those people:
+
+alexlehm
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..0a55ffd
--- /dev/null
+++ b/TODO
@@ -0,0 +1,38 @@
+maybe make the repl use the readline library.
+create lisp code for addition.
+use secure functions.
+make quest a thing.
+remove macros?
+
+split, join.
+make unit testing.
+instead of returning "#<Lambda ..." it should return the expression.
+remove lambda_type and macro_type.
+
+
+fix a security bug of setting a primitive. (done)
+improve performance of progs1. (done)
+makes no sense to make progs accept a lambda argument. (because the evaluation of each step would require a fixing a symbol, such as fixing the symbol "t").
+add \b interpretation. (done)
+improve performance of evalProg. (done)
+(random n). (done)
+add math operator %. (done)
+make time. (done)
+makes no sense to remove numbers altogether, because symbols won't be neutral (as with previous badly thought plans).
+make list structure begin with an cell which has a "special" symbol in car (still don't know which symbol might be). then make a C predicate function to detect if the car is the beginning of a list (the end already is marked by a cdr of ()). (done)
+change from label to set. (done)
+makefile with lib. (done)
+update documentation. (done)
+makes no sense to remove strings altogether. (because of " ", "(", ")", or ".")
+create progs, a prog that stops if expr evaluates to n. (done)
+rename progn to prog. (done)
+make read function. (done)
+make princ functions not print the parentesis of a list. (done)
+support scripting with a shebang. (done)
+receive input from stdin. (done)
+make load code of argument files. (done)
+remove <= , >= and = . add !. (done)
+expose eval as a function. (done)
+add dif. remove eq. (done)
+add fill. (done)
+add n. (done)
diff --git a/examples/hanoi.lali b/examples/hanoi.lali
new file mode 100644
index 0000000..cf82382
--- /dev/null
+++ b/examples/hanoi.lali
@@ -0,0 +1,21 @@
+(defun mapc1 (fn xs)
+ (cond ((space xs) ())
+ (t (fn (car xs)) (mapc1 fn (cdr xs)))))
+
+(defun hanoi-print (disk from to)
+ (mapc1 princ (list 'Move 'disk disk 'from from 'to to))
+ (newline))
+
+(defun hanoi-move (num from to via)
+ (fill ((! num 1)
+ (hanoi-print num from to))
+ (f
+ (prog
+ (hanoi-move (- num 1) from via to)
+ (hanoi-print num from to)
+ (hanoi-move (- num 1) via to from)))))
+
+(defun hanoi (num)
+ (hanoi-move num 'L 'M 'R))
+
+(hanoi 3)
diff --git a/examples/mandelbrot.lali b/examples/mandelbrot.lali
new file mode 100644
index 0000000..d546f4f
--- /dev/null
+++ b/examples/mandelbrot.lali
@@ -0,0 +1,30 @@
+(defun for (from to func)
+ (cond ((< from to) (func from) (for (+ from 1) to func))))
+
+(set 'mandelbrot-chars
+ (list " " "." "," "`" "'" "\"" ":" ";" "-" "+" "o" "O" "0" "1"
+ "2" "3" "4" "5" "6" "7" "8" "9" "%" "*" "&" "$" "@" "#"))
+
+(defun mandelbrot-iter (x y x0 y0 i)
+ (fill ((! i 28) " ")
+ ((not (> (+ (* x0 x0) (* y0 y0)) 4)) (nth i mandelbrot-chars))
+ (f (mandelbrot-iter x y
+ (+ (- (* x0 x0) (* y0 y0)) x) (+ (* 2 x0 y0) y)
+ (+ i 1)))))
+
+(defun mandelbrot-char (x y)
+ (mandelbrot-iter x y
+ (+ (- (* x x) (* y y)) x) (+ (* 2 x y) y)
+ 0))
+
+(defun mandelbrot (xmin xmax ymin ymax)
+ (for 0 24 (lambda (py)
+ (for 0 80 (lambda (px)
+ (princ
+ (mandelbrot-char
+ (+ (* (/ px 80) (- xmax xmin)) xmin)
+ (+ (* (/ py 24) (- ymax ymin)) ymin)))))
+ (newline))))
+
+(mandelbrot -2.15 1.25 -1.25 1.25)
+; (mandelbrot -1.5 -1.1 -0.2 0.1)
diff --git a/lali.c b/lali.c
new file mode 100644
index 0000000..15e90a2
--- /dev/null
+++ b/lali.c
@@ -0,0 +1,160 @@
+/*
+ * lali (Lali Another Lisp Implementation)
+ *
+ * Author: Daniel Cerqueira (dan.git@lispclub.com)
+ * Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+ * Version: 0.0
+ * Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+ * computer programming language
+ * Homepage: https://gitlab.com/alexandre1985/lali
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Copyright (C) 2025 Daniel Cerqueira
+ *
+ * This file is part of lali.
+ *
+ * lali 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/>.
+ *
+ *
+ * lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/>
+ * version from 2016, written by Matthias Pirstitz, which is released to public
+ * domain under Unlicense <https://unlicense.org/>.
+ */
+
+
+#include "liblali.h"
+
+#define YEARS "2025"
+
+// MAIN ///////////////////////////////////////////////////////////////////////
+
+void runFile(int infd, Object **env, GC_PARAM) {
+ Stream stream = { STREAM_TYPE_FILE, .fd = infd };
+ GC_TRACE(gcObject, nil);
+
+ if (setjmp(exceptionEnv))
+ return;
+
+ // deal with shebang
+ if (peekForward(&stream, true) == EOF)
+ return;
+
+ do {
+ *gcObject = nil;
+ *gcObject = readExpr(&stream, GC_ROOTS);
+ evalExpr(gcObject, env, GC_ROOTS);
+ } while (peekNext(&stream) != EOF);
+}
+
+void runREPL(int infd, Object **env, GC_PARAM) {
+ Stream stream = { STREAM_TYPE_FILE, .fd = infd };
+ GC_TRACE(gcObject, nil);
+
+ for (;;) {
+ if (setjmp(exceptionEnv))
+ continue;
+
+ for (;;) {
+ *gcObject = nil;
+
+ fputs("lali> ", stdout);
+ fflush(stdout);
+
+ if (peekNext(&stream) == EOF) {
+ fputc('\n', stdout);
+ return;
+ }
+
+ *gcObject = readExpr(&stream, GC_ROOTS);
+ *gcObject = evalExpr(gcObject, env, GC_ROOTS);
+
+ writeObject(*gcObject, true, stdout);
+ fputc('\n', stdout);
+ }
+ }
+}
+
+int main(int argc, char *argv[]) {
+ int options = 0;
+ bool repl = false;
+ bool close = false;
+ bool quiet = false;
+
+ if (argc >= 2) {
+ if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+ fprintf(stderr, "basic usages:\nnormal: %s [file]...\nclose stdin: %s -c [file]...\nrun repl: %s -r [-q] [file]...\n\nin normal mode, reads from standard input.\nwhen present, load file(s) at startup. can be used as library file(s).\n", argv[0], argv[0], argv[0]);
+ return EXIT_SUCCESS;
+ } else if (!strcmp(argv[1], "-c")) {
+ options++;
+ close = true;
+ } else if (!strcmp(argv[1], "-r")) {
+ options++;
+ repl = true;
+ if (argc > 2 && !strcmp(argv[2], "-q")) {
+ options++;
+ quiet = true;
+ }
+ } else if (!strcmp(argv[1], "-q")) {
+ options++;
+ quiet = true;
+ if (argc > 2 && !strcmp(argv[2], "-r")) {
+ options++;
+ repl = true;
+ }
+ } else if (argv[1][0] == '-') {
+ fprintf(stderr, "%s: unrecognized option, `%s'\n", argv[0], argv[1]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ GC_PARAM = nil;
+
+ if (setjmp(exceptionEnv))
+ return EXIT_FAILURE;
+
+ symbols = nil;
+ symbols = newCons(&nil, &symbols, GC_ROOTS);
+ symbols = newCons(&n, &symbols, GC_ROOTS);
+ symbols = newCons(&t, &symbols, GC_ROOTS);
+ symbols = newCons(&f, &symbols, GC_ROOTS);
+
+ GC_TRACE(gcEnv, newRootEnv(GC_ROOTS));
+
+ int fd = 0;
+
+ if (argc > 1) {
+ for (int i = ++options; i < argc; i++) {
+ if ((fd = open(argv[i], O_RDONLY)) == -1) {
+ fprintf(stderr, "%s: open() failed, %s\n", argv[0], strerror(errno));
+ return EXIT_FAILURE;
+ }
+ runFile(fd, gcEnv, GC_ROOTS);
+ }
+ }
+
+ if (repl) {
+ if(!isatty(STDIN_FILENO)) {
+ fprintf(stderr, "%s: repl does not accept piping\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (!quiet)
+ fprintf(stdout, "lali Copyright (C) %s Daniel Cerqueira\n\nThis is free software: you are free to change and redistribute it,\nunder certain conditions.\nThis program comes with NO WARRANTY, to the extent permitted by law.\n", YEARS);
+
+ runREPL(STDIN_FILENO, gcEnv, GC_ROOTS);
+ }
+ else if (!close)
+ runFile(STDIN_FILENO, gcEnv, GC_ROOTS);
+
+ return EXIT_SUCCESS;
+}
diff --git a/liblali.c b/liblali.c
new file mode 100644
index 0000000..e2239dd
--- /dev/null
+++ b/liblali.c
@@ -0,0 +1,1404 @@
+/*
+ * lali (Lali Another Lisp Implementation)
+ *
+ * Author: Daniel Cerqueira (dan.git@lispclub.com)
+ * Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+ * Version: 0.0
+ * Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+ * computer programming language
+ * Homepage: https://gitlab.com/alexandre1985/lali
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Copyright (C) 2025 Daniel Cerqueira
+ *
+ * This file is part of lali.
+ *
+ * lali 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/>.
+ *
+ *
+ * lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/>
+ * version from 2016, written by Matthias Pirstitz, which is released to public
+ * domain under Unlicense <https://unlicense.org/>.
+ */
+
+
+#include "liblali.h"
+
+#define MEMORY_SIZE 3000000UL
+
+static Memory *memory = &(Memory){ MEMORY_SIZE };
+
+Object *symbols = NULL;
+Object *nil = &(Object){ TYPE_SYMBOL, .string = "()" };
+Object *n = &(Object){ TYPE_SYMBOL, .string = "n" };
+Object *t = &(Object){ TYPE_SYMBOL, .string = "t" };
+Object *f = &(Object){ TYPE_SYMBOL, .string = "f" };
+
+static unsigned int seed = 1;
+
+jmp_buf exceptionEnv;
+
+
+// EXCEPTION HANDLING /////////////////////////////////////////////////////////
+
+void exceptionWithObject(Object *object, char *format, ...) {
+ fputs("error: ", stderr);
+
+ if (object) {
+ writeObject(object, true, stderr);
+ fputc(' ', stderr);
+ }
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fputc('\n', stderr);
+
+ longjmp(exceptionEnv, 1);
+}
+
+// GARBAGE COLLECTION /////////////////////////////////////////////////////////
+
+/* This implements Cheney's copying garbage collector, with which memory is
+ * divided into two equal halves (semispaces): from- and to-space. From-space
+ * is where new objects are allocated, whereas to-space is used during garbage
+ * collection.
+ *
+ * When garbage collection is performed, objects that are still in use (live)
+ * are copied from from-space to to-space. To-space then becomes the new
+ * from-space and vice versa, thereby discarding all objects that have not
+ * been copied.
+ *
+ * Our garbage collector takes as input a list of root objects. Objects that
+ * can be reached by recursively traversing this list are considered live and
+ * will be moved to to-space. When we move an object, we must also update its
+ * pointer within the list to point to the objects new location in memory.
+ *
+ * However, this implies that our interpreter cannot use raw pointers to
+ * objects in any function that might trigger garbage collection (or risk
+ * causing a SEGV when accessing an object that has been moved). Instead,
+ * objects must be added to the list and then only accessed through the
+ * pointer inside the list.
+ *
+ * Thus, whenever we would have used a raw pointer to an object, we use a
+ * pointer to the pointer inside the list instead:
+ *
+ * function: pointer to pointer inside list (Object **)
+ * |
+ * v
+ * list of root objects: pointer to object (Object *)
+ * |
+ * v
+ * semispace: object in memory
+ *
+ * GC_ROOTS and GC_PARAM are used to pass the list from function to function.
+ *
+ * GC_TRACE adds an object to the list and declares a variable which points to
+ * the objects pointer inside the list.
+ *
+ * GC_TRACE(gcX, X): add object X to the list and declare Object **gcX
+ * to point to the pointer to X inside the list.
+ */
+
+Object *gcMoveObject(Object *object) {
+ // skip object if it is not within from-space (i.e. on the stack)
+ if (object < (Object *)memory->fromSpace
+ || object >= (Object *)((char *)memory->fromSpace + memory->fromOffset))
+ return object;
+
+ // if the object has already been moved, return its new location
+ if (object->type == (Type)-1)
+ return object->forward;
+
+ // copy object to to-space
+ Object *forward = (Object *)((char *)memory->toSpace + memory->toOffset);
+ memcpy(forward, object, object->size);
+ memory->toOffset += object->size;
+
+ // mark object as moved and set forwarding pointer
+ object->type = (Type)-1;
+ object->forward = forward;
+
+ return object->forward;
+}
+
+void gc(GC_PARAM) {
+ memory->toOffset = 0;
+
+ // move symbols and root objects
+ symbols = gcMoveObject(symbols);
+
+ for (Object *object = GC_ROOTS; object != nil; object = object->cdr)
+ object->car = gcMoveObject(object->car);
+
+ // iterate over objects in to-space and move all objects they reference
+ for (Object *object = memory->toSpace;
+ object < (Object *)((char *)memory->toSpace + memory->toOffset);
+ object = (Object *)((char *)object + object->size)) {
+
+ switch (object->type) {
+ case TYPE_NUMBER:
+ case TYPE_STRING:
+ case TYPE_SYMBOL:
+ case TYPE_PRIMITIVE:
+ break;
+ case TYPE_CONS:
+ object->car = gcMoveObject(object->car);
+ object->cdr = gcMoveObject(object->cdr);
+ break;
+ case TYPE_LAMBDA:
+ case TYPE_MACRO:
+ object->params = gcMoveObject(object->params);
+ object->body = gcMoveObject(object->body);
+ object->env = gcMoveObject(object->env);
+ break;
+ case TYPE_ENV:
+ object->parent = gcMoveObject(object->parent);
+ object->vars = gcMoveObject(object->vars);
+ object->vals = gcMoveObject(object->vals);
+ break;
+ }
+ }
+
+ // swap from- and to-space
+ void *swap = memory->fromSpace;
+ memory->fromSpace = memory->toSpace;
+ memory->toSpace = swap;
+ memory->fromOffset = memory->toOffset;
+}
+
+// MEMORY MANAGEMENT //////////////////////////////////////////////////////////
+
+size_t memoryAlign(size_t size, size_t alignment) {
+ return (size + alignment - 1) & ~(alignment - 1);
+}
+
+Object *memoryAllocObject(Type type, size_t size, GC_PARAM) {
+ size = memoryAlign(size, sizeof (void *));
+
+ // allocate from- and to-space
+ if (!memory->fromSpace) {
+ if (!(memory->fromSpace = mmap(NULL, memory->capacity * 2,
+ PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)))
+ exception("mmap() failed, %s", strerror(errno));
+
+ memory->toSpace = (char *)memory->fromSpace + memory->capacity;
+ }
+
+ // run garbage collection if capacity exceeded
+ if (memory->fromOffset + size >= memory->capacity)
+ gc(GC_ROOTS);
+ if (memory->fromOffset + size >= memory->capacity)
+ exception("out of memory, %lu bytes", (unsigned long)size);
+
+ // allocate object in from-space
+ Object *object = (Object *)((char *)memory->fromSpace + memory->fromOffset);
+ object->type = type;
+ object->size = size;
+ memory->fromOffset += size;
+
+ return object;
+}
+
+// CONSTRUCTING OBJECTS ///////////////////////////////////////////////////////
+
+Object *newObject(Type type, GC_PARAM) {
+ return memoryAllocObject(type, sizeof (Object), GC_ROOTS);
+}
+
+Object *newObjectFrom(Object **from, GC_PARAM) {
+ Object *object = memoryAllocObject((*from)->type, (*from)->size, GC_ROOTS);
+ memcpy(object, *from, (*from)->size);
+ return object;
+}
+
+Object *newNumber(double number, GC_PARAM) {
+ Object *object = newObject(TYPE_NUMBER, GC_ROOTS);
+ object->number = number;
+ return object;
+}
+
+Object *newObjectWithString(Type type, size_t size, GC_PARAM) {
+ size = (size > sizeof (((Object *)NULL)->string))
+ ? size - sizeof (((Object *)NULL)->string)
+ : 0;
+ return memoryAllocObject(type, sizeof (Object) + size, GC_ROOTS);
+}
+
+Object *newStringWithLength(char *string, size_t length, GC_PARAM) {
+ int nEscapes = 0;
+
+ for (int i = 1; i < length; ++i)
+ if (string[i - 1] == '\\' && strchr("\\\"trn", string[i]))
+ ++i, ++nEscapes;
+
+ Object *object = newObjectWithString(TYPE_STRING,
+ length - nEscapes + 1, GC_ROOTS);
+
+ for (int r = 1, w = 0; r <= length; ++r) {
+ if (string[r - 1] == '\\' && r < length) {
+ switch (string[r]) {
+ case '\\': object->string[w++] = '\\'; r++; break;
+ case '"': object->string[w++] = '"'; r++; break;
+ case 't': object->string[w++] = '\t'; r++; break;
+ case 'r': object->string[w++] = '\r'; r++; break;
+ case 'b': object->string[w++] = '\b'; r++; break;
+ case 'n': object->string[w++] = '\n'; r++; break;
+ default: object->string[w++] = '\\'; break;
+ }
+ } else
+ object->string[w++] = string[r - 1];
+ }
+
+ object->string[length - nEscapes] = '\0';
+ return object;
+}
+
+Object *newString(char *string, GC_PARAM) {
+ return newStringWithLength(string, strlen(string), GC_ROOTS);
+}
+
+Object *newCons(Object **car, Object **cdr, GC_PARAM) {
+ Object *object = newObject(TYPE_CONS, GC_ROOTS);
+ object->car = *car;
+ object->cdr = *cdr;
+ return object;
+}
+
+Object *newSymbolWithLength(char *string, size_t length, GC_PARAM) {
+ for (Object *object = symbols; object != nil; object = object->cdr)
+ if (memcmp(object->car->string, string, length) == 0
+ && object->car->string[length] == '\0')
+ return object->car;
+
+ GC_TRACE(gcObject, newObjectWithString(TYPE_SYMBOL, length + 1, GC_ROOTS));
+ memcpy((*gcObject)->string, string, length);
+ (*gcObject)->string[length] = '\0';
+
+ symbols = newCons(gcObject, &symbols, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *newSymbol(char *string, GC_PARAM) {
+ return newSymbolWithLength(string, strlen(string), GC_ROOTS);
+}
+
+Object *newObjectWithClosure(Type type, Object **params, Object **body, Object **env, GC_PARAM) {
+ Object *list;
+
+ for (list = *params; list->type == TYPE_CONS; list = list->cdr) {
+ if (list->car->type != TYPE_SYMBOL)
+ exceptionWithObject(list->car, "is not a symbol");
+ if (list->car == nil || list->car == n || list->car == t || list->car == f)
+ exceptionWithObject(list->car, "cannot be used as a parameter");
+ }
+
+ if (list != nil && list->type != TYPE_SYMBOL)
+ exceptionWithObject(list, "is not a symbol");
+
+ Object *object = newObject(type, GC_ROOTS);
+
+ object->params = *params;
+ object->body = *body;
+ object->env = *env;
+ return object;
+}
+
+Object *newLambda(Object **params, Object **body, Object **env, GC_PARAM) {
+ return newObjectWithClosure(TYPE_LAMBDA, params, body, env, GC_ROOTS);
+}
+
+Object *newMacro(Object **params, Object **body, Object **env, GC_PARAM) {
+ return newObjectWithClosure(TYPE_MACRO, params, body, env, GC_ROOTS);
+}
+
+Object *newPrimitive(int primitive, char *name, GC_PARAM) {
+ Object *object = newObject(TYPE_PRIMITIVE, GC_ROOTS);
+ object->primitive = primitive;
+ object->name = name;
+ return object;
+}
+
+Object *newEnv(Object **func, Object **vals, GC_PARAM) {
+ Object *object = newObject(TYPE_ENV, GC_ROOTS);
+
+ if ((*func) == nil)
+ object->parent = object->vars = object->vals = nil;
+ else {
+ Object *param = (*func)->params, *val = *vals;
+
+ for (int nArgs = 0;; param = param->cdr, val = val->cdr, ++nArgs) {
+ if (param == nil && val == nil)
+ break;
+ else if (param != nil && param->type == TYPE_SYMBOL)
+ break;
+ else if (val != nil && val->type != TYPE_CONS)
+ exceptionWithObject(val, "is not a list");
+ else if (param == nil && val != nil)
+ exceptionWithObject(*func, "expects at most %d arguments", nArgs);
+ else if (param != nil && val == nil) {
+ for (; param->type == TYPE_CONS; param = param->cdr, ++nArgs);
+ exceptionWithObject(*func, "expects at least %d arguments", nArgs);
+ }
+ }
+
+ object->parent = (*func)->env;
+ object->vars = (*func)->params;
+ object->vals = *vals;
+ }
+
+ return object;
+}
+
+// STREAM INPUT ///////////////////////////////////////////////////////////////
+
+/* The purpose of the stream functions is to provide an abstraction over file
+ * and string inputs. In order to accommodate the REPL, we need to be able to
+ * process character special files (such as stdin) character by character and
+ * evaluate expressions as they are being entered.
+ */
+
+int streamGetc(Stream *stream) {
+ if (stream->offset >= stream->length) {
+ switch (stream->type) {
+ case STREAM_TYPE_STRING:
+ // set length if a string was given but its length has not been set
+ if (!stream->length && stream->buffer && *stream->buffer) {
+ stream->length = strlen(stream->buffer);
+ return streamGetc(stream);
+ }
+
+ return EOF;
+
+ case STREAM_TYPE_FILE:
+ // if this is the first read, try to find the size of the file
+ if (!stream->buffer) {
+ struct stat st;
+
+ if (fstat(stream->fd, &st) == -1)
+ exception("fstat() failed, %s", strerror(errno));
+
+ if (S_ISREG(st.st_mode)) {
+ stream->size = st.st_size;
+
+ if (!(stream->buffer = malloc(stream->size)))
+ exception("out of memory, %ld bytes", (long)stream->size);
+
+ stream->capacity = stream->size;
+ } else
+ stream->size = -1;
+ }
+
+ // resize buffer to nearest multiple of BUFSIZ if capacity exceeded
+ if (stream->offset >= stream->capacity) {
+ char *buffer;
+ size_t capacity = stream->offset
+ ? (stream->offset / BUFSIZ + 1) * BUFSIZ
+ : BUFSIZ;
+
+ if (!(buffer = realloc(stream->buffer, capacity)))
+ exception("out of memory, %ld bytes", (long)capacity);
+
+ stream->buffer = buffer;
+ stream->capacity = capacity;
+ }
+
+ // read until offset reached
+ while (stream->length <= stream->offset) {
+ ssize_t nbytes = read(stream->fd, stream->buffer + stream->length,
+ stream->capacity - stream->length);
+
+ if (nbytes > 0)
+ stream->length += nbytes;
+ else if (nbytes < 0 && errno != EINTR)
+ exception("read() failed, %s", strerror(errno));
+
+ if (nbytes == 0 || stream->length == stream->size) {
+ stream->type = STREAM_TYPE_STRING;
+ return streamGetc(stream);
+ }
+ }
+
+ break;
+ }
+ }
+
+ return (unsigned char)stream->buffer[stream->offset++];
+}
+
+Stream *streamSeek(Stream *stream, int offset) {
+ if (offset < 0 && -offset >= stream->offset)
+ stream->offset = 0;
+ else
+ stream->offset += offset;
+ return stream;
+}
+
+int streamPeek(Stream *stream) {
+ int ch = streamGetc(stream);
+ if (ch != EOF)
+ streamSeek(stream, -1);
+ return ch;
+}
+
+// READING S-EXPRESSIONS //////////////////////////////////////////////////////
+
+int readNext(Stream *stream) {
+ for (;;) {
+ int ch = streamGetc(stream);
+ if (ch == ';')
+ while ((ch = streamGetc(stream)) != EOF && ch != '\n');
+ if (isspace(ch))
+ continue;
+ return ch;
+ }
+}
+
+int initialReadNext(Stream *stream) {
+ for (;;) {
+ int ch = streamGetc(stream);
+ if (ch == ';' || ch == '#')
+ while ((ch = streamGetc(stream)) != EOF && ch != '\n');
+ if (isspace(ch))
+ continue;
+ return ch;
+ }
+}
+
+int peekForward(Stream *stream, bool shebang) {
+ int ch = (shebang) ? initialReadNext(stream) : readNext(stream);
+ if (ch != EOF)
+ streamSeek(stream, -1);
+ return ch;
+}
+
+int peekNext(Stream *stream) {
+ return peekForward(stream, false);
+}
+
+int readWhile(Stream *stream, int (*predicate)(int ch)) {
+ for (;;) {
+ int ch = streamPeek(stream);
+ if (!predicate(ch))
+ return ch;
+ streamGetc(stream);
+ }
+}
+
+Object *readUnary(Stream *stream, char *symbol, GC_PARAM) {
+ if (peekNext(stream) == EOF)
+ exception("unexpected end of stream in %s", symbol);
+
+ GC_TRACE(gcSymbol, newSymbol(symbol, GC_ROOTS));
+ GC_TRACE(gcObject, readExpr(stream, GC_ROOTS));
+
+ *gcObject = newCons(gcObject, &nil, GC_ROOTS);
+ *gcObject = newCons(gcSymbol, gcObject, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *readString(Stream *stream, GC_PARAM) {
+ size_t offset = stream->offset;
+
+ for (bool isEscaped = false;;) {
+ int ch = streamGetc(stream);
+ if (ch == EOF)
+ exception("unexpected end of stream in string literal \"%.*s\"",
+ (int)(stream->offset - offset), stream->buffer + offset);
+ if (ch == '"' && !isEscaped)
+ return newStringWithLength(stream->buffer + offset,
+ stream->offset - offset - 1, GC_ROOTS);
+
+ isEscaped = (ch == '\\' && !isEscaped);
+ }
+}
+
+int isSymbolChar(int ch) {
+ static const char *valid = "!#$%&*+-./:<=>?@^_~";
+ return isalnum(ch) || strchr(valid, ch);
+}
+
+Object *readNumberOrSymbol(Stream *stream, GC_PARAM) {
+ size_t offset = stream->offset;
+ int ch = streamPeek(stream);
+
+ // skip optional leading sign
+ if (ch == '+' || ch == '-') {
+ streamGetc(stream);
+ ch = streamPeek(stream);
+ }
+
+ // try to read a number in integer or decimal format
+ if (ch == '.' || isdigit(ch)) {
+ if (isdigit(ch))
+ ch = readWhile(stream, isdigit);
+ if (!isSymbolChar(ch))
+ return newNumber(strtol(stream->buffer + offset, NULL, 10), GC_ROOTS);
+ if (ch == '.') {
+ ch = streamGetc(stream);
+ if (isdigit(streamPeek(stream))) {
+ ch = readWhile(stream, isdigit);
+ if (!isSymbolChar(ch))
+ return newNumber(strtod(stream->buffer + offset, NULL), GC_ROOTS);
+ }
+ }
+ }
+
+ // non-numeric character encountered, read a symbol
+ readWhile(stream, isSymbolChar);
+ return newSymbolWithLength(stream->buffer + offset,
+ stream->offset - offset, GC_ROOTS);
+}
+
+Object *reverseList(Object *list) {
+ Object *object = nil;
+
+ while (list != nil) {
+ Object *swap = list;
+ list = list->cdr;
+ swap->cdr = object;
+ object = swap;
+ }
+
+ return object;
+}
+
+Object *readList(Stream *stream, GC_PARAM) {
+ GC_TRACE(gcList, nil);
+ GC_TRACE(gcLast, nil);
+
+ for (;;) {
+ int ch = readNext(stream);
+ if (ch == EOF)
+ exception("unexpected end of stream in list");
+ else if (ch == ')')
+ return reverseList(*gcList);
+ else if (ch == '.' && !isSymbolChar(streamPeek(stream))) {
+ if (*gcLast == nil)
+ exception("unexpected dot at start of list");
+ if ((ch = peekNext(stream)) == ')')
+ exception("expected object at end of dotted list");
+ if (!(*gcLast = readExpr(stream, GC_ROOTS)))
+ exception("unexpected end of stream in dotted list");
+ if ((ch = peekNext(stream)) != ')')
+ exception("unexpected object at end of dotted list");
+
+ readNext(stream);
+ Object *list = reverseList(*gcList);
+ (*gcList)->cdr = *gcLast;
+
+ return list;
+ } else {
+ *gcLast = readExpr(streamSeek(stream, -1), GC_ROOTS);
+ *gcList = newCons(gcLast, gcList, GC_ROOTS);
+ }
+ }
+}
+
+Object *readExpr(Stream *stream, GC_PARAM) {
+ for (;;) {
+ int ch = readNext(stream);
+ if (ch == EOF)
+ return NULL;
+ else if (ch == '\'')
+ return readUnary(stream, "quote", GC_ROOTS);
+ else if (ch == '\\')
+ return readUnary(stream, "say", GC_ROOTS);
+ else if (ch == '"')
+ return readString(stream, GC_ROOTS);
+ else if (ch == '(')
+ return readList(stream, GC_ROOTS);
+ else if (isSymbolChar(ch)
+ && (ch != '.' || isSymbolChar(streamPeek(stream))))
+ return readNumberOrSymbol(streamSeek(stream, -1), GC_ROOTS);
+ else
+ exception("unexpected character, `%c'", ch);
+ }
+}
+
+// WRITING OBJECTS ////////////////////////////////////////////////////////////
+
+void writeObject(Object *object, bool readably, FILE *file) {
+ switch (object->type) {
+#define CASE(type, ...) \
+ case type: \
+ fprintf(file, __VA_ARGS__); \
+ break
+ CASE(TYPE_NUMBER, "%g", object->number);
+ CASE(TYPE_SYMBOL, "%s", object->string);
+ CASE(TYPE_PRIMITIVE, "#<Primitive %s>", object->name);
+#undef CASE
+ case TYPE_STRING:
+ if (readably) {
+ fputc('"', file);
+ for (char *string = object->string; *string; ++string) {
+ switch (*string) {
+ case '"': fputs("\\\"", file); break;
+ case '\t': fputs("\\t", file); break;
+ case '\r': fputs("\\r", file); break;
+ case '\b': fputs("\\b", file); break;
+ case '\n': fputs("\\n", file); break;
+ case '\\': fputs("\\\\", file); break;
+ default: fputc(*string, file); break;
+ }
+ }
+ fputc('"', file);
+ } else
+ fprintf(file, "%s", object->string);
+ break;
+ case TYPE_CONS:
+ if (readably)
+ fputc('(', file);
+ writeObject(object->car, true, file);
+ while (object->cdr != nil) {
+ object = object->cdr;
+ if (object->type == TYPE_CONS) {
+ fputc(' ', file);
+ writeObject(object->car, true, file);
+ } else {
+ fputs(" . ", file);
+ writeObject(object, true, file);
+ break;
+ }
+ }
+ if (readably)
+ fputc(')', file);
+ break;
+#define CASE(type, name, object) \
+ case type: \
+ fprintf(file, "#<%s ", name); \
+ writeObject(object, readably, file); \
+ fprintf(file, ">"); \
+ break
+ CASE(TYPE_LAMBDA, "Lambda", object->params);
+ CASE(TYPE_MACRO, "Macro", object->params);
+ CASE(TYPE_ENV, "Env", object->vars);
+#undef CASE
+ }
+}
+
+// ENVIRONMENT ////////////////////////////////////////////////////////////////
+
+/* An environment consists of a pointer to its parent environment (if any) and
+ * two parallel lists - vars and vals.
+ *
+ * Case 1 - vars is a regular list:
+ * vars: (a b c), vals: (1 2 3) ; a = 1, b = 2, c = 3
+ *
+ * Case 2 - vars is a dotted list:
+ * vars: (a b . c), vals: (1 2) ; a = 1, b = 2, c = nil
+ * vars: (a b . c), vals: (1 2 3) ; a = 1, b = 2, c = (3)
+ * vars: (a b . c), vals: (1 2 3 4 5) ; a = 1, b = 2, c = (3 4 5)
+ *
+ * Case 3 - vars is a symbol:
+ * vars: a, vals: nil ; a = nil
+ * vars: a, vals: (1) ; a = (1)
+ * vars: a, vals: (1 2 3) ; a = (1 2 3)
+ *
+ * Case 4 - vars and vals are both nil:
+ * vars: nil, vals: nil
+ */
+
+Object *envLookup(Object *var, Object *env) {
+ for (; env != nil; env = env->parent) {
+ Object *vars = env->vars, *vals = env->vals;
+
+ for (; vars->type == TYPE_CONS; vars = vars->cdr, vals = vals->cdr)
+ if (vars->car == var)
+ return vals->car;
+
+ if (vars == var)
+ return vals;
+ }
+
+ exceptionWithObject(var, "has no value");
+}
+
+Object *envAdd(Object **var, Object **val, Object **env, GC_PARAM) {
+ GC_TRACE(gcVars, newCons(var, &nil, GC_ROOTS));
+ GC_TRACE(gcVals, newCons(val, &nil, GC_ROOTS));
+
+ (*gcVars)->cdr = (*env)->vars, (*env)->vars = *gcVars;
+ (*gcVals)->cdr = (*env)->vals, (*env)->vals = *gcVals;
+
+ return *val;
+}
+
+Object *envSet(Object **var, Object **val, Object **env, GC_PARAM) {
+ GC_TRACE(gcEnv, *env);
+
+ for (;;) {
+ Object *vars = (*gcEnv)->vars, *vals = (*gcEnv)->vals;
+
+ for (; vars->type == TYPE_CONS; vars = vars->cdr, vals = vals->cdr) {
+ if (vars->car == *var)
+ return vals->car = *val;
+ if (vars->cdr == *var)
+ return vals->cdr = *val;
+ }
+
+ if ((*gcEnv)->parent == nil)
+ return envAdd(var, val, gcEnv, GC_ROOTS);
+ else
+ *gcEnv = (*gcEnv)->parent;
+ }
+}
+
+// PRIMITIVES /////////////////////////////////////////////////////////////////
+
+Object *primitiveSpace(Object **args, GC_PARAM) {
+ return ((*args)->car == nil) ? t : f;
+}
+
+Object *primitiveAtom(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ return (first != nil && first->type != TYPE_CONS) ? t : f;
+}
+
+/* Object *primitiveEq(Object **args, GC_PARAM) {
+ * Object *first = (*args)->car, *second = (*args)->cdr->car;
+ *
+ * if (first->type == TYPE_NUMBER && second->type == TYPE_NUMBER)
+ * return (first->number == second->number) ? t : f;
+ * else if (first->type == TYPE_STRING && second->type == TYPE_STRING)
+ * return !strcmp(first->string, second->string) ? t : f;
+ * else
+ * return (first == second) ? t : f;
+ * }
+ */
+
+Object *primitiveDif(Object **args, GC_PARAM) {
+ Object *first = (*args)->car, *second = (*args)->cdr->car;
+
+ if (first->type == TYPE_NUMBER && second->type == TYPE_NUMBER)
+ return (first->number != second->number) ? t : f;
+ else if (first->type == TYPE_STRING && second->type == TYPE_STRING)
+ return strcmp(first->string, second->string) ? t : f;
+ else
+ return (first != second) ? t : f;
+}
+
+Object *primitiveCar(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ if (first == nil)
+ return nil;
+ else if (first->type == TYPE_CONS)
+ return first->car;
+ else
+ exceptionWithObject(first, "is not a list");
+}
+
+Object *primitiveCdr(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ if (first == nil)
+ return nil;
+ else if (first->type == TYPE_CONS)
+ return first->cdr;
+ else
+ exceptionWithObject(first, "is not a list");
+}
+
+Object *primitiveCons(Object **args, GC_PARAM) {
+ GC_TRACE(gcFirst, (*args)->car);
+ GC_TRACE(gcSecond, (*args)->cdr->car);
+
+ return newCons(gcFirst, gcSecond, GC_ROOTS);
+}
+
+Object *primitivePrint(Object **args, GC_PARAM) {
+ writeObject((*args)->car, true, stdout);
+ fputc(' ', stdout);
+ return (*args)->car;
+}
+
+Object *primitivePrinc(Object **args, GC_PARAM) {
+ writeObject((*args)->car, false, stdout);
+ fputc(' ', stdout);
+ return (*args)->car;
+}
+
+Object *primitiveNewline(Object **args, GC_PARAM) {
+ fputc('\n', stdout);
+ return n;
+}
+
+Object *primitiveRead(Object **args, GC_PARAM) {
+ Stream stream = { STREAM_TYPE_FILE, .fd = STDIN_FILENO };
+
+ fflush(stdout);
+ if (peekNext(&stream) == EOF) {
+ fputc('\n', stdout);
+ return n;
+ }
+ return readExpr(&stream, GC_ROOTS);
+}
+
+Object *primitiveTime(Object **args, GC_PARAM) {
+ time_t secs;
+ struct tm *today;
+
+ time(&secs);
+ today = localtime(&secs);
+
+ GC_TRACE(gcSymbol, nil);
+ GC_TRACE(gcCons, nil);
+
+ *gcSymbol = newNumber(today->tm_gmtoff, GC_ROOTS);
+ *gcCons = newCons(gcSymbol, gcCons, GC_ROOTS);
+ *gcCons = newCons(today->tm_isdst ? &t : &f, gcCons, GC_ROOTS);
+
+ int tmp [7] = {
+ today->tm_wday, today->tm_year+1900, today->tm_mon+1, today->tm_mday,
+ today->tm_hour, today->tm_min, today->tm_sec
+ };
+
+ for (int i = 0; i < 7; i++) {
+ *gcSymbol = newNumber(tmp[i], GC_ROOTS);
+ *gcCons = newCons(gcSymbol, gcCons, GC_ROOTS);
+ }
+
+ return *gcCons;
+}
+
+Object *primitiveRandom(Object **args, GC_PARAM) {
+ srandom((unsigned int)(seed + time(NULL)));
+ double number = (double)random();
+ seed = (unsigned int)number;
+
+ if (*args == nil)
+ return newNumber(number, GC_ROOTS);
+ else
+ return
+ newNumber(((unsigned int)number % (unsigned int)(*args)->car->number),
+ GC_ROOTS);
+}
+
+#define DEFINE_PRIMITIVE_ARITHMETIC(name, op, init, datatype) \
+ Object *name(Object **args, GC_PARAM) { \
+ if (*args == nil) \
+ return newNumber(init, GC_ROOTS); \
+ else if ((*args)->car->type != TYPE_NUMBER) \
+ exceptionWithObject((*args)->car, "is not a number"); \
+ else { \
+ Object *object, *rest; \
+ \
+ if ((*args)->cdr == nil) { \
+ object = newNumber(init, GC_ROOTS); \
+ rest = *args; \
+ } else { \
+ GC_TRACE(gcFirst, (*args)->car); \
+ object = newObjectFrom(gcFirst, GC_ROOTS); \
+ rest = (*args)->cdr; \
+ } \
+ \
+ for (; rest != nil; rest = rest->cdr) { \
+ if (rest->car->type != TYPE_NUMBER) \
+ exceptionWithObject(rest->car, "is not a number"); \
+ \
+ object->number = \
+ (datatype)object->number op (datatype)rest->car->number; \
+ } \
+ \
+ return object; \
+ } \
+ }
+
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveAdd, +, 0, double)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveSubtract, -, 0, double)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveMultiply, *, 1, double)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveDivide, /, 1, double)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveRemainder, %, 1, int )
+
+#define DEFINE_PRIMITIVE_RELATIONAL(name, op, until_the_end) \
+ Object *name(Object **args, GC_PARAM) { \
+ if ((*args)->car->type != TYPE_NUMBER) \
+ exceptionWithObject((*args)->car, "is not a number"); \
+ else { \
+ Object *rest = *args; \
+ bool result = until_the_end; \
+ \
+ for (; (result == until_the_end) && rest->cdr != nil; \
+ rest = rest->cdr) { \
+ if (rest->cdr->car->type != TYPE_NUMBER) \
+ exceptionWithObject(rest->cdr->car, "is not a number"); \
+ else if (until_the_end) \
+ result &= rest->car->number op rest->cdr->car->number; \
+ else \
+ result = rest->car->number op rest->cdr->car->number; \
+ } \
+ return result ? t : f; \
+ } \
+ }
+
+DEFINE_PRIMITIVE_RELATIONAL(primitiveDifferent, !=, false)
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveEqual, ==, true) */
+DEFINE_PRIMITIVE_RELATIONAL(primitiveLess, < , true)
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveLessEqual, <=, true) */
+DEFINE_PRIMITIVE_RELATIONAL(primitiveGreater, > , true)
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveGreaterEqual, >=, true) */
+
+typedef struct Primitive {
+ char *name;
+ int nMinArgs, nMaxArgs;
+ Object *(* eval)(Object **args, GC_PARAM);
+} Primitive;
+
+Primitive primitives[] = {
+ { "eval", 1, 1 /* special form */ },
+ { "quote", 1, 1 /* special form */ },
+ { "say", 1, 1 /* special form */ },
+ { "set", 0, -2 /* special form */ },
+ { "prog", 0, -1 /* special form */ },
+ { "progs", 1, -1 /* special form */ },
+ /* { "if", 2, 3 /\* special form *\/ }, */
+ { "cond", 0, -1 /* special form */ },
+ { "fill", 0, -1 /* special form */ },
+ { "lambda", 1, -1 /* special form */ },
+ { "macro", 1, -1 /* special form */ },
+ { "space", 1, 1, primitiveSpace },
+ { "atom", 1, 1, primitiveAtom },
+ /* { "eq", 2, 2, primitiveEq }, */
+ { "dif", 2, 2, primitiveDif },
+ { "car", 1, 1, primitiveCar },
+ { "cdr", 1, 1, primitiveCdr },
+ { "cons", 2, 2, primitiveCons },
+ { "print", 1, 1, primitivePrint },
+ { "princ", 1, 1, primitivePrinc },
+ { "newline", 0, 0, primitiveNewline },
+ { "read", 0, 0, primitiveRead },
+ { "time", 0, 0, primitiveTime },
+ { "random", 0, 1, primitiveRandom },
+ { "+", 0, -1, primitiveAdd },
+ { "-", 1, -1, primitiveSubtract },
+ { "*", 0, -1, primitiveMultiply },
+ { "/", 1, -1, primitiveDivide },
+ { "%", 1, -1, primitiveRemainder },
+ /* { "=", 1, -1, primitiveEqual }, */
+ { "!", 1, -1, primitiveDifferent },
+ { "<", 1, -1, primitiveLess },
+ /* { "<=", 1, -1, primitiveLessEqual }, */
+ { ">", 1, -1, primitiveGreater },
+ /* { ">=", 1, -1, primitiveGreaterEqual } */
+};
+
+// Special forms handled by evalExpr. Must be in the same order as above.
+enum {
+ PRIMITIVE_EVAL,
+ PRIMITIVE_QUOTE,
+ PRIMITIVE_SAY,
+ PRIMITIVE_SET,
+ PRIMITIVE_PROG,
+ PRIMITIVE_PROGS,
+ /* PRIMITIVE_IF, */
+ PRIMITIVE_COND,
+ PRIMITIVE_FILL,
+ PRIMITIVE_LAMBDA,
+ PRIMITIVE_MACRO
+};
+
+// EVALUATION /////////////////////////////////////////////////////////////////
+
+/* Scheme-style tail recursive evaluation. evalProg, evalProgs, evalCond, and
+ * PRIMITIVE_EVAL, return the object in the tail recursive position to be
+ * evaluated by evalExpr. Macros are expanded in-place the first time they are
+ * evaluated.
+ */
+
+bool isSymbolAPrimitive(Object *symbol) {
+ int nPrimitives = sizeof (primitives) / sizeof (primitives[0]);
+ for (int i = 0; i < nPrimitives; ++i)
+ if (!strcmp(primitives[i].name, symbol->string)) return true;
+
+ return false;
+}
+
+Object *evalSet(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else {
+ GC_TRACE(gcVar, (*args)->car);
+ GC_TRACE(gcVal, (*args)->cdr->car);
+
+ *gcVar = evalExpr(gcVar, env, GC_ROOTS);
+
+ if (*gcVar == nil || *gcVar == n || *gcVar == t || *gcVar == f)
+ exceptionWithObject(*gcVar, "is a constant and cannot be set");
+ else if (isSymbolAPrimitive(*gcVar))
+ exceptionWithObject(*gcVar, "is a primitive name and cannot be set");
+ else if ((*gcVar)->type != TYPE_SYMBOL)
+ exceptionWithObject(*gcVar, "is not an atom and cannot be set");
+
+ *gcVal = evalExpr(gcVal, env, GC_ROOTS);
+ envSet(gcVar, gcVal, env, GC_ROOTS);
+
+ if ((*args)->cdr->cdr == nil)
+ return *gcVal;
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr->cdr);
+ return evalSet(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalProg(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcObject, nil);
+ GC_TRACE(gcCdr, nil);
+
+ for (;;) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->cdr == nil)
+ return (*args)->car;
+ else {
+ *gcObject = (*args)->car;
+ *gcCdr = (*args)->cdr;
+
+ evalExpr(gcObject, env, GC_ROOTS);
+ *args = *gcCdr;
+ /* return evalProg(gcCdr, env, GC_ROOTS); */
+ }
+ }
+}
+
+Object *progs1(Object **stop, Object **body, Object **env, GC_PARAM) {
+ GC_TRACE(gcObject, nil);
+ GC_TRACE(gcCdr, nil);
+
+ for (;;) {
+ if (*body == nil)
+ return *stop;
+ else if ((*body)->cdr == nil)
+ return (*body)->car;
+ else {
+ /* GC_TRACE(gcEnv, *env); */
+ /* GC_TRACE(gcBody, nil); */
+ /* GC_TRACE(gcStop, nil); */
+
+ /* if ((*stop)->type == TYPE_LAMBDA) { */
+ /* *gcBody = (*stop)->body; */
+ /* *gcEnv = newEnv(stop, gcBody, GC_ROOTS); */
+ /* *gcStop = evalExpr(gcBody, gcEnv, GC_ROOTS); */
+
+ /* if (*gcStop == t) */
+ /* return *gcObject; */
+ /* } */
+
+ *gcObject = (*body)->car;
+ *gcCdr = (*body)->cdr;
+
+ *gcObject = evalExpr(gcObject, env, GC_ROOTS);
+
+ if (*gcObject == *stop)
+ return *gcObject;
+
+ *body = *gcCdr;
+ /* return progs1(stop, gcCdr, env, GC_ROOTS); */
+ }
+ }
+}
+
+Object *evalProgs(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcStop, (*args)->car);
+ GC_TRACE(gcBody, (*args)->cdr);
+
+ *gcStop = evalExpr(gcStop, env, GC_ROOTS);
+
+ if ((*gcStop)->type != TYPE_SYMBOL)
+ exceptionWithObject(*gcStop, "is not a symbol");
+
+ return progs1(gcStop, gcBody, env, GC_ROOTS);
+}
+
+/* Object *evalIf(Object **args, Object **env, GC_PARAM) {
+ * GC_TRACE(gcObject, (*args)->car);
+ *
+ * if (evalExpr(gcObject, env, GC_ROOTS) != nil)
+ * return (*args)->cdr->car;
+ * else if ((*args)->cdr->cdr != nil)
+ * return (*args)->cdr->cdr->car;
+ * else
+ * return n;
+ * }
+ */
+
+Object *evalCond(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->car->type != TYPE_CONS)
+ exceptionWithObject((*args)->car, "is not a list");
+ else {
+ GC_TRACE(gcCar, (*args)->car->car);
+ GC_TRACE(gcCdr, (*args)->car->cdr);
+
+ if (*gcCdr == nil) return *gcCar;
+
+ *gcCar = evalExpr(gcCar, env, GC_ROOTS);
+ if (*gcCar == n)
+ return n;
+ else if (*gcCar != f)
+ return evalProg(gcCdr, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr);
+ return evalCond(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalFill(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->car->type != TYPE_CONS)
+ exceptionWithObject((*args)->car, "is not a list");
+ else {
+ GC_TRACE(gcCar, (*args)->car->car);
+ GC_TRACE(gcCdr, (*args)->car->cdr);
+
+ if (*gcCdr == nil) return *gcCar;
+
+ *gcCar = evalExpr(gcCar, env, GC_ROOTS);
+ if (*gcCar == n)
+ return n;
+ else if (*gcCar == f)
+ return evalProg(gcCdr, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr);
+ return evalFill(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalLambda(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcParams, (*args)->car);
+ GC_TRACE(gcBody, (*args)->cdr);
+
+ return newLambda(gcParams, gcBody, env, GC_ROOTS);
+}
+
+Object *evalMacro(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcParams, (*args)->car);
+ GC_TRACE(gcBody, (*args)->cdr);
+
+ return newMacro(gcParams, gcBody, env, GC_ROOTS);
+}
+
+Object *expandMacro(Object **macro, Object **args, GC_PARAM) {
+ GC_TRACE(gcEnv, newEnv(macro, args, GC_ROOTS));
+ GC_TRACE(gcBody, (*macro)->body);
+
+ GC_TRACE(gcObject, evalProg(gcBody, gcEnv, GC_ROOTS));
+ *gcObject = evalExpr(gcObject, gcEnv, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *expandMacroTo(Object **macro, Object **args, Object **cons, GC_PARAM) {
+ GC_TRACE(gcObject, expandMacro(macro, args, GC_ROOTS));
+
+ if ((*gcObject)->type == TYPE_CONS) {
+ (*cons)->car = (*gcObject)->car;
+ (*cons)->cdr = (*gcObject)->cdr;
+ } else {
+ (*cons)->car = newSymbol("prog", GC_ROOTS);
+ (*cons)->cdr = newCons(gcObject, &nil, GC_ROOTS);
+ }
+
+ return *cons;
+}
+
+Object *evalList(Object **args, Object **env, GC_PARAM) {
+ if ((*args)->type != TYPE_CONS)
+ return evalExpr(args, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcObject, (*args)->car);
+ GC_TRACE(gcArgs, (*args)->cdr);
+
+ *gcObject = evalExpr(gcObject, env, GC_ROOTS);
+ *gcArgs = evalList(gcArgs, env, GC_ROOTS);
+
+ return newCons(gcObject, gcArgs, GC_ROOTS);
+ }
+}
+
+Object *evalExpr(Object **object, Object **env, GC_PARAM) {
+ GC_TRACE(gcObject, *object);
+ GC_TRACE(gcEnv, *env);
+
+ GC_TRACE(gcFunc, nil);
+ GC_TRACE(gcArgs, nil);
+ GC_TRACE(gcBody, nil);
+
+ for (;;) {
+ if ((*gcObject)->type == TYPE_SYMBOL)
+ return envLookup(*gcObject, *gcEnv);
+ if ((*gcObject)->type != TYPE_CONS)
+ return *gcObject;
+
+ *gcFunc = (*gcObject)->car;
+ *gcArgs = (*gcObject)->cdr;
+
+ *gcFunc = evalExpr(gcFunc, gcEnv, GC_ROOTS);
+ *gcBody = nil;
+
+ if ((*gcFunc)->type == TYPE_LAMBDA) {
+ *gcBody = (*gcFunc)->body;
+ *gcArgs = evalList(gcArgs, gcEnv, GC_ROOTS);
+ *gcEnv = newEnv(gcFunc, gcArgs, GC_ROOTS);
+ *gcObject = evalProg(gcBody, gcEnv, GC_ROOTS);
+ } else if ((*gcFunc)->type == TYPE_MACRO) {
+ *gcObject = expandMacroTo(gcFunc, gcArgs, gcObject, GC_ROOTS);
+ } else if ((*gcFunc)->type == TYPE_PRIMITIVE) {
+ Primitive *primitive = &primitives[(*gcFunc)->primitive];
+ int nArgs = 0;
+
+ for (Object *args = *gcArgs; args != nil; args = args->cdr, nArgs++)
+ if (args->type != TYPE_CONS)
+ exceptionWithObject(args, "is not a list");
+
+ if (nArgs < primitive->nMinArgs)
+ exceptionWithObject(*gcFunc, "expects at least %d arguments",
+ primitive->nMinArgs);
+ if (nArgs > primitive->nMaxArgs && primitive->nMaxArgs >= 0)
+ exceptionWithObject(*gcFunc, "expects at most %d arguments",
+ primitive->nMaxArgs);
+ if (primitive->nMaxArgs < 0 && nArgs % -primitive->nMaxArgs)
+ exceptionWithObject(*gcFunc, "expects a multiple of %d arguments",
+ -primitive->nMaxArgs);
+
+ switch ((*gcFunc)->primitive) {
+ case PRIMITIVE_EVAL: *gcArgs = (*gcArgs)->car;
+ *gcObject = evalExpr(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_QUOTE: return (*gcArgs)->car;
+ case PRIMITIVE_SAY: *gcArgs = (*gcArgs)->car;
+ return newCons(&nil, gcArgs, GC_ROOTS);
+ case PRIMITIVE_SET: return evalSet(gcArgs, gcEnv, GC_ROOTS);
+ case PRIMITIVE_PROG: *gcObject = evalProg(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_PROGS: *gcObject = evalProgs(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ /* case PRIMITIVE_IF: *gcObject = evalIf(gcArgs, gcEnv, GC_ROOTS);
+ * break;
+ */
+ case PRIMITIVE_COND: *gcObject = evalCond(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_FILL: *gcObject = evalFill(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_LAMBDA: return evalLambda(gcArgs, gcEnv, GC_ROOTS);
+ case PRIMITIVE_MACRO: return evalMacro(gcArgs, gcEnv, GC_ROOTS);
+ default: *gcArgs = evalList(gcArgs, gcEnv, GC_ROOTS);
+ return primitive->eval(gcArgs, GC_ROOTS);
+ }
+ } else
+ exceptionWithObject(*gcFunc, "is not a function");
+ }
+}
+
+// STANDARD LIBRARY ///////////////////////////////////////////////////////////
+
+#define LISP(...) #__VA_ARGS__
+
+static char *stdlib = LISP(
+ (set (quote list) (lambda args args))
+
+ (set (quote defmacro) (macro (name params . body)
+ (list (quote set) (list quote name) (list (quote macro) params . body))))
+
+ (defmacro defun (name params . body)
+ (list (quote set) (list quote name) (list (quote lambda) params . body)))
+
+ (defun not (x) (fill ((dif x n) n)
+ (x t)
+ (f f)))
+
+ (defun ap (x) (not (space x)))
+
+ (defun listp (x) (not (atom x)))
+
+ (defun zerop (x) (not (! x 0)))
+
+ (defmacro and args
+ (fill ((ap args) t)
+ ((ap (cdr args)) (car args))
+ (f (list (quote fill)
+ (list (list (quote not) (car args))
+ (cons (quote and) (cdr args)))
+ (list (quote f) (quote f))))))
+
+ (defun map (func xs)
+ (fill ((ap xs) ())
+ (f (cons (func (car xs)) (map func (cdr xs))))))
+
+ (defmacro or args
+ (fill ((ap args) f)
+ (f (cons (quote fill)
+ (append (map (lambda (x) (list (list (quote not) x) x))
+ args)
+ (list (list f f)))))))
+
+ (defun consp (x) (and (listp x) (ap x)))
+
+ (defun differ (x y)
+ (fill ((or (consp x) (consp y)) (dif x y))
+ (f (or (differ (car x) (car y))
+ (differ (cdr x) (cdr y))))))
+
+ (defun nth (num xs)
+ (fill ((dif num 0) (car xs))
+ (f (nth (- num 1) (cdr xs)))))
+
+ (defun append (xs y)
+ (fill ((ap xs) y)
+ (f (cons (car xs) (append (cdr xs) y)))))
+);
+
+// MAIN ///////////////////////////////////////////////////////////////////////
+
+Object *newRootEnv(GC_PARAM) {
+ GC_TRACE(gcEnv, newEnv(&nil, &nil, GC_ROOTS));
+ GC_TRACE(gcVar, nil);
+ GC_TRACE(gcVal, nil);
+
+ // add constants
+ envSet(&nil, &nil, gcEnv, GC_ROOTS);
+ envSet(&n, &n, gcEnv, GC_ROOTS);
+ envSet(&t, &t, gcEnv, GC_ROOTS);
+ envSet(&f, &f, gcEnv, GC_ROOTS);
+
+ // add primitives
+ int nPrimitives = sizeof (primitives) / sizeof (primitives[0]);
+
+ for (int i = 0; i < nPrimitives; ++i) {
+ *gcVar = newSymbol(primitives[i].name, GC_ROOTS);
+ *gcVal = newPrimitive(i, primitives[i].name, GC_ROOTS);
+
+ envSet(gcVar, gcVal, gcEnv, GC_ROOTS);
+ }
+
+ // add standard library
+ Stream stream = { STREAM_TYPE_STRING, .buffer = stdlib };
+ GC_TRACE(gcObject, nil);
+
+ while (peekNext(&stream) != EOF) {
+ *gcObject = nil;
+ *gcObject = readExpr(&stream, GC_ROOTS);
+ evalExpr(gcObject, gcEnv, GC_ROOTS);
+ }
+
+ return *gcEnv;
+}
diff --git a/liblali.h b/liblali.h
new file mode 100644
index 0000000..797587d
--- /dev/null
+++ b/liblali.h
@@ -0,0 +1,362 @@
+/*
+ * lali (Lali Another Lisp Implementation)
+ *
+ * Author: Daniel Cerqueira (dan.git@lispclub.com)
+ * Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+ * Version: 0.0
+ * Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+ * computer programming language
+ * Homepage: https://gitlab.com/alexandre1985/lali
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Copyright (C) 2025 Daniel Cerqueira
+ *
+ * This file is part of lali.
+ *
+ * lali 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/>.
+ *
+ *
+ * lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/>
+ * version from 2016, written by Matthias Pirstitz, which is released to public
+ * domain under Unlicense <https://unlicense.org/>.
+ */
+
+#ifndef LIBLALI_H
+#define LIBLALI_H
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+
+#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+typedef struct Object Object;
+
+typedef enum Type {
+ TYPE_NUMBER,
+ TYPE_STRING,
+ TYPE_SYMBOL,
+ TYPE_CONS,
+ TYPE_LAMBDA,
+ TYPE_MACRO,
+ TYPE_PRIMITIVE,
+ TYPE_ENV
+} Type;
+
+struct Object {
+ Type type;
+ size_t size;
+ union {
+ struct { double number; }; // number
+ struct { char string[sizeof (Object *[3])]; }; // string, symbol
+ struct { Object *car, *cdr; }; // cons
+ struct { Object *params, *body, *env; }; // lambda, macro
+ struct { int primitive; char *name; }; // primitive
+ struct { Object *parent, *vars, *vals; }; // env
+ struct { Object *forward; }; // forwarding pointer
+ };
+};
+
+extern Object *symbols;
+extern Object *nil;
+extern Object *n;
+extern Object *t;
+extern Object *f;
+
+typedef enum StreamType {
+ STREAM_TYPE_STRING,
+ STREAM_TYPE_FILE
+} StreamType;
+
+typedef struct Stream {
+ StreamType type;
+ char *buffer;
+ int fd;
+ size_t length, capacity;
+ off_t offset, size;
+} Stream;
+
+typedef struct Memory {
+ size_t capacity, fromOffset, toOffset;
+ void *fromSpace, *toSpace;
+} Memory;
+
+extern jmp_buf exceptionEnv;
+
+// EXCEPTION HANDLING /////////////////////////////////////////////////////////
+
+#define exception(...) exceptionWithObject(NULL, __VA_ARGS__)
+
+#ifdef __GNUC__
+void exceptionWithObject(Object *object, char *format, ...)
+ __attribute__ ((noreturn, format(printf, 2, 3)));
+#endif
+
+void exceptionWithObject(Object *object, char *format, ...);
+
+// GARBAGE COLLECTION /////////////////////////////////////////////////////////
+
+/* This implements Cheney's copying garbage collector, with which memory is
+ * divided into two equal halves (semispaces): from- and to-space. From-space
+ * is where new objects are allocated, whereas to-space is used during garbage
+ * collection.
+ *
+ * When garbage collection is performed, objects that are still in use (live)
+ * are copied from from-space to to-space. To-space then becomes the new
+ * from-space and vice versa, thereby discarding all objects that have not
+ * been copied.
+ *
+ * Our garbage collector takes as input a list of root objects. Objects that
+ * can be reached by recursively traversing this list are considered live and
+ * will be moved to to-space. When we move an object, we must also update its
+ * pointer within the list to point to the objects new location in memory.
+ *
+ * However, this implies that our interpreter cannot use raw pointers to
+ * objects in any function that might trigger garbage collection (or risk
+ * causing a SEGV when accessing an object that has been moved). Instead,
+ * objects must be added to the list and then only accessed through the
+ * pointer inside the list.
+ *
+ * Thus, whenever we would have used a raw pointer to an object, we use a
+ * pointer to the pointer inside the list instead:
+ *
+ * function: pointer to pointer inside list (Object **)
+ * |
+ * v
+ * list of root objects: pointer to object (Object *)
+ * |
+ * v
+ * semispace: object in memory
+ *
+ * GC_ROOTS and GC_PARAM are used to pass the list from function to function.
+ *
+ * GC_TRACE adds an object to the list and declares a variable which points to
+ * the objects pointer inside the list.
+ *
+ * GC_TRACE(gcX, X): add object X to the list and declare Object **gcX
+ * to point to the pointer to X inside the list.
+ */
+
+#define GC_ROOTS gcRoots
+#define GC_PARAM Object *GC_ROOTS
+
+#define GC_PASTE1(name, id) name ## id
+#define GC_PASTE2(name, id) GC_PASTE1(name, id)
+#define GC_UNIQUE(name) GC_PASTE2(name, __LINE__)
+
+#define GC_TRACE(name, init) \
+ Object GC_UNIQUE(GC_ROOTS) = { TYPE_CONS, .car = init, .cdr = GC_ROOTS }; \
+ Object **name = &GC_UNIQUE(GC_ROOTS).car; \
+ GC_ROOTS = &GC_UNIQUE(GC_ROOTS)
+
+Object *gcMoveObject(Object *object);
+
+void gc(GC_PARAM);
+
+// MEMORY MANAGEMENT //////////////////////////////////////////////////////////
+
+size_t memoryAlign(size_t size, size_t alignment);
+
+Object *memoryAllocObject(Type type, size_t size, GC_PARAM);
+
+// CONSTRUCTING OBJECTS ///////////////////////////////////////////////////////
+
+Object *newObject(Type type, GC_PARAM);
+
+Object *newObjectFrom(Object **from, GC_PARAM);
+
+Object *newNumber(double number, GC_PARAM);
+
+Object *newObjectWithString(Type type, size_t size, GC_PARAM);
+
+Object *newStringWithLength(char *string, size_t length, GC_PARAM);
+
+Object *newString(char *string, GC_PARAM);
+
+Object *newCons(Object **car, Object **cdr, GC_PARAM);
+
+Object *newSymbolWithLength(char *string, size_t length, GC_PARAM);
+
+Object *newSymbol(char *string, GC_PARAM);
+
+Object *newObjectWithClosure(Type type, Object **params, Object **body, Object **env, GC_PARAM);
+
+Object *newLambda(Object **params, Object **body, Object **env, GC_PARAM);
+
+Object *newMacro(Object **params, Object **body, Object **env, GC_PARAM);
+
+Object *newPrimitive(int primitive, char *name, GC_PARAM);
+
+Object *newEnv(Object **func, Object **vals, GC_PARAM);
+
+// STREAM INPUT ///////////////////////////////////////////////////////////////
+
+/* The purpose of the stream functions is to provide an abstraction over file
+ * and string inputs. In order to accommodate the REPL, we need to be able to
+ * process character special files (such as stdin) character by character and
+ * evaluate expressions as they are being entered.
+ */
+
+int streamGetc(Stream *stream);
+
+Stream *streamSeek(Stream *stream, int offset);
+
+int streamPeek(Stream *stream);
+
+// READING S-EXPRESSIONS //////////////////////////////////////////////////////
+
+Object *readExpr(Stream *stream, GC_PARAM);
+
+int readNext(Stream *stream);
+
+int initialReadNext(Stream *stream);
+
+int peekForward(Stream *stream, bool shebang);
+
+int peekNext(Stream *stream);
+
+int readWhile(Stream *stream, int (*predicate)(int ch));
+
+Object *readUnary(Stream *stream, char *symbol, GC_PARAM);
+
+Object *readString(Stream *stream, GC_PARAM);
+
+int isSymbolChar(int ch);
+
+Object *readNumberOrSymbol(Stream *stream, GC_PARAM);
+
+Object *reverseList(Object *list);
+
+Object *readList(Stream *stream, GC_PARAM);
+
+// WRITING OBJECTS ////////////////////////////////////////////////////////////
+
+void writeObject(Object *object, bool readably, FILE *file);
+
+// ENVIRONMENT ////////////////////////////////////////////////////////////////
+
+/* An environment consists of a pointer to its parent environment (if any) and
+ * two parallel lists - vars and vals.
+ *
+ * Case 1 - vars is a regular list:
+ * vars: (a b c), vals: (1 2 3) ; a = 1, b = 2, c = 3
+ *
+ * Case 2 - vars is a dotted list:
+ * vars: (a b . c), vals: (1 2) ; a = 1, b = 2, c = nil
+ * vars: (a b . c), vals: (1 2 3) ; a = 1, b = 2, c = (3)
+ * vars: (a b . c), vals: (1 2 3 4 5) ; a = 1, b = 2, c = (3 4 5)
+ *
+ * Case 3 - vars is a symbol:
+ * vars: a, vals: nil ; a = nil
+ * vars: a, vals: (1) ; a = (1)
+ * vars: a, vals: (1 2 3) ; a = (1 2 3)
+ *
+ * Case 4 - vars and vals are both nil:
+ * vars: nil, vals: nil
+ */
+
+Object *envLookup(Object *var, Object *env);
+
+Object *envAdd(Object **var, Object **val, Object **env, GC_PARAM);
+
+Object *envSet(Object **var, Object **val, Object **env, GC_PARAM);
+
+// PRIMITIVES /////////////////////////////////////////////////////////////////
+
+Object *primitiveSpace(Object **args, GC_PARAM);
+
+Object *primitiveAtom(Object **args, GC_PARAM);
+
+/* Object *primitiveEq(Object **args, GC_PARAM);
+ */
+
+Object *primitiveDif(Object **args, GC_PARAM);
+
+Object *primitiveCar(Object **args, GC_PARAM);
+
+Object *primitiveCdr(Object **args, GC_PARAM);
+
+Object *primitiveCons(Object **args, GC_PARAM);
+
+Object *primitivePrint(Object **args, GC_PARAM);
+
+Object *primitivePrinc(Object **args, GC_PARAM);
+
+Object *primitiveNewline(Object **args, GC_PARAM);
+
+Object *primitiveRead(Object **args, GC_PARAM);
+
+Object *primitiveTime(Object **args, GC_PARAM);
+
+Object *primitiveRandom(Object **args, GC_PARAM);
+
+Object *primitiveAdd(Object **args, GC_PARAM);
+
+Object *primitiveSubtract(Object **args, GC_PARAM);
+
+Object *primitiveMultiply(Object **args, GC_PARAM);
+
+Object *primitiveDivide(Object **args, GC_PARAM);
+
+Object *primitiveRemainder(Object **args, GC_PARAM);
+
+// EVALUATION /////////////////////////////////////////////////////////////////
+
+/* Scheme-style tail recursive evaluation. evalProg, evalProgs, evalCond, and
+ * PRIMITIVE_EVAL, return the object in the tail recursive position to be
+ * evaluated by evalExpr. Macros are expanded in-place the first time they are
+ * evaluated.
+ */
+
+Object *evalExpr(Object **object, Object **env, GC_PARAM);
+
+Object *evalSet(Object **args, Object **env, GC_PARAM);
+
+Object *evalProg(Object **args, Object **env, GC_PARAM);
+
+Object *progs1(Object **stop, Object **body, Object **env, GC_PARAM);
+
+Object *evalProgs(Object **args, Object **env, GC_PARAM);
+
+Object *evalCond(Object **args, Object **env, GC_PARAM);
+
+Object *evalFill(Object **args, Object **env, GC_PARAM);
+
+Object *evalLambda(Object **args, Object **env, GC_PARAM);
+
+Object *evalMacro(Object **args, Object **env, GC_PARAM);
+
+Object *expandMacro(Object **macro, Object **args, GC_PARAM);
+
+Object *expandMacroTo(Object **macro, Object **args, Object **cons, GC_PARAM);
+
+Object *evalList(Object **args, Object **env, GC_PARAM);
+
+Object *evalRead(GC_PARAM);
+
+Object *newRootEnv(GC_PARAM);
+
+#endif
diff --git a/test/main.lali b/test/main.lali
new file mode 100644
index 0000000..a3e4b9a
--- /dev/null
+++ b/test/main.lali
@@ -0,0 +1,40 @@
+(defun test (test-expr res)
+ (princ
+ (list (fill
+ ((differ (eval test-expr) res) 'ok)
+ ('f 'fail))
+ '--> test-expr))
+ (newline))
+
+;; atom
+(test '(atom 'a) t)
+(test '(atom '(x . a)) f)
+;(test '(atom 'a) undef)
+
+;; dif
+(test '(dif 'a 'b) t)
+(test '(dif 'a 'a) f)
+(test '(dif 'a '(b . c)) t)
+(test '(dif '(b . c) '(b . c)) t)
+
+;; car
+(test '(car '(a . b)) 'a)
+(test '(car '((a . b) . c)) '(a . b))
+;(test '(car 'a) undef)
+
+;; cdr
+(test '(cdr '(a . b)) 'b)
+(test '(cdr '((a . b) . c)) 'c)
+;(test '(cdr 'a) undef)
+
+;; cons
+(test '(cons 'a 'b) '(a . b))
+(test '(cons '(a . b) 'c) '((a . b) . c))
+
+;; or
+(test '(or t (car 'a)) t)
+;(test '(or (car 'a) t) undef)
+
+;; and
+(test '(and f (car 'a)) f)
+;(test '(and (car 'a) f) undef)
diff --git a/test/other/executables/emacs.el b/test/other/executables/emacs.el
new file mode 100755
index 0000000..e7e2efc
--- /dev/null
+++ b/test/other/executables/emacs.el
@@ -0,0 +1,3 @@
+#!/usr/local/bin/emacs --script
+
+(princ 'i)
diff --git a/test/other/executables/empty.lisp b/test/other/executables/empty.lisp
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/test/other/executables/empty.lisp
diff --git a/test/other/executables/script.lali b/test/other/executables/script.lali
new file mode 100755
index 0000000..934323d
--- /dev/null
+++ b/test/other/executables/script.lali
@@ -0,0 +1,4 @@
+#!/home/guest/me/programming/desktop/lisp/interpreter/lali/lali -c
+
+(princ "ola")
+(newline)
diff --git a/test/other/lexical.lali b/test/other/lexical.lali
new file mode 100644
index 0000000..d8d94e2
--- /dev/null
+++ b/test/other/lexical.lali
@@ -0,0 +1,15 @@
+(set 'a '1)
+
+(defun j ()
+ (princ a)
+ (set 'a (+ a 1)))
+
+(j)
+(j)
+(princ a)
+
+(defun i ()
+ (set 'b 9))
+
+(i)
+(princ b)
diff --git a/test/other/mundo.lali b/test/other/mundo.lali
new file mode 100644
index 0000000..19756fe
--- /dev/null
+++ b/test/other/mundo.lali
@@ -0,0 +1,4 @@
+(princ 'proc)
+(princ (list a 'ola))
+(print "mundo")
+(print '(ate breve))
diff --git a/test/other/ola.lali b/test/other/ola.lali
new file mode 100644
index 0000000..d62a537
--- /dev/null
+++ b/test/other/ola.lali
@@ -0,0 +1,3 @@
+(princ "ola")
+(newline)
+(set 'a 1)
diff --git a/test/other/universal.lali b/test/other/universal.lali
new file mode 100644
index 0000000..6b38b19
--- /dev/null
+++ b/test/other/universal.lali
@@ -0,0 +1 @@
+(princ 'i)
diff --git a/work-in-progress/Makefile b/work-in-progress/Makefile
new file mode 100644
index 0000000..f136822
--- /dev/null
+++ b/work-in-progress/Makefile
@@ -0,0 +1,94 @@
+# lali (Lali Another Lisp Implementation)
+#
+# Author: Daniel Cerqueira (dan.git@lispclub.com)
+# Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+# Version: 0.0
+# Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+# computer programming language
+# Homepage: https://gitlab.com/alexandre1985/lali
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# Copyright (C) 2025 Daniel Cerqueira
+#
+# This file is part of lali.
+#
+# lali 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/>.
+#
+#
+# lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/> version
+# from 2016, written by Matthias Pirstitz, which is released to public domain
+# under Unlicense <https://unlicense.org/>.
+
+
+PROGRAM := lali
+LIBOBJECTS := liblali.o
+LIB := lib$(PROGRAM).a
+OBJECTS := $(LIBOBJECTS) lali.o
+HTML := README.html
+
+ifeq ($(shell uname -s), Linux)
+CPPFLAGS += -D_DEFAULT_SOURCE -D_BSD_SOURCE
+endif
+
+CFLAGS += -std=c11 -Wall -pedantic
+
+PREFIX ?= /usr/local
+
+.PHONY: native
+native: CPPFLAGS += -DNDEBUG
+native: CFLAGS += -march=native -O2
+native: $(PROGRAM)
+
+.PHONY: generic
+generic: CPPFLAGS += -DNDEBUG
+generic: CFLAGS += -mtune=generic -O2
+generic: $(PROGRAM)
+
+.PHONY: all
+all: CPPFLAGS += -DNDEBUG
+all: CFLAGS += -march=native -O2
+all: $(PROGRAM) $(LIB) $(HTML)
+
+.PHONY: debug
+debug: CPPFLAGS += -DDEBUG
+debug: CFLAGS += -g
+debug: LDFLAGS += -g
+debug: $(PROGRAM)
+
+.PHONY: clean
+clean:
+ $(RM) $(PROGRAM) $(OBJECTS) $(LIB) $(HTML)
+
+.PHONY: lib
+lib: $(LIB)
+
+$(LIB): $(LIBOBJECTS)
+ $(AR) rc $@ $(LIBOBJECTS)
+ ranlib $@
+
+$(PROGRAM): $(OBJECTS)
+ $(CC) $(LDFLAGS) $(OBJECTS) -o $@
+# $(CC) $(LDFLAGS) lali.o -L. -l$(PROGRAM) -o $@
+
+%.o: %.c %.h
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
+
+.PHONY: html
+html: $(HTML)
+
+%.html: %.md
+ md2html -f -o $@ $<
+
+.PHONY: install
+install: native
+ install -D -m 311 -t $(PREFIX)/bin $(PROGRAM)
diff --git a/work-in-progress/TAGS b/work-in-progress/TAGS
new file mode 100644
index 0000000..16adb82
--- /dev/null
+++ b/work-in-progress/TAGS
@@ -0,0 +1,104 @@
+
+lali.c,86
+#define YEARS 50,1540
+void runFile(54,1658
+void runREPL(72,2058
+int main(100,2617
+
+liblali.c,3469
+#define MEMORY_SIZE 50,1540
+static Memory *memory memory52,1580
+Object *symbols symbols54,1631
+Object *nil nil55,1655
+Object *n n56,1711
+Object *t t57,1764
+Object *f f58,1817
+jmp_buf exceptionEnv;60,1871
+void exceptionWithObject(64,1975
+Object *gcMoveObject(gcMoveObject124,4317
+void gc(146,5016
+size_t memoryAlign(194,6464
+Object *memoryAllocObject(memoryAllocObject198,6571
+Object *newObject(newObject227,7559
+Object *newObjectFrom(newObjectFrom231,7664
+Object *newNumber(newNumber237,7851
+Object *newObjectWithString(newObjectWithString243,7996
+Object *newStringWithLength(newStringWithLength250,8239
+Object *newString(newString278,9169
+Object *newCons(newCons282,9280
+Object *newSymbolWithLength(newSymbolWithLength289,9451
+Object *newSymbol(newSymbol304,9961
+Object *newObjectWithClosure(newObjectWithClosure308,10072
+Object *newLambda(newLambda330,10796
+Object *newMacro(newMacro334,10975
+Object *newPrimitive(newPrimitive338,11152
+Object *newEnv(newEnv345,11344
+int streamGetc(384,12712
+Stream *streamSeek(streamSeek452,14747
+int streamPeek(460,14924
+int readNext(469,15130
+int initialReadNext(480,15347
+int peekForward(491,15584
+int peekNext(498,15760
+int readWhile(502,15831
+Object *readUnary(readUnary511,16007
+Object *readString(readString524,16384
+int isSymbolChar(540,16914
+Object *readNumberOrSymbol(readNumberOrSymbol545,17039
+Object *reverseList(reverseList577,18007
+Object *readList(readList590,18207
+Object *readExpr(readExpr622,19197
+void writeObject(645,19916
+#define CASE(647,20004
+#undef CASE654,20315
+#define CASE(692,21367
+#undef CASE699,21670
+Object *envLookup(envLookup725,22491
+Object *envAdd(envAdd740,22848
+Object *envSet(envSet750,23151
+Object *primitiveSpace(primitiveSpace772,23735
+Object *primitiveAtom(primitiveAtom776,23828
+Object *primitiveDif(primitiveDif794,24412
+Object *primitiveCar(primitiveCar805,24817
+Object *primitiveCdr(primitiveCdr817,25126
+Object *primitiveCons(primitiveCons828,25362
+Object *primitivePrint(primitivePrint835,25538
+Object *primitivePrinc(primitivePrinc841,25679
+Object *primitiveNewline(primitiveNewline847,25821
+Object *primitiveRead(primitiveRead852,25911
+#define DEFINE_PRIMITIVE_ARITHMETIC(865,26159
+DEFINE_PRIMITIVE_ARITHMETIC(894,27946
+#define DEFINE_PRIMITIVE_RELATIONAL(899,28159
+typedef struct Primitive 925,29771
+ char *name;name926,29798
+ int nMinArgs,927,29812
+ int nMinArgs, nMaxArgs;927,29812
+ Object *(* eval)928,29838
+} Primitive;929,29883
+Primitive primitives[primitives931,29897
+ PRIMITIVE_EVAL,969,31590
+ PRIMITIVE_QUOTE,970,31608
+ PRIMITIVE_SAY,971,31627
+ PRIMITIVE_SET,972,31644
+ PRIMITIVE_PROG,973,31661
+ PRIMITIVE_PROGS,974,31679
+ PRIMITIVE_COND,976,31720
+ PRIMITIVE_FILL,977,31738
+ PRIMITIVE_LAMBDA,978,31756
+ PRIMITIVE_MACRO,979,31776
+ PRIMITIVE_TEST980,31795
+Object *evalSet(evalSet991,32147
+Object *evalProg(evalProg1017,32877
+Object *progs1(progs11031,33198
+Object *evalProgs(evalProgs1048,33601
+Object *evalCond(evalCond1072,34218
+Object *evalFill(evalFill1095,34800
+Object *evalLambda(evalLambda1118,35382
+Object *evalMacro(evalMacro1126,35627
+Object *expandMacro(expandMacro1134,35870
+Object *expandMacroTo(expandMacroTo1144,36152
+Object *evalList(evalList1158,36542
+Object *evalExpr(evalExpr1172,36914
+#define LISP(1249,40130
+static char *stdlib stdlib1251,40162
+Object *newRootEnv(newRootEnv1307,41788
diff --git a/work-in-progress/lali.c b/work-in-progress/lali.c
new file mode 100644
index 0000000..e0c0cbb
--- /dev/null
+++ b/work-in-progress/lali.c
@@ -0,0 +1,173 @@
+/*
+ * lali (Lali Another Lisp Implementation)
+ *
+ * Author: Daniel Cerqueira (dan.git@lispclub.com)
+ * Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+ * Version: 0.0
+ * Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+ * computer programming language
+ * Homepage: https://gitlab.com/alexandre1985/lali
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Copyright (C) 2025 Daniel Cerqueira
+ *
+ * This file is part of lali.
+ *
+ * lali 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/>.
+ *
+ *
+ * lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/>
+ * version from 2016, written by Matthias Pirstitz, which is released to public
+ * domain under Unlicense <https://unlicense.org/>.
+ */
+
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "liblali.h"
+
+#define YEARS "2025"
+
+// MAIN ///////////////////////////////////////////////////////////////////////
+
+void runFile(int infd, Object **env, GC_PARAM) {
+ Stream stream = { STREAM_TYPE_FILE, .fd = infd };
+ GC_TRACE(gcObject, nil);
+
+ if (setjmp(exceptionEnv))
+ return;
+
+ // deal with shebang
+ if (peekForward(&stream, true) == EOF)
+ return;
+
+ do {
+ *gcObject = nil;
+ *gcObject = readExpr(&stream, GC_ROOTS);
+ evalExpr(gcObject, env, GC_ROOTS);
+ } while (peekNext(&stream) != EOF);
+}
+
+void runREPL(int infd, Object **env, GC_PARAM) {
+ Stream stream = { STREAM_TYPE_FILE, .fd = infd };
+ GC_TRACE(gcObject, nil);
+
+ for (;;) {
+ if (setjmp(exceptionEnv))
+ continue;
+
+ for (;;) {
+ *gcObject = nil;
+
+ fputs("lali> ", stdout);
+ fflush(stdout);
+
+ if (peekNext(&stream) == EOF) {
+ fputc('\n', stdout);
+ return;
+ }
+
+ *gcObject = readExpr(&stream, GC_ROOTS);
+ *gcObject = evalExpr(gcObject, env, GC_ROOTS);
+
+ writeObject(*gcObject, true, stdout);
+ fputc('\n', stdout);
+ }
+ }
+}
+
+int main(int argc, char *argv[]) {
+ int options = 0;
+ bool repl = false;
+ bool close = false;
+ bool quiet = false;
+
+ if (argc >= 2) {
+ if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+ fprintf(stderr, "basic usages:\nnormal: %s [file]...\nclose stdin: %s -c [file]...\nrun repl: %s -r [-q] [file]...\n\nin normal mode, reads from standard input.\nwhen present, load file(s) at startup. can be used as library file(s).\n", argv[0], argv[0], argv[0]);
+ return EXIT_SUCCESS;
+ } else if (!strcmp(argv[1], "-c")) {
+ options++;
+ close = true;
+ } else if (!strcmp(argv[1], "-r")) {
+ options++;
+ repl = true;
+ if (argc > 2 && !strcmp(argv[2], "-q")) {
+ options++;
+ quiet = true;
+ }
+ } else if (!strcmp(argv[1], "-q")) {
+ options++;
+ quiet = true;
+ if (argc > 2 && !strcmp(argv[2], "-r")) {
+ options++;
+ repl = true;
+ }
+ } else if (argv[1][0] == '-') {
+ fprintf(stderr, "%s: unrecognized option, `%s'\n", argv[0], argv[1]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ GC_PARAM = nil;
+
+ if (setjmp(exceptionEnv))
+ return EXIT_FAILURE;
+
+ symbols = nil;
+ symbols = newCons(&nil, &symbols, GC_ROOTS);
+ symbols = newCons(&n, &symbols, GC_ROOTS);
+ symbols = newCons(&t, &symbols, GC_ROOTS);
+ symbols = newCons(&f, &symbols, GC_ROOTS);
+
+ GC_TRACE(gcEnv, newRootEnv(GC_ROOTS));
+
+ int fd = 0;
+
+ if (argc > 1) {
+ for (int i = ++options; i < argc; i++) {
+ if ((fd = open(argv[i], O_RDONLY)) == -1) {
+ fprintf(stderr, "%s: open() failed, %s\n", argv[0], strerror(errno));
+ return EXIT_FAILURE;
+ }
+ runFile(fd, gcEnv, GC_ROOTS);
+ }
+ }
+
+ if (repl) {
+ if(!isatty(STDIN_FILENO)) {
+ fprintf(stderr, "%s: repl does not accept piping\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (!quiet)
+ fprintf(stdout, "lali Copyright (C) %s Daniel Cerqueira\n\nThis is free software: you are free to change and redistribute it,\nunder certain conditions.\nThis program comes with NO WARRANTY, to the extent permitted by law.\n", YEARS);
+
+ runREPL(STDIN_FILENO, gcEnv, GC_ROOTS);
+ }
+ else if (!close)
+ runFile(STDIN_FILENO, gcEnv, GC_ROOTS);
+
+ return EXIT_SUCCESS;
+}
diff --git a/work-in-progress/liblali.c b/work-in-progress/liblali.c
new file mode 100644
index 0000000..d399822
--- /dev/null
+++ b/work-in-progress/liblali.c
@@ -0,0 +1,1339 @@
+/*
+ * lali (Lali Another Lisp Implementation)
+ *
+ * Author: Daniel Cerqueira (dan.git@lispclub.com)
+ * Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+ * Version: 0.0
+ * Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+ * computer programming language
+ * Homepage: https://gitlab.com/alexandre1985/lali
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Copyright (C) 2025 Daniel Cerqueira
+ *
+ * This file is part of lali.
+ *
+ * lali 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/>.
+ *
+ *
+ * lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/>
+ * version from 2016, written by Matthias Pirstitz, which is released to public
+ * domain under Unlicense <https://unlicense.org/>.
+ */
+
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "liblali.h"
+
+#define MEMORY_SIZE 3000000UL
+
+static Memory *memory = &(Memory){ MEMORY_SIZE };
+
+Object *symbols = NULL;
+Object *nil = &(Object){ TYPE_SYMBOL, .string = "()" };
+Object *n = &(Object){ TYPE_SYMBOL, .string = "n" };
+Object *t = &(Object){ TYPE_SYMBOL, .string = "t" };
+Object *f = &(Object){ TYPE_SYMBOL, .string = "f" };
+
+jmp_buf exceptionEnv;
+
+// EXCEPTION HANDLING /////////////////////////////////////////////////////////
+
+void exceptionWithObject(Object *object, char *format, ...) {
+ fputs("error: ", stderr);
+
+ if (object) {
+ writeObject(object, true, stderr);
+ fputc(' ', stderr);
+ }
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fputc('\n', stderr);
+
+ longjmp(exceptionEnv, 1);
+}
+
+// GARBAGE COLLECTION /////////////////////////////////////////////////////////
+
+/* This implements Cheney's copying garbage collector, with which memory is
+ * divided into two equal halves (semispaces): from- and to-space. From-space
+ * is where new objects are allocated, whereas to-space is used during garbage
+ * collection.
+ *
+ * When garbage collection is performed, objects that are still in use (live)
+ * are copied from from-space to to-space. To-space then becomes the new
+ * from-space and vice versa, thereby discarding all objects that have not
+ * been copied.
+ *
+ * Our garbage collector takes as input a list of root objects. Objects that
+ * can be reached by recursively traversing this list are considered live and
+ * will be moved to to-space. When we move an object, we must also update its
+ * pointer within the list to point to the objects new location in memory.
+ *
+ * However, this implies that our interpreter cannot use raw pointers to
+ * objects in any function that might trigger garbage collection (or risk
+ * causing a SEGV when accessing an object that has been moved). Instead,
+ * objects must be added to the list and then only accessed through the
+ * pointer inside the list.
+ *
+ * Thus, whenever we would have used a raw pointer to an object, we use a
+ * pointer to the pointer inside the list instead:
+ *
+ * function: pointer to pointer inside list (Object **)
+ * |
+ * v
+ * list of root objects: pointer to object (Object *)
+ * |
+ * v
+ * semispace: object in memory
+ *
+ * GC_ROOTS and GC_PARAM are used to pass the list from function to function.
+ *
+ * GC_TRACE adds an object to the list and declares a variable which points to
+ * the objects pointer inside the list.
+ *
+ * GC_TRACE(gcX, X): add object X to the list and declare Object **gcX
+ * to point to the pointer to X inside the list.
+ */
+
+Object *gcMoveObject(Object *object) {
+ // skip object if it is not within from-space (i.e. on the stack)
+ if (object < (Object *)memory->fromSpace ||
+ object >= (Object *)((char *)memory->fromSpace + memory->fromOffset))
+ return object;
+
+ // if the object has already been moved, return its new location
+ if (object->type == (Type)-1)
+ return object->forward;
+
+ // copy object to to-space
+ Object *forward = (Object *)((char *)memory->toSpace + memory->toOffset);
+ memcpy(forward, object, object->size);
+ memory->toOffset += object->size;
+
+ // mark object as moved and set forwarding pointer
+ object->type = (Type)-1;
+ object->forward = forward;
+
+ return object->forward;
+}
+
+void gc(GC_PARAM) {
+ memory->toOffset = 0;
+
+ // move symbols and root objects
+ symbols = gcMoveObject(symbols);
+
+ for (Object *object = GC_ROOTS; object != nil; object = object->cdr)
+ object->car = gcMoveObject(object->car);
+
+ // iterate over objects in to-space and move all objects they reference
+ for (Object *object = memory->toSpace;
+ object < (Object *)((char *)memory->toSpace + memory->toOffset);
+ object = (Object *)((char *)object + object->size)) {
+
+ switch (object->type) {
+ case TYPE_NUMBER:
+ case TYPE_STRING:
+ case TYPE_SYMBOL:
+ case TYPE_PRIMITIVE:
+ break;
+ case TYPE_CONS:
+ object->car = gcMoveObject(object->car);
+ object->cdr = gcMoveObject(object->cdr);
+ break;
+ case TYPE_LAMBDA:
+ case TYPE_MACRO:
+ object->closure = gcMoveObject(object->closure);
+ object->params = gcMoveObject(object->params);
+ object->body = gcMoveObject(object->body);
+ object->env = gcMoveObject(object->env);
+ break;
+ case TYPE_ENV:
+ object->parent = gcMoveObject(object->parent);
+ object->vars = gcMoveObject(object->vars);
+ object->vals = gcMoveObject(object->vals);
+ break;
+ }
+ }
+
+ // swap from- and to-space
+ void *swap = memory->fromSpace;
+ memory->fromSpace = memory->toSpace;
+ memory->toSpace = swap;
+ memory->fromOffset = memory->toOffset;
+}
+
+// MEMORY MANAGEMENT //////////////////////////////////////////////////////////
+
+size_t memoryAlign(size_t size, size_t alignment) {
+ return (size + alignment - 1) & ~(alignment - 1);
+}
+
+Object *memoryAllocObject(Type type, size_t size, GC_PARAM) {
+ size = memoryAlign(size, sizeof (void *));
+
+ // allocate from- and to-space
+ if (!memory->fromSpace) {
+ if (!(memory->fromSpace = mmap(NULL, memory->capacity * 2,
+ PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)))
+ exception("mmap() failed, %s", strerror(errno));
+
+ memory->toSpace = (char *)memory->fromSpace + memory->capacity;
+ }
+
+ // run garbage collection if capacity exceeded
+ if (memory->fromOffset + size >= memory->capacity)
+ gc(GC_ROOTS);
+ if (memory->fromOffset + size >= memory->capacity)
+ exception("out of memory, %lu bytes", (unsigned long)size);
+
+ // allocate object in from-space
+ Object *object = (Object *)((char *)memory->fromSpace + memory->fromOffset);
+ object->type = type;
+ object->size = size;
+ memory->fromOffset += size;
+
+ return object;
+}
+
+// CONSTRUCTING OBJECTS ///////////////////////////////////////////////////////
+
+Object *newObject(Type type, GC_PARAM) {
+ return memoryAllocObject(type, sizeof (Object), GC_ROOTS);
+}
+
+Object *newObjectFrom(Object **from, GC_PARAM) {
+ Object *object = memoryAllocObject((*from)->type, (*from)->size, GC_ROOTS);
+ memcpy(object, *from, (*from)->size);
+ return object;
+}
+
+Object *newNumber(double number, GC_PARAM) {
+ Object *object = newObject(TYPE_NUMBER, GC_ROOTS);
+ object->number = number;
+ return object;
+}
+
+Object *newObjectWithString(Type type, size_t size, GC_PARAM) {
+ size = (size > sizeof (((Object *)NULL)->string))
+ ? size - sizeof (((Object *)NULL)->string)
+ : 0;
+ return memoryAllocObject(type, sizeof (Object) + size, GC_ROOTS);
+}
+
+Object *newStringWithLength(char *string, size_t length, GC_PARAM) {
+ int nEscapes = 0;
+
+ for (int i = 1; i < length; ++i)
+ if (string[i - 1] == '\\' && strchr("\\\"trn", string[i]))
+ ++i, ++nEscapes;
+
+ Object *object = newObjectWithString(TYPE_STRING,
+ length - nEscapes + 1, GC_ROOTS);
+
+ for (int r = 1, w = 0; r <= length; ++r) {
+ if (string[r - 1] == '\\' && r < length) {
+ switch (string[r]) {
+ case '\\': object->string[w++] = '\\'; r++; break;
+ case '"': object->string[w++] = '"'; r++; break;
+ case 't': object->string[w++] = '\t'; r++; break;
+ case 'r': object->string[w++] = '\r'; r++; break;
+ case 'n': object->string[w++] = '\n'; r++; break;
+ default: object->string[w++] = '\\'; break;
+ }
+ } else
+ object->string[w++] = string[r - 1];
+ }
+
+ object->string[length - nEscapes] = '\0';
+ return object;
+}
+
+Object *newString(char *string, GC_PARAM) {
+ return newStringWithLength(string, strlen(string), GC_ROOTS);
+}
+
+Object *newCons(Object **car, Object **cdr, GC_PARAM) {
+ Object *object = newObject(TYPE_CONS, GC_ROOTS);
+ object->car = *car;
+ object->cdr = *cdr;
+ return object;
+}
+
+Object *newSymbolWithLength(char *string, size_t length, GC_PARAM) {
+ for (Object *object = symbols; object != nil; object = object->cdr)
+ if (memcmp(object->car->string, string, length) == 0
+ && object->car->string[length] == '\0')
+ return object->car;
+
+ GC_TRACE(gcObject, newObjectWithString(TYPE_SYMBOL, length + 1, GC_ROOTS));
+ memcpy((*gcObject)->string, string, length);
+ (*gcObject)->string[length] = '\0';
+
+ symbols = newCons(gcObject, &symbols, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *newSymbol(char *string, GC_PARAM) {
+ return newSymbolWithLength(string, strlen(string), GC_ROOTS);
+}
+
+Object *newObjectWithClosure(Type type, Object **closure, Object **params, Object **body, Object **env, GC_PARAM) {
+ Object *list;
+
+ for (list = *params; list->type == TYPE_CONS; list = list->cdr) {
+ if (list->car->type != TYPE_SYMBOL)
+ exceptionWithObject(list->car, "is not a symbol");
+ if (list->car == nil || list->car == n || list->car == t || list->car == f)
+ exceptionWithObject(list->car, "cannot be used as a parameter");
+ }
+
+ if (list != nil && list->type != TYPE_SYMBOL)
+ exceptionWithObject(list, "is not a symbol");
+
+ Object *object = newObject(type, GC_ROOTS);
+
+ object->closure = *closure;
+ object->params = *params;
+ object->body = *body;
+ object->env = *env;
+ return object;
+}
+
+Object *newLambda(Object **closure, Object **params, Object **body, Object **env, GC_PARAM) {
+ return newObjectWithClosure(TYPE_LAMBDA, closure, params, body, env, GC_ROOTS);
+}
+
+Object *newMacro(Object **closure, Object **params, Object **body, Object **env, GC_PARAM) {
+ return newObjectWithClosure(TYPE_MACRO, closure, params, body, env, GC_ROOTS);
+}
+
+Object *newPrimitive(int primitive, char *name, GC_PARAM) {
+ Object *object = newObject(TYPE_PRIMITIVE, GC_ROOTS);
+ object->primitive = primitive;
+ object->name = name;
+ return object;
+}
+
+Object *newEnv(Object **func, Object **vals, GC_PARAM) {
+ Object *object = newObject(TYPE_ENV, GC_ROOTS);
+
+ if ((*func) == nil)
+ object->parent = object->vars = object->vals = nil;
+ else {
+ Object *param = (*func)->params, *val = *vals;
+
+ for (int nArgs = 0;; param = param->cdr, val = val->cdr, ++nArgs) {
+ if (param == nil && val == nil)
+ break;
+ else if (param != nil && param->type == TYPE_SYMBOL)
+ break;
+ else if (val != nil && val->type != TYPE_CONS)
+ exceptionWithObject(val, "is not a list");
+ else if (param == nil && val != nil)
+ exceptionWithObject(*func, "expects at most %d arguments", nArgs);
+ else if (param != nil && val == nil) {
+ for (; param->type == TYPE_CONS; param = param->cdr, ++nArgs);
+ exceptionWithObject(*func, "expects at least %d arguments", nArgs);
+ }
+ }
+
+ object->parent = (*func)->env;
+ object->vars = (*func)->params;
+ object->vals = *vals;
+ }
+
+ return object;
+}
+
+// STREAM INPUT ///////////////////////////////////////////////////////////////
+
+/* The purpose of the stream functions is to provide an abstraction over file
+ * and string inputs. In order to accommodate the REPL, we need to be able to
+ * process character special files (such as stdin) character by character and
+ * evaluate expressions as they are being entered.
+ */
+
+int streamGetc(Stream *stream) {
+ if (stream->offset >= stream->length) {
+ switch (stream->type) {
+ case STREAM_TYPE_STRING:
+ // set length if a string was given but its length has not been set
+ if (!stream->length && stream->buffer && *stream->buffer) {
+ stream->length = strlen(stream->buffer);
+ return streamGetc(stream);
+ }
+
+ return EOF;
+
+ case STREAM_TYPE_FILE:
+ // if this is the first read, try to find the size of the file
+ if (!stream->buffer) {
+ struct stat st;
+
+ if (fstat(stream->fd, &st) == -1)
+ exception("fstat() failed, %s", strerror(errno));
+
+ if (S_ISREG(st.st_mode)) {
+ stream->size = st.st_size;
+
+ if (!(stream->buffer = malloc(stream->size)))
+ exception("out of memory, %ld bytes", (long)stream->size);
+
+ stream->capacity = stream->size;
+ } else
+ stream->size = -1;
+ }
+
+ // resize buffer to nearest multiple of BUFSIZ if capacity exceeded
+ if (stream->offset >= stream->capacity) {
+ char *buffer;
+ size_t capacity = stream->offset
+ ? (stream->offset / BUFSIZ + 1) * BUFSIZ
+ : BUFSIZ;
+
+ if (!(buffer = realloc(stream->buffer, capacity)))
+ exception("out of memory, %ld bytes", (long)capacity);
+
+ stream->buffer = buffer;
+ stream->capacity = capacity;
+ }
+
+ // read until offset reached
+ while (stream->length <= stream->offset) {
+ ssize_t nbytes = read(stream->fd, stream->buffer + stream->length,
+ stream->capacity - stream->length);
+
+ if (nbytes > 0)
+ stream->length += nbytes;
+ else if (nbytes < 0 && errno != EINTR)
+ exception("read() failed, %s", strerror(errno));
+
+ if (nbytes == 0 || stream->length == stream->size) {
+ stream->type = STREAM_TYPE_STRING;
+ return streamGetc(stream);
+ }
+ }
+
+ break;
+ }
+ }
+
+ return (unsigned char)stream->buffer[stream->offset++];
+}
+
+Stream *streamSeek(Stream *stream, int offset) {
+ if (offset < 0 && -offset >= stream->offset)
+ stream->offset = 0;
+ else
+ stream->offset += offset;
+ return stream;
+}
+
+int streamPeek(Stream *stream) {
+ int ch = streamGetc(stream);
+ if (ch != EOF)
+ streamSeek(stream, -1);
+ return ch;
+}
+
+// READING S-EXPRESSIONS //////////////////////////////////////////////////////
+
+int readNext(Stream *stream) {
+ for (;;) {
+ int ch = streamGetc(stream);
+ if (ch == ';')
+ while ((ch = streamGetc(stream)) != EOF && ch != '\n');
+ if (isspace(ch))
+ continue;
+ return ch;
+ }
+}
+
+int initialReadNext(Stream *stream) {
+ for (;;) {
+ int ch = streamGetc(stream);
+ if (ch == ';' || ch == '#')
+ while ((ch = streamGetc(stream)) != EOF && ch != '\n');
+ if (isspace(ch))
+ continue;
+ return ch;
+ }
+}
+
+int peekForward(Stream *stream, bool shebang) {
+ int ch = (shebang) ? initialReadNext(stream) : readNext(stream);
+ if (ch != EOF)
+ streamSeek(stream, -1);
+ return ch;
+}
+
+int peekNext(Stream *stream) {
+ return peekForward(stream, false);
+}
+
+int readWhile(Stream *stream, int (*predicate)(int ch)) {
+ for (;;) {
+ int ch = streamPeek(stream);
+ if (!predicate(ch))
+ return ch;
+ streamGetc(stream);
+ }
+}
+
+Object *readUnary(Stream *stream, char *symbol, GC_PARAM) {
+ if (peekNext(stream) == EOF)
+ exception("unexpected end of stream in %s", symbol);
+
+ GC_TRACE(gcSymbol, newSymbol(symbol, GC_ROOTS));
+ GC_TRACE(gcObject, readExpr(stream, GC_ROOTS));
+
+ *gcObject = newCons(gcObject, &nil, GC_ROOTS);
+ *gcObject = newCons(gcSymbol, gcObject, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *readString(Stream *stream, GC_PARAM) {
+ size_t offset = stream->offset;
+
+ for (bool isEscaped = false;;) {
+ int ch = streamGetc(stream);
+ if (ch == EOF)
+ exception("unexpected end of stream in string literal \"%.*s\"",
+ (int)(stream->offset - offset), stream->buffer + offset);
+ if (ch == '"' && !isEscaped)
+ return newStringWithLength(stream->buffer + offset,
+ stream->offset - offset - 1, GC_ROOTS);
+
+ isEscaped = (ch == '\\' && !isEscaped);
+ }
+}
+
+int isSymbolChar(int ch) {
+ static const char *valid = "!#$%&*+-./:<=>?@^_~";
+ return isalnum(ch) || strchr(valid, ch);
+}
+
+Object *readNumberOrSymbol(Stream *stream, GC_PARAM) {
+ size_t offset = stream->offset;
+ int ch = streamPeek(stream);
+
+ // skip optional leading sign
+ if (ch == '+' || ch == '-') {
+ streamGetc(stream);
+ ch = streamPeek(stream);
+ }
+
+ // try to read a number in integer or decimal format
+ if (ch == '.' || isdigit(ch)) {
+ if (isdigit(ch))
+ ch = readWhile(stream, isdigit);
+ if (!isSymbolChar(ch))
+ return newNumber(strtol(stream->buffer + offset, NULL, 10), GC_ROOTS);
+ if (ch == '.') {
+ ch = streamGetc(stream);
+ if (isdigit(streamPeek(stream))) {
+ ch = readWhile(stream, isdigit);
+ if (!isSymbolChar(ch))
+ return newNumber(strtod(stream->buffer + offset, NULL), GC_ROOTS);
+ }
+ }
+ }
+
+ // non-numeric character encountered, read a symbol
+ readWhile(stream, isSymbolChar);
+ return newSymbolWithLength(stream->buffer + offset,
+ stream->offset - offset, GC_ROOTS);
+}
+
+Object *reverseList(Object *list) {
+ Object *object = nil;
+
+ while (list != nil) {
+ Object *swap = list;
+ list = list->cdr;
+ swap->cdr = object;
+ object = swap;
+ }
+
+ return object;
+}
+
+Object *readList(Stream *stream, GC_PARAM) {
+ GC_TRACE(gcList, nil);
+ GC_TRACE(gcLast, nil);
+
+ for (;;) {
+ int ch = readNext(stream);
+ if (ch == EOF)
+ exception("unexpected end of stream in list");
+ else if (ch == ')')
+ return reverseList(*gcList);
+ else if (ch == '.' && !isSymbolChar(streamPeek(stream))) {
+ if (*gcLast == nil)
+ exception("unexpected dot at start of list");
+ if ((ch = peekNext(stream)) == ')')
+ exception("expected object at end of dotted list");
+ if (!(*gcLast = readExpr(stream, GC_ROOTS)))
+ exception("unexpected end of stream in dotted list");
+ if ((ch = peekNext(stream)) != ')')
+ exception("unexpected object at end of dotted list");
+
+ readNext(stream);
+ Object *list = reverseList(*gcList);
+ (*gcList)->cdr = *gcLast;
+
+ return list;
+ } else {
+ *gcLast = readExpr(streamSeek(stream, -1), GC_ROOTS);
+ *gcList = newCons(gcLast, gcList, GC_ROOTS);
+ }
+ }
+}
+
+Object *readExpr(Stream *stream, GC_PARAM) {
+ for (;;) {
+ int ch = readNext(stream);
+ if (ch == EOF)
+ return NULL;
+ else if (ch == '\'')
+ return readUnary(stream, "quote", GC_ROOTS);
+ else if (ch == '\\')
+ return readUnary(stream, "say", GC_ROOTS);
+ else if (ch == '"')
+ return readString(stream, GC_ROOTS);
+ else if (ch == '(')
+ return readList(stream, GC_ROOTS);
+ else if (isSymbolChar(ch)
+ && (ch != '.' || isSymbolChar(streamPeek(stream))))
+ return readNumberOrSymbol(streamSeek(stream, -1), GC_ROOTS);
+ else
+ exception("unexpected character, `%c'", ch);
+ }
+}
+
+// WRITING OBJECTS ////////////////////////////////////////////////////////////
+
+void writeObject(Object *object, bool readably, FILE *file) {
+ switch (object->type) {
+#define CASE(type, ...) \
+ case type: \
+ fprintf(file, __VA_ARGS__); \
+ break
+ CASE(TYPE_NUMBER, "%g", object->number);
+ CASE(TYPE_SYMBOL, "%s", object->string);
+ CASE(TYPE_PRIMITIVE, "#<Primitive %s>", object->name);
+#undef CASE
+ case TYPE_STRING:
+ if (readably) {
+ fputc('"', file);
+ for (char *string = object->string; *string; ++string) {
+ switch (*string) {
+ case '"': fputs("\\\"", file); break;
+ case '\t': fputs("\\t", file); break;
+ case '\r': fputs("\\r", file); break;
+ case '\n': fputs("\\n", file); break;
+ case '\\': fputs("\\\\", file); break;
+ default: fputc(*string, file); break;
+ }
+ }
+ fputc('"', file);
+ } else
+ fprintf(file, "%s", object->string);
+ break;
+ case TYPE_CONS:
+ case TYPE_LAMBDA:
+ case TYPE_MACRO:
+ if (readably)
+ fputc('(', file);
+ writeObject(object->car, readably, file);
+ while (object->cdr != nil) {
+ object = object->cdr;
+ if (object->type == TYPE_CONS) {
+ fputc(' ', file);
+ writeObject(object->car, readably, file);
+ } else {
+ fputs(" . ", file);
+ writeObject(object, readably, file);
+ break;
+ }
+ }
+ if (readably)
+ fputc(')', file);
+ break;
+#define CASE(type, name, object) \
+ case type: \
+ fprintf(file, "#<%s ", name); \
+ writeObject(object, readably, file); \
+ fprintf(file, ">"); \
+ break
+ CASE(TYPE_ENV, "Env", object->vars);
+#undef CASE
+ }
+}
+
+// ENVIRONMENT ////////////////////////////////////////////////////////////////
+
+/* An environment consists of a pointer to its parent environment (if any) and
+ * two parallel lists - vars and vals.
+ *
+ * Case 1 - vars is a regular list:
+ * vars: (a b c), vals: (1 2 3) ; a = 1, b = 2, c = 3
+ *
+ * Case 2 - vars is a dotted list:
+ * vars: (a b . c), vals: (1 2) ; a = 1, b = 2, c = nil
+ * vars: (a b . c), vals: (1 2 3) ; a = 1, b = 2, c = (3)
+ * vars: (a b . c), vals: (1 2 3 4 5) ; a = 1, b = 2, c = (3 4 5)
+ *
+ * Case 3 - vars is a symbol:
+ * vars: a, vals: nil ; a = nil
+ * vars: a, vals: (1) ; a = (1)
+ * vars: a, vals: (1 2 3) ; a = (1 2 3)
+ *
+ * Case 4 - vars and vals are both nil:
+ * vars: nil, vals: nil
+ */
+
+Object *envLookup(Object *var, Object *env) {
+ for (; env != nil; env = env->parent) {
+ Object *vars = env->vars, *vals = env->vals;
+
+ for (; vars->type == TYPE_CONS; vars = vars->cdr, vals = vals->cdr)
+ if (vars->car == var)
+ return vals->car;
+
+ if (vars == var)
+ return vals;
+ }
+
+ exceptionWithObject(var, "has no value");
+}
+
+Object *envAdd(Object **var, Object **val, Object **env, GC_PARAM) {
+ GC_TRACE(gcVars, newCons(var, &nil, GC_ROOTS));
+ GC_TRACE(gcVals, newCons(val, &nil, GC_ROOTS));
+
+ (*gcVars)->cdr = (*env)->vars, (*env)->vars = *gcVars;
+ (*gcVals)->cdr = (*env)->vals, (*env)->vals = *gcVals;
+
+ return *val;
+}
+
+Object *envSet(Object **var, Object **val, Object **env, GC_PARAM) {
+ GC_TRACE(gcEnv, *env);
+
+ for (;;) {
+ Object *vars = (*gcEnv)->vars, *vals = (*gcEnv)->vals;
+
+ for (; vars->type == TYPE_CONS; vars = vars->cdr, vals = vals->cdr) {
+ if (vars->car == *var)
+ return vals->car = *val;
+ if (vars->cdr == *var)
+ return vals->cdr = *val;
+ }
+
+ if ((*gcEnv)->parent == nil)
+ return envAdd(var, val, gcEnv, GC_ROOTS);
+ else
+ *gcEnv = (*gcEnv)->parent;
+ }
+}
+
+// PRIMITIVES /////////////////////////////////////////////////////////////////
+
+Object *primitiveSpace(Object **args, GC_PARAM) {
+ return ((*args)->car == nil) ? t : f;
+}
+
+Object *primitiveAtom(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ return (first != nil && first->type != TYPE_CONS) ? t : f;
+}
+
+/* Object *primitiveEq(Object **args, GC_PARAM) {
+ * Object *first = (*args)->car, *second = (*args)->cdr->car;
+ *
+ * if (first->type == TYPE_NUMBER && second->type == TYPE_NUMBER)
+ * return (first->number == second->number) ? t : f;
+ * else if (first->type == TYPE_STRING && second->type == TYPE_STRING)
+ * return !strcmp(first->string, second->string) ? t : f;
+ * else
+ * return (first == second) ? t : f;
+ * }
+ */
+
+Object *primitiveDif(Object **args, GC_PARAM) {
+ Object *first = (*args)->car, *second = (*args)->cdr->car;
+
+ if (first->type == TYPE_NUMBER && second->type == TYPE_NUMBER)
+ return (first->number != second->number) ? t : f;
+ else if (first->type == TYPE_STRING && second->type == TYPE_STRING)
+ return strcmp(first->string, second->string) ? t : f;
+ else
+ return (first != second) ? t : f;
+}
+
+Object *primitiveCar(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ if (first == nil)
+ return nil;
+ /* else if (first->type == TYPE_CONS || first->type == TYPE_LAMBDA) */
+ else if (first->type == TYPE_CONS)
+ return first->car;
+ else
+ exceptionWithObject(first, "is not a list");
+}
+
+Object *primitiveCdr(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ if (first == nil)
+ return nil;
+ else if (first->type == TYPE_CONS)
+ return first->cdr;
+ else
+ exceptionWithObject(first, "is not a list");
+}
+
+Object *primitiveCons(Object **args, GC_PARAM) {
+ GC_TRACE(gcFirst, (*args)->car);
+ GC_TRACE(gcSecond, (*args)->cdr->car);
+
+ return newCons(gcFirst, gcSecond, GC_ROOTS);
+}
+
+Object *primitivePrint(Object **args, GC_PARAM) {
+ writeObject((*args)->car, true, stdout);
+ fputc(' ', stdout);
+ return (*args)->car;
+}
+
+Object *primitivePrinc(Object **args, GC_PARAM) {
+ writeObject((*args)->car, false, stdout);
+ fputc(' ', stdout);
+ return (*args)->car;
+}
+
+Object *primitiveNewline(Object **args, GC_PARAM) {
+ fputc('\n', stdout);
+ return n;
+}
+
+Object *primitiveRead(Object **args, GC_PARAM) {
+ Stream stream = { STREAM_TYPE_FILE, .fd = STDIN_FILENO };
+
+ fflush(stdout);
+
+ if (peekNext(&stream) == EOF) {
+ fputc('\n', stdout);
+ return n;
+ }
+
+ return readExpr(&stream, GC_ROOTS);
+}
+
+#define DEFINE_PRIMITIVE_ARITHMETIC(name, op, init) \
+ Object *name(Object **args, GC_PARAM) { \
+ if (*args == nil) \
+ return newNumber(init, GC_ROOTS); \
+ else if ((*args)->car->type != TYPE_NUMBER) \
+ exceptionWithObject((*args)->car, "is not a number"); \
+ else { \
+ Object *object, *rest; \
+ \
+ if ((*args)->cdr == nil) { \
+ object = newNumber(init, GC_ROOTS); \
+ rest = *args; \
+ } else { \
+ GC_TRACE(gcFirst, (*args)->car); \
+ object = newObjectFrom(gcFirst, GC_ROOTS); \
+ rest = (*args)->cdr; \
+ } \
+ \
+ for (; rest != nil; rest = rest->cdr) { \
+ if (rest->car->type != TYPE_NUMBER) \
+ exceptionWithObject(rest->car, "is not a number"); \
+ \
+ object->number = object->number op rest->car->number; \
+ } \
+ \
+ return object; \
+ } \
+ }
+
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveAdd, +, 0)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveSubtract, -, 0)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveMultiply, *, 1)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveDivide, /, 1)
+
+#define DEFINE_PRIMITIVE_RELATIONAL(name, op) \
+ Object *name(Object **args, GC_PARAM) { \
+ if ((*args)->car->type != TYPE_NUMBER) \
+ exceptionWithObject((*args)->car, "is not a number"); \
+ else { \
+ Object *rest = *args; \
+ bool result = true; \
+ \
+ for (; result && rest->cdr != nil; rest = rest->cdr) { \
+ if (rest->cdr->car->type != TYPE_NUMBER) \
+ exceptionWithObject(rest->cdr->car, "is not a number"); \
+ \
+ result &= rest->car->number op rest->cdr->car->number; \
+ } \
+ \
+ return result ? t : f; \
+ } \
+ }
+
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveEqual, ==) */
+DEFINE_PRIMITIVE_RELATIONAL(primitiveDifferent, !=)
+DEFINE_PRIMITIVE_RELATIONAL(primitiveLess, < )
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveLessEqual, <=) */
+DEFINE_PRIMITIVE_RELATIONAL(primitiveGreater, > )
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveGreaterEqual, >=) */
+
+typedef struct Primitive {
+ char *name;
+ int nMinArgs, nMaxArgs;
+ Object *(* eval)(Object **args, GC_PARAM);
+} Primitive;
+
+Primitive primitives[] = {
+ { "eval", 1, 1 /* special form */ },
+ { "quote", 1, 1 /* special form */ },
+ { "say", 1, 1 /* special form */ },
+ { "set", 0, -2 /* special form */ },
+ { "prog", 0, -1 /* special form */ },
+ { "progs", 1, -1 /* special form */ },
+ /* { "if", 2, 3 /\* special form *\/ }, */
+ { "cond", 0, -1 /* special form */ },
+ { "fill", 0, -1 /* special form */ },
+ { "lambda", 1, -1 /* special form */ },
+ { "macro", 1, -1 /* special form */ },
+ { "test", 0, -1 /* special form */ },
+ { "space", 1, 1, primitiveSpace },
+ { "atom", 1, 1, primitiveAtom },
+ /* { "eq", 2, 2, primitiveEq }, */
+ { "dif", 2, 2, primitiveDif },
+ { "car", 1, 1, primitiveCar },
+ { "cdr", 1, 1, primitiveCdr },
+ { "cons", 2, 2, primitiveCons },
+ { "print", 1, 1, primitivePrint },
+ { "princ", 1, 1, primitivePrinc },
+ { "newline", 0, 0, primitiveNewline },
+ { "read", 0, 0, primitiveRead },
+ { "+", 0, -1, primitiveAdd },
+ { "-", 1, -1, primitiveSubtract },
+ { "*", 0, -1, primitiveMultiply },
+ { "/", 1, -1, primitiveDivide },
+ /* { "=", 1, -1, primitiveEqual }, */
+ { "!", 1, -1, primitiveDifferent },
+ { "<", 1, -1, primitiveLess },
+ /* { "<=", 1, -1, primitiveLessEqual }, */
+ { ">", 1, -1, primitiveGreater }
+ /* { ">=", 1, -1, primitiveGreaterEqual } */
+};
+
+// Special forms handled by evalExpr. Must be in the same order as above.
+enum {
+ PRIMITIVE_EVAL,
+ PRIMITIVE_QUOTE,
+ PRIMITIVE_SAY,
+ PRIMITIVE_SET,
+ PRIMITIVE_PROG,
+ PRIMITIVE_PROGS,
+ /* PRIMITIVE_IF, */
+ PRIMITIVE_COND,
+ PRIMITIVE_FILL,
+ PRIMITIVE_LAMBDA,
+ PRIMITIVE_MACRO,
+ PRIMITIVE_TEST
+};
+
+// EVALUATION /////////////////////////////////////////////////////////////////
+
+/* Scheme-style tail recursive evaluation. evalProg, evalProgs, evalCond, and
+ * PRIMITIVE_EVAL, return the object in the tail recursive position to be
+ * evaluated by evalExpr. Macros are expanded in-place the first time they are
+ * evaluated.
+ */
+
+Object *evalSet(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else {
+ GC_TRACE(gcVar, (*args)->car);
+ GC_TRACE(gcVal, (*args)->cdr->car);
+
+ *gcVar = evalExpr(gcVar, env, GC_ROOTS);
+
+ if (*gcVar == nil || *gcVar == n || *gcVar == t || *gcVar == f)
+ exceptionWithObject(*gcVar, "is a constant and cannot be set");
+ else if ((*gcVar)->type != TYPE_SYMBOL)
+ exceptionWithObject(*gcVar, "is not an atom and cannot be set");
+
+ *gcVal = evalExpr(gcVal, env, GC_ROOTS);
+ envSet(gcVar, gcVal, env, GC_ROOTS);
+
+ if ((*args)->cdr->cdr == nil)
+ return *gcVal;
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr->cdr);
+ return evalSet(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalProg(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->cdr == nil)
+ return (*args)->car;
+ else {
+ GC_TRACE(gcObject, (*args)->car);
+ GC_TRACE(gcCdr, (*args)->cdr);
+
+ evalExpr(gcObject, env, GC_ROOTS);
+ return evalProg(gcCdr, env, GC_ROOTS);
+ }
+}
+
+Object *progs1(Object **stop, Object **body, Object **env, GC_PARAM) {
+ if (*body == nil)
+ return *stop;
+ else if ((*body)->cdr == nil)
+ return (*body)->car;
+ else {
+ GC_TRACE(gcObject, (*body)->car);
+ GC_TRACE(gcCdr, (*body)->cdr);
+
+ *gcObject = evalExpr(gcObject, env, GC_ROOTS);
+ if (*gcObject == *stop)
+ return *stop;
+
+ return progs1(stop, gcCdr, env, GC_ROOTS);
+ }
+}
+
+Object *evalProgs(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcStop, (*args)->car);
+ GC_TRACE(gcBody, (*args)->cdr);
+
+ *gcStop = evalExpr(gcStop, env, GC_ROOTS);
+
+ if ((*gcStop)->type != TYPE_SYMBOL)
+ exceptionWithObject(*gcStop, "is not a symbol");
+
+ return progs1(gcStop, gcBody, env, GC_ROOTS);
+}
+
+/* Object *evalIf(Object **args, Object **env, GC_PARAM) {
+ * GC_TRACE(gcObject, (*args)->car);
+ *
+ * if (evalExpr(gcObject, env, GC_ROOTS) != nil)
+ * return (*args)->cdr->car;
+ * else if ((*args)->cdr->cdr != nil)
+ * return (*args)->cdr->cdr->car;
+ * else
+ * return n;
+ * }
+ */
+
+Object *evalCond(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->car->type != TYPE_CONS)
+ exceptionWithObject((*args)->car, "is not a list");
+ else {
+ GC_TRACE(gcCar, (*args)->car->car);
+ GC_TRACE(gcCdr, (*args)->car->cdr);
+
+ if (*gcCdr == nil) return *gcCar;
+
+ *gcCar = evalExpr(gcCar, env, GC_ROOTS);
+ if (*gcCar == n)
+ return n;
+ else if (*gcCar != f)
+ return evalProg(gcCdr, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr);
+ return evalCond(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalFill(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->car->type != TYPE_CONS)
+ exceptionWithObject((*args)->car, "is not a list");
+ else {
+ GC_TRACE(gcCar, (*args)->car->car);
+ GC_TRACE(gcCdr, (*args)->car->cdr);
+
+ if (*gcCdr == nil) return *gcCar;
+
+ *gcCar = evalExpr(gcCar, env, GC_ROOTS);
+ if (*gcCar == n)
+ return n;
+ else if (*gcCar == f)
+ return evalProg(gcCdr, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr);
+ return evalFill(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalLambda(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcClosure, (*args)->car);
+ GC_TRACE(gcParams, (*args)->cdr->car);
+ GC_TRACE(gcBody, (*args)->cdr->cdr);
+
+ return newLambda(gcClosure, gcParams, gcBody, env, GC_ROOTS);
+}
+
+Object *evalMacro(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcClosure, (*args)->car);
+ GC_TRACE(gcParams, (*args)->cdr->car);
+ GC_TRACE(gcBody, (*args)->cdr->cdr);
+
+ return newMacro(gcClosure, gcParams, gcBody, env, GC_ROOTS);
+}
+
+Object *expandMacro(Object **macro, Object **args, GC_PARAM) {
+ GC_TRACE(gcEnv, newEnv(macro, args, GC_ROOTS));
+ GC_TRACE(gcBody, (*macro)->body);
+
+ GC_TRACE(gcObject, evalProg(gcBody, gcEnv, GC_ROOTS));
+ *gcObject = evalExpr(gcObject, gcEnv, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *expandMacroTo(Object **macro, Object **args, Object **cons, GC_PARAM) {
+ GC_TRACE(gcObject, expandMacro(macro, args, GC_ROOTS));
+
+ if ((*gcObject)->type == TYPE_CONS) {
+ (*cons)->car = (*gcObject)->car;
+ (*cons)->cdr = (*gcObject)->cdr;
+ } else {
+ (*cons)->car = newSymbol("prog", GC_ROOTS);
+ (*cons)->cdr = newCons(gcObject, &nil, GC_ROOTS);
+ }
+
+ return *cons;
+}
+
+Object *evalList(Object **args, Object **env, GC_PARAM) {
+ if ((*args)->type != TYPE_CONS)
+ return evalExpr(args, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcObject, (*args)->car);
+ GC_TRACE(gcArgs, (*args)->cdr);
+
+ *gcObject = evalExpr(gcObject, env, GC_ROOTS);
+ *gcArgs = evalList(gcArgs, env, GC_ROOTS);
+
+ return newCons(gcObject, gcArgs, GC_ROOTS);
+ }
+}
+
+Object *evalExpr(Object **object, Object **env, GC_PARAM) {
+ GC_TRACE(gcObject, *object);
+ GC_TRACE(gcEnv, *env);
+
+ GC_TRACE(gcFunc, nil);
+ GC_TRACE(gcArgs, nil);
+ GC_TRACE(gcBody, nil);
+
+ for (;;) {
+ if ((*gcObject)->type == TYPE_SYMBOL)
+ return envLookup(*gcObject, *gcEnv);
+ if ((*gcObject)->type != TYPE_CONS)
+ return *gcObject;
+
+ *gcFunc = (*gcObject)->car;
+ *gcArgs = (*gcObject)->cdr;
+
+ *gcFunc = evalExpr(gcFunc, gcEnv, GC_ROOTS);
+ *gcBody = nil;
+
+ if ((*gcFunc)->type == TYPE_LAMBDA) {
+ *gcBody = (*gcFunc)->body;
+ *gcArgs = evalList(gcArgs, gcEnv, GC_ROOTS);
+ *gcEnv = newEnv(gcFunc, gcArgs, GC_ROOTS);
+ *gcObject = evalProg(gcBody, gcEnv, GC_ROOTS);
+ } else if ((*gcFunc)->type == TYPE_MACRO) {
+ *gcObject = expandMacroTo(gcFunc, gcArgs, gcObject, GC_ROOTS);
+ } else if ((*gcFunc)->type == TYPE_PRIMITIVE) {
+ Primitive *primitive = &primitives[(*gcFunc)->primitive];
+ int nArgs = 0;
+
+ for (Object *args = *gcArgs; args != nil; args = args->cdr, nArgs++)
+ if (args->type != TYPE_CONS)
+ exceptionWithObject(args, "is not a list");
+
+ if (nArgs < primitive->nMinArgs)
+ exceptionWithObject(*gcFunc, "expects at least %d arguments",
+ primitive->nMinArgs);
+ if (nArgs > primitive->nMaxArgs && primitive->nMaxArgs >= 0)
+ exceptionWithObject(*gcFunc, "expects at most %d arguments",
+ primitive->nMaxArgs);
+ if (primitive->nMaxArgs < 0 && nArgs % -primitive->nMaxArgs)
+ exceptionWithObject(*gcFunc, "expects a multiple of %d arguments",
+ -primitive->nMaxArgs);
+
+ switch ((*gcFunc)->primitive) {
+ case PRIMITIVE_EVAL: *gcArgs = (*gcArgs)->car;
+ *gcObject = evalExpr(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_QUOTE: return (*gcArgs)->car;
+ case PRIMITIVE_SAY: *gcArgs = (*gcArgs)->car;
+ return newCons(&nil, gcArgs, GC_ROOTS);
+ case PRIMITIVE_SET: return evalSet(gcArgs, gcEnv, GC_ROOTS);
+ case PRIMITIVE_PROG: *gcObject = evalProg(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_PROGS: *gcObject = evalProgs(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ /* case PRIMITIVE_IF: *gcObject = evalIf(gcArgs, gcEnv, GC_ROOTS);
+ * break;
+ */
+ case PRIMITIVE_COND: *gcObject = evalCond(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_FILL: *gcObject = evalFill(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_LAMBDA: return evalLambda(gcObject, gcEnv, GC_ROOTS);
+ case PRIMITIVE_MACRO: return evalMacro(gcObject, gcEnv, GC_ROOTS);
+ case PRIMITIVE_TEST: return *gcObject;
+ default: *gcArgs = evalList(gcArgs, gcEnv, GC_ROOTS);
+ return primitive->eval(gcArgs, GC_ROOTS);
+ }
+ } else
+ exceptionWithObject(*gcFunc, "is not a function");
+ }
+}
+
+// STANDARD LIBRARY ///////////////////////////////////////////////////////////
+
+#define LISP(...) #__VA_ARGS__
+
+static char *stdlib = LISP(
+ (set (quote list) (lambda args args))
+
+ (set (quote defmacro) (macro (name params . body)
+ (list (quote set) (list quote name) (list (quote macro) params . body))))
+
+ (defmacro defun (name params . body)
+ (list (quote set) (list quote name) (list (quote lambda) params . body)))
+
+ (defun not (x) (fill ((dif x n) n)
+ (x t)
+ (f f)))
+
+ (defun ap (x) (not (space x)))
+
+ (defun listp (x) (not (atom x)))
+
+ (defun zerop (x) (not (! x 0)))
+
+ (defmacro and args
+ (fill ((ap args) t)
+ ((ap (cdr args)) (car args))
+ (f (list (quote fill)
+ (list (list (quote not) (car args))
+ (cons (quote and) (cdr args)))
+ (list (quote f) (quote f))))))
+
+ (defun map (func xs)
+ (fill ((ap xs) ())
+ (f (cons (func (car xs)) (map func (cdr xs))))))
+
+ (defmacro or args
+ (fill ((ap args) f)
+ (f (cons (quote fill)
+ (append (map (lambda (x) (list (list (quote not) x) x))
+ args)
+ (list (list f f)))))))
+
+ (defun consp (x) (and (listp x) (ap x)))
+
+ (defun differ (x y)
+ (fill ((or (consp x) (consp y)) (dif x y))
+ (f (or (differ (car x) (car y))
+ (differ (cdr x) (cdr y))))))
+
+ (defun nth (num xs)
+ (fill ((dif num 0) (car xs))
+ (f (nth (- num 1) (cdr xs)))))
+
+ (defun append (xs y)
+ (fill ((ap xs) y)
+ (f (cons (car xs) (append (cdr xs) y)))))
+);
+
+// MAIN ///////////////////////////////////////////////////////////////////////
+
+Object *newRootEnv(GC_PARAM) {
+ GC_TRACE(gcEnv, newEnv(&nil, &nil, GC_ROOTS));
+ GC_TRACE(gcVar, nil);
+ GC_TRACE(gcVal, nil);
+
+ // add constants
+ envSet(&nil, &nil, gcEnv, GC_ROOTS);
+ envSet(&n, &n, gcEnv, GC_ROOTS);
+ envSet(&t, &t, gcEnv, GC_ROOTS);
+ envSet(&f, &f, gcEnv, GC_ROOTS);
+
+ // add primitives
+ int nPrimitives = sizeof (primitives) / sizeof (primitives[0]);
+
+ for (int i = 0; i < nPrimitives; ++i) {
+ *gcVar = newSymbol(primitives[i].name, GC_ROOTS);
+ *gcVal = newPrimitive(i, primitives[i].name, GC_ROOTS);
+
+ envSet(gcVar, gcVal, gcEnv, GC_ROOTS);
+ }
+
+ // add standard library
+ Stream stream = { STREAM_TYPE_STRING, .buffer = stdlib };
+ GC_TRACE(gcObject, nil);
+
+ while (peekNext(&stream) != EOF) {
+ *gcObject = nil;
+ *gcObject = readExpr(&stream, GC_ROOTS);
+ evalExpr(gcObject, gcEnv, GC_ROOTS);
+ }
+
+ return *gcEnv;
+}
diff --git a/work-in-progress/liblali.h b/work-in-progress/liblali.h
new file mode 100644
index 0000000..026036a
--- /dev/null
+++ b/work-in-progress/liblali.h
@@ -0,0 +1,337 @@
+/*
+ * lali (Lali Another Lisp Implementation)
+ *
+ * Author: Daniel Cerqueira (dan.git@lispclub.com)
+ * Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+ * Version: 0.0
+ * Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+ * computer programming language
+ * Homepage: https://gitlab.com/alexandre1985/lali
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Copyright (C) 2025 Daniel Cerqueira
+ *
+ * This file is part of lali.
+ *
+ * lali 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/>.
+ *
+ *
+ * lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/>
+ * version from 2016, written by Matthias Pirstitz, which is released to public
+ * domain under Unlicense <https://unlicense.org/>.
+ */
+
+#ifndef LIBLALI_H
+#define LIBLALI_H
+
+#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+typedef struct Object Object;
+
+typedef enum Type {
+ TYPE_NUMBER,
+ TYPE_STRING,
+ TYPE_SYMBOL,
+ TYPE_CONS,
+ TYPE_LAMBDA,
+ TYPE_MACRO,
+ TYPE_PRIMITIVE,
+ TYPE_ENV
+} Type;
+
+struct Object {
+ Type type;
+ size_t size;
+ union {
+ struct { double number; }; // number
+ struct { char string[sizeof (Object *[3])]; }; // string, symbol
+ struct { Object *car, *cdr; }; // cons
+ struct { Object *closure, *params, *body, *env; }; // lambda, macro
+ struct { int primitive; char *name; }; // primitive
+ struct { Object *parent, *vars, *vals; }; // env
+ struct { Object *forward; }; // forwarding pointer
+ };
+};
+
+extern Object *symbols;
+extern Object *nil;
+extern Object *n;
+extern Object *t;
+extern Object *f;
+
+typedef enum StreamType {
+ STREAM_TYPE_STRING,
+ STREAM_TYPE_FILE
+} StreamType;
+
+typedef struct Stream {
+ StreamType type;
+ char *buffer;
+ int fd;
+ size_t length, capacity;
+ off_t offset, size;
+} Stream;
+
+typedef struct Memory {
+ size_t capacity, fromOffset, toOffset;
+ void *fromSpace, *toSpace;
+} Memory;
+
+extern jmp_buf exceptionEnv;
+
+// EXCEPTION HANDLING /////////////////////////////////////////////////////////
+
+#define exception(...) exceptionWithObject(NULL, __VA_ARGS__)
+
+#ifdef __GNUC__
+void exceptionWithObject(Object *object, char *format, ...)
+ __attribute__ ((noreturn, format(printf, 2, 3)));
+#endif
+
+void exceptionWithObject(Object *object, char *format, ...);
+
+// GARBAGE COLLECTION /////////////////////////////////////////////////////////
+
+/* This implements Cheney's copying garbage collector, with which memory is
+ * divided into two equal halves (semispaces): from- and to-space. From-space
+ * is where new objects are allocated, whereas to-space is used during garbage
+ * collection.
+ *
+ * When garbage collection is performed, objects that are still in use (live)
+ * are copied from from-space to to-space. To-space then becomes the new
+ * from-space and vice versa, thereby discarding all objects that have not
+ * been copied.
+ *
+ * Our garbage collector takes as input a list of root objects. Objects that
+ * can be reached by recursively traversing this list are considered live and
+ * will be moved to to-space. When we move an object, we must also update its
+ * pointer within the list to point to the objects new location in memory.
+ *
+ * However, this implies that our interpreter cannot use raw pointers to
+ * objects in any function that might trigger garbage collection (or risk
+ * causing a SEGV when accessing an object that has been moved). Instead,
+ * objects must be added to the list and then only accessed through the
+ * pointer inside the list.
+ *
+ * Thus, whenever we would have used a raw pointer to an object, we use a
+ * pointer to the pointer inside the list instead:
+ *
+ * function: pointer to pointer inside list (Object **)
+ * |
+ * v
+ * list of root objects: pointer to object (Object *)
+ * |
+ * v
+ * semispace: object in memory
+ *
+ * GC_ROOTS and GC_PARAM are used to pass the list from function to function.
+ *
+ * GC_TRACE adds an object to the list and declares a variable which points to
+ * the objects pointer inside the list.
+ *
+ * GC_TRACE(gcX, X): add object X to the list and declare Object **gcX
+ * to point to the pointer to X inside the list.
+ */
+
+#define GC_ROOTS gcRoots
+#define GC_PARAM Object *GC_ROOTS
+
+#define GC_PASTE1(name, id) name ## id
+#define GC_PASTE2(name, id) GC_PASTE1(name, id)
+#define GC_UNIQUE(name) GC_PASTE2(name, __LINE__)
+
+#define GC_TRACE(name, init) \
+ Object GC_UNIQUE(GC_ROOTS) = { TYPE_CONS, .car = init, .cdr = GC_ROOTS }; \
+ Object **name = &GC_UNIQUE(GC_ROOTS).car; \
+ GC_ROOTS = &GC_UNIQUE(GC_ROOTS)
+
+Object *gcMoveObject(Object *object);
+
+void gc(GC_PARAM);
+
+// MEMORY MANAGEMENT //////////////////////////////////////////////////////////
+
+size_t memoryAlign(size_t size, size_t alignment);
+
+Object *memoryAllocObject(Type type, size_t size, GC_PARAM);
+
+// CONSTRUCTING OBJECTS ///////////////////////////////////////////////////////
+
+Object *newObject(Type type, GC_PARAM);
+
+Object *newObjectFrom(Object **from, GC_PARAM);
+
+Object *newNumber(double number, GC_PARAM);
+
+Object *newObjectWithString(Type type, size_t size, GC_PARAM);
+
+Object *newStringWithLength(char *string, size_t length, GC_PARAM);
+
+Object *newString(char *string, GC_PARAM);
+
+Object *newCons(Object **car, Object **cdr, GC_PARAM);
+
+Object *newSymbolWithLength(char *string, size_t length, GC_PARAM);
+
+Object *newSymbol(char *string, GC_PARAM);
+
+Object *newObjectWithClosure(Type type, Object **closure, Object **params, Object **body, Object **env, GC_PARAM);
+
+Object *newLambda(Object **closure, Object **params, Object **body, Object **env, GC_PARAM);
+
+Object *newMacro(Object **closure, Object **params, Object **body, Object **env, GC_PARAM);
+
+Object *newPrimitive(int primitive, char *name, GC_PARAM);
+
+Object *newEnv(Object **func, Object **vals, GC_PARAM);
+
+// STREAM INPUT ///////////////////////////////////////////////////////////////
+
+/* The purpose of the stream functions is to provide an abstraction over file
+ * and string inputs. In order to accommodate the REPL, we need to be able to
+ * process character special files (such as stdin) character by character and
+ * evaluate expressions as they are being entered.
+ */
+
+int streamGetc(Stream *stream);
+
+Stream *streamSeek(Stream *stream, int offset);
+
+int streamPeek(Stream *stream);
+
+// READING S-EXPRESSIONS //////////////////////////////////////////////////////
+
+Object *readExpr(Stream *stream, GC_PARAM);
+
+int readNext(Stream *stream);
+
+int initialReadNext(Stream *stream);
+
+int peekForward(Stream *stream, bool shebang);
+
+int peekNext(Stream *stream);
+
+int readWhile(Stream *stream, int (*predicate)(int ch));
+
+Object *readUnary(Stream *stream, char *symbol, GC_PARAM);
+
+Object *readString(Stream *stream, GC_PARAM);
+
+int isSymbolChar(int ch);
+
+Object *readNumberOrSymbol(Stream *stream, GC_PARAM);
+
+Object *reverseList(Object *list);
+
+Object *readList(Stream *stream, GC_PARAM);
+
+
+// WRITING OBJECTS ////////////////////////////////////////////////////////////
+
+void writeObject(Object *object, bool readably, FILE *file);
+
+// ENVIRONMENT ////////////////////////////////////////////////////////////////
+
+/* An environment consists of a pointer to its parent environment (if any) and
+ * two parallel lists - vars and vals.
+ *
+ * Case 1 - vars is a regular list:
+ * vars: (a b c), vals: (1 2 3) ; a = 1, b = 2, c = 3
+ *
+ * Case 2 - vars is a dotted list:
+ * vars: (a b . c), vals: (1 2) ; a = 1, b = 2, c = nil
+ * vars: (a b . c), vals: (1 2 3) ; a = 1, b = 2, c = (3)
+ * vars: (a b . c), vals: (1 2 3 4 5) ; a = 1, b = 2, c = (3 4 5)
+ *
+ * Case 3 - vars is a symbol:
+ * vars: a, vals: nil ; a = nil
+ * vars: a, vals: (1) ; a = (1)
+ * vars: a, vals: (1 2 3) ; a = (1 2 3)
+ *
+ * Case 4 - vars and vals are both nil:
+ * vars: nil, vals: nil
+ */
+
+Object *envLookup(Object *var, Object *env);
+
+Object *envAdd(Object **var, Object **val, Object **env, GC_PARAM);
+
+Object *envSet(Object **var, Object **val, Object **env, GC_PARAM);
+
+// PRIMITIVES /////////////////////////////////////////////////////////////////
+
+Object *primitiveSpace(Object **args, GC_PARAM);
+
+Object *primitiveAtom(Object **args, GC_PARAM);
+
+/* Object *primitiveEq(Object **args, GC_PARAM);
+ */
+
+Object *primitiveDif(Object **args, GC_PARAM);
+
+Object *primitiveCar(Object **args, GC_PARAM);
+
+Object *primitiveCdr(Object **args, GC_PARAM);
+
+Object *primitiveCons(Object **args, GC_PARAM);
+
+Object *primitivePrint(Object **args, GC_PARAM);
+
+Object *primitivePrinc(Object **args, GC_PARAM);
+
+Object *primitiveNewline(Object **args, GC_PARAM);
+
+Object *primitiveRead(Object **args, GC_PARAM);
+
+Object *name(Object **args, GC_PARAM);
+
+
+// EVALUATION /////////////////////////////////////////////////////////////////
+
+/* Scheme-style tail recursive evaluation. evalProg, evalProgs, evalCond, and
+ * PRIMITIVE_EVAL, return the object in the tail recursive position to be
+ * evaluated by evalExpr. Macros are expanded in-place the first time they are
+ * evaluated.
+ */
+
+Object *evalExpr(Object **object, Object **env, GC_PARAM);
+
+Object *evalSet(Object **args, Object **env, GC_PARAM);
+
+Object *evalProg(Object **args, Object **env, GC_PARAM);
+
+Object *progs1(Object **stop, Object **body, Object **env, GC_PARAM);
+
+Object *evalProgs(Object **args, Object **env, GC_PARAM);
+
+Object *evalCond(Object **args, Object **env, GC_PARAM);
+
+Object *evalFill(Object **args, Object **env, GC_PARAM);
+
+Object *evalLambda(Object **args, Object **env, GC_PARAM);
+
+Object *evalMacro(Object **args, Object **env, GC_PARAM);
+
+Object *expandMacro(Object **macro, Object **args, GC_PARAM);
+
+Object *expandMacroTo(Object **macro, Object **args, Object **cons, GC_PARAM);
+
+Object *evalList(Object **args, Object **env, GC_PARAM);
+
+Object *evalRead(GC_PARAM);
+
+Object *newRootEnv(GC_PARAM);
+
+#endif