diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7103e96..745e69b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
with:
- version: 0.12.0-dev.3076+6e078883e
+ version: 0.13.0
- name: Setup Python3.10
uses: actions/setup-python@v2
@@ -27,7 +27,8 @@ jobs:
with:
path: |
~/.cache/zig
- zig-cache
+ .zig-cache
+ zig-out/lib
key: osmium-${{hashFiles('build.zig.zon')}}
- name: Run Tests
@@ -38,5 +39,6 @@ jobs:
with:
path: |
~/.cache/zig
- zig-cache
+ .zig-cache
+ zig-out/lib
key: osmium-${{hashFiles('build.zig.zon')}}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0012c0f..1c724a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-zig-cache/
+.zig-cache/
zig-out/
temp/
.vscode/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e72bfdd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
\ No newline at end of file
diff --git a/build.zig b/build.zig
index d9004fb..2cc831f 100644
--- a/build.zig
+++ b/build.zig
@@ -2,33 +2,31 @@ const std = @import("std");
const cases = @import("tests/cases.zig");
-var trace: ?bool = false;
+var trace: bool = false;
var @"enable-bench": ?bool = false;
var backend: TraceBackend = .None;
pub fn build(b: *std.Build) !void {
- const target = b.standardTargetOptions(.{});
+ const query = b.standardTargetOptionsQueryOnly(.{});
const optimize = b.standardOptimizeOption(.{});
+ // we don't support building cpython to another platform yet
+ if (!query.isNative()) {
+ @panic("cross-compilation isn't allowed");
+ }
+
const exe = b.addExecutable(.{
.name = "osmium",
- .root_source_file = .{ .path = "src/main.zig" },
- .target = target,
+ .root_source_file = b.path("src/main.zig"),
+ .target = b.graph.host,
.optimize = optimize,
});
- // Deps
- const std_extras = b.addModule("std-extras", .{
- .root_source_file = .{ .path = "src/std-extra/std.zig" },
- });
-
- exe.root_module.addImport("std-extras", std_extras);
-
trace = b.option(bool, "trace",
\\Enables tracing of the compiler using the default backend (spall)
- );
+ ) orelse false;
- if (trace) |_| {
+ if (trace) {
backend = b.option(TraceBackend, "trace-backend",
\\Switch between what backend to use. None is default.
) orelse backend;
@@ -46,8 +44,7 @@ pub fn build(b: *std.Build) !void {
exe.use_lld = use_llvm;
const exe_options = b.addOptions();
-
- exe_options.addOption(bool, "trace", trace orelse false);
+ exe_options.addOption(bool, "trace", trace);
exe_options.addOption(TraceBackend, "backend", backend);
exe_options.addOption(std.log.Level, "debug_log", debug_log);
exe_options.addOption(usize, "src_file_trimlen", std.fs.path.dirname(std.fs.path.dirname(@src().file).?).?.len);
@@ -56,8 +53,16 @@ pub fn build(b: *std.Build) !void {
const tracer_dep = b.dependency("tracer", .{});
exe.root_module.addImport("tracer", tracer_dep.module("tracer"));
- // exe.linkLibC(); // Needs libc.
+ const cpython_step = b.step("cpython", "Builds libcpython for the host");
+ const cpython_path = try generateLibPython(b, cpython_step, optimize);
+
+ exe.step.dependOn(cpython_step);
+ exe.linkLibC();
+ exe.addObjectFile(cpython_path);
+
+ const cpython_install = b.addInstallFile(cpython_path, "lib/libpython3.10.a");
+ b.getInstallStep().dependOn(&cpython_install.step);
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
@@ -72,7 +77,7 @@ pub fn build(b: *std.Build) !void {
// Generate steps
const opcode_step = b.step("opcode", "Generate opcodes");
- generateOpCode(b, opcode_step, target);
+ generateOpCode(b, opcode_step);
// Test cases
const test_step = b.step("test", "Test Osmium");
@@ -88,19 +93,55 @@ const TraceBackend = enum {
fn generateOpCode(
b: *std.Build,
step: *std.Build.Step,
- target: std.Build.ResolvedTarget,
) void {
const translator = b.addExecutable(.{
.name = "opcode2zig",
- .root_source_file = .{ .path = "./tools/opcode2zig.zig" },
- .target = target,
+ .root_source_file = b.path("tools/opcode2zig.zig"),
+ .target = b.graph.host,
.optimize = .ReleaseFast,
});
const run_cmd = b.addRunArtifact(translator);
- run_cmd.addArg("includes/opcode.h");
+ run_cmd.addArg("vendor/opcode.h");
run_cmd.addArg("src/compiler/opcodes.zig");
step.dependOn(&run_cmd.step);
}
+
+fn generateLibPython(
+ b: *std.Build,
+ step: *std.Build.Step,
+ optimize: std.builtin.OptimizeMode,
+) !std.Build.LazyPath {
+ const source = b.dependency("python", .{});
+
+ // TODO: cache properly
+ const maybe_lib_path = try b.build_root.join(b.allocator, &.{ "zig-out", "lib", "libpython3.10.a" });
+ const result = if (std.fs.accessAbsolute(maybe_lib_path, .{})) true else |_| false;
+ if (result) {
+ return b.path("zig-out/lib/libpython3.10.a");
+ }
+
+ const configure_run = std.Build.Step.Run.create(b, "cpython-configure");
+ configure_run.setCwd(source.path("."));
+ configure_run.addFileArg(source.path("configure"));
+ configure_run.addArgs(&.{
+ "--disable-shared",
+ if (optimize == .Debug) "" else "--enable-optimizations",
+ });
+
+ const make_run = std.Build.Step.Run.create(b, "cpython-make");
+ make_run.setCwd(source.path("."));
+ make_run.addArgs(&.{
+ "make", b.fmt("-j{d}", .{cpu: {
+ const cpu_set = try std.posix.sched_getaffinity(0);
+ break :cpu std.posix.CPU_COUNT(cpu_set);
+ }}),
+ });
+
+ make_run.step.dependOn(&configure_run.step);
+ step.dependOn(&make_run.step);
+
+ return source.path("libpython3.10.a");
+}
diff --git a/build.zig.zon b/build.zig.zon
index 18cf222..367fe22 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -4,8 +4,12 @@
.paths = .{""},
.dependencies = .{
.tracer = .{
- .url = "https://github.com/Rexicon226/zig-tracer/archive/8c24ad8b1767c874c926417ddccc83981d66aedb.tar.gz",
- .hash = "122012ae514d30f7304fd4e9668e6b8a20e0fa5db28c72b32b3f64e5945e5a1cd8a1",
+ .url = "https://github.com/Rexicon226/zig-tracer/archive/f0c24a3e0ecf232493ab2fadc65f06e48956ccba.tar.gz",
+ .hash = "1220857ddca6c829d4b68f35e929b32e4eee13c265bc096c634ae8b3d6da7daf34df",
+ },
+ .python = .{
+ .url = "https://github.com/python/cpython/archive/333c7dccd87c637d0b15cf81f9bbec28e39664fd.tar.gz",
+ .hash = "1220c520b358bd5e0bbcfae04b2e9e963ad3c3d7cc3b5ba24a0721e60a0123bfb1ea",
},
},
}
diff --git a/demo/print_ast.py b/demo/print_ast.py
index 788f781..e69de29 100644
--- a/demo/print_ast.py
+++ b/demo/print_ast.py
@@ -1,7 +0,0 @@
-
-import marshal
-
-filename = './demo/test.py'
-with open(filename, 'r') as f:
- bytes = marshal.load(f)
-
diff --git a/demo/show_pyc.py b/demo/show_pyc.py
index 532d79e..86f11ee 100644
--- a/demo/show_pyc.py
+++ b/demo/show_pyc.py
@@ -1,177 +1,23 @@
-# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
-# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
-
-"""
-Dump the contents of a .pyc file.
-
-The output will only be correct if run with the same version of Python that
-produced the .pyc.
-
-"""
-
-import binascii
import dis
import marshal
import struct
import sys
-import time
-import types
-
-
-def show_pyc_file(fname):
- f = open(fname, "rb")
- magic = f.read(4)
- print("magic %s" % (binascii.hexlify(magic)))
- read_date_and_size = True
- flags = struct.unpack('= 0x80:
- line_incr -= 0x100
- line_num += line_incr
- if line_num != last_line_num:
- yield (byte_num, line_num)
-
-def flag_words(flags, flag_defs):
- words = []
- for word, flag in flag_defs:
- if flag & flags:
- words.append(word)
- return ", ".join(words)
-
-def show_file(fname):
- if fname.endswith('pyc'):
- show_pyc_file(fname)
- elif fname.endswith('py'):
- show_py_file(fname)
- else:
- print("Odd file:", fname)
-
-def main(args):
- if args[0] == '-c':
- show_py_text(" ".join(args[1:]).replace(";", "\n"))
- else:
- for a in args:
- show_file(a)
-if __name__ == '__main__':
- main(sys.argv[1:])
\ No newline at end of file
+def disassemble_pyc(filename):
+ with open(filename, 'rb') as f:
+ # Read the magic number and timestamp/header
+ magic = f.read(4)
+ timestamp = f.read(4)
+ if sys.version_info >= (3, 7):
+ # Python 3.7+ includes the size of the source file in the header
+ size = f.read(4)
+ code = marshal.load(f)
+ dis.dis(code)
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print("Usage: python disassemble_pyc.py ")
+ sys.exit(1)
+
+ pyc_file = sys.argv[1]
+ disassemble_pyc(pyc_file)
diff --git a/demo/test.py b/demo/test.py
index 2170cd7..d18a9ce 100644
--- a/demo/test.py
+++ b/demo/test.py
@@ -1,5 +1,11 @@
-def a(x):
- return x * 2
+a = 1
+b = 2
+c = a + b
-b = a(10)
-print(b)
\ No newline at end of file
+print(c)
+
+a = 1
+b = a + 2
+a += 1
+
+print(a, b)
\ No newline at end of file
diff --git a/demo/test.pyc b/demo/test.pyc
deleted file mode 100644
index c4af8ca..0000000
Binary files a/demo/test.pyc and /dev/null differ
diff --git a/src/Manager.zig b/src/Manager.zig
index 43bc49e..bd46ac2 100644
--- a/src/Manager.zig
+++ b/src/Manager.zig
@@ -7,8 +7,7 @@ const Manager = @This();
const tracer = @import("tracer");
-// const Tokenizer = @import("frontend/tokenizer/Tokenizer.zig");
-// const Parser = @import("frontend/Parser.zig");
+const Python = @import("frontend/Python.zig");
const Marshal = @import("compiler/Marshal.zig");
const Vm = @import("vm/Vm.zig");
@@ -30,7 +29,30 @@ pub fn run_pyc(manager: *Manager, file_name: []const u8) !void {
defer t.end();
// Open source file.
- const source_file = try std.fs.cwd().openFile(file_name, .{});
+ const source_file = std.fs.cwd().openFile(file_name, .{}) catch |err| {
+ switch (err) {
+ error.FileNotFound => @panic("invalid file provided"),
+ else => |e| return e,
+ }
+ };
+ const source_file_size = (try source_file.stat()).size;
+ const source = try source_file.readToEndAlloc(manager.allocator, source_file_size);
+
+ // Parse the code object
+ const object = try Marshal.load(manager.allocator, source);
+
+ var vm = try Vm.init();
+ try vm.run(manager.allocator, object);
+}
+
+pub fn run_file(manager: *Manager, file_name: []const u8) !void {
+ const source_file = std.fs.cwd().openFile(file_name, .{ .lock = .exclusive }) catch |err| {
+ switch (err) {
+ error.FileNotFound => @panic("invalid file provided"),
+ else => |e| return e,
+ }
+ };
+ defer source_file.close();
const source_file_size = (try source_file.stat()).size;
@@ -42,42 +64,9 @@ pub fn run_pyc(manager: *Manager, file_name: []const u8) !void {
0,
);
- // Parse the code object
- const object = try Marshal.load(manager.allocator, source);
+ const pyc = try Python.parse(source, manager.allocator);
+ const object = try Marshal.load(manager.allocator, pyc);
var vm = try Vm.init();
try vm.run(manager.allocator, object);
}
-
-pub fn run_file(manager: *Manager, file_name: []const u8) !void {
- _ = std.ChildProcess.run(.{
- .allocator = manager.allocator,
- .argv = &.{
- "python3.10",
- "-m",
- "py_compile",
- file_name,
- },
- .cwd = ".",
- .expand_arg0 = .expand,
- }) catch @panic("failed to side-run python");
-
- // This outputs to __pycache__/file_name.cpython-310.pyc
- const output_file_name: []const u8 = name: {
- const trimmed_name: []const u8 = file_name[0 .. file_name.len - ".py".len];
- const output_file = std.fs.path.basename(trimmed_name);
-
- log.debug("Trimmed: {s}", .{trimmed_name});
-
- const output_dir = std.fs.path.dirname(trimmed_name) orelse @panic("why in root");
-
- const output_pyc = try std.fmt.allocPrint(manager.allocator, "{s}/__pycache__/{s}.cpython-310.pyc", .{ output_dir, output_file });
-
- break :name output_pyc;
- };
-
- log.debug("File: {s}", .{output_file_name});
-
- // Run python on that.
- try manager.run_pyc(output_file_name);
-}
diff --git a/src/README.md b/src/README.md
index 92e7c92..afddae7 100644
--- a/src/README.md
+++ b/src/README.md
@@ -3,13 +3,10 @@
### Directory tree
`compiler/` - Compiler source code
-basically the area of Osmium that is in charge of converting `.pyc` into a list of seed instructions for the VM.
+Compiles an AST into codeobjects of linear bytecode.
`frontend/` - Frontend source code
-for parsing and structing `.py` files into code objects.
+Parsing Python source into an AST
`vm/` - VM source code
-for executing the instructions.
-
-`std-extra` - Extra standard library modules
-for things I don't feel like PRing into the stdlib, but I still need sometimes.
+The Python Virtual Machine; runs the input bytecode
\ No newline at end of file
diff --git a/src/compiler/Marshal.zig b/src/compiler/Marshal.zig
index dd8a8f7..efe4e36 100644
--- a/src/compiler/Marshal.zig
+++ b/src/compiler/Marshal.zig
@@ -106,6 +106,22 @@ fn read_object(marshal: *Marshal) Result {
};
},
+ .TYPE_FROZENSET => {
+ const size = marshal.read_long();
+ var results = std.ArrayList(Result).init(marshal.allocator);
+ for (0..@intCast(size.Int)) |_| {
+ results.append(marshal.read_object()) catch {
+ @panic("failed to append to frozenset");
+ };
+ }
+ result = .{
+ .Set = .{
+ .set = results.toOwnedSlice() catch @panic("OOM"),
+ .frozen = true,
+ },
+ };
+ },
+
.TYPE_INT => result = marshal.read_long(),
.TYPE_NONE => result = .{ .None = {} },
@@ -226,6 +242,12 @@ pub const Result = union(enum) {
None: void,
Bool: bool,
+ /// Both frozenset and set.
+ Set: struct {
+ set: []const Result,
+ frozen: bool,
+ },
+
CodeObject: *CodeObject,
pub fn format(
@@ -355,11 +377,11 @@ fn set_version(marshal: *Marshal, magic_bytes: [4]u8) void {
}
/// Set's a bit at `offset` in `int`
-fn testBit(int: anytype, comptime offset: u8) bool {
+fn testBit(int: anytype, comptime offset: u3) bool {
const mask = @as(u8, 1) << offset;
return (int & mask) != 0;
}
-fn clearBit(int: anytype, comptime offset: u8) @TypeOf(int) {
+fn clearBit(int: anytype, comptime offset: u3) @TypeOf(int) {
return int & ~(@as(u8, 1) << offset);
}
diff --git a/src/compiler/opcodes.zig b/src/compiler/opcodes.zig
index 18d880d..f014c26 100644
--- a/src/compiler/opcodes.zig
+++ b/src/compiler/opcodes.zig
@@ -130,5 +130,5 @@ pub const OpCode = enum(u8) {
SET_UPDATE = 163,
DICT_MERGE = 164,
DICT_UPDATE = 165,
- // EXCEPT_HANDLER = 257,
+ // EXCEPT_HANDLER = 257, TODO: why is there 257 in a u8 enum?
};
diff --git a/src/frontend/Ast.zig b/src/frontend/Ast.zig
deleted file mode 100644
index e3bb2c0..0000000
--- a/src/frontend/Ast.zig
+++ /dev/null
@@ -1,258 +0,0 @@
-// Uses https://docs.python.org/3/library/ast.html
-
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-
-const AstError = error{OutOfMemory};
-
-pub const Root = union(enum) {
- /// A module is the entire contents of a single file.
- Module: struct {
- body: []Statement,
- },
-};
-
-pub const Statement = union(enum) {
- Break: void,
- Continue: void,
- Pass: void,
- Expr: Expression,
- Assign: struct {
- targets: []Expression,
- value: *Expression,
- },
- Return: struct {
- value: ?*Expression,
- },
- FunctionDef: struct {
- name: []const u8,
- body: []Expression,
- },
- If: struct {
- case: *Expression,
- body: []Statement,
- },
-
- pub fn newIf(case: *Expression, body: []Statement) Statement {
- return .{
- .If = .{
- .case = case,
- .body = body,
- },
- };
- }
-
- pub fn newAssign(targets: []Expression, value: *Expression) Statement {
- return .{
- .Assign = .{
- .targets = targets,
- .value = value,
- },
- };
- }
-
- pub fn format(
- self: Statement,
- comptime fmt: []const u8,
- _: std.fmt.FormatOptions,
- writer: anytype,
- ) !void {
- std.debug.assert(fmt.len == 0);
-
- switch (self) {
- .Break => try writer.print("BREAK", .{}),
- .Continue => try writer.print("CONTINUE", .{}),
- .Pass => try writer.print("PASS", .{}),
- .Assign => |assign| try writer.print("Assign: {}", .{assign.value}),
- .Expr => |expr| try writer.print("{}", .{expr}),
- else => try writer.print("TODO: format {s}", .{@tagName(self)}),
- }
- }
-};
-
-pub const Expression = union(enum) {
- BinOp: struct {
- left: *Expression,
- op: Op,
- right: *Expression,
- },
- Compare: struct {
- left: *Expression,
- op: CompareOp,
- right: *Expression,
- },
- UnaryOp: struct {
- op: UnaryOp,
- operand: *Expression,
- },
- Call: struct {
- func: *Expression,
- args: []Expression,
- },
- Number: struct {
- value: i32,
- },
- String: struct {
- value: []const u8,
- },
- Identifier: struct {
- name: []const u8,
- },
-
- True: void,
- False: void,
- None: void,
-
- pub fn newCall(
- func: *Expression,
- args: []Expression,
- allocator: Allocator,
- ) AstError!*Expression {
- const expr = try allocator.create(Expression);
-
- expr.* = .{
- .Call = .{
- .func = func,
- .args = args,
- },
- };
-
- return expr;
- }
-
- pub fn newIdentifer(
- name: []const u8,
- allocator: Allocator,
- ) AstError!*Expression {
- const expr = try allocator.create(Expression);
-
- expr.* = .{
- .Identifier = .{
- .name = name,
- },
- };
-
- return expr;
- }
-
- pub fn newNumber(
- val: i32,
- allocator: Allocator,
- ) AstError!*Expression {
- const expr = try allocator.create(Expression);
-
- expr.* = .{
- .Number = .{
- .value = val,
- },
- };
-
- return expr;
- }
-
- pub fn newBinOp(
- lhs: *Expression,
- op: Op,
- rhs: *Expression,
- allocator: Allocator,
- ) AstError!*Expression {
- const expr = try allocator.create(Expression);
-
- expr.* = .{
- .BinOp = .{
- .left = lhs,
- .op = op,
- .right = rhs,
- },
- };
-
- return expr;
- }
-
- pub fn newCompare(
- lhs: *Expression,
- op: CompareOp,
- rhs: *Expression,
- allocator: Allocator,
- ) AstError!*Expression {
- const expr = try allocator.create(Expression);
-
- expr.* = .{
- .Compare = .{
- .left = lhs,
- .op = op,
- .right = rhs,
- },
- };
-
- return expr;
- }
-
- pub fn newBool(
- b: bool,
- allocator: Allocator,
- ) AstError!*Expression {
- const expr = try allocator.create(Expression);
-
- expr.* = if (b)
- .True
- else
- .False;
-
- return expr;
- }
-
- pub fn format(
- self: Expression,
- comptime fmt: []const u8,
- _: std.fmt.FormatOptions,
- writer: anytype,
- ) !void {
- std.debug.assert(fmt.len == 0);
-
- switch (self) {
- .BinOp => |bin_op| try writer.print("{{lhs: {}, op: {}, rhs: {}}}", .{
- bin_op.left,
- bin_op.op,
- bin_op.right,
- }),
- .Number => |num| try writer.print("Number: {}", .{num.value}),
- .Call => |call| try writer.print("Call: {{{}, Arg Count: {}}}", .{ call.func, call.args.len }),
- .Identifier => |ident| try writer.print("Name: {s}", .{ident.name}),
- else => try writer.print("TODO: format {s}", .{@tagName(self)}),
- }
- }
-};
-
-pub const UnaryOp = enum {
- Invert,
- Not,
- UAdd,
- USub,
-};
-
-pub const Op = enum {
- Add,
- Sub,
- Mult,
- MatMult,
- Div,
- Mod,
- Pow,
- LShift,
- RShift,
- BitOr,
- BitXor,
- BitAnd,
- FloorDiv,
-};
-
-/// These compare ops and allow for boolean logic.
-pub const CompareOp = enum {
- Eq, // ==
- NotEq, // !=
- Lt, // <
- LtE, // <=
- Gt, // >
- GtE, // >=
-};
diff --git a/src/frontend/Compiler.zig b/src/frontend/Compiler.zig
deleted file mode 100644
index d2d0117..0000000
--- a/src/frontend/Compiler.zig
+++ /dev/null
@@ -1,282 +0,0 @@
-const std = @import("std");
-const Ast = @import("Ast.zig");
-
-const Allocator = std.mem.Allocator;
-
-const log = std.log.scoped(.compiler);
-
-const Compiler = @This();
-const CompilerError = error{OutOfMemory};
-
-code_object: CodeObject,
-next_label: Label,
-
-const Label = usize;
-
-pub fn init(allocator: std.mem.Allocator) Compiler {
- return .{
- .code_object = CodeObject.init(allocator),
- .next_label = 0,
- };
-}
-
-pub fn deinit(compiler: *Compiler) void {
- compiler.code_object.deinit();
-}
-
-pub fn compile_module(compiler: *Compiler, module: Ast.Root) !void {
- try compiler.compile_statements(module.Module.body);
-}
-
-fn compile_statements(compiler: *Compiler, statements: []Ast.Statement) CompilerError!void {
- for (statements) |statement| {
- try compiler.compile_statement(statement);
- }
-}
-
-fn compile_statement(compiler: *Compiler, statement: Ast.Statement) !void {
- switch (statement) {
- .Continue => try compiler.code_object.emit(.Continue),
- .Pass => try compiler.code_object.emit(.Pass),
- .Break => try compiler.code_object.emit(.Break),
-
- .Expr => |expr| {
- try compiler.compile_expression(expr);
-
- // We discard the result.
- // try compiler.code_object.emit(.Pop);
- },
-
- .Assign => |assign| {
- try compiler.compile_expression(assign.value.*);
-
- for (assign.targets) |target| {
- switch (target) {
- .Identifier => |ident| {
- const inst = Instruction.storeName(ident.name);
- try compiler.code_object.emit(inst);
- },
- else => @panic("assinging to non-ident"),
- }
- }
- },
-
- else => std.debug.panic("TODO compile_statement: {s}", .{@tagName(statement)}),
- }
-}
-
-fn compile_expression(compiler: *Compiler, expression: Ast.Expression) !void {
- switch (expression) {
- .Number => |number| {
- const inst = Instruction.loadConst(.{ .Integer = number.value });
- try compiler.code_object.emit(inst);
- },
-
- .Identifier => |ident| {
- const inst = Instruction.loadName(ident.name);
- try compiler.code_object.emit(inst);
- },
-
- .Call => |call| {
- try compiler.compile_expression(call.func.*);
-
- for (call.args) |arg| {
- try compiler.compile_expression(arg);
- }
-
- const inst = Instruction.callFunction(call.args.len);
- try compiler.code_object.emit(inst);
- },
-
- .BinOp => |bin_op| {
- try compiler.compile_expression(bin_op.left.*);
- try compiler.compile_expression(bin_op.right.*);
-
- const op: BinaryOp = switch (bin_op.op) {
- .Add => .Add,
- .Mult => .Multiply,
- .Div => .Divide,
- .Sub => .Subtract,
- else => std.debug.panic("TODO BinOp: {s}", .{@tagName(bin_op.op)}),
- };
-
- const inst = BinaryOp.newBinaryOp(op);
- try compiler.code_object.emit(inst);
- },
-
- .Compare => |compare| {
- try compiler.compile_expression(compare.left.*);
- try compiler.compile_expression(compare.right.*);
-
- const op: CompareOp = switch (compare.op) {
- .Eq => .Equal,
- .NotEq => .NotEqual,
- .Lt => .Less,
- .LtE => .LessEqual,
- .Gt => .Greater,
- .GtE => .GreaterEqual,
- };
-
- const inst = CompareOp.newCompareOp(op);
- try compiler.code_object.emit(inst);
- },
-
- .True => {
- try compiler.code_object.emit(Instruction.loadConst(.{ .Integer = 1 }));
- },
-
- .False => {
- try compiler.code_object.emit(Instruction.loadConst(.{ .Integer = 0 }));
- },
-
- else => std.debug.panic("TODO: {s}", .{@tagName(expression)}),
- }
-}
-
-pub const CodeObject = struct {
- instructions: std.ArrayList(Instruction),
-
- pub fn init(allocator: std.mem.Allocator) CodeObject {
- return .{
- .instructions = std.ArrayList(Instruction).init(allocator),
- };
- }
-
- pub fn deinit(object: *CodeObject) void {
- object.instructions.deinit();
- }
-
- pub fn emit(object: *CodeObject, instruction: Instruction) !void {
- try object.instructions.append(instruction);
- }
-
- pub fn dump(object: *CodeObject) !void {
- const log_dump = std.log.scoped(.dump);
-
- for (object.instructions.items) |inst| {
- log_dump.debug("{}", .{inst});
- }
- }
-};
-
-pub const Instruction = union(enum) {
- LoadName: struct { name: []const u8 },
- StoreName: struct { name: []const u8 },
- LoadConst: struct { value: Constant },
-
- Pop: void,
- Pass: void,
- Continue: void,
- Break: void,
-
- Jump: struct { target: Label },
- JumpIf: struct { target: Label },
- CallFunction: struct { arg_count: usize },
-
- BinaryOperation: struct { op: BinaryOp },
- UnaryOperation: struct { op: UnaryOp },
- CompareOperation: struct { op: CompareOp },
-
- ReturnValue: void,
- PushBlock: struct { start: Label, end: Label },
-
- pub fn loadConst(value: Constant) Instruction {
- return .{
- .LoadConst = .{
- .value = value,
- },
- };
- }
-
- pub fn loadName(name: []const u8) Instruction {
- return .{
- .LoadName = .{
- .name = name,
- },
- };
- }
-
- pub fn storeName(name: []const u8) Instruction {
- return .{ .StoreName = .{
- .name = name,
- } };
- }
-
- pub fn callFunction(arg_count: usize) Instruction {
- return .{
- .CallFunction = .{
- .arg_count = arg_count,
- },
- };
- }
-
- pub fn jumpIf(target: Label) Instruction {
- return .{
- .JumpIf = .{
- .target = target,
- },
- };
- }
-
- pub fn format(
- self: Instruction,
- comptime fmt: []const u8,
- _: std.fmt.FormatOptions,
- writer: anytype,
- ) !void {
- std.debug.assert(fmt.len == 0);
-
- try writer.print("{s}", .{@tagName(self)});
- }
-};
-
-pub const Constant = union(enum) {
- String: []const u8,
- Integer: i32,
-};
-
-pub const BinaryOp = enum {
- Power,
- Multiply,
- MatrixMultiply,
- Divide,
- FloorDivide,
- Modulo,
- Add,
- Subtract,
- Lshift,
- Rshift,
- And,
- Xor,
- Or,
-
- pub fn newBinaryOp(op: BinaryOp) Instruction {
- return .{
- .BinaryOperation = .{
- .op = op,
- },
- };
- }
-};
-
-pub const UnaryOp = enum {
- Not,
- Minus,
-};
-
-pub const CompareOp = enum {
- Equal,
- NotEqual,
- Less,
- LessEqual,
- Greater,
- GreaterEqual,
-
- pub fn newCompareOp(op: CompareOp) Instruction {
- return .{
- .CompareOperation = .{
- .op = op,
- },
- };
- }
-};
diff --git a/src/frontend/Parser.zig b/src/frontend/Parser.zig
deleted file mode 100644
index 3b14e89..0000000
--- a/src/frontend/Parser.zig
+++ /dev/null
@@ -1,101 +0,0 @@
-//! Inputs a list of tokens, and outputs an Ast.
-
-const std = @import("std");
-const std_extras = @import("std-extras");
-
-const assert = std.debug.assert;
-
-const Allocator = std.mem.Allocator;
-
-const Tokenizer = @import("tokenizer/Tokenizer.zig");
-const Ast = @import("Ast.zig");
-
-const Expression = Ast.Expression;
-
-const Token = Tokenizer.Token;
-
-const log = std.log.scoped(.parser);
-
-const Parser = @This();
-
-const ParserError = error{ OutOfMemory, InvalidCharacter, Overflow };
-
-index: u32 = 0,
-allocator: Allocator,
-tokens: []Token,
-
-pub fn init(allocator: Allocator) !Parser {
- return .{
- .allocator = allocator,
- .tokens = undefined,
- };
-}
-
-pub fn deinit(_: *Parser) void {}
-
-pub fn parseFile(parser: *Parser, source: [:0]const u8) !Ast.Root {
- var tokenizer = try Tokenizer.init(parser.allocator, source);
- defer tokenizer.deinit();
-
- // Tokenize the file.
- parser.tokens = try tokenizer.parse();
-
- // File: [statements] ENDMARKER
- assert(parser.tokens[parser.tokens.len - 1].kind == .endmarker);
-
- var statements = std.ArrayList(Ast.Statement).init(parser.allocator);
-
- parser.index = 0;
- while (parser.index < parser.tokens.len) {
- const token = parser.currentToken();
- if (token.kind == .endmarker) break;
-
- // const statement = parser.parseSimpleStmts(token);
- // _ = statement; // autofix
-
- // try statements.append(try parser.statement(token));
- }
-
- return .{
- .Module = .{
- .body = try statements.toOwnedSlice(),
- },
- };
-}
-
-// General Statements
-
-
-
-/// Verifies the current token is kind, and moves forwards one.
-///
-/// example:
-///```
-/// currentToken().kind == .number;
-/// nextToken().kind == .eof;
-/// eat(.number);
-/// currentToken().kind == .eof;
-/// ```
-fn eat(parser: *Parser, kind: Tokenizer.Kind) void {
- if (parser.currentToken().kind != kind) {
- std.debug.panic("invalid token eaten, found: {}", .{parser.nextToken().kind});
- }
- parser.index += 1;
- if (parser.index >= parser.tokens.len) {
- std.debug.panic("skip caused unexpected eof", .{});
- }
-}
-
-/// Does not advanced, merely peaks
-fn currentToken(parser: *Parser) Token {
- return parser.tokens[parser.index];
-}
-
-/// Does not advanced, merely peaks
-fn nextToken(parser: *Parser) Token {
- return parser.tokens[parser.index + 1];
-}
-
-fn printCurrent(parser: *Parser) void {
- log.debug("Current: {}", .{parser.nextToken().kind});
-}
diff --git a/src/frontend/Python.zig b/src/frontend/Python.zig
new file mode 100644
index 0000000..5094e90
--- /dev/null
+++ b/src/frontend/Python.zig
@@ -0,0 +1,55 @@
+//! Inputs python source and outputs Bytecode
+
+pub fn parse(source: [:0]const u8, allocator: std.mem.Allocator) ![]const u8 {
+ // TODO: this just causes errors for now
+ // const program = cpython.DecodeLocale(std.mem.span(std.os.argv[0]));
+ // cpython.SetProgramName(program);
+
+ cpython.Initialize();
+
+ const compiled = cpython.CompileString(source, "");
+ if (null == compiled) {
+ return error.FailedToCompileString;
+ }
+
+ const bytecode = cpython.Marshal_WriteObjectToString(compiled);
+ if (null == bytecode) {
+ return error.FailedToWriteObjectToString;
+ }
+
+ const size = cpython.Bytes_Size(bytecode);
+ const ptr = cpython.Bytes_AsString(bytecode);
+ if (null == ptr) {
+ return error.FailedToAsStringCode;
+ }
+
+ cpython.DecRef(bytecode);
+ cpython.Finalize();
+
+ // construct the final pyc bytes
+
+ const pyc_bytes = ptr.?[0..size];
+
+ const bytes = try allocator.alloc(u8, size + 16);
+ var fbs = std.io.fixedBufferStream(bytes);
+ const writer = fbs.writer();
+
+ try writer.writeInt(u32, MAGIC_NUMBER, .little);
+ try writer.writeByteNTimes(0, 4);
+
+ const timestamp: u32 = @intCast(std.time.timestamp());
+ try writer.writeInt(u32, timestamp, .little);
+ try writer.writeInt(u32, @intCast(source.len), .little);
+ try writer.writeAll(pyc_bytes);
+
+ return bytes;
+}
+
+const MAGIC_NUMBER: u32 = 0xa0d0d6f;
+
+const Python = @This();
+const std = @import("std");
+
+const log = std.log.scoped(.python);
+
+const cpython = @import("cpython.zig");
diff --git a/src/frontend/cpython.zig b/src/frontend/cpython.zig
new file mode 100644
index 0000000..c5dc952
--- /dev/null
+++ b/src/frontend/cpython.zig
@@ -0,0 +1,59 @@
+//! CPython bindings for compiling source code into bytecode.
+
+const std = @import("std");
+
+extern fn Py_Initialize() void;
+extern fn Py_Finalize() void;
+
+extern fn Py_DecRef(?*anyopaque) void;
+
+extern fn Py_DecodeLocale([*:0]const u8, *usize) ?[*:0]u8;
+extern fn Py_SetProgramName([*:0]const u8) void;
+
+extern fn Py_CompileString([*:0]const u8, [*:0]const u8, c_int) ?*anyopaque;
+extern fn PyMarshal_WriteObjectToString(?*anyopaque, c_int) ?*anyopaque;
+extern fn PyBytes_Size(?*anyopaque) usize;
+extern fn PyBytes_AsString(?*anyopaque) ?[*:0]u8;
+
+const Py_file_input: c_int = 257;
+const Py_MARSHAL_VERSION: c_int = 4;
+
+pub fn Initialize() void {
+ Py_Initialize();
+}
+
+pub fn Finalize() void {
+ Py_Finalize();
+}
+
+pub fn DecRef(code: ?*anyopaque) void {
+ Py_DecRef(code);
+}
+
+pub fn DecodeLocale(argv: [:0]const u8) [:0]const u8 {
+ var len: u64 = undefined;
+ if (Py_DecodeLocale(argv.ptr, &len)) |program| {
+ return program[0 .. len + 1 :0];
+ }
+ std.debug.panic("Fatal error: cannot decode {s}", .{argv});
+}
+
+pub fn SetProgramName(name: [:0]const u8) void {
+ Py_SetProgramName(name.ptr);
+}
+
+pub fn CompileString(source: [:0]const u8, filename: [:0]const u8) ?*anyopaque {
+ return Py_CompileString(source.ptr, filename.ptr, Py_file_input);
+}
+
+pub fn Marshal_WriteObjectToString(code: ?*anyopaque) ?*anyopaque {
+ return PyMarshal_WriteObjectToString(code, Py_MARSHAL_VERSION);
+}
+
+pub fn Bytes_Size(code: ?*anyopaque) usize {
+ return PyBytes_Size(code);
+}
+
+pub fn Bytes_AsString(code: ?*anyopaque) ?[*:0]u8 {
+ return PyBytes_AsString(code);
+}
diff --git a/src/frontend/new-compiler/PyObject.zig b/src/frontend/new-compiler/PyObject.zig
deleted file mode 100644
index cd99b54..0000000
--- a/src/frontend/new-compiler/PyObject.zig
+++ /dev/null
@@ -1,2 +0,0 @@
-//! The PyObject definition in Zig
-
diff --git a/src/frontend/new-compiler/SymTable.zig b/src/frontend/new-compiler/SymTable.zig
deleted file mode 100644
index 0abc8d5..0000000
--- a/src/frontend/new-compiler/SymTable.zig
+++ /dev/null
@@ -1,45 +0,0 @@
-//! The Symbol Table
-
-
-const std = @import("std");
-const PyObject = @import("PyObject.zig");
-
-/// The file currently being compiled.
-filename: PyObject,
-blocks: []PyObject,
-
-/// Current symbol table entry
-current: SymTableEntry,
-
-/// Symbol table entry for module
-top: SymTableEntry,
-
-/// The number of blocks used.
-num_blocks: u32,
-
-/// The name of the current class or NULL
-private: ?PyObject,
-
-/// Current recursion depth
-recursion_depth: u32,
-/// Recursion limit
-recursion_limit: u32,
-
-
-pub const SymTableEntry = struct {
- /// Name of the current block (string)
- name: PyObject,
-
- /// Child Blocks
- children: []PyObject,
-
- /// Location of global and nonlocal statements
- directives: []PyObject,
-
- /// Is the block nested?
- nested: bool,
- /// Are there free variables?
- free: bool = true,
- /// Do any child blocks have free variables?
- child_free: bool = true,
-};
\ No newline at end of file
diff --git a/src/frontend/tokenizer/Tokenizer.zig b/src/frontend/tokenizer/Tokenizer.zig
deleted file mode 100644
index ce43fd8..0000000
--- a/src/frontend/tokenizer/Tokenizer.zig
+++ /dev/null
@@ -1,704 +0,0 @@
-//!
-//! Tools for parsing Python 3 source code.
-//!
-
-const std = @import("std");
-const testing = std.testing;
-
-const Tokenizer = @This();
-
-const log = std.log.scoped(.tokenizer);
-
-const TokenizerError = error{ OutOfMemory, UnexpectedEOF, UnexpectedToken };
-
-allocator: std.mem.Allocator,
-tokens: Tokens,
-source: [:0]const u8,
-offset: usize = 0,
-line: usize = 0,
-column: usize = 0,
-
-/// The kind of token.
-pub const Kind = enum {
- // General
- number,
- identifier,
-
- // Whitespace
- tab,
- newline,
-
- // Keywords
- keyword_if,
- keyword_else,
- keyword_elif,
- keyword_while,
- keyword_for,
- keyword_in,
- keyword_return,
- keyword_break,
- keyword_continue,
- keyword_pass,
- keyword_def,
- keyword_class,
- keyword_as,
- keyword_with,
- keyword_assert,
- keyword_del,
- keyword_except,
- keyword_finally,
- keyword_from,
- keyword_global,
- keyword_import,
- keyword_lambda,
- keyword_nonlocal,
- keyword_raise,
- keyword_try,
- keyword_yield,
- keyword_and,
- keyword_or,
- keyword_not,
- keyword_is,
-
- // Operators
- op_plus,
- op_minus,
- op_multiply,
- op_divide,
-
- op_increment,
- op_plus_equal,
- op_equal,
- op_assign,
-
- // Symbols
- lparen,
- rparen,
- lbracket,
- rbracket,
- colon,
- comma,
- dot,
- semicolon,
- at,
-
- // Extra
- endmarker,
-};
-
-/// A token aka slice of data inside the source.
-pub const Data = []const u8;
-
-/// The token kind and data that will be used inside the MultiArrayList.
-pub const Token = struct {
- kind: Kind,
- data: Data,
-
- pub fn eql(lhs: Token, rhs: Token) bool {
- return lhs.kind == rhs.kind;
- }
-};
-
-/// The list of tokens.
-pub const Tokens = std.MultiArrayList(Token);
-
-/// The index of a token inside the MultiArrayList.
-pub const TokenIndex = usize;
-
-/// Each keyword and its corresponding token kind.
-pub const KeywordMap = std.ComptimeStringMap(Kind, .{
- .{ "if", .keyword_if },
- .{ "else", .keyword_else },
- .{ "elif", .keyword_elif },
- .{ "while", .keyword_while },
- .{ "for", .keyword_for },
- .{ "in", .keyword_in },
- .{ "return", .keyword_return },
- .{ "break", .keyword_break },
- .{ "continue", .keyword_continue },
- .{ "pass", .keyword_pass },
- .{ "def", .keyword_def },
- .{ "class", .keyword_class },
- .{ "as", .keyword_as },
- .{ "with", .keyword_with },
- .{ "assert", .keyword_assert },
- .{ "del", .keyword_del },
- .{ "except", .keyword_except },
- .{ "finally", .keyword_finally },
- .{ "from", .keyword_from },
- .{ "global", .keyword_global },
- .{ "import", .keyword_import },
- .{ "lambda", .keyword_lambda },
- .{ "nonlocal", .keyword_nonlocal },
- .{ "raise", .keyword_raise },
- .{ "try", .keyword_try },
- .{ "yield", .keyword_yield },
- .{ "and", .keyword_and },
- .{ "or", .keyword_or },
- .{ "not", .keyword_not },
- .{ "is", .keyword_is },
-});
-
-/// Each operators starting symbol.
-pub const OperatorStartMap = std.ComptimeStringMap(void, .{
- .{ "+", void },
- .{ "-", void },
- .{ "*", void },
- .{ "/", void },
- .{ "%", void },
- .{ "&", void },
- .{ "|", void },
- .{ "^", void },
- .{ "~", void },
- .{ "<", void },
- .{ ">", void },
- .{ "=", void },
- .{ "!", void },
-});
-
-/// Each symbol and its corresponding token kind.
-pub const SymbolMap = std.ComptimeStringMap(Kind, .{
- .{ "(", .lparen },
- .{ ")", .rparen },
- .{ "[", .lbracket },
- .{ "]", .rbracket },
- .{ ":", .colon },
- .{ ",", .comma },
- .{ ".", .dot },
- .{ ";", .semicolon },
- .{ "@", .at },
-});
-
-// =================================================================
-// Public functions
-// =================================================================
-
-/// Creates a new tokenizer that will tokenize the given source.
-pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) !Tokenizer {
- return .{
- .allocator = allocator,
- .source = source,
- .tokens = Tokens{},
- };
-}
-
-/// Deinitializes the tokenizer.
-pub fn deinit(tokenizer: *Tokenizer) void {
- tokenizer.tokens.deinit(tokenizer.allocator);
-}
-
-/// Parses the input, and returns a list of tokens
-pub fn parse(tokenizer: *Tokenizer) ![]Token {
- var tokens = std.ArrayList(Token).init(tokenizer.allocator);
-
- while (true) {
- const token = tokenizer.nextToken() catch |e| {
- if (e == error.UnexpectedEOF) break;
- return e;
- };
- try tokens.append(token);
- }
-
- try tokens.append(.{ .data = undefined, .kind = .endmarker });
-
- for (tokens.items) |token| {
- log.debug("Token: {}", .{token.kind});
- }
-
- return try tokens.toOwnedSlice();
-}
-
-pub fn nextToken(self: *Tokenizer) TokenizerError!Token {
- const token_id = try self.nextTokenIndex();
- return self.tokens.get(token_id);
-}
-
-/// Parses the next token index in the source.
-pub fn nextTokenIndex(self: *Tokenizer) TokenizerError!TokenIndex {
- // TODO(SeedyROM): This is bad, I should feel bad.
- if (self.checkEOF()) {
- return error.UnexpectedEOF;
- }
-
- // Ignore spaces for now.
- if (self.source[self.offset] == ' ') {
- self.offset += 1;
- return self.nextTokenIndex();
- }
-
- // If we're at a whitespace, we're parsing a whitespace
- if (std.ascii.isWhitespace(self.source[self.offset])) {
- return self.whitespace();
- }
-
- // If we're at a digit, we're parsing a number
- if (std.ascii.isDigit(self.source[self.offset]) or self.source[self.offset] == '.') {
- return self.number();
- }
-
- // If we're at a letter, we're parsing an identifier or keyword
- // TODO(SeedyROM): This isAlphabetic needs to include _
- if (std.ascii.isAlphabetic(self.source[self.offset])) {
- const ident_id = try self.identifier();
- const token_data = self.tokens.items(.data);
-
- // If the identifier is a keyword...
- if (KeywordMap.get(token_data[ident_id])) |keyword| {
- var token = self.tokens.get(ident_id);
- token.kind = keyword;
- self.tokens.set(ident_id, token);
- }
-
- return ident_id;
- }
-
- // Parse symbols
- if (SymbolMap.has(&.{self.source[self.offset]}) == true) {
- return self.symbol();
- }
-
- // Parse operators
- if (OperatorStartMap.has(&.{self.source[self.offset]}) == true) {
- return self.operator();
- }
-
- std.log.err("Unexpected token '{c}' at ({d}:{d})\n", .{ self.source[self.offset], self.line, self.column });
- return error.UnexpectedToken;
-}
-
-fn lastToken(self: *Tokenizer) TokenIndex {
- return self.tokens.len - 1;
-}
-
-pub fn checkEOF(self: *Tokenizer) bool {
- return self.offset >= self.source.len;
-}
-
-fn advance(self: *Tokenizer) void {
- if (self.source[self.offset] == '\n') {
- self.line += 1;
- self.column = 0;
- } else {
- self.column += 1;
- }
- self.offset += 1;
-}
-
-// =================================================================
-// Parsing functions
-// =================================================================
-
-/// Parses a whitespace token.
-fn whitespace(self: *Tokenizer) !TokenIndex {
- // Parse the whitespace
- const value = self.source[self.offset];
- const kind = switch (value) {
- '\t' => Kind.tab,
- '\n' => Kind.newline,
- else => return error.UnexpectedToken,
- };
-
- try self.tokens.append(self.allocator, Token{ .kind = kind, .data = self.source[self.offset .. self.offset + 1] });
- self.advance();
- return self.lastToken();
-}
-
-/// Parses a number.
-fn number(self: *Tokenizer) !TokenIndex {
- // Parse the number
- const start = self.offset;
-
- // If the number starts with a dot we're implying a 0
- if (self.source[self.offset] == '.') {
- self.advance();
- }
-
- outer: while (std.ascii.isDigit(self.source[self.offset])) {
- self.advance();
-
- // If we're starting with 0, we might be parsing a binary, octal, or hex number
- if (self.source[start] == '0') {
- // If we're parsing hex.
- if (self.source[self.offset] == 'x' or self.source[self.offset] == 'X') {
- self.advance();
-
- while (std.ascii.isHex(self.source[self.offset])) {
- self.advance();
-
- // If we're at the end of the source, break
- if (self.checkEOF()) break :outer;
- }
- }
-
- // If we're parsing binary.
- if (self.source[self.offset] == 'b' or self.source[self.offset] == 'B') {
- self.advance();
-
- while (self.source[self.offset] == '0' or self.source[self.offset] == '1') {
- self.advance();
-
- // If we're at the end of the source, break
- if (self.checkEOF()) break :outer;
- }
- }
-
- // If we're parsing octal.
- if (self.source[self.offset] == 'o' or self.source[self.offset] == 'O') {
- self.advance();
-
- while (self.source[self.offset] >= '0' and self.source[self.offset] <= '7') {
- self.advance();
-
- // If we're at the end of the source, break
- if (self.checkEOF()) break :outer;
- }
- }
- }
-
- // If we're at the end of the source, break
- if (self.checkEOF()) break;
-
- // If we get a dot, we're parsing a fractional number, just keep going
- if (self.source[self.offset] == '.') {
- self.advance();
- if (self.checkEOF()) break;
- }
- }
-
- // Create the token
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.number, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
-}
-
-/// Parses an identifier.
-fn identifier(self: *Tokenizer) !TokenIndex {
- // Parse the identifier
- const start = self.offset;
- while (std.ascii.isAlphabetic(self.source[self.offset])) {
- self.advance();
-
- // If we're at the end of the source, break
- if (self.checkEOF()) break;
- }
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.identifier, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
-}
-
-/// Parses an operator.
-// TODO(SeedyROM): Clean this up.
-fn operator(self: *Tokenizer) !TokenIndex {
- const start = self.offset;
-
- if (self.source[start] == '=') {
- self.advance();
- if (self.checkEOF()) {
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_assign, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- if (self.source[self.offset] == '=') {
- self.advance();
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_equal, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- } else {
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_assign, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
- }
-
- if (self.source[start] == '+') {
- self.advance();
- if (self.checkEOF()) {
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_plus, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- if (self.source[self.offset] == '=') {
- self.advance();
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_plus_equal, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- } else if (self.source[self.offset] == '+') {
- self.advance();
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_increment, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_plus, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- if (self.source[start] == '-') {
- self.advance();
- if (self.checkEOF()) {
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_minus, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_minus, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- if (self.source[start] == '*') {
- self.advance();
- if (self.checkEOF()) {
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_multiply, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_multiply, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- if (self.source[start] == '/') {
- self.advance();
- if (self.checkEOF()) {
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_divide, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- const data = self.source[start..self.offset];
- const token = Token{ .kind = Kind.op_divide, .data = data };
- try self.tokens.append(self.allocator, token);
- return self.lastToken();
- }
-
- return error.UnexpectedToken;
-}
-
-fn symbol(self: *Tokenizer) !TokenIndex {
- if (SymbolMap.get(&.{self.source[self.offset]})) |kind| {
- const data = self.source[self.offset .. self.offset + 1];
- const token = Token{ .kind = kind, .data = data };
- try self.tokens.append(self.allocator, token);
- self.advance();
- return self.lastToken();
- }
-
- return error.UnexpectedToken;
-}
-
-// =================================================================
-
-fn testTokenizer(allocator: std.mem.Allocator, source: []const u8, expected: []const Token) !void {
- var tokenizer = try init(allocator, source);
- defer tokenizer.deinit();
-
- const tokens = try tokenizer.parse();
- defer allocator.free(tokens);
-
- try testing.expectEqual(expected.len, tokens.len);
- for (0..tokens.len) |i| {
- const token = tokens[i];
- const expected_token = expected[i];
-
- try testing.expectEqual(expected_token.kind, token.kind);
- try testing.expectEqualStrings(expected_token.data, token.data);
- }
-}
-
-test "whole number" {
- const source = "123";
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.number, .data = source },
- },
- );
-}
-
-test "fractional number" {
- const source = "112355.123";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.number, .data = source },
- },
- );
-}
-
-test "fractional number without whole part" {
- const source = ".123";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.number, .data = source },
- },
- );
-}
-
-test "fractional number without fractional part" {
- const source = "123.";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.number, .data = source },
- },
- );
-}
-
-test "hex number" {
- const source = "0x123abc";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.number, .data = source },
- },
- );
-}
-
-test "binary number" {
- const source = "0b1010101";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.number, .data = source },
- },
- );
-}
-
-test "octal number" {
- const source = "0o1234567";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.number, .data = source },
- },
- );
-}
-
-test "identifier" {
- const source = "hello";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.identifier, .data = source },
- },
- );
-}
-
-test "tab whitespace" {
- const source = "\t";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.tab, .data = source },
- },
- );
-}
-
-test "newline whitespace" {
- const source = "\n";
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = Kind.newline, .data = source },
- },
- );
-}
-
-test "keywords" {
- for (KeywordMap.kvs) |kv| {
- const source = kv.key;
- const kind = kv.value;
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- .{ .kind = kind, .data = source },
- },
- );
- }
-}
-
-test "operators" {
- const operators: []const Token = &.{
- .{ .kind = Kind.op_plus, .data = "+" },
- .{ .kind = Kind.op_plus_equal, .data = "+=" },
- .{ .kind = Kind.op_increment, .data = "++" },
- };
-
- for (operators) |op| {
- const source = op.data;
-
- try testTokenizer(
- testing.allocator,
- source,
- &.{
- op,
- },
- );
- }
-}
-
-// TODO(Sinon): Finish this test case
-test "simple expression" {
- const source =
- \\if x == 5:
- \\ x+=15
- \\ print(x)
- ;
-
- var tokenizer = try init(testing.allocator, source);
- defer tokenizer.deinit();
-
- while (!tokenizer.checkEOF()) {
- const token_id = tokenizer.nextToken() catch |err| {
- switch (err) {
- error.UnexpectedEOF => break,
- else => {
- std.log.err("Error: {any}", .{err});
- return err;
- },
- }
- };
- _ = token_id;
- }
-}
diff --git a/src/std-extra/mem.zig b/src/std-extra/mem.zig
deleted file mode 100644
index c03995e..0000000
--- a/src/std-extra/mem.zig
+++ /dev/null
@@ -1,71 +0,0 @@
-const std = @import("std");
-
-const DelimiterType = enum { sequence, any, scalar, context };
-
-pub fn TokenIteratorContext(
- comptime T: type,
- comptime equalFn: fn (lhs: T, rhs: T) bool,
-) type {
- return struct {
- buffer: []const T,
- delimiter: T,
- index: usize,
-
- const Self = @This();
-
- /// Returns a slice of the current token, or null if tokenization is
- /// complete, and advances to the next token.
- pub fn next(self: *Self) ?[]const T {
- const result = self.peek() orelse return null;
- self.index += result.len;
- return result;
- }
-
- /// Returns a slice of the current token, or null if tokenization is
- /// complete. Does not advance to the next token.
- pub fn peek(self: *Self) ?[]const T {
- // move to beginning of token
- while (self.index < self.buffer.len and self.isDelimiter(self.index)) : (self.index += 1) {}
- const start = self.index;
- if (start == self.buffer.len) {
- return null;
- }
-
- // move to end of token
- var end = start;
- while (end < self.buffer.len and !self.isDelimiter(end)) : (end += 1) {}
-
- return self.buffer[start..end];
- }
-
- /// Returns a slice of the remaining bytes. Does not affect iterator state.
- pub fn rest(self: Self) []const T {
- // move to beginning of token
- var index: usize = self.index;
- while (index < self.buffer.len and self.isDelimiter(index)) : (index += 1) {}
- return self.buffer[index..];
- }
-
- /// Resets the iterator to the initial token.
- pub fn reset(self: *Self) void {
- self.index = 0;
- }
-
- fn isDelimiter(self: Self, index: usize) bool {
- return equalFn(self.buffer[index], self.delimiter);
- }
- };
-}
-
-pub fn tokenizeScalar(
- comptime T: type,
- buffer: []const T,
- delimiters: T,
- comptime equalFn: fn (lhs: T, rhs: T) bool,
-) TokenIteratorContext(T, equalFn) {
- return .{
- .index = 0,
- .buffer = buffer,
- .delimiter = delimiters,
- };
-}
diff --git a/src/std-extra/std.zig b/src/std-extra/std.zig
deleted file mode 100644
index e11b1ec..0000000
--- a/src/std-extra/std.zig
+++ /dev/null
@@ -1,3 +0,0 @@
-//! An standard library of extra functions I need
-
-pub const mem = @import("mem.zig");
diff --git a/src/vm/Object.zig b/src/vm/Object.zig
index e1ea4d7..a43b4e8 100644
--- a/src/vm/Object.zig
+++ b/src/vm/Object.zig
@@ -1,6 +1,6 @@
const std = @import("std");
const Vm = @import("Vm.zig");
-const builtins = @import("../builtins.zig");
+const builtins = @import("builtins.zig");
const Co = @import("../compiler/CodeObject.zig");
const BigIntConst = std.math.big.int.Const;
@@ -28,7 +28,7 @@ payload: ?(*align(blk: {
}) anyopaque),
pub const Tag = enum(usize) {
- const first_payload = @intFromEnum(Tag.none) + 1;
+ const first_payload = @intFromEnum(Tag.int);
// Note: this is the literal None type.
none,
@@ -40,6 +40,7 @@ pub const Tag = enum(usize) {
boolean,
tuple,
list,
+ set,
/// A builtin Zig defined function.
zig_function,
@@ -60,6 +61,8 @@ pub const Tag = enum(usize) {
.tuple => Payload.Tuple,
.list => Payload.List,
+ .set => Payload.Set,
+
.zig_function => Payload.ZigFunc,
.codeobject => Payload.CodeObject,
.function => Payload.PythonFunction,
@@ -95,31 +98,47 @@ pub fn get(object: *const Object, comptime t: Tag) *Data(t) {
}
pub fn getMemberFunction(object: *const Object, name: []const u8, vm: *Vm) error{OutOfMemory}!?Object {
- const member_list =
- switch (object.tag) {
+ const member_list: Payload.MemberFuncTy = switch (object.tag) {
.list => Payload.List.MemberFns,
+ .set => Payload.Set.MemberFns,
else => std.debug.panic("{s} has no member functions", .{@tagName(object.tag)}),
};
-
- inline for (member_list) |func| {
- if (std.mem.eql(u8, func[0], name)) {
- const func_ptr = func[1];
-
+ for (member_list) |func| {
+ if (std.mem.eql(u8, func.name, name)) {
+ const func_ptr = func.func;
return try Object.create(.zig_function, vm.allocator, func_ptr);
}
}
-
return null;
}
+pub fn callMemberFunction(
+ object: *const Object,
+ vm: *Vm,
+ name: []const u8,
+ args: []Object,
+ kw: ?builtins.KW_Type,
+) !void {
+ const func = try object.getMemberFunction(name, vm) orelse return error.NotAMemberFunction;
+ const func_ptr = func.get(.zig_function);
+ const self_args = try std.mem.concat(vm.allocator, Object, &.{ &.{object.*}, args });
+ try @call(.auto, func_ptr.*, .{ vm, self_args, kw });
+}
+
pub const Payload = union(enum) {
value: Value,
zig_func: ZigFunc,
tuple: Tuple,
+ set: Set,
list: List,
codeobject: CodeObject,
function: PythonFunction,
+ pub const MemberFuncTy = []const struct {
+ name: []const u8,
+ func: *const builtins.func_proto,
+ };
+
pub const Value = union(enum) {
int: BigIntManaged,
string: []const u8,
@@ -133,9 +152,8 @@ pub const Payload = union(enum) {
pub const List = struct {
list: std.ArrayListUnmanaged(Object),
- /// First arg is the List itself.
- pub const MemberFns = &.{
- .{ "append", append },
+ pub const MemberFns: MemberFuncTy = &.{
+ .{ .name = "append", .func = append },
};
fn append(vm: *Vm, args: []Object, kw: ?builtins.KW_Type) !void {
@@ -159,6 +177,47 @@ pub const Payload = union(enum) {
name: []const u8,
co: *Co,
};
+
+ pub const Set = struct {
+ set: std.AutoHashMapUnmanaged(Object, void),
+ frozen: bool,
+
+ pub const MemberFns: MemberFuncTy = &.{
+ // zig fmt: off
+ .{ .name = "update", .func = update },
+ .{ .name = "add" , .func = add },
+ // zig fmt: on
+ };
+
+ /// Appends a set or iterable object.
+ fn update(vm: *Vm, args: []Object, kw: ?builtins.KW_Type) !void {
+ if (null != kw) @panic("set.update() has no kw args");
+
+ if (args.len != 2) std.debug.panic("set.update() takes exactly 1 argument ({d} given)", .{args.len - 1});
+
+ const self = args[0].get(.set);
+ const arg = args[0];
+
+ switch (arg.tag) {
+ .set => {
+ const arg_set = args[1].get(.set).set;
+ var obj_iter = arg_set.keyIterator();
+ while (obj_iter.next()) |obj| {
+ try self.set.put(vm.allocator, obj.*, {});
+ }
+ },
+ else => std.debug.panic("can't append {s} to set", .{@tagName(arg.tag)}),
+ }
+ }
+
+ /// Appends an item.
+ fn add(vm: *Vm, args: []Object, kw: ?builtins.KW_Type) !void {
+ if (null != kw) @panic("set.add() has no kw args");
+
+ if (args.len != 2) std.debug.panic("set.add() takes exactly 1 argument ({d} given)", .{args.len - 1});
+ _ = vm;
+ }
+ };
};
pub fn format(
@@ -210,6 +269,21 @@ pub fn format(
try writer.writeAll(")");
},
+ .set => {
+ const set = object.get(.set).set;
+ var iter = set.keyIterator();
+ const set_len = set.count();
+
+ try writer.writeAll("{");
+
+ var i: u32 = 0;
+ while (iter.next()) |obj| : (i += 1){
+ try writer.print("{}", .{obj});
+ if (i < set_len - 1) try writer.writeAll(", ");
+ }
+
+ try writer.writeAll("}");
+ },
else => try writer.print("TODO: Object.format '{s}'", .{@tagName(object.tag)}),
}
diff --git a/src/vm/Vm.zig b/src/vm/Vm.zig
index 57197af..50e9fd8 100644
--- a/src/vm/Vm.zig
+++ b/src/vm/Vm.zig
@@ -16,7 +16,7 @@ const Marshal = @import("../compiler/Marshal.zig");
const Object = @import("Object.zig");
const Vm = @This();
-const builtins = @import("../builtins.zig");
+const builtins = @import("builtins.zig");
const log = std.log.scoped(.vm);
@@ -124,11 +124,14 @@ fn exec(vm: *Vm, inst: Instruction) !void {
.LOAD_FAST => try vm.execLoadFast(inst),
.BUILD_LIST => try vm.execBuildList(inst),
+ .BUILD_SET => try vm.execBuildSet(inst),
.STORE_NAME => try vm.execStoreName(inst),
.STORE_SUBSCR => try vm.execStoreSubScr(),
.STORE_FAST => try vm.execStoreFast(inst),
+ .SET_UPDATE => try vm.execSetUpdate(inst),
+
.RETURN_VALUE => try vm.execReturnValue(),
.POP_TOP => try vm.execPopTop(),
@@ -213,10 +216,23 @@ fn execReturnValue(vm: *Vm) !void {
}
fn execBuildList(vm: *Vm, inst: Instruction) !void {
+ const count = inst.extra;
+
+ if (count == 0) return;
+ _ = vm;
+
+ @panic("TODO: execBuildList count != 0");
+}
+
+fn execBuildSet(vm: *Vm, inst: Instruction) !void {
const objects = try vm.popNObjects(inst.extra);
- const list = std.ArrayListUnmanaged(Object).fromOwnedSlice(objects);
+ var list = std.AutoHashMapUnmanaged(Object, void){};
+
+ for (objects) |object| {
+ try list.put(vm.allocator, object, {});
+ }
- const val = try Object.create(.list, vm.allocator, .{ .list = list });
+ const val = try Object.create(.set, vm.allocator, .{ .set = list, .frozen = false });
try vm.stack.append(vm.allocator, val);
}
@@ -386,6 +402,17 @@ fn execStoreFast(vm: *Vm, inst: Instruction) !void {
vm.current_co.varnames[var_num] = tos;
}
+fn execSetUpdate(vm: *Vm, inst: Instruction) !void {
+ const seq = vm.stack.pop();
+ const target = vm.stack.items[vm.stack.items.len - inst.extra];
+ try target.callMemberFunction(
+ vm,
+ "update",
+ try vm.allocator.dupe(Object, &.{seq}),
+ null,
+ );
+}
+
fn execPopJump(vm: *Vm, inst: Instruction, case: bool) !void {
const tos = vm.stack.pop();
@@ -457,6 +484,18 @@ pub fn loadConst(allocator: Allocator, inst: Marshal.Result) !Object {
.CodeObject => |co| {
return Object.create(.codeobject, allocator, .{ .co = co });
},
+ .Set => |set_struct| {
+ const set = set_struct.set;
+
+ var items = std.AutoHashMapUnmanaged(Object, void){};
+ for (set) |elem| {
+ try items.put(allocator, try loadConst(allocator, elem), {});
+ }
+ return Object.create(.set, allocator, .{
+ .set = items,
+ .frozen = set_struct.frozen,
+ });
+ },
else => std.debug.panic("TODO: loadConst {s}", .{@tagName(inst)}),
}
}
diff --git a/src/builtins.zig b/src/vm/builtins.zig
similarity index 98%
rename from src/builtins.zig
rename to src/vm/builtins.zig
index 8eba331..d38d8c5 100644
--- a/src/builtins.zig
+++ b/src/vm/builtins.zig
@@ -4,9 +4,9 @@
const std = @import("std");
const tracer = @import("tracer");
-const Object = @import("vm/Object.zig");
+const Object = @import("Object.zig");
-const Vm = @import("vm/Vm.zig");
+const Vm = @import("Vm.zig");
const fatal = @import("panic.zig").fatal;
pub const KW_Type = std.StringHashMap(Object);
diff --git a/src/panic.zig b/src/vm/panic.zig
similarity index 96%
rename from src/panic.zig
rename to src/vm/panic.zig
index f879b3a..a8437b6 100644
--- a/src/panic.zig
+++ b/src/vm/panic.zig
@@ -15,5 +15,5 @@ pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
stderr.writeAll(msg) catch |err| @panic(@errorName(err));
- std.os.exit(1);
+ std.posix.exit(1);
}
diff --git a/tests/behaviour/add.py b/tests/behaviour/add.py
index 4754eba..46bfafa 100644
--- a/tests/behaviour/add.py
+++ b/tests/behaviour/add.py
@@ -1,5 +1,9 @@
a = 1
b = 2
c = a + b
-
print(c)
+
+a = 1
+b = a + 2
+a += 1
+print(a, b)
\ No newline at end of file
diff --git a/tests/behaviour/methods.py b/tests/behaviour/methods.py
index b39061e..e69de29 100644
--- a/tests/behaviour/methods.py
+++ b/tests/behaviour/methods.py
@@ -1,3 +0,0 @@
-a = [1, 2]
-a.append(3)
-print(a)
\ No newline at end of file
diff --git a/tests/cases.zig b/tests/cases.zig
index 188b344..414d044 100644
--- a/tests/cases.zig
+++ b/tests/cases.zig
@@ -1,14 +1,17 @@
const std = @import("std");
+const matrix = @import("matrix.zig");
const Build = std.Build;
const Step = Build.Step;
-const builtins = @import("builtins/builtins.zig");
-const real_cases = @import("real_cases/real_cases.zig");
-const behaviour = @import("behaviour/behaviour.zig");
+const test_dirs: []const []const u8 = &.{
+ "builtins",
+ "real_cases",
+ "behaviour",
+};
pub fn addCases(b: *Build, exe: *Step.Compile, parent_step: *Step) !void {
- parent_step.dependOn(try builtins.addCases(b, exe));
- parent_step.dependOn(try real_cases.addCases(b, exe));
- parent_step.dependOn(try behaviour.addCases(b, exe));
+ for (test_dirs) |dir| {
+ parent_step.dependOn(try matrix.addCases(b, dir, exe));
+ }
}
diff --git a/tests/matrix.zig b/tests/matrix.zig
index a15a506..ecf3ca2 100644
--- a/tests/matrix.zig
+++ b/tests/matrix.zig
@@ -3,38 +3,45 @@ const File = std.fs.File;
const Allocator = std.mem.Allocator;
const Step = std.Build.Step;
+const Run = std.Build.Step.Run;
const MatrixError = error{};
-const path_offset: []const u8 = "tests/";
+pub fn addCases(b: *std.Build, test_dir: []const u8, exe: *Step.Compile) !*Step {
+ const root_path = try b.build_root.join(b.allocator, &.{ "tests", test_dir });
-pub fn addCase(b: *std.Build, name: []const u8, exe: *Step.Compile) !*Step {
- const test_path = b.fmt("{s}{s}", .{ path_offset, name });
- const test_step = b.step(name, "");
+ const dir_step = b.step(std.fs.path.basename(test_dir), b.fmt("Tests the files in {s}", .{test_dir}));
+ const files = try getPyFilesInDir(root_path, b.allocator);
+ for (files) |test_file| {
+ const test_run = try addCase(b, test_file, exe);
+ dir_step.dependOn(&test_run.step);
+ }
+
+ return dir_step;
+}
+
+pub fn addCase(b: *std.Build, path: []const u8, exe: *Step.Compile) !*Run {
// Compare against CPython output.
const result = try std.process.Child.run(.{
.allocator = b.allocator,
.argv = &.{
"python3.10",
- test_path,
+ path,
},
- .cwd = ".",
+ .cwd = std.fs.path.dirname(path).?,
.expand_arg0 = .expand,
});
const run_cmd = b.addRunArtifact(exe);
- run_cmd.addArg(test_path);
+ run_cmd.addArg(path);
run_cmd.expectStdOutEqual(result.stdout);
-
- test_step.dependOn(&run_cmd.step);
-
- return test_step;
+ return run_cmd;
}
-pub fn getPyFilesInDir(dir_path: []const u8, ally: Allocator) ![]const []const u8 {
- var files = std.ArrayList([]const u8).init(ally);
+pub fn getPyFilesInDir(dir_path: []const u8, allocator: Allocator) ![]const []const u8 {
+ var files = std.ArrayList([]const u8).init(allocator);
defer files.deinit();
var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true });
@@ -46,7 +53,11 @@ pub fn getPyFilesInDir(dir_path: []const u8, ally: Allocator) ![]const []const u
if (!std.mem.endsWith(u8, file.name, ".py")) {
continue;
}
- try files.append(try ally.dupe(u8, file.name));
+ try files.append(try std.mem.concat(allocator, u8, &.{
+ dir_path,
+ &.{std.fs.path.sep},
+ file.name,
+ }));
}
return try files.toOwnedSlice();
diff --git a/tools/opcode2zig.zig b/tools/opcode2zig.zig
index bfd6943..28a0f8d 100644
--- a/tools/opcode2zig.zig
+++ b/tools/opcode2zig.zig
@@ -2,8 +2,6 @@
const std = @import("std");
-const allocator = std.heap.page_allocator;
-
fn usage() void {
const writer = std.io.getStdOut().writer();
@@ -15,12 +13,21 @@ fn usage() void {
writer.writeAll(usage_string) catch @panic("failed to print usage");
}
+const skip_names = std.StaticStringMap(void).initComptime(.{
+ .{ "HAVE_ARGUMENT", {} },
+});
+
pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+
+ const allocator = gpa.allocator();
+
const args = try std.process.argsAlloc(allocator);
if (args.len < 3) {
usage();
- std.os.exit(0);
+ std.posix.exit(0);
}
const file_name = args[1];
@@ -49,7 +56,7 @@ pub fn main() !void {
var out_buf = std.ArrayList(u8).init(allocator);
const writer = out_buf.writer();
defer {
- std.fs.cwd().writeFile(output_name, out_buf.items) catch @panic("fail to write out_buf");
+ std.fs.cwd().writeFile(.{ .sub_path = output_name, .data = out_buf.items }) catch @panic("fail to write out_buf");
}
try writer.print("// This file was autogenerated by tools/opcode2zig.zig\n", .{});
try writer.print("// DO NOT EDIT\n\n", .{});
@@ -84,9 +91,7 @@ pub fn main() !void {
}
if ((name == null) or (value == null)) continue;
-
- if (std.mem.eql(u8, name.?, "HAVE_ARGUMENT")) continue;
-
+ if (skip_names.get(name.?)) |_| continue;
_ = std.fmt.parseInt(u32, value.?, 10) catch continue;
try writer.print("\t{s} = {s},\n", .{ name.?, value.? });
diff --git a/vendor/README.md b/vendor/README.md
new file mode 100644
index 0000000..f09998e
--- /dev/null
+++ b/vendor/README.md
@@ -0,0 +1,3 @@
+# `vendor`
+
+These are all the different vendored files.
\ No newline at end of file
diff --git a/includes/opcode.h b/vendor/opcode.h
similarity index 100%
rename from includes/opcode.h
rename to vendor/opcode.h